/*
 * Printer routines for the CUPS scheduler.
 *
 * Copyright © 2007-2020 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <cups/dir.h>
#ifdef HAVE_APPLICATIONSERVICES_H
#  include <ApplicationServices/ApplicationServices.h>
#endif /* HAVE_APPLICATIONSERVICES_H */
#ifdef HAVE_SYS_MOUNT_H
#  include <sys/mount.h>
#endif /* HAVE_SYS_MOUNT_H */
#ifdef HAVE_SYS_STATVFS_H
#  include <sys/statvfs.h>
#elif defined(HAVE_SYS_STATFS_H)
#  include <sys/statfs.h>
#endif /* HAVE_SYS_STATVFS_H */
#ifdef HAVE_SYS_VFS_H
#  include <sys/vfs.h>
#endif /* HAVE_SYS_VFS_H */
#ifdef __APPLE__
#  include <asl.h>
#endif /* __APPLE__ */


/*
 * Local functions...
 */

static void	add_printer_defaults(cupsd_printer_t *p);
static void	add_printer_filter(cupsd_printer_t *p, mime_type_t *type,
				   const char *filter);
static void	add_printer_formats(cupsd_printer_t *p);
static int	compare_printers(void *first, void *second, void *data);
static void	delete_printer_filters(cupsd_printer_t *p);
static void	dirty_printer(cupsd_printer_t *p);
static void	load_ppd(cupsd_printer_t *p);
static ipp_t	*new_media_col(pwg_size_t *size);
static void	write_xml_string(cups_file_t *fp, const char *s);


/*
 * 'cupsdAddPrinter()' - Add a printer to the system.
 */

cupsd_printer_t *			/* O - New printer */
cupsdAddPrinter(const char *name)	/* I - Name of printer */
{
  cupsd_printer_t	*p;		/* New printer */
  char			uri[1024],	/* Printer URI */
			uuid[64];	/* Printer UUID */


 /*
  * Range check input...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddPrinter(\"%s\")", name);

 /*
  * Create a new printer entity...
  */

  if ((p = calloc(1, sizeof(cupsd_printer_t))) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_CRIT, "Unable to allocate memory for printer - %s",
                    strerror(errno));
    return (NULL);
  }

  _cupsRWInit(&p->lock);

  cupsdSetString(&p->name, name);
  cupsdSetString(&p->info, name);
  cupsdSetString(&p->hostname, ServerName);

  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
		   ServerName, RemotePort, "/printers/%s", name);
  cupsdSetString(&p->uri, uri);
  cupsdSetString(&p->uuid, httpAssembleUUID(ServerName, RemotePort, name, 0,
                                            uuid, sizeof(uuid)));
  cupsdSetDeviceURI(p, "file:///dev/null");

  p->config_time = time(NULL);
  p->state       = IPP_PRINTER_STOPPED;
  p->state_time  = time(NULL);
  p->accepting   = 0;
  p->shared      = DefaultShared;
  p->filetype    = mimeAddType(MimeDatabase, "printer", name);

  cupsdSetString(&p->job_sheets[0], "none");
  cupsdSetString(&p->job_sheets[1], "none");

  cupsdSetString(&p->error_policy, ErrorPolicy);
  cupsdSetString(&p->op_policy, DefaultPolicy);

  p->op_policy_ptr = DefaultPolicyPtr;

 /*
  * Insert the printer in the printer list alphabetically...
  */

  if (!Printers)
    Printers = cupsArrayNew(compare_printers, NULL);

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdAddPrinter: Adding %s to Printers", p->name);
  cupsArrayAdd(Printers, p);

 /*
  * Return the new printer...
  */

  return (p);
}


/*
 * 'cupsdCreateCommonData()' - Create the common printer data.
 */

void
cupsdCreateCommonData(void)
{
  int			i;		/* Looping var */
  ipp_attribute_t	*attr;		/* Attribute data */
  cups_dir_t		*dir;		/* Notifier directory */
  cups_dentry_t		*dent;		/* Notifier directory entry */
  cups_array_t		*notifiers;	/* Notifier array */
  char			filename[1024],	/* Filename */
			*notifier;	/* Current notifier */
  cupsd_policy_t	*p;		/* Current policy */
  int			k_supported;	/* Maximum file size supported */
#ifdef HAVE_STATVFS
  struct statvfs	spoolinfo;	/* FS info for spool directory */
  double		spoolsize;	/* FS size */
#elif defined(HAVE_STATFS)
  struct statfs		spoolinfo;	/* FS info for spool directory */
  double		spoolsize;	/* FS size */
#endif /* HAVE_STATVFS */
  static const char * const page_delivery[] =
		{			/* page-delivery-supported values */
		  "reverse-order",
		  "same-order"
		};
  static const char * const print_scaling[] =
		{			/* print-scaling-supported values */
		  "auto",
		  "auto-fit",
		  "fill",
		  "fit",
		  "none"
		};
  static const int number_up[] =		/* number-up-supported values */
		{ 1, 2, 4, 6, 9, 16 };
  static const char * const number_up_layout[] =
		{			/* number-up-layout-supported values */
		  "btlr",
		  "btrl",
		  "lrbt",
		  "lrtb",
		  "rlbt",
		  "rltb",
		  "tblr",
		  "tbrl"
		};
  static const int orients[4] =/* orientation-requested-supported values */
		{
		  IPP_PORTRAIT,
		  IPP_LANDSCAPE,
		  IPP_REVERSE_LANDSCAPE,
		  IPP_REVERSE_PORTRAIT
		};
  static const char * const holds[] =	/* job-hold-until-supported values */
		{
		  "no-hold",
		  "indefinite",
		  "day-time",
		  "evening",
		  "night",
		  "second-shift",
		  "third-shift",
		  "weekend"
		};
  static const char * const features[] =/* ipp-features-supported values */
		{
		  "subscription-object"
		};
  static const char * const versions[] =/* ipp-versions-supported values */
		{
		  "1.0",
		  "1.1",
		  "2.0",
		  "2.1"
		};
  static const int	ops[] =		/* operations-supported values */
		{
		  IPP_OP_PRINT_JOB,
		  IPP_OP_VALIDATE_JOB,
		  IPP_OP_CREATE_JOB,
		  IPP_OP_SEND_DOCUMENT,
		  IPP_OP_CANCEL_JOB,
		  IPP_OP_GET_JOB_ATTRIBUTES,
		  IPP_OP_GET_JOBS,
		  IPP_OP_GET_PRINTER_ATTRIBUTES,
		  IPP_OP_HOLD_JOB,
		  IPP_OP_RELEASE_JOB,
		  IPP_OP_PAUSE_PRINTER,
		  IPP_OP_RESUME_PRINTER,
		  IPP_OP_PURGE_JOBS,
		  IPP_OP_SET_PRINTER_ATTRIBUTES,
		  IPP_OP_SET_JOB_ATTRIBUTES,
		  IPP_OP_GET_PRINTER_SUPPORTED_VALUES,
		  IPP_OP_CREATE_PRINTER_SUBSCRIPTIONS,
		  IPP_OP_CREATE_JOB_SUBSCRIPTIONS,
		  IPP_OP_GET_SUBSCRIPTION_ATTRIBUTES,
		  IPP_OP_GET_SUBSCRIPTIONS,
		  IPP_OP_RENEW_SUBSCRIPTION,
		  IPP_OP_CANCEL_SUBSCRIPTION,
		  IPP_OP_GET_NOTIFICATIONS,
		  IPP_OP_ENABLE_PRINTER,
		  IPP_OP_DISABLE_PRINTER,
		  IPP_OP_HOLD_NEW_JOBS,
		  IPP_OP_RELEASE_HELD_NEW_JOBS,
		  IPP_OP_CANCEL_JOBS,
		  IPP_OP_CANCEL_MY_JOBS,
		  IPP_OP_CLOSE_JOB,
		  IPP_OP_CUPS_GET_DEFAULT,
		  IPP_OP_CUPS_GET_PRINTERS,
		  IPP_OP_CUPS_ADD_MODIFY_PRINTER,
		  IPP_OP_CUPS_DELETE_PRINTER,
		  IPP_OP_CUPS_GET_CLASSES,
		  IPP_OP_CUPS_ADD_MODIFY_CLASS,
		  IPP_OP_CUPS_DELETE_CLASS,
		  IPP_OP_CUPS_ACCEPT_JOBS,
		  IPP_OP_CUPS_REJECT_JOBS,
		  IPP_OP_CUPS_SET_DEFAULT,
		  IPP_OP_CUPS_GET_DEVICES,
		  IPP_OP_CUPS_GET_PPDS,
		  IPP_OP_CUPS_MOVE_JOB,
		  IPP_OP_CUPS_AUTHENTICATE_JOB,
		  IPP_OP_CUPS_GET_PPD,
		  IPP_OP_CUPS_GET_DOCUMENT,
		  IPP_OP_RESTART_JOB
		};
  static const char * const charsets[] =/* charset-supported values */
		{
		  "us-ascii",
		  "utf-8"
		};
  static const char * const compressions[] =
		{			/* document-compression-supported values */
		  "none"
#ifdef HAVE_LIBZ
		  ,"gzip"
#endif /* HAVE_LIBZ */
		};
  static const char * const media_col_supported[] =
		{			/* media-col-supported values */
		  "media-bottom-margin",
		  "media-left-margin",
		  "media-right-margin",
		  "media-size",
		  "media-source",
		  "media-top-margin",
		  "media-type"
		};
  static const char * const multiple_document_handling[] =
		{			/* multiple-document-handling-supported values */
		  "separate-documents-uncollated-copies",
		  "separate-documents-collated-copies"
		};
  static const char * const notify_attrs[] =
		{			/* notify-attributes-supported values */
		  "printer-state-change-time",
		  "notify-lease-expiration-time",
		  "notify-subscriber-user-name"
		};
  static const char * const notify_events[] =
		{			/* notify-events-supported values */
        	  "job-completed",
        	  "job-config-changed",
        	  "job-created",
        	  "job-progress",
        	  "job-state-changed",
        	  "job-stopped",
        	  "printer-added",
        	  "printer-changed",
        	  "printer-config-changed",
        	  "printer-deleted",
        	  "printer-finishings-changed",
        	  "printer-media-changed",
        	  "printer-modified",
        	  "printer-restarted",
        	  "printer-shutdown",
        	  "printer-state-changed",
        	  "printer-stopped",
        	  "server-audit",
        	  "server-restarted",
        	  "server-started",
        	  "server-stopped"
		};
  static const char * const job_creation[] =
		{			/* job-creation-attributes-supported */
		  "copies",
		  "finishings",
		  "finishings-col",
		  "ipp-attribute-fidelity",
		  "job-hold-until",
		  "job-name",
		  "job-priority",
		  "job-sheets",
		  "media",
		  "media-col",
		  "multiple-document-handling",
		  "number-up",
		  "number-up-layout",
		  "orientation-requested",
		  "output-bin",
		  "page-delivery",
		  "page-ranges",
		  "print-color-mode",
		  "print-quality",
		  "print-scaling",
		  "printer-resolution",
		  "sides"
		};
  static const char * const job_settable[] =
		{			/* job-settable-attributes-supported */
		  "copies",
		  "finishings",
		  "job-hold-until",
		  "job-name",
		  "job-priority",
		  "media",
		  "media-col",
		  "multiple-document-handling",
		  "number-up",
		  "output-bin",
		  "orientation-requested",
		  "page-ranges",
		  "print-color-mode",
		  "print-quality",
		  "printer-resolution",
		  "sides"
		};
  static const char * const pdf_versions[] =
		{			/* pdf-versions-supported */
		  "adobe-1.2",
		  "adobe-1.3",
		  "adobe-1.4",
		  "adobe-1.5",
		  "adobe-1.6",
		  "adobe-1.7",
		  "iso-19005-1_2005",
		  "iso-32000-1_2008",
		  "pwg-5102.3"
		};
  static const char * const printer_settable[] =
		{			/* printer-settable-attributes-supported */
		  "printer-geo-location",
		  "printer-info",
		  "printer-location",
		  "printer-organization",
		  "printer-organizational-unit"
	        };
  static const char * const which_jobs[] =
		{			/* which-jobs-supported values */
		  "completed",
		  "not-completed",
		  "aborted",
		  "all",
		  "canceled",
		  "pending",
		  "pending-held",
		  "processing",
		  "processing-stopped"
		};


  if (CommonData)
    ippDelete(CommonData);

  CommonData = ippNew();

 /*
  * Get the maximum spool size based on the size of the filesystem used for
  * the RequestRoot directory.  If the host OS doesn't support the statfs call
  * or the filesystem is larger than 2TiB, always report INT_MAX.
  */

#ifdef HAVE_STATVFS
  if (statvfs(RequestRoot, &spoolinfo))
    k_supported = INT_MAX;
  else if ((spoolsize = (double)spoolinfo.f_frsize * spoolinfo.f_blocks / 1024) >
               INT_MAX)
    k_supported = INT_MAX;
  else
    k_supported = (int)spoolsize;

#elif defined(HAVE_STATFS)
  if (statfs(RequestRoot, &spoolinfo))
    k_supported = INT_MAX;
  else if ((spoolsize = (double)spoolinfo.f_bsize * spoolinfo.f_blocks / 1024) >
               INT_MAX)
    k_supported = INT_MAX;
  else
    k_supported = (int)spoolsize;

#else
  k_supported = INT_MAX;
#endif /* HAVE_STATVFS */

 /*
  * This list of attributes is sorted to improve performance when the
  * client provides a requested-attributes attribute...
  */

  /* charset-configured */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
               "charset-configured", NULL, "utf-8");

  /* charset-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
                "charset-supported", sizeof(charsets) / sizeof(charsets[0]),
		NULL, charsets);

  /* compression-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
        	"compression-supported",
		sizeof(compressions) / sizeof(compressions[0]),
		NULL, compressions);

  /* copies-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "copies-supported", 1, MaxCopies);

  /* cups-version */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_TEXT | IPP_TAG_COPY,
               "cups-version", NULL, &CUPS_SVERSION[6]);

  /* generated-natural-language-supported (no IPP_TAG_COPY) */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE,
               "generated-natural-language-supported", NULL, DefaultLanguage);

  /* ipp-features-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features);

  /* ipp-versions-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "ipp-versions-supported", sizeof(versions) / sizeof(versions[0]),
		NULL, versions);

  /* ippget-event-life */
  ippAddInteger(CommonData, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "ippget-event-life", 15);

  /* job-cancel-after-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "job-cancel-after-supported",
              0, INT_MAX);

  /* job-creation-attributes-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "job-creation-attributes-supported",
		sizeof(job_creation) / sizeof(job_creation[0]),
		NULL, job_creation);

  /* job-hold-until-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "job-hold-until-supported", sizeof(holds) / sizeof(holds[0]),
		NULL, holds);

  /* job-ids-supported */
  ippAddBoolean(CommonData, IPP_TAG_PRINTER, "job-ids-supported", 1);

  /* job-k-octets-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "job-k-octets-supported", 0,
              k_supported);

  /* job-priority-supported */
  ippAddInteger(CommonData, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "job-priority-supported", 100);

  /* job-settable-attributes-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "job-settable-attributes-supported",
		sizeof(job_settable) / sizeof(job_settable[0]),
		NULL, job_settable);

  /* job-sheets-supported */
  if (cupsArrayCount(Banners) > 0)
  {
   /*
    * Setup the job-sheets-supported attribute...
    */

    if (Classification && !ClassifyOverride)
      attr = ippAddString(CommonData, IPP_TAG_PRINTER,
                          IPP_TAG_NAME | IPP_TAG_COPY,
                	  "job-sheets-supported", NULL, Classification);
    else
      attr = ippAddStrings(CommonData, IPP_TAG_PRINTER,
                           IPP_TAG_NAME | IPP_TAG_COPY,
                	   "job-sheets-supported", cupsArrayCount(Banners) + 1,
			   NULL, NULL);

    if (attr == NULL)
      cupsdLogMessage(CUPSD_LOG_EMERG,
                      "Unable to allocate memory for "
                      "job-sheets-supported attribute: %s!", strerror(errno));
    else if (!Classification || ClassifyOverride)
    {
      cupsd_banner_t	*banner;	/* Current banner */


      attr->values[0].string.text = _cupsStrAlloc("none");

      for (i = 1, banner = (cupsd_banner_t *)cupsArrayFirst(Banners);
	   banner;
	   i ++, banner = (cupsd_banner_t *)cupsArrayNext(Banners))
	attr->values[i].string.text = banner->name;
    }
  }
  else
    ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
                 "job-sheets-supported", NULL, "none");

  /* jpeg-k-octets-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "jpeg-k-octets-supported", 0,
              k_supported);

  /* jpeg-x-dimension-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "jpeg-x-dimension-supported", 0,
              65535);

  /* jpeg-y-dimension-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "jpeg-y-dimension-supported", 1,
              65535);

  /* media-col-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "media-col-supported",
                sizeof(media_col_supported) /
		    sizeof(media_col_supported[0]), NULL,
	        media_col_supported);

  /* multiple-document-handling-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "multiple-document-handling-supported",
                sizeof(multiple_document_handling) /
		    sizeof(multiple_document_handling[0]), NULL,
	        multiple_document_handling);

  /* multiple-document-jobs-supported */
  ippAddBoolean(CommonData, IPP_TAG_PRINTER,
                "multiple-document-jobs-supported", 1);

  /* multiple-operation-time-out */
  ippAddInteger(CommonData, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "multiple-operation-time-out", MultipleOperationTimeout);

  /* multiple-operation-time-out-action */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-operation-time-out-action", NULL, "process-job");

  /* natural-language-configured (no IPP_TAG_COPY) */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE,
               "natural-language-configured", NULL, DefaultLanguage);

  /* notify-attributes-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "notify-attributes-supported",
		(int)(sizeof(notify_attrs) / sizeof(notify_attrs[0])),
		NULL, notify_attrs);

  /* notify-lease-duration-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER,
              "notify-lease-duration-supported", 0,
	      MaxLeaseDuration ? MaxLeaseDuration : 2147483647);

  /* notify-max-events-supported */
  ippAddInteger(CommonData, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
               "notify-max-events-supported", MaxEvents);

  /* notify-events-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "notify-events-supported",
		(int)(sizeof(notify_events) / sizeof(notify_events[0])),
		NULL, notify_events);

  /* notify-pull-method-supported */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
               "notify-pull-method-supported", NULL, "ippget");

  /* notify-schemes-supported */
  snprintf(filename, sizeof(filename), "%s/notifier", ServerBin);
  if ((dir = cupsDirOpen(filename)) != NULL)
  {
    notifiers = cupsArrayNew((cups_array_func_t)strcmp, NULL);

    while ((dent = cupsDirRead(dir)) != NULL)
      if (S_ISREG(dent->fileinfo.st_mode) &&
          (dent->fileinfo.st_mode & S_IXOTH) != 0)
        cupsArrayAdd(notifiers, _cupsStrAlloc(dent->filename));

    if (cupsArrayCount(notifiers) > 0)
    {
      attr = ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
        	           "notify-schemes-supported",
			   cupsArrayCount(notifiers), NULL, NULL);

      for (i = 0, notifier = (char *)cupsArrayFirst(notifiers);
           notifier;
	   i ++, notifier = (char *)cupsArrayNext(notifiers))
	attr->values[i].string.text = notifier;
    }

    cupsArrayDelete(notifiers);
    cupsDirClose(dir);
  }

  /* number-up-supported */
  ippAddIntegers(CommonData, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                 "number-up-supported", sizeof(number_up) / sizeof(number_up[0]), number_up);

  /* number-up-layout-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "number-up-layout-supported", sizeof(number_up_layout) / sizeof(number_up_layout[0]), NULL, number_up_layout);

  /* operations-supported */
  ippAddIntegers(CommonData, IPP_TAG_PRINTER, IPP_TAG_ENUM,
                 "operations-supported", sizeof(ops) / sizeof(ops[0]), ops);

  /* orientation-requested-supported */
  ippAddIntegers(CommonData, IPP_TAG_PRINTER, IPP_TAG_ENUM,
                 "orientation-requested-supported", 4, orients);

  /* page-delivery-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "page-delivery-supported", sizeof(page_delivery) / sizeof(page_delivery[0]), NULL, page_delivery);

  /* page-ranges-supported */
  ippAddBoolean(CommonData, IPP_TAG_PRINTER, "page-ranges-supported", 1);

  /* pdf-k-octets-supported */
  ippAddRange(CommonData, IPP_TAG_PRINTER, "pdf-k-octets-supported", 0,
              k_supported);

  /* pdf-versions-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "pdf-versions-supported",
                sizeof(pdf_versions) / sizeof(pdf_versions[0]), NULL,
                pdf_versions);

  /* pdl-override-supported */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
               "pdl-override-supported", NULL, "attempted");

  /* print-scaling-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-scaling-supported", sizeof(print_scaling) / sizeof(print_scaling[0]), NULL, print_scaling);

  /* printer-get-attributes-supported */
  ippAddString(CommonData, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-get-attributes-supported", NULL, "document-format");

  /* printer-op-policy-supported */
  attr = ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
                       "printer-op-policy-supported", cupsArrayCount(Policies),
		       NULL, NULL);
  for (i = 0, p = (cupsd_policy_t *)cupsArrayFirst(Policies);
       p;
       i ++, p = (cupsd_policy_t *)cupsArrayNext(Policies))
    attr->values[i].string.text = p->name;

  /* printer-settable-attributes-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "printer-settable-attributes-supported",
		sizeof(printer_settable) / sizeof(printer_settable[0]),
		NULL, printer_settable);

  /* server-is-sharing-printers */
  ippAddBoolean(CommonData, IPP_TAG_PRINTER, "server-is-sharing-printers",
                BrowseLocalProtocols != 0 && Browsing);

  /* which-jobs-supported */
  ippAddStrings(CommonData, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
                "which-jobs-supported",
                sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
}


/*
 * 'cupsdDeleteAllPrinters()' - Delete all printers from the system.
 */

void
cupsdDeleteAllPrinters(void)
{
  cupsd_printer_t	*p;		/* Pointer to current printer/class */


  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
    p->op_policy_ptr = DefaultPolicyPtr;
    cupsdDeletePrinter(p, 0);
  }
}


/*
 * 'cupsdDeletePrinter()' - Delete a printer from the system.
 */

int					/* O - 1 if classes affected, 0 otherwise */
cupsdDeletePrinter(
    cupsd_printer_t *p,			/* I - Printer to delete */
    int             update)		/* I - Update printers.conf? */
{
  int	i,				/* Looping var */
	changed = 0;			/* Class changed? */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdDeletePrinter(p=%p(%s), update=%d)",
                  p, p->name, update);

 /*
  * Save the current position in the Printers array...
  */

  cupsArraySave(Printers);

 /*
  * Stop printing on this printer...
  */

  cupsdSetPrinterState(p, IPP_PRINTER_STOPPED, update);

  p->state = IPP_PRINTER_STOPPED;	/* Force for browsed printers */

  if (p->job)
    cupsdSetJobState(p->job, IPP_JOB_PENDING, CUPSD_JOB_FORCE,
                     update ? "Job stopped due to printer being deleted." :
		              "Job stopped.");

 /*
  * Remove the printer from the list...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdDeletePrinter: Removing %s from Printers", p->name);
  cupsArrayRemove(Printers, p);

 /*
  * If p is the default printer, assign a different one...
  */

  if (p == DefaultPrinter)
    DefaultPrinter = NULL;

 /*
  * Remove this printer from any classes...
  */

  changed = cupsdDeletePrinterFromClasses(p);

 /*
  * Deregister from any browse protocols...
  */

  cupsdDeregisterPrinter(p, 1);

 /*
  * Remove support files if this is a temporary queue and deregister color
  * profiles...
  */

  if (p->temporary)
  {
    char	filename[1024];		/* Script/PPD filename */

   /*
    * Remove any old PPD or script files...
    */

    snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd", ServerRoot, p->name);
    unlink(filename);
    snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd.O", ServerRoot, p->name);
    unlink(filename);

    snprintf(filename, sizeof(filename), "%s/%s.png", CacheDir, p->name);
    unlink(filename);

    snprintf(filename, sizeof(filename), "%s/%s.data", CacheDir, p->name);
    unlink(filename);

   /*
    * Unregister color profiles...
    */

    cupsdUnregisterColor(p);
  }

 /*
  * Free all memory used by the printer...
  */

  if (p->printers != NULL)
    free(p->printers);

  delete_printer_filters(p);

  for (i = 0; i < p->num_reasons; i ++)
    _cupsStrFree(p->reasons[i]);

  ippDelete(p->attrs);
  ippDelete(p->ppd_attrs);

  _ppdCacheDestroy(p->pc);

  mimeDeleteType(MimeDatabase, p->filetype);
  mimeDeleteType(MimeDatabase, p->prefiltertype);

  cupsdFreeStrings(&(p->users));
  cupsdFreeQuotas(p);

  cupsdClearString(&p->uuid);
  cupsdClearString(&p->uri);
  cupsdClearString(&p->hostname);
  cupsdClearString(&p->name);
  cupsdClearString(&p->location);
  cupsdClearString(&p->geo_location);
  cupsdClearString(&p->make_model);
  cupsdClearString(&p->info);
  cupsdClearString(&p->job_sheets[0]);
  cupsdClearString(&p->job_sheets[1]);
  cupsdClearString(&p->device_uri);
  cupsdClearString(&p->sanitized_device_uri);
  cupsdClearString(&p->port_monitor);
  cupsdClearString(&p->op_policy);
  cupsdClearString(&p->error_policy);
  cupsdClearString(&p->strings);

  cupsdClearString(&p->alert);
  cupsdClearString(&p->alert_description);

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
  cupsdClearString(&p->pdl);
  cupsdClearString(&p->reg_name);
#endif /* HAVE_DNSSD || HAVE_AVAHI */

  cupsArrayDelete(p->filetypes);

  cupsFreeOptions(p->num_options, p->options);

  free(p);

 /*
  * Restore the previous position in the Printers array...
  */

  cupsArrayRestore(Printers);

  return (changed);
}


/*
 * 'cupsdDeleteTemporaryPrinters()' - Delete unneeded temporary printers.
 */

void
cupsdDeleteTemporaryPrinters(int force) /* I - Force deletion instead of auto? */
{
  cupsd_printer_t *p;                   /* Current printer */
  time_t          unused_time;          /* Last time for printer state change */


 /*
  * Allow temporary printers to stick around for 60 seconds after the last job
  * completes.
  */

  unused_time = time(NULL) - 60;

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers); p; p = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
    if (p->temporary && (force || p->state_time < unused_time))
      cupsdDeletePrinter(p, 0);
  }
}


/*
 * 'cupsdFindDest()' - Find a destination in the list.
 */

cupsd_printer_t *			/* O - Destination in list */
cupsdFindDest(const char *name)		/* I - Name of printer or class to find */
{
  cupsd_printer_t	key;		/* Search key */


  key.name = (char *)name;
  return ((cupsd_printer_t *)cupsArrayFind(Printers, &key));
}


/*
 * 'cupsdFindPrinter()' - Find a printer in the list.
 */

cupsd_printer_t *			/* O - Printer in list */
cupsdFindPrinter(const char *name)	/* I - Name of printer to find */
{
  cupsd_printer_t	*p;		/* Printer in list */


  if ((p = cupsdFindDest(name)) != NULL && (p->type & CUPS_PRINTER_CLASS))
    return (NULL);
  else
    return (p);
}


/*
 * 'cupsdLoadAllPrinters()' - Load printers from the printers.conf file.
 */

void
cupsdLoadAllPrinters(void)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* printers.conf file */
  int			linenum;	/* Current line number */
  char			line[4096],	/* Line from file */
			*value,		/* Pointer to value */
			*valueptr;	/* Pointer into value */
  cupsd_printer_t	*p;		/* Current printer */


 /*
  * Open the printers.conf file...
  */

  snprintf(line, sizeof(line), "%s/printers.conf", ServerRoot);
  if ((fp = cupsdOpenConfFile(line)) == NULL)
    return;

 /*
  * Read printer configurations until we hit EOF...
  */

  linenum = 0;
  p       = NULL;

  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
  {
   /*
    * Decode the directive...
    */

    if (!_cups_strcasecmp(line, "NextPrinterId"))
    {
      if (value && (i = atoi(value)) > 0)
        NextPrinterId = i;
      else
        cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "<Printer") || !_cups_strcasecmp(line, "<DefaultPrinter"))
    {
     /*
      * <Printer name> or <DefaultPrinter name>
      */

      if (p == NULL && value)
      {
       /*
        * Add the printer and a base file type...
	*/

        cupsdLogMessage(CUPSD_LOG_DEBUG, "Loading printer %s...", value);

        p = cupsdAddPrinter(value);
	p->accepting = 1;
	p->state     = IPP_PRINTER_IDLE;

       /*
        * Set the default printer as needed...
	*/

        if (!_cups_strcasecmp(line, "<DefaultPrinter"))
	  DefaultPrinter = p;
      }
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "</Printer>") || !_cups_strcasecmp(line, "</DefaultPrinter>"))
    {
      if (p != NULL)
      {
       /*
        * Close out the current printer...
	*/

        if (!p->printer_id)
        {
          p->printer_id = NextPrinterId ++;
          cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
	}

        cupsdSetPrinterAttrs(p);

        if (strncmp(p->device_uri, "file:", 5) && p->state != IPP_PRINTER_STOPPED)
	{
	 /*
          * See if the backend exists...
	  */

	  snprintf(line, sizeof(line), "%s/backend/%s", ServerBin, p->device_uri);

          if ((valueptr = strchr(line + strlen(ServerBin), ':')) != NULL)
	    *valueptr = '\0';		/* Chop everything but URI scheme */

          if (access(line, 0))
	  {
	   /*
	    * Backend does not exist, stop printer...
	    */

	    p->state = IPP_PRINTER_STOPPED;
	    snprintf(p->state_message, sizeof(p->state_message), "Backend %s does not exist!", line);
	  }
        }

        p = NULL;
      }
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!p)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "PrinterId"))
    {
      if (value && (i = atoi(value)) > 0)
        p->printer_id = i;
      else
        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad PrinterId on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "UUID"))
    {
      if (value && !strncmp(value, "urn:uuid:", 9))
        cupsdSetString(&(p->uuid), value);
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Bad UUID on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "AuthInfoRequired"))
    {
      if (!cupsdSetAuthInfoRequired(p, value, NULL))
	cupsdLogMessage(CUPSD_LOG_ERROR,
			"Bad AuthInfoRequired on line %d of printers.conf.",
			linenum);
    }
    else if (!_cups_strcasecmp(line, "Info"))
    {
      cupsdSetString(&p->info, value ? value : "");
    }
    else if (!_cups_strcasecmp(line, "MakeModel"))
    {
      if (value)
	cupsdSetString(&p->make_model, value);
    }
    else if (!_cups_strcasecmp(line, "Location"))
    {
      cupsdSetString(&p->location, value ? value : "");
    }
    else if (!_cups_strcasecmp(line, "GeoLocation"))
    {
      cupsdSetString(&p->geo_location, value ? value : "");
    }
    else if (!_cups_strcasecmp(line, "Organization"))
    {
      cupsdSetString(&p->organization, value ? value : "");
    }
    else if (!_cups_strcasecmp(line, "OrganizationalUnit"))
    {
      cupsdSetString(&p->organizational_unit, value ? value : "");
    }
    else if (!_cups_strcasecmp(line, "DeviceURI"))
    {
      if (value)
	cupsdSetDeviceURI(p, value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Option") && value)
    {
     /*
      * Option name value
      */

      for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);

      if (!*valueptr)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
      else
      {
        for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0');

        p->num_options = cupsAddOption(value, valueptr, p->num_options,
	                               &(p->options));
      }
    }
    else if (!_cups_strcasecmp(line, "PortMonitor"))
    {
      if (value && strcmp(value, "none"))
	cupsdSetString(&p->port_monitor, value);
      else if (value)
        cupsdClearString(&p->port_monitor);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Reason"))
    {
      if (value &&
          strcmp(value, "connecting-to-device") &&
          strcmp(value, "cups-insecure-filter-warning") &&
          strcmp(value, "cups-missing-filter-warning"))
      {
        for (i = 0 ; i < p->num_reasons; i ++)
	  if (!strcmp(value, p->reasons[i]))
	    break;

        if (i >= p->num_reasons &&
	    p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
	{
	  p->reasons[p->num_reasons] = _cupsStrAlloc(value);
	  p->num_reasons ++;
	}
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "State"))
    {
     /*
      * Set the initial queue state...
      */

      if (value && !_cups_strcasecmp(value, "idle"))
        p->state = IPP_PRINTER_IDLE;
      else if (value && !_cups_strcasecmp(value, "stopped"))
      {
        p->state = IPP_PRINTER_STOPPED;

        for (i = 0 ; i < p->num_reasons; i ++)
	  if (!strcmp("paused", p->reasons[i]))
	    break;

        if (i >= p->num_reasons &&
	    p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
	{
	  p->reasons[p->num_reasons] = _cupsStrAlloc("paused");
	  p->num_reasons ++;
	}
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "StateMessage"))
    {
     /*
      * Set the initial queue state message...
      */

      if (value)
	strlcpy(p->state_message, value, sizeof(p->state_message));
    }
    else if (!_cups_strcasecmp(line, "StateTime"))
    {
     /*
      * Set the state time...
      */

      if (value)
        p->state_time = atoi(value);
    }
    else if (!_cups_strcasecmp(line, "ConfigTime"))
    {
     /*
      * Set the config time...
      */

      if (value)
        p->config_time = atoi(value);
    }
    else if (!_cups_strcasecmp(line, "Accepting"))
    {
     /*
      * Set the initial accepting state...
      */

      if (value &&
          (!_cups_strcasecmp(value, "yes") ||
           !_cups_strcasecmp(value, "on") ||
           !_cups_strcasecmp(value, "true")))
        p->accepting = 1;
      else if (value &&
               (!_cups_strcasecmp(value, "no") ||
        	!_cups_strcasecmp(value, "off") ||
        	!_cups_strcasecmp(value, "false")))
        p->accepting = 0;
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Type"))
    {
      if (value)
        p->type = (cups_ptype_t)atoi(value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Shared"))
    {
     /*
      * Set the initial shared state...
      */

      if (value &&
          (!_cups_strcasecmp(value, "yes") ||
           !_cups_strcasecmp(value, "on") ||
           !_cups_strcasecmp(value, "true")))
        p->shared = 1;
      else if (value &&
               (!_cups_strcasecmp(value, "no") ||
        	!_cups_strcasecmp(value, "off") ||
        	!_cups_strcasecmp(value, "false")))
        p->shared = 0;
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "JobSheets"))
    {
     /*
      * Set the initial job sheets...
      */

      if (value)
      {
	for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);

	if (*valueptr)
          *valueptr++ = '\0';

	cupsdSetString(&p->job_sheets[0], value);

	while (isspace(*valueptr & 255))
          valueptr ++;

	if (*valueptr)
	{
          for (value = valueptr; *valueptr && !isspace(*valueptr & 255); valueptr ++);

	  if (*valueptr)
            *valueptr = '\0';

	  cupsdSetString(&p->job_sheets[1], value);
	}
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "AllowUser"))
    {
      if (value)
      {
        p->deny_users = 0;
        cupsdAddString(&(p->users), value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "DenyUser"))
    {
      if (value)
      {
        p->deny_users = 1;
        cupsdAddString(&(p->users), value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "QuotaPeriod"))
    {
      if (value)
        p->quota_period = atoi(value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "PageLimit"))
    {
      if (value)
        p->page_limit = atoi(value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "KLimit"))
    {
      if (value)
        p->k_limit = atoi(value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "OpPolicy"))
    {
      if (value)
      {
        cupsd_policy_t *pol;		/* Policy */


        if ((pol = cupsdFindPolicy(value)) != NULL)
	{
          cupsdSetString(&p->op_policy, value);
	  p->op_policy_ptr = pol;
	}
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Bad policy \"%s\" on line %d of printers.conf",
			  value, linenum);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "ErrorPolicy"))
    {
      if (value)
      {
	if (strcmp(value, "retry-current-job") &&
	    strcmp(value, "abort-job") &&
	    strcmp(value, "retry-job") &&
	    strcmp(value, "stop-printer"))
	  cupsdLogMessage(CUPSD_LOG_ALERT, "Invalid ErrorPolicy \"%s\" on line %d or printers.conf.", ErrorPolicy, linenum);
	else
	  cupsdSetString(&p->error_policy, value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Attribute") && value)
    {
      for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);

      if (!*valueptr)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
      else
      {
        for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0');

        if (!p->attrs)
	  cupsdSetPrinterAttrs(p);

        if (!strcmp(value, "marker-change-time"))
	  p->marker_time = atoi(valueptr);
	else
          cupsdSetPrinterAttr(p, value, valueptr);
      }
    }
    else if (_cups_strcasecmp(line, "Filter") &&
             _cups_strcasecmp(line, "Prefilter") &&
             _cups_strcasecmp(line, "Product"))
    {
     /*
      * Something else we don't understand (and that wasn't used in a prior
      * release of CUPS...
      */

      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown configuration directive %s on line %d of "
		      "printers.conf.", line, linenum);
    }
  }

  cupsFileClose(fp);
}


/*
 * 'cupsdRenamePrinter()' - Rename a printer.
 */

void
cupsdRenamePrinter(
    cupsd_printer_t *p,			/* I - Printer */
    const char      *name)		/* I - New name */
{
 /*
  * Remove the printer from the array(s) first...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdRenamePrinter: Removing %s from Printers", p->name);
  cupsArrayRemove(Printers, p);

 /*
  * Rename the printer type...
  */

  mimeDeleteType(MimeDatabase, p->filetype);
  p->filetype = mimeAddType(MimeDatabase, "printer", name);

  if (p->prefiltertype)
  {
    mimeDeleteType(MimeDatabase, p->prefiltertype);
    p->prefiltertype = mimeAddType(MimeDatabase, "prefilter", name);
  }

 /*
  * Rename the printer...
  */

  cupsdSetString(&p->name, name);

 /*
  * Reset printer attributes...
  */

  cupsdSetPrinterAttrs(p);

 /*
  * Add the printer back to the printer array(s)...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdRenamePrinter: Adding %s to Printers", p->name);
  cupsArrayAdd(Printers, p);
}


/*
 * 'cupsdSaveAllPrinters()' - Save all printer definitions to the printers.conf
 *                            file.
 */

void
cupsdSaveAllPrinters(void)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* printers.conf file */
  char			filename[1024],	/* printers.conf filename */
			value[2048],	/* Value string */
			*ptr,		/* Pointer into value */
			*name;		/* Current user/group name */
  cupsd_printer_t	*printer;	/* Current printer class */
  cups_option_t		*option;	/* Current option */
  ipp_attribute_t	*marker;	/* Current marker attribute */


 /*
  * Create the printers.conf file...
  */

  snprintf(filename, sizeof(filename), "%s/printers.conf", ServerRoot);

  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL)
    return;

  cupsdLogMessage(CUPSD_LOG_INFO, "Saving printers.conf...");

 /*
  * Write a small header to the file...
  */

  cupsFilePuts(fp, "# Printer configuration file for " CUPS_SVERSION "\n");
  cupsFilePrintf(fp, "# Written by cupsd\n");
  cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING\n");

  cupsFilePrintf(fp, "NextPrinterId %d\n", NextPrinterId);

 /*
  * Write each local printer known to the system...
  */

  for (printer = (cupsd_printer_t *)cupsArrayFirst(Printers);
       printer;
       printer = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
   /*
    * Skip printer classes and temporary queues...
    */

    if ((printer->type & CUPS_PRINTER_CLASS) || printer->temporary)
      continue;

   /*
    * Write printers as needed...
    */

    if (printer == DefaultPrinter)
      cupsFilePrintf(fp, "<DefaultPrinter %s>\n", printer->name);
    else
      cupsFilePrintf(fp, "<Printer %s>\n", printer->name);

    if (printer->printer_id)
      cupsFilePrintf(fp, "PrinterId %d\n", printer->printer_id);

    cupsFilePrintf(fp, "UUID %s\n", printer->uuid);

    if (printer->num_auth_info_required > 0)
    {
      switch (printer->num_auth_info_required)
      {
        case 1 :
            strlcpy(value, printer->auth_info_required[0], sizeof(value));
	    break;

        case 2 :
            snprintf(value, sizeof(value), "%s,%s",
	             printer->auth_info_required[0],
		     printer->auth_info_required[1]);
	    break;

        case 3 :
	default :
            snprintf(value, sizeof(value), "%s,%s,%s",
	             printer->auth_info_required[0],
		     printer->auth_info_required[1],
		     printer->auth_info_required[2]);
	    break;
      }

      cupsFilePutConf(fp, "AuthInfoRequired", value);
    }

    if (printer->info)
      cupsFilePutConf(fp, "Info", printer->info);

    if (printer->location)
      cupsFilePutConf(fp, "Location", printer->location);

    if (printer->geo_location)
      cupsFilePutConf(fp, "GeoLocation", printer->geo_location);

    if (printer->make_model)
      cupsFilePutConf(fp, "MakeModel", printer->make_model);

    if (printer->organization)
      cupsFilePutConf(fp, "Organization", printer->organization);

    if (printer->organizational_unit)
      cupsFilePutConf(fp, "OrganizationalUnit", printer->organizational_unit);

    cupsFilePutConf(fp, "DeviceURI", printer->device_uri);

    if (printer->port_monitor)
      cupsFilePutConf(fp, "PortMonitor", printer->port_monitor);

    if (printer->state == IPP_PRINTER_STOPPED)
    {
      cupsFilePuts(fp, "State Stopped\n");

      if (printer->state_message[0])
        cupsFilePutConf(fp, "StateMessage", printer->state_message);
    }
    else
      cupsFilePuts(fp, "State Idle\n");

    cupsFilePrintf(fp, "StateTime %d\n", (int)printer->state_time);
    cupsFilePrintf(fp, "ConfigTime %d\n", (int)printer->config_time);

    for (i = 0; i < printer->num_reasons; i ++)
      if (strcmp(printer->reasons[i], "connecting-to-device") &&
          strcmp(printer->reasons[i], "cups-insecure-filter-warning") &&
          strcmp(printer->reasons[i], "cups-missing-filter-warning"))
        cupsFilePutConf(fp, "Reason", printer->reasons[i]);

    cupsFilePrintf(fp, "Type %d\n", printer->type);

    if (printer->accepting)
      cupsFilePuts(fp, "Accepting Yes\n");
    else
      cupsFilePuts(fp, "Accepting No\n");

    if (printer->shared)
      cupsFilePuts(fp, "Shared Yes\n");
    else
      cupsFilePuts(fp, "Shared No\n");

    snprintf(value, sizeof(value), "%s %s", printer->job_sheets[0],
             printer->job_sheets[1]);
    cupsFilePutConf(fp, "JobSheets", value);

    cupsFilePrintf(fp, "QuotaPeriod %d\n", printer->quota_period);
    cupsFilePrintf(fp, "PageLimit %d\n", printer->page_limit);
    cupsFilePrintf(fp, "KLimit %d\n", printer->k_limit);

    for (name = (char *)cupsArrayFirst(printer->users);
         name;
	 name = (char *)cupsArrayNext(printer->users))
      cupsFilePutConf(fp, printer->deny_users ? "DenyUser" : "AllowUser", name);

    if (printer->op_policy)
      cupsFilePutConf(fp, "OpPolicy", printer->op_policy);
    if (printer->error_policy)
      cupsFilePutConf(fp, "ErrorPolicy", printer->error_policy);

    for (i = printer->num_options, option = printer->options;
         i > 0;
	 i --, option ++)
    {
      snprintf(value, sizeof(value), "%s %s", option->name, option->value);
      cupsFilePutConf(fp, "Option", value);
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-colors",
                                   IPP_TAG_NAME)) != NULL)
    {
      snprintf(value, sizeof(value), "%s ", marker->name);

      for (i = 0, ptr = value + strlen(value);
           i < marker->num_values && ptr < (value + sizeof(value) - 1);
	   i ++)
      {
        if (i)
	  *ptr++ = ',';

        strlcpy(ptr, marker->values[i].string.text, (size_t)(value + sizeof(value) - ptr));
        ptr += strlen(ptr);
      }

      *ptr = '\0';
      cupsFilePutConf(fp, "Attribute", value);
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-levels",
                                   IPP_TAG_INTEGER)) != NULL)
    {
      cupsFilePrintf(fp, "Attribute %s %d", marker->name,
                     marker->values[0].integer);
      for (i = 1; i < marker->num_values; i ++)
        cupsFilePrintf(fp, ",%d", marker->values[i].integer);
      cupsFilePuts(fp, "\n");
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-low-levels",
                                   IPP_TAG_INTEGER)) != NULL)
    {
      cupsFilePrintf(fp, "Attribute %s %d", marker->name,
                     marker->values[0].integer);
      for (i = 1; i < marker->num_values; i ++)
        cupsFilePrintf(fp, ",%d", marker->values[i].integer);
      cupsFilePuts(fp, "\n");
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-high-levels",
                                   IPP_TAG_INTEGER)) != NULL)
    {
      cupsFilePrintf(fp, "Attribute %s %d", marker->name,
                     marker->values[0].integer);
      for (i = 1; i < marker->num_values; i ++)
        cupsFilePrintf(fp, ",%d", marker->values[i].integer);
      cupsFilePuts(fp, "\n");
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-message",
                                   IPP_TAG_TEXT)) != NULL)
    {
      snprintf(value, sizeof(value), "%s %s", marker->name,
               marker->values[0].string.text);

      cupsFilePutConf(fp, "Attribute", value);
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-names",
                                   IPP_TAG_NAME)) != NULL)
    {
      snprintf(value, sizeof(value), "%s ", marker->name);

      for (i = 0, ptr = value + strlen(value);
           i < marker->num_values && ptr < (value + sizeof(value) - 1);
	   i ++)
      {
        if (i)
	  *ptr++ = ',';

        strlcpy(ptr, marker->values[i].string.text, (size_t)(value + sizeof(value) - ptr));
        ptr += strlen(ptr);
      }

      *ptr = '\0';
      cupsFilePutConf(fp, "Attribute", value);
    }

    if ((marker = ippFindAttribute(printer->attrs, "marker-types",
                                   IPP_TAG_KEYWORD)) != NULL)
    {
      snprintf(value, sizeof(value), "%s ", marker->name);

      for (i = 0, ptr = value + strlen(value);
           i < marker->num_values && ptr < (value + sizeof(value) - 1);
	   i ++)
      {
        if (i)
	  *ptr++ = ',';

        strlcpy(ptr, marker->values[i].string.text, (size_t)(value + sizeof(value) - ptr));
        ptr += strlen(ptr);
      }

      *ptr = '\0';
      cupsFilePutConf(fp, "Attribute", value);
    }

    if (printer->marker_time)
      cupsFilePrintf(fp, "Attribute marker-change-time %ld\n",
                     (long)printer->marker_time);

    if (printer == DefaultPrinter)
      cupsFilePuts(fp, "</DefaultPrinter>\n");
    else
      cupsFilePuts(fp, "</Printer>\n");
  }

  cupsdCloseCreatedConfFile(fp, filename);
}


/*
 * 'cupsdSetAuthInfoRequired()' - Set the required authentication info.
 */

int					/* O - 1 if value OK, 0 otherwise */
cupsdSetAuthInfoRequired(
    cupsd_printer_t *p,			/* I - Printer */
    const char      *values,		/* I - Plain text value (or NULL) */
    ipp_attribute_t *attr)		/* I - IPP attribute value (or NULL) */
{
  int	i;				/* Looping var */


  p->num_auth_info_required = 0;

 /*
  * Do we have a plain text value?
  */

  if (values)
  {
   /*
    * Yes, grab the keywords...
    */

    const char	*end;			/* End of current value */


    while (*values && p->num_auth_info_required < 4)
    {
      if ((end = strchr(values, ',')) == NULL)
        end = values + strlen(values);

      if ((end - values) == 4 && !strncmp(values, "none", 4))
      {
        if (p->num_auth_info_required != 0 || *end)
	  return (0);

        p->auth_info_required[p->num_auth_info_required] = "none";
	p->num_auth_info_required ++;

	return (1);
      }
      else if ((end - values) == 9 && !strncmp(values, "negotiate", 9))
      {
        if (p->num_auth_info_required != 0 || *end)
	  return (0);

        p->auth_info_required[p->num_auth_info_required] = "negotiate";
	p->num_auth_info_required ++;

       /*
        * Don't allow sharing of queues that require Kerberos authentication.
	*/

	if (p->shared)
	{
	  cupsdDeregisterPrinter(p, 1);
	  p->shared = 0;
	}
      }
      else if ((end - values) == 6 && !strncmp(values, "domain", 6))
      {
        p->auth_info_required[p->num_auth_info_required] = "domain";
	p->num_auth_info_required ++;
      }
      else if ((end - values) == 8 && !strncmp(values, "password", 8))
      {
        p->auth_info_required[p->num_auth_info_required] = "password";
	p->num_auth_info_required ++;
      }
      else if ((end - values) == 8 && !strncmp(values, "username", 8))
      {
        p->auth_info_required[p->num_auth_info_required] = "username";
	p->num_auth_info_required ++;
      }
      else
        return (0);

      values = (*end) ? end + 1 : end;
    }

    if (p->num_auth_info_required == 0)
    {
      p->auth_info_required[0]  = "none";
      p->num_auth_info_required = 1;
    }

   /*
    * Update the printer-type value as needed...
    */

    if (p->num_auth_info_required > 1 ||
        strcmp(p->auth_info_required[0], "none"))
      p->type |= CUPS_PRINTER_AUTHENTICATED;
    else
      p->type &= (cups_ptype_t)~CUPS_PRINTER_AUTHENTICATED;

    return (1);
  }

 /*
  * Grab values from an attribute instead...
  */

  if (!attr || attr->num_values > 4)
    return (0);

  for (i = 0; i < attr->num_values; i ++)
  {
    if (!strcmp(attr->values[i].string.text, "none"))
    {
      if (p->num_auth_info_required != 0 || attr->num_values != 1)
	return (0);

      p->auth_info_required[p->num_auth_info_required] = "none";
      p->num_auth_info_required ++;

      return (1);
    }
    else if (!strcmp(attr->values[i].string.text, "negotiate"))
    {
      if (p->num_auth_info_required != 0 || attr->num_values != 1)
	return (0);

      p->auth_info_required[p->num_auth_info_required] = "negotiate";
      p->num_auth_info_required ++;

     /*
      * Don't allow sharing of queues that require Kerberos authentication.
      */

      if (p->shared)
      {
	cupsdDeregisterPrinter(p, 1);
	p->shared = 0;
      }

      return (1);
    }
    else if (!strcmp(attr->values[i].string.text, "domain"))
    {
      p->auth_info_required[p->num_auth_info_required] = "domain";
      p->num_auth_info_required ++;
    }
    else if (!strcmp(attr->values[i].string.text, "password"))
    {
      p->auth_info_required[p->num_auth_info_required] = "password";
      p->num_auth_info_required ++;
    }
    else if (!strcmp(attr->values[i].string.text, "username"))
    {
      p->auth_info_required[p->num_auth_info_required] = "username";
      p->num_auth_info_required ++;
    }
    else
      return (0);
  }

  return (1);
}


/*
 * 'cupsdSetDeviceURI()' - Set the device URI for a printer.
 */

void
cupsdSetDeviceURI(cupsd_printer_t *p,	/* I - Printer */
                  const char      *uri)	/* I - Device URI */
{
  char	buffer[1024],			/* URI buffer */
	*start,				/* Start of data after scheme */
	*slash,				/* First slash after scheme:// */
	*ptr;				/* Pointer into user@host:port part */


 /*
  * Set the full device URI..
  */

  cupsdSetString(&(p->device_uri), uri);

 /*
  * Copy the device URI to a temporary buffer so we can sanitize any auth
  * info in it...
  */

  strlcpy(buffer, uri, sizeof(buffer));

 /*
  * Find the end of the scheme:// part...
  */

  if ((ptr = strchr(buffer, ':')) != NULL)
  {
    for (start = ptr + 1; *start; start ++)
      if (*start != '/')
        break;

   /*
    * Find the next slash (/) in the URI...
    */

    if ((slash = strchr(start, '/')) == NULL)
      slash = start + strlen(start);	/* No slash, point to the end */

   /*
    * Check for an @ sign before the slash...
    */

    if ((ptr = strchr(start, '@')) != NULL && ptr < slash)
    {
     /*
      * Found an @ sign and it is before the resource part, so we have
      * an authentication string.  Copy the remaining URI over the
      * authentication string...
      */

      _cups_strcpy(start, ptr + 1);
    }
  }

 /*
  * Save the sanitized URI...
  */

  cupsdSetString(&(p->sanitized_device_uri), buffer);
}


/*
 * 'cupsdSetPrinterAttr()' - Set a printer attribute.
 */

void
cupsdSetPrinterAttr(
    cupsd_printer_t *p,			/* I - Printer */
    const char      *name,		/* I - Attribute name */
    const char      *value)		/* I - Attribute value string */
{
  ipp_attribute_t	*attr;		/* Attribute */
  int			i,		/* Looping var */
			count;		/* Number of values */
  char			*temp,		/* Temporary copy of value string */
			*ptr,		/* Pointer into value */
			*start,		/* Start of value */
			quote;		/* Quote character */
  ipp_tag_t		value_tag;	/* Value tag for this attribute */


 /*
  * Don't allow empty values...
  */

  if (!*value && strcmp(name, "marker-message"))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Ignoring empty \"%s\" attribute", name);
    return;
  }

 /*
  * Copy the value string so we can do what we want with it...
  */

  if ((temp = strdup(value)) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "Unable to duplicate value for \"%s\" attribute.", name);
    return;
  }

 /*
  * Count the number of values...
  */

  for (count = 1, quote = '\0', ptr = temp;
       *ptr;
       ptr ++)
  {
    if (*ptr == quote)
      quote = '\0';
    else if (quote)
      continue;
    else if (*ptr == '\\' && ptr[1])
      ptr ++;
    else if (*ptr == '\'' || *ptr == '\"')
      quote = *ptr;
    else if (*ptr == ',')
      count ++;
  }

 /*
  * Then add or update the attribute as needed...
  */

  if (!strcmp(name, "marker-levels") || !strcmp(name, "marker-low-levels") ||
      !strcmp(name, "marker-high-levels"))
  {
   /*
    * Integer values...
    */

    if ((attr = ippFindAttribute(p->attrs, name, IPP_TAG_INTEGER)) != NULL &&
        attr->num_values < count)
    {
      ippDeleteAttribute(p->attrs, attr);
      attr = NULL;
    }

    if (attr)
      attr->num_values = count;
    else
      attr = ippAddIntegers(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, name,
                            count, NULL);

    if (!attr)
    {
      free(temp);
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unable to allocate memory for printer attribute "
		      "(%d values)", count);
      return;
    }

    for (i = 0, start = temp; i < count; i ++)
    {
      if ((ptr = strchr(start, ',')) != NULL)
        *ptr++ = '\0';

      attr->values[i].integer = strtol(start, NULL, 10);

      if (ptr)
        start = ptr;
    }
  }
  else
  {
   /*
    * Name or keyword values...
    */

    if (!strcmp(name, "marker-types"))
      value_tag = IPP_TAG_KEYWORD;
    else if (!strcmp(name, "marker-message"))
      value_tag = IPP_TAG_TEXT;
    else
      value_tag = IPP_TAG_NAME;

    if ((attr = ippFindAttribute(p->attrs, name, value_tag)) != NULL &&
        attr->num_values < count)
    {
      ippDeleteAttribute(p->attrs, attr);
      attr = NULL;
    }

    if (attr)
    {
      for (i = 0; i < attr->num_values; i ++)
	_cupsStrFree(attr->values[i].string.text);

      attr->num_values = count;
    }
    else
      attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, value_tag, name,
                           count, NULL, NULL);

    if (!attr)
    {
      free(temp);
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unable to allocate memory for printer attribute "
		      "(%d values)", count);
      return;
    }

    for (i = 0, quote = '\0', ptr = temp; i < count; i ++)
    {
      for (start = ptr; *ptr; ptr ++)
      {
	if (*ptr == quote)
	  *ptr = quote = '\0';
	else if (quote)
	  continue;
	else if (*ptr == '\\' && ptr[1])
	  _cups_strcpy(ptr, ptr + 1);
	else if (*ptr == '\'' || *ptr == '\"')
	{
	  quote = *ptr;

	  if (ptr == start)
	    start ++;
	  else
	    _cups_strcpy(ptr, ptr + 1);
	}
	else if (*ptr == ',')
	{
	  *ptr++ = '\0';
	  break;
	}
      }

      attr->values[i].string.text = _cupsStrAlloc(start);
    }
  }

  free(temp);

 /*
  * Update the printer-supply and printer-supply-description, as needed...
  */

  if (!strcmp(name, "marker-names"))
  {
    ipp_attribute_t *supply_desc = ippFindAttribute(p->attrs, "printer-supply-description", IPP_TAG_TEXT);
					/* printer-supply-description attribute */

    if (supply_desc != NULL)
      ippDeleteAttribute(p->attrs, supply_desc);

    supply_desc = ippCopyAttribute(p->attrs, attr, 0);
    ippSetName(p->attrs, &supply_desc, "printer-supply-description");
    ippSetValueTag(p->attrs, &supply_desc, IPP_TAG_TEXT);
  }
  else if (!strcmp(name, "marker-colors") || !strcmp(name, "marker-levels") || !strcmp(name, "marker-types"))
  {
    char	buffer[256],		/* printer-supply values */
		pstype[64],		/* printer-supply type value */
		*psptr;			/* Pointer into type */
    const char	*color,			/* marker-colors value */
		*type;			/* marker-types value */
    int		level;			/* marker-levels value */
    ipp_attribute_t *colors = ippFindAttribute(p->attrs, "marker-colors", IPP_TAG_NAME);
					/* marker-colors attribute */
    ipp_attribute_t *levels = ippFindAttribute(p->attrs, "marker-levels", IPP_TAG_INTEGER);
					/* marker-levels attribute */
    ipp_attribute_t *types = ippFindAttribute(p->attrs, "marker-types", IPP_TAG_KEYWORD);
					/* marker-types attribute */
    ipp_attribute_t *supply = ippFindAttribute(p->attrs, "printer-supply", IPP_TAG_STRING);
					/* printer-supply attribute */

    if (supply != NULL)
    {
      ippDeleteAttribute(p->attrs, supply);
      supply = NULL;
    }

    if (!colors || !levels || !types)
      return;

    count = ippGetCount(colors);
    if (count != ippGetCount(levels) || count != ippGetCount(types))
      return;

    for (i = 0; i < count; i ++)
    {
      color = ippGetString(colors, i, NULL);
      level = ippGetInteger(levels, i);
      type  = ippGetString(types, i, NULL);

      for (psptr = pstype; *type && psptr < (pstype + sizeof(pstype) - 1); type ++)
        if (*type == '-')
	{
	  type ++;
	  *psptr++ = (char)toupper(*type & 255);
	}
	else
	  *psptr++ = *type;
      *psptr = '\0';

      snprintf(buffer, sizeof(buffer), "index=%d;class=%s;type=%s;unit=percent;maxcapacity=100;level=%d;colorantname=%s;", i + 1, strncmp(pstype, "waste", 5) ? "supplyThatIsConsumed" : "receptacleThatIsFilled", pstype, level, color);

      if (!i)
        supply = ippAddOctetString(p->attrs, IPP_TAG_PRINTER, "printer-supply", buffer, (int)strlen(buffer));
      else
        ippSetOctetString(p->attrs, &supply, i, buffer, (int)strlen(buffer));
    }
  }
}


/*
 * 'cupsdSetPrinterAttrs()' - Set printer attributes based upon the PPD file.
 */

void
cupsdSetPrinterAttrs(cupsd_printer_t *p)/* I - Printer to setup */
{
  int		i;			/* Looping var */
  char		resource[HTTP_MAX_URI];	/* Resource portion of URI */
  cupsd_location_t *auth;		/* Pointer to authentication element */
  const char	*auth_supported;	/* Authentication supported */
  ipp_t		*oldattrs;		/* Old printer attributes */
  ipp_attribute_t *attr;		/* Attribute data */
  char		*name,			/* Current user/group name */
		*filter;		/* Current filter */


 /*
  * Make sure that we have the common attributes defined...
  */

  if (!CommonData)
    cupsdCreateCommonData();

  _cupsRWLockWrite(&p->lock);

 /*
  * Clear out old filters, if any...
  */

  delete_printer_filters(p);

 /*
  * Figure out the authentication that is required for the printer.
  */

  auth_supported = "requesting-user-name";

  if (p->type & CUPS_PRINTER_CLASS)
    snprintf(resource, sizeof(resource), "/classes/%s", p->name);
  else
    snprintf(resource, sizeof(resource), "/printers/%s", p->name);

  if ((auth = cupsdFindBest(resource, HTTP_POST)) == NULL ||
      auth->type == CUPSD_AUTH_NONE)
    auth = cupsdFindPolicyOp(p->op_policy_ptr, IPP_PRINT_JOB);

  if (auth)
  {
    int	auth_type;		/* Authentication type */


    if ((auth_type = auth->type) == CUPSD_AUTH_DEFAULT)
      auth_type = cupsdDefaultAuthType();

    if (auth_type == CUPSD_AUTH_BASIC)
      auth_supported = "basic";
#ifdef HAVE_GSSAPI
    else if (auth_type == CUPSD_AUTH_NEGOTIATE)
      auth_supported = "negotiate";
#endif /* HAVE_GSSAPI */

    if (auth_type != CUPSD_AUTH_NONE)
      p->type |= CUPS_PRINTER_AUTHENTICATED;
    else
      p->type &= (cups_ptype_t)~CUPS_PRINTER_AUTHENTICATED;
  }
  else
    p->type &= (cups_ptype_t)~CUPS_PRINTER_AUTHENTICATED;

 /*
  * Create the required IPP attributes for a printer...
  */

  oldattrs = p->attrs;
  p->attrs = ippNew();

  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
               "uri-authentication-supported", NULL, auth_supported);
  if (p->printer_id)
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "printer-id", p->printer_id);
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL,
               p->name);
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location",
               NULL, p->location ? p->location : "");
  if (p->geo_location)
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-geo-location", NULL, p->geo_location);
  else
    ippAddOutOfBand(p->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location");
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
               NULL, p->info ? p->info : "");
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-organization", NULL, p->organization ? p->organization : "");
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-organizational-unit", NULL, p->organizational_unit ? p->organizational_unit : "");
  ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uuid", NULL, p->uuid);

  if (cupsArrayCount(p->users) > 0)
  {
    if (p->deny_users)
      attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
                           "requesting-user-name-denied",
			   cupsArrayCount(p->users), NULL, NULL);
    else
      attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
                           "requesting-user-name-allowed",
			   cupsArrayCount(p->users), NULL, NULL);

    for (i = 0, name = (char *)cupsArrayFirst(p->users);
         name;
	 i ++, name = (char *)cupsArrayNext(p->users))
      attr->values[i].string.text = _cupsStrAlloc(name);
  }

  ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "job-quota-period", p->quota_period);
  ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "job-k-limit", p->k_limit);
  ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                "job-page-limit", p->page_limit);
  if (p->num_auth_info_required > 0 && strcmp(p->auth_info_required[0], "none"))
    ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		  "auth-info-required", p->num_auth_info_required, NULL,
		  p->auth_info_required);

  if (cupsArrayCount(Banners) > 0)
  {
   /*
    * Setup the job-sheets-default attribute...
    */

    attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
                	 "job-sheets-default", 2, NULL, NULL);

    if (attr != NULL)
    {
      attr->values[0].string.text = _cupsStrAlloc(Classification ?
	                                   Classification : p->job_sheets[0]);
      attr->values[1].string.text = _cupsStrAlloc(Classification ?
	                                   Classification : p->job_sheets[1]);
    }
  }

  p->raw    = 0;
  p->remote = 0;

 /*
  * Assign additional attributes depending on whether this is a printer
  * or class...
  */

  if (p->type & CUPS_PRINTER_CLASS)
  {
    p->raw = 1;
    p->type &= (cups_ptype_t)~CUPS_PRINTER_OPTIONS;

   /*
    * Add class-specific attributes...
    */

    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
		 "printer-make-and-model", NULL, "Local Printer Class");
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "device-uri", NULL,
		 "file:///dev/null");

    if (p->num_printers > 0)
    {
     /*
      * Add a list of member names; URIs are added in copy_printer_attrs...
      */

      attr    = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
			      "member-names", p->num_printers, NULL, NULL);
      p->type |= CUPS_PRINTER_OPTIONS;

      for (i = 0; i < p->num_printers; i ++)
      {
	if (attr != NULL)
	  attr->values[i].string.text = _cupsStrAlloc(p->printers[i]->name);

	p->type &= (cups_ptype_t)~CUPS_PRINTER_OPTIONS | p->printers[i]->type;
      }
    }
  }
  else
  {
   /*
    * Add printer-specific attributes...
    */

    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "device-uri", NULL,
		 p->sanitized_device_uri);

   /*
    * Assign additional attributes from the PPD file (if any)...
    */

    load_ppd(p);

   /*
    * Add filters for printer...
    */

    cupsdSetPrinterReasons(p, "-cups-missing-filter-warning,"
			      "cups-insecure-filter-warning");

    if (p->pc && p->pc->filters)
    {
      for (filter = (char *)cupsArrayFirst(p->pc->filters);
	   filter;
	   filter = (char *)cupsArrayNext(p->pc->filters))
	add_printer_filter(p, p->filetype, filter);
    }
    else if (!(p->type & CUPS_PRINTER_REMOTE))
    {
     /*
      * Add a filter from application/vnd.cups-raw to printer/name to
      * handle "raw" printing by users.
      */

      add_printer_filter(p, p->filetype, "application/vnd.cups-raw 0 -");

     /*
      * Add a PostScript filter, since this is still possibly PS printer.
      */

      add_printer_filter(p, p->filetype,
			 "application/vnd.cups-postscript 0 -");
    }

    if (p->pc && p->pc->prefilters)
    {
      if (!p->prefiltertype)
	p->prefiltertype = mimeAddType(MimeDatabase, "prefilter", p->name);

      for (filter = (char *)cupsArrayFirst(p->pc->prefilters);
	   filter;
	   filter = (char *)cupsArrayNext(p->pc->prefilters))
	add_printer_filter(p, p->prefiltertype, filter);
    }
  }

 /*
  * Copy marker attributes as needed...
  */

  if (oldattrs)
  {
    ipp_attribute_t *oldattr;		/* Old attribute */


    if ((oldattr = ippFindAttribute(oldattrs, "marker-colors",
                                    IPP_TAG_NAME)) != NULL)
    {
      if ((attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
                                "marker-colors", oldattr->num_values, NULL,
				NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].string.text =
	      _cupsStrAlloc(oldattr->values[i].string.text);
      }
    }

    if ((oldattr = ippFindAttribute(oldattrs, "marker-levels",
                                    IPP_TAG_INTEGER)) != NULL)
    {
      if ((attr = ippAddIntegers(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                                 "marker-levels", oldattr->num_values,
				 NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].integer = oldattr->values[i].integer;
      }
    }

    if ((oldattr = ippFindAttribute(oldattrs, "marker-message",
                                    IPP_TAG_TEXT)) != NULL)
      ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "marker-message",
                   NULL, oldattr->values[0].string.text);

    if ((oldattr = ippFindAttribute(oldattrs, "marker-low-levels",
                                    IPP_TAG_INTEGER)) != NULL)
    {
      if ((attr = ippAddIntegers(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                                 "marker-low-levels", oldattr->num_values,
				 NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].integer = oldattr->values[i].integer;
      }
    }

    if ((oldattr = ippFindAttribute(oldattrs, "marker-high-levels",
                                    IPP_TAG_INTEGER)) != NULL)
    {
      if ((attr = ippAddIntegers(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                                 "marker-high-levels", oldattr->num_values,
				 NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].integer = oldattr->values[i].integer;
      }
    }

    if ((oldattr = ippFindAttribute(oldattrs, "marker-names",
                                    IPP_TAG_NAME)) != NULL)
    {
      if ((attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
                                "marker-names", oldattr->num_values, NULL,
				NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].string.text =
	      _cupsStrAlloc(oldattr->values[i].string.text);
      }
    }

    if ((oldattr = ippFindAttribute(oldattrs, "marker-types",
                                    IPP_TAG_KEYWORD)) != NULL)
    {
      if ((attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                                "marker-types", oldattr->num_values, NULL,
				NULL)) != NULL)
      {
	for (i = 0; i < oldattr->num_values; i ++)
	  attr->values[i].string.text =
	      _cupsStrAlloc(oldattr->values[i].string.text);
      }
    }

    ippDelete(oldattrs);
  }

 /*
  * Force sharing off for remote queues...
  */

  if (p->type & CUPS_PRINTER_REMOTE)
    p->shared = 0;

 /*
  * Populate the document-format-supported attribute...
  */

  add_printer_formats(p);

 /*
  * Add name-default attributes...
  */

  add_printer_defaults(p);

  _cupsRWUnlock(&p->lock);

 /*
  * Let the browse protocols reflect the change
  */

  cupsdRegisterPrinter(p);
}


/*
 * 'cupsdSetPrinterReasons()' - Set/update the reasons strings.
 */

int					/* O - 1 if something changed, 0 otherwise */
cupsdSetPrinterReasons(
    cupsd_printer_t *p,			/* I - Printer */
    const char      *s)			/* I - Reasons strings */
{
  int		i,			/* Looping var */
		changed = 0;		/* Did something change? */
  const char	*sptr;			/* Pointer into reasons */
  char		reason[255],		/* Reason string */
		*rptr;			/* Pointer into reason */


  cupsdLogMessage(CUPSD_LOG_DEBUG2,
		  "cupsdSetPrinterReasons(p=%p(%s),s=\"%s\"", p, p->name, s);

  if (s[0] == '-' || s[0] == '+')
  {
   /*
    * Add/remove reasons...
    */

    sptr = s + 1;
  }
  else
  {
   /*
    * Replace reasons...
    */

    sptr = s;

    for (i = 0; i < p->num_reasons; i ++)
      _cupsStrFree(p->reasons[i]);

    p->num_reasons = 0;
    changed        = 1;

    dirty_printer(p);
  }

  if (!strcmp(s, "none"))
    return (changed);

 /*
  * Loop through all of the reasons...
  */

  while (*sptr)
  {
   /*
    * Skip leading whitespace and commas...
    */

    while (isspace(*sptr & 255) || *sptr == ',')
      sptr ++;

    for (rptr = reason; *sptr && !isspace(*sptr & 255) && *sptr != ','; sptr ++)
      if (rptr < (reason + sizeof(reason) - 1))
        *rptr++ = *sptr;

    if (rptr == reason)
      break;

    *rptr = '\0';

    if (s[0] == '-')
    {
     /*
      * Remove reason...
      */

      for (i = 0; i < p->num_reasons; i ++)
        if (!strcmp(reason, p->reasons[i]))
	{
	 /*
	  * Found a match, so remove it...
	  */

	  p->num_reasons --;
          changed = 1;
	  _cupsStrFree(p->reasons[i]);

	  if (i < p->num_reasons)
	    memmove(p->reasons + i, p->reasons + i + 1, (size_t)(p->num_reasons - i) * sizeof(char *));

          if (!strcmp(reason, "paused") && p->state == IPP_PRINTER_STOPPED)
	    cupsdSetPrinterState(p, IPP_PRINTER_IDLE, 1);

          if (!strcmp(reason, "cups-waiting-for-job-completed") && p->job)
            p->job->completed = 0;

          if (strcmp(reason, "connecting-to-device"))
	    dirty_printer(p);

	  break;
	}
    }
    else if (p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
    {
     /*
      * Add reason...
      */

      for (i = 0; i < p->num_reasons; i ++)
        if (!strcmp(reason, p->reasons[i]))
	  break;

      if (i >= p->num_reasons)
      {
        if (i >= (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
	{
	  cupsdLogMessage(CUPSD_LOG_ALERT,
	                  "Too many printer-state-reasons values for %s (%d)",
			  p->name, i + 1);
          return (changed);
        }

        p->reasons[i] = _cupsStrAlloc(reason);
	p->num_reasons ++;
        changed = 1;

	if (!strcmp(reason, "paused") && p->state != IPP_PRINTER_STOPPED)
	  cupsdSetPrinterState(p, IPP_PRINTER_STOPPED, 1);

	if (!strcmp(reason, "cups-waiting-for-job-completed") && p->job)
	  p->job->completed = 1;

	if (strcmp(reason, "connecting-to-device"))
	  dirty_printer(p);
      }
    }
  }

  return (changed);
}


/*
 * 'cupsdSetPrinterState()' - Update the current state of a printer.
 */

void
cupsdSetPrinterState(
    cupsd_printer_t *p,			/* I - Printer to change */
    ipp_pstate_t    s,			/* I - New state */
    int             update)		/* I - Update printers.conf? */
{
  cupsd_job_t	*job;			/* Current job */
  ipp_pstate_t	old_state;		/* Old printer state */
  static const char * const printer_states[] =
  {					/* State strings */
    "idle",
    "processing",
    "stopped"
  };


 /*
  * Set the new state...
  */

  old_state = p->state;
  p->state  = s;

  if (old_state != s)
  {
    cupsdAddEvent(s == IPP_PRINTER_STOPPED ? CUPSD_EVENT_PRINTER_STOPPED :
                      CUPSD_EVENT_PRINTER_STATE, p, NULL,
		  "%s \"%s\" state changed to %s.",
		  (p->type & CUPS_PRINTER_CLASS) ? "Class" : "Printer",
		  p->name, printer_states[p->state - IPP_PRINTER_IDLE]);

   /*
    * Let the browse code know this needs to be updated...
    */

    p->state_time = time(NULL);
  }

 /*
  * Set/clear the paused reason as needed...
  */

  if (s == IPP_PRINTER_STOPPED)
    cupsdSetPrinterReasons(p, "+paused");
  else
    cupsdSetPrinterReasons(p, "-paused");

  if (old_state != s)
  {
    for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
	 job;
	 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
      if (job->reasons && job->state_value == IPP_JOB_PENDING &&
	  !_cups_strcasecmp(job->dest, p->name))
	ippSetString(job->attrs, &job->reasons, 0,
		     s == IPP_PRINTER_STOPPED ? "printer-stopped" : "none");
  }

 /*
  * Clear the message for the queue when going to processing...
  */

  if (s == IPP_PRINTER_PROCESSING)
    p->state_message[0] = '\0';

 /*
  * Let the browse protocols reflect the change...
  */

  if (update)
    cupsdRegisterPrinter(p);

 /*
  * Save the printer configuration if a printer goes from idle or processing
  * to stopped (or visa-versa)...
  */

  if (update &&
      (old_state == IPP_PRINTER_STOPPED) != (s == IPP_PRINTER_STOPPED))
    dirty_printer(p);
}


/*
 * 'cupsdStopPrinter()' - Stop a printer from printing any jobs...
 */

void
cupsdStopPrinter(cupsd_printer_t *p,	/* I - Printer to stop */
                 int             update)/* I - Update printers.conf? */
{
 /*
  * Set the printer state...
  */

  cupsdSetPrinterState(p, IPP_PRINTER_STOPPED, update);

 /*
  * See if we have a job printing on this printer...
  */

  if (p->job && p->job->state_value == IPP_JOB_PROCESSING)
    cupsdSetJobState(p->job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT,
                     "Job stopped due to printer being paused.");
}


/*
 * 'cupsdUpdatePrinterPPD()' - Update keywords in a printer's PPD file.
 */

int					/* O - 1 if successful, 0 otherwise */
cupsdUpdatePrinterPPD(
    cupsd_printer_t *p,			/* I - Printer */
    int             num_keywords,	/* I - Number of keywords */
    cups_option_t   *keywords)		/* I - Keywords */
{
  int		i;			/* Looping var */
  cups_file_t	*src,			/* Original file */
		*dst;			/* New file */
  char		srcfile[1024],		/* Original filename */
		dstfile[1024],		/* New filename */
		line[1024],		/* Line from file */
		keystring[41];		/* Keyword from line */
  cups_option_t	*keyword;		/* Current keyword */


  cupsdLogMessage(CUPSD_LOG_INFO, "Updating keywords in PPD file for %s...",
                  p->name);

 /*
  * Get the old and new PPD filenames...
  */

  snprintf(srcfile, sizeof(srcfile), "%s/ppd/%s.ppd.O", ServerRoot, p->name);
  snprintf(dstfile, sizeof(srcfile), "%s/ppd/%s.ppd", ServerRoot, p->name);

 /*
  * Rename the old file and open the old and new...
  */

  if (rename(dstfile, srcfile))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to backup PPD file for %s: %s",
                    p->name, strerror(errno));
    return (0);
  }

  if ((src = cupsFileOpen(srcfile, "r")) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open PPD file \"%s\": %s",
                    srcfile, strerror(errno));
    rename(srcfile, dstfile);
    return (0);
  }

  if ((dst = cupsFileOpen(dstfile, "w")) == NULL)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create PPD file \"%s\": %s",
                    dstfile, strerror(errno));
    cupsFileClose(src);
    rename(srcfile, dstfile);
    return (0);
  }

 /*
  * Copy the first line and then write out all of the keywords...
  */

  if (!cupsFileGets(src, line, sizeof(line)))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to read PPD file \"%s\": %s",
                    srcfile, strerror(errno));
    cupsFileClose(src);
    cupsFileClose(dst);
    rename(srcfile, dstfile);
    return (0);
  }

  cupsFilePrintf(dst, "%s\n", line);

  for (i = num_keywords, keyword = keywords; i > 0; i --, keyword ++)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG, "*%s: %s", keyword->name, keyword->value);
    cupsFilePrintf(dst, "*%s: %s\n", keyword->name, keyword->value);
  }

 /*
  * Then copy the rest of the PPD file, dropping any keywords we changed.
  */

  while (cupsFileGets(src, line, sizeof(line)))
  {
   /*
    * Skip keywords we've already set...
    */

    if (sscanf(line, "*%40[^:]:", keystring) == 1 &&
        cupsGetOption(keystring, num_keywords, keywords))
      continue;

   /*
    * Otherwise write the line...
    */

    cupsFilePrintf(dst, "%s\n", line);
  }

 /*
  * Close files and return...
  */

  cupsFileClose(src);
  cupsFileClose(dst);

  return (1);
}


/*
 * 'cupsdUpdatePrinters()' - Update printers after a partial reload.
 */

void
cupsdUpdatePrinters(void)
{
  cupsd_printer_t	*p;		/* Current printer */


 /*
  * Loop through the printers and recreate the printer attributes
  * for any local printers since the policy and/or access control
  * stuff may have changed.  Also, if browsing is disabled, remove
  * any remote printers...
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
   /*
    * Update the operation policy pointer...
    */

    if ((p->op_policy_ptr = cupsdFindPolicy(p->op_policy)) == NULL)
      p->op_policy_ptr = DefaultPolicyPtr;

   /*
    * Update printer attributes...
    */

    cupsdSetPrinterAttrs(p);
  }
}


/*
 * 'cupsdValidateDest()' - Validate a printer/class destination.
 */

const char *				/* O - Printer or class name */
cupsdValidateDest(
    const char      *uri,		/* I - Printer URI */
    cups_ptype_t    *dtype,		/* O - Type (printer or class) */
    cupsd_printer_t **printer)		/* O - Printer pointer */
{
  cupsd_printer_t	*p;		/* Current printer */
  char			localname[1024],/* Localized hostname */
			*lptr,		/* Pointer into localized hostname */
			*sptr,		/* Pointer into server name */
			*rptr,		/* Pointer into resource */
			scheme[32],	/* Scheme portion of URI */
			username[64],	/* Username portion of URI */
			hostname[HTTP_MAX_HOST],
					/* Host portion of URI */
			resource[HTTP_MAX_URI];
					/* Resource portion of URI */
  int			port;		/* Port portion of URI */


 /*
  * Initialize return values...
  */

  if (printer)
    *printer = NULL;

  if (dtype)
    *dtype = (cups_ptype_t)0;

 /*
  * Pull the hostname and resource from the URI...
  */

  httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme),
                  username, sizeof(username), hostname, sizeof(hostname),
		  &port, resource, sizeof(resource));

 /*
  * See if the resource is a class or printer...
  */

  if (!strncmp(resource, "/classes/", 9))
  {
   /*
    * Class...
    */

    rptr = resource + 9;
  }
  else if (!strncmp(resource, "/printers/", 10))
  {
   /*
    * Printer...
    */

    rptr = resource + 10;
  }
  else
  {
   /*
    * Bad resource name...
    */

    return (NULL);
  }

 /*
  * See if the printer or class name exists...
  */

  p = cupsdFindDest(rptr);

  if (p == NULL && strchr(rptr, '@') == NULL)
    return (NULL);
  else if (p != NULL)
  {
    if (printer)
      *printer = p;

    if (dtype)
      *dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);

    return (p->name);
  }

 /*
  * Change localhost to the server name...
  */

  if (!_cups_strcasecmp(hostname, "localhost"))
    strlcpy(hostname, ServerName, sizeof(hostname));

  strlcpy(localname, hostname, sizeof(localname));

  if (!_cups_strcasecmp(hostname, ServerName))
  {
   /*
    * Localize the hostname...
    */

    lptr = strchr(localname, '.');
    sptr = strchr(ServerName, '.');

    if (sptr != NULL && lptr != NULL)
    {
     /*
      * Strip the common domain name components...
      */

      while (lptr != NULL)
      {
	if (!_cups_strcasecmp(lptr, sptr))
	{
          *lptr = '\0';
	  break;
	}
	else
          lptr = strchr(lptr + 1, '.');
      }
    }
  }

 /*
  * Find a matching printer or class...
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
    if (!_cups_strcasecmp(p->hostname, localname) &&
        !_cups_strcasecmp(p->name, rptr))
    {
      if (printer)
        *printer = p;

      if (dtype)
	*dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);

      return (p->name);
    }

  return (NULL);
}


/*
 * 'cupsdWritePrintcap()' - Write a pseudo-printcap file for older applications
 *                          that need it...
 */

void
cupsdWritePrintcap(void)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* Printcap file */
  cupsd_printer_t	*p;		/* Current printer */


 /*
  * See if we have a printcap file; if not, don't bother writing it.
  */

  if (!Printcap || !*Printcap)
    return;

  cupsdLogMessage(CUPSD_LOG_INFO, "Generating printcap %s...", Printcap);

 /*
  * Open the printcap file...
  */

  if ((fp = cupsFileOpen(Printcap, "w")) == NULL)
    return;

 /*
  * Put a comment header at the top so that users will know where the
  * data has come from...
  */

  if (PrintcapFormat != PRINTCAP_PLIST)
    cupsFilePrintf(fp, "# This file was automatically generated by cupsd(8) "
                       "from the\n"
                       "# %s/printers.conf file.  All changes to this file\n"
		       "# will be lost.\n", ServerRoot);

 /*
  * Write a new printcap with the current list of printers.
  */

  switch (PrintcapFormat)
  {
    case PRINTCAP_BSD :
       /*
	* Each printer is put in the file as:
	*
	*    Printer1:
	*    Printer2:
	*    Printer3:
	*    ...
	*    PrinterN:
	*/

	if (DefaultPrinter)
	  cupsFilePrintf(fp, "%s|%s:rm=%s:rp=%s:\n", DefaultPrinter->name,
			 DefaultPrinter->info, ServerName,
			 DefaultPrinter->name);

	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
	     p;
	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
	  if (p != DefaultPrinter)
	    cupsFilePrintf(fp, "%s|%s:rm=%s:rp=%s:\n", p->name, p->info,
			   ServerName, p->name);
	break;

    case PRINTCAP_PLIST :
       /*
	* Each printer is written as a dictionary in a plist file.
	* Currently the printer-name, printer-info, printer-is-accepting-jobs,
	* printer-location, printer-make-and-model, printer-state,
	* printer-state-reasons, printer-type, and (sanitized) device-uri.
	*/

	cupsFilePuts(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
			 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD "
			 "PLIST 1.0//EN\" \"http://www.apple.com/DTDs/"
			 "PropertyList-1.0.dtd\">\n"
			 "<plist version=\"1.0\">\n"
			 "<array>\n");

	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
	     p;
	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
	{
	  cupsFilePuts(fp, "\t<dict>\n"
			   "\t\t<key>printer-name</key>\n"
			   "\t\t<string>");
	  write_xml_string(fp, p->name);
	  cupsFilePuts(fp, "</string>\n"
			   "\t\t<key>printer-info</key>\n"
			   "\t\t<string>");
	  write_xml_string(fp, p->info);
	  cupsFilePrintf(fp, "</string>\n"
			     "\t\t<key>printer-is-accepting-jobs</key>\n"
			     "\t\t<%s/>\n"
			     "\t\t<key>printer-location</key>\n"
			     "\t\t<string>", p->accepting ? "true" : "false");
	  write_xml_string(fp, p->location);
	  cupsFilePuts(fp, "</string>\n"
			   "\t\t<key>printer-make-and-model</key>\n"
			   "\t\t<string>");
	  write_xml_string(fp, p->make_model);
	  cupsFilePrintf(fp, "</string>\n"
			     "\t\t<key>printer-state</key>\n"
			     "\t\t<integer>%d</integer>\n"
			     "\t\t<key>printer-state-reasons</key>\n"
			     "\t\t<array>\n", p->state);
	  for (i = 0; i < p->num_reasons; i ++)
	  {
	    cupsFilePuts(fp, "\t\t\t<string>");
	    write_xml_string(fp, p->reasons[i]);
	    cupsFilePuts(fp, "</string>\n");
	  }
	  cupsFilePrintf(fp, "\t\t</array>\n"
			     "\t\t<key>printer-type</key>\n"
			     "\t\t<integer>%d</integer>\n"
			     "\t\t<key>device-uri</key>\n"
			     "\t\t<string>", p->type);
	  write_xml_string(fp, p->sanitized_device_uri);
	  cupsFilePuts(fp, "</string>\n"
			   "\t</dict>\n");
	}
	cupsFilePuts(fp, "</array>\n"
			 "</plist>\n");
	break;

    case PRINTCAP_SOLARIS :
       /*
	* Each printer is put in the file as:
	*
	*    _all:all=Printer1,Printer2,Printer3,...,PrinterN
	*    _default:use=DefaultPrinter
	*    Printer1:\
	*            :bsdaddr=ServerName,Printer1:\
	*            :description=Description:
	*    Printer2:
	*            :bsdaddr=ServerName,Printer2:\
	*            :description=Description:
	*    Printer3:
	*            :bsdaddr=ServerName,Printer3:\
	*            :description=Description:
	*    ...
	*    PrinterN:
	*            :bsdaddr=ServerName,PrinterN:\
	*            :description=Description:
	*/

	cupsFilePuts(fp, "_all:all=");
	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
	     p;
	     p = (cupsd_printer_t *)cupsArrayCurrent(Printers))
	  cupsFilePrintf(fp, "%s%c", p->name,
			 cupsArrayNext(Printers) ? ',' : '\n');

	if (DefaultPrinter)
	  cupsFilePrintf(fp, "_default:use=%s\n", DefaultPrinter->name);

	for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
	     p;
	     p = (cupsd_printer_t *)cupsArrayNext(Printers))
	  cupsFilePrintf(fp, "%s:\\\n"
			     "\t:bsdaddr=%s,%s:\\\n"
			     "\t:description=%s:\n",
			 p->name, ServerName, p->name,
			 p->info ? p->info : "");
	break;
  }

 /*
  * Close the file...
  */

  cupsFileClose(fp);
}


/*
 * 'add_printer_defaults()' - Add name-default attributes to the printer attributes.
 */

static void
add_printer_defaults(cupsd_printer_t *p)/* I - Printer */
{
  int		i;			/* Looping var */
  int		num_options;		/* Number of default options */
  cups_option_t	*options,		/* Default options */
		*option;		/* Current option */
  char		name[256];		/* name-default */


 /*
  * Maintain a common array of default attribute names...
  */

  if (!CommonDefaults)
  {
    CommonDefaults = cupsArrayNew((cups_array_func_t)strcmp, NULL);

    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("copies-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("document-format-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("finishings-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-account-id-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-accounting-user-id-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-cancel-after-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-hold-until-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-priority-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("job-sheets-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("media-col-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("notify-lease-duration-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("notify-events-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("number-up-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("orientation-requested-default"));
    cupsArrayAdd(CommonDefaults, _cupsStrAlloc("print-quality-default"));
  }

 /*
  * Add all of the default options from the .conf files...
  */

  for (num_options = 0, options = NULL, i = p->num_options, option = p->options;
       i > 0;
       i --, option ++)
  {
    if (strcmp(option->name, "ipp-options") &&
	strcmp(option->name, "job-sheets") &&
        strcmp(option->name, "lease-duration"))
    {
      snprintf(name, sizeof(name), "%s-default", option->name);
      num_options = cupsAddOption(name, option->value, num_options, &options);

      if (!cupsArrayFind(CommonDefaults, name))
        cupsArrayAdd(CommonDefaults, _cupsStrAlloc(name));
    }
  }

 /*
  * Convert options to IPP attributes...
  */

  cupsEncodeOptions2(p->attrs, num_options, options, IPP_TAG_PRINTER);
  cupsFreeOptions(num_options, options);

 /*
  * Add standard -default attributes as needed...
  */

  if (!cupsGetOption("copies", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default",
                  1);

  if (!cupsGetOption("document-format", p->num_options, p->options))
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
        	 "document-format-default", NULL, "application/octet-stream");

  if (!cupsGetOption("job-cancel-after", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, MaxJobTime > 0 ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
		  "job-cancel-after-default", MaxJobTime);

  if (!cupsGetOption("job-hold-until", p->num_options, p->options))
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                 "job-hold-until-default", NULL, "no-hold");

  if (!cupsGetOption("job-priority", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                  "job-priority-default", 50);

  if (!cupsGetOption("number-up", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                  "number-up-default", 1);

  if (!cupsGetOption("notify-lease-duration", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
        	  "notify-lease-duration-default", DefaultLeaseDuration);

  if (!cupsGetOption("notify-events", p->num_options, p->options))
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
        	 "notify-events-default", NULL, "job-completed");

  if (!cupsGetOption("orientation-requested", p->num_options, p->options))
    ippAddString(p->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE,
                 "orientation-requested-default", NULL, NULL);

  if (!cupsGetOption("print-quality", p->num_options, p->options))
    ippAddInteger(p->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
                  "print-quality-default", IPP_QUALITY_NORMAL);
}


/*
 * 'add_printer_filter()' - Add a MIME filter for a printer.
 */

static void
add_printer_filter(
    cupsd_printer_t  *p,		/* I - Printer to add to */
    mime_type_t	     *filtertype,	/* I - Filter or prefilter MIME type */
    const char       *filter)		/* I - Filter to add */
{
  char		super[MIME_MAX_SUPER],	/* Super-type for filter */
		type[MIME_MAX_TYPE],	/* Type for filter */
		dsuper[MIME_MAX_SUPER],	/* Destination super-type for filter */
		dtype[MIME_MAX_TYPE],	/* Destination type for filter */
		dest[MIME_MAX_SUPER + MIME_MAX_TYPE + 2],
					/* Destination super/type */
		program[1024];		/* Program/filter name */
  int		cost;			/* Cost of filter */
  size_t	maxsize = 0;		/* Maximum supported file size */
  mime_type_t	*temptype,		/* MIME type looping var */
		*desttype;		/* Destination MIME type */
  mime_filter_t	*filterptr;		/* MIME filter */
  char		filename[1024];		/* Full filter filename */


  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "add_printer_filter(p=%p(%s), filtertype=%p(%s/%s), "
		  "filter=\"%s\")", p, p->name, filtertype, filtertype->super,
		  filtertype->type, filter);

 /*
  * Parse the filter string; it should be in one of the following formats:
  *
  *     source/type cost program
  *     source/type cost maxsize(nnnn) program
  *     source/type dest/type cost program
  *     source/type dest/type cost maxsize(nnnn) program
  */

  if (sscanf(filter, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
             super, type, dsuper, dtype, &cost, program) == 6)
  {
    snprintf(dest, sizeof(dest), "%s/%s/%s", p->name, dsuper, dtype);

    if ((desttype = mimeType(MimeDatabase, "printer", dest)) == NULL)
    {
      desttype = mimeAddType(MimeDatabase, "printer", dest);
      if (!p->dest_types)
        p->dest_types = cupsArrayNew(NULL, NULL);

      cupsArrayAdd(p->dest_types, desttype);
    }

  }
  else
  {
    if (sscanf(filter, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type, &cost,
               program) == 4)
    {
      desttype = filtertype;
    }
    else
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "%s: invalid filter string \"%s\"!",
                      p->name, filter);
      return;
    }
  }

  if (!strncmp(program, "maxsize(", 8))
  {
    char	*ptr;			/* Pointer into maxsize(nnnn) program */

    maxsize = (size_t)strtoll(program + 8, &ptr, 10);

    if (*ptr != ')')
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "%s: invalid filter string \"%s\"!",
                      p->name, filter);
      return;
    }

    ptr ++;
    while (_cups_isspace(*ptr))
      ptr ++;

    _cups_strcpy(program, ptr);
  }

 /*
  * Check permissions on the filter and its containing directory...
  */

  if (strcmp(program, "-"))
  {
    if (program[0] == '/')
      strlcpy(filename, program, sizeof(filename));
    else
      snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);

    _cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !RunUser,
                   cupsdLogFCMessage, p);
  }

 /*
  * Add the filter to the MIME database, supporting wildcards as needed...
  */

  for (temptype = mimeFirstType(MimeDatabase);
       temptype;
       temptype = mimeNextType(MimeDatabase))
    if (((super[0] == '*' && _cups_strcasecmp(temptype->super, "printer")) ||
         !_cups_strcasecmp(temptype->super, super)) &&
        (type[0] == '*' || !_cups_strcasecmp(temptype->type, type)))
    {
      if (desttype != filtertype)
      {
        cupsdLogMessage(CUPSD_LOG_DEBUG2,
		        "add_printer_filter: %s: adding filter %s/%s %s/%s %d "
		        "%s", p->name, temptype->super, temptype->type,
		        desttype->super, desttype->type,
		        cost, program);
        filterptr = mimeAddFilter(MimeDatabase, temptype, desttype, cost,
	                          program);

        if (!mimeFilterLookup(MimeDatabase, desttype, filtertype))
        {
          cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                  "add_printer_filter: %s: adding filter %s/%s %s/%s "
	                  "0 -", p->name, desttype->super, desttype->type,
		          filtertype->super, filtertype->type);
          mimeAddFilter(MimeDatabase, desttype, filtertype, 0, "-");
        }
      }
      else
      {
        cupsdLogMessage(CUPSD_LOG_DEBUG2,
		        "add_printer_filter: %s: adding filter %s/%s %s/%s %d "
		        "%s", p->name, temptype->super, temptype->type,
		        filtertype->super, filtertype->type,
		        cost, program);
        filterptr = mimeAddFilter(MimeDatabase, temptype, filtertype, cost,
	                          program);
      }

      if (filterptr)
	filterptr->maxsize = maxsize;
    }
}


/*
 * 'add_printer_formats()' - Add document-format-supported values for a printer.
 */

static void
add_printer_formats(cupsd_printer_t *p)	/* I - Printer */
{
  int		i;			/* Looping var */
  mime_type_t	*type;			/* Current MIME type */
  cups_array_t	*filters;		/* Filters */
  ipp_attribute_t *attr;		/* document-format-supported attribute */
  char		mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
					/* MIME type name */


 /*
  * Raw (and remote) queues advertise all of the supported MIME
  * types...
  */

  cupsArrayDelete(p->filetypes);
  p->filetypes = NULL;

  if (p->raw)
  {
    ippAddStrings(p->attrs, IPP_TAG_PRINTER,
                  (ipp_tag_t)(IPP_TAG_MIMETYPE | IPP_TAG_COPY),
                  "document-format-supported", NumMimeTypes, NULL, MimeTypes);
    return;
  }

 /*
  * Otherwise, loop through the supported MIME types and see if there
  * are filters for them...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_printer_formats: %d types, %d filters",
                  mimeNumTypes(MimeDatabase), mimeNumFilters(MimeDatabase));

  p->filetypes = cupsArrayNew(NULL, NULL);

  for (type = mimeFirstType(MimeDatabase);
       type;
       type = mimeNextType(MimeDatabase))
  {
    if (!_cups_strcasecmp(type->super, "printer"))
      continue;

    snprintf(mimetype, sizeof(mimetype), "%s/%s", type->super, type->type);

    if ((filters = mimeFilter(MimeDatabase, type, p->filetype, NULL)) != NULL)
    {
      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "add_printer_formats: %s: %s needs %d filters",
                      p->name, mimetype, cupsArrayCount(filters));

      cupsArrayDelete(filters);
      cupsArrayAdd(p->filetypes, type);
    }
    else
      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "add_printer_formats: %s: %s not supported",
                      p->name, mimetype);
  }

 /*
  * Add the file formats that can be filtered...
  */

  if ((type = mimeType(MimeDatabase, "application", "octet-stream")) == NULL ||
      !cupsArrayFind(p->filetypes, type))
    i = 1;
  else
    i = 0;

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "add_printer_formats: %s: %d supported types",
                  p->name, cupsArrayCount(p->filetypes) + i);

  attr = ippAddStrings(p->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
                       "document-format-supported",
                       cupsArrayCount(p->filetypes) + i, NULL, NULL);

  if (i)
    attr->values[0].string.text = _cupsStrAlloc("application/octet-stream");

  for (type = (mime_type_t *)cupsArrayFirst(p->filetypes);
       type;
       i ++, type = (mime_type_t *)cupsArrayNext(p->filetypes))
  {
    snprintf(mimetype, sizeof(mimetype), "%s/%s", type->super, type->type);

    attr->values[i].string.text = _cupsStrAlloc(mimetype);
  }

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
  {
    char		pdl[1024];	/* Buffer to build pdl list */
    mime_filter_t	*filter;	/* MIME filter looping var */


   /*
    * We only support raw printing if this is not a Tioga PrintJobMgr based
    * queue and if application/octet-stream is a known type...
    */

    for (filter = (mime_filter_t *)cupsArrayFirst(MimeDatabase->filters);
	 filter;
	 filter = (mime_filter_t *)cupsArrayNext(MimeDatabase->filters))
    {
      if (filter->dst == p->filetype && strstr(filter->filter, "PrintJobMgr"))
	break;
    }

    pdl[0] = '\0';

    if (!filter && mimeType(MimeDatabase, "application", "octet-stream"))
      strlcat(pdl, "application/octet-stream,", sizeof(pdl));

   /*
    * Then list a bunch of formats that are supported by the printer...
    */

    for (type = (mime_type_t *)cupsArrayFirst(p->filetypes);
	 type;
	 type = (mime_type_t *)cupsArrayNext(p->filetypes))
    {
      if (!_cups_strcasecmp(type->super, "application"))
      {
        if (!_cups_strcasecmp(type->type, "pdf"))
	  strlcat(pdl, "application/pdf,", sizeof(pdl));
        else if (!_cups_strcasecmp(type->type, "postscript"))
	  strlcat(pdl, "application/postscript,", sizeof(pdl));
      }
      else if (!_cups_strcasecmp(type->super, "image"))
      {
        if (!_cups_strcasecmp(type->type, "jpeg"))
	  strlcat(pdl, "image/jpeg,", sizeof(pdl));
	else if (!_cups_strcasecmp(type->type, "png"))
	  strlcat(pdl, "image/png,", sizeof(pdl));
	else if (!_cups_strcasecmp(type->type, "pwg-raster"))
	  strlcat(pdl, "image/pwg-raster,", sizeof(pdl));
      }
    }

    if (pdl[0])
      pdl[strlen(pdl) - 1] = '\0';	/* Remove trailing comma */

    cupsdSetString(&p->pdl, pdl);
  }
#endif /* HAVE_DNSSD || HAVE_AVAHI */
}


/*
 * 'compare_printers()' - Compare two printers.
 */

static int				/* O - Result of comparison */
compare_printers(void *first,		/* I - First printer */
                 void *second,		/* I - Second printer */
		 void *data)		/* I - App data (not used) */
{
  (void)data;

  return (_cups_strcasecmp(((cupsd_printer_t *)first)->name,
                     ((cupsd_printer_t *)second)->name));
}


/*
 * 'delete_printer_filters()' - Delete all MIME filters for a printer.
 */

static void
delete_printer_filters(
    cupsd_printer_t *p)			/* I - Printer to remove from */
{
  mime_filter_t	*filter;		/* MIME filter looping var */
  mime_type_t	*type;			/* Destination types for filters */


 /*
  * Range check input...
  */

  if (p == NULL)
    return;

 /*
  * Remove all filters from the MIME database that have a destination
  * type == printer...
  */

  for (filter = mimeFirstFilter(MimeDatabase);
       filter;
       filter = mimeNextFilter(MimeDatabase))
    if (filter->dst == p->filetype || filter->dst == p->prefiltertype ||
        cupsArrayFind(p->dest_types, filter->dst))
    {
     /*
      * Delete the current filter...
      */

      mimeDeleteFilter(MimeDatabase, filter);
    }

  for (type = (mime_type_t *)cupsArrayFirst(p->dest_types);
       type;
       type = (mime_type_t *)cupsArrayNext(p->dest_types))
    mimeDeleteType(MimeDatabase, type);

  cupsArrayDelete(p->dest_types);
  p->dest_types = NULL;

  cupsdSetPrinterReasons(p, "-cups-insecure-filter-warning"
                            ",cups-missing-filter-warning");
}


/*
 * 'dirty_printer()' - Mark config and state files dirty for the specified
 *                     printer.
 */

static void
dirty_printer(cupsd_printer_t *p)	/* I - Printer */
{
  if (p->type & CUPS_PRINTER_CLASS)
    cupsdMarkDirty(CUPSD_DIRTY_CLASSES);
  else
    cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);

  if (PrintcapFormat == PRINTCAP_PLIST)
    cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);
}


/*
 * 'load_ppd()' - Load a cached PPD file, updating the cache as needed.
 */

static void
load_ppd(cupsd_printer_t *p)		/* I - Printer */
{
  int		i, j;			/* Looping vars */
  char		cache_name[1024];	/* Cache filename */
  struct stat	cache_info;		/* Cache file info */
  ppd_file_t	*ppd;			/* PPD file */
  char		ppd_name[1024];		/* PPD filename */
  struct stat	ppd_info;		/* PPD file info */
  char		strings_name[1024];	/* Strings filename */
  int		num_media;		/* Number of media values */
  ppd_size_t	*size;			/* Current PPD size */
  ppd_option_t	*duplex,		/* Duplex option */
		*output_bin,		/* OutputBin option */
		*output_mode,		/* OutputMode option */
		*resolution;		/* (Set|JCL|)Resolution option */
  ppd_choice_t	*choice;		/* Current PPD choice */
  ppd_attr_t	*ppd_attr;		/* PPD attribute */
  int		xdpi,			/* Horizontal resolution */
		ydpi;			/* Vertical resolution */
  const char	*resptr;		/* Pointer into resolution keyword */
  pwg_size_t	*pwgsize;		/* Current PWG size */
  pwg_map_t	*pwgsource,		/* Current PWG source */
		*pwgtype;		/* Current PWG type */
  ipp_attribute_t *attr;		/* Attribute data */
  _ipp_value_t	*val;			/* Attribute value */
  int		num_finishings,		/* Number of finishings */
		finishings[100];	/* finishings-supported values */
  int		num_qualities,		/* Number of print-quality values */
		qualities[3];		/* print-quality values */
  int		num_margins,		/* Number of media-*-margin-supported values */
		margins[16];		/* media-*-margin-supported values */
  const char	*filter,		/* Current filter */
		*mandatory;		/* Current mandatory attribute */
  static const char * const pwg_raster_document_types[] =
		{
		  "black_1",
		  "sgray_8",
		  "srgb_8"
		};
  static const char * const sides[3] =	/* sides-supported values */
		{
		  "one-sided",
		  "two-sided-long-edge",
		  "two-sided-short-edge"
		};
  static const char * const standard_commands[] =
		{			/* Standard CUPS commands */
		  "AutoConfigure",
		  "Clean",
		  "PrintSelfTestPage"
		};


 /*
  * Check to see if the cache is up-to-date...
  */

  snprintf(cache_name, sizeof(cache_name), "%s/%s.data", CacheDir, p->name);
  if (stat(cache_name, &cache_info))
    cache_info.st_mtime = 0;

  snprintf(ppd_name, sizeof(ppd_name), "%s/ppd/%s.ppd", ServerRoot, p->name);
  if (stat(ppd_name, &ppd_info))
    ppd_info.st_mtime = 1;

  snprintf(strings_name, sizeof(strings_name), "%s/%s.strings", CacheDir, p->name);

  ippDelete(p->ppd_attrs);
  p->ppd_attrs = NULL;

  _ppdCacheDestroy(p->pc);
  p->pc = NULL;

  if (cache_info.st_mtime >= ppd_info.st_mtime)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG, "load_ppd: Loading %s...", cache_name);

    if ((p->pc = _ppdCacheCreateWithFile(cache_name, &p->ppd_attrs)) != NULL &&
        p->ppd_attrs)
    {
     /*
      * Loaded successfully!
      */

      return;
    }
  }

 /*
  * Reload PPD attributes from disk...
  */

  cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);

  cupsdLogMessage(CUPSD_LOG_DEBUG, "load_ppd: Loading %s...", ppd_name);

  cupsdClearString(&(p->make_model));

  p->type &= (cups_ptype_t)~CUPS_PRINTER_OPTIONS;
  p->type |= CUPS_PRINTER_BW;

  finishings[0]  = IPP_FINISHINGS_NONE;
  num_finishings = 1;

  p->ppd_attrs = ippNew();

  if ((ppd = _ppdOpenFile(ppd_name, _PPD_LOCALIZATION_NONE)) != NULL)
  {
   /*
    * Add make/model and other various attributes...
    */

    p->pc = _ppdCacheCreateWithPPD(ppd);

    if (!p->pc)
      cupsdLogMessage(CUPSD_LOG_WARN, "Unable to create cache of \"%s\": %s",
                      ppd_name, cupsLastErrorString());

    ppdMarkDefaults(ppd);

    if (ppd->color_device)
      p->type |= CUPS_PRINTER_COLOR;
    if (ppd->variable_sizes)
      p->type |= CUPS_PRINTER_VARIABLE;
    if (!ppd->manual_copies)
      p->type |= CUPS_PRINTER_COPIES;
    if ((ppd_attr = ppdFindAttr(ppd, "cupsFax", NULL)) != NULL)
      if (ppd_attr->value && !_cups_strcasecmp(ppd_attr->value, "true"))
	p->type |= CUPS_PRINTER_FAX;

    ippAddBoolean(p->ppd_attrs, IPP_TAG_PRINTER, "color-supported", (char)ppd->color_device);

    if (p->pc && p->pc->charge_info_uri)
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
                   "printer-charge-info-uri", NULL, p->pc->charge_info_uri);

    if (p->pc && p->pc->account_id)
      ippAddBoolean(p->ppd_attrs, IPP_TAG_PRINTER, "job-account-id-supported",
                    1);

    if (p->pc && p->pc->accounting_user_id)
      ippAddBoolean(p->ppd_attrs, IPP_TAG_PRINTER,
                    "job-accounting-user-id-supported", 1);

    if (p->pc && p->pc->password)
    {
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                   "job-password-encryption-supported", NULL, "none");
      ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                    "job-password-supported", (int)strlen(p->pc->password));
    }

    if (ppd->throughput)
    {
      ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "pages-per-minute", ppd->throughput);
      if (ppd->color_device)
	ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "pages-per-minute-color", ppd->throughput);
    }
    else
    {
     /*
      * When there is no speed information, just say "1 page per minute".
      */

      ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		    "pages-per-minute", 1);
      if (ppd->color_device)
	ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "pages-per-minute-color", 1);
    }

    if ((ppd_attr = ppdFindAttr(ppd, "1284DeviceId", NULL)) != NULL)
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, ppd_attr->value);

    num_qualities = 0;

    if ((output_mode = ppdFindOption(ppd, "OutputMode")) ||
        (output_mode = ppdFindOption(ppd, "cupsPrintQuality")))
    {
      if (ppdFindChoice(output_mode, "draft") ||
          ppdFindChoice(output_mode, "fast"))
        qualities[num_qualities ++] = IPP_QUALITY_DRAFT;

      qualities[num_qualities ++] = IPP_QUALITY_NORMAL;

      if (ppdFindChoice(output_mode, "best") ||
          ppdFindChoice(output_mode, "high"))
        qualities[num_qualities ++] = IPP_QUALITY_HIGH;
    }
    else if ((ppd_attr = ppdFindAttr(ppd, "APPrinterPreset", NULL)) != NULL)
    {
      do
      {
        if (strstr(ppd_attr->spec, "draft") ||
	    strstr(ppd_attr->spec, "Draft"))
	{
	  qualities[num_qualities ++] = IPP_QUALITY_DRAFT;
	  break;
	}
      }
      while ((ppd_attr = ppdFindNextAttr(ppd, "APPrinterPreset",
                                         NULL)) != NULL);

      qualities[num_qualities ++] = IPP_QUALITY_NORMAL;
      qualities[num_qualities ++] = IPP_QUALITY_HIGH;
    }
    else
      qualities[num_qualities ++] = IPP_QUALITY_NORMAL;

    ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
                   "print-quality-supported", num_qualities, qualities);

    if (ppd->nickname)
    {
     /*
      * The NickName can be localized in the character set specified
      * by the LanugageEncoding attribute.  However, ppdOpen2() has
      * already converted the ppd->nickname member to UTF-8 for us
      * (the original attribute value is available separately)
      */

      cupsdSetString(&p->make_model, ppd->nickname);
    }
    else if (ppd->modelname)
    {
     /*
      * Model name can only contain specific characters...
      */

      cupsdSetString(&p->make_model, ppd->modelname);
    }
    else
      cupsdSetString(&p->make_model, "Bad PPD File");

    ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
		 "printer-make-and-model", NULL, p->make_model);

    if (p->pc && p->pc->strings)
      _cupsMessageSave(strings_name, _CUPS_MESSAGE_STRINGS, p->pc->strings);

    if (!access(strings_name, R_OK))
      cupsdSetString(&p->strings, strings_name);
    else
      cupsdClearString(&p->strings);

   /*
    * Add media options from the PPD file...
    */

    if (ppd->num_sizes == 0 || !p->pc)
    {
      if (!ppdFindAttr(ppd, "APScannerOnly", NULL) && !ppdFindAttr(ppd, "cups3D", NULL))
	cupsdLogMessage(CUPSD_LOG_CRIT,
			"The PPD file for printer %s contains no media "
			"options and is therefore invalid.", p->name);

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "media-default", NULL, "unknown");
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "media-supported", NULL, "unknown");
    }
    else
    {
     /*
      * media-default
      */

      if ((size = ppdPageSize(ppd, NULL)) != NULL)
        pwgsize = _ppdCacheGetSize(p->pc, size->name);
      else
        pwgsize = NULL;

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "media-default", NULL,
		   pwgsize ? pwgsize->map.pwg : "unknown");

     /*
      * media-col-default
      */

      if (pwgsize)
      {
        ipp_t	*col;			/* Collection value */

	col = new_media_col(pwgsize);
	ippAddCollection(p->ppd_attrs, IPP_TAG_PRINTER, "media-col-default", col);
        ippDelete(col);
      }

     /*
      * media-supported
      */

      num_media = p->pc->num_sizes;
      if (p->pc->custom_min_keyword)
	num_media += 2;

      if ((attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			        "media-supported", num_media, NULL,
				NULL)) != NULL)
      {
	val = attr->values;

        for (i = p->pc->num_sizes, pwgsize = p->pc->sizes;
	     i > 0;
	     i --, pwgsize ++, val ++)
	  val->string.text = _cupsStrAlloc(pwgsize->map.pwg);

        if (p->pc->custom_min_keyword)
	{
	  val->string.text = _cupsStrAlloc(p->pc->custom_min_keyword);
	  val ++;
	  val->string.text = _cupsStrAlloc(p->pc->custom_max_keyword);
        }
      }

     /*
      * media-size-supported
      */

      num_media = p->pc->num_sizes;
      if (p->pc->custom_min_keyword)
	num_media ++;

      if ((attr = ippAddCollections(p->ppd_attrs, IPP_TAG_PRINTER,
				    "media-size-supported", num_media,
				    NULL)) != NULL)
      {
	val = attr->values;

        for (i = p->pc->num_sizes, pwgsize = p->pc->sizes;
	     i > 0;
	     i --, pwgsize ++, val ++)
	{
	  val->collection = ippNew();
	  ippAddInteger(val->collection, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
	                "x-dimension", pwgsize->width);
	  ippAddInteger(val->collection, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
	                "y-dimension", pwgsize->length);
        }

        if (p->pc->custom_min_keyword)
	{
	  val->collection = ippNew();
	  ippAddRange(val->collection, IPP_TAG_PRINTER, "x-dimension",
	              p->pc->custom_min_width, p->pc->custom_max_width);
	  ippAddRange(val->collection, IPP_TAG_PRINTER, "y-dimension",
	              p->pc->custom_min_length, p->pc->custom_max_length);
        }
      }

     /*
      * media-source-supported
      */

      if (p->pc->num_sources > 0 &&
          (attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	                        "media-source-supported", p->pc->num_sources,
			        NULL, NULL)) != NULL)
      {
	for (i = p->pc->num_sources, pwgsource = p->pc->sources,
	         val = attr->values;
	     i > 0;
	     i --, pwgsource ++, val ++)
	  val->string.text = _cupsStrAlloc(pwgsource->pwg);
      }

     /*
      * media-type-supported
      */

      if (p->pc->num_types > 0 &&
          (attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	                        "media-type-supported", p->pc->num_types,
			        NULL, NULL)) != NULL)
      {
	for (i = p->pc->num_types, pwgtype = p->pc->types,
	         val = attr->values;
	     i > 0;
	     i --, pwgtype ++, val ++)
	  val->string.text = _cupsStrAlloc(pwgtype->pwg);
      }

     /*
      * media-*-margin-supported
      */

      for (i = p->pc->num_sizes, pwgsize = p->pc->sizes, num_margins = 0;
	   i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
	   i --, pwgsize ++)
      {
        for (j = 0; j < num_margins; j ++)
	  if (pwgsize->bottom == margins[j])
	    break;

	if (j >= num_margins)
	{
	  margins[num_margins] = pwgsize->bottom;
	  num_margins ++;
	}
      }

      if (num_margins > 0)
        ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		       "media-bottom-margin-supported", num_margins, margins);
      else
        ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "media-bottom-margin-supported", 0);

      for (i = p->pc->num_sizes, pwgsize = p->pc->sizes, num_margins = 0;
	   i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
	   i --, pwgsize ++)
      {
        for (j = 0; j < num_margins; j ++)
	  if (pwgsize->left == margins[j])
	    break;

	if (j >= num_margins)
	{
	  margins[num_margins] = pwgsize->left;
	  num_margins ++;
	}
      }

      if (num_margins > 0)
        ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		       "media-left-margin-supported", num_margins, margins);
      else
        ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "media-left-margin-supported", 0);

      for (i = p->pc->num_sizes, pwgsize = p->pc->sizes, num_margins = 0;
	   i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
	   i --, pwgsize ++)
      {
        for (j = 0; j < num_margins; j ++)
	  if (pwgsize->right == margins[j])
	    break;

	if (j >= num_margins)
	{
	  margins[num_margins] = pwgsize->right;
	  num_margins ++;
	}
      }

      if (num_margins > 0)
        ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		       "media-right-margin-supported", num_margins, margins);
      else
        ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "media-right-margin-supported", 0);

      for (i = p->pc->num_sizes, pwgsize = p->pc->sizes, num_margins = 0;
	   i > 0 && num_margins < (int)(sizeof(margins) / sizeof(margins[0]));
	   i --, pwgsize ++)
      {
        for (j = 0; j < num_margins; j ++)
	  if (pwgsize->top == margins[j])
	    break;

	if (j >= num_margins)
	{
	  margins[num_margins] = pwgsize->top;
	  num_margins ++;
	}
      }

      if (num_margins > 0)
        ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		       "media-top-margin-supported", num_margins, margins);
      else
        ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
		      "media-top-margin-supported", 0);

     /*
      * media-col-database
      */

      if ((attr = ippAddCollections(p->ppd_attrs, IPP_TAG_PRINTER, "media-col-database", p->pc->num_sizes, NULL)) != NULL)
      {
       /*
	* Add each page size without source or type...
	*/

        for (i = 0, pwgsize = p->pc->sizes; i < p->pc->num_sizes; i ++, pwgsize ++)
	{
	  ipp_t *col = new_media_col(pwgsize);

	  ippSetCollection(p->ppd_attrs, &attr, i, col);
	  ippDelete(col);
	}
      }
    }

   /*
    * Output bin...
    */

    if (p->pc && p->pc->num_bins > 0)
    {
      attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			   "output-bin-supported", p->pc->num_bins,
			   NULL, NULL);

      if (attr != NULL)
      {
	for (i = 0, val = attr->values;
	     i < p->pc->num_bins;
	     i ++, val ++)
	  val->string.text = _cupsStrAlloc(p->pc->bins[i].pwg);
      }

      if ((output_bin = ppdFindOption(ppd, "OutputBin")) != NULL)
      {
	for (i = 0; i < p->pc->num_bins; i ++)
	  if (!strcmp(p->pc->bins[i].ppd, output_bin->defchoice))
	    break;

        if (i >= p->pc->num_bins)
	  i = 0;

	ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		     "output-bin-default", NULL, p->pc->bins[i].pwg);
      }
      else
        ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
	             "output-bin-default", NULL, p->pc->bins[0].pwg);
    }
    else if (((ppd_attr = ppdFindAttr(ppd, "DefaultOutputOrder",
                                     NULL)) != NULL &&
	      !_cups_strcasecmp(ppd_attr->value, "Reverse")) ||
	     (!ppd_attr && ppd->manufacturer &&	/* "Compatibility heuristic" */
	      (!_cups_strcasecmp(ppd->manufacturer, "epson") ||
	       !_cups_strcasecmp(ppd->manufacturer, "lexmark"))))
    {
     /*
      * Report that this printer has a single output bin that leaves pages face
      * up.
      */

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "output-bin-supported", NULL, "face-up");
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "output-bin-default", NULL, "face-up");
    }
    else
    {
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "output-bin-supported", NULL, "face-down");
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "output-bin-default", NULL, "face-down");
    }

   /*
    * print-color-mode...
    */

    if (ppd->color_device)
    {
      static const char * const color_modes[] =
      {
        "monochrome",
	"color"
      };

      ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                    "print-color-mode-supported", 2, NULL, color_modes);
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                   "print-color-mode-default", NULL, "color");
      ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "pwg-raster-document-type-supported", 3, NULL, pwg_raster_document_types);
    }
    else
    {
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                   "print-color-mode-supported", NULL, "monochrome");
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                   "print-color-mode-default", NULL, "monochrome");
      ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "pwg-raster-document-type-supported", 2, NULL, pwg_raster_document_types);
    }

   /*
    * Mandatory job attributes, if any...
    */

    if (p->pc && cupsArrayCount(p->pc->mandatory) > 0)
    {
      int	count = cupsArrayCount(p->pc->mandatory);
					/* Number of mandatory attributes */

      attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                           "printer-mandatory-job-attributes", count, NULL,
                           NULL);

      for (val = attr->values,
               mandatory = (char *)cupsArrayFirst(p->pc->mandatory);
           mandatory;
           val ++, mandatory = (char *)cupsArrayNext(p->pc->mandatory))
        val->string.text = _cupsStrAlloc(mandatory);
    }

   /*
    * Printer resolutions...
    */

    if ((resolution = ppdFindOption(ppd, "Resolution")) == NULL)
      if ((resolution = ppdFindOption(ppd, "JCLResolution")) == NULL)
        if ((resolution = ppdFindOption(ppd, "SetResolution")) == NULL)
	  resolution = ppdFindOption(ppd, "CNRes_PGP");

    if (resolution)
    {
     /*
      * Report all supported resolutions...
      */

      attr = ippAddResolutions(p->ppd_attrs, IPP_TAG_PRINTER, "printer-resolution-supported", resolution->num_choices, IPP_RES_PER_INCH, NULL, NULL);

      for (i = 0, choice = resolution->choices;
           i < resolution->num_choices;
	   i ++, choice ++)
      {
        xdpi = ydpi = (int)strtol(choice->choice, (char **)&resptr, 10);
	if (resptr > choice->choice && xdpi > 0 && *resptr == 'x')
	  ydpi = (int)strtol(resptr + 1, (char **)&resptr, 10);

	if (xdpi <= 0 || ydpi <= 0)
	{
	  cupsdLogMessage(CUPSD_LOG_WARN,
	                  "Bad resolution \"%s\" for printer %s.",
			  choice->choice, p->name);
	  xdpi = ydpi = 300;
	}

        attr->values[i].resolution.xres  = xdpi;
        attr->values[i].resolution.yres  = ydpi;
        attr->values[i].resolution.units = IPP_RES_PER_INCH;

        if (choice->marked)
	  ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, xdpi, ydpi);

        if (i == 0)
	  ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xdpi, ydpi);
      }
    }
    else if ((ppd_attr = ppdFindAttr(ppd, "DefaultResolution", NULL)) != NULL &&
             ppd_attr->value)
    {
     /*
      * Just the DefaultResolution to report...
      */

      xdpi = ydpi = (int)strtol(ppd_attr->value, (char **)&resptr, 10);
      if (resptr > ppd_attr->value && xdpi > 0)
      {
	if (*resptr == 'x')
	  ydpi = (int)strtol(resptr + 1, (char **)&resptr, 10);
	else
	  ydpi = xdpi;
      }

      if (xdpi <= 0 || ydpi <= 0)
      {
	cupsdLogMessage(CUPSD_LOG_WARN,
			"Bad default resolution \"%s\" for printer %s.",
			ppd_attr->value, p->name);
	xdpi = ydpi = 300;
      }

      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
		       "printer-resolution-default", IPP_RES_PER_INCH,
		       xdpi, ydpi);
      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
		       "printer-resolution-supported", IPP_RES_PER_INCH,
		       xdpi, ydpi);
      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, xdpi, ydpi);
    }
    else
    {
     /*
      * No resolutions in PPD - make one up...
      */

      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
		       "printer-resolution-default", IPP_RES_PER_INCH,
		       300, 300);
      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER,
		       "printer-resolution-supported", IPP_RES_PER_INCH,
		       300, 300);
      ippAddResolution(p->ppd_attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", IPP_RES_PER_INCH, 300, 300);
    }

   /*
    * Duplexing, etc...
    */

    ppdMarkDefaults(ppd);

    if ((duplex = ppdFindOption(ppd, "Duplex")) == NULL)
      if ((duplex = ppdFindOption(ppd, "EFDuplex")) == NULL)
	if ((duplex = ppdFindOption(ppd, "EFDuplexing")) == NULL)
	  if ((duplex = ppdFindOption(ppd, "KD03Duplex")) == NULL)
	    duplex = ppdFindOption(ppd, "JCLDuplex");

    if (duplex && duplex->num_choices > 1 &&
	!ppdInstallableConflict(ppd, duplex->keyword, "DuplexTumble"))
    {
      p->type |= CUPS_PRINTER_DUPLEX;

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "pwg-raster-document-sheet-back", NULL, "normal");

      ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		    "sides-supported", 3, NULL, sides);

      if (!_cups_strcasecmp(duplex->defchoice, "DuplexTumble"))
	ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		     "sides-default", NULL, "two-sided-short-edge");
      else if (!_cups_strcasecmp(duplex->defchoice, "DuplexNoTumble"))
	ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		     "sides-default", NULL, "two-sided-long-edge");
      else
	ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		     "sides-default", NULL, "one-sided");
    }
    else
    {
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "sides-supported", NULL, "one-sided");
      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "sides-default", NULL, "one-sided");
    }

    if (ppdFindOption(ppd, "Collate") != NULL)
      p->type |= CUPS_PRINTER_COLLATE;

    if (p->pc && p->pc->finishings)
    {
      _pwg_finishings_t	*fin;		/* Current finishing value */

      for (fin = (_pwg_finishings_t *)cupsArrayFirst(p->pc->finishings); fin; fin = (_pwg_finishings_t *)cupsArrayNext(p->pc->finishings))
      {
        if (num_finishings < (int)(sizeof(finishings) / sizeof(finishings[0])))
          finishings[num_finishings++] = (int)fin->value;

        switch (fin->value)
        {
          case IPP_FINISHINGS_BIND :
          case IPP_FINISHINGS_BIND_LEFT :
          case IPP_FINISHINGS_BIND_TOP :
          case IPP_FINISHINGS_BIND_RIGHT :
          case IPP_FINISHINGS_BIND_BOTTOM :
          case IPP_FINISHINGS_EDGE_STITCH :
          case IPP_FINISHINGS_EDGE_STITCH_LEFT :
          case IPP_FINISHINGS_EDGE_STITCH_TOP :
          case IPP_FINISHINGS_EDGE_STITCH_RIGHT :
          case IPP_FINISHINGS_EDGE_STITCH_BOTTOM :
              p->type |= CUPS_PRINTER_BIND;
              break;

          case IPP_FINISHINGS_COVER :
              p->type |= CUPS_PRINTER_COVER;
              break;

          case IPP_FINISHINGS_PUNCH :
          case IPP_FINISHINGS_PUNCH_TOP_LEFT :
          case IPP_FINISHINGS_PUNCH_BOTTOM_LEFT :
          case IPP_FINISHINGS_PUNCH_TOP_RIGHT :
          case IPP_FINISHINGS_PUNCH_BOTTOM_RIGHT :
          case IPP_FINISHINGS_PUNCH_DUAL_LEFT :
          case IPP_FINISHINGS_PUNCH_DUAL_TOP :
          case IPP_FINISHINGS_PUNCH_DUAL_RIGHT :
          case IPP_FINISHINGS_PUNCH_DUAL_BOTTOM :
          case IPP_FINISHINGS_PUNCH_TRIPLE_LEFT :
          case IPP_FINISHINGS_PUNCH_TRIPLE_TOP :
          case IPP_FINISHINGS_PUNCH_TRIPLE_RIGHT :
          case IPP_FINISHINGS_PUNCH_TRIPLE_BOTTOM :
          case IPP_FINISHINGS_PUNCH_QUAD_LEFT :
          case IPP_FINISHINGS_PUNCH_QUAD_TOP :
          case IPP_FINISHINGS_PUNCH_QUAD_RIGHT :
          case IPP_FINISHINGS_PUNCH_QUAD_BOTTOM :
              p->type |= CUPS_PRINTER_PUNCH;
              break;

          case IPP_FINISHINGS_STAPLE :
          case IPP_FINISHINGS_STAPLE_TOP_LEFT :
          case IPP_FINISHINGS_STAPLE_BOTTOM_LEFT :
          case IPP_FINISHINGS_STAPLE_TOP_RIGHT :
          case IPP_FINISHINGS_STAPLE_BOTTOM_RIGHT :
          case IPP_FINISHINGS_STAPLE_DUAL_LEFT :
          case IPP_FINISHINGS_STAPLE_DUAL_TOP :
          case IPP_FINISHINGS_STAPLE_DUAL_RIGHT :
          case IPP_FINISHINGS_STAPLE_DUAL_BOTTOM :
          case IPP_FINISHINGS_STAPLE_TRIPLE_LEFT :
          case IPP_FINISHINGS_STAPLE_TRIPLE_TOP :
          case IPP_FINISHINGS_STAPLE_TRIPLE_RIGHT :
          case IPP_FINISHINGS_STAPLE_TRIPLE_BOTTOM :
              p->type |= CUPS_PRINTER_STAPLE;
              break;

          default :
              break;
        }
      }
    }

    if (p->pc && p->pc->templates)
    {
      const char 	*template;	/* Finishing template */
      ipp_attribute_t	*fin_col_db;	/* finishings-col-database attribute */
      ipp_t		*fin_col;	/* finishings-col value */

      fin_col_db = ippAddCollections(p->ppd_attrs, IPP_TAG_PRINTER, "finishings-col-database", cupsArrayCount(p->pc->templates), NULL);
      for (i = 0, template = (const char *)cupsArrayFirst(p->pc->templates); template; i ++, template = (const char *)cupsArrayNext(p->pc->templates))
      {
        fin_col = ippNew();
        ippAddString(fin_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "finishing-template", NULL, template);
        ippSetCollection(p->ppd_attrs, &fin_col_db, i, fin_col);
        ippDelete(fin_col);
      }
    }

    for (i = 0; i < ppd->num_sizes; i ++)
      if (ppd->sizes[i].length > 1728)
	p->type |= CUPS_PRINTER_LARGE;
      else if (ppd->sizes[i].length > 1008)
	p->type |= CUPS_PRINTER_MEDIUM;
      else
	p->type |= CUPS_PRINTER_SMALL;

    if ((ppd_attr = ppdFindAttr(ppd, "APICADriver", NULL)) != NULL &&
        ppd_attr->value && !_cups_strcasecmp(ppd_attr->value, "true"))
    {
      if ((ppd_attr = ppdFindAttr(ppd, "APScannerOnly", NULL)) != NULL &&
	  ppd_attr->value && !_cups_strcasecmp(ppd_attr->value, "true"))
        p->type |= CUPS_PRINTER_SCANNER;
      else
        p->type |= CUPS_PRINTER_MFP;
    }

   /*
    * Scan the filters in the PPD file...
    */

    if (p->pc)
    {
      for (filter = (const char *)cupsArrayFirst(p->pc->filters);
	   filter;
	   filter = (const char *)cupsArrayNext(p->pc->filters))
      {
	if (!_cups_strncasecmp(filter, "application/vnd.cups-command", 28) &&
	    _cups_isspace(filter[28]))
	{
	  p->type |= CUPS_PRINTER_COMMANDS;
	  break;
	}
      }
    }

    if (p->type & CUPS_PRINTER_COMMANDS)
    {
      char	*commands,		/* Copy of commands */
		*start,			/* Start of name */
		*end;			/* End of name */
      int	count;			/* Number of commands */

      if ((ppd_attr = ppdFindAttr(ppd, "cupsCommands", NULL)) != NULL)
      {
	for (count = 0, start = ppd_attr->value; *start; count ++)
	{
	  while (_cups_isspace(*start))
	    start ++;

	  if (!*start)
	    break;

	  while (*start && !isspace(*start & 255))
	    start ++;
	}
      }
      else
	count = 0;

      if (count > 0)
      {
       /*
	* Make a copy of the commands string and count how many commands there
	* are...
	*/

	attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
			     "printer-commands", count, NULL, NULL);

	commands = strdup(ppd_attr->value);

	for (count = 0, start = commands; *start; count ++)
	{
	  while (isspace(*start & 255))
	    start ++;

	  if (!*start)
	    break;

	  end = start;
	  while (*end && !isspace(*end & 255))
	    end ++;

	  if (*end)
	    *end++ = '\0';

	  attr->values[count].string.text = _cupsStrAlloc(start);

	  start = end;
	}

	free(commands);
      }
      else
      {
       /*
	* Add the standard list of commands...
	*/

	ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		      "printer-commands",
		      (int)(sizeof(standard_commands) /
			    sizeof(standard_commands[0])), NULL,
		      standard_commands);
      }
    }
    else
    {
     /*
      * No commands supported...
      */

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
		   "printer-commands", NULL, "none");
    }

   /*
    * Show current and available port monitors for this printer...
    */

    ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "port-monitor",
		 NULL, p->port_monitor ? p->port_monitor : "none");

    for (i = 1, ppd_attr = ppdFindAttr(ppd, "cupsPortMonitor", NULL);
	 ppd_attr;
	 i ++, ppd_attr = ppdFindNextAttr(ppd, "cupsPortMonitor", NULL));

    if (ppd->protocols)
    {
      if (strstr(ppd->protocols, "TBCP"))
	i ++;
      else if (strstr(ppd->protocols, "BCP"))
	i ++;
    }

    attr = ippAddStrings(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_NAME,
			 "port-monitor-supported", i, NULL, NULL);

    attr->values[0].string.text = _cupsStrAlloc("none");

    for (i = 1, ppd_attr = ppdFindAttr(ppd, "cupsPortMonitor", NULL);
	 ppd_attr;
	 i ++, ppd_attr = ppdFindNextAttr(ppd, "cupsPortMonitor", NULL))
      attr->values[i].string.text = _cupsStrAlloc(ppd_attr->value);

    if (ppd->protocols)
    {
      if (strstr(ppd->protocols, "TBCP"))
	attr->values[i].string.text = _cupsStrAlloc("tbcp");
      else if (strstr(ppd->protocols, "BCP"))
	attr->values[i].string.text = _cupsStrAlloc("bcp");
    }

    if (ppdFindAttr(ppd, "APRemoteQueueID", NULL))
      p->type |= CUPS_PRINTER_REMOTE;

#ifdef HAVE_APPLICATIONSERVICES_H
   /*
    * Convert the file referenced in APPrinterIconPath to a 128x128 PNG
    * and save it as cacheDir/printername.png
    */

    if ((ppd_attr = ppdFindAttr(ppd, "APPrinterIconPath", NULL)) != NULL &&
        ppd_attr->value &&
	!_cupsFileCheck(ppd_attr->value, _CUPS_FILE_CHECK_FILE, !RunUser,
	                cupsdLogFCMessage, p))
    {
      CGImageRef	imageRef = NULL;/* Current icon image */
      CGImageRef	biggestIconRef = NULL;
					/* Biggest icon image */
      CGImageRef	closestTo128IconRef = NULL;
					/* Icon image closest to and >= 128 */
      CGImageSourceRef	sourceRef;	/* The file's image source */
      char		outPath[HTTP_MAX_URI];
					/* The path to the PNG file */
      CFURLRef		outUrl;		/* The URL made from the outPath */
      CFURLRef		icnsFileUrl;	/* The URL of the original ICNS icon file */
      CGImageDestinationRef destRef;	/* The image destination to write */
      size_t		bytesPerRow;	/* The bytes per row used for resizing */
      CGContextRef	context;	/* The CG context used for resizing */

      snprintf(outPath, sizeof(outPath), "%s/%s.png", CacheDir, p->name);
      outUrl      = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)outPath, (CFIndex)strlen(outPath), FALSE);
      icnsFileUrl = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)ppd_attr->value, (CFIndex)strlen(ppd_attr->value), FALSE);
      if (outUrl && icnsFileUrl)
      {
        sourceRef = CGImageSourceCreateWithURL(icnsFileUrl, NULL);
        if (sourceRef)
        {
          for (i = 0; i < (int)CGImageSourceGetCount(sourceRef); i ++)
          {
            imageRef = CGImageSourceCreateImageAtIndex(sourceRef, (size_t)i, NULL);
	    if (!imageRef)
	      continue;

            if (CGImageGetWidth(imageRef) == CGImageGetHeight(imageRef))
            {
             /*
              * Loop through remembering the icon closest to 128 but >= 128
              * and then remember the largest icon.
              */

              if (CGImageGetWidth(imageRef) >= 128 &&
		  (!closestTo128IconRef ||
		   CGImageGetWidth(imageRef) <
		       CGImageGetWidth(closestTo128IconRef)))
              {
                CGImageRelease(closestTo128IconRef);
                CGImageRetain(imageRef);
                closestTo128IconRef = imageRef;
              }

              if (!biggestIconRef ||
		  CGImageGetWidth(imageRef) > CGImageGetWidth(biggestIconRef))
              {
                CGImageRelease(biggestIconRef);
                CGImageRetain(imageRef);
                biggestIconRef = imageRef;
              }
	    }

	    CGImageRelease(imageRef);
          }

          if (biggestIconRef)
          {
           /*
            * If biggestIconRef is NULL, we found no icons. Otherwise we first
            * want the closest to 128, but if none are larger than 128, we want
            * the largest icon available.
            */

            imageRef = closestTo128IconRef ? closestTo128IconRef :
                                             biggestIconRef;
            CGImageRetain(imageRef);
            CGImageRelease(biggestIconRef);
            if (closestTo128IconRef)
	      CGImageRelease(closestTo128IconRef);
            destRef = CGImageDestinationCreateWithURL(outUrl, kUTTypePNG, 1,
                                                      NULL);
            if (destRef)
            {
              if (CGImageGetWidth(imageRef) != 128)
              {
                bytesPerRow = CGImageGetBytesPerRow(imageRef) /
                              CGImageGetWidth(imageRef) * 128;
                context     = CGBitmapContextCreate(NULL, 128, 128,
						    CGImageGetBitsPerComponent(imageRef),
						    bytesPerRow,
						    CGImageGetColorSpace(imageRef),
						    kCGImageAlphaPremultipliedFirst);
                if (context)
                {
                  CGContextDrawImage(context, CGRectMake(0, 0, 128, 128),
				     imageRef);
                  CGImageRelease(imageRef);
                  imageRef = CGBitmapContextCreateImage(context);
                  CGContextRelease(context);
                }
              }

              CGImageDestinationAddImage(destRef, imageRef, NULL);
              CGImageDestinationFinalize(destRef);
              CFRelease(destRef);
            }

            CGImageRelease(imageRef);
          }

          CFRelease(sourceRef);
        }
      }

      if (outUrl)
        CFRelease(outUrl);

      if (icnsFileUrl)
        CFRelease(icnsFileUrl);
    }
#endif /* HAVE_APPLICATIONSERVICES_H */

   /*
    * Close the PPD and set the type...
    */

    ppdClose(ppd);
  }
  else if (!access(ppd_name, 0))
  {
    int			pline;		/* PPD line number */
    ppd_status_t	pstatus;	/* PPD load status */


    pstatus = ppdLastError(&pline);

    cupsdLogMessage(CUPSD_LOG_ERROR, "PPD file for %s cannot be loaded.", p->name);

    if (pstatus <= PPD_ALLOC_ERROR)
      cupsdLogMessage(CUPSD_LOG_ERROR, "%s: %s", ppd_name, strerror(errno));
    else
      cupsdLogMessage(CUPSD_LOG_ERROR, "%s on line %d of %s.", ppdErrorString(pstatus), pline, ppd_name);

    cupsdLogMessage(CUPSD_LOG_INFO,
		    "Hint: Run \"cupstestppd %s\" and fix any errors.",
		    ppd_name);
  }
  else
  {
    if (((!strncmp(p->device_uri, "ipp://", 6) ||
	  !strncmp(p->device_uri, "ipps://", 7)) &&
	 (strstr(p->device_uri, "/printers/") != NULL ||
	  strstr(p->device_uri, "/classes/") != NULL)) ||
	((strstr(p->device_uri, "._ipp.") != NULL ||
	  strstr(p->device_uri, "._ipps.") != NULL) &&
	 !strcmp(p->device_uri + strlen(p->device_uri) - 5, "/cups")))
    {
     /*
      * Tell the client this is really a hard-wired remote printer.
      */

      p->type |= CUPS_PRINTER_REMOTE;

     /*
      * Then set the make-and-model accordingly...
      */

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
		   "printer-make-and-model", NULL, "Remote Printer");

     /*
      * Print all files directly...
      */

      p->raw    = 1;
      p->remote = 1;
    }
    else
    {
     /*
      * Otherwise we have neither - treat this as a "dumb" printer
      * with no PPD file...
      */

      ippAddString(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
		   "printer-make-and-model", NULL, "Local Raw Printer");

      p->raw = 1;
    }
  }

  ippAddIntegers(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		 "finishings-supported", num_finishings, finishings);
  ippAddInteger(p->ppd_attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
		"finishings-default", IPP_FINISHINGS_NONE);

  if (ppd && p->pc)
  {
   /*
    * Save cached PPD attributes to disk...
    */

    cupsdLogMessage(CUPSD_LOG_DEBUG, "load_ppd: Saving %s...", cache_name);

    _ppdCacheWriteFile(p->pc, cache_name, p->ppd_attrs);
  }
  else
  {
   /*
    * Remove cache files...
    */

    if (cache_info.st_mtime)
      unlink(cache_name);
  }
}


/*
 * 'new_media_col()' - Create a media-col collection value.
 */

static ipp_t *				/* O - Collection value */
new_media_col(pwg_size_t *size)		/* I - media-size/margin values */
{
  ipp_t	*media_col,			/* Collection value */
	*media_size;			/* media-size value */


  media_col = ippNew();

  media_size = ippNew();
  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension", size->width);
  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension", size->length);
  ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
  ippDelete(media_size);

  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", size->bottom);
  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", size->left);
  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", size->right);
  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", size->top);

  return (media_col);
}


/*
 * 'write_xml_string()' - Write a string with XML escaping.
 */

static void
write_xml_string(cups_file_t *fp,	/* I - File to write to */
                 const char  *s)	/* I - String to write */
{
  const char	*start;			/* Start of current sequence */


  if (!s)
    return;

  for (start = s; *s; s ++)
  {
    if (*s == '&')
    {
      if (s > start)
        cupsFileWrite(fp, start, (size_t)(s - start));

      cupsFilePuts(fp, "&amp;");
      start = s + 1;
    }
    else if (*s == '<')
    {
      if (s > start)
        cupsFileWrite(fp, start, (size_t)(s - start));

      cupsFilePuts(fp, "&lt;");
      start = s + 1;
    }
  }

  if (s > start)
    cupsFilePuts(fp, start);
}
