xref: /netbsd-src/usr.bin/ftp/fetch.c (revision a536ee5124e62c9a0051a252f7833dc8f50f44c9)
1 /*	$NetBSD: fetch.c,v 1.201 2012/12/22 16:58:51 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * This code is derived from software contributed to The NetBSD Foundation
11  * by Scott Aaron Bamford.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __RCSID("$NetBSD: fetch.c,v 1.201 2012/12/22 16:58:51 christos Exp $");
38 #endif /* not lint */
39 
40 /*
41  * FTP User Program -- Command line file retrieval
42  */
43 
44 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <sys/socket.h>
47 #include <sys/stat.h>
48 #include <sys/time.h>
49 
50 #include <netinet/in.h>
51 
52 #include <arpa/ftp.h>
53 #include <arpa/inet.h>
54 
55 #include <assert.h>
56 #include <ctype.h>
57 #include <err.h>
58 #include <errno.h>
59 #include <netdb.h>
60 #include <fcntl.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <time.h>
66 
67 #include "ssl.h"
68 #include "ftp_var.h"
69 #include "version.h"
70 
71 typedef enum {
72 	UNKNOWN_URL_T=-1,
73 	HTTP_URL_T,
74 #ifdef WITH_SSL
75 	HTTPS_URL_T,
76 #endif
77 	FTP_URL_T,
78 	FILE_URL_T,
79 	CLASSIC_URL_T
80 } url_t;
81 
82 __dead static void	aborthttp(int);
83 #ifndef NO_AUTH
84 static int	auth_url(const char *, char **, const char *, const char *);
85 static void	base64_encode(const unsigned char *, size_t, unsigned char *);
86 #endif
87 static int	go_fetch(const char *);
88 static int	fetch_ftp(const char *);
89 static int	fetch_url(const char *, const char *, char *, char *);
90 static const char *match_token(const char **, const char *);
91 static int	parse_url(const char *, const char *, url_t *, char **,
92 			    char **, char **, char **, in_port_t *, char **);
93 static void	url_decode(char *);
94 
95 static int	redirect_loop;
96 
97 
98 #define	STRNEQUAL(a,b)	(strncasecmp((a), (b), sizeof((b))-1) == 0)
99 #define	ISLWS(x)	((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
100 #define	SKIPLWS(x)	do { while (ISLWS((*x))) x++; } while (0)
101 
102 
103 #define	ABOUT_URL	"about:"	/* propaganda */
104 #define	FILE_URL	"file://"	/* file URL prefix */
105 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
106 #define	HTTP_URL	"http://"	/* http URL prefix */
107 #ifdef WITH_SSL
108 #define	HTTPS_URL	"https://"	/* https URL prefix */
109 
110 #define	IS_HTTP_TYPE(urltype) \
111 	(((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T))
112 #else
113 #define	IS_HTTP_TYPE(urltype) \
114 	((urltype) == HTTP_URL_T)
115 #endif
116 
117 /*
118  * Determine if token is the next word in buf (case insensitive).
119  * If so, advance buf past the token and any trailing LWS, and
120  * return a pointer to the token (in buf).  Otherwise, return NULL.
121  * token may be preceded by LWS.
122  * token must be followed by LWS or NUL.  (I.e, don't partial match).
123  */
124 static const char *
125 match_token(const char **buf, const char *token)
126 {
127 	const char	*p, *orig;
128 	size_t		tlen;
129 
130 	tlen = strlen(token);
131 	p = *buf;
132 	SKIPLWS(p);
133 	orig = p;
134 	if (strncasecmp(p, token, tlen) != 0)
135 		return NULL;
136 	p += tlen;
137 	if (*p != '\0' && !ISLWS(*p))
138 		return NULL;
139 	SKIPLWS(p);
140 	orig = *buf;
141 	*buf = p;
142 	return orig;
143 }
144 
145 #ifndef NO_AUTH
146 /*
147  * Generate authorization response based on given authentication challenge.
148  * Returns -1 if an error occurred, otherwise 0.
149  * Sets response to a malloc(3)ed string; caller should free.
150  */
151 static int
152 auth_url(const char *challenge, char **response, const char *guser,
153 	const char *gpass)
154 {
155 	const char	*cp, *scheme, *errormsg;
156 	char		*ep, *clear, *realm;
157 	char		 uuser[BUFSIZ], *gotpass;
158 	const char	*upass;
159 	int		 rval;
160 	size_t		 len, clen, rlen;
161 
162 	*response = NULL;
163 	clear = realm = NULL;
164 	rval = -1;
165 	cp = challenge;
166 	scheme = "Basic";	/* only support Basic authentication */
167 	gotpass = NULL;
168 
169 	DPRINTF("auth_url: challenge `%s'\n", challenge);
170 
171 	if (! match_token(&cp, scheme)) {
172 		warnx("Unsupported authentication challenge `%s'",
173 		    challenge);
174 		goto cleanup_auth_url;
175 	}
176 
177 #define	REALM "realm=\""
178 	if (STRNEQUAL(cp, REALM))
179 		cp += sizeof(REALM) - 1;
180 	else {
181 		warnx("Unsupported authentication challenge `%s'",
182 		    challenge);
183 		goto cleanup_auth_url;
184 	}
185 /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
186 	if ((ep = strchr(cp, '\"')) != NULL) {
187 		len = ep - cp;
188 		realm = (char *)ftp_malloc(len + 1);
189 		(void)strlcpy(realm, cp, len + 1);
190 	} else {
191 		warnx("Unsupported authentication challenge `%s'",
192 		    challenge);
193 		goto cleanup_auth_url;
194 	}
195 
196 	fprintf(ttyout, "Username for `%s': ", realm);
197 	if (guser != NULL) {
198 		(void)strlcpy(uuser, guser, sizeof(uuser));
199 		fprintf(ttyout, "%s\n", uuser);
200 	} else {
201 		(void)fflush(ttyout);
202 		if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) {
203 			warnx("%s; can't authenticate", errormsg);
204 			goto cleanup_auth_url;
205 		}
206 	}
207 	if (gpass != NULL)
208 		upass = gpass;
209 	else {
210 		gotpass = getpass("Password: ");
211 		if (gotpass == NULL) {
212 			warnx("Can't read password");
213 			goto cleanup_auth_url;
214 		}
215 		upass = gotpass;
216 	}
217 
218 	clen = strlen(uuser) + strlen(upass) + 2;	/* user + ":" + pass + "\0" */
219 	clear = (char *)ftp_malloc(clen);
220 	(void)strlcpy(clear, uuser, clen);
221 	(void)strlcat(clear, ":", clen);
222 	(void)strlcat(clear, upass, clen);
223 	if (gotpass)
224 		memset(gotpass, 0, strlen(gotpass));
225 
226 						/* scheme + " " + enc + "\0" */
227 	rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
228 	*response = (char *)ftp_malloc(rlen);
229 	(void)strlcpy(*response, scheme, rlen);
230 	len = strlcat(*response, " ", rlen);
231 			/* use  `clen - 1'  to not encode the trailing NUL */
232 	base64_encode((unsigned char *)clear, clen - 1,
233 	    (unsigned char *)*response + len);
234 	memset(clear, 0, clen);
235 	rval = 0;
236 
237  cleanup_auth_url:
238 	FREEPTR(clear);
239 	FREEPTR(realm);
240 	return (rval);
241 }
242 
243 /*
244  * Encode len bytes starting at clear using base64 encoding into encoded,
245  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
246  */
247 static void
248 base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
249 {
250 	static const unsigned char enc[] =
251 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
252 	unsigned char	*cp;
253 	size_t	 i;
254 
255 	cp = encoded;
256 	for (i = 0; i < len; i += 3) {
257 		*(cp++) = enc[((clear[i + 0] >> 2))];
258 		*(cp++) = enc[((clear[i + 0] << 4) & 0x30)
259 			    | ((clear[i + 1] >> 4) & 0x0f)];
260 		*(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
261 			    | ((clear[i + 2] >> 6) & 0x03)];
262 		*(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
263 	}
264 	*cp = '\0';
265 	while (i-- > len)
266 		*(--cp) = '=';
267 }
268 #endif
269 
270 /*
271  * Decode %xx escapes in given string, `in-place'.
272  */
273 static void
274 url_decode(char *url)
275 {
276 	unsigned char *p, *q;
277 
278 	if (EMPTYSTRING(url))
279 		return;
280 	p = q = (unsigned char *)url;
281 
282 #define	HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
283 	while (*p) {
284 		if (p[0] == '%'
285 		    && p[1] && isxdigit((unsigned char)p[1])
286 		    && p[2] && isxdigit((unsigned char)p[2])) {
287 			*q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
288 			p+=3;
289 		} else
290 			*q++ = *p++;
291 	}
292 	*q = '\0';
293 }
294 
295 
296 /*
297  * Parse URL of form (per RFC 3986):
298  *	<type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
299  * Returns -1 if a parse error occurred, otherwise 0.
300  * It's the caller's responsibility to url_decode() the returned
301  * user, pass and path.
302  *
303  * Sets type to url_t, each of the given char ** pointers to a
304  * malloc(3)ed strings of the relevant section, and port to
305  * the number given, or ftpport if ftp://, or httpport if http://.
306  *
307  * XXX: this is not totally RFC 3986 compliant; <path> will have the
308  * leading `/' unless it's an ftp:// URL, as this makes things easier
309  * for file:// and http:// URLs.  ftp:// URLs have the `/' between the
310  * host and the URL-path removed, but any additional leading slashes
311  * in the URL-path are retained (because they imply that we should
312  * later do "CWD" with a null argument).
313  *
314  * Examples:
315  *	 input URL			 output path
316  *	 ---------			 -----------
317  *	"http://host"			"/"
318  *	"http://host/"			"/"
319  *	"http://host/path"		"/path"
320  *	"file://host/dir/file"		"dir/file"
321  *	"ftp://host"			""
322  *	"ftp://host/"			""
323  *	"ftp://host//"			"/"
324  *	"ftp://host/dir/file"		"dir/file"
325  *	"ftp://host//dir/file"		"/dir/file"
326  */
327 static int
328 parse_url(const char *url, const char *desc, url_t *utype,
329 		char **uuser, char **pass, char **host, char **port,
330 		in_port_t *portnum, char **path)
331 {
332 	const char	*origurl, *tport;
333 	char		*cp, *ep, *thost;
334 	size_t		 len;
335 
336 	if (url == NULL || desc == NULL || utype == NULL || uuser == NULL
337 	    || pass == NULL || host == NULL || port == NULL || portnum == NULL
338 	    || path == NULL)
339 		errx(1, "parse_url: invoked with NULL argument!");
340 	DPRINTF("parse_url: %s `%s'\n", desc, url);
341 
342 	origurl = url;
343 	*utype = UNKNOWN_URL_T;
344 	*uuser = *pass = *host = *port = *path = NULL;
345 	*portnum = 0;
346 	tport = NULL;
347 
348 	if (STRNEQUAL(url, HTTP_URL)) {
349 		url += sizeof(HTTP_URL) - 1;
350 		*utype = HTTP_URL_T;
351 		*portnum = HTTP_PORT;
352 		tport = httpport;
353 	} else if (STRNEQUAL(url, FTP_URL)) {
354 		url += sizeof(FTP_URL) - 1;
355 		*utype = FTP_URL_T;
356 		*portnum = FTP_PORT;
357 		tport = ftpport;
358 	} else if (STRNEQUAL(url, FILE_URL)) {
359 		url += sizeof(FILE_URL) - 1;
360 		*utype = FILE_URL_T;
361 #ifdef WITH_SSL
362 	} else if (STRNEQUAL(url, HTTPS_URL)) {
363 		url += sizeof(HTTPS_URL) - 1;
364 		*utype = HTTPS_URL_T;
365 		*portnum = HTTPS_PORT;
366 		tport = httpsport;
367 #endif
368 	} else {
369 		warnx("Invalid %s `%s'", desc, url);
370  cleanup_parse_url:
371 		FREEPTR(*uuser);
372 		if (*pass != NULL)
373 			memset(*pass, 0, strlen(*pass));
374 		FREEPTR(*pass);
375 		FREEPTR(*host);
376 		FREEPTR(*port);
377 		FREEPTR(*path);
378 		return (-1);
379 	}
380 
381 	if (*url == '\0')
382 		return (0);
383 
384 			/* find [user[:pass]@]host[:port] */
385 	ep = strchr(url, '/');
386 	if (ep == NULL)
387 		thost = ftp_strdup(url);
388 	else {
389 		len = ep - url;
390 		thost = (char *)ftp_malloc(len + 1);
391 		(void)strlcpy(thost, url, len + 1);
392 		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
393 			ep++;
394 		*path = ftp_strdup(ep);
395 	}
396 
397 	cp = strchr(thost, '@');	/* look for user[:pass]@ in URLs */
398 	if (cp != NULL) {
399 		if (*utype == FTP_URL_T)
400 			anonftp = 0;	/* disable anonftp */
401 		*uuser = thost;
402 		*cp = '\0';
403 		thost = ftp_strdup(cp + 1);
404 		cp = strchr(*uuser, ':');
405 		if (cp != NULL) {
406 			*cp = '\0';
407 			*pass = ftp_strdup(cp + 1);
408 		}
409 		url_decode(*uuser);
410 		if (*pass)
411 			url_decode(*pass);
412 	}
413 
414 #ifdef INET6
415 			/*
416 			 * Check if thost is an encoded IPv6 address, as per
417 			 * RFC 3986:
418 			 *	`[' ipv6-address ']'
419 			 */
420 	if (*thost == '[') {
421 		cp = thost + 1;
422 		if ((ep = strchr(cp, ']')) == NULL ||
423 		    (ep[1] != '\0' && ep[1] != ':')) {
424 			warnx("Invalid address `%s' in %s `%s'",
425 			    thost, desc, origurl);
426 			goto cleanup_parse_url;
427 		}
428 		len = ep - cp;		/* change `[xyz]' -> `xyz' */
429 		memmove(thost, thost + 1, len);
430 		thost[len] = '\0';
431 		if (! isipv6addr(thost)) {
432 			warnx("Invalid IPv6 address `%s' in %s `%s'",
433 			    thost, desc, origurl);
434 			goto cleanup_parse_url;
435 		}
436 		cp = ep + 1;
437 		if (*cp == ':')
438 			cp++;
439 		else
440 			cp = NULL;
441 	} else
442 #endif /* INET6 */
443 		if ((cp = strchr(thost, ':')) != NULL)
444 			*cp++ = '\0';
445 	*host = thost;
446 
447 			/* look for [:port] */
448 	if (cp != NULL) {
449 		unsigned long	nport;
450 
451 		nport = strtoul(cp, &ep, 10);
452 		if (*cp == '\0' || *ep != '\0' ||
453 		    nport < 1 || nport > MAX_IN_PORT_T) {
454 			warnx("Unknown port `%s' in %s `%s'",
455 			    cp, desc, origurl);
456 			goto cleanup_parse_url;
457 		}
458 		*portnum = nport;
459 		tport = cp;
460 	}
461 
462 	if (tport != NULL)
463 		*port = ftp_strdup(tport);
464 	if (*path == NULL) {
465 		const char *emptypath = "/";
466 		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
467 			emptypath++;
468 		*path = ftp_strdup(emptypath);
469 	}
470 
471 	DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
472 	    "path `%s'\n",
473 	    STRorNULL(*uuser), STRorNULL(*pass),
474 	    STRorNULL(*host), STRorNULL(*port),
475 	    *portnum ? *portnum : -1, STRorNULL(*path));
476 
477 	return (0);
478 }
479 
480 sigjmp_buf	httpabort;
481 
482 /*
483  * Retrieve URL, via a proxy if necessary, using HTTP.
484  * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
485  * http_proxy/https_proxy as appropriate.
486  * Supports HTTP redirects.
487  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
488  * is still open (e.g, ftp xfer with trailing /)
489  */
490 static int
491 fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
492 {
493 	struct addrinfo		hints, *res, *res0 = NULL;
494 	int			error;
495 	sigfunc volatile	oldintr;
496 	sigfunc volatile	oldintp;
497 	int volatile		s;
498 	struct stat		sb;
499 	int volatile		ischunked;
500 	int volatile		isproxy;
501 	int volatile		rval;
502 	int volatile		hcode;
503 	int			len;
504 	size_t			flen;
505 	static size_t		bufsize;
506 	static char		*xferbuf;
507 	const char		*cp, *token;
508 	char			*ep;
509 	char			buf[FTPBUFLEN];
510 	const char		*errormsg;
511 	char			*volatile savefile;
512 	char			*volatile auth;
513 	char			*volatile location;
514 	char			*volatile message;
515 	char			*uuser, *pass, *host, *port, *path;
516 	char			*volatile decodedpath;
517 	char			*puser, *ppass, *useragent;
518 	off_t			hashbytes, rangestart, rangeend, entitylen;
519 	int			(*volatile closefunc)(FILE *);
520 	FETCH			*volatile fin;
521 	FILE			*volatile fout;
522 	time_t			mtime;
523 	url_t			urltype;
524 	in_port_t		portnum;
525 #ifdef WITH_SSL
526 	void			*ssl;
527 #endif
528 
529 	DPRINTF("fetch_url: `%s' proxyenv `%s'\n", url, STRorNULL(proxyenv));
530 
531 	oldintr = oldintp = NULL;
532 	closefunc = NULL;
533 	fin = NULL;
534 	fout = NULL;
535 	s = -1;
536 	savefile = NULL;
537 	auth = location = message = NULL;
538 	ischunked = isproxy = hcode = 0;
539 	rval = 1;
540 	uuser = pass = host = path = decodedpath = puser = ppass = NULL;
541 
542 	if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port,
543 	    &portnum, &path) == -1)
544 		goto cleanup_fetch_url;
545 
546 	if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
547 	    && strcasecmp(host, "localhost") != 0) {
548 		warnx("No support for non local file URL `%s'", url);
549 		goto cleanup_fetch_url;
550 	}
551 
552 	if (EMPTYSTRING(path)) {
553 		if (urltype == FTP_URL_T) {
554 			rval = fetch_ftp(url);
555 			goto cleanup_fetch_url;
556 		}
557 		if (!IS_HTTP_TYPE(urltype) || outfile == NULL)  {
558 			warnx("Invalid URL (no file after host) `%s'", url);
559 			goto cleanup_fetch_url;
560 		}
561 	}
562 
563 	decodedpath = ftp_strdup(path);
564 	url_decode(decodedpath);
565 
566 	if (outfile)
567 		savefile = ftp_strdup(outfile);
568 	else {
569 		cp = strrchr(decodedpath, '/');		/* find savefile */
570 		if (cp != NULL)
571 			savefile = ftp_strdup(cp + 1);
572 		else
573 			savefile = ftp_strdup(decodedpath);
574 	}
575 	DPRINTF("fetch_url: savefile `%s'\n", savefile);
576 	if (EMPTYSTRING(savefile)) {
577 		if (urltype == FTP_URL_T) {
578 			rval = fetch_ftp(url);
579 			goto cleanup_fetch_url;
580 		}
581 		warnx("No file after directory (you must specify an "
582 		    "output file) `%s'", url);
583 		goto cleanup_fetch_url;
584 	}
585 
586 	restart_point = 0;
587 	filesize = -1;
588 	rangestart = rangeend = entitylen = -1;
589 	mtime = -1;
590 	if (restartautofetch) {
591 		if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
592 		    stat(savefile, &sb) == 0)
593 			restart_point = sb.st_size;
594 	}
595 	if (urltype == FILE_URL_T) {		/* file:// URLs */
596 		direction = "copied";
597 		fin = fetch_open(decodedpath, "r");
598 		if (fin == NULL) {
599 			warn("Can't open `%s'", decodedpath);
600 			goto cleanup_fetch_url;
601 		}
602 		if (fstat(fetch_fileno(fin), &sb) == 0) {
603 			mtime = sb.st_mtime;
604 			filesize = sb.st_size;
605 		}
606 		if (restart_point) {
607 			if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) < 0) {
608 				warn("Can't seek to restart `%s'",
609 				    decodedpath);
610 				goto cleanup_fetch_url;
611 			}
612 		}
613 		if (verbose) {
614 			fprintf(ttyout, "Copying %s", decodedpath);
615 			if (restart_point)
616 				fprintf(ttyout, " (restarting at " LLF ")",
617 				    (LLT)restart_point);
618 			fputs("\n", ttyout);
619 		}
620 		if (0 == rcvbuf_size) {
621 			rcvbuf_size = 8 * 1024; /* XXX */
622 		}
623 	} else {				/* ftp:// or http:// URLs */
624 		const char *leading;
625 		int hasleading;
626 
627 		if (proxyenv == NULL) {
628 #ifdef WITH_SSL
629 			if (urltype == HTTPS_URL_T)
630 				proxyenv = getoptionvalue("https_proxy");
631 #endif
632 			if (proxyenv == NULL && IS_HTTP_TYPE(urltype))
633 				proxyenv = getoptionvalue("http_proxy");
634 			else if (urltype == FTP_URL_T)
635 				proxyenv = getoptionvalue("ftp_proxy");
636 		}
637 		direction = "retrieved";
638 		if (! EMPTYSTRING(proxyenv)) {			/* use proxy */
639 			url_t purltype;
640 			char *phost, *ppath;
641 			char *pport, *no_proxy;
642 			in_port_t pportnum;
643 
644 			isproxy = 1;
645 
646 				/* check URL against list of no_proxied sites */
647 			no_proxy = getoptionvalue("no_proxy");
648 			if (! EMPTYSTRING(no_proxy)) {
649 				char *np, *np_copy, *np_iter;
650 				unsigned long np_port;
651 				size_t hlen, plen;
652 
653 				np_iter = np_copy = ftp_strdup(no_proxy);
654 				hlen = strlen(host);
655 				while ((cp = strsep(&np_iter, " ,")) != NULL) {
656 					if (*cp == '\0')
657 						continue;
658 					if ((np = strrchr(cp, ':')) != NULL) {
659 						*np++ =  '\0';
660 						np_port = strtoul(np, &ep, 10);
661 						if (*np == '\0' || *ep != '\0')
662 							continue;
663 						if (np_port != portnum)
664 							continue;
665 					}
666 					plen = strlen(cp);
667 					if (hlen < plen)
668 						continue;
669 					if (strncasecmp(host + hlen - plen,
670 					    cp, plen) == 0) {
671 						isproxy = 0;
672 						break;
673 					}
674 				}
675 				FREEPTR(np_copy);
676 				if (isproxy == 0 && urltype == FTP_URL_T) {
677 					rval = fetch_ftp(url);
678 					goto cleanup_fetch_url;
679 				}
680 			}
681 
682 			if (isproxy) {
683 				if (restart_point) {
684 					warnx("Can't restart via proxy URL `%s'",
685 					    proxyenv);
686 					goto cleanup_fetch_url;
687 				}
688 				if (parse_url(proxyenv, "proxy URL", &purltype,
689 				    &puser, &ppass, &phost, &pport, &pportnum,
690 				    &ppath) == -1)
691 					goto cleanup_fetch_url;
692 
693 				if ((!IS_HTTP_TYPE(purltype)
694 				     && purltype != FTP_URL_T) ||
695 				    EMPTYSTRING(phost) ||
696 				    (! EMPTYSTRING(ppath)
697 				     && strcmp(ppath, "/") != 0)) {
698 					warnx("Malformed proxy URL `%s'",
699 					    proxyenv);
700 					FREEPTR(phost);
701 					FREEPTR(pport);
702 					FREEPTR(ppath);
703 					goto cleanup_fetch_url;
704 				}
705 				if (isipv6addr(host) &&
706 				    strchr(host, '%') != NULL) {
707 					warnx(
708 "Scoped address notation `%s' disallowed via web proxy",
709 					    host);
710 					FREEPTR(phost);
711 					FREEPTR(pport);
712 					FREEPTR(ppath);
713 					goto cleanup_fetch_url;
714 				}
715 
716 				FREEPTR(host);
717 				host = phost;
718 				FREEPTR(port);
719 				port = pport;
720 				FREEPTR(path);
721 				path = ftp_strdup(url);
722 				FREEPTR(ppath);
723 				urltype = purltype;
724 			}
725 		} /* ! EMPTYSTRING(proxyenv) */
726 
727 		memset(&hints, 0, sizeof(hints));
728 		hints.ai_flags = 0;
729 		hints.ai_family = family;
730 		hints.ai_socktype = SOCK_STREAM;
731 		hints.ai_protocol = 0;
732 		error = getaddrinfo(host, port, &hints, &res0);
733 		if (error) {
734 			warnx("Can't LOOKUP `%s:%s': %s", host, port,
735 			    (error == EAI_SYSTEM) ? strerror(errno)
736 						  : gai_strerror(error));
737 			goto cleanup_fetch_url;
738 		}
739 		if (res0->ai_canonname)
740 			host = res0->ai_canonname;
741 
742 		s = -1;
743 #ifdef WITH_SSL
744 		ssl = NULL;
745 #endif
746 		for (res = res0; res; res = res->ai_next) {
747 			char	hname[NI_MAXHOST], sname[NI_MAXSERV];
748 
749 			ai_unmapped(res);
750 			if (getnameinfo(res->ai_addr, res->ai_addrlen,
751 			    hname, sizeof(hname), sname, sizeof(sname),
752 			    NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
753 				strlcpy(hname, "?", sizeof(hname));
754 				strlcpy(sname, "?", sizeof(sname));
755 			}
756 
757 			if (verbose && res0->ai_next) {
758 				fprintf(ttyout, "Trying %s:%s ...\n",
759 				    hname, sname);
760 			}
761 
762 			s = socket(res->ai_family, SOCK_STREAM,
763 			    res->ai_protocol);
764 			if (s < 0) {
765 				warn(
766 				    "Can't create socket for connection to "
767 				    "`%s:%s'", hname, sname);
768 				continue;
769 			}
770 
771 			if (ftp_connect(s, res->ai_addr, res->ai_addrlen,
772 			    verbose || !res->ai_next) < 0) {
773 				close(s);
774 				s = -1;
775 				continue;
776 			}
777 
778 #ifdef WITH_SSL
779 			if (urltype == HTTPS_URL_T) {
780 				if ((ssl = fetch_start_ssl(s)) == NULL) {
781 					close(s);
782 					s = -1;
783 					continue;
784 				}
785 			}
786 #endif
787 
788 			/* success */
789 			break;
790 		}
791 
792 		if (s < 0) {
793 			warnx("Can't connect to `%s:%s'", host, port);
794 			goto cleanup_fetch_url;
795 		}
796 
797 		fin = fetch_fdopen(s, "r+");
798 		fetch_set_ssl(fin, ssl);
799 
800 		/*
801 		 * Construct and send the request.
802 		 */
803 		if (verbose)
804 			fprintf(ttyout, "Requesting %s\n", url);
805 		leading = "  (";
806 		hasleading = 0;
807 		if (isproxy) {
808 			if (verbose) {
809 				fprintf(ttyout, "%svia %s:%s", leading,
810 				    host, port);
811 				leading = ", ";
812 				hasleading++;
813 			}
814 			fetch_printf(fin, "GET %s HTTP/1.0\r\n", path);
815 			if (flushcache)
816 				fetch_printf(fin, "Pragma: no-cache\r\n");
817 		} else {
818 			fetch_printf(fin, "GET %s HTTP/1.1\r\n", path);
819 			if (strchr(host, ':')) {
820 				char *h, *p;
821 
822 				/*
823 				 * strip off IPv6 scope identifier, since it is
824 				 * local to the node
825 				 */
826 				h = ftp_strdup(host);
827 				if (isipv6addr(h) &&
828 				    (p = strchr(h, '%')) != NULL) {
829 					*p = '\0';
830 				}
831 				fetch_printf(fin, "Host: [%s]", h);
832 				free(h);
833 			} else
834 				fetch_printf(fin, "Host: %s", host);
835 #ifdef WITH_SSL
836 			if ((urltype == HTTP_URL_T && portnum != HTTP_PORT) ||
837 			    (urltype == HTTPS_URL_T && portnum != HTTPS_PORT))
838 #else
839 			if (portnum != HTTP_PORT)
840 #endif
841 				fetch_printf(fin, ":%u", portnum);
842 			fetch_printf(fin, "\r\n");
843 			fetch_printf(fin, "Accept: */*\r\n");
844 			fetch_printf(fin, "Connection: close\r\n");
845 			if (restart_point) {
846 				fputs(leading, ttyout);
847 				fprintf(ttyout, "Range: bytes=" LLF "-\r\n",
848 				    (LLT)restart_point);
849 				fprintf(ttyout, "restarting at " LLF,
850 				    (LLT)restart_point);
851 				leading = ", ";
852 				hasleading++;
853 			}
854 			if (flushcache)
855 				fetch_printf(fin, "Cache-Control: no-cache\r\n");
856 		}
857 		if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
858 			fetch_printf(fin, "User-Agent: %s\r\n", useragent);
859 		} else {
860 			fetch_printf(fin, "User-Agent: %s/%s\r\n",
861 			    FTP_PRODUCT, FTP_VERSION);
862 		}
863 		if (wwwauth) {
864 			if (verbose) {
865 				fprintf(ttyout, "%swith authorization",
866 				    leading);
867 				leading = ", ";
868 				hasleading++;
869 			}
870 			fetch_printf(fin, "Authorization: %s\r\n", wwwauth);
871 		}
872 		if (proxyauth) {
873 			if (verbose) {
874 				fprintf(ttyout,
875 				    "%swith proxy authorization", leading);
876 				leading = ", ";
877 				hasleading++;
878 			}
879 			fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
880 		}
881 		if (verbose && hasleading)
882 			fputs(")\n", ttyout);
883 		fetch_printf(fin, "\r\n");
884 		if (fetch_flush(fin) == EOF) {
885 			warn("Writing HTTP request");
886 			goto cleanup_fetch_url;
887 		}
888 
889 				/* Read the response */
890 		len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
891 		if (len < 0) {
892 			if (*errormsg == '\n')
893 				errormsg++;
894 			warnx("Receiving HTTP reply: %s", errormsg);
895 			goto cleanup_fetch_url;
896 		}
897 		while (len > 0 && (ISLWS(buf[len-1])))
898 			buf[--len] = '\0';
899 		DPRINTF("fetch_url: received `%s'\n", buf);
900 
901 				/* Determine HTTP response code */
902 		cp = strchr(buf, ' ');
903 		if (cp == NULL)
904 			goto improper;
905 		else
906 			cp++;
907 		hcode = strtol(cp, &ep, 10);
908 		if (*ep != '\0' && !isspace((unsigned char)*ep))
909 			goto improper;
910 		message = ftp_strdup(cp);
911 
912 				/* Read the rest of the header. */
913 		while (1) {
914 			len = fetch_getline(fin, buf, sizeof(buf), &errormsg);
915 			if (len < 0) {
916 				if (*errormsg == '\n')
917 					errormsg++;
918 				warnx("Receiving HTTP reply: %s", errormsg);
919 				goto cleanup_fetch_url;
920 			}
921 			while (len > 0 && (ISLWS(buf[len-1])))
922 				buf[--len] = '\0';
923 			if (len == 0)
924 				break;
925 			DPRINTF("fetch_url: received `%s'\n", buf);
926 
927 		/*
928 		 * Look for some headers
929 		 */
930 
931 			cp = buf;
932 
933 			if (match_token(&cp, "Content-Length:")) {
934 				filesize = STRTOLL(cp, &ep, 10);
935 				if (filesize < 0 || *ep != '\0')
936 					goto improper;
937 				DPRINTF("fetch_url: parsed len as: " LLF "\n",
938 				    (LLT)filesize);
939 
940 			} else if (match_token(&cp, "Content-Range:")) {
941 				if (! match_token(&cp, "bytes"))
942 					goto improper;
943 
944 				if (*cp == '*')
945 					cp++;
946 				else {
947 					rangestart = STRTOLL(cp, &ep, 10);
948 					if (rangestart < 0 || *ep != '-')
949 						goto improper;
950 					cp = ep + 1;
951 					rangeend = STRTOLL(cp, &ep, 10);
952 					if (rangeend < 0 || rangeend < rangestart)
953 						goto improper;
954 					cp = ep;
955 				}
956 				if (*cp != '/')
957 					goto improper;
958 				cp++;
959 				if (*cp == '*')
960 					cp++;
961 				else {
962 					entitylen = STRTOLL(cp, &ep, 10);
963 					if (entitylen < 0)
964 						goto improper;
965 					cp = ep;
966 				}
967 				if (*cp != '\0')
968 					goto improper;
969 
970 #ifndef NO_DEBUG
971 				if (ftp_debug) {
972 					fprintf(ttyout, "parsed range as: ");
973 					if (rangestart == -1)
974 						fprintf(ttyout, "*");
975 					else
976 						fprintf(ttyout, LLF "-" LLF,
977 						    (LLT)rangestart,
978 						    (LLT)rangeend);
979 					fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
980 				}
981 #endif
982 				if (! restart_point) {
983 					warnx(
984 				    "Received unexpected Content-Range header");
985 					goto cleanup_fetch_url;
986 				}
987 
988 			} else if (match_token(&cp, "Last-Modified:")) {
989 				struct tm parsed;
990 				const char *t;
991 
992 				memset(&parsed, 0, sizeof(parsed));
993 				t = parse_rfc2616time(&parsed, cp);
994 				if (t != NULL) {
995 					parsed.tm_isdst = -1;
996 					if (*t == '\0')
997 						mtime = timegm(&parsed);
998 #ifndef NO_DEBUG
999 					if (ftp_debug && mtime != -1) {
1000 						fprintf(ttyout,
1001 						    "parsed time as: %s",
1002 						rfc2822time(localtime(&mtime)));
1003 					}
1004 #endif
1005 				}
1006 
1007 			} else if (match_token(&cp, "Location:")) {
1008 				location = ftp_strdup(cp);
1009 				DPRINTF("fetch_url: parsed location as `%s'\n",
1010 				    cp);
1011 
1012 			} else if (match_token(&cp, "Transfer-Encoding:")) {
1013 				if (match_token(&cp, "binary")) {
1014 					warnx(
1015 			"Bogus transfer encoding `binary' (fetching anyway)");
1016 					continue;
1017 				}
1018 				if (! (token = match_token(&cp, "chunked"))) {
1019 					warnx(
1020 				    "Unsupported transfer encoding `%s'",
1021 					    token);
1022 					goto cleanup_fetch_url;
1023 				}
1024 				ischunked++;
1025 				DPRINTF("fetch_url: using chunked encoding\n");
1026 
1027 			} else if (match_token(&cp, "Proxy-Authenticate:")
1028 				|| match_token(&cp, "WWW-Authenticate:")) {
1029 				if (! (token = match_token(&cp, "Basic"))) {
1030 					DPRINTF(
1031 			"fetch_url: skipping unknown auth scheme `%s'\n",
1032 						    token);
1033 					continue;
1034 				}
1035 				FREEPTR(auth);
1036 				auth = ftp_strdup(token);
1037 				DPRINTF("fetch_url: parsed auth as `%s'\n", cp);
1038 			}
1039 
1040 		}
1041 				/* finished parsing header */
1042 
1043 		switch (hcode) {
1044 		case 200:
1045 			break;
1046 		case 206:
1047 			if (! restart_point) {
1048 				warnx("Not expecting partial content header");
1049 				goto cleanup_fetch_url;
1050 			}
1051 			break;
1052 		case 300:
1053 		case 301:
1054 		case 302:
1055 		case 303:
1056 		case 305:
1057 		case 307:
1058 			if (EMPTYSTRING(location)) {
1059 				warnx(
1060 				"No redirection Location provided by server");
1061 				goto cleanup_fetch_url;
1062 			}
1063 			if (redirect_loop++ > 5) {
1064 				warnx("Too many redirections requested");
1065 				goto cleanup_fetch_url;
1066 			}
1067 			if (hcode == 305) {
1068 				if (verbose)
1069 					fprintf(ttyout, "Redirected via %s\n",
1070 					    location);
1071 				rval = fetch_url(url, location,
1072 				    proxyauth, wwwauth);
1073 			} else {
1074 				if (verbose)
1075 					fprintf(ttyout, "Redirected to %s\n",
1076 					    location);
1077 				rval = go_fetch(location);
1078 			}
1079 			goto cleanup_fetch_url;
1080 #ifndef NO_AUTH
1081 		case 401:
1082 		case 407:
1083 		    {
1084 			char **authp;
1085 			char *auser, *apass;
1086 
1087 			if (hcode == 401) {
1088 				authp = &wwwauth;
1089 				auser = uuser;
1090 				apass = pass;
1091 			} else {
1092 				authp = &proxyauth;
1093 				auser = puser;
1094 				apass = ppass;
1095 			}
1096 			if (verbose || *authp == NULL ||
1097 			    auser == NULL || apass == NULL)
1098 				fprintf(ttyout, "%s\n", message);
1099 			if (EMPTYSTRING(auth)) {
1100 				warnx(
1101 			    "No authentication challenge provided by server");
1102 				goto cleanup_fetch_url;
1103 			}
1104 			if (*authp != NULL) {
1105 				char reply[10];
1106 
1107 				fprintf(ttyout,
1108 				    "Authorization failed. Retry (y/n)? ");
1109 				if (get_line(stdin, reply, sizeof(reply), NULL)
1110 				    < 0) {
1111 					goto cleanup_fetch_url;
1112 				}
1113 				if (tolower((unsigned char)reply[0]) != 'y')
1114 					goto cleanup_fetch_url;
1115 				auser = NULL;
1116 				apass = NULL;
1117 			}
1118 			if (auth_url(auth, authp, auser, apass) == 0) {
1119 				rval = fetch_url(url, proxyenv,
1120 				    proxyauth, wwwauth);
1121 				memset(*authp, 0, strlen(*authp));
1122 				FREEPTR(*authp);
1123 			}
1124 			goto cleanup_fetch_url;
1125 		    }
1126 #endif
1127 		default:
1128 			if (message)
1129 				warnx("Error retrieving file `%s'", message);
1130 			else
1131 				warnx("Unknown error retrieving file");
1132 			goto cleanup_fetch_url;
1133 		}
1134 	}		/* end of ftp:// or http:// specific setup */
1135 
1136 			/* Open the output file. */
1137 	if (strcmp(savefile, "-") == 0) {
1138 		fout = stdout;
1139 	} else if (*savefile == '|') {
1140 		oldintp = xsignal(SIGPIPE, SIG_IGN);
1141 		fout = popen(savefile + 1, "w");
1142 		if (fout == NULL) {
1143 			warn("Can't execute `%s'", savefile + 1);
1144 			goto cleanup_fetch_url;
1145 		}
1146 		closefunc = pclose;
1147 	} else {
1148 		if ((rangeend != -1 && rangeend <= restart_point) ||
1149 		    (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
1150 			/* already done */
1151 			if (verbose)
1152 				fprintf(ttyout, "already done\n");
1153 			rval = 0;
1154 			goto cleanup_fetch_url;
1155 		}
1156 		if (restart_point && rangestart != -1) {
1157 			if (entitylen != -1)
1158 				filesize = entitylen;
1159 			if (rangestart != restart_point) {
1160 				warnx(
1161 				    "Size of `%s' differs from save file `%s'",
1162 				    url, savefile);
1163 				goto cleanup_fetch_url;
1164 			}
1165 			fout = fopen(savefile, "a");
1166 		} else
1167 			fout = fopen(savefile, "w");
1168 		if (fout == NULL) {
1169 			warn("Can't open `%s'", savefile);
1170 			goto cleanup_fetch_url;
1171 		}
1172 		closefunc = fclose;
1173 	}
1174 
1175 			/* Trap signals */
1176 	if (sigsetjmp(httpabort, 1))
1177 		goto cleanup_fetch_url;
1178 	(void)xsignal(SIGQUIT, psummary);
1179 	oldintr = xsignal(SIGINT, aborthttp);
1180 
1181 	assert(rcvbuf_size > 0);
1182 	if ((size_t)rcvbuf_size > bufsize) {
1183 		if (xferbuf)
1184 			(void)free(xferbuf);
1185 		bufsize = rcvbuf_size;
1186 		xferbuf = ftp_malloc(bufsize);
1187 	}
1188 
1189 	bytes = 0;
1190 	hashbytes = mark;
1191 	progressmeter(-1);
1192 
1193 			/* Finally, suck down the file. */
1194 	do {
1195 		long chunksize;
1196 		short lastchunk;
1197 
1198 		chunksize = 0;
1199 		lastchunk = 0;
1200 					/* read chunk-size */
1201 		if (ischunked) {
1202 			if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
1203 				warnx("Unexpected EOF reading chunk-size");
1204 				goto cleanup_fetch_url;
1205 			}
1206 			errno = 0;
1207 			chunksize = strtol(xferbuf, &ep, 16);
1208 			if (ep == xferbuf) {
1209 				warnx("Invalid chunk-size");
1210 				goto cleanup_fetch_url;
1211 			}
1212 			if (errno == ERANGE || chunksize < 0) {
1213 				errno = ERANGE;
1214 				warn("Chunk-size `%.*s'",
1215 				    (int)(ep-xferbuf), xferbuf);
1216 				goto cleanup_fetch_url;
1217 			}
1218 
1219 				/*
1220 				 * XXX:	Work around bug in Apache 1.3.9 and
1221 				 *	1.3.11, which incorrectly put trailing
1222 				 *	space after the chunk-size.
1223 				 */
1224 			while (*ep == ' ')
1225 				ep++;
1226 
1227 					/* skip [ chunk-ext ] */
1228 			if (*ep == ';') {
1229 				while (*ep && *ep != '\r')
1230 					ep++;
1231 			}
1232 
1233 			if (strcmp(ep, "\r\n") != 0) {
1234 				warnx("Unexpected data following chunk-size");
1235 				goto cleanup_fetch_url;
1236 			}
1237 			DPRINTF("fetch_url: got chunk-size of " LLF "\n",
1238 			    (LLT)chunksize);
1239 			if (chunksize == 0) {
1240 				lastchunk = 1;
1241 				goto chunkdone;
1242 			}
1243 		}
1244 					/* transfer file or chunk */
1245 		while (1) {
1246 			struct timeval then, now, td;
1247 			off_t bufrem;
1248 
1249 			if (rate_get)
1250 				(void)gettimeofday(&then, NULL);
1251 			bufrem = rate_get ? rate_get : (off_t)bufsize;
1252 			if (ischunked)
1253 				bufrem = MIN(chunksize, bufrem);
1254 			while (bufrem > 0) {
1255 				flen = fetch_read(xferbuf, sizeof(char),
1256 				    MIN((off_t)bufsize, bufrem), fin);
1257 				if (flen <= 0)
1258 					goto chunkdone;
1259 				bytes += flen;
1260 				bufrem -= flen;
1261 				if (fwrite(xferbuf, sizeof(char), flen, fout)
1262 				    != flen) {
1263 					warn("Writing `%s'", savefile);
1264 					goto cleanup_fetch_url;
1265 				}
1266 				if (hash && !progress) {
1267 					while (bytes >= hashbytes) {
1268 						(void)putc('#', ttyout);
1269 						hashbytes += mark;
1270 					}
1271 					(void)fflush(ttyout);
1272 				}
1273 				if (ischunked) {
1274 					chunksize -= flen;
1275 					if (chunksize <= 0)
1276 						break;
1277 				}
1278 			}
1279 			if (rate_get) {
1280 				while (1) {
1281 					(void)gettimeofday(&now, NULL);
1282 					timersub(&now, &then, &td);
1283 					if (td.tv_sec > 0)
1284 						break;
1285 					usleep(1000000 - td.tv_usec);
1286 				}
1287 			}
1288 			if (ischunked && chunksize <= 0)
1289 				break;
1290 		}
1291 					/* read CRLF after chunk*/
1292  chunkdone:
1293 		if (ischunked) {
1294 			if (fetch_getln(xferbuf, bufsize, fin) == NULL) {
1295 				warnx("Unexpected EOF reading chunk CRLF");
1296 				goto cleanup_fetch_url;
1297 			}
1298 			if (strcmp(xferbuf, "\r\n") != 0) {
1299 				warnx("Unexpected data following chunk");
1300 				goto cleanup_fetch_url;
1301 			}
1302 			if (lastchunk)
1303 				break;
1304 		}
1305 	} while (ischunked);
1306 
1307 /* XXX: deal with optional trailer & CRLF here? */
1308 
1309 	if (hash && !progress && bytes > 0) {
1310 		if (bytes < mark)
1311 			(void)putc('#', ttyout);
1312 		(void)putc('\n', ttyout);
1313 	}
1314 	if (fetch_error(fin)) {
1315 		warn("Reading file");
1316 		goto cleanup_fetch_url;
1317 	}
1318 	progressmeter(1);
1319 	(void)fflush(fout);
1320 	if (closefunc == fclose && mtime != -1) {
1321 		struct timeval tval[2];
1322 
1323 		(void)gettimeofday(&tval[0], NULL);
1324 		tval[1].tv_sec = mtime;
1325 		tval[1].tv_usec = 0;
1326 		(*closefunc)(fout);
1327 		fout = NULL;
1328 
1329 		if (utimes(savefile, tval) == -1) {
1330 			fprintf(ttyout,
1331 			    "Can't change modification time to %s",
1332 			    rfc2822time(localtime(&mtime)));
1333 		}
1334 	}
1335 	if (bytes > 0)
1336 		ptransfer(0);
1337 	bytes = 0;
1338 
1339 	rval = 0;
1340 	goto cleanup_fetch_url;
1341 
1342  improper:
1343 	warnx("Improper response from `%s:%s'", host, port);
1344 
1345  cleanup_fetch_url:
1346 	if (oldintr)
1347 		(void)xsignal(SIGINT, oldintr);
1348 	if (oldintp)
1349 		(void)xsignal(SIGPIPE, oldintp);
1350 	if (fin != NULL)
1351 		fetch_close(fin);
1352 	else if (s != -1)
1353 		close(s);
1354 	if (closefunc != NULL && fout != NULL)
1355 		(*closefunc)(fout);
1356 	if (res0)
1357 		freeaddrinfo(res0);
1358 	FREEPTR(savefile);
1359 	FREEPTR(uuser);
1360 	if (pass != NULL)
1361 		memset(pass, 0, strlen(pass));
1362 	FREEPTR(pass);
1363 	FREEPTR(host);
1364 	FREEPTR(port);
1365 	FREEPTR(path);
1366 	FREEPTR(decodedpath);
1367 	FREEPTR(puser);
1368 	if (ppass != NULL)
1369 		memset(ppass, 0, strlen(ppass));
1370 	FREEPTR(ppass);
1371 	FREEPTR(auth);
1372 	FREEPTR(location);
1373 	FREEPTR(message);
1374 	return (rval);
1375 }
1376 
1377 /*
1378  * Abort a HTTP retrieval
1379  */
1380 static void
1381 aborthttp(int notused)
1382 {
1383 	char msgbuf[100];
1384 	size_t len;
1385 
1386 	sigint_raised = 1;
1387 	alarmtimer(0);
1388 	len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
1389 	write(fileno(ttyout), msgbuf, len);
1390 	siglongjmp(httpabort, 1);
1391 }
1392 
1393 /*
1394  * Retrieve ftp URL or classic ftp argument using FTP.
1395  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1396  * is still open (e.g, ftp xfer with trailing /)
1397  */
1398 static int
1399 fetch_ftp(const char *url)
1400 {
1401 	char		*cp, *xargv[5], rempath[MAXPATHLEN];
1402 	char		*host, *path, *dir, *file, *uuser, *pass;
1403 	char		*port;
1404 	char		 cmdbuf[MAXPATHLEN];
1405 	char		 dirbuf[4];
1406 	int		 dirhasglob, filehasglob, rval, transtype, xargc;
1407 	int		 oanonftp, oautologin;
1408 	in_port_t	 portnum;
1409 	url_t		 urltype;
1410 
1411 	DPRINTF("fetch_ftp: `%s'\n", url);
1412 	host = path = dir = file = uuser = pass = NULL;
1413 	port = NULL;
1414 	rval = 1;
1415 	transtype = TYPE_I;
1416 
1417 	if (STRNEQUAL(url, FTP_URL)) {
1418 		if ((parse_url(url, "URL", &urltype, &uuser, &pass,
1419 		    &host, &port, &portnum, &path) == -1) ||
1420 		    (uuser != NULL && *uuser == '\0') ||
1421 		    EMPTYSTRING(host)) {
1422 			warnx("Invalid URL `%s'", url);
1423 			goto cleanup_fetch_ftp;
1424 		}
1425 		/*
1426 		 * Note: Don't url_decode(path) here.  We need to keep the
1427 		 * distinction between "/" and "%2F" until later.
1428 		 */
1429 
1430 					/* check for trailing ';type=[aid]' */
1431 		if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1432 			if (strcasecmp(cp, ";type=a") == 0)
1433 				transtype = TYPE_A;
1434 			else if (strcasecmp(cp, ";type=i") == 0)
1435 				transtype = TYPE_I;
1436 			else if (strcasecmp(cp, ";type=d") == 0) {
1437 				warnx(
1438 			    "Directory listing via a URL is not supported");
1439 				goto cleanup_fetch_ftp;
1440 			} else {
1441 				warnx("Invalid suffix `%s' in URL `%s'", cp,
1442 				    url);
1443 				goto cleanup_fetch_ftp;
1444 			}
1445 			*cp = 0;
1446 		}
1447 	} else {			/* classic style `[user@]host:[file]' */
1448 		urltype = CLASSIC_URL_T;
1449 		host = ftp_strdup(url);
1450 		cp = strchr(host, '@');
1451 		if (cp != NULL) {
1452 			*cp = '\0';
1453 			uuser = host;
1454 			anonftp = 0;	/* disable anonftp */
1455 			host = ftp_strdup(cp + 1);
1456 		}
1457 		cp = strchr(host, ':');
1458 		if (cp != NULL) {
1459 			*cp = '\0';
1460 			path = ftp_strdup(cp + 1);
1461 		}
1462 	}
1463 	if (EMPTYSTRING(host))
1464 		goto cleanup_fetch_ftp;
1465 
1466 			/* Extract the file and (if present) directory name. */
1467 	dir = path;
1468 	if (! EMPTYSTRING(dir)) {
1469 		/*
1470 		 * If we are dealing with classic `[user@]host:[path]' syntax,
1471 		 * then a path of the form `/file' (resulting from input of the
1472 		 * form `host:/file') means that we should do "CWD /" before
1473 		 * retrieving the file.  So we set dir="/" and file="file".
1474 		 *
1475 		 * But if we are dealing with URLs like `ftp://host/path' then
1476 		 * a path of the form `/file' (resulting from a URL of the form
1477 		 * `ftp://host//file') means that we should do `CWD ' (with an
1478 		 * empty argument) before retrieving the file.  So we set
1479 		 * dir="" and file="file".
1480 		 *
1481 		 * If the path does not contain / at all, we set dir=NULL.
1482 		 * (We get a path without any slashes if we are dealing with
1483 		 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1484 		 *
1485 		 * In all other cases, we set dir to a string that does not
1486 		 * include the final '/' that separates the dir part from the
1487 		 * file part of the path.  (This will be the empty string if
1488 		 * and only if we are dealing with a path of the form `/file'
1489 		 * resulting from an URL of the form `ftp://host//file'.)
1490 		 */
1491 		cp = strrchr(dir, '/');
1492 		if (cp == dir && urltype == CLASSIC_URL_T) {
1493 			file = cp + 1;
1494 			(void)strlcpy(dirbuf, "/", sizeof(dirbuf));
1495 			dir = dirbuf;
1496 		} else if (cp != NULL) {
1497 			*cp++ = '\0';
1498 			file = cp;
1499 		} else {
1500 			file = dir;
1501 			dir = NULL;
1502 		}
1503 	} else
1504 		dir = NULL;
1505 	if (urltype == FTP_URL_T && file != NULL) {
1506 		url_decode(file);
1507 		/* but still don't url_decode(dir) */
1508 	}
1509 	DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
1510 	    "path `%s' dir `%s' file `%s'\n",
1511 	    STRorNULL(uuser), STRorNULL(pass),
1512 	    STRorNULL(host), STRorNULL(port),
1513 	    STRorNULL(path), STRorNULL(dir), STRorNULL(file));
1514 
1515 	dirhasglob = filehasglob = 0;
1516 	if (doglob && urltype == CLASSIC_URL_T) {
1517 		if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
1518 			dirhasglob = 1;
1519 		if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
1520 			filehasglob = 1;
1521 	}
1522 
1523 			/* Set up the connection */
1524 	oanonftp = anonftp;
1525 	if (connected)
1526 		disconnect(0, NULL);
1527 	anonftp = oanonftp;
1528 	(void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
1529 	xargv[0] = cmdbuf;
1530 	xargv[1] = host;
1531 	xargv[2] = NULL;
1532 	xargc = 2;
1533 	if (port) {
1534 		xargv[2] = port;
1535 		xargv[3] = NULL;
1536 		xargc = 3;
1537 	}
1538 	oautologin = autologin;
1539 		/* don't autologin in setpeer(), use ftp_login() below */
1540 	autologin = 0;
1541 	setpeer(xargc, xargv);
1542 	autologin = oautologin;
1543 	if ((connected == 0) ||
1544 	    (connected == 1 && !ftp_login(host, uuser, pass))) {
1545 		warnx("Can't connect or login to host `%s:%s'",
1546 			host, port ? port : "?");
1547 		goto cleanup_fetch_ftp;
1548 	}
1549 
1550 	switch (transtype) {
1551 	case TYPE_A:
1552 		setascii(1, xargv);
1553 		break;
1554 	case TYPE_I:
1555 		setbinary(1, xargv);
1556 		break;
1557 	default:
1558 		errx(1, "fetch_ftp: unknown transfer type %d", transtype);
1559 	}
1560 
1561 		/*
1562 		 * Change directories, if necessary.
1563 		 *
1564 		 * Note: don't use EMPTYSTRING(dir) below, because
1565 		 * dir=="" means something different from dir==NULL.
1566 		 */
1567 	if (dir != NULL && !dirhasglob) {
1568 		char *nextpart;
1569 
1570 		/*
1571 		 * If we are dealing with a classic `[user@]host:[path]'
1572 		 * (urltype is CLASSIC_URL_T) then we have a raw directory
1573 		 * name (not encoded in any way) and we can change
1574 		 * directories in one step.
1575 		 *
1576 		 * If we are dealing with an `ftp://host/path' URL
1577 		 * (urltype is FTP_URL_T), then RFC 3986 says we need to
1578 		 * send a separate CWD command for each unescaped "/"
1579 		 * in the path, and we have to interpret %hex escaping
1580 		 * *after* we find the slashes.  It's possible to get
1581 		 * empty components here, (from multiple adjacent
1582 		 * slashes in the path) and RFC 3986 says that we should
1583 		 * still do `CWD ' (with a null argument) in such cases.
1584 		 *
1585 		 * Many ftp servers don't support `CWD ', so if there's an
1586 		 * error performing that command, bail out with a descriptive
1587 		 * message.
1588 		 *
1589 		 * Examples:
1590 		 *
1591 		 * host:			dir="", urltype=CLASSIC_URL_T
1592 		 *		logged in (to default directory)
1593 		 * host:file			dir=NULL, urltype=CLASSIC_URL_T
1594 		 *		"RETR file"
1595 		 * host:dir/			dir="dir", urltype=CLASSIC_URL_T
1596 		 *		"CWD dir", logged in
1597 		 * ftp://host/			dir="", urltype=FTP_URL_T
1598 		 *		logged in (to default directory)
1599 		 * ftp://host/dir/		dir="dir", urltype=FTP_URL_T
1600 		 *		"CWD dir", logged in
1601 		 * ftp://host/file		dir=NULL, urltype=FTP_URL_T
1602 		 *		"RETR file"
1603 		 * ftp://host//file		dir="", urltype=FTP_URL_T
1604 		 *		"CWD ", "RETR file"
1605 		 * host:/file			dir="/", urltype=CLASSIC_URL_T
1606 		 *		"CWD /", "RETR file"
1607 		 * ftp://host///file		dir="/", urltype=FTP_URL_T
1608 		 *		"CWD ", "CWD ", "RETR file"
1609 		 * ftp://host/%2F/file		dir="%2F", urltype=FTP_URL_T
1610 		 *		"CWD /", "RETR file"
1611 		 * ftp://host/foo/file		dir="foo", urltype=FTP_URL_T
1612 		 *		"CWD foo", "RETR file"
1613 		 * ftp://host/foo/bar/file	dir="foo/bar"
1614 		 *		"CWD foo", "CWD bar", "RETR file"
1615 		 * ftp://host//foo/bar/file	dir="/foo/bar"
1616 		 *		"CWD ", "CWD foo", "CWD bar", "RETR file"
1617 		 * ftp://host/foo//bar/file	dir="foo//bar"
1618 		 *		"CWD foo", "CWD ", "CWD bar", "RETR file"
1619 		 * ftp://host/%2F/foo/bar/file	dir="%2F/foo/bar"
1620 		 *		"CWD /", "CWD foo", "CWD bar", "RETR file"
1621 		 * ftp://host/%2Ffoo/bar/file	dir="%2Ffoo/bar"
1622 		 *		"CWD /foo", "CWD bar", "RETR file"
1623 		 * ftp://host/%2Ffoo%2Fbar/file	dir="%2Ffoo%2Fbar"
1624 		 *		"CWD /foo/bar", "RETR file"
1625 		 * ftp://host/%2Ffoo%2Fbar%2Ffile	dir=NULL
1626 		 *		"RETR /foo/bar/file"
1627 		 *
1628 		 * Note that we don't need `dir' after this point.
1629 		 */
1630 		do {
1631 			if (urltype == FTP_URL_T) {
1632 				nextpart = strchr(dir, '/');
1633 				if (nextpart) {
1634 					*nextpart = '\0';
1635 					nextpart++;
1636 				}
1637 				url_decode(dir);
1638 			} else
1639 				nextpart = NULL;
1640 			DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
1641 			    STRorNULL(dir), STRorNULL(nextpart));
1642 			if (urltype == FTP_URL_T || *dir != '\0') {
1643 				(void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
1644 				xargv[0] = cmdbuf;
1645 				xargv[1] = dir;
1646 				xargv[2] = NULL;
1647 				dirchange = 0;
1648 				cd(2, xargv);
1649 				if (! dirchange) {
1650 					if (*dir == '\0' && code == 500)
1651 						fprintf(stderr,
1652 "\n"
1653 "ftp: The `CWD ' command (without a directory), which is required by\n"
1654 "     RFC 3986 to support the empty directory in the URL pathname (`//'),\n"
1655 "     conflicts with the server's conformance to RFC 959.\n"
1656 "     Try the same URL without the `//' in the URL pathname.\n"
1657 "\n");
1658 					goto cleanup_fetch_ftp;
1659 				}
1660 			}
1661 			dir = nextpart;
1662 		} while (dir != NULL);
1663 	}
1664 
1665 	if (EMPTYSTRING(file)) {
1666 		rval = -1;
1667 		goto cleanup_fetch_ftp;
1668 	}
1669 
1670 	if (dirhasglob) {
1671 		(void)strlcpy(rempath, dir,	sizeof(rempath));
1672 		(void)strlcat(rempath, "/",	sizeof(rempath));
1673 		(void)strlcat(rempath, file,	sizeof(rempath));
1674 		file = rempath;
1675 	}
1676 
1677 			/* Fetch the file(s). */
1678 	xargc = 2;
1679 	(void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
1680 	xargv[0] = cmdbuf;
1681 	xargv[1] = file;
1682 	xargv[2] = NULL;
1683 	if (dirhasglob || filehasglob) {
1684 		int ointeractive;
1685 
1686 		ointeractive = interactive;
1687 		interactive = 0;
1688 		if (restartautofetch)
1689 			(void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf));
1690 		else
1691 			(void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf));
1692 		xargv[0] = cmdbuf;
1693 		mget(xargc, xargv);
1694 		interactive = ointeractive;
1695 	} else {
1696 		if (outfile == NULL) {
1697 			cp = strrchr(file, '/');	/* find savefile */
1698 			if (cp != NULL)
1699 				outfile = cp + 1;
1700 			else
1701 				outfile = file;
1702 		}
1703 		xargv[2] = (char *)outfile;
1704 		xargv[3] = NULL;
1705 		xargc++;
1706 		if (restartautofetch)
1707 			reget(xargc, xargv);
1708 		else
1709 			get(xargc, xargv);
1710 	}
1711 
1712 	if ((code / 100) == COMPLETE)
1713 		rval = 0;
1714 
1715  cleanup_fetch_ftp:
1716 	FREEPTR(port);
1717 	FREEPTR(host);
1718 	FREEPTR(path);
1719 	FREEPTR(uuser);
1720 	if (pass)
1721 		memset(pass, 0, strlen(pass));
1722 	FREEPTR(pass);
1723 	return (rval);
1724 }
1725 
1726 /*
1727  * Retrieve the given file to outfile.
1728  * Supports arguments of the form:
1729  *	"host:path", "ftp://host/path"	if $ftpproxy, call fetch_url() else
1730  *					call fetch_ftp()
1731  *	"http://host/path"		call fetch_url() to use HTTP
1732  *	"file:///path"			call fetch_url() to copy
1733  *	"about:..."			print a message
1734  *
1735  * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1736  * is still open (e.g, ftp xfer with trailing /)
1737  */
1738 static int
1739 go_fetch(const char *url)
1740 {
1741 	char *proxyenv;
1742 	char *p;
1743 
1744 #ifndef NO_ABOUT
1745 	/*
1746 	 * Check for about:*
1747 	 */
1748 	if (STRNEQUAL(url, ABOUT_URL)) {
1749 		url += sizeof(ABOUT_URL) -1;
1750 		if (strcasecmp(url, "ftp") == 0 ||
1751 		    strcasecmp(url, "tnftp") == 0) {
1752 			fputs(
1753 "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
1754 "for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
1755 		} else if (strcasecmp(url, "lukem") == 0) {
1756 			fputs(
1757 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1758 "Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
1759 		} else if (strcasecmp(url, "netbsd") == 0) {
1760 			fputs(
1761 "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1762 "For more information, see http://www.NetBSD.org/\n", ttyout);
1763 		} else if (strcasecmp(url, "version") == 0) {
1764 			fprintf(ttyout, "Version: %s %s%s\n",
1765 			    FTP_PRODUCT, FTP_VERSION,
1766 #ifdef INET6
1767 			    ""
1768 #else
1769 			    " (-IPv6)"
1770 #endif
1771 			);
1772 		} else {
1773 			fprintf(ttyout, "`%s' is an interesting topic.\n", url);
1774 		}
1775 		fputs("\n", ttyout);
1776 		return (0);
1777 	}
1778 #endif
1779 
1780 	/*
1781 	 * Check for file:// and http:// URLs.
1782 	 */
1783 	if (STRNEQUAL(url, HTTP_URL)
1784 #ifdef WITH_SSL
1785 	    || STRNEQUAL(url, HTTPS_URL)
1786 #endif
1787 	    || STRNEQUAL(url, FILE_URL))
1788 		return (fetch_url(url, NULL, NULL, NULL));
1789 
1790 	/*
1791 	 * If it contains "://" but does not begin with ftp://
1792 	 * or something that was already handled, then it's
1793 	 * unsupported.
1794 	 *
1795 	 * If it contains ":" but not "://" then we assume the
1796 	 * part before the colon is a host name, not an URL scheme,
1797 	 * so we don't try to match that here.
1798 	 */
1799 	if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL))
1800 		errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url);
1801 
1802 	/*
1803 	 * Try FTP URL-style and host:file arguments next.
1804 	 * If ftpproxy is set with an FTP URL, use fetch_url()
1805 	 * Othewise, use fetch_ftp().
1806 	 */
1807 	proxyenv = getoptionvalue("ftp_proxy");
1808 	if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
1809 		return (fetch_url(url, NULL, NULL, NULL));
1810 
1811 	return (fetch_ftp(url));
1812 }
1813 
1814 /*
1815  * Retrieve multiple files from the command line,
1816  * calling go_fetch() for each file.
1817  *
1818  * If an ftp path has a trailing "/", the path will be cd-ed into and
1819  * the connection remains open, and the function will return -1
1820  * (to indicate the connection is alive).
1821  * If an error occurs the return value will be the offset+1 in
1822  * argv[] of the file that caused a problem (i.e, argv[x]
1823  * returns x+1)
1824  * Otherwise, 0 is returned if all files retrieved successfully.
1825  */
1826 int
1827 auto_fetch(int argc, char *argv[])
1828 {
1829 	volatile int	argpos, rval;
1830 
1831 	argpos = rval = 0;
1832 
1833 	if (sigsetjmp(toplevel, 1)) {
1834 		if (connected)
1835 			disconnect(0, NULL);
1836 		if (rval > 0)
1837 			rval = argpos + 1;
1838 		return (rval);
1839 	}
1840 	(void)xsignal(SIGINT, intr);
1841 	(void)xsignal(SIGPIPE, lostpeer);
1842 
1843 	/*
1844 	 * Loop through as long as there's files to fetch.
1845 	 */
1846 	for (; (rval == 0) && (argpos < argc); argpos++) {
1847 		if (strchr(argv[argpos], ':') == NULL)
1848 			break;
1849 		redirect_loop = 0;
1850 		if (!anonftp)
1851 			anonftp = 2;	/* Handle "automatic" transfers. */
1852 		rval = go_fetch(argv[argpos]);
1853 		if (outfile != NULL && strcmp(outfile, "-") != 0
1854 		    && outfile[0] != '|')
1855 			outfile = NULL;
1856 		if (rval > 0)
1857 			rval = argpos + 1;
1858 	}
1859 
1860 	if (connected && rval != -1)
1861 		disconnect(0, NULL);
1862 	return (rval);
1863 }
1864 
1865 
1866 /*
1867  * Upload multiple files from the command line.
1868  *
1869  * If an error occurs the return value will be the offset+1 in
1870  * argv[] of the file that caused a problem (i.e, argv[x]
1871  * returns x+1)
1872  * Otherwise, 0 is returned if all files uploaded successfully.
1873  */
1874 int
1875 auto_put(int argc, char **argv, const char *uploadserver)
1876 {
1877 	char	*uargv[4], *path, *pathsep;
1878 	int	 uargc, rval, argpos;
1879 	size_t	 len;
1880 	char	 cmdbuf[MAX_C_NAME];
1881 
1882 	(void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf));
1883 	uargv[0] = cmdbuf;
1884 	uargv[1] = argv[0];
1885 	uargc = 2;
1886 	uargv[2] = uargv[3] = NULL;
1887 	pathsep = NULL;
1888 	rval = 1;
1889 
1890 	DPRINTF("auto_put: target `%s'\n", uploadserver);
1891 
1892 	path = ftp_strdup(uploadserver);
1893 	len = strlen(path);
1894 	if (path[len - 1] != '/' && path[len - 1] != ':') {
1895 			/*
1896 			 * make sure we always pass a directory to auto_fetch
1897 			 */
1898 		if (argc > 1) {		/* more than one file to upload */
1899 			len = strlen(uploadserver) + 2;	/* path + "/" + "\0" */
1900 			free(path);
1901 			path = (char *)ftp_malloc(len);
1902 			(void)strlcpy(path, uploadserver, len);
1903 			(void)strlcat(path, "/", len);
1904 		} else {		/* single file to upload */
1905 			(void)strlcpy(cmdbuf, "put", sizeof(cmdbuf));
1906 			uargv[0] = cmdbuf;
1907 			pathsep = strrchr(path, '/');
1908 			if (pathsep == NULL) {
1909 				pathsep = strrchr(path, ':');
1910 				if (pathsep == NULL) {
1911 					warnx("Invalid URL `%s'", path);
1912 					goto cleanup_auto_put;
1913 				}
1914 				pathsep++;
1915 				uargv[2] = ftp_strdup(pathsep);
1916 				pathsep[0] = '/';
1917 			} else
1918 				uargv[2] = ftp_strdup(pathsep + 1);
1919 			pathsep[1] = '\0';
1920 			uargc++;
1921 		}
1922 	}
1923 	DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
1924 	    path, STRorNULL(uargv[2]));
1925 
1926 			/* connect and cwd */
1927 	rval = auto_fetch(1, &path);
1928 	if(rval >= 0)
1929 		goto cleanup_auto_put;
1930 
1931 	rval = 0;
1932 
1933 			/* target filename provided; upload 1 file */
1934 			/* XXX : is this the best way? */
1935 	if (uargc == 3) {
1936 		uargv[1] = argv[0];
1937 		put(uargc, uargv);
1938 		if ((code / 100) != COMPLETE)
1939 			rval = 1;
1940 	} else {	/* otherwise a target dir: upload all files to it */
1941 		for(argpos = 0; argv[argpos] != NULL; argpos++) {
1942 			uargv[1] = argv[argpos];
1943 			mput(uargc, uargv);
1944 			if ((code / 100) != COMPLETE) {
1945 				rval = argpos + 1;
1946 				break;
1947 			}
1948 		}
1949 	}
1950 
1951  cleanup_auto_put:
1952 	free(path);
1953 	FREEPTR(uargv[2]);
1954 	return (rval);
1955 }
1956