1 /*
2 * Get/put file functions for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2018 by Apple Inc.
6 * Copyright © 1997-2006 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
26
27 /*
28 * 'cupsGetFd()' - Get a file from the server.
29 *
30 * This function returns @code HTTP_STATUS_OK@ when the file is successfully retrieved.
31 *
32 * @since CUPS 1.1.20/macOS 10.4@
33 */
34
35 http_status_t /* O - HTTP status */
cupsGetFd(http_t * http,const char * resource,int fd)36 cupsGetFd(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
37 const char *resource, /* I - Resource name */
38 int fd) /* I - File descriptor */
39 {
40 ssize_t bytes; /* Number of bytes read */
41 char buffer[8192]; /* Buffer for file */
42 http_status_t status; /* HTTP status from server */
43 char if_modified_since[HTTP_MAX_VALUE];
44 /* If-Modified-Since header */
45 int new_auth = 0; /* Using new auth information? */
46 int digest; /* Are we using Digest authentication? */
47
48
49 /*
50 * Range check input...
51 */
52
53 DEBUG_printf(("cupsGetFd(http=%p, resource=\"%s\", fd=%d)", (void *)http, resource, fd));
54
55 if (!resource || fd < 0)
56 {
57 if (http)
58 http->error = EINVAL;
59
60 return (HTTP_STATUS_ERROR);
61 }
62
63 if (!http)
64 if ((http = _cupsConnect()) == NULL)
65 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
66
67 /*
68 * Then send GET requests to the HTTP server...
69 */
70
71 strlcpy(if_modified_since, httpGetField(http, HTTP_FIELD_IF_MODIFIED_SINCE),
72 sizeof(if_modified_since));
73
74 do
75 {
76 if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close"))
77 {
78 httpClearFields(http);
79 if (httpReconnect2(http, 30000, NULL))
80 {
81 status = HTTP_STATUS_ERROR;
82 break;
83 }
84 }
85
86 httpClearFields(http);
87 httpSetField(http, HTTP_FIELD_IF_MODIFIED_SINCE, if_modified_since);
88
89 digest = http->authstring && !strncmp(http->authstring, "Digest ", 7);
90
91 if (digest && !new_auth)
92 {
93 /*
94 * Update the Digest authentication string...
95 */
96
97 _httpSetDigestAuthString(http, http->nextnonce, "GET", resource);
98 }
99
100 #ifdef HAVE_GSSAPI
101 if (http->authstring && !strncmp(http->authstring, "Negotiate", 9) && !new_auth)
102 {
103 /*
104 * Do not use cached Kerberos credentials since they will look like a
105 * "replay" attack...
106 */
107
108 _cupsSetNegotiateAuthString(http, "GET", resource);
109 }
110 #endif /* HAVE_GSSAPI */
111
112 httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring);
113
114 if (httpGet(http, resource))
115 {
116 if (httpReconnect2(http, 30000, NULL))
117 {
118 status = HTTP_STATUS_ERROR;
119 break;
120 }
121 else
122 {
123 status = HTTP_STATUS_UNAUTHORIZED;
124 continue;
125 }
126 }
127
128 new_auth = 0;
129
130 while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE);
131
132 if (status == HTTP_STATUS_UNAUTHORIZED)
133 {
134 /*
135 * Flush any error message...
136 */
137
138 httpFlush(http);
139
140 /*
141 * See if we can do authentication...
142 */
143
144 new_auth = 1;
145
146 if (cupsDoAuthentication(http, "GET", resource))
147 {
148 status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
149 break;
150 }
151
152 if (httpReconnect2(http, 30000, NULL))
153 {
154 status = HTTP_STATUS_ERROR;
155 break;
156 }
157
158 continue;
159 }
160 #ifdef HAVE_TLS
161 else if (status == HTTP_STATUS_UPGRADE_REQUIRED)
162 {
163 /* Flush any error message... */
164 httpFlush(http);
165
166 /* Reconnect... */
167 if (httpReconnect2(http, 30000, NULL))
168 {
169 status = HTTP_STATUS_ERROR;
170 break;
171 }
172
173 /* Upgrade with encryption... */
174 httpEncryption(http, HTTP_ENCRYPTION_REQUIRED);
175
176 /* Try again, this time with encryption enabled... */
177 continue;
178 }
179 #endif /* HAVE_TLS */
180 }
181 while (status == HTTP_STATUS_UNAUTHORIZED || status == HTTP_STATUS_UPGRADE_REQUIRED);
182
183 /*
184 * See if we actually got the file or an error...
185 */
186
187 if (status == HTTP_STATUS_OK)
188 {
189 /*
190 * Yes, copy the file...
191 */
192
193 while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
194 write(fd, buffer, (size_t)bytes);
195 }
196 else
197 {
198 _cupsSetHTTPError(http, status);
199 httpFlush(http);
200 }
201
202 /*
203 * Return the request status...
204 */
205
206 DEBUG_printf(("1cupsGetFd: Returning %d...", status));
207
208 return (status);
209 }
210
211
212 /*
213 * 'cupsGetFile()' - Get a file from the server.
214 *
215 * This function returns @code HTTP_STATUS_OK@ when the file is successfully retrieved.
216 *
217 * @since CUPS 1.1.20/macOS 10.4@
218 */
219
220 http_status_t /* O - HTTP status */
cupsGetFile(http_t * http,const char * resource,const char * filename)221 cupsGetFile(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
222 const char *resource, /* I - Resource name */
223 const char *filename) /* I - Filename */
224 {
225 int fd; /* File descriptor */
226 http_status_t status; /* Status */
227
228
229 /*
230 * Range check input...
231 */
232
233 if (!http || !resource || !filename)
234 {
235 if (http)
236 http->error = EINVAL;
237
238 return (HTTP_STATUS_ERROR);
239 }
240
241 /*
242 * Create the file...
243 */
244
245 if ((fd = open(filename, O_WRONLY | O_EXCL | O_TRUNC)) < 0)
246 {
247 /*
248 * Couldn't open the file!
249 */
250
251 http->error = errno;
252
253 return (HTTP_STATUS_ERROR);
254 }
255
256 /*
257 * Get the file...
258 */
259
260 status = cupsGetFd(http, resource, fd);
261
262 /*
263 * If the file couldn't be gotten, then remove the file...
264 */
265
266 close(fd);
267
268 if (status != HTTP_STATUS_OK)
269 unlink(filename);
270
271 /*
272 * Return the HTTP status code...
273 */
274
275 return (status);
276 }
277
278
279 /*
280 * 'cupsPutFd()' - Put a file on the server.
281 *
282 * This function returns @code HTTP_STATUS_CREATED@ when the file is stored
283 * successfully.
284 *
285 * @since CUPS 1.1.20/macOS 10.4@
286 */
287
288 http_status_t /* O - HTTP status */
cupsPutFd(http_t * http,const char * resource,int fd)289 cupsPutFd(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
290 const char *resource, /* I - Resource name */
291 int fd) /* I - File descriptor */
292 {
293 ssize_t bytes; /* Number of bytes read */
294 int retries; /* Number of retries */
295 char buffer[8192]; /* Buffer for file */
296 http_status_t status; /* HTTP status from server */
297 int new_auth = 0; /* Using new auth information? */
298 int digest; /* Are we using Digest authentication? */
299
300
301 /*
302 * Range check input...
303 */
304
305 DEBUG_printf(("cupsPutFd(http=%p, resource=\"%s\", fd=%d)", (void *)http, resource, fd));
306
307 if (!resource || fd < 0)
308 {
309 if (http)
310 http->error = EINVAL;
311
312 return (HTTP_STATUS_ERROR);
313 }
314
315 if (!http)
316 if ((http = _cupsConnect()) == NULL)
317 return (HTTP_STATUS_SERVICE_UNAVAILABLE);
318
319 /*
320 * Then send PUT requests to the HTTP server...
321 */
322
323 retries = 0;
324
325 do
326 {
327 if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close"))
328 {
329 httpClearFields(http);
330 if (httpReconnect2(http, 30000, NULL))
331 {
332 status = HTTP_STATUS_ERROR;
333 break;
334 }
335 }
336
337 DEBUG_printf(("2cupsPutFd: starting attempt, authstring=\"%s\"...",
338 http->authstring));
339
340 httpClearFields(http);
341 httpSetField(http, HTTP_FIELD_TRANSFER_ENCODING, "chunked");
342 httpSetExpect(http, HTTP_STATUS_CONTINUE);
343
344 digest = http->authstring && !strncmp(http->authstring, "Digest ", 7);
345
346 if (digest && !new_auth)
347 {
348 /*
349 * Update the Digest authentication string...
350 */
351
352 _httpSetDigestAuthString(http, http->nextnonce, "PUT", resource);
353 }
354
355 #ifdef HAVE_GSSAPI
356 if (http->authstring && !strncmp(http->authstring, "Negotiate", 9) && !new_auth)
357 {
358 /*
359 * Do not use cached Kerberos credentials since they will look like a
360 * "replay" attack...
361 */
362
363 _cupsSetNegotiateAuthString(http, "PUT", resource);
364 }
365 #endif /* HAVE_GSSAPI */
366
367 httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring);
368
369 if (httpPut(http, resource))
370 {
371 if (httpReconnect2(http, 30000, NULL))
372 {
373 status = HTTP_STATUS_ERROR;
374 break;
375 }
376 else
377 {
378 status = HTTP_STATUS_UNAUTHORIZED;
379 continue;
380 }
381 }
382
383 /*
384 * Wait up to 1 second for a 100-continue response...
385 */
386
387 if (httpWait(http, 1000))
388 status = httpUpdate(http);
389 else
390 status = HTTP_STATUS_CONTINUE;
391
392 if (status == HTTP_STATUS_CONTINUE)
393 {
394 /*
395 * Copy the file...
396 */
397
398 lseek(fd, 0, SEEK_SET);
399
400 while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
401 if (httpCheck(http))
402 {
403 if ((status = httpUpdate(http)) != HTTP_STATUS_CONTINUE)
404 break;
405 }
406 else
407 httpWrite2(http, buffer, (size_t)bytes);
408 }
409
410 if (status == HTTP_STATUS_CONTINUE)
411 {
412 httpWrite2(http, buffer, 0);
413
414 while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE);
415 }
416
417 if (status == HTTP_STATUS_ERROR && !retries)
418 {
419 DEBUG_printf(("2cupsPutFd: retry on status %d", status));
420
421 retries ++;
422 status = HTTP_STATUS_NONE;
423
424 /* Flush any error message... */
425 httpFlush(http);
426
427 /* Reconnect... */
428 if (httpReconnect2(http, 30000, NULL))
429 {
430 status = HTTP_STATUS_ERROR;
431 break;
432 }
433
434 /* Try again... */
435 continue;
436 }
437
438 DEBUG_printf(("2cupsPutFd: status=%d", status));
439
440 new_auth = 0;
441
442 if (status == HTTP_STATUS_UNAUTHORIZED)
443 {
444 /*
445 * Flush any error message...
446 */
447
448 httpFlush(http);
449
450 /*
451 * See if we can do authentication...
452 */
453
454 new_auth = 1;
455
456 if (cupsDoAuthentication(http, "PUT", resource))
457 {
458 status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
459 break;
460 }
461
462 if (httpReconnect2(http, 30000, NULL))
463 {
464 status = HTTP_STATUS_ERROR;
465 break;
466 }
467
468 continue;
469 }
470 #ifdef HAVE_TLS
471 else if (status == HTTP_STATUS_UPGRADE_REQUIRED)
472 {
473 /* Flush any error message... */
474 httpFlush(http);
475
476 /* Reconnect... */
477 if (httpReconnect2(http, 30000, NULL))
478 {
479 status = HTTP_STATUS_ERROR;
480 break;
481 }
482
483 /* Upgrade with encryption... */
484 httpEncryption(http, HTTP_ENCRYPTION_REQUIRED);
485
486 /* Try again, this time with encryption enabled... */
487 continue;
488 }
489 #endif /* HAVE_TLS */
490 }
491 while (status == HTTP_STATUS_UNAUTHORIZED || status == HTTP_STATUS_UPGRADE_REQUIRED || status == HTTP_STATUS_NONE);
492
493 /*
494 * See if we actually put the file or an error...
495 */
496
497 if (status != HTTP_STATUS_CREATED)
498 {
499 _cupsSetHTTPError(http, status);
500 httpFlush(http);
501 }
502
503 DEBUG_printf(("1cupsPutFd: Returning %d...", status));
504
505 return (status);
506 }
507
508
509 /*
510 * 'cupsPutFile()' - Put a file on the server.
511 *
512 * This function returns @code HTTP_CREATED@ when the file is stored
513 * successfully.
514 *
515 * @since CUPS 1.1.20/macOS 10.4@
516 */
517
518 http_status_t /* O - HTTP status */
cupsPutFile(http_t * http,const char * resource,const char * filename)519 cupsPutFile(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
520 const char *resource, /* I - Resource name */
521 const char *filename) /* I - Filename */
522 {
523 int fd; /* File descriptor */
524 http_status_t status; /* Status */
525
526
527 /*
528 * Range check input...
529 */
530
531 if (!http || !resource || !filename)
532 {
533 if (http)
534 http->error = EINVAL;
535
536 return (HTTP_STATUS_ERROR);
537 }
538
539 /*
540 * Open the local file...
541 */
542
543 if ((fd = open(filename, O_RDONLY)) < 0)
544 {
545 /*
546 * Couldn't open the file!
547 */
548
549 http->error = errno;
550
551 return (HTTP_STATUS_ERROR);
552 }
553
554 /*
555 * Put the file...
556 */
557
558 status = cupsPutFd(http, resource, fd);
559
560 close(fd);
561
562 return (status);
563 }
564