]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/ipp.c
Merge changes from CUPS 1.4svn-r8722 (tentative 1.4.0 GM)
[thirdparty/cups.git] / scheduler / ipp.c
index 9bb2f0e89f88f1c17474e4aa8ea9714e9e3da172..7ca9537de987e8a49d18acb6a0a59b0b2c76e1d4 100644 (file)
 #include "cupsd.h"
 #include <cups/ppd-private.h>
 
-#ifdef HAVE_LIBPAPER
-#  include <paper.h>
-#endif /* HAVE_LIBPAPER */
-
 #ifdef __APPLE__
 #  include <ApplicationServices/ApplicationServices.h>
 #  include <CoreFoundation/CoreFoundation.h>
@@ -299,6 +295,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,
@@ -346,7 +356,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
@@ -355,7 +366,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
@@ -1178,6 +1190,12 @@ add_class(cupsd_client_t  *con,          /* I - Client connection */
                        _("The printer or class was not found."));
        return;
       }
+      else if (dtype & CUPS_PRINTER_CLASS)
+      {
+        send_ipp_status(con, IPP_BAD_REQUEST,
+                       _("Nested classes are not allowed!"));
+        return;
+      }
 
      /*
       * Add it to the class...
@@ -1202,23 +1220,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)
@@ -1285,7 +1294,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,
@@ -1359,9 +1369,9 @@ add_job(cupsd_client_t  *con,             /* I - Client connection */
     send_http_error(con, status, printer);
     return (NULL);
   }
-  else if (printer->num_auth_info_required > 0 &&
-           strcmp(printer->auth_info_required[0], "none") &&
-           !con->username[0] && !auth_info)
+  else if (printer->num_auth_info_required == 1 &&
+           !strcmp(printer->auth_info_required[0], "negotiate") &&
+           !con->username[0])
   {
     send_http_error(con, HTTP_UNAUTHORIZED, printer);
     return (NULL);
@@ -1724,7 +1734,7 @@ 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;
@@ -1882,7 +1892,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);
       }
 
@@ -2641,6 +2653,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)
   {
@@ -2674,8 +2687,9 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
 
       printer->reasons[printer->num_reasons] =
           _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,
@@ -2683,8 +2697,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)
@@ -2912,23 +2924,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)
@@ -2972,8 +2975,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,
@@ -3418,8 +3420,10 @@ apple_register_profiles(
     * Use the default colorspace...
     */
 
-    num_profiles = 2;
+    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,
@@ -3447,8 +3451,11 @@ apple_register_profiles(
                             _ppdHashName("CMYK.."), "CMYK", "CMYK", NULL);
           break;
 
+      case PPD_CS_GRAY :
+          if (attr)
+           break;
+
       case PPD_CS_N :
-      default :
           apple_init_profile(ppd, NULL, profiles->profiles + 1,
                             _ppdHashName("DeviceN.."), "DeviceN", "DeviceN",
                             NULL);
@@ -3725,7 +3732,8 @@ authenticate_job(cupsd_client_t  *con,    /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
@@ -3776,7 +3784,7 @@ cancel_all_jobs(cupsd_client_t  *con,     /* I - Client connection */
   int          port;                   /* Port portion of URI */
   ipp_attribute_t *attr;               /* Attribute in request */
   const char   *username;              /* Username */
-  int          purge;                  /* Purge? */
+  cupsd_jobaction_t purge;             /* Purge? */
   cupsd_printer_t *printer;            /* Printer */
 
 
@@ -3822,9 +3830,9 @@ cancel_all_jobs(cupsd_client_t  *con,     /* I - Client connection */
 
   if ((attr = ippFindAttribute(con->request, "purge-jobs",
                                IPP_TAG_BOOLEAN)) != NULL)
-    purge = attr->values[0].boolean;
+    purge = attr->values[0].boolean ? CUPSD_JOB_PURGE : CUPSD_JOB_DEFAULT;
   else
-    purge = 1;
+    purge = CUPSD_JOB_PURGE;
 
  /*
   * And if the destination is valid...
@@ -3866,7 +3874,8 @@ cancel_all_jobs(cupsd_client_t  *con,     /* I - Client connection */
     cupsdCancelJobs(NULL, username, purge);
 
     cupsdLogMessage(CUPSD_LOG_INFO, "All jobs were %s by \"%s\".",
-                    purge ? "purged" : "canceled", get_username(con));
+                    purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
+                   get_username(con));
   }
   else
   {
@@ -3888,7 +3897,8 @@ cancel_all_jobs(cupsd_client_t  *con,     /* I - Client connection */
     cupsdCancelJobs(printer->name, username, purge);
 
     cupsdLogMessage(CUPSD_LOG_INFO, "All jobs on \"%s\" were %s by \"%s\".",
-                    printer->name, purge ? "purged" : "canceled",
+                    printer->name,
+                   purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
                    get_username(con));
   }
 
@@ -3914,7 +3924,7 @@ cancel_job(cupsd_client_t  *con,  /* I - Client connection */
   cupsd_job_t  *job;                   /* Job information */
   cups_ptype_t dtype;                  /* Destination type (printer/class) */
   cupsd_printer_t *printer;            /* Printer data */
-  int          purge;                  /* Purge the job? */
+  cupsd_jobaction_t purge;             /* Purge the job? */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_job(%p[%d], %s)", con,
@@ -3956,21 +3966,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;
 
@@ -3978,21 +3995,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;
        }
       }
     }
@@ -4028,9 +4033,9 @@ cancel_job(cupsd_client_t  *con,  /* I - Client connection */
 
   if ((attr = ippFindAttribute(con->request, "purge-job",
                                IPP_TAG_BOOLEAN)) != NULL)
-    purge = attr->values[0].boolean;
+    purge = attr->values[0].boolean ? CUPSD_JOB_PURGE : CUPSD_JOB_DEFAULT;
   else
-    purge = 0;
+    purge = CUPSD_JOB_DEFAULT;
 
  /*
   * See if the job exists...
@@ -4052,7 +4057,8 @@ cancel_job(cupsd_client_t  *con,  /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
@@ -4061,7 +4067,7 @@ cancel_job(cupsd_client_t  *con,  /* I - Client connection */
   * we can't cancel...
   */
 
-  if (job->state_value >= IPP_JOB_CANCELED && !purge)
+  if (job->state_value >= IPP_JOB_CANCELED && purge != CUPSD_JOB_PURGE)
   {
     switch (job->state_value)
     {
@@ -4091,10 +4097,13 @@ 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 == CUPSD_JOB_PURGE ? "Job purged by \"%s\"" :
+                                             "Job canceled by \"%s\"",
+                  username);
   cupsdCheckJobs();
 
-  if (purge)
+  if (purge == CUPSD_JOB_PURGE)
     cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Purged by \"%s\".", jobid,
                    username);
   else
@@ -5113,7 +5122,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, NULL, &temppid))
   {
     close(tempfd);
     unlink(tempfile);
@@ -5494,6 +5503,25 @@ copy_printer_attrs(
     ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME,
                 "printer-error-policy", NULL, printer->error_policy);
 
+  if (!ra || cupsArrayFind(ra, "printer-error-policy-supported"))
+  {
+    static const char * const errors[] =/* printer-error-policy-supported values */
+                 {
+                   "abort-job",
+                   "retry-current-job",
+                   "retry-job",
+                   "stop-printer"
+                 };
+
+    if (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS))
+      ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
+                   "printer-error-policy-supported", NULL, "retry-current-job");
+    else
+      ippAddStrings(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
+                   "printer-error-policy-supported",
+                   sizeof(errors) / sizeof(errors[0]), NULL, errors);
+  }
+
   if (!ra || cupsArrayFind(ra, "printer-is-accepting-jobs"))
     ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-accepting-jobs",
                   printer->accepting);
@@ -6852,7 +6880,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;
@@ -7613,6 +7641,7 @@ get_printers(cupsd_client_t *con, /* I - Client connection */
   const char   *username;              /* Current user */
   char         *first_printer_name;    /* first-printer-name attribute */
   cups_array_t *ra;                    /* Requested attributes array */
+  int          local;                  /* Local connection? */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printers(%p[%d], %x)", con,
@@ -7670,6 +7699,8 @@ get_printers(cupsd_client_t *con, /* I - Client connection */
   else
     printer_mask = 0;
 
+  local = httpAddrLocalhost(&(con->clientaddr));
+
   if ((attr = ippFindAttribute(con->request, "printer-location",
                                IPP_TAG_TEXT)) != NULL)
     location = attr->values[0].string.text;
@@ -7702,6 +7733,9 @@ get_printers(cupsd_client_t *con, /* I - Client connection */
        count < limit && printer;
        printer = (cupsd_printer_t *)cupsArrayNext(Printers))
   {
+    if (!local && !printer->shared)
+      continue;
+
     if ((!type || (printer->type & CUPS_PRINTER_CLASS) == type) &&
         (printer->type & printer_mask) == printer_type &&
        (!location ||
@@ -7721,8 +7755,7 @@ get_printers(cupsd_client_t *con, /* I - Client connection */
       * access...
       */
 
-      if (!(printer->type & CUPS_PRINTER_AUTHENTICATED) &&
-          printer->num_users && username && !user_allowed(printer, username))
+      if (printer->num_users && username && !user_allowed(printer, username))
         continue;
 
      /*
@@ -7980,8 +8013,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 */
@@ -8059,7 +8092,8 @@ hold_job(cupsd_client_t  *con,            /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                   cupsdFindDest(job->dest));
     return;
   }
 
@@ -8067,50 +8101,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 =
-          _cupsStrRetain(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;
 }
@@ -8383,7 +8390,8 @@ move_job(cupsd_client_t  *con,            /* I - Client connection */
 
     if (!validate_user(job, con, job->username, username, sizeof(username)))
     {
-      send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+      send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                      cupsdFindDest(job->dest));
       return;
     }
 
@@ -9197,7 +9205,8 @@ release_job(cupsd_client_t  *con, /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
@@ -9332,12 +9341,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,
@@ -9440,15 +9449,43 @@ restart_job(cupsd_client_t  *con,       /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
  /*
-  * 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);
+    cupsdCheckJobs();
+  }
 
   cupsdLogJob(job, CUPSD_LOG_INFO, "Restarted by \"%s\".", username);
 
@@ -9581,7 +9618,12 @@ save_auth_info(
   cupsFileClose(fp);
 
 #if defined(HAVE_GSSAPI) && defined(HAVE_KRB5_H)
-  if (con->gss_have_creds)
+#  ifdef HAVE_KRB5_IPC_CLIENT_SET_TARGET_UID
+  if (con->have_gss &&
+      (con->http.hostaddr->addr.sa_family == AF_LOCAL || con->gss_creds))
+#  else
+  if (con->have_gss && con->gss_creds)
+#  endif /* HAVE_KRB5_IPC_CLIENT_SET_TARGET_UID */
     save_krb5_creds(con, job);
   else if (job->ccname)
     cupsdClearString(&(job->ccname));
@@ -9598,121 +9640,26 @@ static void
 save_krb5_creds(cupsd_client_t *con,   /* I - Client connection */
                 cupsd_job_t    *job)   /* I - Job */
 {
-#  if !defined(HAVE_KRB5_CC_NEW_UNIQUE) && !defined(HAVE_HEIMDAL)
-  cupsdLogMessage(CUPSD_LOG_INFO,
-                  "Sorry, your version of Kerberos does not support delegated "
-                 "credentials!");
-  return;
-
-#  else
-  krb5_error_code      error;          /* Kerberos error code */
-  OM_uint32            major_status,   /* Major status code */
-                       minor_status;   /* Minor status code */
-  krb5_principal       principal;      /* Kerberos principal */
-
-
-#   ifdef __APPLE__
  /*
-  * If the weak-linked GSSAPI/Kerberos library is not present, don't try
-  * to use it...
+  * Get the credentials...
   */
 
-  if (krb5_init_context == NULL)
-    return;
-#    endif /* __APPLE__ */
-
-  if (!KerberosInitialized)
-  {
-   /*
-    * Setup a Kerberos context for the scheduler to use...
-    */
-
-    KerberosInitialized = 1;
-
-    if (krb5_init_context(&KerberosContext))
-    {
-      KerberosContext = NULL;
-
-      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to initialize Kerberos context");
-      return;
-    }
-  }
-
- /*
-  * We MUST create a file-based cache because memory-based caches are
-  * only valid for the current process/address space.
-  *
-  * Due to various bugs/features in different versions of Kerberos, we
-  * need either the krb5_cc_new_unique() function or Heimdal's version
-  * of krb5_cc_gen_new() to create a new FILE: credential cache that
-  * can be passed to the backend.  These functions create a temporary
-  * file (typically in /tmp) containing the cached credentials, which
-  * are removed when we have successfully printed a job.
-  */
-
-#    ifdef HAVE_KRB5_CC_NEW_UNIQUE
-  if ((error = krb5_cc_new_unique(KerberosContext, "FILE", NULL,
-                                  &(job->ccache))) != 0)
-#    else /* HAVE_HEIMDAL */
-  if ((error = krb5_cc_gen_new(KerberosContext, &krb5_fcc_ops,
-                               &(job->ccache))) != 0)
-#    endif /* HAVE_KRB5_CC_NEW_UNIQUE */
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to create new credentials cache (%d/%s)",
-                    error, strerror(errno));
-    job->ccache = NULL;
-    return;
-  }
-
-  if ((error = krb5_parse_name(KerberosContext, con->username, &principal)) != 0)
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to parse kerberos username (%d/%s)",
-                    error, strerror(errno));
-    krb5_cc_destroy(KerberosContext, job->ccache);
-    job->ccache = NULL;
-    return;
-  }
-
-  if ((error = krb5_cc_initialize(KerberosContext, job->ccache, principal)))
-  {
-    cupsdLogMessage(CUPSD_LOG_ERROR,
-                    "Unable to initialize credentials cache (%d/%s)", error,
-                   strerror(errno));
-    krb5_cc_destroy(KerberosContext, job->ccache);
-    krb5_free_principal(KerberosContext, principal);
-    job->ccache = NULL;
-    return;
-  }
-
-  krb5_free_principal(KerberosContext, principal);
-
- /*
-  * Copy the user's credentials to the new cache file...
-  */
-
-  major_status = gss_krb5_copy_ccache(&minor_status, con->gss_delegated_cred,
-                                     job->ccache);
-
-  if (GSS_ERROR(major_status))
-  {
-    cupsdLogGSSMessage(CUPSD_LOG_ERROR, major_status, minor_status,
-                       "Unable to import client credentials cache");
-    krb5_cc_destroy(KerberosContext, job->ccache);
-    job->ccache = NULL;
-    return;
-  }
+  job->ccache = cupsdCopyKrb5Creds(con);
 
  /*
   * Add the KRB5CCNAME environment variable to the job so that the
   * backend can use the credentials when printing.
   */
 
-  cupsdSetStringf(&(job->ccname), "KRB5CCNAME=FILE:%s",
-                  krb5_cc_get_name(KerberosContext, job->ccache));
+  if (job->ccache)
+  {
+    cupsdSetStringf(&(job->ccname), "KRB5CCNAME=FILE:%s",
+                   krb5_cc_get_name(KerberosContext, job->ccache));
 
-  cupsdLogJob(job, CUPSD_LOG_DEBUG2, "save_krb5_creds: %s", job->ccname);
-#  endif /* HAVE_KRB5_CC_NEW_UNIQUE || HAVE_HEIMDAL */
+    cupsdLogJob(job, CUPSD_LOG_DEBUG2, "save_krb5_creds: %s", job->ccname);
+  }
+  else
+    cupsdClearString(&(job->ccname));
 }
 #endif /* HAVE_GSSAPI && HAVE_KRB5_H */
 
@@ -9827,7 +9774,8 @@ send_document(cupsd_client_t  *con,       /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
@@ -10415,7 +10363,8 @@ set_job_attrs(cupsd_client_t  *con,     /* I - Client connection */
 
   if (!validate_user(job, con, job->username, username, sizeof(username)))
   {
-    send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest));
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
     return;
   }
 
@@ -10530,11 +10479,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;
@@ -10562,8 +10509,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;
@@ -10603,15 +10551,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)
@@ -10694,7 +10645,7 @@ set_printer_attrs(cupsd_client_t  *con, /* I - Client connection */
   cups_ptype_t         dtype;          /* Destination type (printer/class) */
   cupsd_printer_t      *printer;       /* Printer/class */
   ipp_attribute_t      *attr;          /* Printer attribute */
-  int                  changed;        /* Was anything changed? */
+  int                  changed = 0;    /* Was anything changed? */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_printer_attrs(%p[%d], %s)", con,
@@ -10906,10 +10857,11 @@ set_printer_defaults(
       if (attr->value_tag != IPP_TAG_NAME && attr->value_tag != IPP_TAG_KEYWORD)
         continue;
 
-      if (strcmp(attr->values[0].string.text, "abort-job") &&
-          strcmp(attr->values[0].string.text, "retry-current-job") &&
-          strcmp(attr->values[0].string.text, "retry-job") &&
-          strcmp(attr->values[0].string.text, "stop-printer"))
+      if (strcmp(attr->values[0].string.text, "retry-current-job") &&
+          ((printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)) ||
+          (strcmp(attr->values[0].string.text, "abort-job") &&
+           strcmp(attr->values[0].string.text, "retry-job") &&
+           strcmp(attr->values[0].string.text, "stop-printer"))))
       {
        send_ipp_status(con, IPP_NOT_POSSIBLE,
                        _("Unknown printer-error-policy \"%s\"."),