Flaw in reading chunked HTTP responses (causes "Unable to get printer status" error) #4065

Closed
michaelrsweet opened this Issue Apr 29, 2012 · 15 comments

Projects

None yet

1 participant

@michaelrsweet
Collaborator

Version: 1.5.2
CUPS.org User: ef

There is a flaw in the logic for reading chunked HTTP responses in _httpPeek() and httpRead2(): in case no-one tries to read past the end of chunked data (which is the common case with IPP because the end of data is known from the IPP), the zero-value chunk length marking the end of chunks (and the empty line thereafter) are never being consumed from the input buffer.

This causes that "0" line being misinterpreted (i.e. as an HTTP Status) upon the next request, causing all sorts of weird failures.

The most common effect known to me is this happening with the printer's response to a Print-Job command, causing the following Get-Job-Attributes to fail and the printer being disabled with an "Unable to get printer status" error message.

The attached patch fixes the problem by reading the size of the next chunk in advance at the end of httpRead2() and storing it in a new chunk_len field of the http structure. It also factors out common code for reading the chunk length from both _httpPeek() and httpRead2() into _httpChunkLen() since it's needed twice in httpRead2() anyway.
[The size of the dummy string to consume the empty line at the end of the chunk actually has to be 2 since httpGets() refuses to read even an empty line into a one-length-string. I didn't try to fix that.]

@michaelrsweet
Collaborator

CUPS.org User: mike

Please re-test with current trunk - I believe we have addressed this issue for 1.5.3 and trunk already.

@michaelrsweet
Collaborator

CUPS.org User: ef

It's somewhat difficult to me to test a trunk version.
It would be easier to be given a patch or be pointed to an SVN revision that you believe to fix that problem.
Additionally, I'd simply be curious to learn how you fixed it. I can't find any changes to httpRead2() that I could identify to have fixed it.

@michaelrsweet
Collaborator

CUPS.org User: mike

The current revision as of this morning is r10439. Use that.

Alternately you can try the current 1.6svn snapshot.

In any case, if we haven't read the final 0 chunk then we do so when a request is next submitted on that socket (in cupsSendRequest). Pre-reading the chunk length (as you have done so in your patch) is not going to happen because we don't want to block on that...

@michaelrsweet
Collaborator

CUPS.org User: ef

I was indeed also concerned about blocking on pre-reading the next chunk's length. It seemed preferable to me to discard the zero chunk length on the next action on the socket, however I was not sure what values of "next action" were possible.

I still cannot figure out how you solved the problem and really would like to learn how you did. You refer to cupsSendRequest() and probably the httpFlush() in there. But that will only be called if htt->state is HTTP_GET_SEND or HTTP_POST_SEND. However, at the end of httpRead2(), http->state will only be advanced from HTTP_POST_RECV to HTTP_POST_SEND if the encoding is NOT chunked.

If, for any reason, it's not feasable for you to provide me with a patch, point me at a code reference or give me an SVN commit number in which you fixed the issue, it may take quite some time for me to verify that you actually fixed it for our case. I cannot simply deploy a 1.6-current version on our productive CUPS server. Meaning I have to set up a test CUPS server. Given I do have an (admittedly sub-optimal) solution, I may have a hard time convincing my employer to spend time on that.

@michaelrsweet
Collaborator

CUPS.org User: mike

Edgar,

The following code in cupsSendRequest makes sure that we have read any prior data from the socket:

 /*
  * If the prior request was not flushed out, do so now...
  */

  if (http->state == HTTP_GET_SEND ||
      http->state == HTTP_POST_SEND)
  {
    DEBUG_puts("2cupsSendRequest: Flush prior response.");
    httpFlush(http);
  }

This works because http->state will still be HTTP_POST_SEND when receiving the chunked response from the printer. Remember that the state names are server-centric, so the state will be HTTP_POST_RECV when the client is sending the IPP request to the printer and HTTP_POST_SEND when the printer is sending the IPP response back to the client.

(yes, the choice of names is unfortunate, and if I had it to do all over again I'd use _REQUEST and _RESPONSE in the names instead...)

This particular code was added in r9878 (for 1.6.x) and r9879 (for 1.5.x), so the chunking fix is already present in 1.5.1/1.5.2. I have attached a "rollup" patch that updates the IPP backend to what will be shipping in CUPS 1.5.3.

@michaelrsweet
Collaborator

CUPS.org User: ef

Thanks for the detailed explanation.

However, now I'm totally confused about which version you want me to try. You say the chunking fix is present in 1.5.2, however it's exactly 1.5.2 that I reported to have problems with.

Luckily, I still have a CUPS_DEBUG_LEVEL=2 output of the failing IPP backend around. It shows NO call of httpFlush() in cupsSendRequest(), but that there is a reconnect going on between the Send-Job command and the following Get-Job-Attributes.

The problem, in my opinion, is that httpReconnect() fails to clear http->used (and maybe some other fields that only got initialized by the calloc() in _httpCreate()). So upon entering cupsSendRequest, http->state will be HTTP_WAITING (so no flushing will be done), yet http->used will still indicate data present in http->buffer.
So the next httpGets() will read that "0" line.

The fix may be as simple as clearing http->used in httpReconnect(), but a) there may be other fields to reset and b) this looks like too obvious a bug to me to have gone unnoticed so I'm afraid my analysis is wrong and the problem is still more subtle.

@michaelrsweet
Collaborator

CUPS.org User: mike

Edgar,

Can you attach your log file?

As for the patch, the libcups changes are in 1.5.2, but there are additional changes in the IPP backend (which handles the actual submission to the printer) which were added.

WRT httpReconnect not clearing the input buffer, you are right and that is a bug. Will attach a patch for that shortly that, used in conjunction with the rollup patch for the IPP backend fixes.

@michaelrsweet
Collaborator

CUPS.org User: ef

The log file is half a megabyte. Is that OK to attach to the STR?

Would it be possible to send you the log file in private? Otherwise, I would have to dig trough it first whether it contains any confidential information.

@michaelrsweet
Collaborator

CUPS.org User: mike

The size isn't an issue, however if you want to send to me in private feel free: msweet@apple.com.

@michaelrsweet
Collaborator

CUPS.org User: mike

OK, please try the attached patches and let me know if you continue to experience difficulties...

@michaelrsweet
Collaborator

CUPS.org User: ef

Yes, I applied your str4065.patch and it seems to work, thanks.

I think it would be fair to mention at least at this place that I did all this bug-hunting for my employer, Mathematisches Institut der Universität Bonn. After all, I spent some three working days on this problem.

@michaelrsweet
Collaborator

CUPS.org User: mike

Thanks for confirming things are fixed, and for spending so much time researching this bug. It's greatly appreciated and will help millions of people print!

@michaelrsweet
Collaborator

CUPS.org User: mike

Fixed in Subversion repository.

@michaelrsweet
Collaborator

"str4065-rollup.patch":

Index: backend/ipp.c

--- backend/ipp.c (revision 10232)
+++ backend/ipp.c (working copy)
@@ -3,7 +3,7 @@
*

  • IPP backend for CUPS.
  • * Copyright 2007-2011 by Apple Inc.
  • * Copyright 2007-2012 by Apple Inc.
  • Copyright 1997-2007 by Easy Software Products, all rights reserved.
  • These coded instructions, statements, and computer programs are the
    @@ -63,6 +63,7 @@
    int port, /* Port number /
    version, /
    IPP version /
    job_id; /
    Job ID for submitted job */

  • const char job_name; / Job name for submitted job /
    http_encryption_t encryption; /
    Use encryption? /
    ipp_jstate_t job_state; /
    Current job state /
    ipp_pstate_t printer_state; /
    Current printer state */
    @@ -82,12 +83,16 @@
    {
    "job-impressions-completed",
    "job-media-sheets-completed",

  • "job-name",

  • "job-originating-user-name",
    "job-state",
    "job-state-reasons"
    };
    static int job_canceled = 0;
    /* Job cancelled? */
    -static char *password = NULL;
    +static char username[256] = "",

  •               /\* Username for device URI */
    
  •       _password = NULL;
            /_ Password for device URI _/
    

    static int password_tries = 0;
    /_ Password tries /
    @@ -186,7 +191,6 @@
    const char *device_uri; /
    Device URI /
    char scheme[255], /
    Scheme in URI /
    hostname[1024], /
    Hostname */

  •   username[255],      /\* Username info _/
    

    resource[1024], /_ Resource info (printer name) /
    addrname[256], /
    Address name /
    *optptr, /
    Pointer to URI options /
    @@ -205,6 +209,7 @@
    int port; /
    Port number (not used) /
    char portname[255]; /
    Port name /
    char uri[HTTP_MAX_URI]; /
    Updated URI without user/pass */

  • char print_job_name[1024]; /* Update job-name for Print-Job /
    http_status_t http_status; /
    Status of HTTP request /
    ipp_status_t ipp_status; /
    Status of IPP request /
    http_t *http; /
    HTTP connection /
    @@ -231,7 +236,9 @@
    ipp_attribute_t *doc_handling_sup; /
    multiple-document-handling-supported /
    ipp_attribute_t *printer_state; /
    printer-state attribute /
    ipp_attribute_t *printer_accepting; /
    printer-is-accepting-jobs */

  • int validate_job; /* Does printer support Validate-Job? */

  • int create_job = 0, /* Does printer support Create-Job? */

  •   send_document = 0,  /\* Does printer support Send-Document? */
    
  •   validate_job = 0;   /* Does printer support Validate-Job? */
    

    int copies, /* Number of copies for job /
    copies_remaining; /
    Number of copies remaining /
    const char *content_type, /
    CONTENT_TYPE environment variable */
    @@ -604,7 +611,10 @@
    const char *ptr = getenv("AUTH_USERNAME");

    if (ptr)

  • {

  •  strlcpy(username, ptr, sizeof(username));
    

    cupsSetUser(ptr);

  • }

password = getenv("AUTH_PASSWORD");
}
@@ -789,7 +799,6 @@
supported        = NULL;
operations_sup   = NULL;
doc_handling_sup = NULL;
  • validate_job = 0;

do
{
@@ -834,7 +843,9 @@
fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n",
ippErrorString(ipp_status), cupsLastErrorString());

  • if (ipp_status > IPP_OK_CONFLICT)
  • if (ipp_status <= IPP_OK_CONFLICT)
  •  password_tries = 0;
    
  • else
    {
    fprintf(stderr, "DEBUG: Get-Printer-Attributes returned %s.\n",
    ippErrorString(ipp_status));
    @@ -891,16 +902,21 @@

return (CUPS_BACKEND_STOP);
}

  •  else if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN)
    
  •  else if (ipp_status == IPP_FORBIDDEN ||
    
  •           ipp_status == IPP_AUTHENTICATION_CANCELED)
    

    {

  • if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE),

  •        "Negotiate", 9))
    
  •    const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
    
  •                   /\* WWW-Authenticate field value */
    
  • if (!strncmp(www_auth, "Negotiate", 9))
    auth_info_required = "negotiate";

  •    else if (www_auth[0])
    
  •      auth_info_required = "username,password";
    

    fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
    return (CUPS_BACKEND_AUTH_REQUIRED);
    }

  •  else
    
  •  else if (ipp_status != IPP_NOT_AUTHORIZED)
    

    {
    _cupsLangPrintFilter(stderr, "ERROR",
    _("Unable to get printer status."));
    @@ -1042,12 +1058,22 @@
    "cups-ipp-missing-get-printer-attributes");

    for (i = 0; i < operations_sup->num_values; i ++)

  •  {
     if (operations_sup->values[i].integer == IPP_VALIDATE_JOB)
    
  • {
    validate_job = 1;

  • break;
    
  • }

  •    else if (operations_sup->values[i].integer == IPP_CREATE_JOB)
    
  • create_job = 1;
    
  •    else if (operations_sup->values[i].integer == IPP_SEND_DOCUMENT)
    
  • send_document = 1;
    
  •  }
    
  •  if (!send_document)
    
  •  {
    
  •    fputs("DEBUG: Printer supports Create-Job but not Send-Document.\n",
    
  •          stderr);
    
  •    create_job = 0;
    
  •  }
    
    • if (!validate_job)
      update_reasons(NULL, "+cups-ipp-conformance-failure-report,"
      "cups-ipp-missing-validate-job");
      @@ -1116,7 +1142,7 @@
      {
      copies_remaining = 1;
  • if (argc < 7 && !send_options)

  • if (argc < 7 && !_cups_strncasecmp(final_content_type, "image/", 6))
    copies = 1;
    }
    else
    @@ -1153,7 +1179,8 @@
    if (format_sup != NULL)
    {
    for (i = 0; i < format_sup->num_values; i ++)

  •  if (!_cups_strcasecmp(final_content_type, format_sup->values[i].string.text))
    
  •  if (!_cups_strcasecmp(final_content_type,
    
  •                        format_sup->values[i].string.text))
    

    {
    document_format = final_content_type;
    break;
    @@ -1163,7 +1190,7 @@
    {
    for (i = 0; i < format_sup->num_values; i ++)
    if (!_cups_strcasecmp("application/octet-stream",

  •               format_sup->values[i].string.text))
    
  •                     format_sup->values[i].string.text))
    

    {
    document_format = "application/octet-stream";
    break;
    @@ -1171,6 +1198,9 @@
    }
    }

  • fprintf(stderr, "DEBUG: final_content_type="%s", document_format="%s"\n",

  •      final_content_type, document_format ? document_format : "(null)");
    

    /*

    • If the printer does not support HTTP/1.1 (which IPP requires), copy stdin

    • to a temporary file so that we can do a HTTP/1.0 submission...
      @@ -1188,9 +1218,18 @@

      _cupsLangPrintFilter(stderr, "INFO", _("Copying print data."));

  • compatsize = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0,

  •                   backendNetworkSideCB);
    
  • if ((compatsize = write(fd, buffer, bytes)) < 0)

  • {

  •  perror("DEBUG: Unable to write temporary file");
    
  •  return (CUPS_BACKEND_FAILED);
    
  • }

  • if ((bytes = backendRunLoop(-1, fd, snmp_fd, &(addrlist->addr), 0, 0,

  •                   backendNetworkSideCB)) < 0)
    
  •  return (CUPS_BACKEND_FAILED);
    
  • compatsize += bytes;

close(fd);

compatfile = tmpfilename;
@@ -1220,6 +1259,17 @@
monitor.job_state = IPP_JOB_PENDING;
monitor.printer_state = IPP_PRINTER_IDLE;

  • if (create_job)
  • {
  • monitor.job_name = argv[3];
  • }
  • else
  • {
  • snprintf(print_job_name, sizeof(print_job_name), "%s - %s", argv[1],
  •         argv[3]);
    
  • monitor.job_name = print_job_name;
  • }

_cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor);

/*
@@ -1228,8 +1278,8 @@

while (!job_canceled && validate_job)
{

  • request = new_request(IPP_VALIDATE_JOB, version, uri, argv[2], argv[3],
  •                      num_options, options, compression,
    
  • request = new_request(IPP_VALIDATE_JOB, version, uri, argv[2],
  •                      monitor.job_name, num_options, options, compression,
          copies_sup ? copies : 1, document_format, pc,
          media_col_sup, doc_handling_sup);
    

@@ -1248,26 +1298,16 @@
_cupsLangPrintFilter(stderr, "INFO", _("The printer is busy."));
sleep(10);
}

  • else if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN ||
  • else if (ipp_status == IPP_FORBIDDEN ||
    ipp_status == IPP_AUTHENTICATION_CANCELED)
    {
  • /*
    
  •  \* Update auth-info-required as needed...
    
  •  */
    
  •  const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
    
  •               /* WWW-Authenticate field value */
    
  •  fprintf(stderr, "DEBUG: WWW-Authenticate=\"%s\"\n",
    

- httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE));

  • /*
    
  •  \* Normal authentication goes through the password callback, which sets
    
  •  \* auth_info_required to "username,password".  Kerberos goes directly
    
  •  \* through GSSAPI, so look for Negotiate in the WWW-Authenticate header
    
  •  \* here and set auth_info_required as needed...
    

- */

  •  if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE),
    
  •      "Negotiate", 9))
    
  •  if (!strncmp(www_auth, "Negotiate", 9))
    

    auth_info_required = "negotiate";

  •  else if (www_auth[0])
    
  • auth_info_required = "username,password";

    goto cleanup;
    }
    @@ -1306,16 +1346,17 @@
    if (job_canceled)
    break;

  • request = new_request(num_files > 1 ? IPP_CREATE_JOB : IPP_PRINT_JOB,

  •         version, uri, argv[2], argv[3], num_options, options,
    
  •         compression, copies_sup ? copies : 1, document_format,
    
  •         pc, media_col_sup, doc_handling_sup);
    
  • request = new_request((num_files > 1 || create_job) ? IPP_CREATE_JOB :

  •                                                      IPP_PRINT_JOB,
    
  •         version, uri, argv[2], monitor.job_name, num_options,
    
  •         options, compression, copies_sup ? copies : 1,
    
  •         document_format, pc, media_col_sup, doc_handling_sup);
    

    /*

    • Do the request...
      */
  • if (num_files > 1)

  • if (num_files > 1 || create_job)
    response = cupsDoRequest(http, request, resource);
    else
    {
    @@ -1333,7 +1374,13 @@
    if (http_status == HTTP_CONTINUE && request->state == IPP_DATA)
    {
    if (num_files == 1)

  • fd = open(files[0], O_RDONLY);
    
  •    {
    
  • if ((fd = open(files[0], O_RDONLY)) < 0)
    
  • {
    
  •   _cupsLangPrintError("ERROR", _("Unable to open print file"));
    
  •   return (CUPS_BACKEND_FAILED);
    
  • }
    
  • }
    else
    {
    fd = 0;
    @@ -1382,7 +1429,7 @@
    ipp_status = cupsLastError();

fprintf(stderr, "DEBUG: %s: %s (%s)\n",

  •        num_files > 1 ? "Create-Job" : "Print-Job",
    
  •        (num_files > 1 || create_job) ? "Create-Job" : "Print-Job",
         ippErrorString(ipp_status), cupsLastErrorString());
    

    if (ipp_status > IPP_OK_CONFLICT)
    @@ -1411,6 +1458,8 @@
    }
    else if (ipp_status == IPP_ERROR_JOB_CANCELED)
    goto cleanup;

  •  else if (ipp_status == IPP_NOT_AUTHORIZED)
    
  •    continue;
    

    else
    {
    /*
    @@ -1420,21 +1469,24 @@
    _cupsLangPrintFilter(stderr, "ERROR",
    _("Print file was not accepted."));

  • if (ipp_status == IPP_NOT_AUTHORIZED || ipp_status == IPP_FORBIDDEN)

  •    if (ipp_status == IPP_FORBIDDEN ||
    
  •        ipp_status == IPP_AUTHENTICATION_CANCELED)
    

    {

  • fprintf(stderr, "DEBUG: WWW-Authenticate=\"%s\"\n",
    
  •     httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE));
    
  • const char *www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
    
  •               /* WWW-Authenticate field value */
    
  •     /*
    
  • \* Normal authentication goes through the password callback, which sets
    
  • \* auth_info_required to "username,password".  Kerberos goes directly
    
  • \* through GSSAPI, so look for Negotiate in the WWW-Authenticate header
    
  • \* here and set auth_info_required as needed...
    
  • if (!strncmp(www_auth, "Negotiate", 9))
    
  •   auth_info_required = "negotiate";
    
  • else if (www_auth[0])
    
  •   auth_info_required = "username,password";
    
  • }

  • else if (ipp_status == IPP_REQUEST_VALUE)

  • {

  • /*

  • * Print file is too large, abort this job...
    

    */

  • if (!strncmp(httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE),
    
  •          "Negotiate", 9))
    
  •   auth_info_required = "negotiate";
    
  • goto cleanup;
    

    }
    else
    sleep(10);
    @@ -1461,19 +1513,21 @@
    }
    else
    {

  •  password_tries = 0;
    

    monitor.job_id = job_id = job_id_attr->values[0].integer;
    _cupsLangPrintFilter(stderr, "INFO",
    _("Print file accepted - job ID %d."), job_id);
    }

  • fprintf(stderr, "DEBUG: job-id=%d\n", job_id);
    ippDelete(response);

if (job_canceled)
break;

  • if (job_id && num_files > 1)

  • if (job_id && (num_files > 1 || create_job))
    {

  •  for (i = 0; i < num_files; i ++)
    
  •  for (i = 0; num_files == 0 || i < num_files; i ++)
    

    {
    /*

    • Check for side-channel requests...
      @@ -1499,21 +1553,41 @@
      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
      "requesting-user-name", NULL, argv[2]);
  •    if ((i + 1) == num_files)
    
  •    if ((i + 1) >= num_files)
    

    ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);

  •    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
    
  •            "document-format", NULL, content_type);
    
  • if (document_format)

  • ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
    
  •          "document-format", NULL, document_format);
    

    fprintf(stderr, "DEBUG: Sending file %d using chunking...\n", i + 1);
    http_status = cupsSendRequest(http, request, resource, 0);

  • if (http_status == HTTP_CONTINUE && request->state == IPP_DATA &&

  •   (fd = open(files[i], O_RDONLY)) >= 0)
    
  • if (http_status == HTTP_CONTINUE && request->state == IPP_DATA)
    {

  • while (!job_canceled &&
    
  • if (num_files == 0)
    
  • {
    
  •   fd          = 0;
    
  •   http_status = cupsWriteRequestData(http, buffer, bytes);
    
  • }
    
  • else
    
  • {
    
  •   if ((fd = open(files[i], O_RDONLY)) < 0)
    
  •   {
    
  •     _cupsLangPrintError("ERROR", _("Unable to open print file"));
    
  •     return (CUPS_BACKEND_FAILED);
    
  •   }
    
  • }
    
  • }

  • else

  • fd = -1;
    
  • if (fd >= 0)

  • {

  • while (!job_canceled && http_status == HTTP_CONTINUE &&
         (bytes = read(fd, buffer, sizeof(buffer))) > 0)
    

    {

  •   if (cupsWriteRequestData(http, buffer, bytes) != HTTP_CONTINUE)
    
  •   if ((http_status = cupsWriteRequestData(http, buffer, bytes))
    
  •           != HTTP_CONTINUE)
      break;
    else
    {
    

    @@ -1525,7 +1599,8 @@
    }
    }

  • close(fd);
    
  •      if (fd > 0)
    
  •   close(fd);
    

    }

    ippDelete(cupsGetResponse(http, resource));
    @@ -1542,6 +1617,13 @@
    _("Unable to add document to print job."));
    break;
    }

  • else

  • {

  • password_tries = 0;
    
  • if (num_files == 0 || fd < 0)
    
  •   break;
    
  • }
    }
    }

@@ -1554,6 +1636,14 @@
ipp_status == IPP_NOT_POSSIBLE ||
ipp_status == IPP_PRINTER_BUSY)
continue;

  • else if (ipp_status == IPP_REQUEST_VALUE)
  • {
  • /*
    
  •  \* Print file is too large, abort this job...
    
  •  */
    
  •  goto cleanup;
    
  • }
    else
    copies_remaining --;

@@ -1621,16 +1711,16 @@
fprintf(stderr, "DEBUG: Get-Job-Attributes: %s (%s)\n",
ippErrorString(ipp_status), cupsLastErrorString());

  •  if (ipp_status > IPP_OK_CONFLICT)
    
  •  if (ipp_status <= IPP_OK_CONFLICT)
    
  • password_tries = 0;
  •  else
    

    {
    if (ipp_status != IPP_SERVICE_UNAVAILABLE &&
    ipp_status != IPP_NOT_POSSIBLE &&
    ipp_status != IPP_PRINTER_BUSY)
    {
    ippDelete(response);

  •      _cupsLangPrintFilter(stderr, "ERROR",
    
  •              _("Unable to get print job status."));
    
  •      ipp_status = IPP_OK;
       break;
    
    }
    }
    @@ -1777,6 +1867,11 @@
    else if (ipp_status == IPP_DOCUMENT_FORMAT ||
    ipp_status == IPP_CONFLICT)
    return (CUPS_BACKEND_FAILED);
  • else if (ipp_status == IPP_REQUEST_VALUE)
  • {
  • _cupsLangPrintFilter(stderr, "ERROR", _("Print job too large."));
  • return (CUPS_BACKEND_CANCEL);
  • }
    else if (ipp_status > IPP_OK_CONFLICT && ipp_status != IPP_ERROR_JOB_CANCELED)
    return (CUPS_BACKEND_RETRY_CURRENT);
    else
    @@ -1879,6 +1974,9 @@
    fprintf(stderr, "DEBUG: Get-Printer-Attributes: %s (%s)\n",
    ippErrorString(cupsLastError()), cupsLastErrorString());
  • if (cupsLastError() <= IPP_OK_CONFLICT)
  • password_tries = 0;

/*

  • Return the printer-state value...
    /
    @@ -1972,6 +2070,11 @@
    ipp_attribute_t *attr; /
    Attribute in response /
    int delay, /
    Current delay /
    prev_delay; /
    Previous delay */
  • ipp_op_t job_op; /* Operation to use */
  • int job_id; /* Job ID */
  • const char job_name; / Job name */
  • ipp_jstate_t job_state; /* Job state */
  • const char job_user; / Job originating user name */

/*
@@ -1981,6 +2084,8 @@
http = _httpCreate(monitor->hostname, monitor->port, NULL, monitor->encryption,
AF_UNSPEC);
httpSetTimeout(http, 30.0, timeout_cb, NULL);

  • if (username[0])
  • cupsSetUser(username);
    cupsSetPasswordCB(password_cb);

/*
@@ -2006,47 +2111,99 @@
monitor->user,
monitor->version);

  •  if (monitor->job_id > 0)
    
  •  {
    
  •   /*
    
  •    \* Check the status of the job itself...
    
  • */

  • /*
    
  •  \* Check the status of the job itself...
    
  •  */
    
  • request = ippNewRequest(IPP_GET_JOB_ATTRIBUTES);

  • request->request.op.version[0] = monitor->version / 10;

  • request->request.op.version[1] = monitor->version % 10;

  •  job_op  = monitor->job_id > 0 ? IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS;
    
  •  request = ippNewRequest(job_op);
    
  •  request->request.op.version[0] = monitor->version / 10;
    
  •  request->request.op.version[1] = monitor->version % 10;
    
  • ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",

  •        NULL, monitor->uri);
    
  •  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
    
  •      NULL, monitor->uri);
    
  •  if (job_op == IPP_GET_JOB_ATTRIBUTES)
    

    ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",

  •             monitor->job_id);
    
  •         monitor->job_id);
    
  • if (monitor->user && monitor->user[0])

  • ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
    
  •          "requesting-user-name", NULL, monitor->user);
    
  •  if (monitor->user && monitor->user[0])
    
  • ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,

  •        "requesting-user-name", NULL, monitor->user);
    
  • ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,

  •         "requested-attributes",
    
  •         (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs);
    
  •  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
    
  •       "requested-attributes",
    
  •       (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs);
    
  •   /*
    
  • * Do the request...

  • */

  • /*
    
  •  \* Do the request...
    
  •  */
    
  • response = cupsDoRequest(http, request, monitor->resource);

  •  response = cupsDoRequest(http, request, monitor->resource);
    
  • fprintf(stderr, "DEBUG: Get-Job-Attributes: %s (%s)\n",

  •   ippErrorString(cupsLastError()), cupsLastErrorString());
    
  •  fprintf(stderr, "DEBUG: %s: %s (%s)\n", ippOpString(job_op),
    
  •     ippErrorString(cupsLastError()), cupsLastErrorString());
    
  •  if (cupsLastError() <= IPP_OK_CONFLICT)
    
  •    password_tries = 0;
    
  •  if (job_op == IPP_GET_JOB_ATTRIBUTES)
    
  •  {
    

    if ((attr = ippFindAttribute(response, "job-state",
    IPP_TAG_ENUM)) != NULL)
    monitor->job_state = (ipp_jstate_t)attr->values[0].integer;
    else
    monitor->job_state = IPP_JOB_COMPLETED;

  •  }
    
  •  else if (response)
    
  •  {
    
  •    for (attr = response->attrs; attr; attr = attr->next)
    
  •    {
    
  •      job_id    = 0;
    
  •      job_name  = NULL;
    
  •      job_state = IPP_JOB_PENDING;
    
  •      job_user  = NULL;
    
  • ippDelete(response);

  •      while (attr && attr->group_tag != IPP_TAG_JOB)
    
  •        attr = attr->next;
    
  •      if (!attr)
    
  •        break;
    
  •      while (attr && attr->group_tag == IPP_TAG_JOB)
    
  •      {
    
  •        if (!strcmp(attr->name, "job-id") &&
    
  •            attr->value_tag == IPP_TAG_INTEGER)
    
  •          job_id = attr->values[0].integer;
    
  •        else if (!strcmp(attr->name, "job-name") &&
    
  •        (attr->value_tag == IPP_TAG_NAME ||
    
  •         attr->value_tag == IPP_TAG_NAMELANG))
    
  •          job_name = attr->values[0].string.text;
    
  •        else if (!strcmp(attr->name, "job-state") &&
    
  •        attr->value_tag == IPP_TAG_ENUM)
    
  •          job_state = attr->values[0].integer;
    
  •        else if (!strcmp(attr->name, "job-originating-user-name") &&
    
  •        (attr->value_tag == IPP_TAG_NAME ||
    
  •         attr->value_tag == IPP_TAG_NAMELANG))
    
  •          job_user = attr->values[0].string.text;
    
  •        attr = attr->next;
    
  •      }
    
  •      if (job_id > 0 && job_name && !strcmp(job_name, monitor->job_name) &&
    
  •          job_user && monitor->user && !strcmp(job_user, monitor->user))
    
  •      {
    
  •        monitor->job_id    = job_id;
    
  •        monitor->job_state = job_state;
    
  •        break;
    
  •      }
    
  •      if (!attr)
    
  •        break;
    
  •    }
    

    }

  •  ippDelete(response);
    
    • /*
    • Disconnect from the printer - we'll reconnect on the next poll...
      */
      @@ -2064,6 +2221,15 @@
      }

    /*

  • * Cancel the job if necessary...

  • */

  • if (job_canceled && monitor->job_id > 0)
  • if (!httpReconnect(http))
  •  cancel_job(http, monitor->uri, monitor->job_id, monitor->resource,
    
  •             monitor->user, monitor->version);
    
  • /*
    • Cleanup and return...
      */

@@ -2088,7 +2254,7 @@
cups_option_t options, / I - Options to send /
const char *compression, /
I - compression value or NULL /
int copies, /
I - copies value or 0 */

  • const char format, / I - documet-format value or NULL */

  • const char format, / I - document-format value or NULL _/
    ppd_cache_t *pc, / I - PPD cache and mapping data /
    ipp_attribute_t *media_col_sup, /
    I - media-col-supported values /
    ipp_attribute_t *doc_handling_sup) /
    I - multiple-document-handling-supported values */
    @@ -2163,6 +2329,9 @@
    {
    if (pc)
    {

  •  int  num_finishings = 0, /\* Number of finishing values */
    
  •   finishings[10];     /* Finishing enum values */
    
    • /*
    • Send standard IPP attributes...
      */
      @@ -2284,6 +2453,7 @@
      }

      if (doc_handling_sup &&

  •      (!format || _cups_strncasecmp(format, "image/", 6)) &&
    

    (keyword = cupsGetOption("collate", num_options, options)) != NULL)
    {
    if (!_cups_strcasecmp(keyword, "true"))
    @@ -2299,6 +2469,43 @@
    break;
    }
    }
    +

  • /*
    
  •  \* Map finishing options...
    
  •  */
    
  •  num_finishings = _ppdCacheGetFinishingValues(pc, num_options, options,
    
  •                                               (int)(sizeof(finishings) /
    
  •                                                     sizeof(finishings[0])),
    
  •                                               finishings);
    
  •  if (num_finishings > 0)
    
  • ippAddIntegers(request, IPP_TAG_JOB, IPP_TAG_ENUM, "finishings",

  •          num_finishings, finishings);
    
  • /*
    
  •  \* Map FaxOut options...
    
  •  */
    
  •  if ((keyword = cupsGetOption("phone", num_options, options)) != NULL)
    
  •  {
    
  • ipp_t destination; / destination collection */

  • char tel_uri[1024]; /* tel: URI */

  •    destination = ippNew();
    
  •    httpAssembleURI(HTTP_URI_CODING_ALL, tel_uri, sizeof(tel_uri), "tel",
    
  •                    NULL, NULL, 0, keyword);
    
  •    ippAddString(destination, IPP_TAG_JOB, IPP_TAG_URI, "destination-uri",
    
  •                 NULL, tel_uri);
    
  • if ((keyword = cupsGetOption("faxPrefix", num_options,

  •                            options)) != NULL && *keyword)
    
  • ippAddString(destination, IPP_TAG_JOB, IPP_TAG_TEXT,
    
  •              "pre-dial-string", NULL, keyword);
    
  •    ippAddCollection(request, IPP_TAG_JOB, "destination-uris", destination);
    
  •    ippDelete(destination);
    
  •  }
    

    }
    else
    {
    @@ -2324,6 +2531,9 @@
    static const char * /* O - Password /
    password_cb(const char *prompt) /
    I - Prompt (not used) */
    {

  • fprintf(stderr, "DEBUG: password_cb(prompt="%s"), password=%p, "

  •      "password_tries=%d\n", prompt, password, password_tries);
    

    (void)prompt;

    /*
    @@ -2386,17 +2596,23 @@
    case IPP_TAG_TEXT :
    case IPP_TAG_NAME :
    case IPP_TAG_KEYWORD :

  •      *valptr++ = '\'';
       *valptr++ = '\"';
    

    for (attrptr = attr->values[i].string.text;
    *attrptr && valptr < (value + sizeof(value) - 10);
    attrptr ++)
    {

  •   if (*attrptr == '\' || *attrptr == '\"')
    
  •   if (*attrptr == '\' || *attrptr == '\"' || *attrptr == '\'')
    
  •   {
      *valptr++ = '\';
    
  •     *valptr++ = '\';
    
  •     *valptr++ = '\';
    
  •   }
    
    *valptr++ = *attrptr;
    

    }
    *valptr++ = '"';

  •      *valptr++ = '\'';
       break;
    

    default :

    Index: cups/auth.c

    --- cups/auth.c (revision 10232)
    +++ cups/auth.c (working copy)
    @@ -124,7 +124,8 @@
    const char method, / I - Request method ("GET", "POST", "PUT") /
    const char *resource) /
    I - Resource path */
    {

  • const char password; / Password string */

  • const char password, / Password string */

  •   *www_auth;      /* WWW-Authenticate header */
    

    char prompt[1024], /* Prompt for user /
    realm[HTTP_MAX_VALUE], /
    realm="xyz" string /
    nonce[HTTP_MAX_VALUE]; /
    nonce="xyz" string */
    @@ -179,9 +180,11 @@

    • Nope, see if we should retry the current username:password...
      */
  • www_auth = http->fields[HTTP_FIELD_WWW_AUTHENTICATE];

if ((http->digest_tries > 1 || !http->userpass[0]) &&

  •  (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Basic", 5) ||
    
  •   !strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Digest", 6)))
    
  •  (!_cups_strncasecmp(www_auth, "Basic", 5) ||
    
  •   !_cups_strncasecmp(www_auth, "Digest", 6)))
    

    {
    /*

    • Nope - get a new password from the user...
      @@ -197,8 +200,7 @@
      cupsUser(),
      http->hostname[0] == '/' ? "localhost" : http->hostname);
  • http->digest_tries = _cups_strncasecmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE],

  •                                  "Digest", 5) != 0;
    
  • http->digest_tries = _cups_strncasecmp(www_auth, "Digest", 6) != 0;
    http->userpass[0] = '\0';

if ((password = cupsGetPassword2(prompt, http, method, resource)) == NULL)
@@ -227,7 +229,7 @@
*/

#ifdef HAVE_GSSAPI

  • if (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Negotiate", 9))
  • if (!_cups_strncasecmp(www_auth, "Negotiate", 9))
    {
    /*
  • Kerberos authentication...
    @@ -241,7 +243,7 @@
    }
    else
    #endif /* HAVE_GSSAPI */
  • if (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Basic", 5))
  • if (!_cups_strncasecmp(www_auth, "Basic", 5))
    {
    /*
  • Basic authentication...
    @@ -254,7 +256,7 @@
    (int)strlen(http->userpass));
    httpSetAuthString(http, "Basic", encode);
    }
  • else if (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Digest", 6))
  • else if (!_cups_strncasecmp(www_auth, "Digest", 6))
    {
    /*
  • Digest authentication...
    @@ -277,7 +279,7 @@
    else
    {
    DEBUG_printf(("1cupsDoAuthentication: Unknown auth type: "%s"",
  •              http->fields[HTTP_FIELD_WWW_AUTHENTICATE]));
    
  •              www_auth));
    
    http->status = HTTP_AUTHORIZATION_CANCELED;
    return (-1);
    }
@michaelrsweet
Collaborator

"str4065.patch":

Index: cups/http.c

--- cups/http.c (revision 10444)
+++ cups/http.c (working copy)
@@ -2357,6 +2357,23 @@
}

/*

  • * Reset all state (except fields, which may be reused)...
  • */
  • http->state = HTTP_WAITING;
  • http->status = HTTP_CONTINUE;
  • http->version = HTTP_1_1;
  • http->keep_alive = HTTP_KEEPALIVE_OFF;
  • memset(&http->_hostaddr, 0, sizeof(http->_hostaddr));
  • http->data_encoding = HTTP_ENCODE_LENGTH;
  • http->_data_remaining = 0;
  • http->used = 0;
  • http->expect = 0;
  • http->data_remaining = 0;
  • http->hostaddr = NULL;
  • http->wused = 0;
  • /*
    • Connect to the server...
      */

@@ -2394,8 +2411,6 @@

http->hostaddr = &(addr->addr);
http->error = 0;

  • http->status = HTTP_CONTINUE;
  • http->state = HTTP_WAITING;

#ifdef HAVE_SSL
if (http->encryption == HTTP_ENCRYPT_ALWAYS)

@michaelrsweet michaelrsweet added this to the Stable milestone Mar 17, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment