1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * WGET/HTTP support driver based on U-BOOT's nfs.c
4  * Copyright Duncan Hare <dh@synoia.com> 2017
5  */
6 
7 #include <asm/global_data.h>
8 #include <command.h>
9 #include <display_options.h>
10 #include <env.h>
11 #include <efi_loader.h>
12 #include <image.h>
13 #include <lmb.h>
14 #include <mapmem.h>
15 #include <net.h>
16 #include <net/tcp.h>
17 #include <net/wget.h>
18 #include <stdlib.h>
19 
20 DECLARE_GLOBAL_DATA_PTR;
21 
22 /* The default, change with environment variable 'httpdstp' */
23 #define SERVER_PORT		80
24 
25 #define HASHES_PER_LINE		65
26 
27 #define HTTP_MAX_HDR_LEN	2048
28 
29 #define HTTP_STATUS_BAD		0
30 #define HTTP_STATUS_OK		200
31 
32 static const char http_proto[] = "HTTP/1.0";
33 static const char http_eom[] = "\r\n\r\n";
34 static const char content_len[] = "Content-Length:";
35 static const char linefeed[] = "\r\n";
36 static struct in_addr web_server_ip;
37 static unsigned int server_port;
38 static unsigned long content_length;
39 static u32 http_hdr_size, max_rx_pos;
40 static int wget_tsize_num_hash;
41 
42 static char *image_url;
43 static enum net_loop_state wget_loop_state;
44 
45 /**
46  * store_block() - store block in memory
47  * @src: source of data
48  * @offset: offset
49  * @len: length
50  */
store_block(uchar * src,unsigned int offset,unsigned int len)51 static inline int store_block(uchar *src, unsigned int offset, unsigned int len)
52 {
53 	ulong store_addr = image_load_addr + offset;
54 	uchar *ptr;
55 
56 	// Avoid overflow
57 	if (wget_info->buffer_size && wget_info->buffer_size < offset + len)
58 		return -1;
59 	if (CONFIG_IS_ENABLED(LMB) && wget_info->set_bootdev) {
60 		if (store_addr < image_load_addr ||
61 		    lmb_read_check(store_addr, len)) {
62 			if (!wget_info->silent) {
63 				printf("\nwget error: ");
64 				printf("trying to overwrite reserved memory\n");
65 			}
66 			return -1;
67 		}
68 	}
69 
70 	ptr = map_sysmem(store_addr, len);
71 	memcpy(ptr, src, len);
72 	unmap_sysmem(ptr);
73 
74 	return 0;
75 }
76 
show_block_marker(u32 packets)77 static void show_block_marker(u32 packets)
78 {
79 	int cnt;
80 
81 	if (wget_info->silent)
82 		return;
83 
84 	if (content_length != -1) {
85 		if (net_boot_file_size > content_length)
86 			content_length = net_boot_file_size;
87 
88 		cnt = net_boot_file_size * 50 / content_length;
89 		while (wget_tsize_num_hash < cnt) {
90 			putc('#');
91 			wget_tsize_num_hash++;
92 		}
93 	} else {
94 		if ((packets % 10) == 0)
95 			putc('#');
96 		else if (((packets + 1) % (10 * HASHES_PER_LINE)) == 0)
97 			puts("\n");
98 	}
99 }
100 
tcp_stream_on_closed(struct tcp_stream * tcp)101 static void tcp_stream_on_closed(struct tcp_stream *tcp)
102 {
103 	if (tcp->status != TCP_ERR_OK)
104 		wget_loop_state = NETLOOP_FAIL;
105 
106 	net_set_state(wget_loop_state);
107 	if (wget_loop_state != NETLOOP_SUCCESS) {
108 		net_boot_file_size = 0;
109 		if (!wget_info->silent)
110 			printf("\nwget: Transfer Fail, TCP status - %d\n",
111 			       tcp->status);
112 		return;
113 	}
114 
115 	if (!wget_info->silent)
116 		printf("\nPackets received %d, Transfer Successful\n",
117 		       tcp->rx_packets);
118 	wget_info->file_size = net_boot_file_size;
119 	if (wget_info->method == WGET_HTTP_METHOD_GET && wget_info->set_bootdev) {
120 		efi_set_bootdev("Http", NULL, image_url,
121 				map_sysmem(image_load_addr, 0),
122 				net_boot_file_size);
123 		env_set_hex("filesize", net_boot_file_size);
124 	}
125 }
126 
tcp_stream_on_rcv_nxt_update(struct tcp_stream * tcp,u32 rx_bytes)127 static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
128 {
129 	char	*pos, *tail;
130 	uchar	saved, *ptr;
131 	int	reply_len;
132 
133 	if (http_hdr_size) {
134 		net_boot_file_size = rx_bytes - http_hdr_size;
135 		show_block_marker(tcp->rx_packets);
136 		return;
137 	}
138 
139 	ptr = map_sysmem(image_load_addr, rx_bytes + 1);
140 
141 	saved = ptr[rx_bytes];
142 	ptr[rx_bytes] = '\0';
143 	pos = strstr((char *)ptr, http_eom);
144 	ptr[rx_bytes] = saved;
145 
146 	if (!pos) {
147 		if (rx_bytes < HTTP_MAX_HDR_LEN &&
148 		    tcp->state == TCP_ESTABLISHED)
149 			goto end;
150 
151 		if (!wget_info->silent)
152 			printf("ERROR: misssed HTTP header\n");
153 		tcp_stream_close(tcp);
154 		goto end;
155 	}
156 
157 	http_hdr_size = pos - (char *)ptr + strlen(http_eom);
158 	*pos = '\0';
159 
160 	if (wget_info->headers && http_hdr_size < MAX_HTTP_HEADERS_SIZE)
161 		strcpy(wget_info->headers, ptr);
162 
163 	/* check for HTTP proto */
164 	if (strncasecmp((char *)ptr, "HTTP/", 5)) {
165 		debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
166 				       "(no HTTP Status Line found)\n");
167 		tcp_stream_close(tcp);
168 		goto end;
169 	}
170 
171 	/* get HTTP reply len */
172 	pos = strstr((char *)ptr, linefeed);
173 	if (pos)
174 		reply_len = pos - (char *)ptr;
175 	else
176 		reply_len = http_hdr_size - strlen(http_eom);
177 
178 	pos = strchr((char *)ptr, ' ');
179 	if (!pos || pos - (char *)ptr > reply_len) {
180 		debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
181 				       "(no HTTP Status Code found)\n");
182 		tcp_stream_close(tcp);
183 		goto end;
184 	}
185 
186 	wget_info->status_code = (u32)simple_strtoul(pos + 1, &tail, 10);
187 	if (tail == pos + 1 || *tail != ' ') {
188 		debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer "
189 				       "(bad HTTP Status Code)\n");
190 		tcp_stream_close(tcp);
191 		goto end;
192 	}
193 
194 	debug_cond(DEBUG_WGET,
195 		   "wget: HTTP Status Code %d\n", wget_info->status_code);
196 
197 	if (wget_info->status_code != HTTP_STATUS_OK) {
198 		debug_cond(DEBUG_WGET, "wget: Connected Bad Xfer\n");
199 		tcp_stream_close(tcp);
200 		goto end;
201 	}
202 
203 	debug_cond(DEBUG_WGET, "wget: Connctd pkt %p  hlen %x\n",
204 		   ptr, http_hdr_size);
205 
206 	content_length = -1;
207 	pos = strstr((char *)ptr, content_len);
208 	if (pos) {
209 		pos += strlen(content_len) + 1;
210 		while (*pos == ' ')
211 			pos++;
212 		content_length = simple_strtoul(pos, &tail, 10);
213 		if (*tail != '\r' && *tail != '\n' && *tail != '\0')
214 			content_length = -1;
215 	}
216 
217 	if (content_length != -1) {
218 		debug_cond(DEBUG_WGET,
219 			   "wget: Connected Len %lu\n",
220 			   content_length);
221 		wget_info->hdr_cont_len = content_length;
222 		if (wget_info->buffer_size && wget_info->buffer_size < wget_info->hdr_cont_len){
223 			tcp_stream_reset(tcp);
224 			goto end;
225 		}
226 
227 	}
228 
229 	net_boot_file_size = rx_bytes - http_hdr_size;
230 	memmove(ptr, ptr + http_hdr_size, max_rx_pos + 1 - http_hdr_size);
231 	wget_loop_state = NETLOOP_SUCCESS;
232 
233 end:
234 	unmap_sysmem(ptr);
235 }
236 
tcp_stream_rx(struct tcp_stream * tcp,u32 rx_offs,void * buf,int len)237 static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
238 {
239 	if ((max_rx_pos == (u32)(-1)) || (max_rx_pos < rx_offs + len - 1))
240 		max_rx_pos = rx_offs + len - 1;
241 
242 	// Avoid overflow
243 	if (store_block(buf, rx_offs - http_hdr_size, len) < 0)
244 		return -1;
245 
246 	return len;
247 }
248 
tcp_stream_tx(struct tcp_stream * tcp,u32 tx_offs,void * buf,int maxlen)249 static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen)
250 {
251 	int ret;
252 	const char *method;
253 
254 	if (tx_offs)
255 		return 0;
256 
257 	switch (wget_info->method) {
258 	case WGET_HTTP_METHOD_HEAD:
259 		method = "HEAD";
260 		break;
261 	case WGET_HTTP_METHOD_GET:
262 	default:
263 		method = "GET";
264 		break;
265 	}
266 
267 	ret = snprintf(buf, maxlen, "%s %s %s\r\n\r\n",
268 		       method, image_url, http_proto);
269 
270 	return ret;
271 }
272 
tcp_stream_on_create(struct tcp_stream * tcp)273 static int tcp_stream_on_create(struct tcp_stream *tcp)
274 {
275 	if (tcp->rhost.s_addr != web_server_ip.s_addr ||
276 	    tcp->rport != server_port)
277 		return 0;
278 
279 	tcp->max_retry_count = WGET_RETRY_COUNT;
280 	tcp->initial_timeout = WGET_TIMEOUT;
281 	tcp->on_closed = tcp_stream_on_closed;
282 	tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
283 	tcp->rx = tcp_stream_rx;
284 	tcp->tx = tcp_stream_tx;
285 
286 	return 1;
287 }
288 
289 #define BLOCKSIZE 512
290 
wget_start(void)291 void wget_start(void)
292 {
293 	struct tcp_stream *tcp;
294 
295 	if (!wget_info)
296 		wget_info = &default_wget_info;
297 
298 	image_url = strchr(net_boot_file_name, ':');
299 	if (image_url > 0) {
300 		web_server_ip = string_to_ip(net_boot_file_name);
301 		++image_url;
302 		net_server_ip = web_server_ip;
303 	} else {
304 		web_server_ip = net_server_ip;
305 		image_url = net_boot_file_name;
306 	}
307 
308 	debug_cond(DEBUG_WGET,
309 		   "wget: Transfer HTTP Server %pI4; our IP %pI4\n",
310 		   &web_server_ip, &net_ip);
311 
312 	/* Check if we need to send across this subnet */
313 	if (net_gateway.s_addr && net_netmask.s_addr) {
314 		struct in_addr our_net;
315 		struct in_addr server_net;
316 
317 		our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
318 		server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr;
319 		if (our_net.s_addr != server_net.s_addr)
320 			debug_cond(DEBUG_WGET,
321 				   "wget: sending through gateway %pI4",
322 				   &net_gateway);
323 	}
324 	debug_cond(DEBUG_WGET, "URL '%s'\n", image_url);
325 
326 	if (net_boot_file_expected_size_in_blocks) {
327 		debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ",
328 			   net_boot_file_expected_size_in_blocks * BLOCKSIZE);
329 		print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE,
330 			   "");
331 	}
332 	debug_cond(DEBUG_WGET,
333 		   "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr);
334 
335 	/*
336 	 * Zero out server ether to force arp resolution in case
337 	 * the server ip for the previous u-boot command, for example dns
338 	 * is not the same as the web server ip.
339 	 */
340 
341 	memset(net_server_ethaddr, 0, 6);
342 
343 	max_rx_pos = (u32)(-1);
344 	net_boot_file_size = 0;
345 	http_hdr_size = 0;
346 	wget_tsize_num_hash = 0;
347 	wget_loop_state = NETLOOP_FAIL;
348 
349 	wget_info->status_code = HTTP_STATUS_BAD;
350 	wget_info->file_size = 0;
351 	wget_info->hdr_cont_len = 0;
352 	if (wget_info->headers)
353 		wget_info->headers[0] = 0;
354 
355 	server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff;
356 	tcp_stream_set_on_create_handler(tcp_stream_on_create);
357 	tcp = tcp_stream_connect(web_server_ip, server_port);
358 	if (!tcp) {
359 		if (!wget_info->silent)
360 			printf("No free tcp streams\n");
361 		net_set_state(NETLOOP_FAIL);
362 		return;
363 	}
364 	tcp_stream_put(tcp);
365 }
366 
wget_do_request(ulong dst_addr,char * uri)367 int wget_do_request(ulong dst_addr, char *uri)
368 {
369 	int ret;
370 	char *s, *host_name, *file_name, *str_copy;
371 
372 	/*
373 	 * Download file using wget.
374 	 *
375 	 * U-Boot wget takes the target uri in this format.
376 	 *  "<http server ip>:<file path>"  e.g.) 192.168.1.1:/sample/test.iso
377 	 * Need to resolve the http server ip address before starting wget.
378 	 */
379 	str_copy = strdup(uri);
380 	if (!str_copy)
381 		return -ENOMEM;
382 
383 	s = str_copy + strlen("http://");
384 	host_name = strsep(&s, "/");
385 	if (!s) {
386 		ret = -EINVAL;
387 		goto out;
388 	}
389 	file_name = s;
390 
391 	host_name = strsep(&host_name, ":");
392 
393 	if (string_to_ip(host_name).s_addr) {
394 		s = host_name;
395 	} else {
396 #if IS_ENABLED(CONFIG_DNS)
397 		net_dns_resolve = host_name;
398 		net_dns_env_var = "httpserverip";
399 		if (net_loop(DNS) < 0) {
400 			ret = -EINVAL;
401 			goto out;
402 		}
403 		s = env_get("httpserverip");
404 		if (!s) {
405 			ret = -EINVAL;
406 			goto out;
407 		}
408 #else
409 		ret = -EINVAL;
410 		goto out;
411 #endif
412 	}
413 
414 	strlcpy(net_boot_file_name, s, sizeof(net_boot_file_name));
415 	strlcat(net_boot_file_name, ":/", sizeof(net_boot_file_name)); /* append '/' which is removed by strsep() */
416 	strlcat(net_boot_file_name, file_name, sizeof(net_boot_file_name));
417 	image_load_addr = dst_addr;
418 	ret = net_loop(WGET);
419 
420 out:
421 	free(str_copy);
422 
423 	return ret < 0 ? ret : 0;
424 }
425 
426 /**
427  * wget_validate_uri() - validate the uri for wget
428  *
429  * @uri:	uri string
430  *
431  * This function follows the current U-Boot wget implementation.
432  * scheme: only "http:" is supported
433  * authority:
434  *   - user information: not supported
435  *   - host: supported
436  *   - port: not supported(always use the default port)
437  *
438  * Uri is expected to be correctly percent encoded.
439  * This is the minimum check, control codes(0x1-0x19, 0x7F, except '\0')
440  * and space character(0x20) are not allowed.
441  *
442  * TODO: stricter uri conformance check
443  *
444  * Return:	true on success, false on failure
445  */
wget_validate_uri(char * uri)446 bool wget_validate_uri(char *uri)
447 {
448 	char c;
449 	bool ret = true;
450 	char *str_copy, *s, *authority;
451 
452 	for (c = 0x1; c < 0x21; c++) {
453 		if (strchr(uri, c)) {
454 			log_err("invalid character is used\n");
455 			return false;
456 		}
457 	}
458 	if (strchr(uri, 0x7f)) {
459 		log_err("invalid character is used\n");
460 		return false;
461 	}
462 
463 	if (strncmp(uri, "http://", 7)) {
464 		log_err("only http:// is supported\n");
465 		return false;
466 	}
467 	str_copy = strdup(uri);
468 	if (!str_copy)
469 		return false;
470 
471 	s = str_copy + strlen("http://");
472 	authority = strsep(&s, "/");
473 	if (!s) {
474 		log_err("invalid uri, no file path\n");
475 		ret = false;
476 		goto out;
477 	}
478 	s = strchr(authority, '@');
479 	if (s) {
480 		log_err("user information is not supported\n");
481 		ret = false;
482 		goto out;
483 	}
484 	s = strchr(authority, ':');
485 	if (s) {
486 		log_err("user defined port is not supported\n");
487 		ret = false;
488 		goto out;
489 	}
490 
491 out:
492 	free(str_copy);
493 
494 	return ret;
495 }
496