• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/uclient/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 <sys/socket.h>
 22 #include <unistd.h>
 23 #include <stdio.h>
 24 #include <dlfcn.h>
 25 #include <getopt.h>
 26 #include <fcntl.h>
 27 #include <glob.h>
 28 #include <stdint.h>
 29 #include <inttypes.h>
 30 #include <signal.h>
 31 
 32 #include <libubox/blobmsg.h>
 33 
 34 #include "progress.h"
 35 #include "uclient.h"
 36 #include "uclient-utils.h"
 37 
 38 #ifdef __APPLE__
 39 #define LIB_EXT "dylib"
 40 #else
 41 #define LIB_EXT "so"
 42 #endif
 43 
 44 static const char *user_agent = "uclient-fetch";
 45 static const char *post_data;
 46 static const char *post_file;
 47 static struct ustream_ssl_ctx *ssl_ctx;
 48 static const struct ustream_ssl_ops *ssl_ops;
 49 static int quiet = false;
 50 static bool verify = true;
 51 static bool proxy = true;
 52 static bool default_certs = false;
 53 static bool no_output;
 54 static const char *opt_output_file;
 55 static int output_fd = -1;
 56 static int error_ret;
 57 static off_t out_offset;
 58 static off_t out_bytes;
 59 static off_t out_len;
 60 static char *auth_str;
 61 static char **urls;
 62 static int n_urls;
 63 static int timeout;
 64 static bool resume, cur_resume;
 65 
 66 static struct progress pmt;
 67 static struct uloop_timeout pmt_timer;
 68 
 69 static int init_request(struct uclient *cl);
 70 static void request_done(struct uclient *cl);
 71 
 72 static void pmt_update(struct uloop_timeout *t)
 73 {
 74         progress_update(&pmt, out_offset, out_bytes, out_len);
 75         uloop_timeout_set(t, 1000);
 76 }
 77 
 78 static const char *
 79 get_proxy_url(char *url)
 80 {
 81         char prefix[16];
 82         char *sep;
 83 
 84         if (!proxy)
 85                 return NULL;
 86 
 87         sep = strchr(url, ':');
 88         if (!sep)
 89                 return NULL;
 90 
 91         if (sep - url > 5)
 92                 return NULL;
 93 
 94         memcpy(prefix, url, sep - url);
 95         strcpy(prefix + (sep - url), "_proxy");
 96         return getenv(prefix);
 97 }
 98 
 99 static int open_output_file(const char *path, uint64_t resume_offset)
100 {
101         const char *output_file = opt_output_file;
102         char *filename = NULL;
103         int flags;
104         int ret;
105 
106         if (cur_resume)
107                 flags = O_RDWR;
108         else
109                 flags = O_WRONLY | O_TRUNC;
110 
111         if (!cur_resume && !output_file)
112                 flags |= O_EXCL;
113 
114         flags |= O_CREAT;
115 
116         if (output_file) {
117                 if (!strcmp(output_file, "-")) {
118                         if (!quiet)
119                                 fprintf(stderr, "Writing to stdout\n");
120 
121                         ret = STDOUT_FILENO;
122                         goto done;
123                 }
124         } else {
125                 filename = uclient_get_url_filename(path, "index.html");
126                 if (!filename) {
127                         ret = -ENOMEM;
128                         goto out;
129                 }
130 
131                 output_file = filename;
132         }
133 
134         if (!quiet)
135                 fprintf(stderr, "Writing to '%s'\n", output_file);
136         ret = open(output_file, flags, 0644);
137         if (ret < 0)
138                 goto free;
139 
140         if (resume_offset &&
141             lseek(ret, resume_offset, SEEK_SET) < 0) {
142                 if (!quiet)
143                         fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset);
144                 close(ret);
145                 ret = -1;
146                 goto free;
147         }
148 
149         out_offset = resume_offset;
150         out_bytes += resume_offset;
151 done:
152         if (!quiet) {
153                 progress_init(&pmt, output_file);
154                 pmt_timer.cb = pmt_update;
155                 pmt_timer.cb(&pmt_timer);
156         }
157 
158 free:
159         free(filename);
160 out:
161         return ret;
162 }
163 
164 static void header_done_cb(struct uclient *cl)
165 {
166         enum {
167                 H_RANGE,
168                 H_LEN,
169                 __H_MAX
170         };
171         static const struct blobmsg_policy policy[__H_MAX] = {
172                 [H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
173                 [H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING },
174         };
175         struct blob_attr *tb[__H_MAX];
176         uint64_t resume_offset = 0, resume_end, resume_size;
177         static int retries;
178 
179         if (retries < 10) {
180                 int ret = uclient_http_redirect(cl);
181                 if (ret < 0) {
182                         if (!quiet)
183                                 fprintf(stderr, "Failed to redirect to %s on %s\n", cl->url->location, cl->url->host);
184                         error_ret = 8;
185                         request_done(cl);
186                         return;
187                 }
188                 if (ret > 0) {
189                         if (!quiet)
190                                 fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
191 
192                         retries++;
193                         return;
194                 }
195         }
196 
197         if (cl->status_code == 204 && cur_resume) {
198                 /* Resume attempt failed, try normal download */
199                 cur_resume = false;
200                 init_request(cl);
201                 return;
202         }
203 
204         blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
205 
206         switch (cl->status_code) {
207         case 416:
208                 if (!quiet)
209                         fprintf(stderr, "File download already fully retrieved; nothing to do.\n");
210                 request_done(cl);
211                 break;
212         case 206:
213                 if (!cur_resume) {
214                         if (!quiet)
215                                 fprintf(stderr, "Error: Partial content received, full content requested\n");
216                         error_ret = 8;
217                         request_done(cl);
218                         break;
219                 }
220 
221                 if (!tb[H_RANGE]) {
222                         if (!quiet)
223                                 fprintf(stderr, "Content-Range header is missing\n");
224                         error_ret = 8;
225                         break;
226                 }
227 
228                 if (sscanf(blobmsg_get_string(tb[H_RANGE]),
229                            "bytes %"PRIu64"-%"PRIu64"/%"PRIu64,
230                            &resume_offset, &resume_end, &resume_size) != 3) {
231                         if (!quiet)
232                                 fprintf(stderr, "Content-Range header is invalid\n");
233                         error_ret = 8;
234                         break;
235                 }
236                 /* fall through */
237         case 204:
238         case 200:
239                 if (no_output)
240                         break;
241 
242                 if (tb[H_LEN])
243                         out_len = strtoul(blobmsg_get_string(tb[H_LEN]), NULL, 10);
244 
245                 output_fd = open_output_file(cl->url->location, resume_offset);
246                 if (output_fd < 0) {
247                         if (!quiet)
248                                 perror("Cannot open output file");
249                         error_ret = 3;
250                         request_done(cl);
251                 }
252                 break;
253 
254         default:
255                 if (!quiet)
256                         fprintf(stderr, "HTTP error %d\n", cl->status_code);
257                 request_done(cl);
258                 error_ret = 8;
259                 break;
260         }
261 }
262 
263 static void read_data_cb(struct uclient *cl)
264 {
265         char buf[256];
266         ssize_t n;
267         int len;
268 
269         if (!no_output && output_fd < 0)
270                 return;
271 
272         while (1) {
273                 len = uclient_read(cl, buf, sizeof(buf));
274                 if (len <= 0)
275                         return;
276 
277                 out_bytes += len;
278                 if (!no_output) {
279                         n = write(output_fd, buf, len);
280                         if (n < 0)
281                                 return;
282                 }
283         }
284 }
285 
286 static void msg_connecting(struct uclient *cl)
287 {
288         char addr[INET6_ADDRSTRLEN];
289         int port;
290 
291         if (quiet)
292                 return;
293 
294         uclient_get_addr(addr, &port, &cl->remote_addr);
295         fprintf(stderr, "Connecting to %s:%d\n", addr, port);
296 }
297 
298 static void check_resume_offset(struct uclient *cl)
299 {
300         char range_str[64];
301         struct stat st;
302         char *file;
303         int ret;
304 
305         file = uclient_get_url_filename(cl->url->location, "index.html");
306         if (!file)
307                 return;
308 
309         ret = stat(file, &st);
310         free(file);
311         if (ret)
312                 return;
313 
314         if (!st.st_size)
315                 return;
316 
317         snprintf(range_str, sizeof(range_str), "bytes=%"PRIu64"-", (uint64_t) st.st_size);
318         uclient_http_set_header(cl, "Range", range_str);
319 }
320 
321 static int init_request(struct uclient *cl)
322 {
323         int rc;
324 
325         out_offset = 0;
326         out_bytes = 0;
327         out_len = 0;
328         uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify);
329 
330         if (timeout)
331                 cl->timeout_msecs = timeout * 1000;
332 
333         rc = uclient_connect(cl);
334         if (rc)
335                 return rc;
336 
337         msg_connecting(cl);
338 
339         rc = uclient_http_set_request_type(cl, post_data || post_file ? "POST" : "GET");
340         if (rc)
341                 return rc;
342 
343         uclient_http_reset_headers(cl);
344         uclient_http_set_header(cl, "User-Agent", user_agent);
345         if (cur_resume)
346                 check_resume_offset(cl);
347 
348         if (post_data) {
349                 uclient_http_set_header(cl, "Content-Type", "application/x-www-form-urlencoded");
350                 uclient_write(cl, post_data, strlen(post_data));
351         }
352         else if(post_file)
353         {
354                 FILE *input_file;
355                 uclient_http_set_header(cl, "Content-Type", "application/x-www-form-urlencoded");
356 
357                 input_file = fopen(post_file, "r");
358                 if (!input_file)
359                         return errno;
360 
361                 char tbuf[1024];
362                 size_t rlen = 0;
363                 do
364                 {
365                         rlen = fread(tbuf, 1, sizeof(tbuf), input_file);
366                         uclient_write(cl, tbuf, rlen);
367                 }
368                 while(rlen);
369 
370                 fclose(input_file);
371         }
372 
373         rc = uclient_request(cl);
374         if (rc)
375                 return rc;
376 
377         return 0;
378 }
379 
380 static void request_done(struct uclient *cl)
381 {
382         const char *proxy_url;
383 
384         if (n_urls) {
385                 proxy_url = get_proxy_url(*urls);
386                 if (proxy_url) {
387                         uclient_set_url(cl, proxy_url, NULL);
388                         uclient_set_proxy_url(cl, *urls, auth_str);
389                 } else {
390                         uclient_set_url(cl, *urls, auth_str);
391                 }
392                 n_urls--;
393                 cur_resume = resume;
394                 error_ret = init_request(cl);
395                 if (error_ret == 0)
396                         return;
397         }
398 
399         if (output_fd >= 0 && !opt_output_file) {
400                 close(output_fd);
401                 output_fd = -1;
402         }
403         uclient_disconnect(cl);
404         uloop_end();
405 }
406 
407 
408 static void eof_cb(struct uclient *cl)
409 {
410         if (!quiet) {
411                 pmt_update(&pmt_timer);
412                 uloop_timeout_cancel(&pmt_timer);
413                 fprintf(stderr, "\n");
414         }
415 
416         if (!cl->data_eof) {
417                 if (!quiet)
418                         fprintf(stderr, "Connection reset prematurely\n");
419                 error_ret = 4;
420         } else if (!quiet) {
421                 fprintf(stderr, "Download completed (%"PRIu64" bytes)\n", (uint64_t) out_bytes);
422         }
423         request_done(cl);
424 }
425 
426 static void handle_uclient_error(struct uclient *cl, int code)
427 {
428         const char *type = "Unknown error";
429         bool ignore = false;
430 
431         switch(code) {
432         case UCLIENT_ERROR_CONNECT:
433                 type = "Connection failed";
434                 error_ret = 4;
435                 break;
436         case UCLIENT_ERROR_TIMEDOUT:
437                 type = "Connection timed out";
438                 error_ret = 4;
439                 break;
440         case UCLIENT_ERROR_SSL_INVALID_CERT:
441                 type = "Invalid SSL certificate";
442                 ignore = !verify;
443                 error_ret = 5;
444                 break;
445         case UCLIENT_ERROR_SSL_CN_MISMATCH:
446                 type = "Server hostname does not match SSL certificate";
447                 ignore = !verify;
448                 error_ret = 5;
449                 break;
450         default:
451                 error_ret = 1;
452                 break;
453         }
454 
455         if (!quiet)
456                 fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : "");
457 
458         if (ignore)
459                 error_ret = 0;
460         else
461                 request_done(cl);
462 }
463 
464 static const struct uclient_cb cb = {
465         .header_done = header_done_cb,
466         .data_read = read_data_cb,
467         .data_eof = eof_cb,
468         .error = handle_uclient_error,
469 };
470 
471 static int usage(const char *progname)
472 {
473         fprintf(stderr,
474                 "Usage: %s [options] <URL>\n"
475                 "Options:\n"
476                 "       -4                              Use IPv4 only\n"
477                 "       -6                              Use IPv6 only\n"
478                 "       -O <file>                       Redirect output to file (use \"-\" for stdout)\n"
479                 "       -P <dir>                        Set directory for output files\n"
480                 "       --quiet | -q                    Turn off status messages\n"
481                 "       --continue | -c                 Continue a partially-downloaded file\n"
482                 "       --user=<user>                   HTTP authentication username\n"
483                 "       --password=<password>           HTTP authentication password\n"
484                 "       --user-agent | -U <str>         Set HTTP user agent\n"
485                 "       --post-data=STRING              use the POST method; send STRING as the data\n"
486                 "       --post-file=FILE                use the POST method; send FILE as the data\n"
487                 "       --spider | -s                   Spider mode - only check file existence\n"
488                 "       --timeout=N | -T N              Set connect/request timeout to N seconds\n"
489                 "       --proxy=on | -Y on              Enable interpretation of proxy env vars (default)\n"
490                 "       --proxy=off | -Y off |\n"
491                 "       --no-proxy                      Disable interpretation of proxy env vars\n"
492                 "\n"
493                 "HTTPS options:\n"
494                 "       --ca-certificate=<cert>         Load CA certificates from file <cert>\n"
495                 "       --no-check-certificate          don't validate the server's certificate\n"
496                 "       --ciphers=<cipherlist>          Set the cipher list string\n"
497                 "\n", progname);
498         return 1;
499 }
500 
501 static void init_ca_cert(void)
502 {
503         glob_t gl;
504         unsigned int i;
505 
506         glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
507         for (i = 0; i < gl.gl_pathc; i++)
508                 ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
509         globfree(&gl);
510 }
511 
512 static void init_ustream_ssl(void)
513 {
514         void *dlh;
515 
516         dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
517         if (!dlh)
518                 return;
519 
520         ssl_ops = dlsym(dlh, "ustream_ssl_ops");
521         if (!ssl_ops)
522                 return;
523 
524         ssl_ctx = ssl_ops->context_new(false);
525 }
526 
527 static int no_ssl(const char *progname)
528 {
529         fprintf(stderr,
530                 "%s: SSL support not available, please install one of the "
531                 "libustream-.*[ssl|tls] packages as well as the ca-bundle and "
532                 "ca-certificates packages.\n",
533                 progname);
534 
535         return 1;
536 }
537 
538 enum {
539         L_NO_CHECK_CERTIFICATE,
540         L_CA_CERTIFICATE,
541         L_CIPHERS,
542         L_USER,
543         L_PASSWORD,
544         L_USER_AGENT,
545         L_POST_DATA,
546         L_POST_FILE,
547         L_SPIDER,
548         L_TIMEOUT,
549         L_CONTINUE,
550         L_PROXY,
551         L_NO_PROXY,
552         L_QUIET,
553 };
554 
555 static const struct option longopts[] = {
556         [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument, NULL, 0 },
557         [L_CA_CERTIFICATE] = { "ca-certificate", required_argument, NULL, 0 },
558         [L_CIPHERS] = { "ciphers", required_argument, NULL, 0 },
559         [L_USER] = { "user", required_argument, NULL, 0 },
560         [L_PASSWORD] = { "password", required_argument, NULL, 0 },
561         [L_USER_AGENT] = { "user-agent", required_argument, NULL, 0 },
562         [L_POST_DATA] = { "post-data", required_argument, NULL, 0 },
563         [L_POST_FILE] = { "post-file", required_argument, NULL, 0 },
564         [L_SPIDER] = { "spider", no_argument, NULL, 0 },
565         [L_TIMEOUT] = { "timeout", required_argument, NULL, 0 },
566         [L_CONTINUE] = { "continue", no_argument, NULL, 0 },
567         [L_PROXY] = { "proxy", required_argument, NULL, 0 },
568         [L_NO_PROXY] = { "no-proxy", no_argument, NULL, 0 },
569         [L_QUIET] = { "quiet", no_argument, NULL, 0 },
570         {}
571 };
572 
573 
574 
575 int main(int argc, char **argv)
576 {
577         const char *progname = argv[0];
578         const char *proxy_url;
579         char *username = NULL;
580         char *password = NULL;
581         struct uclient *cl;
582         int longopt_idx = 0;
583         bool has_cert = false;
584         int i, ch;
585         int rc;
586         int af = -1;
587 
588         signal(SIGPIPE, SIG_IGN);
589         init_ustream_ssl();
590 
591         while ((ch = getopt_long(argc, argv, "46cO:P:qsT:U:Y:", longopts, &longopt_idx)) != -1) {
592                 switch(ch) {
593                 case 0:
594                         switch (longopt_idx) {
595                         case L_NO_CHECK_CERTIFICATE:
596                                 verify = false;
597                                 if (ssl_ctx)
598                                         ssl_ops->context_set_require_validation(ssl_ctx, verify);
599                                 break;
600                         case L_CA_CERTIFICATE:
601                                 has_cert = true;
602                                 if (ssl_ctx)
603                                         ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg);
604                                 break;
605                         case L_CIPHERS:
606                                 if (ssl_ctx) {
607                                         if (ssl_ops->context_set_ciphers(ssl_ctx, optarg)) {
608                                                 if (!quiet)
609                                                         fprintf(stderr, "No recognized ciphers in cipher list\n");
610                                                 exit(1);
611                                         }
612                                 }
613                                 break;
614                         case L_USER:
615                                 if (!strlen(optarg))
616                                         break;
617                                 username = strdupa(optarg);
618                                 memset(optarg, '*', strlen(optarg));
619                                 break;
620                         case L_PASSWORD:
621                                 if (!strlen(optarg))
622                                         break;
623                                 password = strdupa(optarg);
624                                 memset(optarg, '*', strlen(optarg));
625                                 break;
626                         case L_USER_AGENT:
627                                 user_agent = optarg;
628                                 break;
629                         case L_POST_DATA:
630                                 post_data = optarg;
631                                 break;
632                         case L_POST_FILE:
633                                 post_file = optarg;
634                                 break;
635                         case L_SPIDER:
636                                 no_output = true;
637                                 break;
638                         case L_TIMEOUT:
639                                 timeout = atoi(optarg);
640                                 break;
641                         case L_CONTINUE:
642                                 resume = true;
643                                 break;
644                         case L_PROXY:
645                                 if (strcmp(optarg, "on") != 0)
646                                         proxy = false;
647                                 break;
648                         case L_NO_PROXY:
649                                 proxy = false;
650                                 break;
651                         case L_QUIET:
652                                 quiet = true;
653                                 break;
654                         default:
655                                 return usage(progname);
656                         }
657                         break;
658                 case '4':
659                         af = AF_INET;
660                         break;
661                 case '6':
662                         af = AF_INET6;
663                         break;
664                 case 'c':
665                         resume = true;
666                         break;
667                 case 'U':
668                         user_agent = optarg;
669                         break;
670                 case 'O':
671                         opt_output_file = optarg;
672                         break;
673                 case 'P':
674                         if (chdir(optarg)) {
675                                 if (!quiet)
676                                         perror("Change output directory");
677                                 exit(1);
678                         }
679                         break;
680                 case 'q':
681                         quiet = true;
682                         break;
683                 case 's':
684                         no_output = true;
685                         break;
686                 case 'T':
687                         timeout = atoi(optarg);
688                         break;
689                 case 'Y':
690                         if (strcmp(optarg, "on") != 0)
691                                 proxy = false;
692                         break;
693                 default:
694                         return usage(progname);
695                 }
696         }
697 
698         argv += optind;
699         argc -= optind;
700 
701         if (verify && !has_cert)
702                 default_certs = true;
703 
704         if (argc < 1)
705                 return usage(progname);
706 
707         if (!ssl_ctx) {
708                 for (i = 0; i < argc; i++) {
709                         if (!strncmp(argv[i], "https", 5))
710                                 return no_ssl(progname);
711                 }
712         }
713 
714         urls = argv + 1;
715         n_urls = argc - 1;
716 
717         uloop_init();
718 
719         if (username) {
720                 if (password) {
721                         rc = asprintf(&auth_str, "%s:%s", username, password);
722                         if (rc < 0)
723                                 return rc;
724                 } else
725                         auth_str = username;
726         }
727 
728         if (!quiet)
729                 fprintf(stderr, "Downloading '%s'\n", argv[0]);
730 
731         proxy_url = get_proxy_url(argv[0]);
732         if (proxy_url) {
733                 cl = uclient_new(proxy_url, auth_str, &cb);
734                 if (cl)
735                     uclient_set_proxy_url(cl, argv[0], NULL);
736         } else {
737                 cl = uclient_new(argv[0], auth_str, &cb);
738         }
739         if (!cl) {
740                 fprintf(stderr, "Failed to allocate uclient context\n");
741                 return 1;
742         }
743         if (af >= 0)
744             uclient_http_set_address_family(cl, af);
745 
746         if (ssl_ctx && default_certs)
747                 init_ca_cert();
748 
749         cur_resume = resume;
750         rc = init_request(cl);
751         if (!rc) {
752                 /* no error received, we can enter main loop */
753                 uloop_run();
754         } else {
755                 fprintf(stderr, "Failed to send request: %s\n", strerror(rc));
756                 error_ret = 4;
757         }
758 
759         uloop_done();
760 
761         uclient_free(cl);
762 
763         if (output_fd >= 0 && output_fd != STDOUT_FILENO)
764                 close(output_fd);
765 
766         if (ssl_ctx)
767                 ssl_ops->context_free(ssl_ctx);
768 
769         return error_ret;
770 }
771 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt