]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cgi-bin/admin.c
Merge pull request #5621 from zdohnal/cgigetarray-sigsegv
[thirdparty/cups.git] / cgi-bin / admin.c
index 01252ba5cf65b356d87b803da37bb9fdbd9c7a28..f087809f7cf1e3d6298cf84daed90fb844869f0e 100644 (file)
@@ -1,41 +1,11 @@
 /*
- * "$Id: admin.c 4943 2006-01-18 20:30:42Z mike $"
+ * Administration CGI for CUPS.
  *
- *   Administration CGI for the Common UNIX Printing System (CUPS).
+ * Copyright © 2007-2019 by Apple Inc.
+ * Copyright © 1997-2007 by Easy Software Products.
  *
- *   Copyright 1997-2006 by Easy Software Products.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Easy Software Products and are protected by Federal
- *   copyright law.  Distribution and use rights are outlined in the file
- *   "LICENSE.txt" which should have been included with this file.  If this
- *   file is missing or damaged please contact Easy Software Products
- *   at:
- *
- *       Attn: CUPS Licensing Information
- *       Easy Software Products
- *       44141 Airport View Drive, Suite 204
- *       Hollywood, Maryland 20636 USA
- *
- *       Voice: (301) 373-9600
- *       EMail: cups-info@cups.org
- *         WWW: http://www.cups.org
- *
- * Contents:
- *
- *   main()                    - Main entry for CGI.
- *   do_am_class()             - Add or modify a class.
- *   do_am_printer()           - Add or modify a printer.
- *   do_config_printer()       - Configure the default options for a printer.
- *   do_config_server()        - Configure server settings.
- *   do_delete_class()         - Delete a class...
- *   do_delete_printer()       - Delete a printer...
- *   do_export()               - Export printers to Samba...
- *   do_menu()                 - Show the main menu...
- *   do_printer_op()           - Do a printer operation.
- *   do_set_allowed_users()    - Set the allowed/denied users for a queue.
- *   do_set_sharing()          - Set printer-is-shared value...
- *   match_string()            - Return the number of matching characters.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
  */
 
 /*
  */
 
 #include "cgi-private.h"
-#include <cups/file.h>
+#include <cups/http-private.h>
+#include <cups/ppd-private.h>
+#include <cups/adminutil.h>
+#include <cups/ppd.h>
 #include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/wait.h>
+#include <limits.h>
+
+
+/*
+ * Local globals...
+ */
+
+static int     current_device = 0;     /* Current device shown */
 
 
 /*
  * Local functions...
  */
 
+static void    choose_device_cb(const char *device_class, const char *device_id, const char *device_info, const char *device_make_and_model, const char *device_uri, const char *device_location, const char *title);
 static void    do_am_class(http_t *http, int modify);
 static void    do_am_printer(http_t *http, int modify);
-static void    do_config_printer(http_t *http);
 static void    do_config_server(http_t *http);
 static void    do_delete_class(http_t *http);
 static void    do_delete_printer(http_t *http);
-static void    do_export(http_t *http);
+static void    do_list_printers(http_t *http);
 static void    do_menu(http_t *http);
-static void    do_printer_op(http_t *http,
-                             ipp_op_t op, const char *title);
 static void    do_set_allowed_users(http_t *http);
+static void    do_set_default(http_t *http);
+static void    do_set_options(http_t *http, int is_class);
 static void    do_set_sharing(http_t *http);
-static int     match_string(const char *a, const char *b);
+static char    *get_option_value(ppd_file_t *ppd, const char *name,
+                                 char *buffer, size_t bufsize);
+static double  get_points(double number, const char *uval);
+static char    *get_printer_ppd(const char *uri, char *buffer, size_t bufsize);
 
 
 /*
@@ -74,8 +58,7 @@ static int    match_string(const char *a, const char *b);
  */
 
 int                                    /* O - Exit status */
-main(int  argc,                                /* I - Number of command-line arguments */
-     char *argv[])                     /* I - Command-line arguments */
+main(void)
 {
   http_t       *http;                  /* Connection to the server */
   const char   *op;                    /* Operation name */
@@ -85,62 +68,83 @@ main(int  argc,                             /* I - Number of command-line arguments */
   * Connect to the HTTP server...
   */
 
+  fputs("DEBUG: admin.cgi started...\n", stderr);
+
   http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption());
 
+  if (!http)
+  {
+    perror("ERROR: Unable to connect to cupsd");
+    fprintf(stderr, "DEBUG: cupsServer()=\"%s\"\n",
+            cupsServer() ? cupsServer() : "(null)");
+    fprintf(stderr, "DEBUG: ippPort()=%d\n", ippPort());
+    fprintf(stderr, "DEBUG: cupsEncryption()=%d\n", cupsEncryption());
+    exit(1);
+  }
+
+  fprintf(stderr, "DEBUG: http=%p\n", http);
+
  /*
   * Set the web interface section...
   */
 
   cgiSetVariable("SECTION", "admin");
+  cgiSetVariable("REFRESH_PAGE", "");
 
  /*
   * See if we have form data...
   */
 
-  if (!cgiInitialize())
+  if (!cgiInitialize() || !cgiGetVariable("OP"))
   {
    /*
     * Nope, send the administration menu...
     */
 
+    fputs("DEBUG: No form data, showing main menu...\n", stderr);
+
     do_menu(http);
   }
-  else if ((op = cgiGetVariable("OP")) != NULL)
+  else if ((op = cgiGetVariable("OP")) != NULL && cgiIsPOST())
   {
    /*
     * Do the operation...
     */
 
-    if (!strcmp(op, "redirect"))
-    {
-      const char *url;                 /* Redirection URL... */
-
+    fprintf(stderr, "DEBUG: op=\"%s\"...\n", op);
 
-      if ((url = cgiGetVariable("URL")) != NULL)
-        printf("Location: %s\n\n", url);
+    if (!*op)
+    {
+      const char *printer = getenv("PRINTER_NAME"),
+                                       /* Printer or class name */
+               *server_port = getenv("SERVER_PORT");
+                                       /* Port number string */
+      int      port = atoi(server_port ? server_port : "0");
+                                       /* Port number */
+      char     uri[1024];              /* URL */
+
+      if (printer)
+        httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri),
+                        getenv("HTTPS") ? "https" : "http", NULL,
+                        getenv("SERVER_NAME"), port, "/%s/%s",
+                        cgiGetVariable("IS_CLASS") ? "classes" : "printers",
+                        printer);
       else
-        puts("Location: /admin\n");
-    }
-    else if (!strcmp(op, "start-printer"))
-      do_printer_op(http, IPP_RESUME_PRINTER, cgiText(_("Start Printer")));
-    else if (!strcmp(op, "stop-printer"))
-      do_printer_op(http, IPP_PAUSE_PRINTER, cgiText(_("Stop Printer")));
-    else if (!strcmp(op, "start-class"))
-      do_printer_op(http, IPP_RESUME_PRINTER, cgiText(_("Start Class")));
-    else if (!strcmp(op, "stop-class"))
-      do_printer_op(http, IPP_PAUSE_PRINTER, cgiText(_("Stop Class")));
-    else if (!strcmp(op, "accept-jobs"))
-      do_printer_op(http, CUPS_ACCEPT_JOBS, cgiText(_("Accept Jobs")));
-    else if (!strcmp(op, "reject-jobs"))
-      do_printer_op(http, CUPS_REJECT_JOBS, cgiText(_("Reject Jobs")));
-    else if (!strcmp(op, "purge-jobs"))
-      do_printer_op(http, IPP_PURGE_JOBS, cgiText(_("Purge Jobs")));
+        httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri),
+                       getenv("HTTPS") ? "https" : "http", NULL,
+                       getenv("SERVER_NAME"), port, "/admin");
+
+      printf("Location: %s\n\n", uri);
+    }
     else if (!strcmp(op, "set-allowed-users"))
       do_set_allowed_users(http);
     else if (!strcmp(op, "set-as-default"))
-      do_printer_op(http, CUPS_SET_DEFAULT, cgiText(_("Set As Default")));
+      do_set_default(http);
     else if (!strcmp(op, "set-sharing"))
       do_set_sharing(http);
+    else if (!strcmp(op, "find-new-printers") ||
+             !strcmp(op, "list-available-printers"))
+      do_list_printers(http);
     else if (!strcmp(op, "add-class"))
       do_am_class(http, 0);
     else if (!strcmp(op, "add-printer"))
@@ -153,16 +157,16 @@ main(int  argc,                           /* I - Number of command-line arguments */
       do_delete_class(http);
     else if (!strcmp(op, "delete-printer"))
       do_delete_printer(http);
+    else if (!strcmp(op, "set-class-options"))
+      do_set_options(http, 1);
     else if (!strcmp(op, "set-printer-options"))
-      do_config_printer(http);
+      do_set_options(http, 0);
     else if (!strcmp(op, "config-server"))
       do_config_server(http);
-    else if (!strcmp(op, "export-samba"))
-      do_export(http);
     else
     {
      /*
-      * Bad operation code...  Display an error...
+      * Bad operation code - display an error...
       */
 
       cgiStartHTML(cgiText(_("Administration")));
@@ -170,10 +174,73 @@ main(int  argc,                           /* I - Number of command-line arguments */
       cgiEndHTML();
     }
   }
+  else if (op && !strcmp(op, "redirect"))
+  {
+    const char *url;                   /* Redirection URL... */
+    char       prefix[1024];           /* URL prefix */
+
+
+    if (getenv("HTTPS"))
+      snprintf(prefix, sizeof(prefix), "https://%s:%s",
+              getenv("SERVER_NAME"), getenv("SERVER_PORT"));
+    else
+      snprintf(prefix, sizeof(prefix), "http://%s:%s",
+              getenv("SERVER_NAME"), getenv("SERVER_PORT"));
+
+    fprintf(stderr, "DEBUG: redirecting with prefix %s!\n", prefix);
+
+    if ((url = cgiGetVariable("URL")) != NULL)
+    {
+      char     encoded[1024],          /* Encoded URL string */
+               *ptr;                   /* Pointer into encoded string */
+
+
+      ptr = encoded;
+      if (*url != '/')
+        *ptr++ = '/';
+
+      for (; *url && ptr < (encoded + sizeof(encoded) - 4); url ++)
+      {
+        if (strchr("%@&+ <>#=", *url) || *url < ' ' || *url & 128)
+       {
+        /*
+         * Percent-encode this character; safe because we have at least 4
+         * bytes left in the array...
+         */
+
+         sprintf(ptr, "%%%02X", *url & 255);
+         ptr += 3;
+       }
+       else
+         *ptr++ = *url;
+      }
+
+      *ptr = '\0';
+
+      if (*url)
+      {
+       /*
+        * URL was too long, just redirect to the admin page...
+       */
+
+       printf("Location: %s/admin\n\n", prefix);
+      }
+      else
+      {
+       /*
+        * URL is OK, redirect there...
+       */
+
+        printf("Location: %s%s\n\n", prefix, encoded);
+      }
+    }
+    else
+      printf("Location: %s/admin\n\n", prefix);
+  }
   else
   {
    /*
-    * Form data but no operation code...  Display an error...
+    * Form data but no operation code - display an error...
     */
 
     cgiStartHTML(cgiText(_("Administration")));
@@ -195,6 +262,50 @@ main(int  argc,                            /* I - Number of command-line arguments */
 }
 
 
+/*
+ * 'choose_device_cb()' - Add a device to the device selection page.
+ */
+
+static void
+choose_device_cb(
+    const char *device_class,          /* I - Class */
+    const char *device_id,             /* I - 1284 device ID */
+    const char *device_info,           /* I - Description */
+    const char *device_make_and_model, /* I - Make and model */
+    const char *device_uri,            /* I - Device URI */
+    const char *device_location,       /* I - Location */
+    const char *title)                 /* I - Page title */
+{
+ /*
+  * For modern browsers, start a multi-part page so we can show that something
+  * is happening.  Non-modern browsers just get everything at the end...
+  */
+
+  if (current_device == 0 && cgiSupportsMultipart())
+  {
+    cgiStartMultipart();
+    cgiStartHTML(title);
+    cgiCopyTemplateLang("choose-device.tmpl");
+    cgiEndHTML();
+    fflush(stdout);
+  }
+
+
+ /*
+  * Add the device to the array...
+  */
+
+  cgiSetArray("device_class", current_device, device_class);
+  cgiSetArray("device_id", current_device, device_id);
+  cgiSetArray("device_info", current_device, device_info);
+  cgiSetArray("device_make_and_model", current_device, device_make_and_model);
+  cgiSetArray("device_uri", current_device, device_uri);
+  cgiSetArray("device_location", current_device, device_location);
+
+  current_device ++;
+}
+
+
 /*
  * 'do_am_class()' - Add or modify a class.
  */
@@ -211,6 +322,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
   ipp_attribute_t *attr;               /* member-uris attribute */
   char         uri[HTTP_MAX_URI];      /* Device or printer URI */
   const char   *name,                  /* Pointer to class name */
+               *op,                    /* Operation name */
                *ptr;                   /* Pointer to CGI variable */
   const char   *title;                 /* Title of page */
   static const char * const pattrs[] = /* Requested printer attributes */
@@ -222,6 +334,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
 
 
   title = cgiText(modify ? _("Modify Class") : _("Add Class"));
+  op    = cgiGetVariable("OP");
   name  = cgiGetVariable("PRINTER_NAME");
 
   if (cgiGetVariable("PRINTER_LOCATION") == NULL)
@@ -232,18 +345,25 @@ do_am_class(http_t *http,         /* I - HTTP connection */
     *
     *    attributes-charset
     *    attributes-natural-language
-    *    printer-uri
     */
 
     request = ippNewRequest(CUPS_GET_PRINTERS);
 
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                 NULL, "ipp://localhost/printers");
+    ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type",
+                 CUPS_PRINTER_LOCAL);
+    ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask",
+                 CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);
 
    /*
     * Do the request and get back a response...
     */
 
+    cgiClearVariables();
+    if (op)
+      cgiSetVariable("OP", op);
+    if (name)
+      cgiSetVariable("PRINTER_NAME", name);
+
     if ((response = cupsDoRequest(http, request, "/")) != NULL)
     {
      /*
@@ -256,7 +376,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
        if (attr->name && !strcmp(attr->name, "printer-uri-supported"))
        {
          if ((ptr = strrchr(attr->values[0].string.text, '/')) != NULL &&
-             (!name || strcasecmp(name, ptr + 1)))
+             (!name || _cups_strcasecmp(name, ptr + 1)))
          {
           /*
            * Don't show the current class...
@@ -272,7 +392,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
           attr = attr->next)
        if (attr->name && !strcmp(attr->name, "printer-name"))
        {
-         if (!name || strcasecmp(name, attr->values[0].string.text))
+         if (!name || _cups_strcasecmp(name, attr->values[0].string.text))
          {
           /*
            * Don't show the current class...
@@ -303,8 +423,8 @@ do_am_class(http_t *http,           /* I - HTTP connection */
 
       request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
 
-      httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                       "/classes/%s", name);
+      httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                       "localhost", 0, "/classes/%s", name);
       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                    NULL, uri);
 
@@ -319,7 +439,8 @@ do_am_class(http_t *http,           /* I - HTTP connection */
 
       if ((response = cupsDoRequest(http, request, "/")) != NULL)
       {
-       if ((attr = ippFindAttribute(response, "member-names", IPP_TAG_NAME)) != NULL)
+       if ((attr = ippFindAttribute(response, "member-names",
+                                    IPP_TAG_NAME)) != NULL)
        {
         /*
           * Mark any current members in the class...
@@ -332,7 +453,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
          {
            for (j = 0; j < num_printers; j ++)
            {
-             if (!strcasecmp(attr->values[i].string.text,
+             if (!_cups_strcasecmp(attr->values[i].string.text,
                              cgiGetArray("MEMBER_NAMES", j)))
              {
                cgiSetArray("MEMBER_SELECTED", j, "SELECTED");
@@ -375,6 +496,15 @@ do_am_class(http_t *http,          /* I - HTTP connection */
     return;
   }
 
+  if (!name)
+  {
+    cgiStartHTML(title);
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
+    cgiCopyTemplateLang("error.tmpl");
+    cgiEndHTML();
+    return;
+  }
+
   for (ptr = name; *ptr; ptr ++)
     if ((*ptr >= 0 && *ptr <= ' ') || *ptr == 127 || *ptr == '/' || *ptr == '#')
       break;
@@ -408,8 +538,8 @@ do_am_class(http_t *http,           /* I - HTTP connection */
 
   request = ippNewRequest(CUPS_ADD_CLASS);
 
-  httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                   "/classes/%s", cgiGetVariable("PRINTER_NAME"));
+  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                   "localhost", 0, "/classes/%s", name);
   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                NULL, uri);
 
@@ -429,7 +559,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
     attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_URI, "member-uris",
                          num_printers, NULL, NULL);
     for (i = 0; i < num_printers; i ++)
-      attr->values[i].string.text = strdup(cgiGetArray("MEMBER_URIS", i));
+      ippSetString(request, &attr, i, cgiGetArray("MEMBER_URIS", i));
   }
 
  /*
@@ -438,11 +568,16 @@ do_am_class(http_t *http,         /* I - HTTP connection */
 
   ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-  if (cupsLastError() > IPP_OK_CONFLICT)
+  if (cupsLastError() == IPP_NOT_AUTHORIZED)
+  {
+    puts("Status: 401\n");
+    exit(0);
+  }
+  else if (cupsLastError() > IPP_OK_CONFLICT)
   {
     cgiStartHTML(title);
-    cgiShowIPPError(modify ? _("Unable to modify class:") :
-                             _("Unable to add class:"));
+    cgiShowIPPError(modify ? _("Unable to modify class") :
+                             _("Unable to add class"));
   }
   else
   {
@@ -453,7 +588,7 @@ do_am_class(http_t *http,           /* I - HTTP connection */
     char       refresh[1024];          /* Refresh URL */
 
     cgiFormEncode(uri, name, sizeof(uri));
-    snprintf(refresh, sizeof(refresh), "5;/admin/?OP=redirect&URL=/classes/%s",
+    snprintf(refresh, sizeof(refresh), "5;URL=/admin/?OP=redirect&URL=/classes/%s",
              uri);
     cgiSetVariable("refresh_page", refresh);
 
@@ -478,16 +613,15 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
              int    modify)            /* I - Modify the printer? */
 {
   int          i;                      /* Looping var */
-  int          element;                /* Element number */
-  ipp_attribute_t *attr,               /* Current attribute */
-               *last;                  /* Last attribute */
+  ipp_attribute_t *attr;               /* Current attribute */
   ipp_t                *request,               /* IPP request */
                *response,              /* IPP response */
                *oldinfo;               /* Old printer information */
   const cgi_file_t *file;              /* Uploaded file, if any */
   const char   *var;                   /* CGI variable */
   char         uri[HTTP_MAX_URI],      /* Device or printer URI */
-               *uriptr;                /* Pointer into URI */
+               *uriptr,                /* Pointer into URI */
+               evefile[1024] = "";     /* IPP Everywhere PPD file */
   int          maxrate;                /* Maximum baud rate */
   char         baudrate[255];          /* Baud rate string */
   const char   *name,                  /* Pointer to class name */
@@ -508,8 +642,9 @@ do_am_printer(http_t *http,         /* I - HTTP connection */
                };
 
 
+  ptr = cgiGetVariable("DEVICE_URI");
   fprintf(stderr, "DEBUG: do_am_printer: DEVICE_URI=\"%s\"\n",
-          cgiGetVariable("DEVICE_URI"));
+          ptr ? ptr : "(null)");
 
   title = cgiText(modify ? _("Modify Printer") : _("Add Printer"));
 
@@ -526,8 +661,9 @@ do_am_printer(http_t *http,         /* I - HTTP connection */
 
     request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
 
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", cgiGetVariable("PRINTER_NAME"));
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, "/printers/%s",
+                    cgiGetVariable("PRINTER_NAME"));
     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                  NULL, uri);
 
@@ -540,94 +676,100 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
   else
     oldinfo = NULL;
 
-  if ((name = cgiGetVariable("PRINTER_NAME")) == NULL ||
-      cgiGetVariable("PRINTER_LOCATION") == NULL)
-  {
-    cgiStartHTML(title);
+  file = cgiGetFile();
 
-    if (modify)
-    {
-     /*
-      * Update the location and description of an existing printer...
-      */
+  if (file)
+  {
+    fprintf(stderr, "DEBUG: file->tempfile=%s\n", file->tempfile);
+    fprintf(stderr, "DEBUG: file->name=%s\n", file->name);
+    fprintf(stderr, "DEBUG: file->filename=%s\n", file->filename);
+    fprintf(stderr, "DEBUG: file->mimetype=%s\n", file->mimetype);
+  }
 
-      if (oldinfo)
-       cgiSetIPPVars(oldinfo, NULL, NULL, NULL, 0);
+  if ((name = cgiGetVariable("PRINTER_NAME")) != NULL)
+  {
+    for (ptr = name; *ptr; ptr ++)
+      if ((*ptr >= 0 && *ptr <= ' ') || *ptr == 127 || *ptr == '/' || *ptr == '\\' || *ptr == '?' || *ptr == '\'' || *ptr == '\"' || *ptr == '#')
+       break;
 
-      cgiCopyTemplateLang("modify-printer.tmpl");
+    if (*ptr || ptr == name || strlen(name) > 127)
+    {
+      cgiSetVariable("ERROR",
+                    cgiText(_("The printer name may only contain up to 127 printable characters and may not contain spaces, slashes (/ \\), quotes (' \"), question mark (?), or the pound sign (#).")));
+      cgiStartHTML(title);
+      cgiCopyTemplateLang("error.tmpl");
+      cgiEndHTML();
+      return;
     }
-    else
+  }
+
+  if ((var = cgiGetVariable("DEVICE_URI")) != NULL)
+  {
+    if ((uriptr = strrchr(var, '|')) != NULL)
     {
      /*
-      * Get the name, location, and description for a new printer...
+      * Extract make and make/model from device URI string...
       */
 
-      cgiCopyTemplateLang("add-printer.tmpl");
-    }
-
-    cgiEndHTML();
-
-    if (oldinfo)
-      ippDelete(oldinfo);
-
-    return;
-  }
+      char     make[1024],             /* Make string */
+               *makeptr;               /* Pointer into make */
 
-  for (ptr = name; *ptr; ptr ++)
-    if ((*ptr >= 0 && *ptr <= ' ') || *ptr == 127 || *ptr == '/' || *ptr == '#')
-      break;
 
-  if (*ptr || ptr == name || strlen(name) > 127)
-  {
-    cgiSetVariable("ERROR",
-                   cgiText(_("The printer name may only contain up to "
-                            "127 printable characters and may not "
-                            "contain spaces, slashes (/), or the "
-                            "pound sign (#).")));
-    cgiStartHTML(title);
-    cgiCopyTemplateLang("error.tmpl");
-    cgiEndHTML();
-    return;
-  }
+      *uriptr++ = '\0';
 
-  file = cgiGetFile();
+      strlcpy(make, uriptr, sizeof(make));
 
-  if (file)
-  {
-    fprintf(stderr, "DEBUG: file->tempfile=%s\n", file->tempfile);
-    fprintf(stderr, "DEBUG: file->name=%s\n", file->name);
-    fprintf(stderr, "DEBUG: file->filename=%s\n", file->filename);
-    fprintf(stderr, "DEBUG: file->mimetype=%s\n", file->mimetype);
-  }
+      if ((makeptr = strchr(make, ' ')) != NULL)
+        *makeptr = '\0';
+      else if ((makeptr = strchr(make, '-')) != NULL)
+        *makeptr = '\0';
+      else if (!_cups_strncasecmp(make, "laserjet", 8) ||
+               !_cups_strncasecmp(make, "deskjet", 7) ||
+               !_cups_strncasecmp(make, "designjet", 9))
+        strlcpy(make, "HP", sizeof(make));
+      else if (!_cups_strncasecmp(make, "phaser", 6))
+        strlcpy(make, "Xerox", sizeof(make));
+      else if (!_cups_strncasecmp(make, "stylus", 6))
+        strlcpy(make, "Epson", sizeof(make));
+      else
+        strlcpy(make, "Generic", sizeof(make));
 
-  if ((var = cgiGetVariable("DEVICE_URI")) == NULL)
-  {
-   /*
-    * Build a CUPS_GET_DEVICES request, which requires the following
-    * attributes:
-    *
-    *    attributes-charset
-    *    attributes-natural-language
-    *    printer-uri
-    */
+      if (!cgiGetVariable("CURRENT_MAKE"))
+        cgiSetVariable("CURRENT_MAKE", make);
 
-    request = ippNewRequest(CUPS_GET_DEVICES);
+      if (!cgiGetVariable("CURRENT_MAKE_AND_MODEL"))
+        cgiSetVariable("CURRENT_MAKE_AND_MODEL", uriptr);
 
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                 NULL, "ipp://localhost/printers/");
+      if (!modify)
+      {
+        char   template[128],          /* Template name */
+               *tptr;                  /* Pointer into template name */
+
+       cgiSetVariable("PRINTER_INFO", uriptr);
+
+       for (tptr = template;
+            tptr < (template + sizeof(template) - 1) && *uriptr;
+            uriptr ++)
+         if (isalnum(*uriptr & 255) || *uriptr == '_' || *uriptr == '-' ||
+             *uriptr == '.')
+           *tptr++ = *uriptr;
+         else if ((*uriptr == ' ' || *uriptr == '/') && tptr > template &&
+                  tptr[-1] != '_')
+           *tptr++ = '_';
+         else if (*uriptr == '?' || *uriptr == '(')
+           break;
 
-   /*
-    * Do the request and get back a response...
-    */
+        *tptr = '\0';
 
-    if ((response = cupsDoRequest(http, request, "/")) != NULL)
-    {
-      cgiSetIPPVars(response, NULL, NULL, NULL, 0);
-      ippDelete(response);
+        cgiSetVariable("TEMPLATE_NAME", template);
+      }
     }
+  }
 
+  if (!var)
+  {
    /*
-    * Let the user choose...
+    * Look for devices so the user can pick something...
     */
 
     if ((attr = ippFindAttribute(oldinfo, "device-uri", IPP_TAG_URI)) != NULL)
@@ -640,11 +782,52 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
       cgiSetVariable("CURRENT_DEVICE_SCHEME", uri);
     }
 
-    cgiStartHTML(title);
-    cgiCopyTemplateLang("choose-device.tmpl");
-    cgiEndHTML();
+   /*
+    * Scan for devices for up to 30 seconds...
+    */
+
+    fputs("DEBUG: Getting list of devices...\n", stderr);
+
+    current_device = 0;
+    if (cupsGetDevices(http, 5, CUPS_INCLUDE_ALL, CUPS_EXCLUDE_NONE,
+                       (cups_device_cb_t)choose_device_cb,
+                      (void *)title) == IPP_OK)
+    {
+      fputs("DEBUG: Got device list!\n", stderr);
+
+      if (cgiSupportsMultipart())
+        cgiStartMultipart();
+
+      cgiSetVariable("CUPS_GET_DEVICES_DONE", "1");
+      cgiStartHTML(title);
+      cgiCopyTemplateLang("choose-device.tmpl");
+      cgiEndHTML();
+
+      if (cgiSupportsMultipart())
+        cgiEndMultipart();
+    }
+    else
+    {
+      fprintf(stderr,
+              "ERROR: CUPS-Get-Devices request failed with status %x: %s\n",
+             cupsLastError(), cupsLastErrorString());
+      if (cupsLastError() == IPP_NOT_AUTHORIZED)
+      {
+       puts("Status: 401\n");
+       exit(0);
+      }
+      else
+      {
+       cgiStartHTML(title);
+       cgiShowIPPError(modify ? _("Unable to modify printer") :
+                                _("Unable to add printer"));
+       cgiEndHTML();
+        return;
+      }
+    }
   }
-  else if (strchr(var, '/') == NULL)
+  else if (!strchr(var, '/') ||
+           (!strncmp(var, "lpd://", 6) && !strchr(var + 6, '/')))
   {
     if ((attr = ippFindAttribute(oldinfo, "device-uri", IPP_TAG_URI)) != NULL)
     {
@@ -653,7 +836,7 @@ do_am_printer(http_t *http,         /* I - HTTP connection */
       */
 
       if (strncmp(attr->values[0].string.text, var, strlen(var)) == 0)
-       cgiSetVariable("DEVICE_URI", attr->values[0].string.text);
+       cgiSetVariable("CURRENT_DEVICE_URI", attr->values[0].string.text);
     }
 
    /*
@@ -689,9 +872,63 @@ do_am_printer(http_t *http,                /* I - HTTP connection */
     cgiCopyTemplateLang("choose-serial.tmpl");
     cgiEndHTML();
   }
-  else if (!file && (var = cgiGetVariable("PPD_NAME")) == NULL)
+  else if (!name || !cgiGetVariable("PRINTER_LOCATION"))
   {
+    cgiStartHTML(title);
+
     if (modify)
+    {
+     /*
+      * Update the location and description of an existing printer...
+      */
+
+      if (oldinfo)
+      {
+        if ((attr = ippFindAttribute(oldinfo, "printer-info",
+                                    IPP_TAG_TEXT)) != NULL)
+          cgiSetVariable("PRINTER_INFO", attr->values[0].string.text);
+
+        if ((attr = ippFindAttribute(oldinfo, "printer-location",
+                                    IPP_TAG_TEXT)) != NULL)
+          cgiSetVariable("PRINTER_LOCATION", attr->values[0].string.text);
+
+       if ((attr = ippFindAttribute(oldinfo, "printer-is-shared",
+                                    IPP_TAG_BOOLEAN)) != NULL)
+         cgiSetVariable("PRINTER_IS_SHARED",
+                        attr->values[0].boolean ? "1" : "0");
+      }
+
+      cgiCopyTemplateLang("modify-printer.tmpl");
+    }
+    else
+    {
+     /*
+      * Get the name, location, and description for a new printer...
+      */
+
+#ifdef __APPLE__
+      if (!strncmp(var, "usb:", 4))
+        cgiSetVariable("printer_is_shared", "1");
+      else
+#endif /* __APPLE__ */
+        cgiSetVariable("printer_is_shared", "0");
+
+      cgiCopyTemplateLang("add-printer.tmpl");
+    }
+
+    cgiEndHTML();
+
+    if (oldinfo)
+      ippDelete(oldinfo);
+
+    return;
+  }
+  else if (!file &&
+           (!cgiGetVariable("PPD_NAME") || cgiGetVariable("SELECT_MAKE")))
+  {
+    int ipp_everywhere = !strncmp(var, "ipp://", 6) || !strncmp(var, "ipps://", 7) || (!strncmp(var, "dnssd://", 8) && (strstr(var, "_ipp._tcp") || strstr(var, "_ipps._tcp")));
+
+    if (modify && !cgiGetVariable("SELECT_MAKE"))
     {
      /*
       * Get the PPD file...
@@ -701,10 +938,11 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
       char             filename[1024]; /* PPD filename */
       ppd_file_t       *ppd;           /* PPD information */
       char             buffer[1024];   /* Buffer */
-      int              bytes;          /* Number of bytes */
+      ssize_t          bytes;          /* Number of bytes */
       http_status_t    get_status;     /* Status of GET */
 
 
+      /* TODO: Use cupsGetFile() API... */
       snprintf(uri, sizeof(uri), "/printers/%s.ppd", name);
 
       if (httpGet(http, uri))
@@ -714,13 +952,15 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
 
       if (get_status != HTTP_OK)
       {
+        httpFlush(http);
+
         fprintf(stderr, "ERROR: Unable to get PPD file %s: %d - %s\n",
                uri, get_status, httpStatus(get_status));
       }
       else if ((fd = cupsTempFd(filename, sizeof(filename))) >= 0)
       {
-       while ((bytes = httpRead(http, buffer, sizeof(buffer))) > 0)
-          write(fd, buffer, bytes);
+       while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
+          write(fd, buffer, (size_t)bytes);
 
        close(fd);
 
@@ -737,8 +977,10 @@ do_am_printer(http_t *http,                /* I - HTTP connection */
        }
        else
        {
+         int linenum;                  /* Line number */
+
          fprintf(stderr, "ERROR: Unable to open PPD file %s: %s\n",
-                 filename, ppdErrorString(ppdLastError(&bytes)));
+                 filename, ppdErrorString(ppdLastError(&linenum)));
        }
       }
       else
@@ -750,39 +992,6 @@ do_am_printer(http_t *http,                /* I - HTTP connection */
                strerror(errno));
       }
     }
-    else if ((uriptr = strrchr(cgiGetVariable("DEVICE_URI"), '|')) != NULL)
-    {
-     /*
-      * Extract make and make/model from device URI string...
-      */
-
-      char     make[1024],             /* Make string */
-               *makeptr;               /* Pointer into make */
-
-
-      *uriptr++ = '\0';
-
-      strlcpy(make, uriptr, sizeof(make));
-
-      if ((makeptr = strchr(make, ' ')) != NULL)
-        *makeptr = '\0';
-      else if ((makeptr = strchr(make, '-')) != NULL)
-        *makeptr = '\0';
-      else if (!strncasecmp(make, "laserjet", 8) ||
-               !strncasecmp(make, "deskjet", 7) ||
-               !strncasecmp(make, "designjet", 9))
-        strcpy(make, "HP");
-      else if (!strncasecmp(make, "phaser", 6))
-        strcpy(make, "Xerox");
-      else if (!strncasecmp(make, "stylus", 6))
-        strcpy(make, "Epson");
-      else
-        strcpy(make, "Generic");
-
-      cgiSetVariable("CURRENT_MAKE", make);
-      cgiSetVariable("PPD_MAKE", make);
-      cgiSetVariable("CURRENT_MAKE_AND_MODEL", uriptr);
-    }
 
    /*
     * Build a CUPS_GET_PPDS request, which requires the following
@@ -798,9 +1007,20 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                  NULL, "ipp://localhost/printers/");
 
-    if ((var = cgiGetVariable("PPD_MAKE")) != NULL)
+    if ((var = cgiGetVariable("PPD_MAKE")) == NULL)
+      var = cgiGetVariable("CURRENT_MAKE");
+    if (var && !cgiGetVariable("SELECT_MAKE"))
+    {
+      const char *make_model;          /* Make and model */
+
+
       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
                    "ppd-make", NULL, var);
+
+      if ((make_model = cgiGetVariable("CURRENT_MAKE_AND_MODEL")) != NULL)
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXT,
+                    "ppd-make-and-model", NULL, make_model);
+    }
     else
       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                    "requested-attributes", NULL, "ppd-make");
@@ -815,27 +1035,31 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
       * Got the list of PPDs, see if the user has selected a make...
       */
 
-      cgiSetIPPVars(response, NULL, NULL, NULL, 0);
-
-      if (var == NULL)
+      if (cgiSetIPPVars(response, NULL, NULL, NULL, 0) == 0 && !modify)
       {
        /*
-       * Let the user choose a make...
+        * No PPD files with this make, try again with all makes...
        */
 
-        for (element = 0, attr = response->attrs, last = NULL;
-            attr != NULL;
-            attr = attr->next)
-         if (attr->name && strcmp(attr->name, "ppd-make") == 0)
-           if (last == NULL ||
-               strcasecmp(last->values[0].string.text,
-                          attr->values[0].string.text) != 0)
-           {
-             cgiSetArray("PPD_MAKE", element, attr->values[0].string.text);
-             element ++;
-             last = attr;
-           }
+        ippDelete(response);
+
+       request = ippNewRequest(CUPS_GET_PPDS);
+
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+                     NULL, "ipp://localhost/printers/");
+
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                     "requested-attributes", NULL, "ppd-make");
 
+       if ((response = cupsDoRequest(http, request, "/")) != NULL)
+          cgiSetIPPVars(response, NULL, NULL, NULL, 0);
+
+        cgiStartHTML(title);
+       cgiCopyTemplateLang("choose-make.tmpl");
+        cgiEndHTML();
+      }
+      else if (!var || cgiGetVariable("SELECT_MAKE"))
+      {
         cgiStartHTML(title);
        cgiCopyTemplateLang("choose-make.tmpl");
         cgiEndHTML();
@@ -846,58 +1070,21 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
        * Let the user choose a model...
        */
 
-        const char     *make_model;    /* Current make/model string */
-
-
-        if ((make_model = cgiGetVariable("CURRENT_MAKE_AND_MODEL")) != NULL)
-       {
-        /*
-         * Scan for "close" matches...
-         */
-
-          int          match,          /* Current match */
-                       best_match,     /* Best match so far */
-                       count;          /* Number of drivers */
-         const char    *best,          /* Best matching string */
-                       *current;       /* Current string */
-
-
-          count = cgiGetSize("PPD_MAKE_AND_MODEL");
-
-         for (i = 0, best_match = 0, best = NULL; i < count; i ++)
-         {
-           current = cgiGetArray("PPD_MAKE_AND_MODEL", i);
-           match   = match_string(make_model, current);
-
-           if (match > best_match)
-           {
-             best_match = match;
-             best       = current;
-           }
-         }
-
-          if (best_match > strlen(var))
-         {
-          /*
-           * Found a match longer than the make...
-           */
-
-            cgiSetVariable("CURRENT_MAKE_AND_MODEL", best);
-         }
-       }
-
         cgiStartHTML(title);
+       if (!cgiGetVariable("PPD_MAKE"))
+         cgiSetVariable("PPD_MAKE", cgiGetVariable("CURRENT_MAKE"));
+        if (ipp_everywhere)
+         cgiSetVariable("SHOW_IPP_EVERYWHERE", "1");
        cgiCopyTemplateLang("choose-model.tmpl");
         cgiEndHTML();
       }
 
-      
       ippDelete(response);
     }
     else
     {
       cgiStartHTML(title);
-      cgiShowIPPError(_("Unable to get list of printer drivers:"));
+      cgiShowIPPError(_("Unable to get list of printer drivers"));
       cgiCopyTemplateLang("error.tmpl");
       cgiEndHTML();
     }
@@ -916,26 +1103,34 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
     *    ppd-name
     *    device-uri
     *    printer-is-accepting-jobs
+    *    printer-is-shared
     *    printer-state
     */
 
     request = ippNewRequest(CUPS_ADD_PRINTER);
 
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", cgiGetVariable("PRINTER_NAME"));
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, "/printers/%s",
+                    cgiGetVariable("PRINTER_NAME"));
     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                  NULL, uri);
 
+    if (!file)
+    {
+      var = cgiGetVariable("PPD_NAME");
+      if (!strcmp(var, "everywhere"))
+        get_printer_ppd(cgiGetVariable("DEVICE_URI"), evefile, sizeof(evefile));
+      else if (strcmp(var, "__no_change__"))
+       ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name",
+                    NULL, var);
+    }
+
     ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-location",
                  NULL, cgiGetVariable("PRINTER_LOCATION"));
 
     ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
                  NULL, cgiGetVariable("PRINTER_INFO"));
 
-    if (!file)
-      ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME, "ppd-name",
-                   NULL, cgiGetVariable("PPD_NAME"));
-
     strlcpy(uri, cgiGetVariable("DEVICE_URI"), sizeof(uri));
 
    /*
@@ -954,7 +1149,7 @@ do_am_printer(http_t *http,                /* I - HTTP connection */
       if ((uriptr = strchr(uri, '?')) == NULL)
         uriptr = uri + strlen(uri);
 
-      snprintf(uriptr, sizeof(uri) - (uriptr - uri),
+      snprintf(uriptr, sizeof(uri) - (size_t)(uriptr - uri),
                "?baud=%s+bits=%s+parity=%s+flow=%s",
                cgiGetVariable("BAUDRATE"), cgiGetVariable("BITS"),
               cgiGetVariable("PARITY"), cgiGetVariable("FLOW"));
@@ -965,6 +1160,10 @@ do_am_printer(http_t *http,               /* I - HTTP connection */
 
     ippAddBoolean(request, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1);
 
+    var = cgiGetVariable("printer_is_shared");
+    ippAddBoolean(request, IPP_TAG_PRINTER, "printer-is-shared",
+                  var && (!strcmp(var, "1") || !strcmp(var, "on")));
+
     ippAddInteger(request, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state",
                   IPP_PRINTER_IDLE);
 
@@ -974,19 +1173,29 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
 
     if (file)
       ippDelete(cupsDoFileRequest(http, request, "/admin/", file->tempfile));
+    else if (evefile[0])
+    {
+      ippDelete(cupsDoFileRequest(http, request, "/admin/", evefile));
+      unlink(evefile);
+    }
     else
       ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-    if (cupsLastError() > IPP_OK_CONFLICT)
+    if (cupsLastError() == IPP_NOT_AUTHORIZED)
+    {
+      puts("Status: 401\n");
+      exit(0);
+    }
+    else if (cupsLastError() > IPP_OK_CONFLICT)
     {
       cgiStartHTML(title);
-      cgiShowIPPError(modify ? _("Unable to modify printer:") :
-                               _("Unable to add printer:"));
+      cgiShowIPPError(modify ? _("Unable to modify printer") :
+                               _("Unable to add printer"));
     }
-    else
+    else if (modify)
     {
      /*
-      * Redirect successful updates back to the printer or set-options pages...
+      * Redirect successful updates back to the printer page...
       */
 
       char     refresh[1024];          /* Refresh URL */
@@ -994,21 +1203,24 @@ do_am_printer(http_t *http,              /* I - HTTP connection */
 
       cgiFormEncode(uri, name, sizeof(uri));
 
-      if (modify)
-       snprintf(refresh, sizeof(refresh),
-                "5;/admin/?OP=redirect&URL=/printers/%s", uri);
-      else
-       snprintf(refresh, sizeof(refresh),
-                "5;/admin/?OP=set-printer-options&PRINTER_NAME=%s", uri);
+      snprintf(refresh, sizeof(refresh),
+              "5;/admin/?OP=redirect&URL=/printers/%s", uri);
 
       cgiSetVariable("refresh_page", refresh);
 
       cgiStartHTML(title);
 
-      if (modify)
-        cgiCopyTemplateLang("printer-modified.tmpl");
-      else
-        cgiCopyTemplateLang("printer-added.tmpl");
+      cgiCopyTemplateLang("printer-modified.tmpl");
+    }
+    else
+    {
+     /*
+      * Set the printer options...
+      */
+
+      cgiSetVariable("OP", "set-printer-options");
+      do_set_options(http, 0);
+      return;
     }
 
     cgiEndHTML();
@@ -1020,554 +1232,404 @@ do_am_printer(http_t *http,           /* I - HTTP connection */
 
 
 /*
- * 'do_config_printer()' - Configure the default options for a printer.
+ * 'do_config_server()' - Configure server settings.
  */
 
 static void
-do_config_printer(http_t *http)                /* I - HTTP connection */
+do_config_server(http_t *http)         /* I - HTTP connection */
 {
-  int          i, j, k, m;             /* Looping vars */
-  int          have_options;           /* Have options? */
-  ipp_t                *request,               /* IPP request */
-               *response;              /* IPP response */
-  ipp_attribute_t *attr;               /* IPP attribute */
-  char         uri[HTTP_MAX_URI];      /* Job URI */
-  const char   *var;                   /* Variable value */
-  const char   *printer;               /* Printer printer name */
-  const char   *filename;              /* PPD filename */
-  char         tempfile[1024];         /* Temporary filename */
-  cups_file_t  *in,                    /* Input file */
-               *out;                   /* Output file */
-  char         line[1024];             /* Line from PPD file */
-  char         keyword[1024],          /* Keyword from Default line */
-               *keyptr;                /* Pointer into keyword... */
-  ppd_file_t   *ppd;                   /* PPD file */
-  ppd_group_t  *group;                 /* Option group */
-  ppd_option_t *option;                /* Option */
-  ppd_attr_t   *protocol;              /* cupsProtocol attribute */
-  const char   *title;                 /* Page title */
-
-
-  title = cgiText(_("Set Printer Options"));
+  if (cgiGetVariable("CHANGESETTINGS"))
+  {
+   /*
+    * Save basic setting changes...
+    */
 
- /*
-  * Get the printer name...
-  */
+    int                        num_settings;   /* Number of server settings */
+    cups_option_t      *settings;      /* Server settings */
+    int                        advanced,       /* Advanced settings shown? */
+                       changed;        /* Have settings changed? */
+    const char         *debug_logging, /* DEBUG_LOGGING value */
+                       *preserve_jobs = NULL,
+                                       /* PRESERVE_JOBS value */
+                       *remote_admin,  /* REMOTE_ADMIN value */
+                       *remote_any,    /* REMOTE_ANY value */
+                       *share_printers,/* SHARE_PRINTERS value */
+                       *user_cancel_any,
+                                       /* USER_CANCEL_ANY value */
+                       *browse_web_if = NULL,
+                                       /* BrowseWebIF value */
+                       *preserve_job_history = NULL,
+                                       /* PreserveJobHistory value */
+                       *preserve_job_files = NULL,
+                                       /* PreserveJobFiles value */
+                       *max_clients = NULL,
+                                       /* MaxClients value */
+                       *max_jobs = NULL,
+                                       /* MaxJobs value */
+                       *max_log_size = NULL;
+                                       /* MaxLogSize value */
+    const char         *current_browse_web_if,
+                                       /* BrowseWebIF value */
+                       *current_preserve_job_history,
+                                       /* PreserveJobHistory value */
+                       *current_preserve_job_files,
+                                       /* PreserveJobFiles value */
+                       *current_max_clients,
+                                       /* MaxClients value */
+                       *current_max_jobs,
+                                       /* MaxJobs value */
+                       *current_max_log_size;
+                                       /* MaxLogSize value */
+#ifdef HAVE_GSSAPI
+    char               default_auth_type[255];
+                                       /* DefaultAuthType value */
+    const char         *val;           /* Setting value */
+#endif /* HAVE_GSSAPI */
 
-  if ((printer = cgiGetVariable("PRINTER_NAME")) != NULL)
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", printer);
-  else
-  {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
-    cgiStartHTML(title);
-    cgiCopyTemplateLang("error.tmpl");
-    cgiEndHTML();
-    return;
-  }
 
- /*
-  * Get the PPD file...
-  */
  /*
+    * Get the checkbox values from the form...
+    */
 
-  if ((filename = cupsGetPPD(printer)) == NULL)
-  {
-    cgiStartHTML(title);
-    cgiShowIPPError(_("Unable to get PPD file!"));
-    cgiEndHTML();
-    return;
-  }
+    debug_logging        = cgiGetVariable("DEBUG_LOGGING") ? "1" : "0";
+    remote_admin         = cgiGetVariable("REMOTE_ADMIN") ? "1" : "0";
+    remote_any           = cgiGetVariable("REMOTE_ANY") ? "1" : "0";
+    share_printers       = cgiGetVariable("SHARE_PRINTERS") ? "1" : "0";
+    user_cancel_any      = cgiGetVariable("USER_CANCEL_ANY") ? "1" : "0";
 
-  if ((ppd = ppdOpenFile(filename)) == NULL)
-  {
-    cgiSetVariable("ERROR", ppdErrorString(ppdLastError(&i)));
-    cgiSetVariable("MESSAGE", cgiText(_("Unable to open PPD file:")));
-    cgiStartHTML(title);
-    cgiCopyTemplateLang("error.tmpl");
-    cgiEndHTML();
-    return;
-  }
+    advanced = cgiGetVariable("ADVANCEDSETTINGS") != NULL;
+    if (advanced)
+    {
+     /*
+      * Get advanced settings...
+      */
 
-  if (cgiGetVariable("job_sheets_start") != NULL ||
-      cgiGetVariable("job_sheets_end") != NULL)
-    have_options = 1;
-  else
-    have_options = 0;
+      browse_web_if        = cgiGetVariable("BROWSE_WEB_IF") ? "Yes" : "No";
+      max_clients          = cgiGetVariable("MAX_CLIENTS");
+      max_log_size         = cgiGetVariable("MAX_LOG_SIZE");
+      preserve_jobs        = cgiGetVariable("PRESERVE_JOBS");
 
-  ppdMarkDefaults(ppd);
+      if (preserve_jobs)
+      {
+        max_jobs             = cgiGetVariable("MAX_JOBS");
+       preserve_job_history = cgiGetVariable("PRESERVE_JOB_HISTORY");
+       preserve_job_files   = cgiGetVariable("PRESERVE_JOB_FILES");
 
-  DEBUG_printf(("<P>ppd->num_groups = %d\n"
-                "<UL>\n", ppd->num_groups));
+       if (!max_jobs || atoi(max_jobs) < 0)
+         max_jobs = "500";
 
-  for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
-  {
-    DEBUG_printf(("<LI>%s<UL>\n", group->text));
+       if (!preserve_job_history)
+         preserve_job_history = "On";
 
-    for (j = group->num_options, option = group->options; j > 0; j --, option ++)
-      if ((var = cgiGetVariable(option->keyword)) != NULL)
-      {
-        DEBUG_printf(("<LI>%s = \"%s\"</LI>\n", option->keyword, var));
-        have_options = 1;
-       ppdMarkOption(ppd, option->keyword, var);
+       if (!preserve_job_files)
+         preserve_job_files = "1d";
       }
-#ifdef DEBUG
       else
-        printf("<LI>%s not defined!</LI>\n", option->keyword);
-#endif /* DEBUG */
+      {
+        max_jobs             = "0";
+        preserve_job_history = "No";
+        preserve_job_files   = "No";
+      }
 
-    DEBUG_puts("</UL></LI>");
-  }
+      if (!max_clients || atoi(max_clients) <= 0)
+       max_clients = "100";
 
-  DEBUG_printf(("</UL>\n"
-                "<P>ppdConflicts(ppd) = %d\n", ppdConflicts(ppd)));
+      if (!max_log_size || atoi(max_log_size) <= 0.0)
+       max_log_size = "1m";
+    }
 
-  if (!have_options || ppdConflicts(ppd))
-  {
    /*
-    * Show the options to the user...
+    * Get the current server settings...
     */
 
-    ppdLocalize(ppd);
-
-    cgiStartHTML("Set Printer Options");
-    cgiCopyTemplateLang("set-printer-options-header.tmpl");
-
-    if (ppdConflicts(ppd))
+    if (!cupsAdminGetServerSettings(http, &num_settings, &settings))
     {
-      for (i = ppd->num_groups, k = 0, group = ppd->groups; i > 0; i --, group ++)
-       for (j = group->num_options, option = group->options; j > 0; j --, option ++)
-          if (option->conflicted)
-         {
-           cgiSetArray("ckeyword", k, option->keyword);
-           cgiSetArray("ckeytext", k, option->text);
-           k ++;
-         }
-
-      cgiCopyTemplateLang("option-conflict.tmpl");
+      cgiStartHTML(cgiText(_("Change Settings")));
+      cgiSetVariable("MESSAGE",
+                     cgiText(_("Unable to change server settings")));
+      cgiSetVariable("ERROR", cupsLastErrorString());
+      cgiCopyTemplateLang("error.tmpl");
+      cgiEndHTML();
+      return;
     }
 
-    for (i = ppd->num_groups, group = ppd->groups;
-        i > 0;
-        i --, group ++)
-    {
-      if (!strcmp(group->name, "InstallableOptions"))
-       cgiSetVariable("GROUP", cgiText(_("Options Installed")));
-      else
-       cgiSetVariable("GROUP", group->text);
-
-      cgiCopyTemplateLang("option-header.tmpl");
-      
-      for (j = group->num_options, option = group->options;
-           j > 0;
-          j --, option ++)
-      {
-        if (!strcmp(option->keyword, "PageRegion"))
-         continue;
-
-        cgiSetVariable("KEYWORD", option->keyword);
-        cgiSetVariable("KEYTEXT", option->text);
-           
-       if (option->conflicted)
-         cgiSetVariable("CONFLICTED", "1");
-       else
-         cgiSetVariable("CONFLICTED", "0");
-
-       cgiSetSize("CHOICES", 0);
-       cgiSetSize("TEXT", 0);
-       for (k = 0, m = 0; k < option->num_choices; k ++)
-       {
-        /*
-         * Hide custom option values...
-         */
+#ifdef HAVE_GSSAPI
+   /*
+    * Get authentication settings...
+    */
 
-         if (!strcmp(option->choices[k].choice, "Custom"))
-           continue;
+    if (cgiGetVariable("KERBEROS"))
+      strlcpy(default_auth_type, "Negotiate", sizeof(default_auth_type));
+    else
+    {
+      val = cupsGetOption("DefaultAuthType", num_settings, settings);
 
-         cgiSetArray("CHOICES", m, option->choices[k].choice);
-         cgiSetArray("TEXT", m, option->choices[k].text);
+      if (!val || !_cups_strcasecmp(val, "Negotiate"))
+        strlcpy(default_auth_type, "Basic", sizeof(default_auth_type));
+      else
+        strlcpy(default_auth_type, val, sizeof(default_auth_type));
+    }
 
-          m ++;
+    fprintf(stderr, "DEBUG: DefaultAuthType %s\n", default_auth_type);
+#endif /* HAVE_GSSAPI */
 
-          if (option->choices[k].marked)
-           cgiSetVariable("DEFCHOICE", option->choices[k].choice);
-       }
+    if ((current_browse_web_if = cupsGetOption("BrowseWebIF", num_settings,
+                                               settings)) == NULL)
+      current_browse_web_if = "No";
 
-        switch (option->ui)
-       {
-         case PPD_UI_BOOLEAN :
-              cgiCopyTemplateLang("option-boolean.tmpl");
-              break;
-         case PPD_UI_PICKONE :
-              cgiCopyTemplateLang("option-pickone.tmpl");
-              break;
-         case PPD_UI_PICKMANY :
-              cgiCopyTemplateLang("option-pickmany.tmpl");
-              break;
-       }
-      }
+    if ((current_preserve_job_history = cupsGetOption("PreserveJobHistory",
+                                                      num_settings,
+                                                     settings)) == NULL)
+      current_preserve_job_history = "Yes";
 
-      cgiCopyTemplateLang("option-trailer.tmpl");
-    }
+    if ((current_preserve_job_files = cupsGetOption("PreserveJobFiles",
+                                                    num_settings,
+                                                   settings)) == NULL)
+      current_preserve_job_files = "1d";
 
-   /*
-    * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the
-    * following attributes:
-    *
-    *    attributes-charset
-    *    attributes-natural-language
-    *    printer-uri
-    */
+    if ((current_max_clients = cupsGetOption("MaxClients", num_settings,
+                                             settings)) == NULL)
+      current_max_clients = "100";
 
-    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
+    if ((current_max_jobs = cupsGetOption("MaxJobs", num_settings,
+                                          settings)) == NULL)
+      current_max_jobs = "500";
 
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", printer);
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                 NULL, uri);
+    if ((current_max_log_size = cupsGetOption("MaxLogSize", num_settings,
+                                              settings)) == NULL)
+      current_max_log_size = "1m";
 
    /*
-    * Do the request and get back a response...
+    * See if the settings have changed...
     */
 
-    if ((response = cupsDoRequest(http, request, "/")) != NULL)
+    changed = strcmp(debug_logging, cupsGetOption(CUPS_SERVER_DEBUG_LOGGING,
+                                                  num_settings, settings)) ||
+             strcmp(remote_admin, cupsGetOption(CUPS_SERVER_REMOTE_ADMIN,
+                                                num_settings, settings)) ||
+             strcmp(remote_any, cupsGetOption(CUPS_SERVER_REMOTE_ANY,
+                                              num_settings, settings)) ||
+             strcmp(share_printers, cupsGetOption(CUPS_SERVER_SHARE_PRINTERS,
+                                                  num_settings, settings)) ||
+#ifdef HAVE_GSSAPI
+             !cupsGetOption("DefaultAuthType", num_settings, settings) ||
+             strcmp(default_auth_type, cupsGetOption("DefaultAuthType",
+                                                     num_settings, settings)) ||
+#endif /* HAVE_GSSAPI */
+             strcmp(user_cancel_any, cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY,
+                                                   num_settings, settings));
+
+    if (advanced && !changed)
+      changed = _cups_strcasecmp(browse_web_if, current_browse_web_if) ||
+               _cups_strcasecmp(preserve_job_history, current_preserve_job_history) ||
+               _cups_strcasecmp(preserve_job_files, current_preserve_job_files) ||
+               _cups_strcasecmp(max_clients, current_max_clients) ||
+               _cups_strcasecmp(max_jobs, current_max_jobs) ||
+               _cups_strcasecmp(max_log_size, current_max_log_size);
+
+    if (changed)
     {
-      if ((attr = ippFindAttribute(response, "job-sheets-supported",
-                                   IPP_TAG_ZERO)) != NULL)
+     /*
+      * Settings *have* changed, so save the changes...
+      */
+
+      cupsFreeOptions(num_settings, settings);
+
+      num_settings = 0;
+      num_settings = cupsAddOption(CUPS_SERVER_DEBUG_LOGGING,
+                                   debug_logging, num_settings, &settings);
+      num_settings = cupsAddOption(CUPS_SERVER_REMOTE_ADMIN,
+                                   remote_admin, num_settings, &settings);
+      num_settings = cupsAddOption(CUPS_SERVER_REMOTE_ANY,
+                                   remote_any, num_settings, &settings);
+      num_settings = cupsAddOption(CUPS_SERVER_SHARE_PRINTERS,
+                                   share_printers, num_settings, &settings);
+      num_settings = cupsAddOption(CUPS_SERVER_USER_CANCEL_ANY,
+                                   user_cancel_any, num_settings, &settings);
+#ifdef HAVE_GSSAPI
+      num_settings = cupsAddOption("DefaultAuthType", default_auth_type,
+                                   num_settings, &settings);
+#endif /* HAVE_GSSAPI */
+
+      if (advanced)
       {
        /*
-       * Add the job sheets options...
+        * Add advanced settings...
        */
 
-       cgiSetVariable("GROUP", cgiText(_("Banners")));
-       cgiCopyTemplateLang("option-header.tmpl");
+       if (_cups_strcasecmp(browse_web_if, current_browse_web_if))
+         num_settings = cupsAddOption("BrowseWebIF", browse_web_if,
+                                      num_settings, &settings);
+       if (_cups_strcasecmp(preserve_job_history, current_preserve_job_history))
+         num_settings = cupsAddOption("PreserveJobHistory",
+                                      preserve_job_history, num_settings,
+                                      &settings);
+       if (_cups_strcasecmp(preserve_job_files, current_preserve_job_files))
+         num_settings = cupsAddOption("PreserveJobFiles", preserve_job_files,
+                                      num_settings, &settings);
+        if (_cups_strcasecmp(max_clients, current_max_clients))
+         num_settings = cupsAddOption("MaxClients", max_clients, num_settings,
+                                      &settings);
+        if (_cups_strcasecmp(max_jobs, current_max_jobs))
+         num_settings = cupsAddOption("MaxJobs", max_jobs, num_settings,
+                                      &settings);
+        if (_cups_strcasecmp(max_log_size, current_max_log_size))
+         num_settings = cupsAddOption("MaxLogSize", max_log_size, num_settings,
+                                      &settings);
+      }
 
-       cgiSetSize("CHOICES", attr->num_values);
-       cgiSetSize("TEXT", attr->num_values);
-       for (k = 0; k < attr->num_values; k ++)
+      if (!cupsAdminSetServerSettings(http, num_settings, settings))
+      {
+        if (cupsLastError() == IPP_NOT_AUTHORIZED)
        {
-         cgiSetArray("CHOICES", k, attr->values[k].string.text);
-         cgiSetArray("TEXT", k, attr->values[k].string.text);
+         puts("Status: 401\n");
+         exit(0);
        }
 
-        attr = ippFindAttribute(response, "job-sheets-default", IPP_TAG_ZERO);
-
-        cgiSetVariable("KEYWORD", "job_sheets_start");
-       cgiSetVariable("KEYTEXT", cgiText(_("Starting Banner")));
-        cgiSetVariable("DEFCHOICE", attr == NULL ?
-                                   "" : attr->values[0].string.text);
-
-       cgiCopyTemplateLang("option-pickone.tmpl");
-
-        cgiSetVariable("KEYWORD", "job_sheets_end");
-       cgiSetVariable("KEYTEXT", cgiText(_("Ending Banner")));
-        cgiSetVariable("DEFCHOICE", attr == NULL && attr->num_values > 1 ?
-                                   "" : attr->values[1].string.text);
-
-       cgiCopyTemplateLang("option-pickone.tmpl");
-
-       cgiCopyTemplateLang("option-trailer.tmpl");
+       cgiStartHTML(cgiText(_("Change Settings")));
+       cgiSetVariable("MESSAGE",
+                       cgiText(_("Unable to change server settings")));
+       cgiSetVariable("ERROR", cupsLastErrorString());
+       cgiCopyTemplateLang("error.tmpl");
       }
-
-      if (ippFindAttribute(response, "printer-error-policy-supported",
-                           IPP_TAG_ZERO) ||
-          ippFindAttribute(response, "printer-op-policy-supported",
-                          IPP_TAG_ZERO))
+      else
       {
-       /*
-       * Add the error and operation policy options...
-       */
+        if (advanced)
+         cgiSetVariable("refresh_page", "5;URL=/admin/?OP=redirect&"
+                                        "URL=/admin/?ADVANCEDSETTINGS=YES");
+        else
+         cgiSetVariable("refresh_page", "5;URL=/admin/?OP=redirect");
+       cgiStartHTML(cgiText(_("Change Settings")));
+       cgiCopyTemplateLang("restart.tmpl");
+      }
+    }
+    else
+    {
+     /*
+      * No changes...
+      */
 
-       cgiSetVariable("GROUP", cgiText(_("Policies")));
-       cgiCopyTemplateLang("option-header.tmpl");
+      cgiSetVariable("refresh_page", "5;URL=/admin/?OP=redirect");
+      cgiStartHTML(cgiText(_("Change Settings")));
+      cgiCopyTemplateLang("norestart.tmpl");
+    }
 
-       /*
-        * Error policy...
-       */
+    cupsFreeOptions(num_settings, settings);
 
-        attr = ippFindAttribute(response, "printer-error-policy-supported",
-                               IPP_TAG_ZERO);
+    cgiEndHTML();
+  }
+  else if (cgiGetVariable("SAVECHANGES") && cgiGetVariable("CUPSDCONF"))
+  {
+   /*
+    * Save hand-edited config file...
+    */
 
-        if (attr)
-       {
-         cgiSetSize("CHOICES", attr->num_values);
-         cgiSetSize("TEXT", attr->num_values);
-         for (k = 0; k < attr->num_values; k ++)
-         {
-           cgiSetArray("CHOICES", k, attr->values[k].string.text);
-           cgiSetArray("TEXT", k, attr->values[k].string.text);
-         }
+    http_status_t status;              /* PUT status */
+    char       tempfile[1024];         /* Temporary new cupsd.conf */
+    int                tempfd;                 /* Temporary file descriptor */
+    cups_file_t        *temp;                  /* Temporary file */
+    const char *start,                 /* Start of line */
+               *end;                   /* End of line */
 
-          attr = ippFindAttribute(response, "printer-error-policy",
-                                 IPP_TAG_ZERO);
 
-          cgiSetVariable("KEYWORD", "printer_error_policy");
-         cgiSetVariable("KEYTEXT", cgiText(_("Error Policy")));
-          cgiSetVariable("DEFCHOICE", attr == NULL ?
-                                     "" : attr->values[0].string.text);
-        }
+   /*
+    * Create a temporary file for the new cupsd.conf file...
+    */
 
-       cgiCopyTemplateLang("option-pickone.tmpl");
+    if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
+    {
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiSetVariable("MESSAGE", cgiText(_("Unable to create temporary file")));
+      cgiSetVariable("ERROR", strerror(errno));
+      cgiCopyTemplateLang("error.tmpl");
+      cgiEndHTML();
 
-       /*
-        * Operation policy...
-       */
-
-        attr = ippFindAttribute(response, "printer-op-policy-supported",
-                               IPP_TAG_ZERO);
-
-        if (attr)
-       {
-         cgiSetSize("CHOICES", attr->num_values);
-         cgiSetSize("TEXT", attr->num_values);
-         for (k = 0; k < attr->num_values; k ++)
-         {
-           cgiSetArray("CHOICES", k, attr->values[k].string.text);
-           cgiSetArray("TEXT", k, attr->values[k].string.text);
-         }
-
-          attr = ippFindAttribute(response, "printer-op-policy", IPP_TAG_ZERO);
-
-          cgiSetVariable("KEYWORD", "printer_op_policy");
-         cgiSetVariable("KEYTEXT", cgiText(_("Operation Policy")));
-          cgiSetVariable("DEFCHOICE", attr == NULL ?
-                                     "" : attr->values[0].string.text);
-
-         cgiCopyTemplateLang("option-pickone.tmpl");
-        }
-
-       cgiCopyTemplateLang("option-trailer.tmpl");
-      }
-
-      ippDelete(response);
-    }
-
-   /*
-    * Binary protocol support...
-    */
-
-    if (ppd->protocols && strstr(ppd->protocols, "BCP"))
-    {
-      protocol = ppdFindAttr(ppd, "cupsProtocol", NULL);
-
-      cgiSetVariable("GROUP", cgiText(_("PS Binary Protocol")));
-      cgiCopyTemplateLang("option-header.tmpl");
-
-      cgiSetSize("CHOICES", 2);
-      cgiSetSize("TEXT", 2);
-      cgiSetArray("CHOICES", 0, "None");
-      cgiSetArray("TEXT", 0, cgiText(_("None")));
-
-      if (strstr(ppd->protocols, "TBCP"))
-      {
-       cgiSetArray("CHOICES", 1, "TBCP");
-       cgiSetArray("TEXT", 1, "TBCP");
-      }
-      else
-      {
-       cgiSetArray("CHOICES", 1, "BCP");
-       cgiSetArray("TEXT", 1, "BCP");
-      }
-
-      cgiSetVariable("KEYWORD", "protocol");
-      cgiSetVariable("KEYTEXT", cgiText(_("PS Binary Protocol")));
-      cgiSetVariable("DEFCHOICE", protocol ? protocol->value : "None");
-
-      cgiCopyTemplateLang("option-pickone.tmpl");
-
-      cgiCopyTemplateLang("option-trailer.tmpl");
+      perror(tempfile);
+      return;
     }
 
-    cgiCopyTemplateLang("set-printer-options-trailer.tmpl");
-    cgiEndHTML();
-  }
-  else
-  {
-   /*
-    * Set default options...
-    */
-
-    out = cupsTempFile2(tempfile, sizeof(tempfile));
-    in  = cupsFileOpen(filename, "r");
-
-    if (!in || !out)
+    if ((temp = cupsFileOpenFd(tempfd, "w")) == NULL)
     {
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiSetVariable("MESSAGE", cgiText(_("Unable to create temporary file")));
       cgiSetVariable("ERROR", strerror(errno));
-      cgiStartHTML("Set Printer Options");
       cgiCopyTemplateLang("error.tmpl");
       cgiEndHTML();
 
-      if (in)
-        cupsFileClose(in);
-
-      if (out)
-      {
-        cupsFileClose(out);
-       unlink(tempfile);
-      }
-
-      unlink(filename);
+      perror(tempfile);
+      close(tempfd);
+      unlink(tempfile);
       return;
     }
 
-    while (cupsFileGets(in, line, sizeof(line)))
-    {
-      if (!strncmp(line, "*cupsProtocol:", 14) && cgiGetVariable("protocol"))
-        continue;
-      else if (strncmp(line, "*Default", 8))
-        cupsFilePrintf(out, "%s\n", line);
-      else
-      {
-       /*
-        * Get default option name...
-       */
-
-        strlcpy(keyword, line + 8, sizeof(keyword));
-
-       for (keyptr = keyword; *keyptr; keyptr ++)
-         if (*keyptr == ':' || isspace(*keyptr & 255))
-           break;
-
-        *keyptr = '\0';
-
-        if (!strcmp(keyword, "PageRegion"))
-         var = cgiGetVariable("PageSize");
-       else
-         var = cgiGetVariable(keyword);
-
-        if (var != NULL)
-         cupsFilePrintf(out, "*Default%s: %s\n", keyword, var);
-       else
-         cupsFilePrintf(out, "%s\n", line);
-      }
-    }
-
-    if ((var = cgiGetVariable("protocol")) != NULL)
-      cupsFilePrintf(out, "*cupsProtocol: %s\n", cgiGetVariable("protocol"));
-
-    cupsFileClose(in);
-    cupsFileClose(out);
-
    /*
-    * Build a CUPS_ADD_PRINTER request, which requires the following
-    * attributes:
-    *
-    *    attributes-charset
-    *    attributes-natural-language
-    *    printer-uri
-    *    job-sheets-default
-    *    [ppd file]
+    * Copy the cupsd.conf text from the form variable...
     */
 
-    request = ippNewRequest(CUPS_ADD_PRINTER);
-
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", cgiGetVariable("PRINTER_NAME"));
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                 NULL, uri);
+    start = cgiGetVariable("CUPSDCONF");
+    while (start)
+    {
+      if ((end = strstr(start, "\r\n")) == NULL)
+        if ((end = strstr(start, "\n")) == NULL)
+         end = start + strlen(start);
 
-    attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
-                         "job-sheets-default", 2, NULL, NULL);
-    attr->values[0].string.text = strdup(cgiGetVariable("job_sheets_start"));
-    attr->values[1].string.text = strdup(cgiGetVariable("job_sheets_end"));
+      cupsFileWrite(temp, start, (size_t)(end - start));
+      cupsFilePutChar(temp, '\n');
 
-    if ((var = cgiGetVariable("printer_error_policy")) != NULL)
-      attr = ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
-                          "printer-error-policy", NULL, var);
+      if (*end == '\r')
+        start = end + 2;
+      else if (*end == '\n')
+        start = end + 1;
+      else
+        start = NULL;
+    }
 
-    if ((var = cgiGetVariable("printer_op_policy")) != NULL)
-      attr = ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
-                          "printer-op-policy", NULL, var);
+    cupsFileClose(temp);
 
    /*
-    * Do the request and get back a response...
+    * Upload the configuration file to the server...
     */
 
-    ippDelete(cupsDoFileRequest(http, request, "/admin/", tempfile));
+    status = cupsPutFile(http, "/admin/conf/cupsd.conf", tempfile);
 
-    if (cupsLastError() > IPP_OK_CONFLICT)
+    if (status == HTTP_UNAUTHORIZED)
     {
-      cgiStartHTML(title);
-      cgiShowIPPError(_("Unable to set options:"));
+      puts("Status: 401\n");
+      unlink(tempfile);
+      exit(0);
     }
-    else
+    else if (status != HTTP_CREATED)
     {
-     /*
-      * Redirect successful updates back to the printer page...
-      */
-
-      char     refresh[1024];          /* Refresh URL */
-
-
-      cgiFormEncode(uri, printer, sizeof(uri));
-      snprintf(refresh, sizeof(refresh), "5;/admin/?OP=redirect&URL=/printers/%s",
-               uri);
-      cgiSetVariable("refresh_page", refresh);
+      cgiSetVariable("MESSAGE",
+                     cgiText(_("Unable to upload cupsd.conf file")));
+      cgiSetVariable("ERROR", httpStatus(status));
 
-      cgiStartHTML(title);
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiCopyTemplateLang("error.tmpl");
+    }
+    else
+    {
+      cgiSetVariable("refresh_page", "5;URL=/admin/");
 
-      cgiCopyTemplateLang("printer-configured.tmpl");
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiCopyTemplateLang("restart.tmpl");
     }
 
     cgiEndHTML();
 
     unlink(tempfile);
   }
-
-  unlink(filename);
-}
-
-
-/*
- * 'do_config_server()' - Configure server settings.
- */
-
-static void
-do_config_server(http_t *http)         /* I - HTTP connection */
-{
-  if (cgiIsPOST() && !cgiGetVariable("CUPSDCONF"))
+  else
   {
-   /*
-    * Save basic setting changes...
-    */
-
-    http_status_t status;              /* PUT status */
+    struct stat        info;                   /* cupsd.conf information */
     cups_file_t        *cupsd;                 /* cupsd.conf file */
-    char       tempfile[1024];         /* Temporary new cupsd.conf */
-    int                tempfd;                 /* Temporary file descriptor */
-    cups_file_t        *temp;                  /* Temporary file */
-    char       line[1024],             /* Line from cupsd.conf file */
-               *value;                 /* Value on line */
+    char       *buffer,                /* Buffer for entire file */
+               *bufptr,                /* Pointer into buffer */
+               *bufend;                /* End of buffer */
+    int                ch;                     /* Character from file */
+    char       filename[1024];         /* Filename */
     const char *server_root;           /* Location of config files */
-    int                linenum,                /* Line number in file */
-               in_policy,              /* In a policy section? */
-               in_cancel_job,          /* In a cancel-job section? */
-               in_admin_location,      /* In the /admin location? */
-               in_conf_location,       /* In the /admin/conf location? */
-               in_root_location;       /* In the / location? */
-    int                remote_printers,        /* Show remote printers */
-               share_printers,         /* Share local printers */
-               remote_admin,           /* Remote administration allowed? */
-               user_cancel_any,        /* Cancel-job policy set? */
-               debug_logging;          /* LogLevel debug set? */
-    int                wrote_port_listen,      /* Wrote the port/listen lines? */
-               wrote_browsing,         /* Wrote the browsing lines? */
-               wrote_policy,           /* Wrote the policy? */
-               wrote_loglevel,         /* Wrote the LogLevel line? */
-               wrote_admin_location,   /* Wrote the /admin location? */
-               wrote_conf_location,    /* Wrote the /admin/conf location? */
-               wrote_root_location;    /* Wrote the / location? */
-    int                indent;                 /* Indentation */
-
-
-   /*
-    * Get form variables...
-    */
 
-    remote_printers = cgiGetVariable("REMOTE_PRINTERS") != NULL;
-    share_printers  = cgiGetVariable("SHARE_PRINTERS") != NULL;
-    remote_admin    = cgiGetVariable("REMOTE_ADMIN") != NULL;
-    user_cancel_any = cgiGetVariable("USER_CANCEL_ANY") != NULL;
-    debug_logging   = cgiGetVariable("DEBUG_LOGGING") != NULL;
 
    /*
     * Locate the cupsd.conf file...
@@ -1576,1528 +1638,896 @@ do_config_server(http_t *http)               /* I - HTTP connection */
     if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
       server_root = CUPS_SERVERROOT;
 
-    snprintf(line, sizeof(line), "%s/cupsd.conf", server_root);
+    snprintf(filename, sizeof(filename), "%s/cupsd.conf", server_root);
 
    /*
-    * Open the cupsd.conf file...
+    * Figure out the size...
     */
 
-    if ((cupsd = cupsFileOpen(line, "r")) == NULL)
+    if (stat(filename, &info))
     {
-     /*
-      * Unable to open - log an error...
-      */
-
-      cgiStartHTML(cgiText(_("Change Settings")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to change server settings:")));
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiSetVariable("MESSAGE",
+                     cgiText(_("Unable to access cupsd.conf file")));
       cgiSetVariable("ERROR", strerror(errno));
       cgiCopyTemplateLang("error.tmpl");
       cgiEndHTML();
-      
-      perror(line);
+
+      perror(filename);
       return;
     }
 
-   /*
-    * Create a temporary file for the new cupsd.conf file...
-    */
-
-    if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
+    if (info.st_size > (1024 * 1024))
     {
-      cgiStartHTML(cgiText(_("Change Settings")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to change server settings:")));
-      cgiSetVariable("ERROR", strerror(errno));
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiSetVariable("MESSAGE",
+                     cgiText(_("Unable to access cupsd.conf file")));
+      cgiSetVariable("ERROR",
+                     cgiText(_("Unable to edit cupsd.conf files larger than "
+                              "1MB")));
       cgiCopyTemplateLang("error.tmpl");
       cgiEndHTML();
-      
-      perror(tempfile);
-      cupsFileClose(cupsd);
+
+      fprintf(stderr, "ERROR: \"%s\" too large (%ld) to edit!\n", filename,
+              (long)info.st_size);
       return;
     }
 
-    if ((temp = cupsFileOpenFd(tempfd, "w")) == NULL)
+   /*
+    * Open the cupsd.conf file...
+    */
+
+    if ((cupsd = cupsFileOpen(filename, "r")) == NULL)
     {
-      cgiStartHTML(cgiText(_("Change Settings")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to change server settings:")));
+     /*
+      * Unable to open - log an error...
+      */
+
+      cgiStartHTML(cgiText(_("Edit Configuration File")));
+      cgiSetVariable("MESSAGE",
+                     cgiText(_("Unable to access cupsd.conf file")));
       cgiSetVariable("ERROR", strerror(errno));
       cgiCopyTemplateLang("error.tmpl");
       cgiEndHTML();
-      
-      perror(tempfile);
-      close(tempfd);
-      unlink(tempfile);
-      cupsFileClose(cupsd);
+
+      perror(filename);
       return;
     }
 
    /*
-    * Copy the old file to the new, making changes along the way...
+    * Allocate memory and load the file into a string buffer...
+    */
+
+    if ((buffer = calloc(1, (size_t)info.st_size + 1)) != NULL)
+    {
+      cupsFileRead(cupsd, buffer, (size_t)info.st_size);
+      cgiSetVariable("CUPSDCONF", buffer);
+      free(buffer);
+    }
+
+    cupsFileClose(cupsd);
+
+   /*
+    * Then get the default cupsd.conf file and put that into a string as
+    * well...
     */
 
-    in_admin_location    = 0;
-    in_cancel_job        = 0;
-    in_conf_location     = 0;
-    in_policy            = 0;
-    in_root_location     = 0;
-    linenum              = 0;
-    wrote_admin_location = 0;
-    wrote_browsing       = 0;
-    wrote_conf_location  = 0;
-    wrote_loglevel       = 0;
-    wrote_policy         = 0;
-    wrote_port_listen    = 0;
-    wrote_root_location  = 0;
-    indent               = 0;
-
-    while (cupsFileGetConf(cupsd, line, sizeof(line), &value, &linenum))
-    {
-      if (!strcasecmp(line, "Port") || !strcasecmp(line, "Listen"))
+    strlcat(filename, ".default", sizeof(filename));
+
+    if (!stat(filename, &info) && info.st_size < (1024 * 1024) &&
+        (cupsd = cupsFileOpen(filename, "r")) != NULL)
+    {
+      if ((buffer = calloc(1, 2 * (size_t)info.st_size + 1)) != NULL)
       {
-       if (!wrote_port_listen)
-       {
-          wrote_port_listen = 1;
+       bufend = buffer + 2 * info.st_size - 1;
 
-         if (share_printers || remote_admin)
+       for (bufptr = buffer;
+            bufptr < bufend && (ch = cupsFileGetChar(cupsd)) != EOF;)
+       {
+         if (ch == '\\' || ch == '\"')
          {
-           cupsFilePuts(temp, "# Allow remote access\n");
-           cupsFilePrintf(temp, "Listen *:%d\n", ippPort());
+           *bufptr++ = '\\';
+           *bufptr++ = (char)ch;
          }
-         else
+         else if (ch == '\n')
          {
-           cupsFilePuts(temp, "# Only listen for connections from the local machine.\n");
-           cupsFilePrintf(temp, "Listen localhost:%d\n", ippPort());
+           *bufptr++ = '\\';
+           *bufptr++ = 'n';
          }
-
-#ifdef CUPS_DEFAULT_DOMAINSOCKET
-          cupsFilePuts(temp, "Listen " CUPS_DEFAULT_DOMAINSOCKET "\n");
-#endif /* CUPS_DEFAULT_DOMAINSOCKET */
-       }
-      }
-      else if (!strcasecmp(line, "Browsing") ||
-               !strcasecmp(line, "BrowseAddress") ||
-               !strcasecmp(line, "BrowseAllow") ||
-               !strcasecmp(line, "BrowseDeny") ||
-               !strcasecmp(line, "BrowseOrder"))
-      {
-       if (!wrote_browsing)
-       {
-          wrote_browsing = 1;
-
-          if (remote_printers || share_printers)
-         {
-           if (remote_printers && share_printers)
-             cupsFilePuts(temp, "# Enable printer sharing and shared printers.\n");
-           else if (remote_printers)
-             cupsFilePuts(temp, "# Show shared printers on the local network.\n");
-           else
-             cupsFilePuts(temp, "# Share local printers on the local network.\n");
-
-           cupsFilePuts(temp, "Browsing On\n");
-           cupsFilePuts(temp, "BrowseOrder allow,deny\n");
-
-           if (remote_printers)
-             cupsFilePuts(temp, "BrowseAllow @LOCAL\n");
-
-           if (share_printers)
-             cupsFilePuts(temp, "BrowseAddress @LOCAL\n");
-          }
-         else
+         else if (ch == '\t')
          {
-           cupsFilePuts(temp, "# Disable printer sharing and shared printers.\n");
-           cupsFilePuts(temp, "Browsing Off\n");
+           *bufptr++ = '\\';
+           *bufptr++ = 't';
          }
+         else if (ch >= ' ')
+           *bufptr++ = (char)ch;
        }
-      }
-      else if (!strcasecmp(line, "LogLevel"))
-      {
-        wrote_loglevel = 1;
 
-       if (debug_logging)
-       {
-          cupsFilePuts(temp, "# Show troubleshooting information in error_log.\n");
-         cupsFilePuts(temp, "LogLevel debug\n");
-       }
-       else
-       {
-          cupsFilePuts(temp, "# Show general information in error_log.\n");
-         cupsFilePuts(temp, "LogLevel info\n");
-       }
-      }
-      else if (!strcasecmp(line, "<Policy") && !strcasecmp(value, "default"))
-      {
-       in_policy = 1;
+       *bufptr = '\0';
 
-       cupsFilePrintf(temp, "%s %s>\n", line, value);
-       indent += 2;
+       cgiSetVariable("CUPSDCONF_DEFAULT", buffer);
+       free(buffer);
       }
-      else if (!strcasecmp(line, "</Policy>"))
-      {
-       indent -= 2;
-        if (!wrote_policy)
-       {
-         wrote_policy = 1;
-
-          if (!user_cancel_any)
-           cupsFilePuts(temp, "  # Only the owner or an administrator can cancel a job...\n"
-                              "  <Limit Cancel-Job>\n"
-                              "    Order deny,allow\n"
-                              "    Allow @SYSTEM\n"
-                              "    Allow @OWNER\n"
-                              "  </Limit>\n");
-        }
 
-       in_policy = 0;
+      cupsFileClose(cupsd);
+    }
 
-       cupsFilePuts(temp, "</Policy>\n");
-      }
-      else if (!strcasecmp(line, "<Location"))
-      {
-       indent += 2;
-        if (!strcmp(value, "/admin"))
-         in_admin_location = 1;
-        if (!strcmp(value, "/admin/conf"))
-         in_conf_location = 1;
-       else if (!strcmp(value, "/"))
-         in_root_location = 1;
-
-       cupsFilePrintf(temp, "%s %s>\n", line, value);
-      }
-      else if (!strcasecmp(line, "</Location>"))
-      {
-       indent -= 2;
-        if (in_admin_location)
-       {
-         wrote_admin_location = 1;
+   /*
+    * Show the current config file...
+    */
 
-         if (remote_admin)
-            cupsFilePuts(temp, "  # Allow remote administration...\n");
-         else
-            cupsFilePuts(temp, "  # Restrict access to the admin pages...\n");
+    cgiStartHTML(cgiText(_("Edit Configuration File")));
 
-          cupsFilePuts(temp, "  Order allow,deny\n");
-
-         if (remote_admin)
-           cupsFilePuts(temp, "  Allow @LOCAL\n");
-         else
-           cupsFilePuts(temp, "  Allow localhost\n");
-       }
-        else if (in_conf_location)
-       {
-         wrote_conf_location = 1;
-
-         if (remote_admin)
-            cupsFilePuts(temp, "  # Allow remote access to the configuration files...\n");
-         else
-            cupsFilePuts(temp, "  # Restrict access to the configuration files...\n");
-
-          cupsFilePuts(temp, "  Order allow,deny\n");
+    cgiCopyTemplateLang("edit-config.tmpl");
 
-         if (remote_admin)
-           cupsFilePuts(temp, "  Allow @LOCAL\n");
-         else
-           cupsFilePuts(temp, "  Allow localhost\n");
-       }
-       else if (in_root_location)
-       {
-         wrote_root_location = 1;
-
-         if (remote_admin && share_printers)
-            cupsFilePuts(temp, "  # Allow shared printing and remote administration...\n");
-         else if (remote_admin)
-            cupsFilePuts(temp, "  # Allow remote administration...\n");
-         else if (share_printers)
-            cupsFilePuts(temp, "  # Allow shared printing...\n");
-         else
-            cupsFilePuts(temp, "  # Restrict access to the server...\n");
+    cgiEndHTML();
+  }
+}
 
-          cupsFilePuts(temp, "  Order allow,deny\n");
 
-         if (remote_admin || share_printers)
-           cupsFilePuts(temp, "  Allow @LOCAL\n");
-         else
-           cupsFilePuts(temp, "  Allow localhost\n");
-       }
+/*
+ * 'do_delete_class()' - Delete a class.
+ */
 
-       in_admin_location = 0;
-       in_conf_location  = 0;
-        in_root_location  = 0;
+static void
+do_delete_class(http_t *http)          /* I - HTTP connection */
+{
+  ipp_t                *request;               /* IPP request */
+  char         uri[HTTP_MAX_URI];      /* Job URI */
+  const char   *pclass;                /* Printer class name */
 
-       cupsFilePuts(temp, "</Location>\n");
-      }
-      else if (!strcasecmp(line, "<Limit") && in_policy)
-      {
-       /*
-       * See if the policy limit is for the Cancel-Job operation...
-       */
 
-       char    *valptr;                /* Pointer into value */
+ /*
+  * Get form variables...
+  */
 
+  if (cgiGetVariable("CONFIRM") == NULL)
+  {
+    cgiStartHTML(cgiText(_("Delete Class")));
+    cgiCopyTemplateLang("class-confirm.tmpl");
+    cgiEndHTML();
+    return;
+  }
 
-       indent += 2;
+  if ((pclass = cgiGetVariable("PRINTER_NAME")) != NULL)
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, "/classes/%s", pclass);
+  else
+  {
+    cgiStartHTML(cgiText(_("Delete Class")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
+    cgiCopyTemplateLang("error.tmpl");
+    cgiEndHTML();
+    return;
+  }
 
-        if (!strcasecmp(value, "cancel-job"))
-       {
-        /*
-         * Don't write anything for this limit section...
-         */
+ /*
+  * Build a CUPS_DELETE_CLASS request, which requires the following
+  * attributes:
+  *
+  *    attributes-charset
+  *    attributes-natural-language
+  *    printer-uri
+  */
 
-         in_cancel_job = 2;
-       }
-       else
-       {
-         cupsFilePrintf(temp, "  %s", line);
+  request = ippNewRequest(CUPS_DELETE_CLASS);
 
-         while (*value)
-         {
-           for (valptr = value; !isspace(*valptr & 255) && *valptr; valptr ++);
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+               NULL, uri);
 
-           if (*valptr)
-             *valptr++ = '\0';
+ /*
+  * Do the request and get back a response...
+  */
 
-            if (!strcasecmp(value, "cancel-job"))
-           {
-            /*
-             * Write everything except for this definition...
-             */
+  ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-             in_cancel_job = 1;
-           }
-           else
-             cupsFilePrintf(temp, " %s", value);
+ /*
+  * Show the results...
+  */
 
-            for (value = valptr; isspace(*value & 255); value ++);
-         }
+  if (cupsLastError() == IPP_NOT_AUTHORIZED)
+  {
+    puts("Status: 401\n");
+    exit(0);
+  }
+  else if (cupsLastError() <= IPP_OK_CONFLICT)
+  {
+   /*
+    * Redirect successful updates back to the classes page...
+    */
 
-         cupsFilePuts(temp, ">\n");
-        }
-      }
-      else if (!strcasecmp(line, "</Limit>") && in_cancel_job)
-      {
-       indent -= 2;
+    cgiSetVariable("refresh_page", "5;URL=/admin/?OP=redirect&URL=/classes");
+  }
 
-        if (in_cancel_job == 1)
-          cupsFilePuts(temp, "  </Limit>\n");
+  cgiStartHTML(cgiText(_("Delete Class")));
 
-        wrote_policy = 1;
+  if (cupsLastError() > IPP_OK_CONFLICT)
+    cgiShowIPPError(_("Unable to delete class"));
+  else
+    cgiCopyTemplateLang("class-deleted.tmpl");
 
-        if (!user_cancel_any)
-         cupsFilePuts(temp, "  # Only the owner or an administrator can cancel a job...\n"
-                            "  <Limit Cancel-Job>\n"
-                            "    Order deny,allow\n"
-                            "    Require user @OWNER @SYSTEM\n"
-                            "  </Limit>\n");
+  cgiEndHTML();
+}
 
-       in_cancel_job = 0;
-      }
-      else if ((in_admin_location || in_conf_location || in_root_location) &&
-               (!strcasecmp(line, "Allow") || !strcasecmp(line, "Deny") ||
-               !strcasecmp(line, "Order")))
-       continue;
-      else if (in_cancel_job == 2)
-        continue;
-      else if (!strcasecmp(line, "<Limit")  && value)
-       cupsFilePrintf(temp, "  %s %s>\n", line, value);
-      else if (line[0] == '<')
-      {
-        if (value)
-       {
-          cupsFilePrintf(temp, "%*s%s %s>\n", indent, "", line, value);
-         indent += 2;
-       }
-       else
-       {
-         if (line[1] == '/')
-           indent -= 2;
 
-         cupsFilePrintf(temp, "%*s%s\n", indent, "", line);
-       }
-      }
-      else if (value)
-       cupsFilePrintf(temp, "%*s%s %s\n", indent, "", line, value);
-      else
-       cupsFilePrintf(temp, "%*s%s\n", indent, "", line);
-    }
+/*
+ * 'do_delete_printer()' - Delete a printer.
+ */
 
-   /*
-    * Write any missing info...
-    */
+static void
+do_delete_printer(http_t *http)                /* I - HTTP connection */
+{
+  ipp_t                *request;               /* IPP request */
+  char         uri[HTTP_MAX_URI];      /* Job URI */
+  const char   *printer;               /* Printer printer name */
 
-    if (!wrote_browsing)
-    {
-      if (remote_printers || share_printers)
-      {
-       if (remote_printers && share_printers)
-         cupsFilePuts(temp, "# Enable printer sharing and shared printers.\n");
-       else if (remote_printers)
-         cupsFilePuts(temp, "# Show shared printers on the local network.\n");
-       else
-         cupsFilePuts(temp, "# Share local printers on the local network.\n");
 
-       cupsFilePuts(temp, "Browsing On\n");
-       cupsFilePuts(temp, "BrowseOrder allow,deny\n");
+ /*
+  * Get form variables...
+  */
 
-       if (remote_printers)
-         cupsFilePuts(temp, "BrowseAllow @LOCAL\n");
+  if (cgiGetVariable("CONFIRM") == NULL)
+  {
+    cgiStartHTML(cgiText(_("Delete Printer")));
+    cgiCopyTemplateLang("printer-confirm.tmpl");
+    cgiEndHTML();
+    return;
+  }
 
-       if (share_printers)
-         cupsFilePuts(temp, "BrowseAddress @LOCAL\n");
-      }
-      else
-      {
-       cupsFilePuts(temp, "# Disable printer sharing and shared printers.\n");
-       cupsFilePuts(temp, "Browsing Off\n");
-      }
-    }
+  if ((printer = cgiGetVariable("PRINTER_NAME")) != NULL)
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, "/printers/%s", printer);
+  else
+  {
+    cgiStartHTML(cgiText(_("Delete Printer")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
+    cgiCopyTemplateLang("error.tmpl");
+    cgiEndHTML();
+    return;
+  }
 
-    if (!wrote_loglevel)
-    {
-      if (debug_logging)
-      {
-        cupsFilePuts(temp, "# Show troubleshooting information in error_log.\n");
-       cupsFilePuts(temp, "LogLevel debug\n");
-      }
-      else
-      {
-        cupsFilePuts(temp, "# Show general information in error_log.\n");
-       cupsFilePuts(temp, "LogLevel info\n");
-      }
-    }
+ /*
+  * Build a CUPS_DELETE_PRINTER request, which requires the following
+  * attributes:
+  *
+  *    attributes-charset
+  *    attributes-natural-language
+  *    printer-uri
+  */
 
-    if (!wrote_port_listen)
-    {
-      if (share_printers || remote_admin)
-      {
-       cupsFilePuts(temp, "# Allow remote access\n");
-       cupsFilePrintf(temp, "Listen *:%d\n", ippPort());
-      }
-      else
-      {
-       cupsFilePuts(temp, "# Only listen for connections from the local machine.\n");
-       cupsFilePrintf(temp, "Listen localhost:%d\n", ippPort());
-      }
+  request = ippNewRequest(CUPS_DELETE_PRINTER);
 
-#ifdef CUPS_DEFAULT_DOMAINSOCKET
-      cupsFilePuts(temp, "Listen " CUPS_DEFAULT_DOMAINSOCKET "\n");
-#endif /* CUPS_DEFAULT_DOMAINSOCKET */
-    }
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+               NULL, uri);
 
-    if (!wrote_root_location)
-    {
-      if (remote_admin && share_printers)
-        cupsFilePuts(temp, "# Allow shared printing and remote administration...\n");
-      else if (remote_admin)
-        cupsFilePuts(temp, "# Allow remote administration...\n");
-      else if (share_printers)
-        cupsFilePuts(temp, "# Allow shared printing...\n");
-      else
-        cupsFilePuts(temp, "# Restrict access to the server...\n");
+ /*
+  * Do the request and get back a response...
+  */
 
-      cupsFilePuts(temp, "<Location />\n"
-                         "  Order allow,deny\n");
+  ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-      if (remote_admin || share_printers)
-       cupsFilePuts(temp, "  Allow @LOCAL\n");
-      else
-       cupsFilePuts(temp, "  Allow localhost\n");
+ /*
+  * Show the results...
+  */
 
-      cupsFilePuts(temp, "</Location>\n");
-    }
+  if (cupsLastError() == IPP_NOT_AUTHORIZED)
+  {
+    puts("Status: 401\n");
+    exit(0);
+  }
+  else if (cupsLastError() <= IPP_OK_CONFLICT)
+  {
+   /*
+    * Redirect successful updates back to the printers page...
+    */
 
-    if (!wrote_admin_location)
-    {
-      if (remote_admin)
-        cupsFilePuts(temp, "# Allow remote administration...\n");
-      else
-        cupsFilePuts(temp, "# Restrict access to the admin pages...\n");
+    cgiSetVariable("refresh_page", "5;URL=/admin/?OP=redirect&URL=/printers");
+  }
 
-      cupsFilePuts(temp, "<Location /admin>\n"
-                         "  Order allow,deny\n");
+  cgiStartHTML(cgiText(_("Delete Printer")));
 
-      if (remote_admin)
-       cupsFilePuts(temp, "  Allow @LOCAL\n");
-      else
-       cupsFilePuts(temp, "  Allow localhost\n");
+  if (cupsLastError() > IPP_OK_CONFLICT)
+    cgiShowIPPError(_("Unable to delete printer"));
+  else
+    cgiCopyTemplateLang("printer-deleted.tmpl");
 
-      cupsFilePuts(temp, "</Location>\n");
-    }
+  cgiEndHTML();
+}
 
-    if (!wrote_conf_location)
-    {
-      if (remote_admin)
-        cupsFilePuts(temp, "# Allow remote access to the configuration files...\n");
-      else
-        cupsFilePuts(temp, "# Restrict access to the configuration files...\n");
 
-      cupsFilePuts(temp, "<Location /admin/conf>\n"
-                         "  AuthType Basic\n"
-                         "  Require user @SYSTEM\n"
-                         "  Order allow,deny\n");
+/*
+ * 'do_list_printers()' - List available printers.
+ */
 
-      if (remote_admin)
-       cupsFilePuts(temp, "  Allow @LOCAL\n");
-      else
-       cupsFilePuts(temp, "  Allow localhost\n");
-
-      cupsFilePuts(temp, "</Location>\n");
-    }
-
-    if (!wrote_policy)
-    {
-      cupsFilePuts(temp, "<Policy default>\n"
-                         "  # Job-related operations must be done by the owner or an adminstrator...\n"
-                         "  <Limit Send-Document Send-URI Hold-Job Release-Job "
-                        "Restart-Job Purge-Jobs Set-Job-Attributes "
-                        "Create-Job-Subscription Renew-Subscription "
-                        "Cancel-Subscription Get-Notifications Reprocess-Job "
-                        "Cancel-Current-Job Suspend-Current-Job Resume-Job "
-                        "CUPS-Move-Job>\n"
-                         "    Require user @OWNER @SYSTEM\n"
-                         "    Order deny,allow\n"
-                         "  </Limit>\n"
-                         "  # All administration operations require an adminstrator to authenticate...\n"
-                        "  <Limit Pause-Printer Resume-Printer "
-                         "Set-Printer-Attributes Enable-Printer "
-                        "Disable-Printer Pause-Printer-After-Current-Job "
-                        "Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer "
-                        "Activate-Printer Restart-Printer Shutdown-Printer "
-                        "Startup-Printer Promote-Job Schedule-Job-After "
-                        "CUPS-Add-Printer CUPS-Delete-Printer "
-                        "CUPS-Add-Class CUPS-Delete-Class "
-                        "CUPS-Accept-Jobs CUPS-Reject-Jobs "
-                        "CUPS-Set-Default CUPS-Add-Device CUPS-Delete-Device>\n"
-                         "    AuthType Basic\n"
-                        "    Require user @SYSTEM\n"
-                         "    Order deny,allow\n"
-                         "</Limit>\n");
-
-      if (!user_cancel_any)
-       cupsFilePuts(temp, "  # Only the owner or an administrator can cancel a job...\n"
-                          "  <Limit Cancel-Job>\n"
-                          "    Require user @OWNER @SYSTEM\n"
-                          "    Order deny,allow\n"
-                          "  </Limit>\n");
-
-      cupsFilePuts(temp, "  <Limit All>\n"
-                         "  Order deny,allow\n"
-                         "  </Limit>\n"
-                        "</Policy>\n");
-    }
+static void
+do_list_printers(http_t *http)         /* I - HTTP connection */
+{
+  ipp_t                *request,               /* IPP request */
+               *response;              /* IPP response */
+  ipp_attribute_t *attr;               /* IPP attribute */
 
-    cupsFileClose(cupsd);
-    cupsFileClose(temp);
 
-   /*
-    * Upload the configuration file to the server...
-    */
+  cgiStartHTML(cgiText(_("List Available Printers")));
+  fflush(stdout);
 
-    status = cupsPutFile(http, "/admin/conf/cupsd.conf", tempfile);
+ /*
+  * Get the list of printers and their devices...
+  */
 
-    if (status != HTTP_CREATED)
-    {
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to upload cupsd.conf file:")));
-      cgiSetVariable("ERROR", httpStatus(status));
-      cgiStartHTML(cgiText(_("Change Settings")));
-      cgiCopyTemplateLang("error.tmpl");
-    }
-    else
-    {
-      cgiSetVariable("refresh_page", "5;/admin/?OP=redirect");
+  request = ippNewRequest(CUPS_GET_PRINTERS);
 
-      cgiStartHTML(cgiText(_("Change Settings")));
-      cgiCopyTemplateLang("restart.tmpl");
-    }
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+               "requested-attributes", NULL, "device-uri");
 
-    cgiEndHTML();
+  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type",
+                CUPS_PRINTER_LOCAL);
+  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask",
+                CUPS_PRINTER_LOCAL);
 
-    unlink(tempfile);
-  }
-  else if (cgiIsPOST())
+  if ((response = cupsDoRequest(http, request, "/")) != NULL)
   {
    /*
-    * Save hand-edited config file...
+    * Got the printer list, now load the devices...
     */
 
-    http_status_t status;              /* PUT status */
-    char       tempfile[1024];         /* Temporary new cupsd.conf */
-    int                tempfd;                 /* Temporary file descriptor */
-    cups_file_t        *temp;                  /* Temporary file */
-    const char *start,                 /* Start of line */
-               *end;                   /* End of line */
+    int                i;                      /* Looping var */
+    cups_array_t *printer_devices;     /* Printer devices for local printers */
+    char       *printer_device;        /* Current printer device */
 
 
    /*
-    * Create a temporary file for the new cupsd.conf file...
+    * Allocate an array and copy the device strings...
     */
 
-    if ((tempfd = cupsTempFd(tempfile, sizeof(tempfile))) < 0)
+    printer_devices = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+
+    for (attr = ippFindAttribute(response, "device-uri", IPP_TAG_URI);
+         attr;
+        attr = ippFindNextAttribute(response, "device-uri", IPP_TAG_URI))
     {
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to create temporary file:")));
-      cgiSetVariable("ERROR", strerror(errno));
-      cgiCopyTemplateLang("error.tmpl");
-      cgiEndHTML();
-      
-      perror(tempfile);
-      return;
-    }
-
-    if ((temp = cupsFileOpenFd(tempfd, "w")) == NULL)
-    {
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to create temporary file:")));
-      cgiSetVariable("ERROR", strerror(errno));
-      cgiCopyTemplateLang("error.tmpl");
-      cgiEndHTML();
-      
-      perror(tempfile);
-      close(tempfd);
-      unlink(tempfile);
-      return;
+      cupsArrayAdd(printer_devices, strdup(attr->values[0].string.text));
     }
 
    /*
-    * Copy the cupsd.conf text from the form variable...
+    * Free the printer list and get the device list...
     */
 
-    start = cgiGetVariable("CUPSDCONF");
-    while (start)
-    {
-      if ((end = strstr(start, "\r\n")) == NULL)
-        if ((end = strstr(start, "\n")) == NULL)
-         end = start + strlen(start);
+    ippDelete(response);
 
-      cupsFileWrite(temp, start, end - start);
-      cupsFilePutChar(temp, '\n');
+    request = ippNewRequest(CUPS_GET_DEVICES);
 
-      if (*end == '\r')
-        start = end + 2;
-      else if (*end == '\n')
-        start = end + 1;
-      else
-        start = NULL;
-    }
+    if ((response = cupsDoRequest(http, request, "/")) != NULL)
+    {
+     /*
+      * Got the device list, let's parse it...
+      */
 
-    cupsFileClose(temp);
+      const char *device_uri,          /* device-uri attribute value */
+               *device_make_and_model, /* device-make-and-model value */
+               *device_info;           /* device-info value */
 
-   /*
-    * Upload the configuration file to the server...
-    */
 
-    status = cupsPutFile(http, "/admin/conf/cupsd.conf", tempfile);
+      for (i = 0, attr = response->attrs; attr; attr = attr->next)
+      {
+       /*
+        * Skip leading attributes until we hit a device...
+       */
 
-    if (status != HTTP_CREATED)
-    {
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to upload cupsd.conf file:")));
-      cgiSetVariable("ERROR", httpStatus(status));
+       while (attr && attr->group_tag != IPP_TAG_PRINTER)
+          attr = attr->next;
 
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiCopyTemplateLang("error.tmpl");
-    }
-    else
-    {
-      cgiSetVariable("refresh_page", "5;/admin/?OP=redirect");
+       if (!attr)
+          break;
 
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiCopyTemplateLang("restart.tmpl");
-    }
+       /*
+       * Pull the needed attributes from this device...
+       */
 
-    cgiEndHTML();
+       device_info           = NULL;
+       device_make_and_model = NULL;
+       device_uri            = NULL;
 
-    unlink(tempfile);
-  }
-  else
-  {
-    struct stat        info;                   /* cupsd.conf information */
-    cups_file_t        *cupsd;                 /* cupsd.conf file */
-    char       *buffer;                /* Buffer for entire file */
-    char       filename[1024];         /* Filename */
-    const char *server_root;           /* Location of config files */
+       while (attr && attr->group_tag == IPP_TAG_PRINTER)
+       {
+          if (!strcmp(attr->name, "device-info") &&
+             attr->value_tag == IPP_TAG_TEXT)
+           device_info = attr->values[0].string.text;
 
+          if (!strcmp(attr->name, "device-make-and-model") &&
+             attr->value_tag == IPP_TAG_TEXT)
+           device_make_and_model = attr->values[0].string.text;
 
-   /*
-    * Locate the cupsd.conf file...
-    */
+          if (!strcmp(attr->name, "device-uri") &&
+             attr->value_tag == IPP_TAG_URI)
+           device_uri = attr->values[0].string.text;
 
-    if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
-      server_root = CUPS_SERVERROOT;
+          attr = attr->next;
+       }
 
-    snprintf(filename, sizeof(filename), "%s/cupsd.conf", server_root);
+       /*
+       * See if we have everything needed...
+       */
 
-   /*
-    * Figure out the size...
-    */
+       if (device_info && device_make_and_model && device_uri &&
+           _cups_strcasecmp(device_make_and_model, "unknown") &&
+           strchr(device_uri, ':'))
+       {
+        /*
+         * Yes, now see if there is already a printer for this
+         * device...
+         */
 
-    if (stat(filename, &info))
-    {
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to access cupsd.conf file:")));
-      cgiSetVariable("ERROR", strerror(errno));
-      cgiCopyTemplateLang("error.tmpl");
-      cgiEndHTML();
+          if (!cupsArrayFind(printer_devices, (void *)device_uri))
+          {
+          /*
+           * Not found, so it must be a new printer...
+           */
 
-      perror(filename);
-      return;
-    }
+            char       option[1024],   /* Form variables for this device */
+                       *option_ptr;    /* Pointer into string */
+           const char  *ptr;           /* Pointer into device string */
 
-    if (info.st_size > (1024 * 1024))
-    {
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to access cupsd.conf file:")));
-      cgiSetVariable("ERROR",
-                     cgiText(_("Unable to edit cupsd.conf files larger than "
-                              "1MB!")));
-      cgiCopyTemplateLang("error.tmpl");
-      cgiEndHTML();
 
-      fprintf(stderr, "ERROR: \"%s\" too large (%ld) to edit!\n", filename,
-              (long)info.st_size);
-      return;
-    }
+           /*
+           * Format the printer name variable for this device...
+           *
+           * We use the device-info string first, then device-uri,
+           * and finally device-make-and-model to come up with a
+           * suitable name.
+           */
 
-   /*
-    * Open the cupsd.conf file...
-    */
+            if (_cups_strncasecmp(device_info, "unknown", 7))
+             ptr = device_info;
+            else if ((ptr = strstr(device_uri, "://")) != NULL)
+             ptr += 3;
+           else
+             ptr = device_make_and_model;
 
-    if ((cupsd = cupsFileOpen(filename, "r")) == NULL)
-    {
-     /*
-      * Unable to open - log an error...
-      */
+           for (option_ptr = option;
+                option_ptr < (option + sizeof(option) - 1) && *ptr;
+                ptr ++)
+             if (isalnum(*ptr & 255) || *ptr == '_' || *ptr == '-' ||
+                 *ptr == '.')
+               *option_ptr++ = *ptr;
+             else if ((*ptr == ' ' || *ptr == '/') && option_ptr > option &&
+                      option_ptr[-1] != '_')
+               *option_ptr++ = '_';
+             else if (*ptr == '?' || *ptr == '(')
+               break;
 
-      cgiStartHTML(cgiText(_("Edit Configuration File")));
-      cgiSetVariable("MESSAGE", cgiText(_("Unable to access cupsd.conf file:")));
-      cgiSetVariable("ERROR", strerror(errno));
-      cgiCopyTemplateLang("error.tmpl");
-      cgiEndHTML();
+            *option_ptr = '\0';
 
-      perror(filename);
-      return;
-    }
+            cgiSetArray("TEMPLATE_NAME", i, option);
 
-   /*
-    * Allocate memory and load the file into a string buffer...
-    */
+           /*
+           * Finally, set the form variables for this printer...
+           */
 
-    buffer = calloc(1, info.st_size + 1);
+           cgiSetArray("device_info", i, device_info);
+           cgiSetArray("device_make_and_model", i, device_make_and_model);
+            cgiSetArray("device_uri", i, device_uri);
+           i ++;
+         }
+       }
 
-    cupsFileRead(cupsd, buffer, info.st_size);
-    cupsFileClose(cupsd);
+        if (!attr)
+         break;
+      }
 
-    cgiSetVariable("CUPSDCONF", buffer);
-    free(buffer);
+      ippDelete(response);
 
-   /*
-    * Show the current config file...
-    */
+     /*
+      * Free the device list...
+      */
 
-    cgiStartHTML("Edit Configuration File");
+      for (printer_device = (char *)cupsArrayFirst(printer_devices);
+           printer_device;
+          printer_device = (char *)cupsArrayNext(printer_devices))
+        free(printer_device);
 
-    printf("<!-- \"%s\" -->\n", filename);
+      cupsArrayDelete(printer_devices);
+    }
+  }
 
-    cgiCopyTemplateLang("edit-config.tmpl");
+ /*
+  * Finally, show the printer list...
+  */
 
-    cgiEndHTML();
-  }
+  cgiCopyTemplateLang("list-available-printers.tmpl");
+
+  cgiEndHTML();
 }
 
 
 /*
- * 'do_delete_class()' - Delete a class...
+ * 'do_menu()' - Show the main menu.
  */
 
 static void
-do_delete_class(http_t *http)          /* I - HTTP connection */
+do_menu(http_t *http)                  /* I - HTTP connection */
 {
-  ipp_t                *request;               /* IPP request */
-  char         uri[HTTP_MAX_URI];      /* Job URI */
-  const char   *pclass;                /* Printer class name */
+  int          num_settings;           /* Number of server settings */
+  cups_option_t        *settings;              /* Server settings */
+  const char   *val;                   /* Setting value */
 
 
-  cgiStartHTML(cgiText(_("Delete Class")));
+ /*
+  * Get the current server settings...
+  */
 
-  if (cgiGetVariable("CONFIRM") == NULL)
+  if (!cupsAdminGetServerSettings(http, &num_settings, &settings))
   {
-    cgiCopyTemplateLang("class-confirm.tmpl");
-    cgiEndHTML();
-    return;
+    cgiSetVariable("SETTINGS_MESSAGE",
+                   cgiText(_("Unable to open cupsd.conf file:")));
+    cgiSetVariable("SETTINGS_ERROR", cupsLastErrorString());
   }
 
-  if ((pclass = cgiGetVariable("PRINTER_NAME")) != NULL)
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/classes/%s", pclass);
+  if ((val = cupsGetOption(CUPS_SERVER_DEBUG_LOGGING, num_settings,
+                           settings)) != NULL && atoi(val))
+    cgiSetVariable("DEBUG_LOGGING", "CHECKED");
+
+  if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ADMIN, num_settings,
+                           settings)) != NULL && atoi(val))
+    cgiSetVariable("REMOTE_ADMIN", "CHECKED");
+
+  if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ANY, num_settings,
+                           settings)) != NULL && atoi(val))
+    cgiSetVariable("REMOTE_ANY", "CHECKED");
+
+  if ((val = cupsGetOption(CUPS_SERVER_SHARE_PRINTERS, num_settings,
+                           settings)) != NULL && atoi(val))
+    cgiSetVariable("SHARE_PRINTERS", "CHECKED");
+
+  if ((val = cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY, num_settings,
+                           settings)) != NULL && atoi(val))
+    cgiSetVariable("USER_CANCEL_ANY", "CHECKED");
+
+#ifdef HAVE_GSSAPI
+  cgiSetVariable("HAVE_GSSAPI", "1");
+
+  if ((val = cupsGetOption("DefaultAuthType", num_settings,
+                           settings)) != NULL && !_cups_strcasecmp(val, "Negotiate"))
+    cgiSetVariable("KERBEROS", "CHECKED");
   else
+#endif /* HAVE_GSSAPI */
+  cgiSetVariable("KERBEROS", "");
+
+  if ((val = cupsGetOption("BrowseWebIF", num_settings,
+                           settings)) == NULL)
+    val = "No";
+
+  if (!_cups_strcasecmp(val, "yes") || !_cups_strcasecmp(val, "on") ||
+      !_cups_strcasecmp(val, "true"))
+    cgiSetVariable("BROWSE_WEB_IF", "CHECKED");
+
+  if ((val = cupsGetOption("PreserveJobHistory", num_settings,
+                           settings)) == NULL)
+    val = "Yes";
+
+  if (val &&
+      (!_cups_strcasecmp(val, "0") || !_cups_strcasecmp(val, "no") ||
+       !_cups_strcasecmp(val, "off") || !_cups_strcasecmp(val, "false") ||
+       !_cups_strcasecmp(val, "disabled")))
   {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
-    cgiCopyTemplateLang("error.tmpl");
-    cgiEndHTML();
-    return;
+    cgiSetVariable("PRESERVE_JOB_HISTORY", "0");
+    cgiSetVariable("PRESERVE_JOB_FILES", "0");
   }
+  else
+  {
+    cgiSetVariable("PRESERVE_JOBS", "CHECKED");
+    cgiSetVariable("PRESERVE_JOB_HISTORY", val);
 
- /*
-  * Build a CUPS_DELETE_CLASS request, which requires the following
-  * attributes:
-  *
-  *    attributes-charset
-  *    attributes-natural-language
-  *    printer-uri
-  */
+    if ((val = cupsGetOption("PreserveJobFiles", num_settings,
+                            settings)) == NULL)
+      val = "1d";
 
-  request = ippNewRequest(CUPS_DELETE_CLASS);
+    cgiSetVariable("PRESERVE_JOB_FILES", val);
 
-  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-               NULL, uri);
+  }
+
+  if ((val = cupsGetOption("MaxClients", num_settings, settings)) == NULL)
+    val = "100";
+
+  cgiSetVariable("MAX_CLIENTS", val);
+
+  if ((val = cupsGetOption("MaxJobs", num_settings, settings)) == NULL)
+    val = "500";
+
+  cgiSetVariable("MAX_JOBS", val);
+
+  if ((val = cupsGetOption("MaxLogSize", num_settings, settings)) == NULL)
+    val = "1m";
+
+  cgiSetVariable("MAX_LOG_SIZE", val);
+
+  cupsFreeOptions(num_settings, settings);
 
  /*
-  * Do the request and get back a response...
+  * Finally, show the main menu template...
   */
 
-  ippDelete(cupsDoRequest(http, request, "/admin/"));
+  cgiStartHTML(cgiText(_("Administration")));
 
-  if (cupsLastError() > IPP_OK_CONFLICT)
-    cgiShowIPPError(_("Unable to delete class:"));
-  else
-    cgiCopyTemplateLang("class-deleted.tmpl");
+  cgiCopyTemplateLang("admin.tmpl");
 
   cgiEndHTML();
 }
 
 
 /*
- * 'do_delete_printer()' - Delete a printer...
+ * 'do_set_allowed_users()' - Set the allowed/denied users for a queue.
  */
 
 static void
-do_delete_printer(http_t *http)                /* I - HTTP connection */
+do_set_allowed_users(http_t *http)     /* I - HTTP connection */
 {
-  ipp_t                *request;               /* IPP request */
-  char         uri[HTTP_MAX_URI];      /* Job URI */
-  const char   *printer;               /* Printer printer name */
-
+  int          i;                      /* Looping var */
+  ipp_t                *request,               /* IPP request */
+               *response;              /* IPP response */
+  char         uri[HTTP_MAX_URI];      /* Printer URI */
+  const char   *printer,               /* Printer name (purge-jobs) */
+               *is_class,              /* Is a class? */
+               *users,                 /* List of users or groups */
+               *type;                  /* Allow/deny type */
+  int          num_users;              /* Number of users */
+  char         *ptr,                   /* Pointer into users string */
+               *end,                   /* Pointer to end of users string */
+               quote;                  /* Quote character */
+  ipp_attribute_t *attr;               /* Attribute */
+  static const char * const attrs[] =  /* Requested attributes */
+               {
+                 "requesting-user-name-allowed",
+                 "requesting-user-name-denied"
+               };
 
-  cgiStartHTML(cgiText(_("Delete Printer")));
 
-  if (cgiGetVariable("CONFIRM") == NULL)
-  {
-    cgiCopyTemplateLang("printer-confirm.tmpl");
-    cgiEndHTML();
-    return;
-  }
+  is_class = cgiGetVariable("IS_CLASS");
+  printer  = cgiGetVariable("PRINTER_NAME");
 
-  if ((printer = cgiGetVariable("PRINTER_NAME")) != NULL)
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     "/printers/%s", printer);
-  else
+  if (!printer)
   {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
+    cgiStartHTML(cgiText(_("Set Allowed Users")));
     cgiCopyTemplateLang("error.tmpl");
     cgiEndHTML();
     return;
   }
 
- /*
-  * Build a CUPS_DELETE_PRINTER request, which requires the following
-  * attributes:
-  *
-  *    attributes-charset
-  *    attributes-natural-language
-  *    printer-uri
-  */
-
-  request = ippNewRequest(CUPS_DELETE_PRINTER);
-
-  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-               NULL, uri);
-
- /*
-  * Do the request and get back a response...
-  */
-
-  ippDelete(cupsDoRequest(http, request, "/admin/"));
-
-  if (cupsLastError() > IPP_OK_CONFLICT)
-    cgiShowIPPError(_("Unable to delete printer:"));
-  else
-    cgiCopyTemplateLang("printer-deleted.tmpl");
-
-  cgiEndHTML();
-}
-
-
-/*
- * 'do_export()' - Export printers to Samba...
- */
-
-static void
-do_export(http_t *http)                        /* I - HTTP connection */
-{
-  int          i, j;                   /* Looping vars */
-  ipp_t                *request,               /* IPP request */
-               *response;              /* IPP response */
-  const char   *username,              /* Samba username */
-               *password,              /* Samba password */
-               *export_all;            /* Export all printers? */
-  int          export_count,           /* Number of printers to export */
-               printer_count;          /* Number of available printers */
-
-
- /*
-  * Show header...
-  */
-
-  cgiStartHTML(cgiText(_("Export Printers to Samba")));
-
- /*
-  * Get form data...
-  */
-
-  username     = cgiGetVariable("USERNAME");
-  password     = cgiGetVariable("PASSWORD");
-  export_all   = cgiGetVariable("EXPORT_ALL");
-  export_count = cgiGetSize("EXPORT_NAME");
+  users = cgiGetVariable("users");
+  type  = cgiGetVariable("type");
 
-  if (username && *username && password && *password && export_count <= 1000)
+  if (!users || !type ||
+      (strcmp(type, "requesting-user-name-allowed") &&
+       strcmp(type, "requesting-user-name-denied")))
   {
    /*
-    * Do export...
+    * Build a Get-Printer-Attributes request, which requires the following
+    * attributes:
+    *
+    *    attributes-charset
+    *    attributes-natural-language
+    *    printer-uri
+    *    requested-attributes
     */
 
-    char       userpass[1024],         /* Username%password */
-               *argv[1005];            /* Arguments */
-    int                argc;                   /* Number of arguments */
-    int                pid;                    /* Process ID of child */
-    int                status;                 /* Status of command */
+    request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
 
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, is_class ? "/classes/%s" : "/printers/%s",
+                    printer);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+                NULL, uri);
 
-    fputs("DEBUG: Export printers...\n", stderr);
+    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+                  "requested-attributes",
+                 (int)(sizeof(attrs) / sizeof(attrs[0])), NULL, attrs);
 
    /*
-    * Create the command-line for cupsaddsmb...
+    * Do the request and get back a response...
     */
 
-    snprintf(userpass, sizeof(userpass), "%s%%%s", username, password);
+    if ((response = cupsDoRequest(http, request, "/")) != NULL)
+    {
+      cgiSetIPPVars(response, NULL, NULL, NULL, 0);
+
+      ippDelete(response);
+    }
 
-    argv[0] = "cupsaddsmb";
-    argv[1] = "-v";
-    argv[2] = "-U";
-    argv[3] = userpass;
-    argc    = 4;
+    cgiStartHTML(cgiText(_("Set Allowed Users")));
 
-    if (export_all)
-      argv[argc ++] = "-a";
-    else
+    if (cupsLastError() == IPP_NOT_AUTHORIZED)
     {
-      for (i = 0; i < export_count; i ++)
-        argv[argc ++] = (char *)cgiGetArray("EXPORT_NAME", i);
+      puts("Status: 401\n");
+      exit(0);
     }
+    else if (cupsLastError() > IPP_OK_CONFLICT)
+      cgiShowIPPError(_("Unable to get printer attributes"));
+    else
+      cgiCopyTemplateLang("users.tmpl");
 
-    argv[argc] = NULL; 
-
+    cgiEndHTML();
+  }
+  else
+  {
    /*
-    * Run the command...
+    * Save the changes...
     */
 
-    if ((pid = fork()) == 0)
+    for (num_users = 0, ptr = (char *)users; *ptr; num_users ++)
     {
      /*
-      * Child goes here...
+      * Skip whitespace and commas...
       */
 
-      close(0);
-      open("/dev/null", O_RDONLY);
-      close(1);
-      dup(2);
-
-      execvp("cupsaddsmb", argv);
-      perror("ERROR: Unable to execute cupsaddsmb");
-      exit(20);
-    }
-    else if (pid < 0)
-      cgiSetVariable("ERROR", cgiText(_("Unable to fork process!")));
-    else
-    {
-     /*
-      * Parent goes here, wait for child to finish...
-      */
+      while (*ptr == ',' || isspace(*ptr & 255))
+       ptr ++;
 
-      while (wait(&status) < 0);
+      if (!*ptr)
+        break;
 
-      if (status)
+      if (*ptr == '\'' || *ptr == '\"')
       {
-        char   message[1024];          /* Error message */
-
+       /*
+       * Scan quoted name...
+       */
 
-       if (WIFEXITED(status))
-       {
-         switch (WEXITSTATUS(status))
-         {
-           case 1 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to connect to server!")));
-                break;
-
-           case 2 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to get printer "
-                                                 "attributes!")));
-                break;
-
-           case 3 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to convert PPD file!")));
-                break;
-
-           case 4 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to copy Windows 2000 "
-                                                 "printer driver files!")));
-                break;
-
-           case 5 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to install Windows "
-                                                 "2000 printer driver files!")));
-                break;
-
-           case 6 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to copy Windows 9x "
-                                                 "printer driver files!")));
-                break;
-
-           case 7 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to install Windows "
-                                                 "9x printer driver files!")));
-                break;
-
-           case 8 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to set Windows "
-                                                 "printer driver!")));
-                break;
-
-           case 9 :
-               cgiSetVariable("ERROR", cgiText(_("No printer drivers found!")));
-                break;
-
-           case 20 :
-               cgiSetVariable("ERROR", cgiText(_("Unable to execute "
-                                                 "cupsaddsmb command!")));
-                break;
-
-           default :
-               snprintf(message, sizeof(message),
-                         cgiText(_("cupsaddsmb failed with status %d")),
-                        WEXITSTATUS(status));
-
-                cgiSetVariable("ERROR", message);
-               break;
-          }
-       }
-        else
-       {
-          snprintf(message, sizeof(message),
-                   cgiText(_("cupsaddsmb crashed on signal %d")),
-                  WTERMSIG(status));
+       quote = *ptr++;
 
-          cgiSetVariable("ERROR", message);
-       }
+       for (end = ptr; *end; end ++)
+         if (*end == quote)
+           break;
       }
       else
       {
-        cgiCopyTemplateLang("samba-exported");
-       cgiEndHTML();
-       return;
-      }
-    }
-  }
-  else if (username && !*username)
-    cgiSetVariable("ERROR",
-                   cgiText(_("A Samba username is required to export "
-                            "printer drivers!")));
-  else if (username && (!password || !*password))
-    cgiSetVariable("ERROR",
-                   cgiText(_("A Samba password is required to export "
-                            "printer drivers!")));
-
- /*
-  * Get list of available printers...
-  */
+       /*
+       * Scan space or comma-delimited name...
+       */
 
-  cgiSetSize("PRINTER_NAME", 0);
-  cgiSetSize("PRINTER_EXPORT", 0);
+        for (end = ptr; *end; end ++)
+         if (isspace(*end & 255) || *end == ',')
+           break;
+      }
 
-  request = ippNewRequest(CUPS_GET_PRINTERS);
+     /*
+      * Advance to the next name...
+      */
 
-  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM,
-                "printer-type", 0);
+      ptr = end;
+    }
 
-  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM,
-                "printer-type-mask", CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE |
-                                    CUPS_PRINTER_IMPLICIT);
+   /*
+    * Build a CUPS-Add-Printer/Class request, which requires the following
+    * attributes:
+    *
+    *    attributes-charset
+    *    attributes-natural-language
+    *    printer-uri
+    *    requesting-user-name-{allowed,denied}
+    */
 
-  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
-               "requested-attributes", NULL, "printer-name");
+    request = ippNewRequest(is_class ? CUPS_ADD_CLASS : CUPS_ADD_PRINTER);
 
-  if ((response = cupsDoRequest(http, request, "/")) != NULL)
-  {
-    cgiSetIPPVars(response, NULL, NULL, NULL, 0);
-    ippDelete(response);
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, is_class ? "/classes/%s" : "/printers/%s",
+                    printer);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+                NULL, uri);
 
-    if (!export_all)
+    if (num_users == 0)
+      ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                   "requesting-user-name-allowed", NULL, "all");
+    else
     {
-      printer_count = cgiGetSize("PRINTER_NAME");
+      attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                           type, num_users, NULL, NULL);
 
-      for (i = 0; i < printer_count; i ++)
+      for (i = 0, ptr = (char *)users; *ptr; i ++)
       {
-        for (j = 0; j < export_count; j ++)
-         if (!strcasecmp(cgiGetArray("PRINTER_NAME", i),
-                         cgiGetArray("EXPORT_NAME", j)))
-            break;
-
-        cgiSetArray("PRINTER_EXPORT", i, j < export_count ? "Y" : "");
-      }
-    }
-  }
-
- /*
-  * Show form...
-  */
-
-  cgiCopyTemplateLang("samba-export.tmpl");
-  cgiEndHTML();
-}
+       /*
+        * Skip whitespace and commas...
+       */
 
+        while (*ptr == ',' || isspace(*ptr & 255))
+         ptr ++;
 
-/*
- * 'do_menu()' - Show the main menu...
- */
+        if (!*ptr)
+         break;
 
-static void
-do_menu(http_t *http)                  /* I - HTTP connection */
-{
-  cups_file_t  *cupsd;                 /* cupsd.conf file */
-  char         line[1024],             /* Line from cupsd.conf file */
-               *value;                 /* Value on line */
-  const char   *server_root;           /* Location of config files */
-  const char   *datadir;               /* Location of data files */
-  ipp_t                *request,               /* IPP request */
-               *response;              /* IPP response */
-  ipp_attribute_t *attr;               /* IPP attribute */
+        if (*ptr == '\'' || *ptr == '\"')
+       {
+        /*
+         * Scan quoted name...
+         */
 
+         quote = *ptr++;
 
- /*
-  * Locate the cupsd.conf file...
-  */
+         for (end = ptr; *end; end ++)
+           if (*end == quote)
+             break;
+       }
+       else
+       {
+        /*
+         * Scan space or comma-delimited name...
+         */
 
-  if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
-    server_root = CUPS_SERVERROOT;
+          for (end = ptr; *end; end ++)
+           if (isspace(*end & 255) || *end == ',')
+             break;
+        }
 
-  snprintf(line, sizeof(line), "%s/cupsd.conf", server_root);
+       /*
+        * Terminate the name...
+       */
 
-  cgiStartHTML(cgiText(_("Administration")));
+        if (*end)
+          *end++ = '\0';
 
-  printf("<!-- \"%s\" -->\n", line);
+       /*
+        * Add the name...
+       */
 
- /*
-  * Open the cupsd.conf file...
-  */
+        ippSetString(request, &attr, i, ptr);
 
-  if ((cupsd = cupsFileOpen(line, "r")) == NULL)
-  {
-   /*
-    * Unable to open - log an error...
-    */
+       /*
+        * Advance to the next name...
+       */
 
-    cgiSetVariable("MESSAGE", cgiText(_("Unable to open cupsd.conf file:")));
-    cgiSetVariable("ERROR", strerror(errno));
-    cgiCopyTemplateLang("error.tmpl");
-    cgiEndHTML();
+        ptr = end;
+      }
+    }
 
-    perror(line);
-  }
-  else
-  {
    /*
-    * Read the file, keeping track of what settings are enabled...
+    * Do the request and get back a response...
     */
 
-    int                remote_access = 0,      /* Remote access allowed? */
-               remote_admin = 0,       /* Remote administration allowed? */
-               browsing = 1,           /* Browsing enabled? */
-               browse_allow = 1,       /* Browse address set? */
-               browse_address = 0,     /* Browse address set? */
-               cancel_policy = 1,      /* Cancel-job policy set? */
-               debug_logging = 0;      /* LogLevel debug set? */
-    int                linenum = 0,            /* Line number in file */
-               in_policy = 0,          /* In a policy section? */
-               in_cancel_job = 0,      /* In a cancel-job section? */
-               in_admin_location = 0;  /* In the /admin location? */
+    ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-
-    while (cupsFileGetConf(cupsd, line, sizeof(line), &value, &linenum))
+    if (cupsLastError() == IPP_NOT_AUTHORIZED)
     {
-      if (!strcasecmp(line, "Port"))
-      {
-        remote_access = 1;
-      }
-      else if (!strcasecmp(line, "Listen"))
-      {
-        char   *port;                  /* Pointer to port number, if any */
-
-
-       if ((port = strrchr(value, ':')) != NULL)
-         *port = '\0';
-
-        if (strcasecmp(value, "localhost") && strcmp(value, "127.0.0.1"))
-         remote_access = 1;
-      }
-      else if (!strcasecmp(line, "Browsing"))
-      {
-        browsing = !strcasecmp(value, "yes") || !strcasecmp(value, "on") ||
-                  !strcasecmp(value, "true");
-      }
-      else if (!strcasecmp(line, "BrowseAddress"))
-      {
-        browse_address = 1;
-      }
-      else if (!strcasecmp(line, "BrowseAllow"))
-      {
-        browse_allow = 1;
-      }
-      else if (!strcasecmp(line, "BrowseOrder"))
-      {
-        browse_allow = !strncasecmp(value, "deny,", 5);
-      }
-      else if (!strcasecmp(line, "LogLevel"))
-      {
-        debug_logging = !strncasecmp(value, "debug", 5);
-      }
-      else if (!strcasecmp(line, "<Policy") && !strcasecmp(value, "default"))
-      {
-        in_policy = 1;
-      }
-      else if (!strcasecmp(line, "</Policy>"))
-      {
-        in_policy = 0;
-      }
-      else if (!strcasecmp(line, "<Limit") && in_policy)
-      {
-       /*
-        * See if the policy limit is for the Cancel-Job operation...
-       */
-
-        char   *valptr;                /* Pointer into value */
-
-
-        while (*value)
-       {
-         for (valptr = value; !isspace(*valptr & 255) && *valptr; valptr ++);
-
-         if (*valptr)
-           *valptr++ = '\0';
-
-          if (!strcasecmp(value, "cancel-job") || !strcasecmp(value, "all"))
-         {
-           in_cancel_job = 1;
-           break;
-         }
-
-          for (value = valptr; isspace(*value & 255); value ++);
-        }
-      }
-      else if (!strcasecmp(line, "</Limit>"))
-      {
-        in_cancel_job = 0;
-      }
-      else if (!strcasecmp(line, "Require") && in_cancel_job)
-      {
-        cancel_policy = 0;
-      }
-      else if (!strcasecmp(line, "<Location") && !strcasecmp(value, "/admin"))
-      {
-        in_admin_location = 1;
-      }
-      else if (!strcasecmp(line, "</Location>"))
-      {
-        in_admin_location = 0;
-      }
-      else if (!strcasecmp(line, "Allow") && in_admin_location &&
-               strcasecmp(value, "localhost") && strcasecmp(value, "127.0.0.1"))
-      {
-        remote_admin = 1;
-      }
+      puts("Status: 401\n");
+      exit(0);
     }
-
-    cupsFileClose(cupsd);
-
-    if (browsing && browse_allow)
-      cgiSetVariable("REMOTE_PRINTERS", "CHECKED");
-
-    if (remote_access && browsing && browse_address)
-      cgiSetVariable("SHARE_PRINTERS", "CHECKED");
-
-    if (remote_access && remote_admin)
-      cgiSetVariable("REMOTE_ADMIN", "CHECKED");
-
-    if (cancel_policy)
-      cgiSetVariable("USER_CANCEL_ANY", "CHECKED");
-
-    if (debug_logging)
-      cgiSetVariable("DEBUG_LOGGING", "CHECKED");
-  }
-
- /*
-  * Get the list of printers and their devices...
-  */
-
-  request = ippNewRequest(CUPS_GET_PRINTERS);
-
-  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
-               "requested-attributes", NULL, "device-uri");
-
-  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type",
-                CUPS_PRINTER_LOCAL);
-  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask",
-                CUPS_PRINTER_LOCAL);
-
-  if ((response = cupsDoRequest(http, request, "/")) != NULL)
-  {
-   /*
-    * Got the printer list, now load the devices...
-    */
-
-    int                i;                      /* Looping var */
-    cups_array_t *printer_devices;     /* Printer devices for local printers */
-    char       *printer_device;        /* Current printer device */
-
-
-   /*
-    * Allocate an array and copy the device strings...
-    */
-
-    printer_devices = cupsArrayNew((cups_array_func_t)strcmp, NULL);
-
-    for (attr = ippFindAttribute(response, "device-uri", IPP_TAG_URI);
-         attr;
-        attr = ippFindNextAttribute(response, "device-uri", IPP_TAG_URI))
+    else if (cupsLastError() > IPP_OK_CONFLICT)
     {
-      cupsArrayAdd(printer_devices, strdup(attr->values[0].string.text));
+      cgiStartHTML(cgiText(_("Set Allowed Users")));
+      cgiShowIPPError(_("Unable to change printer"));
     }
-
-   /*
-    * Free the printer list and get the device list...
-    */
-
-    ippDelete(response);
-
-    request = ippNewRequest(CUPS_GET_DEVICES);
-
-    if ((response = cupsDoRequest(http, request, "/")) != NULL)
+    else
     {
      /*
-      * Got the device list, let's parse it...
+      * Redirect successful updates back to the printer page...
       */
 
-      const char *device_uri,          /* device-uri attribute value */
-               *device_make_and_model, /* device-make-and-model value */
-               *device_info;           /* device-info value */
-
-
-      for (i = 0, attr = response->attrs; attr; attr = attr->next)
-      {
-       /*
-        * Skip leading attributes until we hit a device...
-       */
-
-       while (attr && attr->group_tag != IPP_TAG_PRINTER)
-          attr = attr->next;
-
-       if (!attr)
-          break;
-
-       /*
-       * Pull the needed attributes from this device...
-       */
-
-       device_info           = NULL;
-       device_make_and_model = NULL;
-       device_uri            = NULL;
-
-       while (attr && attr->group_tag == IPP_TAG_PRINTER)
-       {
-          if (!strcmp(attr->name, "device-info") &&
-             attr->value_tag == IPP_TAG_TEXT)
-           device_info = attr->values[0].string.text;
-
-          if (!strcmp(attr->name, "device-make-and-model") &&
-             attr->value_tag == IPP_TAG_TEXT)
-           device_make_and_model = attr->values[0].string.text;
-
-          if (!strcmp(attr->name, "device-uri") &&
-             attr->value_tag == IPP_TAG_URI)
-           device_uri = attr->values[0].string.text;
-
-          attr = attr->next;
-       }
-
-       /*
-       * See if we have everything needed...
-       */
-
-       if (device_info && device_make_and_model && device_uri &&
-           strcasecmp(device_make_and_model, "unknown") &&
-           strchr(device_uri, ':'))
-       {
-        /*
-         * Yes, now see if there is already a printer for this
-         * device...
-         */
-
-          if (!cupsArrayFind(printer_devices, (void *)device_uri))
-          {
-          /*
-           * Not found, so it must be a new printer...
-           */
-
-            char       options[1024],  /* Form variables for this device */
-                       *options_ptr;   /* Pointer into string */
-           const char  *ptr;           /* Pointer into device string */
-
-
-           /*
-           * Format the printer name variable for this device...
-           *
-           * We use the device-info string first, then device-uri,
-           * and finally device-make-and-model to come up with a
-           * suitable name.
-           */
-
-           strcpy(options, "PRINTER_NAME=");
-           options_ptr = options + strlen(options);
-
-            if (strncasecmp(device_info, "unknown", 7))
-             ptr = device_info;
-            else if ((ptr = strstr(device_uri, "://")) != NULL)
-             ptr += 3;
-           else
-             ptr = device_make_and_model;
-
-           for (;
-                options_ptr < (options + sizeof(options) - 1) && *ptr;
-                ptr ++)
-             if (isalnum(*ptr & 255) || *ptr == '_' || *ptr == '-' || *ptr == '.')
-               *options_ptr++ = *ptr;
-             else if ((*ptr == ' ' || *ptr == '/') && options_ptr[-1] != '_')
-               *options_ptr++ = '_';
-             else if (*ptr == '?' || *ptr == '(')
-               break;
-
-           /*
-           * Then add the make and model in the printer info, so
-           * that MacOS clients see something reasonable...
-           */
-
-            strlcpy(options_ptr, "&PRINTER_LOCATION=Local+Printer"
-                                "&PRINTER_INFO=",
-                   sizeof(options) - (options_ptr - options));
-           options_ptr += strlen(options_ptr);
-
-            cgiFormEncode(options_ptr, device_make_and_model,
-                         sizeof(options) - (options_ptr - options));
-           options_ptr += strlen(options_ptr);
-
-           /*
-           * Then copy the device URI...
-           */
-
-           strlcpy(options_ptr, "&DEVICE_URI=",
-                   sizeof(options) - (options_ptr - options));
-           options_ptr += strlen(options_ptr);
-
-            cgiFormEncode(options_ptr, device_uri,
-                         sizeof(options) - (options_ptr - options));
-           options_ptr += strlen(options_ptr);
-
-            if (options_ptr < (options + sizeof(options) - 1))
-           {
-             *options_ptr++ = '|';
-             cgiFormEncode(options_ptr, device_make_and_model,
-                         sizeof(options) - (options_ptr - options));
-           }
-
-           /*
-           * Finally, set the form variables for this printer...
-           */
-
-           cgiSetArray("device_info", i, device_info);
-           cgiSetArray("device_make_and_model", i, device_make_and_model);
-           cgiSetArray("device_options", i, options);
-            cgiSetArray("device_uri", i, device_uri);
-           i ++;
-         }
-       }
-
-        if (!attr)
-         break;
-      }
+      char     url[1024],              /* Printer/class URL */
+               refresh[1024];          /* Refresh URL */
 
-      ippDelete(response);
 
-     /*
-      * Free the device list...
-      */
+      cgiRewriteURL(uri, url, sizeof(url), NULL);
+      cgiFormEncode(uri, url, sizeof(uri));
+      snprintf(refresh, sizeof(refresh), "5;URL=/admin/?OP=redirect&URL=%s",
+               uri);
+      cgiSetVariable("refresh_page", refresh);
 
-      for (printer_device = (char *)cupsArrayFirst(printer_devices);
-           printer_device;
-          printer_device = (char *)cupsArrayNext(printer_devices))
-        free(printer_device);
+      cgiStartHTML(cgiText(_("Set Allowed Users")));
 
-      cupsArrayDelete(printer_devices);
+      cgiCopyTemplateLang(is_class ? "class-modified.tmpl" :
+                                     "printer-modified.tmpl");
     }
-  }
-
- /*
-  * See if Samba and the Windows drivers are installed...
-  */
-
-  if ((datadir = getenv("CUPS_DATADIR")) == NULL)
-    datadir = CUPS_DATADIR;
-
-  snprintf(line, sizeof(line), "%s/drivers/pscript5.dll", datadir);
-  if (!access(line, 0))
-  {
-   /*
-    * Found Windows 2000 driver file, see if we have smbclient and
-    * rpcclient...
-    */
-
-    if (cupsFileFind("smbclient", getenv("PATH"), line, sizeof(line)) &&
-        cupsFileFind("rpcclient", getenv("PATH"), line, sizeof(line)))
-      cgiSetVariable("HAVE_SAMBA", "Y");
-    else
-    {
-      if (!cupsFileFind("smbclient", getenv("PATH"), line, sizeof(line)))
-        fputs("ERROR: smbclient not found!\n", stderr);
 
-      if (!cupsFileFind("rpcclient", getenv("PATH"), line, sizeof(line)))
-        fputs("ERROR: rpcclient not found!\n", stderr);
-    }
+    cgiEndHTML();
   }
-  else
-    perror(line);
-
- /*
-  * Finally, show the main menu template...
-  */
-
-  cgiCopyTemplateLang("admin.tmpl");
-
-  cgiEndHTML();
 }
 
 
 /*
- * 'do_printer_op()' - Do a printer operation.
+ * 'do_set_default()' - Set the server default printer/class.
  */
 
 static void
-do_printer_op(http_t      *http,       /* I - HTTP connection */
-             ipp_op_t    op,           /* I - Operation to perform */
-             const char  *title)       /* I - Title of page */
+do_set_default(http_t *http)           /* I - HTTP connection */
 {
+  const char   *title;                 /* Page title */
   ipp_t                *request;               /* IPP request */
   char         uri[HTTP_MAX_URI];      /* Printer URI */
   const char   *printer,               /* Printer name (purge-jobs) */
@@ -3106,10 +2536,11 @@ do_printer_op(http_t      *http,        /* I - HTTP connection */
 
   is_class = cgiGetVariable("IS_CLASS");
   printer  = cgiGetVariable("PRINTER_NAME");
+  title    = cgiText(_("Set As Server Default"));
 
   if (!printer)
   {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
     cgiStartHTML(title);
     cgiCopyTemplateLang("error.tmpl");
     cgiEndHTML();
@@ -3125,10 +2556,11 @@ do_printer_op(http_t      *http,        /* I - HTTP connection */
   *    printer-uri
   */
 
-  request = ippNewRequest(op);
+  request = ippNewRequest(CUPS_SET_DEFAULT);
 
-  httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                   is_class ? "/classes/%s" : "/printers/%s", printer);
+  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                   "localhost", 0, is_class ? "/classes/%s" : "/printers/%s",
+                  printer);
   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                NULL, uri);
 
@@ -3138,10 +2570,15 @@ do_printer_op(http_t      *http,        /* I - HTTP connection */
 
   ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-  if (cupsLastError() > IPP_OK_CONFLICT)
+  if (cupsLastError() == IPP_NOT_AUTHORIZED)
+  {
+    puts("Status: 401\n");
+    exit(0);
+  }
+  else if (cupsLastError() > IPP_OK_CONFLICT)
   {
     cgiStartHTML(title);
-    cgiShowIPPError(_("Unable to change printer:"));
+    cgiShowIPPError(_("Unable to set server default"));
   }
   else
   {
@@ -3155,23 +2592,11 @@ do_printer_op(http_t      *http,        /* I - HTTP connection */
 
     cgiRewriteURL(uri, url, sizeof(url), NULL);
     cgiFormEncode(uri, url, sizeof(uri));
-    snprintf(refresh, sizeof(refresh), "5;/admin/?OP=redirect&URL=%s", uri);
+    snprintf(refresh, sizeof(refresh), "5;URL=/admin/?OP=redirect&URL=%s", uri);
     cgiSetVariable("refresh_page", refresh);
 
     cgiStartHTML(title);
-
-    if (op == IPP_PAUSE_PRINTER)
-      cgiCopyTemplateLang("printer-stop.tmpl");
-    else if (op == IPP_RESUME_PRINTER)
-      cgiCopyTemplateLang("printer-start.tmpl");
-    else if (op == CUPS_ACCEPT_JOBS)
-      cgiCopyTemplateLang("printer-accept.tmpl");
-    else if (op == CUPS_REJECT_JOBS)
-      cgiCopyTemplateLang("printer-reject.tmpl");
-    else if (op == IPP_PURGE_JOBS)
-      cgiCopyTemplateLang("printer-purge.tmpl");
-    else if (op == CUPS_SET_DEFAULT)
-      cgiCopyTemplateLang("printer-default.tmpl");
+    cgiCopyTemplateLang("printer-default.tmpl");
   }
 
   cgiEndHTML();
@@ -3179,225 +2604,714 @@ do_printer_op(http_t      *http,      /* I - HTTP connection */
 
 
 /*
- * 'do_set_allowed_users()' - Set the allowed/denied users for a queue.
+ * 'do_set_options()' - Configure the default options for a queue.
  */
 
 static void
-do_set_allowed_users(http_t *http)     /* I - HTTP connection */
+do_set_options(http_t *http,           /* I - HTTP connection */
+               int    is_class)                /* I - Set options for class? */
 {
-  int          i;                      /* Looping var */
+  int          i, j, k, m;             /* Looping vars */
+  int          have_options;           /* Have options? */
   ipp_t                *request,               /* IPP request */
                *response;              /* IPP response */
-  char         uri[HTTP_MAX_URI];      /* Printer URI */
-  const char   *printer,               /* Printer name (purge-jobs) */
-               *is_class,              /* Is a class? */
-               *users,                 /* List of users or groups */
-               *type;                  /* Allow/deny type */
-  int          num_users;              /* Number of users */
-  char         *ptr,                   /* Pointer into users string */
-               *end,                   /* Pointer to end of users string */
-               quote;                  /* Quote character */
-  ipp_attribute_t *attr;               /* Attribute */
-  static const char * const attrs[] =  /* Requested attributes */
-               {
-                 "requesting-user-name-allowed",
-                 "requesting-user-name-denied"
-               };
+  ipp_attribute_t *attr;               /* IPP attribute */
+  char         uri[HTTP_MAX_URI];      /* Job URI */
+  const char   *var;                   /* Variable value */
+  const char   *printer;               /* Printer printer name */
+  const char   *filename;              /* PPD filename */
+  char         tempfile[1024];         /* Temporary filename */
+  cups_file_t  *in,                    /* Input file */
+               *out;                   /* Output file */
+  char         line[1024],             /* Line from PPD file */
+               value[1024],            /* Option value */
+               keyword[1024],          /* Keyword from Default line */
+               *keyptr;                /* Pointer into keyword... */
+  ppd_file_t   *ppd;                   /* PPD file */
+  ppd_group_t  *group;                 /* Option group */
+  ppd_option_t *option;                /* Option */
+  ppd_coption_t        *coption;               /* Custom option */
+  ppd_cparam_t *cparam;                /* Custom parameter */
+  ppd_attr_t   *ppdattr;               /* PPD attribute */
+  const char   *title;                 /* Page title */
 
 
-  is_class = cgiGetVariable("IS_CLASS");
-  printer  = cgiGetVariable("PRINTER_NAME");
+  title = cgiText(is_class ? _("Set Class Options") : _("Set Printer Options"));
 
-  if (!printer)
+  fprintf(stderr, "DEBUG: do_set_options(http=%p, is_class=%d)\n", http,
+          is_class);
+
+ /*
+  * Get the printer name...
+  */
+
+  if ((printer = cgiGetVariable("PRINTER_NAME")) != NULL)
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, is_class ? "/classes/%s" : "/printers/%s",
+                    printer);
+  else
   {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
-    cgiStartHTML(cgiText(_("Set Allowed Users")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
+    cgiStartHTML(title);
     cgiCopyTemplateLang("error.tmpl");
     cgiEndHTML();
     return;
   }
 
-  users = cgiGetVariable("users");
-  type  = cgiGetVariable("type");
+  fprintf(stderr, "DEBUG: printer=\"%s\", uri=\"%s\"...\n", printer, uri);
 
-  if (!users || !type ||
-      (strcmp(type, "requesting-user-name-allowed") &&
-       strcmp(type, "requesting-user-name-denied")))
+ /*
+  * If the user clicks on the Auto-Configure button, send an AutoConfigure
+  * command file to the printer...
+  */
+
+  if (cgiGetVariable("AUTOCONFIGURE"))
+  {
+    cgiPrintCommand(http, printer, "AutoConfigure", "Set Default Options");
+    return;
+  }
+
+ /*
+  * Get the PPD file...
+  */
+
+  if (is_class)
+    filename = NULL;
+  else
+    filename = cupsGetPPD2(http, printer);
+
+  if (filename)
+  {
+    fprintf(stderr, "DEBUG: Got PPD file: \"%s\"\n", filename);
+
+    if ((ppd = ppdOpenFile(filename)) == NULL)
+    {
+      cgiSetVariable("ERROR", ppdErrorString(ppdLastError(&i)));
+      cgiSetVariable("MESSAGE", cgiText(_("Unable to open PPD file")));
+      cgiStartHTML(title);
+      cgiCopyTemplateLang("error.tmpl");
+      cgiEndHTML();
+      return;
+    }
+  }
+  else
+  {
+    fputs("DEBUG: No PPD file\n", stderr);
+    ppd = NULL;
+  }
+
+  if (cgiGetVariable("job_sheets_start") != NULL ||
+      cgiGetVariable("job_sheets_end") != NULL)
+    have_options = 1;
+  else
+    have_options = 0;
+
+  if (ppd)
+  {
+    ppdMarkDefaults(ppd);
+
+    for (option = ppdFirstOption(ppd);
+         option;
+        option = ppdNextOption(ppd))
+    {
+      if ((var = cgiGetVariable(option->keyword)) != NULL)
+      {
+       have_options = 1;
+       ppdMarkOption(ppd, option->keyword, var);
+       fprintf(stderr, "DEBUG: Set %s to %s...\n", option->keyword, var);
+      }
+      else
+        fprintf(stderr, "DEBUG: Didn't find %s...\n", option->keyword);
+    }
+  }
+
+  if (!have_options || ppdConflicts(ppd))
   {
    /*
-    * Build a Get-Printer-Attributes request, which requires the following
-    * attributes:
-    *
-    *    attributes-charset
-    *    attributes-natural-language
-    *    printer-uri
-    *    requested-attributes
+    * Show the options to the user...
+    */
+
+    fputs("DEBUG: Showing options...\n", stderr);
+
+   /*
+    * Show auto-configure button if supported...
+    */
+
+    if (ppd)
+    {
+      if (ppd->num_filters == 0 ||
+          ((ppdattr = ppdFindAttr(ppd, "cupsCommands", NULL)) != NULL &&
+           ppdattr->value && strstr(ppdattr->value, "AutoConfigure")))
+        cgiSetVariable("HAVE_AUTOCONFIGURE", "YES");
+      else
+      {
+        for (i = 0; i < ppd->num_filters; i ++)
+         if (!strncmp(ppd->filters[i], "application/vnd.cups-postscript", 31))
+         {
+           cgiSetVariable("HAVE_AUTOCONFIGURE", "YES");
+           break;
+         }
+      }
+    }
+
+   /*
+    * Get the printer attributes...
     */
 
     request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
 
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     is_class ? "/classes/%s" : "/printers/%s", printer);
+    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                     "localhost", 0, "/printers/%s", printer);
     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                NULL, uri);
+                 NULL, uri);
 
-    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
-                  "requested-attributes",
-                 (int)(sizeof(attrs) / sizeof(attrs[0])), NULL, attrs);
+    response = cupsDoRequest(http, request, "/");
 
    /*
-    * Do the request and get back a response...
+    * List the groups used as "tabs"...
     */
 
-    if ((response = cupsDoRequest(http, request, "/")) != NULL)
+    i = 0;
+
+    if (ppd)
     {
-      cgiSetIPPVars(response, NULL, NULL, NULL, 0);
+      for (group = ppd->groups;
+          i < ppd->num_groups;
+          i ++, group ++)
+      {
+        cgiSetArray("GROUP_ID", i, group->name);
 
-      ippDelete(response);
+       if (!strcmp(group->name, "InstallableOptions"))
+         cgiSetArray("GROUP", i, cgiText(_("Options Installed")));
+       else
+         cgiSetArray("GROUP", i, group->text);
+      }
     }
 
-    cgiStartHTML(cgiText(_("Set Allowed Users")));
+    if (ippFindAttribute(response, "job-sheets-supported", IPP_TAG_ZERO))
+    {
+      cgiSetArray("GROUP_ID", i, "CUPS_BANNERS");
+      cgiSetArray("GROUP", i ++, cgiText(_("Banners")));
+    }
 
-    if (cupsLastError() > IPP_OK_CONFLICT)
-      cgiShowIPPError(_("Unable to get printer attributes:"));
-    else
-      cgiCopyTemplateLang("users.tmpl");
+    if (ippFindAttribute(response, "printer-error-policy-supported",
+                        IPP_TAG_ZERO) ||
+       ippFindAttribute(response, "printer-op-policy-supported",
+                        IPP_TAG_ZERO))
+    {
+      cgiSetArray("GROUP_ID", i, "CUPS_POLICIES");
+      cgiSetArray("GROUP", i ++, cgiText(_("Policies")));
+    }
 
-    cgiEndHTML();
-  }
-  else
-  {
-   /*
-    * Save the changes...
-    */
+    if ((attr = ippFindAttribute(response, "port-monitor-supported",
+                                 IPP_TAG_NAME)) != NULL && attr->num_values > 1)
+    {
+      cgiSetArray("GROUP_ID", i, "CUPS_PORT_MONITOR");
+      cgiSetArray("GROUP", i, cgiText(_("Port Monitor")));
+    }
 
-    for (num_users = 0, ptr = (char *)users; *ptr; num_users ++)
+    cgiStartHTML(cgiText(_("Set Printer Options")));
+    cgiCopyTemplateLang("set-printer-options-header.tmpl");
+
+    if (ppd)
+    {
+      ppdLocalize(ppd);
+
+      if (ppdConflicts(ppd))
+      {
+       for (i = ppd->num_groups, k = 0, group = ppd->groups;
+            i > 0;
+            i --, group ++)
+         for (j = group->num_options, option = group->options;
+              j > 0;
+              j --, option ++)
+           if (option->conflicted)
+           {
+             cgiSetArray("ckeyword", k, option->keyword);
+             cgiSetArray("ckeytext", k, option->text);
+
+             for (m = 0; m < option->num_choices; m ++)
+             {
+               if (option->choices[m].marked)
+               {
+                 cgiSetArray("cchoice", k, option->choices[m].text);
+                 break;
+               }
+              }
+
+             k ++;
+           }
+
+       cgiCopyTemplateLang("option-conflict.tmpl");
+      }
+
+      for (i = ppd->num_groups, group = ppd->groups;
+          i > 0;
+          i --, group ++)
+      {
+       for (j = group->num_options, option = group->options;
+            j > 0;
+            j --, option ++)
+       {
+         if (!strcmp(option->keyword, "PageRegion"))
+           continue;
+
+         if (option->num_choices > 1)
+           break;
+       }
+
+        if (j == 0)
+         continue;
+
+        cgiSetVariable("GROUP_ID", group->name);
+
+       if (!strcmp(group->name, "InstallableOptions"))
+         cgiSetVariable("GROUP", cgiText(_("Options Installed")));
+       else
+         cgiSetVariable("GROUP", group->text);
+
+       cgiCopyTemplateLang("option-header.tmpl");
+
+       for (j = group->num_options, option = group->options;
+            j > 0;
+            j --, option ++)
+       {
+         if (!strcmp(option->keyword, "PageRegion") || option->num_choices < 2)
+           continue;
+
+         cgiSetVariable("KEYWORD", option->keyword);
+         cgiSetVariable("KEYTEXT", option->text);
+
+         if (option->conflicted)
+           cgiSetVariable("CONFLICTED", "1");
+         else
+           cgiSetVariable("CONFLICTED", "0");
+
+         cgiSetSize("CHOICES", 0);
+         cgiSetSize("TEXT", 0);
+         for (k = 0, m = 0; k < option->num_choices; k ++)
+         {
+           cgiSetArray("CHOICES", m, option->choices[k].choice);
+           cgiSetArray("TEXT", m, option->choices[k].text);
+
+           m ++;
+
+           if (option->choices[k].marked)
+             cgiSetVariable("DEFCHOICE", option->choices[k].choice);
+         }
+
+         cgiSetSize("PARAMS", 0);
+         cgiSetSize("PARAMTEXT", 0);
+         cgiSetSize("PARAMVALUE", 0);
+         cgiSetSize("INPUTTYPE", 0);
+
+         if ((coption = ppdFindCustomOption(ppd, option->keyword)))
+         {
+            const char *units = NULL;  /* Units value, if any */
+
+           cgiSetVariable("ISCUSTOM", "1");
+
+           for (cparam = ppdFirstCustomParam(coption), m = 0;
+                cparam;
+                cparam = ppdNextCustomParam(coption), m ++)
+           {
+             if (!_cups_strcasecmp(option->keyword, "PageSize") &&
+                 _cups_strcasecmp(cparam->name, "Width") &&
+                 _cups_strcasecmp(cparam->name, "Height"))
+              {
+               m --;
+               continue;
+              }
+
+             cgiSetArray("PARAMS", m, cparam->name);
+             cgiSetArray("PARAMTEXT", m, cparam->text);
+             cgiSetArray("INPUTTYPE", m, "text");
+
+             switch (cparam->type)
+             {
+               case PPD_CUSTOM_UNKNOWN :
+                   break;
+
+               case PPD_CUSTOM_POINTS :
+                   if (!_cups_strncasecmp(option->defchoice, "Custom.", 7))
+                   {
+                     units = option->defchoice + strlen(option->defchoice) - 2;
+
+                     if (strcmp(units, "mm") && strcmp(units, "cm") &&
+                         strcmp(units, "in") && strcmp(units, "ft"))
+                     {
+                       if (units[1] == 'm')
+                         units ++;
+                       else
+                         units = "pt";
+                     }
+                   }
+                   else
+                     units = "pt";
+
+                    if (!strcmp(units, "mm"))
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points / 72.0 * 25.4);
+                    else if (!strcmp(units, "cm"))
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points / 72.0 * 2.54);
+                    else if (!strcmp(units, "in"))
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points / 72.0);
+                    else if (!strcmp(units, "ft"))
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points / 72.0 / 12.0);
+                    else if (!strcmp(units, "m"))
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points / 72.0 * 0.0254);
+                    else
+                     snprintf(value, sizeof(value), "%g",
+                              cparam->current.custom_points);
+                   cgiSetArray("PARAMVALUE", m, value);
+                   break;
+
+               case PPD_CUSTOM_CURVE :
+               case PPD_CUSTOM_INVCURVE :
+               case PPD_CUSTOM_REAL :
+                   snprintf(value, sizeof(value), "%g",
+                            cparam->current.custom_real);
+                   cgiSetArray("PARAMVALUE", m, value);
+                   break;
+
+               case PPD_CUSTOM_INT:
+                   snprintf(value, sizeof(value), "%d",
+                            cparam->current.custom_int);
+                   cgiSetArray("PARAMVALUE", m, value);
+                   break;
+
+               case PPD_CUSTOM_PASSCODE:
+               case PPD_CUSTOM_PASSWORD:
+                   if (cparam->current.custom_password)
+                     cgiSetArray("PARAMVALUE", m,
+                                 cparam->current.custom_password);
+                   else
+                     cgiSetArray("PARAMVALUE", m, "");
+                   cgiSetArray("INPUTTYPE", m, "password");
+                   break;
+
+               case PPD_CUSTOM_STRING:
+                   if (cparam->current.custom_string)
+                     cgiSetArray("PARAMVALUE", m,
+                                 cparam->current.custom_string);
+                   else
+                     cgiSetArray("PARAMVALUE", m, "");
+                   break;
+             }
+           }
+
+            if (units)
+           {
+             cgiSetArray("PARAMS", m, "Units");
+             cgiSetArray("PARAMTEXT", m, cgiText(_("Units")));
+             cgiSetArray("PARAMVALUE", m, units);
+           }
+         }
+         else
+           cgiSetVariable("ISCUSTOM", "0");
+
+         switch (option->ui)
+         {
+           case PPD_UI_BOOLEAN :
+               cgiCopyTemplateLang("option-boolean.tmpl");
+               break;
+           case PPD_UI_PICKONE :
+               cgiCopyTemplateLang("option-pickone.tmpl");
+               break;
+           case PPD_UI_PICKMANY :
+               cgiCopyTemplateLang("option-pickmany.tmpl");
+               break;
+         }
+       }
+
+       cgiCopyTemplateLang("option-trailer.tmpl");
+      }
+    }
+
+    if ((attr = ippFindAttribute(response, "job-sheets-supported",
+                                IPP_TAG_ZERO)) != NULL)
     {
      /*
-      * Skip whitespace and commas...
+      * Add the job sheets options...
       */
 
-      while (*ptr == ',' || isspace(*ptr & 255))
-       ptr ++;
+      cgiSetVariable("GROUP_ID", "CUPS_BANNERS");
+      cgiSetVariable("GROUP", cgiText(_("Banners")));
+      cgiCopyTemplateLang("option-header.tmpl");
 
-      if (*ptr == '\'' || *ptr == '\"')
+      cgiSetSize("CHOICES", attr->num_values);
+      cgiSetSize("TEXT", attr->num_values);
+      for (k = 0; k < attr->num_values; k ++)
       {
-       /*
-       * Scan quoted name...
-       */
+       cgiSetArray("CHOICES", k, attr->values[k].string.text);
+       cgiSetArray("TEXT", k, attr->values[k].string.text);
+      }
 
-       quote = *ptr++;
+      attr = ippFindAttribute(response, "job-sheets-default", IPP_TAG_ZERO);
+
+      cgiSetVariable("KEYWORD", "job_sheets_start");
+      cgiSetVariable("KEYTEXT",
+                     /* TRANSLATORS: Banner/cover sheet before the print job. */
+                     cgiText(_("Starting Banner")));
+      cgiSetVariable("DEFCHOICE", attr != NULL ?
+                                 attr->values[0].string.text : "");
+
+      cgiCopyTemplateLang("option-pickone.tmpl");
+
+      cgiSetVariable("KEYWORD", "job_sheets_end");
+      cgiSetVariable("KEYTEXT",
+                     /* TRANSLATORS: Banner/cover sheet after the print job. */
+                     cgiText(_("Ending Banner")));
+      cgiSetVariable("DEFCHOICE", attr != NULL && attr->num_values > 1 ?
+                                 attr->values[1].string.text : "");
+
+      cgiCopyTemplateLang("option-pickone.tmpl");
+
+      cgiCopyTemplateLang("option-trailer.tmpl");
+    }
+
+    if (ippFindAttribute(response, "printer-error-policy-supported",
+                        IPP_TAG_ZERO) ||
+       ippFindAttribute(response, "printer-op-policy-supported",
+                        IPP_TAG_ZERO))
+    {
+     /*
+      * Add the error and operation policy options...
+      */
+
+      cgiSetVariable("GROUP_ID", "CUPS_POLICIES");
+      cgiSetVariable("GROUP", cgiText(_("Policies")));
+      cgiCopyTemplateLang("option-header.tmpl");
+
+     /*
+      * Error policy...
+      */
+
+      attr = ippFindAttribute(response, "printer-error-policy-supported",
+                             IPP_TAG_ZERO);
+
+      if (attr)
+      {
+       cgiSetSize("CHOICES", attr->num_values);
+       cgiSetSize("TEXT", attr->num_values);
+       for (k = 0; k < attr->num_values; k ++)
+       {
+         cgiSetArray("CHOICES", k, attr->values[k].string.text);
+         cgiSetArray("TEXT", k, attr->values[k].string.text);
+       }
+
+       attr = ippFindAttribute(response, "printer-error-policy",
+                               IPP_TAG_ZERO);
+
+       cgiSetVariable("KEYWORD", "printer_error_policy");
+       cgiSetVariable("KEYTEXT", cgiText(_("Error Policy")));
+       cgiSetVariable("DEFCHOICE", attr == NULL ?
+                                   "" : attr->values[0].string.text);
+      }
+
+      cgiCopyTemplateLang("option-pickone.tmpl");
+
+     /*
+      * Operation policy...
+      */
+
+      attr = ippFindAttribute(response, "printer-op-policy-supported",
+                             IPP_TAG_ZERO);
+
+      if (attr)
+      {
+       cgiSetSize("CHOICES", attr->num_values);
+       cgiSetSize("TEXT", attr->num_values);
+       for (k = 0; k < attr->num_values; k ++)
+       {
+         cgiSetArray("CHOICES", k, attr->values[k].string.text);
+         cgiSetArray("TEXT", k, attr->values[k].string.text);
+       }
+
+       attr = ippFindAttribute(response, "printer-op-policy", IPP_TAG_ZERO);
+
+       cgiSetVariable("KEYWORD", "printer_op_policy");
+       cgiSetVariable("KEYTEXT", cgiText(_("Operation Policy")));
+       cgiSetVariable("DEFCHOICE", attr == NULL ?
+                                   "" : attr->values[0].string.text);
+
+       cgiCopyTemplateLang("option-pickone.tmpl");
+      }
+
+      cgiCopyTemplateLang("option-trailer.tmpl");
+    }
+
+   /*
+    * Binary protocol support...
+    */
+
+    if ((attr = ippFindAttribute(response, "port-monitor-supported",
+                                 IPP_TAG_NAME)) != NULL && attr->num_values > 1)
+    {
+      cgiSetVariable("GROUP_ID", "CUPS_PORT_MONITOR");
+      cgiSetVariable("GROUP", cgiText(_("Port Monitor")));
+
+      cgiSetSize("CHOICES", attr->num_values);
+      cgiSetSize("TEXT", attr->num_values);
 
-       for (end = ptr; *end; end ++)
-         if (*end == quote)
-           break;
-      }
-      else
+      for (i = 0; i < attr->num_values; i ++)
       {
-       /*
-       * Scan space or comma-delimited name...
-       */
-
-        for (end = ptr; *end; end ++)
-         if (isspace(*end & 255) || *end == ',')
-           break;
+        cgiSetArray("CHOICES", i, attr->values[i].string.text);
+        cgiSetArray("TEXT", i, attr->values[i].string.text);
       }
 
-     /*
-      * Advance to the next name...
-      */
+      attr = ippFindAttribute(response, "port-monitor", IPP_TAG_NAME);
+      cgiSetVariable("KEYWORD", "port_monitor");
+      cgiSetVariable("KEYTEXT", cgiText(_("Port Monitor")));
+      cgiSetVariable("DEFCHOICE", attr ? attr->values[0].string.text : "none");
 
-      ptr = end;
+      cgiCopyTemplateLang("option-header.tmpl");
+      cgiCopyTemplateLang("option-pickone.tmpl");
+      cgiCopyTemplateLang("option-trailer.tmpl");
     }
 
+    cgiCopyTemplateLang("set-printer-options-trailer.tmpl");
+    cgiEndHTML();
+
+    ippDelete(response);
+  }
+  else
+  {
    /*
-    * Build a CUPS-Add-Printer/Class request, which requires the following
-    * attributes:
-    *
-    *    attributes-charset
-    *    attributes-natural-language
-    *    printer-uri
-    *    requesting-user-name-{allowed,denied}
+    * Set default options...
     */
 
-    request = ippNewRequest(is_class ? CUPS_ADD_CLASS : CUPS_ADD_PRINTER);
-
-    httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                     is_class ? "/classes/%s" : "/printers/%s", printer);
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
-                NULL, uri);
+    fputs("DEBUG: Setting options...\n", stderr);
 
-    if (num_users == 0)
-      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
-                   "requesting-user-name-allowed", NULL, "all");
-    else
+    if (filename)
     {
-      attr = ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
-                           type, num_users, NULL, NULL);
+      out = cupsTempFile2(tempfile, sizeof(tempfile));
+      in  = cupsFileOpen(filename, "r");
 
-      for (i = 0, ptr = (char *)users; *ptr; i ++)
+      if (!in || !out)
       {
-       /*
-        * Skip whitespace and commas...
-       */
+       cgiSetVariable("ERROR", strerror(errno));
+       cgiStartHTML(cgiText(_("Set Printer Options")));
+       cgiCopyTemplateLang("error.tmpl");
+       cgiEndHTML();
 
-        while (*ptr == ',' || isspace(*ptr & 255))
-         ptr ++;
+       if (in)
+         cupsFileClose(in);
 
-        if (*ptr == '\'' || *ptr == '\"')
+       if (out)
        {
-        /*
-         * Scan quoted name...
-         */
+         cupsFileClose(out);
+         unlink(tempfile);
+       }
 
-         quote = *ptr++;
+       unlink(filename);
+       return;
+      }
 
-         for (end = ptr; *end; end ++)
-           if (*end == quote)
-             break;
-       }
+      while (cupsFileGets(in, line, sizeof(line)))
+      {
+       if (!strncmp(line, "*cupsProtocol:", 14))
+         continue;
+       else if (strncmp(line, "*Default", 8))
+         cupsFilePrintf(out, "%s\n", line);
        else
        {
         /*
-         * Scan space or comma-delimited name...
+         * Get default option name...
          */
 
-          for (end = ptr; *end; end ++)
-           if (isspace(*end & 255) || *end == ',')
-             break;
-        }
+         strlcpy(keyword, line + 8, sizeof(keyword));
 
-       /*
-        * Terminate the name...
-       */
+         for (keyptr = keyword; *keyptr; keyptr ++)
+           if (*keyptr == ':' || isspace(*keyptr & 255))
+             break;
 
-        if (*end)
-          *end++ = '\0';
+         *keyptr = '\0';
 
-       /*
-        * Add the name...
-       */
+         if (!strcmp(keyword, "PageRegion") ||
+             !strcmp(keyword, "PaperDimension") ||
+             !strcmp(keyword, "ImageableArea"))
+           var = get_option_value(ppd, "PageSize", value, sizeof(value));
+         else
+           var = get_option_value(ppd, keyword, value, sizeof(value));
 
-        attr->values[i].string.text = strdup(ptr);
+         if (!var)
+           cupsFilePrintf(out, "%s\n", line);
+         else
+           cupsFilePrintf(out, "*Default%s: %s\n", keyword, var);
+       }
+      }
 
-       /*
-        * Advance to the next name...
-       */
+      cupsFileClose(in);
+      cupsFileClose(out);
+    }
+    else
+    {
+     /*
+      * Make sure temporary filename is cleared when there is no PPD...
+      */
 
-        ptr = end;
-      }
+      tempfile[0] = '\0';
     }
 
+   /*
+    * Build a CUPS_ADD_MODIFY_CLASS/PRINTER request, which requires the
+    * following attributes:
+    *
+    *    attributes-charset
+    *    attributes-natural-language
+    *    printer-uri
+    *    job-sheets-default
+    *    printer-error-policy
+    *    printer-op-policy
+    *    [ppd file]
+    */
+
+    request = ippNewRequest(is_class ? CUPS_ADD_MODIFY_CLASS :
+                                       CUPS_ADD_MODIFY_PRINTER);
+
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
+                 NULL, uri);
+
+    attr = ippAddStrings(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                         "job-sheets-default", 2, NULL, NULL);
+    ippSetString(request, &attr, 0, cgiGetVariable("job_sheets_start"));
+    ippSetString(request, &attr, 1, cgiGetVariable("job_sheets_end"));
+
+    if ((var = cgiGetVariable("printer_error_policy")) != NULL)
+      ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                  "printer-error-policy", NULL, var);
+
+    if ((var = cgiGetVariable("printer_op_policy")) != NULL)
+      ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                  "printer-op-policy", NULL, var);
+
+    if ((var = cgiGetVariable("port_monitor")) != NULL)
+      ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME,
+                  "port-monitor", NULL, var);
+
    /*
     * Do the request and get back a response...
     */
 
-    ippDelete(cupsDoRequest(http, request, "/admin/"));
+    if (filename)
+      ippDelete(cupsDoFileRequest(http, request, "/admin/", tempfile));
+    else
+      ippDelete(cupsDoRequest(http, request, "/admin/"));
 
-    if (cupsLastError() > IPP_OK_CONFLICT)
+    if (cupsLastError() == IPP_NOT_AUTHORIZED)
     {
-      cgiStartHTML(cgiText(_("Set Allowed Users")));
-      cgiShowIPPError(_("Unable to change printer:"));
+      puts("Status: 401\n");
+      exit(0);
+    }
+    else if (cupsLastError() > IPP_OK_CONFLICT)
+    {
+      cgiStartHTML(title);
+      cgiShowIPPError(_("Unable to set options"));
     }
     else
     {
@@ -3405,28 +3319,32 @@ do_set_allowed_users(http_t *http)      /* I - HTTP connection */
       * Redirect successful updates back to the printer page...
       */
 
-      char     url[1024],              /* Printer/class URL */
-               refresh[1024];          /* Refresh URL */
+      char     refresh[1024];          /* Refresh URL */
 
 
-      cgiRewriteURL(uri, url, sizeof(url), NULL);
-      cgiFormEncode(uri, url, sizeof(uri));
-      snprintf(refresh, sizeof(refresh), "5;/admin/?OP=redirect&URL=%s", uri);
+      cgiFormEncode(uri, printer, sizeof(uri));
+      snprintf(refresh, sizeof(refresh), "5;URL=/admin/?OP=redirect&URL=/%s/%s",
+              is_class ? "classes" : "printers", uri);
       cgiSetVariable("refresh_page", refresh);
 
-      cgiStartHTML(cgiText(_("Set Allowed Users")));
+      cgiStartHTML(title);
 
-      cgiCopyTemplateLang(is_class ? "class-modified.tmpl" :
-                                     "printer-modified.tmpl");
+      cgiCopyTemplateLang("printer-configured.tmpl");
     }
 
     cgiEndHTML();
+
+    if (filename)
+      unlink(tempfile);
   }
+
+  if (filename)
+    unlink(filename);
 }
 
 
 /*
- * 'do_set_sharing()' - Set printer-is-shared value...
+ * 'do_set_sharing()' - Set printer-is-shared value.
  */
 
 static void
@@ -3446,7 +3364,7 @@ do_set_sharing(http_t *http)              /* I - HTTP connection */
 
   if (!printer || !shared)
   {
-    cgiSetVariable("ERROR", cgiText(_("Missing form variable!")));
+    cgiSetVariable("ERROR", cgiText(_("Missing form variable")));
     cgiStartHTML(cgiText(_("Set Publishing")));
     cgiCopyTemplateLang("error.tmpl");
     cgiEndHTML();
@@ -3465,12 +3383,13 @@ do_set_sharing(http_t *http)            /* I - HTTP connection */
 
   request = ippNewRequest(is_class ? CUPS_ADD_CLASS : CUPS_ADD_PRINTER);
 
-  httpAssembleURIf(uri, sizeof(uri), "ipp", NULL, "localhost", 0,
-                   is_class ? "/classes/%s" : "/printers/%s", printer);
+  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
+                   "localhost", 0, is_class ? "/classes/%s" : "/printers/%s",
+                  printer);
   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
                NULL, uri);
 
-  ippAddBoolean(request, IPP_TAG_OPERATION, "printer-is-shared", atoi(shared));
+  ippAddBoolean(request, IPP_TAG_OPERATION, "printer-is-shared", (char)atoi(shared));
 
  /*
   * Do the request and get back a response...
@@ -3483,10 +3402,15 @@ do_set_sharing(http_t *http)            /* I - HTTP connection */
     ippDelete(response);
   }
 
-  if (cupsLastError() > IPP_OK_CONFLICT)
+  if (cupsLastError() == IPP_NOT_AUTHORIZED)
+  {
+    puts("Status: 401\n");
+    exit(0);
+  }
+  else if (cupsLastError() > IPP_OK_CONFLICT)
   {
     cgiStartHTML(cgiText(_("Set Publishing")));
-    cgiShowIPPError(_("Unable to change printer-is-shared attribute:"));
+    cgiShowIPPError(_("Unable to change printer-is-shared attribute"));
   }
   else
   {
@@ -3500,7 +3424,7 @@ do_set_sharing(http_t *http)              /* I - HTTP connection */
 
     cgiRewriteURL(uri, url, sizeof(url), NULL);
     cgiFormEncode(uri, url, sizeof(uri));
-    snprintf(refresh, sizeof(refresh), "5;/admin/?OP=redirect&URL=%s", uri);
+    snprintf(refresh, sizeof(refresh), "5;URL=/admin/?OP=redirect&URL=%s", uri);
     cgiSetVariable("refresh_page", refresh);
 
     cgiStartHTML(cgiText(_("Set Publishing")));
@@ -3513,54 +3437,373 @@ do_set_sharing(http_t *http)           /* I - HTTP connection */
 
 
 /*
- * 'match_string()' - Return the number of matching characters.
+ * 'get_option_value()' - Return the value of an option.
+ *
+ * This function also handles generation of custom option values.
  */
 
-static int                             /* O - Number of matching characters */
-match_string(const char *a,            /* I - First string */
-             const char *b)            /* I - Second string */
+static char *                          /* O - Value string or NULL on error */
+get_option_value(
+    ppd_file_t    *ppd,                        /* I - PPD file */
+    const char    *name,               /* I - Option name */
+    char          *buffer,             /* I - String buffer */
+    size_t        bufsize)             /* I - Size of buffer */
 {
-  int  count;                          /* Number of matching characters */
+  char         *bufptr,                /* Pointer into buffer */
+               *bufend;                /* End of buffer */
+  ppd_coption_t *coption;              /* Custom option */
+  ppd_cparam_t *cparam;                /* Current custom parameter */
+  char         keyword[256];           /* Parameter name */
+  const char   *val,                   /* Parameter value */
+               *uval;                  /* Units value */
+  long         integer;                /* Integer value */
+  double       number,                 /* Number value */
+               number_points;          /* Number in points */
 
 
  /*
-  * Loop through both strings until we hit the end of either or we find
-  * a non-matching character.  For the purposes of comparison, we ignore
-  * whitespace and do a case-insensitive comparison so that we have a
-  * better chance of finding a match...
+  * See if we have a custom option choice...
   */
 
-  for (count = 0; *a && *b; a++, b++, count ++)
+  if ((val = cgiGetVariable(name)) == NULL)
   {
    /*
-    * Skip leading whitespace characters...
+    * Option not found!
     */
 
-    while (isspace(*a & 255))
-      a ++;
-
-    while (isspace(*b & 255))
-      b ++;
-
+    return (NULL);
+  }
+  else if (_cups_strcasecmp(val, "Custom") ||
+           (coption = ppdFindCustomOption(ppd, name)) == NULL)
+  {
    /*
-    * Break out if we run out of characters...
+    * Not a custom choice...
     */
 
-    if (!*a || !*b)
-      break;
+    strlcpy(buffer, val, bufsize);
+    return (buffer);
+  }
 
  /*
-    * Do a case-insensitive comparison of the next two chars...
-    */
+ /*
+  * OK, we have a custom option choice, format it...
+  */
 
-    if (tolower(*a & 255) != tolower(*b & 255))
-      break;
+  *buffer = '\0';
+
+  if (!strcmp(coption->keyword, "PageSize"))
+  {
+    const char *lval;                  /* Length string value */
+    double     width,                  /* Width value */
+               width_points,           /* Width in points */
+               length,                 /* Length value */
+               length_points;          /* Length in points */
+
+
+    val  = cgiGetVariable("PageSize.Width");
+    lval = cgiGetVariable("PageSize.Height");
+    uval = cgiGetVariable("PageSize.Units");
+
+    if (!val || !lval || !uval ||
+        (width = strtod(val, NULL)) == 0.0 ||
+        (length = strtod(lval, NULL)) == 0.0 ||
+        (strcmp(uval, "pt") && strcmp(uval, "in") && strcmp(uval, "ft") &&
+        strcmp(uval, "cm") && strcmp(uval, "mm") && strcmp(uval, "m")))
+      return (NULL);
+
+    width_points  = get_points(width, uval);
+    length_points = get_points(length, uval);
+
+    if (width_points < ppd->custom_min[0] ||
+        width_points > ppd->custom_max[0] ||
+        length_points < ppd->custom_min[1] ||
+       length_points > ppd->custom_max[1])
+      return (NULL);
+
+    snprintf(buffer, bufsize, "Custom.%gx%g%s", width, length, uval);
+  }
+  else if (cupsArrayCount(coption->params) == 1)
+  {
+    cparam = ppdFirstCustomParam(coption);
+    snprintf(keyword, sizeof(keyword), "%s.%s", coption->keyword, cparam->name);
+
+    if ((val = cgiGetVariable(keyword)) == NULL)
+      return (NULL);
+
+    switch (cparam->type)
+    {
+      case PPD_CUSTOM_UNKNOWN :
+         break;
+
+      case PPD_CUSTOM_CURVE :
+      case PPD_CUSTOM_INVCURVE :
+      case PPD_CUSTOM_REAL :
+         if ((number = strtod(val, NULL)) == 0.0 ||
+             number < cparam->minimum.custom_real ||
+             number > cparam->maximum.custom_real)
+           return (NULL);
+
+          snprintf(buffer, bufsize, "Custom.%g", number);
+          break;
+
+      case PPD_CUSTOM_INT :
+          if (!*val || (integer = strtol(val, NULL, 10)) == LONG_MIN ||
+             integer == LONG_MAX ||
+             integer < cparam->minimum.custom_int ||
+             integer > cparam->maximum.custom_int)
+            return (NULL);
+
+          snprintf(buffer, bufsize, "Custom.%ld", integer);
+          break;
+
+      case PPD_CUSTOM_POINTS :
+          snprintf(keyword, sizeof(keyword), "%s.Units", coption->keyword);
+
+         if ((number = strtod(val, NULL)) == 0.0 ||
+             (uval = cgiGetVariable(keyword)) == NULL ||
+             (strcmp(uval, "pt") && strcmp(uval, "in") && strcmp(uval, "ft") &&
+              strcmp(uval, "cm") && strcmp(uval, "mm") && strcmp(uval, "m")))
+           return (NULL);
+
+         number_points = get_points(number, uval);
+         if (number_points < cparam->minimum.custom_points ||
+             number_points > cparam->maximum.custom_points)
+           return (NULL);
+
+         snprintf(buffer, bufsize, "Custom.%g%s", number, uval);
+          break;
+
+      case PPD_CUSTOM_PASSCODE :
+          for (uval = val; *uval; uval ++)
+           if (!isdigit(*uval & 255))
+             return (NULL);
+
+      case PPD_CUSTOM_PASSWORD :
+      case PPD_CUSTOM_STRING :
+          integer = (long)strlen(val);
+         if (integer < cparam->minimum.custom_string ||
+             integer > cparam->maximum.custom_string)
+           return (NULL);
+
+          snprintf(buffer, bufsize, "Custom.%s", val);
+         break;
+    }
+  }
+  else
+  {
+    const char *prefix = "{";          /* Prefix string */
+
+
+    bufptr = buffer;
+    bufend = buffer + bufsize;
+
+    for (cparam = ppdFirstCustomParam(coption);
+        cparam;
+        cparam = ppdNextCustomParam(coption))
+    {
+      snprintf(keyword, sizeof(keyword), "%s.%s", coption->keyword,
+               cparam->name);
+
+      if ((val = cgiGetVariable(keyword)) == NULL)
+       return (NULL);
+
+      snprintf(bufptr, (size_t)(bufend - bufptr), "%s%s=", prefix, cparam->name);
+      bufptr += strlen(bufptr);
+      prefix = " ";
+
+      switch (cparam->type)
+      {
+       case PPD_CUSTOM_UNKNOWN :
+           break;
+
+       case PPD_CUSTOM_CURVE :
+       case PPD_CUSTOM_INVCURVE :
+       case PPD_CUSTOM_REAL :
+           if ((number = strtod(val, NULL)) == 0.0 ||
+               number < cparam->minimum.custom_real ||
+               number > cparam->maximum.custom_real)
+             return (NULL);
+
+           snprintf(bufptr, (size_t)(bufend - bufptr), "%g", number);
+           break;
+
+       case PPD_CUSTOM_INT :
+           if (!*val || (integer = strtol(val, NULL, 10)) == LONG_MIN ||
+               integer == LONG_MAX ||
+               integer < cparam->minimum.custom_int ||
+               integer > cparam->maximum.custom_int)
+             return (NULL);
+
+           snprintf(bufptr, (size_t)(bufend - bufptr), "%ld", integer);
+           break;
+
+       case PPD_CUSTOM_POINTS :
+           snprintf(keyword, sizeof(keyword), "%s.Units", coption->keyword);
+
+           if ((number = strtod(val, NULL)) == 0.0 ||
+               (uval = cgiGetVariable(keyword)) == NULL ||
+               (strcmp(uval, "pt") && strcmp(uval, "in") &&
+                strcmp(uval, "ft") && strcmp(uval, "cm") &&
+                strcmp(uval, "mm") && strcmp(uval, "m")))
+             return (NULL);
+
+           number_points = get_points(number, uval);
+           if (number_points < cparam->minimum.custom_points ||
+               number_points > cparam->maximum.custom_points)
+             return (NULL);
+
+           snprintf(bufptr, (size_t)(bufend - bufptr), "%g%s", number, uval);
+           break;
+
+       case PPD_CUSTOM_PASSCODE :
+           for (uval = val; *uval; uval ++)
+             if (!isdigit(*uval & 255))
+               return (NULL);
+
+       case PPD_CUSTOM_PASSWORD :
+       case PPD_CUSTOM_STRING :
+           integer = (long)strlen(val);
+           if (integer < cparam->minimum.custom_string ||
+               integer > cparam->maximum.custom_string)
+             return (NULL);
+
+           if ((bufptr + 2) > bufend)
+             return (NULL);
+
+           bufend --;
+           *bufptr++ = '\"';
+
+           while (*val && bufptr < bufend)
+           {
+             if (*val == '\\' || *val == '\"')
+             {
+               if ((bufptr + 1) >= bufend)
+                 return (NULL);
+
+               *bufptr++ = '\\';
+             }
+
+             *bufptr++ = *val++;
+           }
+
+           if (bufptr >= bufend)
+             return (NULL);
+
+           *bufptr++ = '\"';
+           *bufptr   = '\0';
+           bufend ++;
+           break;
+      }
+
+      bufptr += strlen(bufptr);
+    }
+
+    if (bufptr == buffer || (bufend - bufptr) < 2)
+      return (NULL);
+
+    memcpy(bufptr, "}", 2);
   }
 
-  return (count);
+  return (buffer);
 }
 
-    
+
+/*
+ * 'get_points()' - Get a value in points.
+ */
+
+static double                          /* O - Number in points */
+get_points(double     number,          /* I - Original number */
+           const char *uval)           /* I - Units */
+{
+  if (!strcmp(uval, "mm"))             /* Millimeters */
+    return (number * 72.0 / 25.4);
+  else if (!strcmp(uval, "cm"))                /* Centimeters */
+    return (number * 72.0 / 2.54);
+  else if (!strcmp(uval, "in"))                /* Inches */
+    return (number * 72.0);
+  else if (!strcmp(uval, "ft"))                /* Feet */
+    return (number * 72.0 * 12.0);
+  else if (!strcmp(uval, "m"))         /* Meters */
+    return (number * 72.0 / 0.0254);
+  else                                 /* Points */
+    return (number);
+}
+
+
 /*
- * End of "$Id: admin.c 4943 2006-01-18 20:30:42Z mike $".
+ * 'get_printer_ppd()' - Get an IPP Everywhere PPD file for the given URI.
  */
+
+static char *                          /* O - Filename or NULL */
+get_printer_ppd(const char *uri,       /* I - Printer URI */
+                char       *buffer,    /* I - Filename buffer */
+               size_t     bufsize)     /* I - Size of filename buffer */
+{
+  http_t       *http;                  /* Connection to printer */
+  ipp_t                *request,               /* Get-Printer-Attributes request */
+               *response;              /* Get-Printer-Attributes response */
+  char         resolved[1024],         /* Resolved URI */
+               scheme[32],             /* URI scheme */
+               userpass[256],          /* Username:password */
+               host[256],              /* Hostname */
+               resource[256];          /* Resource path */
+  int          port;                   /* Port number */
+  static const char * const pattrs[] = /* Printer attributes we need */
+  {
+    "all",
+    "media-col-database"
+  };
+
+
+ /*
+  * Connect to the printer...
+  */
+
+  if (strstr(uri, "._tcp"))
+  {
+   /*
+    * Resolve URI...
+    */
+
+    if (!_httpResolveURI(uri, resolved, sizeof(resolved), _HTTP_RESOLVE_DEFAULT, NULL, NULL))
+    {
+      fprintf(stderr, "ERROR: Unable to resolve \"%s\".\n", uri);
+      return (NULL);
+    }
+
+    uri = resolved;
+  }
+
+  if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+  {
+    fprintf(stderr, "ERROR: Bad printer URI \"%s\".\n", uri);
+    return (NULL);
+  }
+
+  http = httpConnect2(host, port, NULL, AF_UNSPEC, !strcmp(scheme, "ipps") ? HTTP_ENCRYPTION_ALWAYS : HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL);
+  if (!http)
+  {
+    fprintf(stderr, "ERROR: Unable to connect to \"%s:%d\": %s\n", host, port, cupsLastErrorString());
+    return (NULL);
+  }
+
+ /*
+  * Send a Get-Printer-Attributes request...
+  */
+
+  request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
+  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes",  (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
+  response = cupsDoRequest(http, request, resource);
+
+  if (!_ppdCreateFromIPP(buffer, bufsize, response))
+    fprintf(stderr, "ERROR: Unable to create PPD file: %s\n", strerror(errno));
+
+  ippDelete(response);
+  httpClose(http);
+
+  if (buffer[0])
+    return (buffer);
+  else
+    return (NULL);
+}