xref: /openbsd-src/usr.bin/ftp/fetch.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: fetch.c,v 1.96 2009/08/26 11:54:31 sthen Exp $	*/
2 /*	$NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $	*/
3 
4 /*-
5  * Copyright (c) 1997 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Jason Thorpe and Luke Mewburn.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * FTP User Program -- Command line file retrieval
35  */
36 
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 
42 #include <netinet/in.h>
43 
44 #include <arpa/ftp.h>
45 #include <arpa/inet.h>
46 
47 #include <ctype.h>
48 #include <err.h>
49 #include <libgen.h>
50 #include <limits.h>
51 #include <netdb.h>
52 #include <fcntl.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdarg.h>
56 #include <errno.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 #include <util.h>
61 #include <resolv.h>
62 
63 #ifndef SMALL
64 #include <openssl/ssl.h>
65 #include <openssl/err.h>
66 #else /* !SMALL */
67 #define SSL void
68 #endif /* !SMALL */
69 
70 #include "ftp_var.h"
71 #include "cmds.h"
72 
73 static int	url_get(const char *, const char *, const char *);
74 void		aborthttp(int);
75 void		abortfile(int);
76 char		hextochar(const char *);
77 char		*urldecode(const char *);
78 int		ftp_printf(FILE *, SSL *, const char *, ...) __attribute__((format(printf, 3, 4)));
79 char		*ftp_readline(FILE *, SSL *, size_t *);
80 size_t		ftp_read(FILE *, SSL *, char *, size_t);
81 #ifndef SMALL
82 int		proxy_connect(int, char *, char *);
83 int		SSL_vprintf(SSL *, const char *, va_list);
84 char		*SSL_readline(SSL *, size_t *);
85 #endif /* !SMALL */
86 
87 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
88 #define	HTTP_URL	"http://"	/* http URL prefix */
89 #define	HTTPS_URL	"https://"	/* https URL prefix */
90 #define	FILE_URL	"file:"		/* file URL prefix */
91 #define FTP_PROXY	"ftp_proxy"	/* env var with ftp proxy location */
92 #define HTTP_PROXY	"http_proxy"	/* env var with http proxy location */
93 
94 #define COOKIE_MAX_LEN	42
95 
96 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
97 
98 static const char *at_encoding_warning =
99     "Extra `@' characters in usernames and passwords should be encoded as %%40";
100 
101 jmp_buf	httpabort;
102 
103 static int	redirect_loop;
104 
105 /*
106  * Determine whether the character needs encoding, per RFC1738:
107  * 	- No corresponding graphic US-ASCII.
108  * 	- Unsafe characters.
109  */
110 static int
111 unsafe_char(const char *c)
112 {
113 	const char *unsafe_chars = " <>\"#{}|\\^~[]`";
114 
115 	/*
116 	 * No corresponding graphic US-ASCII.
117 	 * Control characters and octets not used in US-ASCII.
118 	 */
119 	return (iscntrl(*c) || !isascii(*c) ||
120 
121 	    /*
122 	     * Unsafe characters.
123 	     * '%' is also unsafe, if is not followed by two
124 	     * hexadecimal digits.
125 	     */
126 	    strchr(unsafe_chars, *c) != NULL ||
127 	    (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c))));
128 }
129 
130 /*
131  * Encode given URL, per RFC1738.
132  * Allocate and return string to the caller.
133  */
134 static char *
135 url_encode(const char *path)
136 {
137 	size_t i, length, new_length;
138 	char *epath, *epathp;
139 
140 	length = new_length = strlen(path);
141 
142 	/*
143 	 * First pass:
144 	 * Count unsafe characters, and determine length of the
145 	 * final URL.
146 	 */
147 	for (i = 0; i < length; i++)
148 		if (unsafe_char(path + i))
149 			new_length += 2;
150 
151 	epath = epathp = malloc(new_length + 1);	/* One more for '\0'. */
152 	if (epath == NULL)
153 		return NULL;
154 
155 	/*
156 	 * Second pass:
157 	 * Encode, and copy final URL.
158 	 */
159 	for (i = 0; i < length; i++)
160 		if (unsafe_char(path + i)) {
161 			snprintf(epathp, 4, "%%" "%02x", path[i]);
162 			epathp += 3;
163 		} else
164 			*(epathp++) = path[i];
165 
166 	*epathp = '\0';
167 	return (epath);
168 }
169 
170 /*
171  * Retrieve URL, via the proxy in $proxyvar if necessary.
172  * Modifies the string argument given.
173  * Returns -1 on failure, 0 on success
174  */
175 static int
176 url_get(const char *origline, const char *proxyenv, const char *outfile)
177 {
178 	char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4];
179 	char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL;
180 	char *epath;
181 	int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1;
182 	struct addrinfo hints, *res0, *res;
183 	const char * volatile savefile;
184 	char * volatile proxyurl = NULL;
185 	char *cookie = NULL;
186 	volatile int s = -1, out;
187 	volatile sig_t oldintr;
188 	FILE *fin = NULL;
189 	off_t hashbytes;
190 	const char *errstr;
191 	size_t len, wlen;
192 #ifndef SMALL
193 	char *sslpath = NULL, *sslhost = NULL;
194 	int ishttpsurl = 0;
195 	SSL_CTX *ssl_ctx = NULL;
196 #endif /* !SMALL */
197 	SSL *ssl = NULL;
198 	int status;
199 
200 	newline = strdup(origline);
201 	if (newline == NULL)
202 		errx(1, "Can't allocate memory to parse URL");
203 	if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
204 		host = newline + sizeof(HTTP_URL) - 1;
205 	else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
206 		host = newline + sizeof(FTP_URL) - 1;
207 		isftpurl = 1;
208 	} else if (strncasecmp(newline, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
209 		host = newline + sizeof(FILE_URL) - 1;
210 		isfileurl = 1;
211 #ifndef SMALL
212 	} else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) {
213 		host = newline + sizeof(HTTPS_URL) - 1;
214 		ishttpsurl = 1;
215 #endif /* !SMALL */
216 	} else
217 		errx(1, "url_get: Invalid URL '%s'", newline);
218 
219 	if (isfileurl) {
220 		path = host;
221 	} else {
222 		path = strchr(host, '/');		/* Find path */
223 		if (EMPTYSTRING(path)) {
224 			if (outfile) {			/* No slash, but */
225 				path=strchr(host,'\0');	/* we have outfile. */
226 				goto noslash;
227 			}
228 			if (isftpurl)
229 				goto noftpautologin;
230 			warnx("No `/' after host (use -o): %s", origline);
231 			goto cleanup_url_get;
232 		}
233 		*path++ = '\0';
234 		if (EMPTYSTRING(path) && !outfile) {
235 			if (isftpurl)
236 				goto noftpautologin;
237 			warnx("No filename after host (use -o): %s", origline);
238 			goto cleanup_url_get;
239 		}
240 	}
241 
242 noslash:
243 	if (outfile)
244 		savefile = outfile;
245 	else {
246 		if (path[strlen(path) - 1] == '/')	/* Consider no file */
247 			savefile = NULL;		/* after dir invalid. */
248 		else
249 			savefile = basename(path);
250 	}
251 
252 	if (EMPTYSTRING(savefile)) {
253 		if (isftpurl)
254 			goto noftpautologin;
255 		warnx("No filename after directory (use -o): %s", origline);
256 		goto cleanup_url_get;
257 	}
258 
259 #ifndef SMALL
260 	if (resume && (strcmp(savefile, "-") == 0)) {
261 		warnx("can't append to stdout");
262 		goto cleanup_url_get;
263 	}
264 #endif /* !SMALL */
265 
266 	if (!isfileurl && proxyenv != NULL) {		/* use proxy */
267 #ifndef SMALL
268 		if (ishttpsurl) {
269 			sslpath = strdup(path);
270 			sslhost = strdup(host);
271 			if (! sslpath || ! sslhost)
272 				errx(1, "Can't allocate memory for https path/host.");
273 		}
274 #endif /* !SMALL */
275 		proxyurl = strdup(proxyenv);
276 		if (proxyurl == NULL)
277 			errx(1, "Can't allocate memory for proxy URL.");
278 		if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
279 			host = proxyurl + sizeof(HTTP_URL) - 1;
280 		else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0)
281 			host = proxyurl + sizeof(FTP_URL) - 1;
282 		else {
283 			warnx("Malformed proxy URL: %s", proxyenv);
284 			goto cleanup_url_get;
285 		}
286 		if (EMPTYSTRING(host)) {
287 			warnx("Malformed proxy URL: %s", proxyenv);
288 			goto cleanup_url_get;
289 		}
290 		if (*--path == '\0')
291 			*path = '/';		/* add / back to real path */
292 		path = strchr(host, '/');	/* remove trailing / on host */
293 		if (!EMPTYSTRING(path))
294 			*path++ = '\0';		/* i guess this ++ is useless */
295 
296 		path = strchr(host, '@');	/* look for credentials in proxy */
297 		if (!EMPTYSTRING(path)) {
298 			*path = '\0';
299 			cookie = strchr(host, ':');
300 			if (EMPTYSTRING(cookie)) {
301 				warnx("Malformed proxy URL: %s", proxyenv);
302 				goto cleanup_url_get;
303 			}
304 			cookie  = malloc(COOKIE_MAX_LEN);
305 			if (cookie == NULL)
306 				errx(1, "out of memory");
307 			if (b64_ntop(host, strlen(host), cookie, COOKIE_MAX_LEN) == -1)
308 				errx(1, "error in base64 encoding");
309 			*path = '@'; /* restore @ in proxyurl */
310 			/*
311 			 * This removes the password from proxyurl,
312 			 * filling with stars
313 			 */
314 			for (host = 1 + strchr(proxyurl + 5, ':');  *host != '@';
315 			     host++)
316 				*host = '*';
317 
318 			host = path + 1;
319 		}
320 		path = newline;
321 	}
322 
323 	if (isfileurl) {
324 		struct stat st;
325 
326 		s = open(path, O_RDONLY);
327 		if (s == -1) {
328 			warn("Can't open file %s", path);
329 			goto cleanup_url_get;
330 		}
331 
332 		if (fstat(s, &st) == -1)
333 			filesize = -1;
334 		else
335 			filesize = st.st_size;
336 
337 		/* Open the output file.  */
338 		if (strcmp(savefile, "-") != 0) {
339 #ifndef SMALL
340 			if (resume)
341 				out = open(savefile, O_CREAT | O_WRONLY |
342 					O_APPEND, 0666);
343 
344 			else
345 #endif /* !SMALL */
346 				out = open(savefile, O_CREAT | O_WRONLY |
347 					O_TRUNC, 0666);
348 			if (out < 0) {
349 				warn("Can't open %s", savefile);
350 				goto cleanup_url_get;
351 			}
352 		} else
353 			out = fileno(stdout);
354 
355 #ifndef SMALL
356 		if (resume) {
357 			if (fstat(out, &st) == -1) {
358 				warn("Can't fstat %s", savefile);
359 				goto cleanup_url_get;
360 			}
361 			if (lseek(s, st.st_size, SEEK_SET) == -1) {
362 				warn("Can't lseek %s", path);
363 				goto cleanup_url_get;
364 			}
365 			restart_point = st.st_size;
366 		}
367 #endif /* !SMALL */
368 
369 		/* Trap signals */
370 		oldintr = NULL;
371 		if (setjmp(httpabort)) {
372 			if (oldintr)
373 				(void)signal(SIGINT, oldintr);
374 			goto cleanup_url_get;
375 		}
376 		oldintr = signal(SIGINT, abortfile);
377 
378 		bytes = 0;
379 		hashbytes = mark;
380 		progressmeter(-1, path);
381 
382 		if ((buf = malloc(4096)) == NULL)
383 			errx(1, "Can't allocate memory for transfer buffer");
384 
385 		/* Finally, suck down the file. */
386 		i = 0;
387 		while ((len = read(s, buf, 4096)) > 0) {
388 			bytes += len;
389 			for (cp = buf; len > 0; len -= i, cp += i) {
390 				if ((i = write(out, cp, len)) == -1) {
391 					warn("Writing %s", savefile);
392 					goto cleanup_url_get;
393 				}
394 				else if (i == 0)
395 					break;
396 			}
397 			if (hash && !progress) {
398 				while (bytes >= hashbytes) {
399 					(void)putc('#', ttyout);
400 					hashbytes += mark;
401 				}
402 				(void)fflush(ttyout);
403 			}
404 		}
405 		if (hash && !progress && bytes > 0) {
406 			if (bytes < mark)
407 				(void)putc('#', ttyout);
408 			(void)putc('\n', ttyout);
409 			(void)fflush(ttyout);
410 		}
411 		if (len != 0) {
412 			warn("Reading from file");
413 			goto cleanup_url_get;
414 		}
415 		progressmeter(1, NULL);
416 		if (verbose)
417 			fputs("Successfully retrieved file.\n", ttyout);
418 		(void)signal(SIGINT, oldintr);
419 
420 		rval = 0;
421 		goto cleanup_url_get;
422 	}
423 
424 	if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
425 	    (hosttail[1] == '\0' || hosttail[1] == ':')) {
426 		host++;
427 		*hosttail++ = '\0';
428 	} else
429 		hosttail = host;
430 
431 	portnum = strrchr(hosttail, ':');		/* find portnum */
432 	if (portnum != NULL)
433 		*portnum++ = '\0';
434 
435 #ifndef SMALL
436 	if (debug)
437 		fprintf(ttyout, "host %s, port %s, path %s, save as %s.\n",
438 		    host, portnum, path, savefile);
439 #endif /* !SMALL */
440 
441 	memset(&hints, 0, sizeof(hints));
442 	hints.ai_family = family;
443 	hints.ai_socktype = SOCK_STREAM;
444 #ifndef SMALL
445 	port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
446 #else /* !SMALL */
447 	port = portnum ? portnum : httpport;
448 #endif /* !SMALL */
449 	error = getaddrinfo(host, port, &hints, &res0);
450 	/*
451 	 * If the services file is corrupt/missing, fall back
452 	 * on our hard-coded defines.
453 	 */
454 	if (error == EAI_SERVICE && port == httpport) {
455 		snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
456 		error = getaddrinfo(host, pbuf, &hints, &res0);
457 #ifndef SMALL
458 	} else if (error == EAI_SERVICE && port == httpsport) {
459 		snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
460 		error = getaddrinfo(host, pbuf, &hints, &res0);
461 #endif /* !SMALL */
462 	}
463 	if (error) {
464 		warnx("%s: %s", gai_strerror(error), host);
465 		goto cleanup_url_get;
466 	}
467 
468 	s = -1;
469 	for (res = res0; res; res = res->ai_next) {
470 		if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
471 		    sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
472 			strlcpy(hbuf, "(unknown)", sizeof(hbuf));
473 		if (verbose)
474 			fprintf(ttyout, "Trying %s...\n", hbuf);
475 
476 		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
477 		if (s == -1) {
478 			cause = "socket";
479 			continue;
480 		}
481 
482 again:
483 		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
484 			int save_errno;
485 
486 			if (errno == EINTR)
487 				goto again;
488 			save_errno = errno;
489 			close(s);
490 			errno = save_errno;
491 			s = -1;
492 			cause = "connect";
493 			continue;
494 		}
495 
496 		/* get port in numeric */
497 		if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
498 		    pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
499 			port = pbuf;
500 		else
501 			port = NULL;
502 
503 #ifndef SMALL
504 		if (proxyenv && sslhost)
505 			proxy_connect(s, sslhost, cookie);
506 #endif /* !SMALL */
507 		break;
508 	}
509 	freeaddrinfo(res0);
510 	if (s < 0) {
511 		warn("%s", cause);
512 		goto cleanup_url_get;
513 	}
514 
515 #ifndef SMALL
516 	if (ishttpsurl) {
517 		if (proxyenv && sslpath) {
518 			ishttpsurl = 0;
519 			proxyurl = NULL;
520 			path = sslpath;
521 		}
522 		SSL_library_init();
523 		SSL_load_error_strings();
524 		SSLeay_add_ssl_algorithms();
525 		ssl_ctx = SSL_CTX_new(SSLv23_client_method());
526 		ssl = SSL_new(ssl_ctx);
527 		if (ssl == NULL || ssl_ctx == NULL) {
528 			ERR_print_errors_fp(ttyout);
529 			goto cleanup_url_get;
530 		}
531 		if (SSL_set_fd(ssl, s) == 0) {
532 			ERR_print_errors_fp(ttyout);
533 			goto cleanup_url_get;
534 		}
535 		if (SSL_connect(ssl) <= 0) {
536 			ERR_print_errors_fp(ttyout);
537 			goto cleanup_url_get;
538 		}
539 	} else {
540 		fin = fdopen(s, "r+");
541 	}
542 #else /* !SMALL */
543 	fin = fdopen(s, "r+");
544 #endif /* !SMALL */
545 
546 	if (verbose)
547 		fprintf(ttyout, "Requesting %s", origline);
548 
549 	/*
550 	 * Construct and send the request. Proxy requests don't want leading /.
551 	 */
552 #ifndef SMALL
553 	cookie_get(host, path, ishttpsurl, &buf);
554 #endif /* !SMALL */
555 
556 	epath = url_encode(path);
557 	if (epath == NULL)
558 		return (-1);
559 	if (proxyurl) {
560 		if (verbose)
561 			fprintf(ttyout, " (via %s)\n", proxyurl);
562 		/*
563 		 * Host: directive must use the destination host address for
564 		 * the original URI (path).  We do not attach it at this moment.
565 		 */
566 		if (cookie)
567 			ftp_printf(fin, ssl, "GET %s HTTP/1.0\r\n"
568 			    "Proxy-Authorization: Basic %s%s\r\n%s\r\n\r\n",
569 			    epath, cookie, buf ? buf : "", HTTP_USER_AGENT);
570 		else
571 			ftp_printf(fin, ssl, "GET %s HTTP/1.0\r\n%s%s\r\n\r\n",
572 			    epath, buf ? buf : "", HTTP_USER_AGENT);
573 
574 	} else {
575 #ifndef SMALL
576 		if (resume) {
577 			struct stat stbuf;
578 
579 			if (stat(savefile, &stbuf) == 0)
580 				restart_point = stbuf.st_size;
581 			else
582 				restart_point = 0;
583 		}
584 #endif /* !SMALL */
585 		ftp_printf(fin, ssl, "GET /%s %s\r\nHost: ", epath,
586 #ifndef SMALL
587 			restart_point ? "HTTP/1.1" :
588 #endif /* !SMALL */
589 			"HTTP/1.0");
590 		if (strchr(host, ':')) {
591 			char *h, *p;
592 
593 			/*
594 			 * strip off scoped address portion, since it's
595 			 * local to node
596 			 */
597 			h = strdup(host);
598 			if (h == NULL)
599 				errx(1, "Can't allocate memory.");
600 			if ((p = strchr(h, '%')) != NULL)
601 				*p = '\0';
602 			ftp_printf(fin, ssl, "[%s]", h);
603 			free(h);
604 		} else
605 			ftp_printf(fin, ssl, "%s", host);
606 
607 		/*
608 		 * Send port number only if it's specified and does not equal
609 		 * 80. Some broken HTTP servers get confused if you explicitly
610 		 * send them the port number.
611 		 */
612 #ifndef SMALL
613 		if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
614 			ftp_printf(fin, ssl, ":%s", port);
615 		if (restart_point)
616 			ftp_printf(fin, ssl, "\r\nRange: bytes=%lld-",
617 				(long long)restart_point);
618 #else /* !SMALL */
619 		if (port && strcmp(port, "80") != 0)
620 			ftp_printf(fin, ssl, ":%s", port);
621 #endif /* !SMALL */
622 		ftp_printf(fin, ssl, "\r\n%s%s\r\n\r\n",
623 		    buf ? buf : "", HTTP_USER_AGENT);
624 		if (verbose)
625 			fprintf(ttyout, "\n");
626 	}
627 	free(epath);
628 
629 #ifndef SMALL
630 	free(buf);
631 #endif /* !SMALL */
632 	buf = NULL;
633 
634 	if (fin != NULL && fflush(fin) == EOF) {
635 		warn("Writing HTTP request");
636 		goto cleanup_url_get;
637 	}
638 	if ((buf = ftp_readline(fin, ssl, &len)) == NULL) {
639 		warn("Receiving HTTP reply");
640 		goto cleanup_url_get;
641 	}
642 
643 	while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
644 		buf[--len] = '\0';
645 #ifndef SMALL
646 	if (debug)
647 		fprintf(ttyout, "received '%s'\n", buf);
648 #endif /* !SMALL */
649 
650 	cp = strchr(buf, ' ');
651 	if (cp == NULL)
652 		goto improper;
653 	else
654 		cp++;
655 
656 	strlcpy(ststr, cp, sizeof(ststr));
657 	status = strtonum(ststr, 200, 416, &errstr);
658 	if (errstr) {
659 		warnx("Error retrieving file: %s", cp);
660 		goto cleanup_url_get;
661 	}
662 
663 	switch (status) {
664 	case 200:	/* OK */
665 #ifndef SMALL
666 	case 206:	/* Partial Content */
667 #endif /* !SMALL */
668 		break;
669 	case 301:	/* Moved Permanently */
670 	case 302:	/* Found */
671 	case 303:	/* See Other */
672 	case 307:	/* Temporary Redirect */
673 		isredirect++;
674 		if (redirect_loop++ > 10) {
675 			warnx("Too many redirections requested");
676 			goto cleanup_url_get;
677 		}
678 		break;
679 #ifndef SMALL
680 	case 416:	/* Requested Range Not Satisfiable */
681 		warnx("File is already fully retrieved.");
682 		goto cleanup_url_get;
683 #endif /* !SMALL */
684 	default:
685 		warnx("Error retrieving file: %s", cp);
686 		goto cleanup_url_get;
687 	}
688 
689 	/*
690 	 * Read the rest of the header.
691 	 */
692 	free(buf);
693 	filesize = -1;
694 
695 	for (;;) {
696 		if ((buf = ftp_readline(fin, ssl, &len)) == NULL) {
697 			warn("Receiving HTTP reply");
698 			goto cleanup_url_get;
699 		}
700 
701 		while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
702 			buf[--len] = '\0';
703 		if (len == 0)
704 			break;
705 #ifndef SMALL
706 		if (debug)
707 			fprintf(ttyout, "received '%s'\n", buf);
708 #endif /* !SMALL */
709 
710 		/* Look for some headers */
711 		cp = buf;
712 #define CONTENTLEN "Content-Length: "
713 		if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
714 			cp += sizeof(CONTENTLEN) - 1;
715 			filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
716 			if (errstr != NULL)
717 				goto improper;
718 #ifndef SMALL
719 			if (restart_point)
720 				filesize += restart_point;
721 #endif /* !SMALL */
722 #define LOCATION "Location: "
723 		} else if (isredirect &&
724 		    strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
725 			cp += sizeof(LOCATION) - 1;
726 			if (verbose)
727 				fprintf(ttyout, "Redirected to %s\n", cp);
728 			if (fin != NULL)
729 				fclose(fin);
730 			else if (s != -1)
731 				close(s);
732 			free(proxyurl);
733 			free(newline);
734 			free(cookie);
735 			rval = url_get(cp, proxyenv, savefile);
736 			free(buf);
737 			return (rval);
738 		}
739 	}
740 
741 	/* Open the output file.  */
742 	if (strcmp(savefile, "-") != 0) {
743 #ifndef SMALL
744 		if (resume)
745 			out = open(savefile, O_CREAT | O_WRONLY | O_APPEND,
746 				0666);
747 		else
748 #endif /* !SMALL */
749 			out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
750 				0666);
751 		if (out < 0) {
752 			warn("Can't open %s", savefile);
753 			goto cleanup_url_get;
754 		}
755 	} else
756 		out = fileno(stdout);
757 
758 	/* Trap signals */
759 	oldintr = NULL;
760 	if (setjmp(httpabort)) {
761 		if (oldintr)
762 			(void)signal(SIGINT, oldintr);
763 		goto cleanup_url_get;
764 	}
765 	oldintr = signal(SIGINT, aborthttp);
766 
767 	bytes = 0;
768 	hashbytes = mark;
769 	progressmeter(-1, path);
770 
771 	free(buf);
772 
773 	/* Finally, suck down the file. */
774 	if ((buf = malloc(4096)) == NULL)
775 		errx(1, "Can't allocate memory for transfer buffer");
776 	i = 0;
777 	len = 1;
778 	while (len > 0) {
779 		len = ftp_read(fin, ssl, buf, 4096);
780 		bytes += len;
781 		for (cp = buf, wlen = len; wlen > 0; wlen -= i, cp += i) {
782 			if ((i = write(out, cp, wlen)) == -1) {
783 				warn("Writing %s", savefile);
784 				goto cleanup_url_get;
785 			}
786 			else if (i == 0)
787 				break;
788 		}
789 		if (hash && !progress) {
790 			while (bytes >= hashbytes) {
791 				(void)putc('#', ttyout);
792 				hashbytes += mark;
793 			}
794 			(void)fflush(ttyout);
795 		}
796 	}
797 	if (hash && !progress && bytes > 0) {
798 		if (bytes < mark)
799 			(void)putc('#', ttyout);
800 		(void)putc('\n', ttyout);
801 		(void)fflush(ttyout);
802 	}
803 	if (len != 0) {
804 		warn("Reading from socket");
805 		goto cleanup_url_get;
806 	}
807 	progressmeter(1, NULL);
808 	if (
809 #ifndef SMALL
810 		!resume &&
811 #endif /* !SMALL */
812 		filesize != -1 && len == 0 && bytes != filesize) {
813 		if (verbose)
814 			fputs("Read short file.\n", ttyout);
815 		goto cleanup_url_get;
816 	}
817 
818 	if (verbose)
819 		fputs("Successfully retrieved file.\n", ttyout);
820 	(void)signal(SIGINT, oldintr);
821 
822 	rval = 0;
823 	goto cleanup_url_get;
824 
825 noftpautologin:
826 	warnx(
827 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
828 	goto cleanup_url_get;
829 
830 improper:
831 	warnx("Improper response from %s", host);
832 
833 cleanup_url_get:
834 #ifndef SMALL
835 	if (ssl) {
836 		SSL_shutdown(ssl);
837 		SSL_free(ssl);
838 	}
839 #endif /* !SMALL */
840 	if (fin != NULL)
841 		fclose(fin);
842 	else if (s != -1)
843 		close(s);
844 	free(buf);
845 	free(proxyurl);
846 	free(newline);
847 	free(cookie);
848 	return (rval);
849 }
850 
851 /*
852  * Abort a http retrieval
853  */
854 /* ARGSUSED */
855 void
856 aborthttp(int signo)
857 {
858 
859 	alarmtimer(0);
860 	fputs("\nhttp fetch aborted.\n", ttyout);
861 	(void)fflush(ttyout);
862 	longjmp(httpabort, 1);
863 }
864 
865 /*
866  * Abort a http retrieval
867  */
868 /* ARGSUSED */
869 void
870 abortfile(int signo)
871 {
872 
873 	alarmtimer(0);
874 	fputs("\nfile fetch aborted.\n", ttyout);
875 	(void)fflush(ttyout);
876 	longjmp(httpabort, 1);
877 }
878 
879 /*
880  * Retrieve multiple files from the command line, transferring
881  * files of the form "host:path", "ftp://host/path" using the
882  * ftp protocol, and files of the form "http://host/path" using
883  * the http protocol.
884  * If path has a trailing "/", then return (-1);
885  * the path will be cd-ed into and the connection remains open,
886  * and the function will return -1 (to indicate the connection
887  * is alive).
888  * If an error occurs the return value will be the offset+1 in
889  * argv[] of the file that caused a problem (i.e, argv[x]
890  * returns x+1)
891  * Otherwise, 0 is returned if all files retrieved successfully.
892  */
893 int
894 auto_fetch(int argc, char *argv[], char *outfile)
895 {
896 	char *xargv[5];
897 	char *cp, *url, *host, *dir, *file, *portnum;
898 	char *username, *pass, *pathstart;
899 	char *ftpproxy, *httpproxy;
900 	int rval, xargc;
901 	volatile int argpos;
902 	int dirhasglob, filehasglob, oautologin;
903 	char rempath[MAXPATHLEN];
904 
905 	argpos = 0;
906 
907 	if (setjmp(toplevel)) {
908 		if (connected)
909 			disconnect(0, NULL);
910 		return (argpos + 1);
911 	}
912 	(void)signal(SIGINT, (sig_t)intr);
913 	(void)signal(SIGPIPE, (sig_t)lostpeer);
914 
915 	if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
916 		ftpproxy = NULL;
917 	if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
918 		httpproxy = NULL;
919 
920 	/*
921 	 * Loop through as long as there's files to fetch.
922 	 */
923 	for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
924 		if (strchr(argv[argpos], ':') == NULL)
925 			break;
926 		host = dir = file = portnum = username = pass = NULL;
927 
928 		/*
929 		 * We muck with the string, so we make a copy.
930 		 */
931 		url = strdup(argv[argpos]);
932 		if (url == NULL)
933 			errx(1, "Can't allocate memory for auto-fetch.");
934 
935 		/*
936 		 * Try HTTP URL-style arguments first.
937 		 */
938 		if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
939 #ifndef SMALL
940 		    /* even if we compiled without SSL, url_get will check */
941 		    strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0 ||
942 #endif /* !SMALL */
943 		    strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
944 			redirect_loop = 0;
945 			if (url_get(url, httpproxy, outfile) == -1)
946 				rval = argpos + 1;
947 			continue;
948 		}
949 
950 		/*
951 		 * Try FTP URL-style arguments next. If ftpproxy is
952 		 * set, use url_get() instead of standard ftp.
953 		 * Finally, try host:file.
954 		 */
955 		host = url;
956 		if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
957 			char *passend, *passagain, *userend;
958 
959 			if (ftpproxy) {
960 				if (url_get(url, ftpproxy, outfile) == -1)
961 					rval = argpos + 1;
962 				continue;
963 			}
964 			host += sizeof(FTP_URL) - 1;
965 			dir = strchr(host, '/');
966 
967 			/* Look for [user:pass@]host[:port] */
968 
969 			/* check if we have "user:pass@" */
970 			userend = strchr(host, ':');
971 			passend = strchr(host, '@');
972 			if (passend && userend && userend < passend &&
973 			    (!dir || passend < dir)) {
974 				username = host;
975 				pass = userend + 1;
976 				host = passend + 1;
977 				*userend = *passend = '\0';
978 				passagain = strchr(host, '@');
979 				if (strchr(pass, '@') != NULL ||
980 				    (passagain != NULL && passagain < dir)) {
981 					warnx(at_encoding_warning);
982 					goto bad_ftp_url;
983 				}
984 
985 				if (EMPTYSTRING(username)) {
986 bad_ftp_url:
987 					warnx("Invalid URL: %s", argv[argpos]);
988 					rval = argpos + 1;
989 					continue;
990 				}
991 				username = urldecode(username);
992 				pass = urldecode(pass);
993 			}
994 
995 #ifdef INET6
996 			/* check [host]:port, or [host] */
997 			if (host[0] == '[') {
998 				cp = strchr(host, ']');
999 				if (cp && (!dir || cp < dir)) {
1000 					if (cp + 1 == dir || cp[1] == ':') {
1001 						host++;
1002 						*cp++ = '\0';
1003 					} else
1004 						cp = NULL;
1005 				} else
1006 					cp = host;
1007 			} else
1008 				cp = host;
1009 #else
1010 			cp = host;
1011 #endif
1012 
1013 			/* split off host[:port] if there is */
1014 			if (cp) {
1015 				portnum = strchr(cp, ':');
1016 				pathstart = strchr(cp, '/');
1017 				/* : in path is not a port # indicator */
1018 				if (portnum && pathstart &&
1019 				    pathstart < portnum)
1020 					portnum = NULL;
1021 
1022 				if (!portnum)
1023 					;
1024 				else {
1025 					if (!dir)
1026 						;
1027 					else if (portnum + 1 < dir) {
1028 						*portnum++ = '\0';
1029 						/*
1030 						 * XXX should check if portnum
1031 						 * is decimal number
1032 						 */
1033 					} else {
1034 						/* empty portnum */
1035 						goto bad_ftp_url;
1036 					}
1037 				}
1038 			} else
1039 				portnum = NULL;
1040 		} else {			/* classic style `host:file' */
1041 			dir = strchr(host, ':');
1042 		}
1043 		if (EMPTYSTRING(host)) {
1044 			rval = argpos + 1;
1045 			continue;
1046 		}
1047 
1048 		/*
1049 		 * If dir is NULL, the file wasn't specified
1050 		 * (URL looked something like ftp://host)
1051 		 */
1052 		if (dir != NULL)
1053 			*dir++ = '\0';
1054 
1055 		/*
1056 		 * Extract the file and (if present) directory name.
1057 		 */
1058 		if (!EMPTYSTRING(dir)) {
1059 			cp = strrchr(dir, '/');
1060 			if (cp != NULL) {
1061 				*cp++ = '\0';
1062 				file = cp;
1063 			} else {
1064 				file = dir;
1065 				dir = NULL;
1066 			}
1067 		}
1068 #ifndef SMALL
1069 		if (debug)
1070 			fprintf(ttyout,
1071 			    "user %s:%s host %s port %s dir %s file %s\n",
1072 			    username, pass ? "XXXX" : NULL, host, portnum,
1073 			    dir, file);
1074 #endif /* !SMALL */
1075 
1076 		/*
1077 		 * Set up the connection.
1078 		 */
1079 		if (connected)
1080 			disconnect(0, NULL);
1081 		xargv[0] = __progname;
1082 		xargv[1] = host;
1083 		xargv[2] = NULL;
1084 		xargc = 2;
1085 		if (!EMPTYSTRING(portnum)) {
1086 			xargv[2] = portnum;
1087 			xargv[3] = NULL;
1088 			xargc = 3;
1089 		}
1090 		oautologin = autologin;
1091 		if (username == NULL)
1092 			anonftp = 1;
1093 		else {
1094 			anonftp = 0;
1095 			autologin = 0;
1096 		}
1097 		setpeer(xargc, xargv);
1098 		autologin = oautologin;
1099 		if (connected == 0 ||
1100 		    (connected == 1 && autologin && (username == NULL ||
1101 		    !ftp_login(host, username, pass)))) {
1102 			warnx("Can't connect or login to host `%s'", host);
1103 			rval = argpos + 1;
1104 			continue;
1105 		}
1106 
1107 		/* Always use binary transfers. */
1108 		setbinary(0, NULL);
1109 
1110 		dirhasglob = filehasglob = 0;
1111 		if (doglob) {
1112 			if (!EMPTYSTRING(dir) &&
1113 			    strpbrk(dir, "*?[]{}") != NULL)
1114 				dirhasglob = 1;
1115 			if (!EMPTYSTRING(file) &&
1116 			    strpbrk(file, "*?[]{}") != NULL)
1117 				filehasglob = 1;
1118 		}
1119 
1120 		/* Change directories, if necessary. */
1121 		if (!EMPTYSTRING(dir) && !dirhasglob) {
1122 			xargv[0] = "cd";
1123 			xargv[1] = dir;
1124 			xargv[2] = NULL;
1125 			cd(2, xargv);
1126 			if (!dirchange) {
1127 				rval = argpos + 1;
1128 				continue;
1129 			}
1130 		}
1131 
1132 		if (EMPTYSTRING(file)) {
1133 #ifndef SMALL
1134 			rval = -1;
1135 #else /* !SMALL */
1136 			recvrequest("NLST", "-", NULL, "w", 0, 0);
1137 			rval = 0;
1138 #endif /* !SMALL */
1139 			continue;
1140 		}
1141 
1142 		if (verbose)
1143 			fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1144 
1145 		if (dirhasglob) {
1146 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
1147 			file = rempath;
1148 		}
1149 
1150 		/* Fetch the file(s). */
1151 		xargc = 2;
1152 		xargv[0] = "get";
1153 		xargv[1] = file;
1154 		xargv[2] = NULL;
1155 		if (dirhasglob || filehasglob) {
1156 			int ointeractive;
1157 
1158 			ointeractive = interactive;
1159 			interactive = 0;
1160 			xargv[0] = "mget";
1161 #ifndef SMALL
1162 			if (resume) {
1163 				xargc = 3;
1164 				xargv[1] = "-c";
1165 				xargv[2] = file;
1166 				xargv[3] = NULL;
1167 			}
1168 #endif /* !SMALL */
1169 			mget(xargc, xargv);
1170 			interactive = ointeractive;
1171 		} else {
1172 			if (outfile != NULL) {
1173 				xargv[2] = outfile;
1174 				xargv[3] = NULL;
1175 				xargc++;
1176 			}
1177 #ifndef SMALL
1178 			if (resume)
1179 				reget(xargc, xargv);
1180 			else
1181 #endif /* !SMALL */
1182 				get(xargc, xargv);
1183 		}
1184 
1185 		if ((code / 100) != COMPLETE)
1186 			rval = argpos + 1;
1187 	}
1188 	if (connected && rval != -1)
1189 		disconnect(0, NULL);
1190 	return (rval);
1191 }
1192 
1193 char *
1194 urldecode(const char *str)
1195 {
1196 	char *ret, c;
1197 	int i, reallen;
1198 
1199 	if (str == NULL)
1200 		return NULL;
1201 	if ((ret = malloc(strlen(str)+1)) == NULL)
1202 		err(1, "Can't allocate memory for URL decoding");
1203 	for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
1204 		c = str[i];
1205 		if (c == '+') {
1206 			*ret = ' ';
1207 			continue;
1208 		}
1209 
1210 		/* Cannot use strtol here because next char
1211 		 * after %xx may be a digit.
1212 		 */
1213 		if (c == '%' && isxdigit(str[i+1]) && isxdigit(str[i+2])) {
1214 			*ret = hextochar(&str[i+1]);
1215 			i+=2;
1216 			continue;
1217 		}
1218 		*ret = c;
1219 	}
1220 	*ret = '\0';
1221 
1222 	return ret-reallen;
1223 }
1224 
1225 char
1226 hextochar(const char *str)
1227 {
1228 	char c, ret;
1229 
1230 	c = str[0];
1231 	ret = c;
1232 	if (isalpha(c))
1233 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1234 	else
1235 		ret -= '0';
1236 	ret *= 16;
1237 
1238 	c = str[1];
1239 	ret += c;
1240 	if (isalpha(c))
1241 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1242 	else
1243 		ret -= '0';
1244 	return ret;
1245 }
1246 
1247 int
1248 isurl(const char *p)
1249 {
1250 
1251 	if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
1252 	    strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1253 #ifndef SMALL
1254 	    strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
1255 #endif /* !SMALL */
1256 	    strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
1257 	    strstr(p, ":/"))
1258 		return (1);
1259 	return (0);
1260 }
1261 
1262 char *
1263 ftp_readline(FILE *fp, SSL *ssl, size_t *lenp)
1264 {
1265 	if (fp != NULL)
1266 		return fparseln(fp, lenp, NULL, "\0\0\0", 0);
1267 #ifndef SMALL
1268 	else if (ssl != NULL)
1269 		return SSL_readline(ssl, lenp);
1270 #endif /* !SMALL */
1271 	else
1272 		return NULL;
1273 }
1274 
1275 size_t
1276 ftp_read(FILE *fp, SSL *ssl, char *buf, size_t len)
1277 {
1278 	size_t ret;
1279 	if (fp != NULL)
1280 		ret = fread(buf, sizeof(char), len, fp);
1281 #ifndef SMALL
1282 	else if (ssl != NULL) {
1283 		int nr;
1284 
1285 		if (len > INT_MAX)
1286 			len = INT_MAX;
1287 		if ((nr = SSL_read(ssl, buf, (int)len)) <= 0)
1288 			ret = 0;
1289 		else
1290 			ret = nr;
1291 	}
1292 #endif /* !SMALL */
1293 	else
1294 		ret = 0;
1295 	return (ret);
1296 }
1297 
1298 int
1299 ftp_printf(FILE *fp, SSL *ssl, const char *fmt, ...)
1300 {
1301 	int ret;
1302 	va_list ap;
1303 
1304 	va_start(ap, fmt);
1305 
1306 	if (fp != NULL)
1307 		ret = vfprintf(fp, fmt, ap);
1308 #ifndef SMALL
1309 	else if (ssl != NULL)
1310 		ret = SSL_vprintf((SSL*)ssl, fmt, ap);
1311 #endif /* !SMALL */
1312 	else
1313 		ret = 0;
1314 
1315 	va_end(ap);
1316 	return (ret);
1317 }
1318 
1319 #ifndef SMALL
1320 int
1321 SSL_vprintf(SSL *ssl, const char *fmt, va_list ap)
1322 {
1323 	int ret;
1324 	char *string;
1325 
1326 	if ((ret = vasprintf(&string, fmt, ap)) == -1)
1327 		return ret;
1328 	ret = SSL_write(ssl, string, ret);
1329 	free(string);
1330 	return ret;
1331 }
1332 
1333 char *
1334 SSL_readline(SSL *ssl, size_t *lenp)
1335 {
1336 	size_t i, len;
1337 	char *buf, *q, c;
1338 
1339 	len = 128;
1340 	if ((buf = malloc(len)) == NULL)
1341 		errx(1, "Can't allocate memory for transfer buffer");
1342 	for (i = 0; ; i++) {
1343 		if (i >= len - 1) {
1344 			if ((q = realloc(buf, 2 * len)) == NULL)
1345 				errx(1, "Can't expand transfer buffer");
1346 			buf = q;
1347 			len *= 2;
1348 		}
1349 		if (SSL_read(ssl, &c, 1) <= 0)
1350 			break;
1351 		buf[i] = c;
1352 		if (c == '\n')
1353 			break;
1354 	}
1355 	*lenp = i;
1356 	return (buf);
1357 }
1358 
1359 int
1360 proxy_connect(int socket, char *host, char *cookie)
1361 {
1362 	int l;
1363 	char buf[1024];
1364 	char *connstr, *hosttail, *port;
1365 
1366 	if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
1367 		(hosttail[1] == '\0' || hosttail[1] == ':')) {
1368 		host++;
1369 		*hosttail++ = '\0';
1370 	} else
1371 		hosttail = host;
1372 
1373 	port = strrchr(hosttail, ':');               /* find portnum */
1374 	if (port != NULL)
1375 		*port++ = '\0';
1376 	if (!port)
1377 		port = "443";
1378 
1379 	if (cookie) {
1380 		l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1381 			"Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
1382 			host, port, cookie, HTTP_USER_AGENT);
1383 	} else {
1384 		l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n",
1385 			host, port, HTTP_USER_AGENT);
1386 	}
1387 
1388 	if (l == -1)
1389 		errx(1, "Could not allocate memory to assemble connect string!");
1390 #ifndef SMALL
1391 	if (debug)
1392 		printf("%s", connstr);
1393 #endif /* !SMALL */
1394 	if (write(socket, connstr, l) != l)
1395 		err(1, "Could not send connect string");
1396 	read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
1397 	free(connstr);
1398 	return(200);
1399 }
1400 #endif /* !SMALL */
1401