8d03c3dc27c2ce3340e59ea5a99808b0f6d187b2
[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 <unistd.h>
21 #include <stdio.h>
22 #include <dlfcn.h>
23 #include <getopt.h>
24 #include <fcntl.h>
25 #include <glob.h>
26
27 #include <libubox/blobmsg.h>
28
29 #include "uclient.h"
30 #include "uclient-utils.h"
31
32 #ifdef __APPLE__
33 #define LIB_EXT "dylib"
34 #else
35 #define LIB_EXT "so"
36 #endif
37
38 static const char *user_agent = "uclient-fetch";
39 static struct ustream_ssl_ctx *ssl_ctx;
40 static const struct ustream_ssl_ops *ssl_ops;
41 static int quiet = false;
42 static bool verify = true;
43 static bool default_certs = false;
44 static const char *output_file;
45 static int output_fd = -1;
46 static int error_ret;
47 static int out_bytes;
48 static char *username;
49 static char *password;
50 static char *auth_str;
51 static char **urls;
52 static int n_urls;
53
54 static void request_done(struct uclient *cl);
55
56 static int open_output_file(const char *path, bool create)
57 {
58 char *filename;
59 int flags = O_WRONLY;
60 int ret;
61
62 if (create)
63 flags |= O_CREAT | O_EXCL;
64
65 if (output_file) {
66 if (!strcmp(output_file, "-"))
67 return STDOUT_FILENO;
68
69 if (!quiet)
70 fprintf(stderr, "Writing to stdout\n");
71
72 unlink(output_file);
73 return open(output_file, flags, 0644);
74 }
75
76 filename = uclient_get_url_filename(path, "index.html");
77 if (!quiet)
78 fprintf(stderr, "Writing to '%s'\n", filename);
79 ret = open(filename, flags, 0644);
80 free(filename);
81
82 return ret;
83 }
84
85 static void header_done_cb(struct uclient *cl)
86 {
87 static int retries;
88
89 if (retries < 10 && uclient_http_redirect(cl)) {
90 if (!quiet)
91 fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
92
93 retries++;
94 return;
95 }
96
97 retries = 0;
98 switch (cl->status_code) {
99 case 204:
100 case 200:
101 output_fd = open_output_file(cl->url->location, true);
102 if (output_fd < 0) {
103 if (!quiet)
104 perror("Cannot open output file");
105 error_ret = 3;
106 request_done(cl);
107 }
108 break;
109
110 default:
111 if (!quiet)
112 fprintf(stderr, "HTTP error %d\n", cl->status_code);
113 request_done(cl);
114 error_ret = 8;
115 break;
116 }
117 }
118
119 static void read_data_cb(struct uclient *cl)
120 {
121 char buf[256];
122 int len;
123
124 if (output_fd < 0)
125 return;
126
127 while (1) {
128 len = uclient_read(cl, buf, sizeof(buf));
129 if (!len)
130 return;
131
132 out_bytes += len;
133 write(output_fd, buf, len);
134 }
135 }
136
137 static void msg_connecting(struct uclient *cl)
138 {
139 char addr[INET6_ADDRSTRLEN];
140 int port;
141
142 if (quiet)
143 return;
144
145 uclient_get_addr(addr, &port, &cl->remote_addr);
146 fprintf(stderr, "Connecting to %s:%d\n", addr, port);
147 }
148
149 static int init_request(struct uclient *cl)
150 {
151 int rc;
152
153 out_bytes = 0;
154 uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify);
155
156 rc = uclient_connect(cl);
157 if (rc)
158 return rc;
159
160 msg_connecting(cl);
161
162 rc = uclient_http_set_request_type(cl, "GET");
163 if (rc)
164 return rc;
165
166 uclient_http_reset_headers(cl);
167 uclient_http_set_header(cl, "User-Agent", user_agent);
168
169 rc = uclient_request(cl);
170 if (rc)
171 return rc;
172
173 return 0;
174 }
175
176 static void request_done(struct uclient *cl)
177 {
178 if (n_urls) {
179 uclient_set_url(cl, *urls, auth_str);
180 n_urls--;
181 error_ret = init_request(cl);
182 if (error_ret == 0)
183 return;
184 }
185
186 if (output_fd >= 0 && !output_file) {
187 close(output_fd);
188 output_fd = -1;
189 }
190 uclient_disconnect(cl);
191 uloop_end();
192 }
193
194
195 static void eof_cb(struct uclient *cl)
196 {
197 if (!cl->data_eof) {
198 if (!quiet)
199 fprintf(stderr, "Connection reset prematurely\n");
200 error_ret = 4;
201 } else if (!quiet) {
202 fprintf(stderr, "Download completed (%d bytes)\n", out_bytes);
203 }
204 request_done(cl);
205 }
206
207 static void handle_uclient_error(struct uclient *cl, int code)
208 {
209 const char *type = "Unknown error";
210 bool ignore = false;
211
212 switch(code) {
213 case UCLIENT_ERROR_CONNECT:
214 type = "Connection failed";
215 error_ret = 4;
216 break;
217 case UCLIENT_ERROR_TIMEDOUT:
218 type = "Connection timed out";
219 error_ret = 4;
220 break;
221 case UCLIENT_ERROR_SSL_INVALID_CERT:
222 type = "Invalid SSL certificate";
223 ignore = !verify;
224 error_ret = 5;
225 break;
226 case UCLIENT_ERROR_SSL_CN_MISMATCH:
227 type = "Server hostname does not match SSL certificate";
228 ignore = !verify;
229 error_ret = 5;
230 break;
231 default:
232 error_ret = 1;
233 break;
234 }
235
236 if (!quiet)
237 fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : "");
238
239 if (ignore)
240 error_ret = 0;
241 else
242 request_done(cl);
243 }
244
245 static const struct uclient_cb cb = {
246 .header_done = header_done_cb,
247 .data_read = read_data_cb,
248 .data_eof = eof_cb,
249 .error = handle_uclient_error,
250 };
251
252 static int usage(const char *progname)
253 {
254 fprintf(stderr,
255 "Usage: %s [options] <URL>\n"
256 "Options:\n"
257 " -q: Turn off status messages\n"
258 " -O <file>: Redirect output to file (use \"-\" for stdout)\n"
259 " --user=<user> HTTP authentication username\n"
260 " --password=<password> HTTP authentication password\n"
261 " --user-agent|-U <str> Set HTTP user agent\n"
262 "\n"
263 "HTTPS options:\n"
264 " --ca-certificate=<cert>: Load CA certificates from file <cert>\n"
265 " --no-check-certificate: don't validate the server's certificate\n"
266 "\n", progname);
267 return 1;
268 }
269
270 static void init_ca_cert(void)
271 {
272 glob_t gl;
273 int i;
274
275 glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
276 for (i = 0; i < gl.gl_pathc; i++)
277 ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
278 }
279
280 static void init_ustream_ssl(void)
281 {
282 void *dlh;
283
284 dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL);
285 if (!dlh)
286 return;
287
288 ssl_ops = dlsym(dlh, "ustream_ssl_ops");
289 if (!ssl_ops)
290 return;
291
292 ssl_ctx = ssl_ops->context_new(false);
293 }
294
295 static int no_ssl(const char *progname)
296 {
297 fprintf(stderr, "%s: SSL support not available, please install ustream-ssl\n", progname);
298 return 1;
299 }
300
301 enum {
302 L_NO_CHECK_CERTIFICATE,
303 L_CA_CERTIFICATE,
304 L_USER,
305 L_PASSWORD,
306 L_USER_AGENT,
307 };
308
309 static const struct option longopts[] = {
310 [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument },
311 [L_CA_CERTIFICATE] = { "ca-certificate", required_argument },
312 [L_USER] = { "user", required_argument },
313 [L_PASSWORD] = { "password", required_argument },
314 [L_USER_AGENT] = { "user-agent", required_argument },
315 {}
316 };
317
318
319
320 int main(int argc, char **argv)
321 {
322 const char *progname = argv[0];
323 struct uclient *cl;
324 int longopt_idx = 0;
325 bool has_cert = false;
326 int i, ch;
327 int rc;
328
329 init_ustream_ssl();
330
331 while ((ch = getopt_long(argc, argv, "qO:U:", longopts, &longopt_idx)) != -1) {
332 switch(ch) {
333 case 0:
334 switch (longopt_idx) {
335 case L_NO_CHECK_CERTIFICATE:
336 verify = false;
337 break;
338 case L_CA_CERTIFICATE:
339 has_cert = true;
340 if (ssl_ctx)
341 ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg);
342 break;
343 case L_USER:
344 if (!strlen(optarg))
345 break;
346 username = strdup(optarg);
347 memset(optarg, '*', strlen(optarg));
348 break;
349 case L_PASSWORD:
350 if (!strlen(optarg))
351 break;
352 password = strdup(optarg);
353 memset(optarg, '*', strlen(optarg));
354 break;
355 case L_USER_AGENT:
356 user_agent = optarg;
357 break;
358 default:
359 return usage(progname);
360 }
361 break;
362 case 'U':
363 user_agent = optarg;
364 break;
365 case 'O':
366 output_file = optarg;
367 break;
368 case 'q':
369 quiet = true;
370 break;
371 default:
372 return usage(progname);
373 }
374 }
375
376 argv += optind;
377 argc -= optind;
378
379 if (verify && !has_cert)
380 default_certs = true;
381
382 if (argc < 1)
383 return usage(progname);
384
385 if (!ssl_ctx) {
386 for (i = 0; i < argc; i++) {
387 if (!strncmp(argv[i], "https", 5))
388 return no_ssl(progname);
389 }
390 }
391
392 urls = argv + 1;
393 n_urls = argc - 1;
394
395 uloop_init();
396
397 if (username) {
398 if (password)
399 asprintf(&auth_str, "%s:%s", username, password);
400 else
401 auth_str = username;
402 }
403
404 if (!quiet)
405 fprintf(stderr, "Downloading '%s'\n", argv[0]);
406
407 cl = uclient_new(argv[0], auth_str, &cb);
408 if (!cl) {
409 fprintf(stderr, "Failed to allocate uclient context\n");
410 return 1;
411 }
412
413 if (ssl_ctx && default_certs)
414 init_ca_cert();
415
416 rc = init_request(cl);
417 if (!rc) {
418 /* no error received, we can enter main loop */
419 uloop_run();
420 } else {
421 fprintf(stderr, "Failed to establish connection\n");
422 error_ret = 4;
423 }
424
425 uloop_done();
426
427 uclient_free(cl);
428
429 if (output_fd >= 0 && output_fd != STDOUT_FILENO)
430 close(output_fd);
431
432 if (ssl_ctx)
433 ssl_ops->context_free(ssl_ctx);
434
435 return error_ret;
436 }