1 /*
2 * IPP utilities for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2018 by Apple Inc.
6 * Copyright © 1997-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 */
11
12 /*
13 * Include necessary headers...
14 */
15
16 #include "cups-private.h"
17 #include "debug-internal.h"
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 #if defined(_WIN32) || defined(__EMX__)
21 # include <io.h>
22 #else
23 # include <unistd.h>
24 #endif /* _WIN32 || __EMX__ */
25 #ifndef O_BINARY
26 # define O_BINARY 0
27 #endif /* O_BINARY */
28 #ifdef _AIX
29 # define MSG_DONTWAIT MSG_NONBLOCK
30 #elif !defined(MSG_DONTWAIT)
31 # define MSG_DONTWAIT 0
32 #endif /* _AIX */
33
34
35 /*
36 * 'cupsDoFileRequest()' - Do an IPP request with a file.
37 *
38 * This function sends the IPP request and attached file to the specified
39 * server, retrying and authenticating as necessary. The request is freed with
40 * @link ippDelete@.
41 */
42
43 ipp_t * /* O - Response data */
cupsDoFileRequest(http_t * http,ipp_t * request,const char * resource,const char * filename)44 cupsDoFileRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
45 ipp_t *request, /* I - IPP request */
46 const char *resource, /* I - HTTP resource for POST */
47 const char *filename) /* I - File to send or @code NULL@ for none */
48 {
49 ipp_t *response; /* IPP response data */
50 int infile; /* Input file */
51
52
53 DEBUG_printf(("cupsDoFileRequest(http=%p, request=%p(%s), resource=\"%s\", filename=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, filename));
54
55 if (filename)
56 {
57 if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0)
58 {
59 /*
60 * Can't get file information!
61 */
62
63 _cupsSetError(errno == ENOENT ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED,
64 NULL, 0);
65
66 ippDelete(request);
67
68 return (NULL);
69 }
70 }
71 else
72 infile = -1;
73
74 response = cupsDoIORequest(http, request, resource, infile, -1);
75
76 if (infile >= 0)
77 close(infile);
78
79 return (response);
80 }
81
82
83 /*
84 * 'cupsDoIORequest()' - Do an IPP request with file descriptors.
85 *
86 * This function sends the IPP request with the optional input file "infile" to
87 * the specified server, retrying and authenticating as necessary. The request
88 * is freed with @link ippDelete@.
89 *
90 * If "infile" is a valid file descriptor, @code cupsDoIORequest@ copies
91 * all of the data from the file after the IPP request message.
92 *
93 * If "outfile" is a valid file descriptor, @code cupsDoIORequest@ copies
94 * all of the data after the IPP response message to the file.
95 *
96 * @since CUPS 1.3/macOS 10.5@
97 */
98
99 ipp_t * /* O - Response data */
cupsDoIORequest(http_t * http,ipp_t * request,const char * resource,int infile,int outfile)100 cupsDoIORequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
101 ipp_t *request, /* I - IPP request */
102 const char *resource, /* I - HTTP resource for POST */
103 int infile, /* I - File to read from or -1 for none */
104 int outfile) /* I - File to write to or -1 for none */
105 {
106 ipp_t *response = NULL; /* IPP response data */
107 size_t length = 0; /* Content-Length value */
108 http_status_t status; /* Status of HTTP request */
109 struct stat fileinfo; /* File information */
110 ssize_t bytes; /* Number of bytes read/written */
111 char buffer[32768]; /* Output buffer */
112
113
114 DEBUG_printf(("cupsDoIORequest(http=%p, request=%p(%s), resource=\"%s\", infile=%d, outfile=%d)", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, infile, outfile));
115
116 /*
117 * Range check input...
118 */
119
120 if (!request || !resource)
121 {
122 ippDelete(request);
123
124 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
125
126 return (NULL);
127 }
128
129 /*
130 * Get the default connection as needed...
131 */
132
133 if (!http && (http = _cupsConnect()) == NULL)
134 {
135 ippDelete(request);
136
137 return (NULL);
138 }
139
140 /*
141 * See if we have a file to send...
142 */
143
144 if (infile >= 0)
145 {
146 if (fstat(infile, &fileinfo))
147 {
148 /*
149 * Can't get file information!
150 */
151
152 _cupsSetError(errno == EBADF ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED, NULL, 0);
153 ippDelete(request);
154
155 return (NULL);
156 }
157
158 #ifdef _WIN32
159 if (fileinfo.st_mode & _S_IFDIR)
160 #else
161 if (S_ISDIR(fileinfo.st_mode))
162 #endif /* _WIN32 */
163 {
164 /*
165 * Can't send a directory...
166 */
167
168 _cupsSetError(IPP_STATUS_ERROR_NOT_POSSIBLE, strerror(EISDIR), 0);
169 ippDelete(request);
170
171 return (NULL);
172 }
173
174 #ifndef _WIN32
175 if (!S_ISREG(fileinfo.st_mode))
176 length = 0; /* Chunk when piping */
177 else
178 #endif /* !_WIN32 */
179 length = ippLength(request) + (size_t)fileinfo.st_size;
180 }
181 else
182 length = ippLength(request);
183
184 DEBUG_printf(("2cupsDoIORequest: Request length=%lu, total length=%lu", (unsigned long)ippLength(request), (unsigned long)length));
185
186 /*
187 * Clear any "Local" authentication data since it is probably stale...
188 */
189
190 if (http->authstring && !strncmp(http->authstring, "Local ", 6))
191 httpSetAuthString(http, NULL, NULL);
192
193 /*
194 * Loop until we can send the request without authorization problems.
195 */
196
197 while (response == NULL)
198 {
199 DEBUG_puts("2cupsDoIORequest: setup...");
200
201 /*
202 * Send the request...
203 */
204
205 status = cupsSendRequest(http, request, resource, length);
206
207 DEBUG_printf(("2cupsDoIORequest: status=%d", status));
208
209 if (status == HTTP_STATUS_CONTINUE && request->state == IPP_STATE_DATA && infile >= 0)
210 {
211 DEBUG_puts("2cupsDoIORequest: file write...");
212
213 /*
214 * Send the file with the request...
215 */
216
217 #ifndef _WIN32
218 if (S_ISREG(fileinfo.st_mode))
219 #endif /* _WIN32 */
220 lseek(infile, 0, SEEK_SET);
221
222 while ((bytes = read(infile, buffer, sizeof(buffer))) > 0)
223 {
224 if ((status = cupsWriteRequestData(http, buffer, (size_t)bytes))
225 != HTTP_STATUS_CONTINUE)
226 break;
227 }
228 }
229
230 /*
231 * Get the server's response...
232 */
233
234 if (status <= HTTP_STATUS_CONTINUE || status == HTTP_STATUS_OK)
235 {
236 response = cupsGetResponse(http, resource);
237 status = httpGetStatus(http);
238 }
239
240 DEBUG_printf(("2cupsDoIORequest: status=%d", status));
241
242 if (status == HTTP_STATUS_ERROR ||
243 (status >= HTTP_STATUS_BAD_REQUEST && status != HTTP_STATUS_UNAUTHORIZED &&
244 status != HTTP_STATUS_UPGRADE_REQUIRED))
245 {
246 _cupsSetHTTPError(http, status);
247 break;
248 }
249
250 if (response && outfile >= 0)
251 {
252 /*
253 * Write trailing data to file...
254 */
255
256 while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
257 if (write(outfile, buffer, (size_t)bytes) < bytes)
258 break;
259 }
260
261 if (http->state != HTTP_STATE_WAITING)
262 {
263 /*
264 * Flush any remaining data...
265 */
266
267 httpFlush(http);
268 }
269 }
270
271 /*
272 * Delete the original request and return the response...
273 */
274
275 ippDelete(request);
276
277 return (response);
278 }
279
280
281 /*
282 * 'cupsDoRequest()' - Do an IPP request.
283 *
284 * This function sends the IPP request to the specified server, retrying
285 * and authenticating as necessary. The request is freed with @link ippDelete@.
286 */
287
288 ipp_t * /* O - Response data */
cupsDoRequest(http_t * http,ipp_t * request,const char * resource)289 cupsDoRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
290 ipp_t *request, /* I - IPP request */
291 const char *resource) /* I - HTTP resource for POST */
292 {
293 DEBUG_printf(("cupsDoRequest(http=%p, request=%p(%s), resource=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource));
294
295 return (cupsDoIORequest(http, request, resource, -1, -1));
296 }
297
298
299 /*
300 * 'cupsGetResponse()' - Get a response to an IPP request.
301 *
302 * Use this function to get the response for an IPP request sent using
303 * @link cupsSendRequest@. For requests that return additional data, use
304 * @link cupsReadResponseData@ after getting a successful response,
305 * otherwise call @link httpFlush@ to complete the response processing.
306 *
307 * @since CUPS 1.4/macOS 10.6@
308 */
309
310 ipp_t * /* O - Response or @code NULL@ on HTTP error */
cupsGetResponse(http_t * http,const char * resource)311 cupsGetResponse(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
312 const char *resource) /* I - HTTP resource for POST */
313 {
314 http_status_t status; /* HTTP status */
315 ipp_state_t state; /* IPP read state */
316 ipp_t *response = NULL; /* IPP response */
317
318
319 DEBUG_printf(("cupsGetResponse(http=%p, resource=\"%s\")", (void *)http, resource));
320 DEBUG_printf(("1cupsGetResponse: http->state=%d", http ? http->state : HTTP_STATE_ERROR));
321
322 /*
323 * Connect to the default server as needed...
324 */
325
326 if (!http)
327 {
328 _cups_globals_t *cg = _cupsGlobals();
329 /* Pointer to library globals */
330
331 if ((http = cg->http) == NULL)
332 {
333 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection."), 1);
334 DEBUG_puts("1cupsGetResponse: No active connection - returning NULL.");
335 return (NULL);
336 }
337 }
338
339 if (http->state != HTTP_STATE_POST_RECV && http->state != HTTP_STATE_POST_SEND)
340 {
341 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No request sent."), 1);
342 DEBUG_puts("1cupsGetResponse: Not in POST state - returning NULL.");
343 return (NULL);
344 }
345
346 /*
347 * Check for an unfinished chunked request...
348 */
349
350 if (http->data_encoding == HTTP_ENCODING_CHUNKED)
351 {
352 /*
353 * Send a 0-length chunk to finish off the request...
354 */
355
356 DEBUG_puts("2cupsGetResponse: Finishing chunked POST...");
357
358 if (httpWrite2(http, "", 0) < 0)
359 {
360 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to finish request."), 1);
361 return (NULL);
362 }
363 }
364
365 /*
366 * Wait for a response from the server...
367 */
368
369 DEBUG_printf(("2cupsGetResponse: Update loop, http->status=%d...",
370 http->status));
371
372 do
373 {
374 status = httpUpdate(http);
375 }
376 while (status == HTTP_STATUS_CONTINUE);
377
378 DEBUG_printf(("2cupsGetResponse: status=%d", status));
379
380 if (status == HTTP_STATUS_OK)
381 {
382 /*
383 * Get the IPP response...
384 */
385
386 response = ippNew();
387
388 while ((state = ippRead(http, response)) != IPP_STATE_DATA)
389 if (state == IPP_STATE_ERROR)
390 break;
391
392 if (state == IPP_STATE_ERROR)
393 {
394 /*
395 * Flush remaining data and delete the response...
396 */
397
398 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to read response."), 1);
399 DEBUG_puts("1cupsGetResponse: IPP read error!");
400
401 httpFlush(http);
402
403 ippDelete(response);
404 response = NULL;
405
406 http->status = HTTP_STATUS_ERROR;
407 http->error = EINVAL;
408 }
409 }
410 else if (status != HTTP_STATUS_ERROR)
411 {
412 /*
413 * Flush any error message...
414 */
415
416 httpFlush(http);
417
418 _cupsSetHTTPError(http, status);
419
420 /*
421 * Then handle encryption and authentication...
422 */
423
424 if (status == HTTP_STATUS_UNAUTHORIZED)
425 {
426 /*
427 * See if we can do authentication...
428 */
429
430 DEBUG_puts("2cupsGetResponse: Need authorization...");
431
432 if (!cupsDoAuthentication(http, "POST", resource))
433 httpReconnect2(http, 30000, NULL);
434 else
435 http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
436 }
437 else if (status == HTTP_STATUS_UPGRADE_REQUIRED)
438 {
439 /*
440 * Force a reconnect with encryption...
441 */
442
443 DEBUG_puts("2cupsGetResponse: Need encryption...");
444
445 #ifdef HAVE_TLS
446 if (!httpReconnect2(http, 30000, NULL))
447 httpEncryption(http, HTTP_ENCRYPTION_REQUIRED);
448
449 #else
450 http->status = HTTP_STATUS_CUPS_PKI_ERROR;
451 #endif /* HAVE_TLS */
452 }
453 }
454
455 if (response)
456 {
457 ipp_attribute_t *attr; /* status-message attribute */
458
459
460 attr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT);
461
462 DEBUG_printf(("1cupsGetResponse: status-code=%s, status-message=\"%s\"",
463 ippErrorString(response->request.status.status_code),
464 attr ? attr->values[0].string.text : ""));
465
466 _cupsSetError(response->request.status.status_code,
467 attr ? attr->values[0].string.text :
468 ippErrorString(response->request.status.status_code), 0);
469 }
470
471 return (response);
472 }
473
474
475 /*
476 * 'cupsLastError()' - Return the last IPP status code received on the current
477 * thread.
478 */
479
480 ipp_status_t /* O - IPP status code from last request */
cupsLastError(void)481 cupsLastError(void)
482 {
483 return (_cupsGlobals()->last_error);
484 }
485
486
487 /*
488 * 'cupsLastErrorString()' - Return the last IPP status-message received on the
489 * current thread.
490 *
491 * @since CUPS 1.2/macOS 10.5@
492 */
493
494 const char * /* O - status-message text from last request */
cupsLastErrorString(void)495 cupsLastErrorString(void)
496 {
497 return (_cupsGlobals()->last_status_message);
498 }
499
500
501 /*
502 * '_cupsNextDelay()' - Return the next retry delay value.
503 *
504 * This function currently returns the Fibonacci sequence 1 1 2 3 5 8.
505 *
506 * Pass 0 for the current delay value to initialize the sequence.
507 */
508
509 int /* O - Next delay value */
_cupsNextDelay(int current,int * previous)510 _cupsNextDelay(int current, /* I - Current delay value or 0 */
511 int *previous) /* IO - Previous delay value */
512 {
513 int next; /* Next delay value */
514
515
516 if (current > 0)
517 {
518 next = (current + *previous) % 12;
519 *previous = next < current ? 0 : current;
520 }
521 else
522 {
523 next = 1;
524 *previous = 0;
525 }
526
527 return (next);
528 }
529
530
531 /*
532 * 'cupsReadResponseData()' - Read additional data after the IPP response.
533 *
534 * This function is used after @link cupsGetResponse@ to read the PPD or document
535 * files from @code CUPS_GET_PPD@ and @code CUPS_GET_DOCUMENT@ requests,
536 * respectively.
537 *
538 * @since CUPS 1.4/macOS 10.6@
539 */
540
541 ssize_t /* O - Bytes read, 0 on EOF, -1 on error */
cupsReadResponseData(http_t * http,char * buffer,size_t length)542 cupsReadResponseData(
543 http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
544 char *buffer, /* I - Buffer to use */
545 size_t length) /* I - Number of bytes to read */
546 {
547 /*
548 * Get the default connection as needed...
549 */
550
551 DEBUG_printf(("cupsReadResponseData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length));
552
553 if (!http)
554 {
555 _cups_globals_t *cg = _cupsGlobals();
556 /* Pointer to library globals */
557
558 if ((http = cg->http) == NULL)
559 {
560 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), 1);
561 return (-1);
562 }
563 }
564
565 /*
566 * Then read from the HTTP connection...
567 */
568
569 return (httpRead2(http, buffer, length));
570 }
571
572
573 /*
574 * 'cupsSendRequest()' - Send an IPP request.
575 *
576 * Use @link cupsWriteRequestData@ to write any additional data (document, PPD
577 * file, etc.) for the request, @link cupsGetResponse@ to get the IPP response,
578 * and @link cupsReadResponseData@ to read any additional data following the
579 * response. Only one request can be sent/queued at a time per @code http_t@
580 * connection.
581 *
582 * Returns the initial HTTP status code, which will be @code HTTP_STATUS_CONTINUE@
583 * on a successful send of the request.
584 *
585 * Note: Unlike @link cupsDoFileRequest@, @link cupsDoIORequest@, and
586 * @link cupsDoRequest@, the request is NOT freed with @link ippDelete@.
587 *
588 * @since CUPS 1.4/macOS 10.6@
589 */
590
591 http_status_t /* O - Initial HTTP status */
cupsSendRequest(http_t * http,ipp_t * request,const char * resource,size_t length)592 cupsSendRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
593 ipp_t *request, /* I - IPP request */
594 const char *resource, /* I - Resource path */
595 size_t length) /* I - Length of data to follow or @code CUPS_LENGTH_VARIABLE@ */
596 {
597 http_status_t status; /* Status of HTTP request */
598 int got_status; /* Did we get the status? */
599 ipp_state_t state; /* State of IPP processing */
600 http_status_t expect; /* Expect: header to use */
601 char date[256]; /* Date: header value */
602 int digest; /* Are we using Digest authentication? */
603
604
605 DEBUG_printf(("cupsSendRequest(http=%p, request=%p(%s), resource=\"%s\", length=" CUPS_LLFMT ")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, CUPS_LLCAST length));
606
607 /*
608 * Range check input...
609 */
610
611 if (!request || !resource)
612 {
613 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
614
615 return (HTTP_STATUS_ERROR);
616 }
617
618 /*
619 * Get the default connection as needed...
620 */
621
622 if (!http && (http = _cupsConnect()) == NULL)
623 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
624
625 /*
626 * If the prior request was not flushed out, do so now...
627 */
628
629 if (http->state == HTTP_STATE_GET_SEND ||
630 http->state == HTTP_STATE_POST_SEND)
631 {
632 DEBUG_puts("2cupsSendRequest: Flush prior response.");
633 httpFlush(http);
634 }
635 else if (http->state != HTTP_STATE_WAITING)
636 {
637 DEBUG_printf(("1cupsSendRequest: Unknown HTTP state (%d), "
638 "reconnecting.", http->state));
639 if (httpReconnect2(http, 30000, NULL))
640 return (HTTP_STATUS_ERROR);
641 }
642
643 #ifdef HAVE_TLS
644 /*
645 * See if we have an auth-info attribute and are communicating over
646 * a non-local link. If so, encrypt the link so that we can pass
647 * the authentication information securely...
648 */
649
650 if (ippFindAttribute(request, "auth-info", IPP_TAG_TEXT) &&
651 !httpAddrLocalhost(http->hostaddr) && !http->tls &&
652 httpEncryption(http, HTTP_ENCRYPTION_REQUIRED))
653 {
654 DEBUG_puts("1cupsSendRequest: Unable to encrypt connection.");
655 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
656 }
657 #endif /* HAVE_TLS */
658
659 /*
660 * Reconnect if the last response had a "Connection: close"...
661 */
662
663 if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close"))
664 {
665 DEBUG_puts("2cupsSendRequest: Connection: close");
666 httpClearFields(http);
667 if (httpReconnect2(http, 30000, NULL))
668 {
669 DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
670 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
671 }
672 }
673
674 /*
675 * Loop until we can send the request without authorization problems.
676 */
677
678 expect = HTTP_STATUS_CONTINUE;
679
680 for (;;)
681 {
682 DEBUG_puts("2cupsSendRequest: Setup...");
683
684 /*
685 * Setup the HTTP variables needed...
686 */
687
688 httpClearFields(http);
689 httpSetExpect(http, expect);
690 httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
691 httpSetField(http, HTTP_FIELD_DATE, httpGetDateString2(time(NULL), date, (int)sizeof(date)));
692 httpSetLength(http, length);
693
694 digest = http->authstring && !strncmp(http->authstring, "Digest ", 7);
695
696 if (digest)
697 {
698 /*
699 * Update the Digest authentication string...
700 */
701
702 _httpSetDigestAuthString(http, http->nextnonce, "POST", resource);
703 }
704
705 #ifdef HAVE_GSSAPI
706 if (http->authstring && !strncmp(http->authstring, "Negotiate", 9))
707 {
708 /*
709 * Do not use cached Kerberos credentials since they will look like a
710 * "replay" attack...
711 */
712
713 _cupsSetNegotiateAuthString(http, "POST", resource);
714 }
715 #endif /* HAVE_GSSAPI */
716
717 httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring);
718
719 DEBUG_printf(("2cupsSendRequest: authstring=\"%s\"", http->authstring));
720
721 /*
722 * Try the request...
723 */
724
725 DEBUG_puts("2cupsSendRequest: Sending HTTP POST...");
726
727 if (httpPost(http, resource))
728 {
729 DEBUG_puts("2cupsSendRequest: POST failed, reconnecting.");
730 if (httpReconnect2(http, 30000, NULL))
731 {
732 DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
733 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
734 }
735 else
736 continue;
737 }
738
739 /*
740 * Send the IPP data...
741 */
742
743 DEBUG_puts("2cupsSendRequest: Writing IPP request...");
744
745 request->state = IPP_STATE_IDLE;
746 status = HTTP_STATUS_CONTINUE;
747 got_status = 0;
748
749 while ((state = ippWrite(http, request)) != IPP_STATE_DATA)
750 {
751 if (httpCheck(http))
752 {
753 got_status = 1;
754
755 _httpUpdate(http, &status);
756 if (status >= HTTP_STATUS_MULTIPLE_CHOICES)
757 break;
758 }
759 else if (state == IPP_STATE_ERROR)
760 break;
761 }
762
763 if (state == IPP_STATE_ERROR)
764 {
765 /*
766 * We weren't able to send the IPP request. But did we already get a HTTP
767 * error status?
768 */
769
770 if (!got_status || status < HTTP_STATUS_MULTIPLE_CHOICES)
771 {
772 /*
773 * No, something else went wrong.
774 */
775
776 DEBUG_puts("1cupsSendRequest: Unable to send IPP request.");
777
778 http->status = HTTP_STATUS_ERROR;
779 http->state = HTTP_STATE_WAITING;
780
781 return (HTTP_STATUS_ERROR);
782 }
783 }
784
785 /*
786 * Wait up to 1 second to get the 100-continue response as needed...
787 */
788
789 if (!got_status || (digest && status == HTTP_STATUS_CONTINUE))
790 {
791 if (expect == HTTP_STATUS_CONTINUE || digest)
792 {
793 DEBUG_puts("2cupsSendRequest: Waiting for 100-continue...");
794
795 if (httpWait(http, 1000))
796 _httpUpdate(http, &status);
797 }
798 else if (httpCheck(http))
799 _httpUpdate(http, &status);
800 }
801
802 DEBUG_printf(("2cupsSendRequest: status=%d", status));
803
804 /*
805 * Process the current HTTP status...
806 */
807
808 if (status >= HTTP_STATUS_MULTIPLE_CHOICES)
809 {
810 int temp_status; /* Temporary status */
811
812 _cupsSetHTTPError(http, status);
813
814 do
815 {
816 temp_status = httpUpdate(http);
817 }
818 while (temp_status != HTTP_STATUS_ERROR &&
819 http->state == HTTP_STATE_POST_RECV);
820
821 httpFlush(http);
822 }
823
824 switch (status)
825 {
826 case HTTP_STATUS_CONTINUE :
827 case HTTP_STATUS_OK :
828 case HTTP_STATUS_ERROR :
829 DEBUG_printf(("1cupsSendRequest: Returning %d.", status));
830 return (status);
831
832 case HTTP_STATUS_UNAUTHORIZED :
833 if (cupsDoAuthentication(http, "POST", resource))
834 {
835 DEBUG_puts("1cupsSendRequest: Returning HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED.");
836 return (HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED);
837 }
838
839 DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_STATUS_UNAUTHORIZED.");
840
841 if (httpReconnect2(http, 30000, NULL))
842 {
843 DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
844 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
845 }
846 break;
847
848 #ifdef HAVE_TLS
849 case HTTP_STATUS_UPGRADE_REQUIRED :
850 /*
851 * Flush any error message, reconnect, and then upgrade with
852 * encryption...
853 */
854
855 DEBUG_puts("2cupsSendRequest: Reconnecting after "
856 "HTTP_STATUS_UPGRADE_REQUIRED.");
857
858 if (httpReconnect2(http, 30000, NULL))
859 {
860 DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
861 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
862 }
863
864 DEBUG_puts("2cupsSendRequest: Upgrading to TLS.");
865 if (httpEncryption(http, HTTP_ENCRYPTION_REQUIRED))
866 {
867 DEBUG_puts("1cupsSendRequest: Unable to encrypt connection.");
868 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
869 }
870 break;
871 #endif /* HAVE_TLS */
872
873 case HTTP_STATUS_EXPECTATION_FAILED :
874 /*
875 * Don't try using the Expect: header the next time around...
876 */
877
878 expect = HTTP_STATUS_NONE;
879
880 DEBUG_puts("2cupsSendRequest: Reconnecting after "
881 "HTTP_EXPECTATION_FAILED.");
882
883 if (httpReconnect2(http, 30000, NULL))
884 {
885 DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
886 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
887 }
888 break;
889
890 default :
891 /*
892 * Some other error...
893 */
894
895 return (status);
896 }
897 }
898 }
899
900
901 /*
902 * 'cupsWriteRequestData()' - Write additional data after an IPP request.
903 *
904 * This function is used after @link cupsSendRequest@ to provide a PPD and
905 * after @link cupsStartDocument@ to provide a document file.
906 *
907 * @since CUPS 1.4/macOS 10.6@
908 */
909
910 http_status_t /* O - @code HTTP_STATUS_CONTINUE@ if OK or HTTP status on error */
cupsWriteRequestData(http_t * http,const char * buffer,size_t length)911 cupsWriteRequestData(
912 http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
913 const char *buffer, /* I - Bytes to write */
914 size_t length) /* I - Number of bytes to write */
915 {
916 int wused; /* Previous bytes in buffer */
917
918
919 /*
920 * Get the default connection as needed...
921 */
922
923 DEBUG_printf(("cupsWriteRequestData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length));
924
925 if (!http)
926 {
927 _cups_globals_t *cg = _cupsGlobals();
928 /* Pointer to library globals */
929
930 if ((http = cg->http) == NULL)
931 {
932 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), 1);
933 DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR.");
934 return (HTTP_STATUS_ERROR);
935 }
936 }
937
938 /*
939 * Then write to the HTTP connection...
940 */
941
942 wused = http->wused;
943
944 if (httpWrite2(http, buffer, length) < 0)
945 {
946 DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR.");
947 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(http->error), 0);
948 return (HTTP_STATUS_ERROR);
949 }
950
951 /*
952 * Finally, check if we have any pending data from the server...
953 */
954
955 if (length >= HTTP_MAX_BUFFER ||
956 http->wused < wused ||
957 (wused > 0 && (size_t)http->wused == length))
958 {
959 /*
960 * We've written something to the server, so check for response data...
961 */
962
963 if (_httpWait(http, 0, 1))
964 {
965 http_status_t status; /* Status from _httpUpdate */
966
967 _httpUpdate(http, &status);
968 if (status >= HTTP_STATUS_MULTIPLE_CHOICES)
969 {
970 _cupsSetHTTPError(http, status);
971
972 do
973 {
974 status = httpUpdate(http);
975 }
976 while (status != HTTP_STATUS_ERROR && http->state == HTTP_STATE_POST_RECV);
977
978 httpFlush(http);
979 }
980
981 DEBUG_printf(("1cupsWriteRequestData: Returning %d.\n", status));
982 return (status);
983 }
984 }
985
986 DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_CONTINUE.");
987 return (HTTP_STATUS_CONTINUE);
988 }
989
990
991 /*
992 * '_cupsConnect()' - Get the default server connection...
993 */
994
995 http_t * /* O - HTTP connection */
_cupsConnect(void)996 _cupsConnect(void)
997 {
998 _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */
999
1000
1001 /*
1002 * See if we are connected to the same server...
1003 */
1004
1005 if (cg->http)
1006 {
1007 /*
1008 * Compare the connection hostname, port, and encryption settings to
1009 * the cached defaults; these were initialized the first time we
1010 * connected...
1011 */
1012
1013 if (strcmp(cg->http->hostname, cg->server) ||
1014 #ifdef AF_LOCAL
1015 (httpAddrFamily(cg->http->hostaddr) != AF_LOCAL && cg->ipp_port != httpAddrPort(cg->http->hostaddr)) ||
1016 #else
1017 cg->ipp_port != httpAddrPort(cg->http->hostaddr) ||
1018 #endif /* AF_LOCAL */
1019 (cg->http->encryption != cg->encryption &&
1020 cg->http->encryption == HTTP_ENCRYPTION_NEVER))
1021 {
1022 /*
1023 * Need to close the current connection because something has changed...
1024 */
1025
1026 httpClose(cg->http);
1027 cg->http = NULL;
1028 }
1029 else
1030 {
1031 /*
1032 * Same server, see if the connection is still established...
1033 */
1034
1035 char ch; /* Connection check byte */
1036 ssize_t n; /* Number of bytes */
1037
1038 #ifdef _WIN32
1039 if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK)) == 0 ||
1040 (n < 0 && WSAGetLastError() != WSAEWOULDBLOCK))
1041 #else
1042 if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK | MSG_DONTWAIT)) == 0 ||
1043 (n < 0 && errno != EWOULDBLOCK))
1044 #endif /* _WIN32 */
1045 {
1046 /*
1047 * Nope, close the connection...
1048 */
1049
1050 httpClose(cg->http);
1051 cg->http = NULL;
1052 }
1053 }
1054 }
1055
1056 /*
1057 * (Re)connect as needed...
1058 */
1059
1060 if (!cg->http)
1061 {
1062 if ((cg->http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC,
1063 cupsEncryption(), 1, 30000, NULL)) == NULL)
1064 {
1065 if (errno)
1066 _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, NULL, 0);
1067 else
1068 _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE,
1069 _("Unable to connect to host."), 1);
1070 }
1071 }
1072
1073 /*
1074 * Return the cached connection...
1075 */
1076
1077 return (cg->http);
1078 }
1079
1080
1081 /*
1082 * '_cupsSetError()' - Set the last IPP status code and status-message.
1083 */
1084
1085 void
_cupsSetError(ipp_status_t status,const char * message,int localize)1086 _cupsSetError(ipp_status_t status, /* I - IPP status code */
1087 const char *message, /* I - status-message value */
1088 int localize) /* I - Localize the message? */
1089 {
1090 _cups_globals_t *cg; /* Global data */
1091
1092
1093 if (!message && errno)
1094 {
1095 message = strerror(errno);
1096 localize = 0;
1097 }
1098
1099 cg = _cupsGlobals();
1100 cg->last_error = status;
1101
1102 if (cg->last_status_message)
1103 {
1104 _cupsStrFree(cg->last_status_message);
1105
1106 cg->last_status_message = NULL;
1107 }
1108
1109 if (message)
1110 {
1111 if (localize)
1112 {
1113 /*
1114 * Get the message catalog...
1115 */
1116
1117 if (!cg->lang_default)
1118 cg->lang_default = cupsLangDefault();
1119
1120 cg->last_status_message = _cupsStrAlloc(_cupsLangString(cg->lang_default,
1121 message));
1122 }
1123 else
1124 cg->last_status_message = _cupsStrAlloc(message);
1125 }
1126
1127 DEBUG_printf(("4_cupsSetError: last_error=%s, last_status_message=\"%s\"",
1128 ippErrorString(cg->last_error), cg->last_status_message));
1129 }
1130
1131
1132 /*
1133 * '_cupsSetHTTPError()' - Set the last error using the HTTP status.
1134 */
1135
1136 void
_cupsSetHTTPError(http_t * http,http_status_t status)1137 _cupsSetHTTPError(http_t *http, /* I - HTTP connection */
1138 http_status_t status) /* I - HTTP status code */
1139 {
1140 switch (status)
1141 {
1142 case HTTP_STATUS_NOT_FOUND :
1143 _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, httpStatus(status), 0);
1144 break;
1145
1146 case HTTP_STATUS_UNAUTHORIZED :
1147 _cupsSetError(IPP_STATUS_ERROR_NOT_AUTHENTICATED, httpStatus(status), 0);
1148 break;
1149
1150 case HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED :
1151 _cupsSetError(IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED, httpStatus(status), 0);
1152 break;
1153
1154 case HTTP_STATUS_FORBIDDEN :
1155 _cupsSetError(IPP_STATUS_ERROR_FORBIDDEN, httpStatus(status), 0);
1156 break;
1157
1158 case HTTP_STATUS_BAD_REQUEST :
1159 _cupsSetError(IPP_STATUS_ERROR_BAD_REQUEST, httpStatus(status), 0);
1160 break;
1161
1162 case HTTP_STATUS_REQUEST_TOO_LARGE :
1163 _cupsSetError(IPP_STATUS_ERROR_REQUEST_VALUE, httpStatus(status), 0);
1164 break;
1165
1166 case HTTP_STATUS_NOT_IMPLEMENTED :
1167 _cupsSetError(IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, httpStatus(status), 0);
1168 break;
1169
1170 case HTTP_STATUS_NOT_SUPPORTED :
1171 _cupsSetError(IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, httpStatus(status), 0);
1172 break;
1173
1174 case HTTP_STATUS_UPGRADE_REQUIRED :
1175 _cupsSetError(IPP_STATUS_ERROR_CUPS_UPGRADE_REQUIRED, httpStatus(status), 0);
1176 break;
1177
1178 case HTTP_STATUS_CUPS_PKI_ERROR :
1179 _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, httpStatus(status), 0);
1180 break;
1181
1182 case HTTP_STATUS_ERROR :
1183 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, http->error != 0 ? strerror(http->error) : "Internal Server Error", 0);
1184 break;
1185
1186 default :
1187 DEBUG_printf(("4_cupsSetHTTPError: HTTP error %d mapped to "
1188 "IPP_STATUS_ERROR_SERVICE_UNAVAILABLE!", status));
1189 _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, httpStatus(status), 0);
1190 break;
1191 }
1192 }
1193