blob: 5501ffdd0043c61ab905b15b2741602161d23194 [file] [log] [blame]
Jerome Forissier359d4ed2024-10-16 12:04:09 +02001// SPDX-License-Identifier: GPL-2.0+
2/* Copyright (C) 2024 Linaro Ltd. */
3
4#include <command.h>
5#include <console.h>
6#include <display_options.h>
7#include <efi_loader.h>
8#include <image.h>
9#include <lwip/apps/http_client.h>
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020010#include "lwip/altcp_tls.h"
Jerome Forissier359d4ed2024-10-16 12:04:09 +020011#include <lwip/timeouts.h>
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020012#include <rng.h>
Jerome Forissier359d4ed2024-10-16 12:04:09 +020013#include <mapmem.h>
14#include <net.h>
15#include <time.h>
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020016#include <dm/uclass.h>
Jerome Forissier359d4ed2024-10-16 12:04:09 +020017
Heinrich Schuchardtd064fc92024-11-08 18:45:26 +010018#define SERVER_NAME_SIZE 254
Jerome Forissier359d4ed2024-10-16 12:04:09 +020019#define HTTP_PORT_DEFAULT 80
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020020#define HTTPS_PORT_DEFAULT 443
Jerome Forissier359d4ed2024-10-16 12:04:09 +020021#define PROGRESS_PRINT_STEP_BYTES (100 * 1024)
22
23enum done_state {
Jerome Forissier97083502024-11-07 12:27:57 +010024 NOT_DONE = 0,
25 SUCCESS = 1,
26 FAILURE = 2
Jerome Forissier359d4ed2024-10-16 12:04:09 +020027};
28
29struct wget_ctx {
30 char *path;
31 ulong daddr;
32 ulong saved_daddr;
33 ulong size;
34 ulong prevsize;
35 ulong start_time;
36 enum done_state done;
37};
38
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020039bool wget_validate_uri(char *uri);
40
41int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len,
42 size_t *olen)
43{
44 struct udevice *dev;
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020045 int ret;
46
47 *olen = 0;
48
49 ret = uclass_get_device(UCLASS_RNG, 0, &dev);
50 if (ret) {
51 log_err("Failed to get an rng: %d\n", ret);
52 return ret;
53 }
Ilias Apalodimas4276a3c2024-11-14 16:29:15 +020054 ret = dm_rng_read(dev, output, len);
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020055 if (ret)
56 return ret;
57
Ilias Apalodimas4276a3c2024-11-14 16:29:15 +020058 *olen = len;
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020059
60 return 0;
61}
62
63static int parse_url(char *url, char *host, u16 *port, char **path,
64 bool *is_https)
Jerome Forissier359d4ed2024-10-16 12:04:09 +020065{
66 char *p, *pp;
67 long lport;
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020068 size_t prefix_len = 0;
Jerome Forissier359d4ed2024-10-16 12:04:09 +020069
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020070 if (!wget_validate_uri(url)) {
71 log_err("Invalid URL. Use http(s)://\n");
72 return -EINVAL;
73 }
74
75 *is_https = false;
76 *port = HTTP_PORT_DEFAULT;
77 prefix_len = strlen("http://");
Jerome Forissier359d4ed2024-10-16 12:04:09 +020078 p = strstr(url, "http://");
79 if (!p) {
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020080 p = strstr(url, "https://");
81 prefix_len = strlen("https://");
82 *port = HTTPS_PORT_DEFAULT;
83 *is_https = true;
Jerome Forissier359d4ed2024-10-16 12:04:09 +020084 }
85
Ilias Apalodimas99618ca2024-11-10 10:28:40 +020086 p += prefix_len;
Jerome Forissier359d4ed2024-10-16 12:04:09 +020087
88 /* Parse hostname */
89 pp = strchr(p, ':');
90 if (!pp)
91 pp = strchr(p, '/');
92 if (!pp)
93 return -EINVAL;
94
95 if (p + SERVER_NAME_SIZE <= pp)
96 return -EINVAL;
97
98 memcpy(host, p, pp - p);
99 host[pp - p] = '\0';
100
101 if (*pp == ':') {
102 /* Parse port number */
103 p = pp + 1;
104 lport = simple_strtol(p, &pp, 10);
105 if (pp && *pp != '/')
106 return -EINVAL;
107 if (lport > 65535)
108 return -EINVAL;
109 *port = (u16)lport;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200110 }
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200111
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200112 if (*pp != '/')
113 return -EINVAL;
114 *path = pp;
115
116 return 0;
117}
118
119/*
120 * Legacy syntax support
121 * Convert [<server_name_or_ip>:]filename into a URL if needed
122 */
123static int parse_legacy_arg(char *arg, char *nurl, size_t rem)
124{
125 char *p = nurl;
126 size_t n;
127 char *col = strchr(arg, ':');
128 char *env;
129 char *server;
130 char *path;
131
132 if (strstr(arg, "http") == arg) {
133 n = snprintf(nurl, rem, "%s", arg);
134 if (n < 0 || n > rem)
135 return -1;
136 return 0;
137 }
138
139 n = snprintf(p, rem, "%s", "http://");
140 if (n < 0 || n > rem)
141 return -1;
142 p += n;
143 rem -= n;
144
145 if (col) {
146 n = col - arg;
147 server = arg;
148 path = col + 1;
149 } else {
150 env = env_get("httpserverip");
151 if (!env)
152 env = env_get("serverip");
153 if (!env) {
154 log_err("error: httpserver/serverip has to be set\n");
155 return -1;
156 }
157 n = strlen(env);
158 server = env;
159 path = arg;
160 }
161
162 if (rem < n)
163 return -1;
Jerome Forissierc7157322024-11-26 15:45:06 +0100164 strncpy(p, server, n);
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200165 p += n;
166 rem -= n;
167 if (rem < 1)
168 return -1;
169 *p = '/';
170 p++;
171 rem--;
172 n = strlen(path);
173 if (rem < n)
174 return -1;
Jerome Forissierc7157322024-11-26 15:45:06 +0100175 strncpy(p, path, n);
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200176 p += n;
177 rem -= n;
178 if (rem < 1)
179 return -1;
180 *p = '\0';
181
182 return 0;
183}
184
185static err_t httpc_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *pbuf,
186 err_t err)
187{
188 struct wget_ctx *ctx = arg;
189 struct pbuf *buf;
190
191 if (!pbuf)
192 return ERR_BUF;
193
194 if (!ctx->start_time)
195 ctx->start_time = get_timer(0);
196
197 for (buf = pbuf; buf; buf = buf->next) {
198 memcpy((void *)ctx->daddr, buf->payload, buf->len);
199 ctx->daddr += buf->len;
200 ctx->size += buf->len;
201 if (ctx->size - ctx->prevsize > PROGRESS_PRINT_STEP_BYTES) {
202 printf("#");
203 ctx->prevsize = ctx->size;
204 }
205 }
206
207 altcp_recved(pcb, pbuf->tot_len);
208 pbuf_free(pbuf);
209 return ERR_OK;
210}
211
212static void httpc_result_cb(void *arg, httpc_result_t httpc_result,
213 u32_t rx_content_len, u32_t srv_res, err_t err)
214{
215 struct wget_ctx *ctx = arg;
216 ulong elapsed;
217
218 if (httpc_result != HTTPC_RESULT_OK) {
219 log_err("\nHTTP client error %d\n", httpc_result);
220 ctx->done = FAILURE;
221 return;
222 }
223 if (srv_res != 200) {
224 log_err("\nHTTP server error %d\n", srv_res);
225 ctx->done = FAILURE;
226 return;
227 }
228
229 elapsed = get_timer(ctx->start_time);
230 if (!elapsed)
231 elapsed = 1;
232 if (rx_content_len > PROGRESS_PRINT_STEP_BYTES)
233 printf("\n");
234 printf("%u bytes transferred in %lu ms (", rx_content_len, elapsed);
235 print_size(rx_content_len / elapsed * 1000, "/s)\n");
236 printf("Bytes transferred = %lu (%lx hex)\n", ctx->size, ctx->size);
237 efi_set_bootdev("Net", "", ctx->path, map_sysmem(ctx->saved_daddr, 0),
238 rx_content_len);
239 if (env_set_hex("filesize", rx_content_len) ||
240 env_set_hex("fileaddr", ctx->saved_daddr)) {
241 log_err("Could not set filesize or fileaddr\n");
242 ctx->done = FAILURE;
243 return;
244 }
245
246 ctx->done = SUCCESS;
247}
248
249static int wget_loop(struct udevice *udev, ulong dst_addr, char *uri)
250{
251 char server_name[SERVER_NAME_SIZE];
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200252#if defined CONFIG_WGET_HTTPS
253 altcp_allocator_t tls_allocator;
254#endif
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200255 httpc_connection_t conn;
256 httpc_state_t *state;
257 struct netif *netif;
258 struct wget_ctx ctx;
259 char *path;
260 u16 port;
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200261 bool is_https;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200262
263 ctx.daddr = dst_addr;
264 ctx.saved_daddr = dst_addr;
265 ctx.done = NOT_DONE;
266 ctx.size = 0;
267 ctx.prevsize = 0;
268 ctx.start_time = 0;
269
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200270 if (parse_url(uri, server_name, &port, &path, &is_https))
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200271 return CMD_RET_USAGE;
272
273 netif = net_lwip_new_netif(udev);
274 if (!netif)
275 return -1;
276
277 memset(&conn, 0, sizeof(conn));
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200278#if defined CONFIG_WGET_HTTPS
279 if (is_https) {
280 tls_allocator.alloc = &altcp_tls_alloc;
281 tls_allocator.arg =
282 altcp_tls_create_config_client(NULL, 0, server_name);
283
284 if (!tls_allocator.arg) {
285 log_err("error: Cannot create a TLS connection\n");
286 net_lwip_remove_netif(netif);
287 return -1;
288 }
289
290 conn.altcp_allocator = &tls_allocator;
291 }
292#endif
293
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200294 conn.result_fn = httpc_result_cb;
295 ctx.path = path;
296 if (httpc_get_file_dns(server_name, port, path, &conn, httpc_recv_cb,
297 &ctx, &state)) {
298 net_lwip_remove_netif(netif);
299 return CMD_RET_FAILURE;
300 }
301
302 while (!ctx.done) {
303 net_lwip_rx(udev, netif);
304 sys_check_timeouts();
305 if (ctrlc())
306 break;
307 }
308
309 net_lwip_remove_netif(netif);
310
311 if (ctx.done == SUCCESS)
312 return 0;
313
314 return -1;
315}
316
317int wget_with_dns(ulong dst_addr, char *uri)
318{
319 eth_set_current();
320
321 return wget_loop(eth_get_dev(), dst_addr, uri);
322}
323
324int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
325{
326 char *end;
327 char *url;
328 ulong dst_addr;
329 char nurl[1024];
330
331 if (argc < 2 || argc > 3)
332 return CMD_RET_USAGE;
333
334 dst_addr = hextoul(argv[1], &end);
Jerome Forissier97083502024-11-07 12:27:57 +0100335 if (end == (argv[1] + strlen(argv[1]))) {
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200336 if (argc < 3)
337 return CMD_RET_USAGE;
338 url = argv[2];
339 } else {
340 dst_addr = image_load_addr;
341 url = argv[1];
342 }
343
344 if (parse_legacy_arg(url, nurl, sizeof(nurl)))
Jerome Forissier97083502024-11-07 12:27:57 +0100345 return CMD_RET_FAILURE;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200346
347 if (wget_with_dns(dst_addr, nurl))
348 return CMD_RET_FAILURE;
349
350 return CMD_RET_SUCCESS;
351}
352
353/**
354 * wget_validate_uri() - validate the uri for wget
355 *
356 * @uri: uri string
357 *
358 * This function follows the current U-Boot wget implementation.
359 * scheme: only "http:" is supported
360 * authority:
361 * - user information: not supported
362 * - host: supported
363 * - port: not supported(always use the default port)
364 *
365 * Uri is expected to be correctly percent encoded.
366 * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
367 * and space character(0x20) are not allowed.
368 *
369 * TODO: stricter uri conformance check
370 *
371 * Return: true on success, false on failure
372 */
373bool wget_validate_uri(char *uri)
374{
375 char c;
376 bool ret = true;
377 char *str_copy, *s, *authority;
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200378 size_t prefix_len = 0;
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200379
380 for (c = 0x1; c < 0x21; c++) {
381 if (strchr(uri, c)) {
382 log_err("invalid character is used\n");
383 return false;
384 }
385 }
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200386
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200387 if (strchr(uri, 0x7f)) {
388 log_err("invalid character is used\n");
389 return false;
390 }
391
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200392 if (!strncmp(uri, "http://", strlen("http://"))) {
393 prefix_len = strlen("http://");
394 } else if (!strncmp(uri, "https://", strlen("https://"))) {
395 prefix_len = strlen("https://");
396 } else {
397 log_err("only http(s):// is supported\n");
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200398 return false;
399 }
Ilias Apalodimas99618ca2024-11-10 10:28:40 +0200400
Jerome Forissier359d4ed2024-10-16 12:04:09 +0200401 str_copy = strdup(uri);
402 if (!str_copy)
403 return false;
404
405 s = str_copy + strlen("http://");
406 authority = strsep(&s, "/");
407 if (!s) {
408 log_err("invalid uri, no file path\n");
409 ret = false;
410 goto out;
411 }
412 s = strchr(authority, '@');
413 if (s) {
414 log_err("user information is not supported\n");
415 ret = false;
416 goto out;
417 }
418
419out:
420 free(str_copy);
421
422 return ret;
423}