uclient-fetch: ignore SIGPIPE
[project/uclient.git] / uclient-fetch.c
1 /*
2 * uclient - ustream based protocol client library
3 *
4 * Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #define _GNU_SOURCE
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <dlfcn.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <glob.h>
27 #include <stdint.h>
28 #include <inttypes.h>
29 #include <signal.h>
30
31 #include <libubox/blobmsg.h>
32
33 #include "uclient.h"
34 #include "uclient-utils.h"
35
36 #ifdef __APPLE__
37 #define LIB_EXT "dylib"
38 #else
39 #define LIB_EXT "so"
40 #endif
41
42 static const char *user_agent = "uclient-fetch";
43 static const char *post_data;
44 static struct ustream_ssl_ctx *ssl_ctx;
45 static const struct ustream_ssl_ops *ssl_ops;
46 static int quiet = false;
47 static bool verify = true;
48 static bool proxy = true;
49 static bool default_certs = false;
50 static bool no_output;
51 static const char *output_file;
52 static int output_fd = -1;
53 static int error_ret;
54 static int out_bytes;
55 static char *auth_str;
56 static char **urls;
57 static int n_urls;
58 static int timeout;
59 static bool resume, cur_resume;
60
61 static int init_request(struct uclient *cl);
62 static void request_done(struct uclient *cl);
63
64 static const char *
65 get_proxy_url(char *url)
66 {
67 char prefix[16];
68 char *sep;
69
70 if (!proxy)
71 return NULL;
72
73 sep = strchr(url, ':');
74 if (!sep)
75 return NULL;
76
77 if (sep - url > 5)
78 return NULL;
79
80 memcpy(prefix, url, sep - url);
81 strcpy(prefix + (sep - url), "_proxy");
82 return getenv(prefix);
83 }
84
85 static int open_output_file(const char *path, uint64_t resume_offset)
86 {
87 char *filename = NULL;
88 int flags;
89 int ret;
90
91 if (cur_resume)
92 flags = O_RDWR;
93 else
94 flags = O_WRONLY | O_EXCL;
95
96 flags |= O_CREAT;
97
98 if (output_file) {
99 if (!strcmp(output_file, "-")) {
100 if (!quiet)
101 fprintf(stderr, "Writing to stdout\n");
102
103 return STDOUT_FILENO;
104 }
105 } else {
106 filename = uclient_get_url_filename(path, "index.html");
107 output_file = filename;
108 }
109
110 if (!quiet)
111 fprintf(stderr, "Writing to '%s'\n", output_file);
112 ret = open(output_file, flags, 0644);
113 free(filename);
114
115 if (ret < 0)
116 return ret;
117
118 if (resume_offset &&
119 lseek(ret, resume_offset, SEEK_SET) < 0) {
120 if (!quiet)
121 fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset);
122 close(ret);
123 return -1;
124 }
125
126 out_bytes += resume_offset;
127
128 return ret;
129 }
130
131 static void header_done_cb(struct uclient *cl)
132 {
133 static const struct blobmsg_policy policy = {
134 .name = "content-range",
135 .type = BLOBMSG_TYPE_STRING
136 };
137 struct blob_attr *attr;
138 uint64_t resume_offset = 0, resume_end, resume_size;
139 static int retries;
140
141 if (retries < 10 && uclient_http_redirect(cl)) {
142 if (!quiet)
143 fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
144
145 retries++;
146 return;
147 }
148
149 if (cl->status_code == 204 && cur_resume) {
150 /* Resume attempt failed, try normal download */
151 cur_resume = false;
152 init_request(cl);
153 return;
154 }
155
156 switch (cl->status_code) {
157 case 416:
158 if (!quiet)
159 fprintf(stderr, "File download already fully retrieved; nothing to do.\n");
160 request_done(cl);
161 break;
162 case 206:
163 if (!cur_resume) {
164 if (!quiet)
165 fprintf(stderr, "Error: Partial content received, full content requested\n");
166 error_ret = 8;
167 request_done(cl);
168 break;
169 }
170
171 blobmsg_parse(&policy, 1, &attr, blob_data(cl->meta), blob_len(cl->meta));
172 if (!attr) {
173 if (!quiet)
174 fprintf(stderr, "Content-Range header is missing\n");
175 error_ret = 8;
176 break;
177 }
178
179 if (sscanf(blobmsg_get_string(attr), "bytes %"PRIu64"-%"PRIu64"/%"PRIu64,
180 &resume_offset, &resume_end, &resume_size) != 3) {
181 if (!quiet)
182 fprintf(stderr, "Content-Range header is invalid\n");
183 error_ret = 8;
184 break;
185 }
186 case 204:
187 case 200:
188 if (no_output)
189 break;
190 output_fd = open_output_file(cl->url->location, resume_offset);
191 if (output_fd < 0) {
192 if (!quiet)
193 perror("Cannot open output file");
194 error_ret = 3;
195 request_done(cl);
196 }
197 break;
198
199 default:
200 if (!quiet)
201 fprintf(stderr, "HTTP error %d\n", cl->status_code);
202 request_done(cl);
203 error_ret = 8;
204 break;
205 }
206 }
207
208 static void read_data_cb(struct uclient *cl)
209 {
210 char buf[256];
211 int len;
212
213 if (!no_output && output_fd < 0)
214 return;
215
216 while (1) {
217 len = uclient_read(cl, buf, sizeof(buf));
218 if (!len)
219 return;
220
221 out_bytes += len;
222 if (!no_output)
223 write(output_fd, buf, len);
224 }
225 }
226
227 static void msg_connecting(struct uclient *cl)
228 {
229 char addr[INET6_ADDRSTRLEN];
230 int port;
231
232 if (quiet)
233 return;
234
235 uclient_get_addr(addr, &port, &cl->remote_addr);
236 fprintf(stderr, "Connecting to %s:%d\n", addr, port);
237 }
238
239 static void check_resume_offset(struct uclient *cl)
240 {
241 char range_str[64];
242 struct stat st;
243 char *file;
244 int ret;
245
246 file = uclient_get_url_filename(cl->url->location, "index.html");
247 if (!file)
248 return;
249
250 ret = stat(file, &st);
251 free(file);
252 if (ret)
253 return;
254
255 if (!st.st_size)
256 return;
257
258 snprintf(range_str, sizeof(range_str), "bytes=%"PRIu64"-", (uint64_t) st.st_size);
259 uclient_http_set_header(cl, "Range", range_str);
260 }
261
262 static int init_request(struct uclient *cl)
263 {
264 int rc;
265
266 out_bytes = 0;
267 uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify);
268
269 if (timeout)
270 cl->timeout_msecs = timeout * 1000;
271
272 rc = uclient_connect(cl);
273 if (rc)
274 return rc;
275
276 msg_connecting(cl);
277
278 rc = uclient_http_set_request_type(cl, post_data ? "POST" : "GET");
279 if (rc)
280 return rc;
281
282 uclient_http_reset_headers(cl);
283 uclient_http_set_header(cl, "User-Agent", user_agent);
284 if (cur_resume)
285 check_resume_offset(cl);
286
287 if (post_data) {
288 uclient_http_set_header(cl, "Content-Type", "application/x-www-form-urlencoded");
289 uclient_write(cl, post_data, strlen(post_data));
290 }
291
292 rc = uclient_request(cl);
293 if (rc)
294 return rc;
295
296 return 0;
297 }
298
299 static void request_done(struct uclient *cl)
300 {
301 const char *proxy_url;
302
303 if (n_urls) {
304 proxy_url = get_proxy_url(*urls);
305 if (proxy_url) {
306 uclient_set_url(cl, proxy_url, NULL);
307 uclient_set_proxy_url(cl, *urls, auth_str);
308 } else {
309 uclient_set_url(cl, *urls, auth_str);
310 }
311 n_urls--;
312 cur_resume = resume;
313 error_ret = init_request(cl);
314 if (error_ret == 0)
315 return;
316 }
317
318 if (output_fd >= 0 && !output_file) {
319 close(output_fd);
320 output_fd = -1;
321 }
322 uclient_disconnect(cl);
323 uloop_end();
324 }
325
326
327 static void eof_cb(struct uclient *cl)
328 {
329 if (!cl->data_eof) {
330 if (!quiet)
331 fprintf(stderr, "Connection reset prematurely\n");
332 error_ret = 4;
333 } else if (!quiet) {
334 fprintf(stderr, "Download completed (%d bytes)\n", out_bytes);
335 }
336 request_done(cl);
337 }
338
339 static void handle_uclient_error(struct uclient *cl, int code)
340 {
341 const char *type = "Unknown error";
342 bool ignore = false;
343
344 switch(code) {
345 case UCLIENT_ERROR_CONNECT:
346 type = "Connection failed";
347 error_ret = 4;
348 break;
349 case UCLIENT_ERROR_TIMEDOUT:
350 type = "Connection timed out";
351 error_ret = 4;
352 break;
353 case UCLIENT_ERROR_SSL_INVALID_CERT:
354 type = "Invalid SSL certificate";
355 ignore = !verify;
356 error_ret = 5;
357 break;
358 case UCLIENT_ERROR_SSL_CN_MISMATCH:
359 type = "Server hostname does not match SSL certificate";
360 ignore = !verify;
361 error_ret = 5;
362 break;
363 default:
364 error_ret = 1;
365 break;
366 }
367
368 if (!quiet)
369 fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : "");
370
371 if (ignore)
372 error_ret = 0;
373 else
374 request_done(cl);
375 }
376
377 static const struct uclient_cb cb = {
378 .header_done = header_done_cb,
379 .data_read = read_data_cb,
380 .data_eof = eof_cb,
381 .error = handle_uclient_error,
382 };
383
384 static int usage(const char *progname)
385 {
386 fprintf(stderr,
387 "Usage: %s [options] <URL>\n"
388 "Options:\n"
389 " -q: Turn off status messages\n"
390 " -O <file>: Redirect output to file (use \"-\" for stdout)\n"
391 " --user=<user> HTTP authentication username\n"
392 " --password=<password> HTTP authentication password\n"
393 " --user-agent|-U <str> Set HTTP user agent\n"
394 " --post-data=STRING use the POST method; send STRING as the data\n"
395 " --spider|-s Spider mode - only check file existence\n"
396 " --timeout=N|-T N Set connect/request timeout to N seconds\n"
397 " --proxy=on|off|-Y on|off Enable/disable env var configured proxy\n"
398 "\n"
399 "HTTPS options:\n"
400 " --ca-certificate=<cert>: Load CA certificates from file <cert>\n"
401 " --no-check-certificate: don't validate the server's certificate\n"
402 "\n", progname);
403 return 1;
404 }
405
406 static void init_ca_cert(void)
407 {
408 glob_t gl;
409 int i;
410
411 glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
412 for (i = 0; i < gl.gl_pathc; i++)
413 ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
414 }
415
416 static void init_ustream_ssl(void)
417 {
418 void *dlh;
419
420 dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
421 if (!dlh)
422 return;
423
424 ssl_ops = dlsym(dlh, "ustream_ssl_ops");
425 if (!ssl_ops)
426 return;
427
428 ssl_ctx = ssl_ops->context_new(false);
429 }
430
431 static int no_ssl(const char *progname)
432 {
433 fprintf(stderr, "%s: SSL support not available, please install ustream-ssl\n", progname);
434 return 1;
435 }
436
437 enum {
438 L_NO_CHECK_CERTIFICATE,
439 L_CA_CERTIFICATE,
440 L_USER,
441 L_PASSWORD,
442 L_USER_AGENT,
443 L_POST_DATA,
444 L_SPIDER,
445 L_TIMEOUT,
446 L_CONTINUE,
447 L_PROXY,
448 L_NO_PROXY,
449 };
450
451 static const struct option longopts[] = {
452 [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument },
453 [L_CA_CERTIFICATE] = { "ca-certificate", required_argument },
454 [L_USER] = { "user", required_argument },
455 [L_PASSWORD] = { "password", required_argument },
456 [L_USER_AGENT] = { "user-agent", required_argument },
457 [L_POST_DATA] = { "post-data", required_argument },
458 [L_SPIDER] = { "spider", no_argument },
459 [L_TIMEOUT] = { "timeout", required_argument },
460 [L_CONTINUE] = { "continue", no_argument },
461 [L_PROXY] = { "proxy", required_argument },
462 [L_NO_PROXY] = { "no-proxy", no_argument },
463 {}
464 };
465
466
467
468 int main(int argc, char **argv)
469 {
470 const char *progname = argv[0];
471 const char *proxy_url;
472 char *username = NULL;
473 char *password = NULL;
474 struct uclient *cl;
475 int longopt_idx = 0;
476 bool has_cert = false;
477 int i, ch;
478 int rc;
479
480 signal(SIGPIPE, SIG_IGN);
481 init_ustream_ssl();
482
483 while ((ch = getopt_long(argc, argv, "cO:qsU:Y:", longopts, &longopt_idx)) != -1) {
484 switch(ch) {
485 case 0:
486 switch (longopt_idx) {
487 case L_NO_CHECK_CERTIFICATE:
488 verify = false;
489 break;
490 case L_CA_CERTIFICATE:
491 has_cert = true;
492 if (ssl_ctx)
493 ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg);
494 break;
495 case L_USER:
496 if (!strlen(optarg))
497 break;
498 username = strdup(optarg);
499 memset(optarg, '*', strlen(optarg));
500 break;
501 case L_PASSWORD:
502 if (!strlen(optarg))
503 break;
504 password = strdup(optarg);
505 memset(optarg, '*', strlen(optarg));
506 break;
507 case L_USER_AGENT:
508 user_agent = optarg;
509 break;
510 case L_POST_DATA:
511 post_data = optarg;
512 break;
513 case L_SPIDER:
514 no_output = true;
515 break;
516 case L_TIMEOUT:
517 timeout = atoi(optarg);
518 break;
519 case L_CONTINUE:
520 resume = true;
521 break;
522 case L_PROXY:
523 if (strcmp(optarg, "on") != 0)
524 proxy = false;
525 break;
526 case L_NO_PROXY:
527 proxy = false;
528 break;
529 default:
530 return usage(progname);
531 }
532 break;
533 case 'c':
534 resume = true;
535 break;
536 case 'U':
537 user_agent = optarg;
538 break;
539 case 'O':
540 output_file = optarg;
541 break;
542 case 'q':
543 quiet = true;
544 break;
545 case 's':
546 no_output = true;
547 break;
548 case 'T':
549 timeout = atoi(optarg);
550 break;
551 case 'Y':
552 if (strcmp(optarg, "on") != 0)
553 proxy = false;
554 break;
555 default:
556 return usage(progname);
557 }
558 }
559
560 argv += optind;
561 argc -= optind;
562
563 if (verify && !has_cert)
564 default_certs = true;
565
566 if (argc < 1)
567 return usage(progname);
568
569 if (!ssl_ctx) {
570 for (i = 0; i < argc; i++) {
571 if (!strncmp(argv[i], "https", 5))
572 return no_ssl(progname);
573 }
574 }
575
576 urls = argv + 1;
577 n_urls = argc - 1;
578
579 uloop_init();
580
581 if (username) {
582 if (password)
583 asprintf(&auth_str, "%s:%s", username, password);
584 else
585 auth_str = username;
586 }
587
588 if (!quiet)
589 fprintf(stderr, "Downloading '%s'\n", argv[0]);
590
591 proxy_url = get_proxy_url(argv[0]);
592 if (proxy_url) {
593 cl = uclient_new(proxy_url, auth_str, &cb);
594 uclient_set_proxy_url(cl, argv[0], NULL);
595 } else {
596 cl = uclient_new(argv[0], auth_str, &cb);
597 }
598 if (!cl) {
599 fprintf(stderr, "Failed to allocate uclient context\n");
600 return 1;
601 }
602
603 if (ssl_ctx && default_certs)
604 init_ca_cert();
605
606 cur_resume = resume;
607 rc = init_request(cl);
608 if (!rc) {
609 /* no error received, we can enter main loop */
610 uloop_run();
611 } else {
612 fprintf(stderr, "Failed to establish connection\n");
613 error_ret = 4;
614 }
615
616 uloop_done();
617
618 uclient_free(cl);
619
620 if (output_fd >= 0 && output_fd != STDOUT_FILENO)
621 close(output_fd);
622
623 if (ssl_ctx)
624 ssl_ops->context_free(ssl_ctx);
625
626 return error_ret;
627 }