uclient-fetch.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. #include <unistd.h>
  19. #include <stdio.h>
  20. #include <dlfcn.h>
  21. #include <getopt.h>
  22. #include <fcntl.h>
  23. #include <libubox/blobmsg.h>
  24. #include "uclient.h"
  25. #include "uclient-utils.h"
  26. #ifdef __APPLE__
  27. #define LIB_EXT "dylib"
  28. #else
  29. #define LIB_EXT "so"
  30. #endif
  31. static struct ustream_ssl_ctx *ssl_ctx;
  32. static const struct ustream_ssl_ops *ssl_ops;
  33. static int quiet = false;
  34. static bool verify = true;
  35. static const char *output_file;
  36. static int output_fd = -1;
  37. static int error_ret;
  38. static int open_output_file(const char *path, bool create)
  39. {
  40. char *filename;
  41. int flags = O_WRONLY;
  42. int ret;
  43. if (create)
  44. flags |= O_CREAT;
  45. if (output_file) {
  46. if (!strcmp(output_file, "-"))
  47. return STDOUT_FILENO;
  48. return open(output_file, flags, 0644);
  49. }
  50. /* Don't automatically overwrite files if the name is derived from the URL */
  51. if (create)
  52. flags |= O_EXCL;
  53. filename = uclient_get_url_filename(path, "index.html");
  54. ret = open(filename, flags, 0644);
  55. free(filename);
  56. return ret;
  57. }
  58. static void request_done(struct uclient *cl)
  59. {
  60. if (output_fd >= 0 && !output_file) {
  61. close(output_fd);
  62. output_fd = -1;
  63. }
  64. uclient_disconnect(cl);
  65. uloop_end();
  66. }
  67. static void header_done_cb(struct uclient *cl)
  68. {
  69. static int retries;
  70. if (retries < 10 && uclient_http_redirect(cl)) {
  71. if (!quiet)
  72. fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
  73. retries++;
  74. return;
  75. }
  76. retries = 0;
  77. switch (cl->status_code) {
  78. case 204:
  79. case 200:
  80. output_fd = open_output_file(cl->url->location, true);
  81. if (output_fd < 0) {
  82. if (!quiet)
  83. perror("Cannot open output file");
  84. error_ret = 3;
  85. request_done(cl);
  86. }
  87. break;
  88. default:
  89. if (!quiet)
  90. fprintf(stderr, "HTTP error %d\n", cl->status_code);
  91. request_done(cl);
  92. error_ret = 8;
  93. break;
  94. }
  95. }
  96. static void read_data_cb(struct uclient *cl)
  97. {
  98. char buf[256];
  99. int len;
  100. if (output_fd < 0)
  101. return;
  102. while (1) {
  103. len = uclient_read(cl, buf, sizeof(buf));
  104. if (!len)
  105. return;
  106. write(output_fd, buf, len);
  107. }
  108. }
  109. static void msg_connecting(struct uclient *cl)
  110. {
  111. char addr[INET6_ADDRSTRLEN];
  112. int port;
  113. if (quiet)
  114. return;
  115. uclient_get_addr(addr, &port, &cl->remote_addr);
  116. fprintf(stderr, "Connecting to %s %s:%d\n", cl->url->host, addr, port);
  117. }
  118. static void init_request(struct uclient *cl)
  119. {
  120. uclient_connect(cl);
  121. msg_connecting(cl);
  122. uclient_http_set_request_type(cl, "GET");
  123. uclient_request(cl);
  124. }
  125. static void eof_cb(struct uclient *cl)
  126. {
  127. if (!cl->data_eof) {
  128. if (!quiet)
  129. fprintf(stderr, "Connection reset prematurely\n");
  130. error_ret = 4;
  131. }
  132. request_done(cl);
  133. }
  134. static void handle_uclient_error(struct uclient *cl, int code)
  135. {
  136. const char *type = "Unknown error";
  137. bool ignore = false;
  138. switch(code) {
  139. case UCLIENT_ERROR_CONNECT:
  140. type = "Connection failed";
  141. error_ret = 4;
  142. break;
  143. case UCLIENT_ERROR_SSL_INVALID_CERT:
  144. type = "Invalid SSL certificate";
  145. ignore = !verify;
  146. error_ret = 5;
  147. break;
  148. case UCLIENT_ERROR_SSL_CN_MISMATCH:
  149. type = "Server hostname does not match SSL certificate";
  150. ignore = !verify;
  151. error_ret = 5;
  152. break;
  153. default:
  154. error_ret = 1;
  155. break;
  156. }
  157. if (!quiet)
  158. fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : "");
  159. if (ignore)
  160. error_ret = 0;
  161. else
  162. request_done(cl);
  163. }
  164. static const struct uclient_cb cb = {
  165. .header_done = header_done_cb,
  166. .data_read = read_data_cb,
  167. .data_eof = eof_cb,
  168. .error = handle_uclient_error,
  169. };
  170. static int usage(const char *progname)
  171. {
  172. fprintf(stderr,
  173. "Usage: %s [options] <URL>\n"
  174. "Options:\n"
  175. " -q: Turn off status messages\n"
  176. " -O <file>: Redirect output to file (use \"-\" for stdout)\n"
  177. "\n"
  178. "HTTPS options:\n"
  179. " --ca-certificate=<cert>: Load CA certificates from file <cert>\n"
  180. " --no-check-certificate: don't validate the server's certificate\n"
  181. "\n", progname);
  182. return 1;
  183. }
  184. static void init_ustream_ssl(void)
  185. {
  186. void *dlh;
  187. dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
  188. if (!dlh)
  189. return;
  190. ssl_ops = dlsym(dlh, "ustream_ssl_ops");
  191. if (!ssl_ops)
  192. return;
  193. ssl_ctx = ssl_ops->context_new(false);
  194. }
  195. static int no_ssl(const char *progname)
  196. {
  197. fprintf(stderr, "%s: SSL support not available, please install ustream-ssl\n", progname);
  198. return 1;
  199. }
  200. enum {
  201. L_NO_CHECK_CERTIFICATE,
  202. L_CA_CERTIFICATE,
  203. };
  204. static const struct option longopts[] = {
  205. [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument },
  206. [L_CA_CERTIFICATE] = { "ca-certificate", required_argument },
  207. {}
  208. };
  209. int main(int argc, char **argv)
  210. {
  211. const char *progname = argv[0];
  212. struct uclient *cl;
  213. int ch;
  214. int longopt_idx = 0;
  215. init_ustream_ssl();
  216. while ((ch = getopt_long(argc, argv, "qO:", longopts, &longopt_idx)) != -1) {
  217. switch(ch) {
  218. case 0:
  219. switch (longopt_idx) {
  220. case L_NO_CHECK_CERTIFICATE:
  221. verify = false;
  222. break;
  223. case L_CA_CERTIFICATE:
  224. if (ssl_ctx)
  225. ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg);
  226. break;
  227. default:
  228. return usage(progname);
  229. }
  230. break;
  231. case 'O':
  232. output_file = optarg;
  233. break;
  234. case 'q':
  235. quiet = true;
  236. break;
  237. default:
  238. return usage(progname);
  239. }
  240. }
  241. argv += optind;
  242. argc -= optind;
  243. if (argc != 1)
  244. return usage(progname);
  245. if (!strncmp(argv[0], "https", 5) && !ssl_ctx)
  246. return no_ssl(progname);
  247. uloop_init();
  248. cl = uclient_new(argv[0], NULL, &cb);
  249. if (!cl) {
  250. fprintf(stderr, "Failed to allocate uclient context\n");
  251. return 1;
  252. }
  253. if (ssl_ctx)
  254. uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify);
  255. init_request(cl);
  256. uloop_run();
  257. uloop_done();
  258. uclient_free(cl);
  259. if (output_fd >= 0 && output_fd != STDOUT_FILENO)
  260. close(output_fd);
  261. if (ssl_ctx)
  262. ssl_ops->context_free(ssl_ctx);
  263. return error_ret;
  264. }