]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/ipp.c
Merge changes from CUPS 1.4svn-r8454.
[thirdparty/cups.git] / scheduler / ipp.c
index 86db35441d3e921e913ad859e227095c8c11198f..edf5c44c36e2ff267040509fa7a0a5c47857b045 100644 (file)
@@ -3,7 +3,7 @@
  *
  *   IPP routines for the Common UNIX Printing System (CUPS) scheduler.
  *
- *   Copyright 2007-2008 by Apple Inc.
+ *   Copyright 2007-2009 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   This file contains Kerberos support code, copyright 2006 by
@@ -89,6 +89,7 @@
  *   send_ipp_status()           - Send a status back to the IPP client.
  *   set_default()               - Set the default destination...
  *   set_job_attrs()             - Set job attributes.
+ *   set_printer_attrs()         - Set printer attributes.
  *   set_printer_defaults()      - Set printer default options from a request.
  *   start_printer()             - Start a printer.
  *   stop_printer()              - Stop a printer.
@@ -193,6 +194,7 @@ static void get_ppd(cupsd_client_t *con, ipp_attribute_t *uri);
 static void    get_ppds(cupsd_client_t *con);
 static void    get_printers(cupsd_client_t *con, int type);
 static void    get_printer_attrs(cupsd_client_t *con, ipp_attribute_t *uri);
+static void    get_printer_supported(cupsd_client_t *con, ipp_attribute_t *uri);
 static void    get_subscription_attrs(cupsd_client_t *con, int sub_id);
 static void    get_subscriptions(cupsd_client_t *con, ipp_attribute_t *uri);
 static const char *get_username(cupsd_client_t *con);
@@ -225,6 +227,7 @@ __attribute__ ((__format__ (__printf__, 3, 4)))
 ;
 static void    set_default(cupsd_client_t *con, ipp_attribute_t *uri);
 static void    set_job_attrs(cupsd_client_t *con, ipp_attribute_t *uri);
+static void    set_printer_attrs(cupsd_client_t *con, ipp_attribute_t *uri);
 static void    set_printer_defaults(cupsd_client_t *con,
                                     cupsd_printer_t *printer);
 static void    start_printer(cupsd_client_t *con, ipp_attribute_t *uri);
@@ -278,10 +281,11 @@ cupsdProcessIPPRequest(
   * Then validate the request header and required attributes...
   */
 
-  if (con->request->request.any.version[0] != 1)
+  if (con->request->request.any.version[0] != 1 &&
+      con->request->request.any.version[0] != 2)
   {
    /*
-    * Return an error, since we only support IPP 1.x.
+    * Return an error, since we only support IPP 1.x and 2.x.
     */
 
     cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL,
@@ -295,6 +299,20 @@ cupsdProcessIPPRequest(
                    con->request->request.any.version[0],
                    con->request->request.any.version[1]);
   }
+  else if (con->request->request.any.request_id < 1)
+  {
+   /*
+    * Return an error, since request IDs must be between 1 and 2^31-1
+    */
+
+    cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL,
+                  "%04X %s Bad request ID %d",
+                 IPP_BAD_REQUEST, con->http.hostname,
+                  con->request->request.any.request_id);
+
+    send_ipp_status(con, IPP_BAD_REQUEST, _("Bad request ID %d!"),
+                   con->request->request.any.request_id);
+  }
   else if (!con->request->attrs)
   {
     cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL,
@@ -342,7 +360,8 @@ cupsdProcessIPPRequest(
       */
 
       attr = con->request->attrs;
-      if (attr && !strcmp(attr->name, "attributes-charset") &&
+      if (attr && attr->name &&
+          !strcmp(attr->name, "attributes-charset") &&
          (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET)
        charset = attr;
       else
@@ -351,7 +370,8 @@ cupsdProcessIPPRequest(
       if (attr)
         attr = attr->next;
 
-      if (attr && !strcmp(attr->name, "attributes-natural-language") &&
+      if (attr && attr->name &&
+          !strcmp(attr->name, "attributes-natural-language") &&
          (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE)
        language = attr;
       else
@@ -539,6 +559,10 @@ cupsdProcessIPPRequest(
               get_printer_attrs(con, uri);
               break;
 
+         case IPP_GET_PRINTER_SUPPORTED_VALUES :
+              get_printer_supported(con, uri);
+              break;
+
          case IPP_HOLD_JOB :
               hold_job(con, uri);
               break;
@@ -567,6 +591,10 @@ cupsdProcessIPPRequest(
               set_job_attrs(con, uri);
               break;
 
+         case IPP_SET_PRINTER_ATTRIBUTES :
+              set_printer_attrs(con, uri);
+              break;
+
          case IPP_HOLD_NEW_JOBS :
               hold_new_jobs(con, uri);
               break;
@@ -699,6 +727,11 @@ cupsdProcessIPPRequest(
                    uri ? uri->values[0].string.text : "no URI",
                    con->http.hostname);
 
+    if (LogLevel == CUPSD_LOG_DEBUG2)
+      cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                     "cupsdProcessIPPRequest: ippLength(response)=%ld",
+                     (long)ippLength(con->response));
+
     if (cupsdSendHeader(con, HTTP_OK, "application/ipp", CUPSD_AUTH_NONE))
     {
 #ifdef CUPSD_USE_CHUNKING
@@ -1185,23 +1218,14 @@ add_class(cupsd_client_t  *con,         /* I - Client connection */
 
   if (need_restart_job && pclass->job)
   {
-    cupsd_job_t *job;
-
    /*
-    * Stop the current job and then restart it below...
+    * Reset the current job to a "pending" status...
     */
 
-    job = (cupsd_job_t *)pclass->job;
-
-    cupsdStopJob(job, 1);
-
-    job->state->values[0].integer = IPP_JOB_PENDING;
-    job->state_value              = IPP_JOB_PENDING;
+    cupsdSetJobState(pclass->job, IPP_JOB_PENDING, CUPSD_JOB_FORCE,
+                     "Job restarted because the class was modified.");
   }
 
-  if (need_restart_job)
-    cupsdCheckJobs();
-
   cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);
 
   if (modify)
@@ -1268,7 +1292,8 @@ add_file(cupsd_client_t *con,             /* I - Connection to client */
 
   if (!compressions || !filetypes)
   {
-    cupsdCancelJob(job, 1, IPP_JOB_ABORTED);
+    cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
+                     "Job aborted because the scheduler ran out of memory.");
 
     if (con)
       send_ipp_status(con, IPP_INTERNAL_ERROR,
@@ -1707,14 +1732,14 @@ add_job(cupsd_client_t  *con,           /* I - Client connection */
     * Hold job until specified time...
     */
 
-    cupsdSetJobHoldUntil(job, attr->values[0].string.text);
+    cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);
 
     job->state->values[0].integer = IPP_JOB_HELD;
     job->state_value              = IPP_JOB_HELD;
   }
   else if (job->attrs->request.op.operation_id == IPP_CREATE_JOB)
   {
-    job->hold_until               = time(NULL) + 60;
+    job->hold_until               = time(NULL) + MultipleOperationTimeout;
     job->state->values[0].integer = IPP_JOB_HELD;
     job->state_value              = IPP_JOB_HELD;
   }
@@ -1740,8 +1765,8 @@ add_job(cupsd_client_t  *con,             /* I - Client connection */
 
       attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets",
                            2, NULL, NULL);
-      attr->values[0].string.text = _cupsStrAlloc(printer->job_sheets[0]);
-      attr->values[1].string.text = _cupsStrAlloc(printer->job_sheets[1]);
+      attr->values[0].string.text = _cupsStrRetain(printer->job_sheets[0]);
+      attr->values[1].string.text = _cupsStrRetain(printer->job_sheets[1]);
     }
 
     job->job_sheets = attr;
@@ -1865,7 +1890,9 @@ add_job(cupsd_client_t  *con,             /* I - Client connection */
 
       if ((kbytes = copy_banner(con, job, attr->values[0].string.text)) < 0)
       {
-        cupsdDeleteJob(job);
+        cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE,
+                        "Aborting job because the start banner could not be "
+                        "copied.");
         return (NULL);
       }
 
@@ -2624,6 +2651,7 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
       cupsdSetPrinterState(printer, (ipp_pstate_t)(attr->values[0].integer), 0);
     }
   }
+
   if ((attr = ippFindAttribute(con->request, "printer-state-message",
                                IPP_TAG_TEXT)) != NULL)
   {
@@ -2656,9 +2684,10 @@ add_printer(cupsd_client_t  *con,        /* I - Client connection */
         continue;
 
       printer->reasons[printer->num_reasons] =
-          _cupsStrAlloc(attr->values[i].string.text);
+          _cupsStrRetain(attr->values[i].string.text);
+      printer->num_reasons ++;
 
-      if (!strcmp(printer->reasons[printer->num_reasons], "paused") &&
+      if (!strcmp(attr->values[i].string.text, "paused") &&
           printer->state != IPP_PRINTER_STOPPED)
       {
        cupsdLogMessage(CUPSD_LOG_INFO,
@@ -2666,8 +2695,6 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
                        printer->name, IPP_PRINTER_STOPPED, printer->state);
        cupsdStopPrinter(printer, 0);
       }
-
-      printer->num_reasons ++;
     }
 
     if (PrintcapFormat == PRINTCAP_PLIST)
@@ -2895,23 +2922,14 @@ add_printer(cupsd_client_t  *con,       /* I - Client connection */
 
   if (need_restart_job && printer->job)
   {
-    cupsd_job_t *job;
-
    /*
-    * Stop the current job and then restart it below...
+    * Restart the current job...
     */
 
-    job = (cupsd_job_t *)printer->job;
-
-    cupsdStopJob(job, 1);
-
-    job->state->values[0].integer = IPP_JOB_PENDING;
-    job->state_value              = IPP_JOB_PENDING;
+    cupsdSetJobState(printer->job, IPP_JOB_PENDING, CUPSD_JOB_FORCE,
+                     "Job restarted because the printer was modified.");
   }
 
-  if (need_restart_job)
-    cupsdCheckJobs();
-
   cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);
 
   if (modify)
@@ -2955,8 +2973,7 @@ add_printer_state_reasons(
 
   if (p->num_reasons == 0)
     ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
-                 "printer-state-reasons", NULL,
-                p->state == IPP_PRINTER_STOPPED ? "paused" : "none");
+                 "printer-state-reasons", NULL, "none");
   else
     ippAddStrings(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
                   "printer-state-reasons", p->num_reasons, NULL,
@@ -3401,8 +3418,10 @@ apple_register_profiles(
     * Use the default colorspace...
     */
 
-    num_profiles = 1 + ppd->colorspace != PPD_CS_GRAY;
-    
+    attr = ppdFindAttr(ppd, "DefaultColorSpace", NULL);
+
+    num_profiles = (attr && ppd->colorspace == PPD_CS_GRAY) ? 1 : 2;
+      
     if ((profiles = calloc(num_profiles, sizeof(CMDeviceProfileArray))) == NULL)
     {
       cupsdLogMessage(CUPSD_LOG_ERROR,
@@ -3430,14 +3449,15 @@ apple_register_profiles(
                             _ppdHashName("CMYK.."), "CMYK", "CMYK", NULL);
           break;
 
+      case PPD_CS_GRAY :
+          if (attr)
+           break;
+
       case PPD_CS_N :
           apple_init_profile(ppd, NULL, profiles->profiles + 1,
                             _ppdHashName("DeviceN.."), "DeviceN", "DeviceN",
                             NULL);
           break;
-
-      default :
-         break;
     }
   }
 
@@ -3941,21 +3961,28 @@ cancel_job(cupsd_client_t  *con,        /* I - Client connection */
       }
 
      /*
-      * See if the printer is currently printing a job...
+      * See if there are any pending jobs...
       */
 
-      if (printer->job)
-        jobid = ((cupsd_job_t *)printer->job)->id;
+      for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
+          job;
+          job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
+       if (job->state_value <= IPP_JOB_PROCESSING &&
+           !strcasecmp(job->dest, printer->name))
+         break;
+
+      if (job)
+       jobid = job->id;
       else
       {
        /*
-        * No, see if there are any pending jobs...
+        * No, try stopped jobs...
        */
 
-        for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
+       for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
             job;
             job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-         if (job->state_value <= IPP_JOB_PROCESSING &&
+         if (job->state_value == IPP_JOB_STOPPED &&
              !strcasecmp(job->dest, printer->name))
            break;
 
@@ -3963,21 +3990,9 @@ cancel_job(cupsd_client_t  *con, /* I - Client connection */
          jobid = job->id;
        else
        {
-         for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs);
-              job;
-              job = (cupsd_job_t *)cupsArrayNext(ActiveJobs))
-           if (job->state_value == IPP_JOB_STOPPED &&
-               !strcasecmp(job->dest, printer->name))
-             break;
-
-          if (job)
-           jobid = job->id;
-         else
-         {
-           send_ipp_status(con, IPP_NOT_POSSIBLE, _("No active jobs on %s!"),
-                           printer->name);
-           return;
-         }
+         send_ipp_status(con, IPP_NOT_POSSIBLE, _("No active jobs on %s!"),
+                         printer->name);
+         return;
        }
       }
     }
@@ -4076,7 +4091,9 @@ cancel_job(cupsd_client_t  *con,  /* I - Client connection */
   * Cancel the job and return...
   */
 
-  cupsdCancelJob(job, purge, IPP_JOB_CANCELED);
+  cupsdSetJobState(job, IPP_JOB_CANCELED, purge,
+                   purge ? "Job purged by \"%s\"" : "Job canceled by \"%s\"",
+                  username);
   cupsdCheckJobs();
 
   if (purge)
@@ -4366,16 +4383,7 @@ check_quotas(cupsd_client_t  *con,       /* I - Client connection */
                          "entry ignored", p->users[i]);
        }
 
-       if ((mbr_err = mbr_check_membership(usr_uuid, usr2_uuid,
-                                           &is_member)) != 0)
-       {
-         cupsdLogMessage(CUPSD_LOG_DEBUG,
-                         "check_quotas: User \"%s\" identity check failed "
-                         "(err=%d)", p->users[i], mbr_err);
-         is_member = 0;
-       }
-
-       if (is_member)
+       if (!uuid_compare(usr_uuid, usr2_uuid))
          break;
       }
 #else
@@ -4543,12 +4551,18 @@ copy_attribute(
           for (i = 0; i < attr->num_values; i ++)
            toattr->values[i].string.text = attr->values[i].string.text;
         }
-       else
+       else if (attr->value_tag & IPP_TAG_COPY)
        {
           for (i = 0; i < attr->num_values; i ++)
            toattr->values[i].string.text =
                _cupsStrAlloc(attr->values[i].string.text);
        }
+       else
+       {
+          for (i = 0; i < attr->num_values; i ++)
+           toattr->values[i].string.text =
+               _cupsStrRetain(attr->values[i].string.text);
+       }
         break;
 
     case IPP_TAG_DATE :
@@ -4594,7 +4608,7 @@ copy_attribute(
            toattr->values[i].string.text    = attr->values[i].string.text;
           }
         }
-       else
+       else if (attr->value_tag & IPP_TAG_COPY)
        {
           for (i = 0; i < attr->num_values; i ++)
          {
@@ -4609,6 +4623,21 @@ copy_attribute(
                _cupsStrAlloc(attr->values[i].string.text);
           }
         }
+       else
+       {
+          for (i = 0; i < attr->num_values; i ++)
+         {
+           if (!i)
+              toattr->values[i].string.charset =
+                 _cupsStrRetain(attr->values[i].string.charset);
+           else
+              toattr->values[i].string.charset =
+                 toattr->values[0].string.charset;
+
+           toattr->values[i].string.text =
+               _cupsStrRetain(attr->values[i].string.text);
+          }
+        }
         break;
 
     case IPP_TAG_BEGIN_COLLECTION :
@@ -4681,7 +4710,18 @@ copy_attrs(ipp_t        *to,             /* I - Destination request */
       continue;
 
     if (!ra || cupsArrayFind(ra, fromattr->name))
+    {
+     /*
+      * Don't send collection attributes by default to IPP/1.x clients
+      * since many do not support collections...
+      */
+
+      if (fromattr->value_tag == IPP_TAG_BEGIN_COLLECTION &&
+          !ra && to->request.status.version[0] == 1)
+       continue;
+
       copy_attribute(to, fromattr, quickcopy);
+    }
   }
 }
 
@@ -4872,7 +4912,10 @@ copy_banner(cupsd_client_t *con, /* I - Client connection */
          case IPP_TAG_INTEGER :
          case IPP_TAG_ENUM :
              if (!strncmp(s, "time-at-", 8))
-               cupsFilePuts(out, cupsdGetDateTime(attr->values[i].integer));
+             {
+               struct timeval tv = { attr->values[i].integer, 0 };
+               cupsFilePuts(out, cupsdGetDateTime(&tv, CUPSD_TIME_STANDARD));
+             }
              else
                cupsFilePrintf(out, "%d", attr->values[i].integer);
              break;
@@ -5072,7 +5115,7 @@ copy_model(cupsd_client_t *con,           /* I - Client connection */
                   "copy_model: Running \"cups-driverd cat %s\"...", from);
 
   if (!cupsdStartProcess(buffer, argv, envp, -1, temppipe[1], CGIPipes[1],
-                         -1, -1, 0, DefaultProfile, &temppid))
+                         -1, -1, 0, DefaultProfile, 0, &temppid))
   {
     close(tempfd);
     unlink(tempfile);
@@ -5409,7 +5452,7 @@ copy_printer_attrs(
         if ((p2_uri = ippFindAttribute(p2->attrs, "printer-uri-supported",
                                       IPP_TAG_URI)) != NULL)
           member_uris->values[i].string.text =
-             _cupsStrAlloc(p2_uri->values[0].string.text);
+             _cupsStrRetain(p2_uri->values[0].string.text);
         else
        {
          httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri,
@@ -6811,7 +6854,7 @@ get_jobs(cupsd_client_t  *con,            /* I - Client connection */
                   sizeof(scheme), username, sizeof(username), host,
                  sizeof(host), &port, resource, sizeof(resource));
 
-  if (!strcmp(resource, "/"))
+  if (!strcmp(resource, "/") || !strcmp(resource, "/jobs"))
   {
     dest    = NULL;
     dtype   = (cups_ptype_t)0;
@@ -6884,7 +6927,7 @@ get_jobs(cupsd_client_t  *con,            /* I - Client connection */
     completed = 0;
     list      = Jobs;
   }
-  else if (attr && !strcmp(attr->values[0].string.text, "printing"))
+  else if (attr && !strcmp(attr->values[0].string.text, "processing"))
   {
     completed = 0;
     list      = PrintingJobs;
@@ -6936,7 +6979,10 @@ get_jobs(cupsd_client_t  *con,           /* I - Client connection */
     * Filter out jobs that don't match...
     */
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: job->id = %d", job->id);
+    cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                    "get_jobs: job->id=%d, dest=\"%s\", username=\"%s\", "
+                   "state_value=%d, attrs=%p", job->id, job->dest,
+                   job->username, job->state_value, job->attrs);
 
     if (!job->dest || !job->username)
       cupsdLoadJob(job);
@@ -6959,7 +7005,11 @@ get_jobs(cupsd_client_t  *con,           /* I - Client connection */
     cupsdLoadJob(job);
 
     if (!job->attrs)
+    {
+      cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d!",
+                      job->id);
       continue;
+    }
 
     if (username[0] && strcasecmp(username, job->username))
       continue;
@@ -6969,11 +7019,11 @@ get_jobs(cupsd_client_t  *con,          /* I - Client connection */
 
     count ++;
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count = %d", count);
-
     copy_job_attrs(con, job, ra);
   }
 
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count=%d", count);
+
   cupsArrayDelete(ra);
 
   con->response->request.status.status_code = IPP_OK;
@@ -7335,6 +7385,10 @@ get_ppds(cupsd_client_t *con)            /* I - Client connection */
   type         = ippFindAttribute(con->request, "ppd-type", IPP_TAG_KEYWORD);
   requested    = ippFindAttribute(con->request, "requested-attributes",
                                   IPP_TAG_KEYWORD);
+  exclude      = ippFindAttribute(con->request, "exclude-schemes",
+                                  IPP_TAG_NAME);
+  include      = ippFindAttribute(con->request, "include-schemes",
+                                  IPP_TAG_NAME);
 
   if (requested)
     url_encode_attr(requested, requested_str, sizeof(requested_str));
@@ -7487,6 +7541,61 @@ get_printer_attrs(cupsd_client_t  *con,  /* I - Client connection */
 }
 
 
+/*
+ * 'get_printer_supported()' - Get printer supported values.
+ */
+
+static void
+get_printer_supported(
+    cupsd_client_t  *con,              /* I - Client connection */
+    ipp_attribute_t *uri)              /* I - Printer URI */
+{
+  http_status_t                status;         /* Policy status */
+  cups_ptype_t         dtype;          /* Destination type (printer/class) */
+  cupsd_printer_t      *printer;       /* Printer/class */
+
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printer_supported(%p[%d], %s)", con,
+                  con->http.fd, uri->values[0].string.text);
+
+ /*
+  * Is the destination valid?
+  */
+
+  if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer))
+  {
+   /*
+    * Bad URI...
+    */
+
+    send_ipp_status(con, IPP_NOT_FOUND,
+                    _("The printer or class was not found."));
+    return;
+  }
+
+ /*
+  * Check policy...
+  */
+
+  if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK)
+  {
+    send_http_error(con, status, printer);
+    return;
+  }
+
+ /*
+  * Return a list of attributes that can be set via Set-Printer-Attributes.
+  */
+
+  ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ADMINDEFINE,
+                "printer-info", 0);
+  ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ADMINDEFINE,
+                "printer-location", 0);
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'get_printers()' - Get a list of printers or classes.
  */
@@ -7873,8 +7982,8 @@ static void
 hold_job(cupsd_client_t  *con,         /* I - Client connection */
          ipp_attribute_t *uri)         /* I - Job or Printer URI */
 {
-  ipp_attribute_t *attr,               /* Current job-hold-until */
-               *newattr;               /* New job-hold-until */
+  ipp_attribute_t *attr;               /* Current job-hold-until */
+  const char   *when;                  /* New value */
   int          jobid;                  /* Job ID */
   char         scheme[HTTP_MAX_URI],   /* Method portion of URI */
                username[HTTP_MAX_URI], /* Username portion of URI */
@@ -7960,50 +8069,23 @@ hold_job(cupsd_client_t  *con,          /* I - Client connection */
   * Hold the job and return...
   */
 
-  cupsdHoldJob(job);
-
-  cupsdAddEvent(CUPSD_EVENT_JOB_STATE, cupsdFindDest(job->dest), job,
-                "Job held by user.");
-
-  if ((newattr = ippFindAttribute(con->request, "job-hold-until",
-                                  IPP_TAG_KEYWORD)) == NULL)
-    newattr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME);
-
-  if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
-                               IPP_TAG_KEYWORD)) == NULL)
-    attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+  if ((attr = ippFindAttribute(con->request, "job-hold-until",
+                              IPP_TAG_KEYWORD)) == NULL)
+    attr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME);
 
   if (attr)
   {
-   /*
-    * Free the old hold value and copy the new one over...
-    */
-
-    _cupsStrFree(attr->values[0].string.text);
-
-    if (newattr)
-    {
-      attr->value_tag = newattr->value_tag;
-      attr->values[0].string.text =
-          _cupsStrAlloc(newattr->values[0].string.text);
-    }
-    else
-    {
-      attr->value_tag = IPP_TAG_KEYWORD;
-      attr->values[0].string.text = _cupsStrAlloc("indefinite");
-    }
-
-   /*
-    * Hold job until specified time...
-    */
-
-    cupsdSetJobHoldUntil(job, attr->values[0].string.text);
+    when = attr->values[0].string.text;
 
     cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, cupsdFindDest(job->dest), job,
-                  "Job job-hold-until value changed by user.");
+                 "Job job-hold-until value changed by user.");
   }
+  else
+    when = "indefinite";
 
-  cupsdLogJob(job, CUPSD_LOG_INFO, "Held by \"%s\".", username);
+  cupsdSetJobHoldUntil(job, when, 1);
+  cupsdSetJobState(job, IPP_JOB_HELD, CUPSD_JOB_DEFAULT, "Job held by \"%s\".",
+                   username);
 
   con->response->request.status.status_code = IPP_OK;
 }
@@ -9225,12 +9307,12 @@ restart_job(cupsd_client_t  *con,       /* I - Client connection */
 {
   ipp_attribute_t *attr;               /* Current attribute */
   int          jobid;                  /* Job ID */
+  cupsd_job_t  *job;                   /* Job information */
   char         scheme[HTTP_MAX_URI],   /* Method portion of URI */
                username[HTTP_MAX_URI], /* Username portion of URI */
                host[HTTP_MAX_URI],     /* Host portion of URI */
                resource[HTTP_MAX_URI]; /* Resource portion of URI */
   int          port;                   /* Port portion of URI */
-  cupsd_job_t  *job;                   /* Job information */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "restart_job(%p[%d], %s)", con,
@@ -9338,10 +9420,36 @@ restart_job(cupsd_client_t  *con,       /* I - Client connection */
   }
 
  /*
-  * Restart the job and return...
+  * See if the job-hold-until attribute is specified...
   */
 
-  cupsdRestartJob(job);
+  if ((attr = ippFindAttribute(con->request, "job-hold-until",
+                               IPP_TAG_KEYWORD)) == NULL)
+    attr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME);
+
+  if (attr && strcmp(attr->values[0].string.text, "no-hold"))
+  {
+   /*
+    * Return the job to a held state...
+    */
+
+    cupsdLogJob(job, CUPSD_LOG_DEBUG,
+               "Restarted by \"%s\" with job-hold-until=%s.",
+                username, attr->values[0].string.text);
+    cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);
+
+    cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE,
+                  NULL, job, "Job restarted by user with job-hold-until=%s",
+                 attr->values[0].string.text);
+  }
+  else
+  {
+   /*
+    * Restart the job...
+    */
+
+    cupsdRestartJob(job);
+  }
 
   cupsdLogJob(job, CUPSD_LOG_INFO, "Restarted by \"%s\".", username);
 
@@ -9959,7 +10067,7 @@ send_document(cupsd_client_t  *con,      /* I - Client connection */
     {
       job->state->values[0].integer = IPP_JOB_HELD;
       job->state_value              = IPP_JOB_HELD;
-      job->hold_until               = time(NULL) + 60;
+      job->hold_until               = time(NULL) + MultipleOperationTimeout;
       job->dirty                    = 1;
 
       cupsdMarkDirty(CUPSD_DIRTY_JOBS);
@@ -10423,11 +10531,9 @@ set_job_attrs(cupsd_client_t  *con,    /* I - Client connection */
              {
                cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d",
                            attr->values[0].integer);
-
-               job->state->values[0].integer = attr->values[0].integer;
-               job->state_value = (ipp_jstate_t)attr->values[0].integer;
-
-                event |= CUPSD_EVENT_JOB_STATE;
+                cupsdSetJobState(job, attr->values[0].integer,
+                                CUPSD_JOB_DEFAULT,
+                                "Job state changed by \"%s\"", username);
                check_jobs = 1;
              }
              break;
@@ -10455,8 +10561,9 @@ set_job_attrs(cupsd_client_t  *con,     /* I - Client connection */
              {
                cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d",
                            attr->values[0].integer);
-                cupsdCancelJob(job, 0, (ipp_jstate_t)attr->values[0].integer);
-
+                cupsdSetJobState(job, (ipp_jstate_t)attr->values[0].integer,
+                                CUPSD_JOB_DEFAULT,
+                                "Job state changed by \"%s\"", username);
                 check_jobs = 1;
              }
              break;
@@ -10496,15 +10603,18 @@ set_job_attrs(cupsd_client_t  *con,   /* I - Client connection */
       {
         cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-hold-until to %s",
                    attr->values[0].string.text);
-        cupsdSetJobHoldUntil(job, attr->values[0].string.text);
+        cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0);
 
        if (!strcmp(attr->values[0].string.text, "no-hold"))
+       {
          cupsdReleaseJob(job);
+          check_jobs = 1;
+       }
        else
-         cupsdHoldJob(job);
+         cupsdSetJobState(job, IPP_JOB_HELD, CUPSD_JOB_DEFAULT,
+                          "Job held by \"%s\".", username);
 
-        check_jobs = 1;
-        event      |= CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE;
+        event |= CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE;
       }
     }
     else if (attr->value_tag == IPP_TAG_DELETEATTR)
@@ -10575,6 +10685,89 @@ set_job_attrs(cupsd_client_t  *con,    /* I - Client connection */
 }
 
 
+/*
+ * 'set_printer_attrs()' - Set printer attributes.
+ */
+
+static void
+set_printer_attrs(cupsd_client_t  *con,        /* I - Client connection */
+                  ipp_attribute_t *uri)        /* I - Printer */
+{
+  http_status_t                status;         /* Policy status */
+  cups_ptype_t         dtype;          /* Destination type (printer/class) */
+  cupsd_printer_t      *printer;       /* Printer/class */
+  ipp_attribute_t      *attr;          /* Printer attribute */
+  int                  changed = 0;    /* Was anything changed? */
+
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_printer_attrs(%p[%d], %s)", con,
+                  con->http.fd, uri->values[0].string.text);
+
+ /*
+  * Is the destination valid?
+  */
+
+  if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer))
+  {
+   /*
+    * Bad URI...
+    */
+
+    send_ipp_status(con, IPP_NOT_FOUND,
+                    _("The printer or class was not found."));
+    return;
+  }
+
+ /*
+  * Check policy...
+  */
+
+  if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK)
+  {
+    send_http_error(con, status, printer);
+    return;
+  }
+
+ /*
+  * Return a list of attributes that can be set via Set-Printer-Attributes.
+  */
+
+  if ((attr = ippFindAttribute(con->request, "printer-location",
+                               IPP_TAG_TEXT)) != NULL)
+  {
+    cupsdSetString(&printer->location, attr->values[0].string.text);
+    changed = 1;
+  }
+
+  if ((attr = ippFindAttribute(con->request, "printer-info",
+                               IPP_TAG_TEXT)) != NULL)
+  {
+    cupsdSetString(&printer->info, attr->values[0].string.text);
+    changed = 1;
+  }
+
+ /*
+  * Update the printer attributes and return...
+  */
+
+  if (changed)
+  {
+    cupsdSetPrinterAttrs(printer);
+    cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_CONFIG, printer, NULL,
+                  "Printer \"%s\" description or location changed by \"%s\".",
+                 printer->name, get_username(con));
+
+    cupsdLogMessage(CUPSD_LOG_INFO,
+                    "Printer \"%s\" description or location changed by \"%s\".",
+                    printer->name, get_username(con));
+  }
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'set_printer_defaults()' - Set printer default options from a request.
  */