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