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