xref: /netbsd-src/usr.bin/ftp/fetch.c (revision 2a399c6883d870daece976daec6ffa7bb7f934ce)
1 /*	$NetBSD: fetch.c,v 1.17 1997/11/01 14:36:55 lukem Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason Thorpe and Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __RCSID("$NetBSD: fetch.c,v 1.17 1997/11/01 14:36:55 lukem Exp $");
42 #endif /* not lint */
43 
44 /*
45  * FTP User Program -- Command line file retrieval
46  */
47 
48 #include <sys/types.h>
49 #include <sys/param.h>
50 #include <sys/socket.h>
51 
52 #include <netinet/in.h>
53 
54 #include <arpa/ftp.h>
55 #include <arpa/inet.h>
56 
57 #include <ctype.h>
58 #include <err.h>
59 #include <netdb.h>
60 #include <fcntl.h>
61 #include <signal.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <unistd.h>
66 
67 #include "ftp_var.h"
68 
69 static int	url_get __P((const char *, const char *));
70 void    	aborthttp __P((int));
71 
72 
73 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
74 #define	HTTP_URL	"http://"	/* http URL prefix */
75 #define FTP_PROXY	"ftp_proxy"	/* env var with ftp proxy location */
76 #define HTTP_PROXY	"http_proxy"	/* env var with http proxy location */
77 
78 
79 #define EMPTYSTRING(x)	((x) == NULL || (*(x) == '\0'))
80 
81 jmp_buf	httpabort;
82 
83 /*
84  * Retrieve URL, via the proxy in $proxyvar if necessary.
85  * Modifies the string argument given.
86  * Returns -1 on failure, 0 on success
87  */
88 static int
89 url_get(origline, proxyenv)
90 	const char *origline;
91 	const char *proxyenv;
92 {
93 	struct sockaddr_in sin;
94 	int i, out, isftpurl;
95 	u_int16_t port;
96 	volatile int s;
97 	size_t len;
98 	char c, *cp, *ep, *portnum, *path, buf[4096];
99 	const char *savefile;
100 	char *line, *proxy, *host;
101 	volatile sig_t oldintr;
102 	off_t hashbytes;
103 
104 	s = -1;
105 	proxy = NULL;
106 	isftpurl = 0;
107 
108 #ifdef __GNUC__			/* XXX: to shut up gcc warnings */
109 	(void)&savefile;
110 	(void)&proxy;
111 #endif
112 
113 	line = strdup(origline);
114 	if (line == NULL)
115 		errx(1, "Can't allocate memory to parse URL");
116 	if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
117 		host = line + sizeof(HTTP_URL) - 1;
118 	else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
119 		host = line + sizeof(FTP_URL) - 1;
120 		isftpurl = 1;
121 	} else
122 		errx(1, "url_get: Invalid URL '%s'", line);
123 
124 	path = strchr(host, '/');		/* find path */
125 	if (EMPTYSTRING(path)) {
126 		if (isftpurl)
127 			goto noftpautologin;
128 		warnx("Invalid URL (no `/' after host): %s", origline);
129 		goto cleanup_url_get;
130 	}
131 	*path++ = '\0';
132 	if (EMPTYSTRING(path)) {
133 		if (isftpurl)
134 			goto noftpautologin;
135 		warnx("Invalid URL (no file after host): %s", origline);
136 		goto cleanup_url_get;
137 	}
138 
139 	savefile = strrchr(path, '/');			/* find savefile */
140 	if (savefile != NULL)
141 		savefile++;
142 	else
143 		savefile = path;
144 	if (EMPTYSTRING(savefile)) {
145 		if (isftpurl)
146 			goto noftpautologin;
147 		warnx("Invalid URL (no file after directory): %s", origline);
148 		goto cleanup_url_get;
149 	}
150 
151 	if (proxyenv != NULL) {				/* use proxy */
152 		proxy = strdup(proxyenv);
153 		if (proxy == NULL)
154 			errx(1, "Can't allocate memory for proxy URL.");
155 		if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
156 			host = proxy + sizeof(HTTP_URL) - 1;
157 		else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
158 			host = proxy + sizeof(FTP_URL) - 1;
159 		else {
160 			warnx("Malformed proxy URL: %s", proxyenv);
161 			goto cleanup_url_get;
162 		}
163 		if (EMPTYSTRING(host)) {
164 			warnx("Malformed proxy URL: %s", proxyenv);
165 			goto cleanup_url_get;
166 		}
167 		*--path = '/';			/* add / back to real path */
168 		path = strchr(host, '/');	/* remove trailing / on host */
169 		if (! EMPTYSTRING(path))
170 			*path++ = '\0';
171 		path = line;
172 	}
173 
174 	portnum = strchr(host, ':');			/* find portnum */
175 	if (portnum != NULL)
176 		*portnum++ = '\0';
177 
178 	if (debug)
179 		printf("host %s, port %s, path %s, save as %s.\n",
180 		    host, portnum, path, savefile);
181 
182 	memset(&sin, 0, sizeof(sin));
183 	sin.sin_family = AF_INET;
184 
185 	if (isdigit(host[0])) {
186 		if (inet_aton(host, &sin.sin_addr) == 0) {
187 			warnx("Invalid IP address: %s", host);
188 			goto cleanup_url_get;
189 		}
190 	} else {
191 		struct hostent *hp;
192 
193 		hp = gethostbyname(host);
194 		if (hp == NULL) {
195 			warnx("%s: %s", host, hstrerror(h_errno));
196 			goto cleanup_url_get;
197 		}
198 		if (hp->h_addrtype != AF_INET) {
199 			warnx("%s: not an Internet address?", host);
200 			goto cleanup_url_get;
201 		}
202 		memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
203 	}
204 
205 	if (! EMPTYSTRING(portnum)) {
206 		char *ep;
207 		long nport;
208 
209 		nport = strtol(portnum, &ep, 10);
210 		if (nport < 1 || nport > 0xffff || *ep != '\0') {
211 			warnx("Invalid port: %s", portnum);
212 			goto cleanup_url_get;
213 		}
214 		port = htons(nport);
215 	} else
216 		port = httpport;
217 	sin.sin_port = port;
218 
219 	s = socket(AF_INET, SOCK_STREAM, 0);
220 	if (s == -1) {
221 		warn("Can't create socket");
222 		goto cleanup_url_get;
223 	}
224 
225 	if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
226 		warn("Can't connect to %s", host);
227 		goto cleanup_url_get;
228 	}
229 
230 	/*
231 	 * Construct and send the request.  We're expecting a return
232 	 * status of "200". Proxy requests don't want leading /.
233 	 */
234 	if (!proxy)
235 		printf("Requesting %s\n", origline);
236 	else
237 		printf("Requesting %s (via %s)\n", origline, proxyenv);
238 	len = snprintf(buf, sizeof(buf), "GET %s%s HTTP/1.0\r\n\r\n",
239 	    proxy ? "" : "/", path);
240 	if (write(s, buf, len) < len) {
241 		warn("Writing HTTP request");
242 		goto cleanup_url_get;
243 	}
244 	memset(buf, 0, sizeof(buf));
245 	for (cp = buf; cp < buf + sizeof(buf); ) {
246 		if (read(s, cp, 1) != 1)
247 			goto improper;
248 		if (*cp == '\r')
249 			continue;
250 		if (*cp == '\n')
251 			break;
252 		cp++;
253 	}
254 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
255 	cp = strchr(buf, ' ');
256 	if (cp == NULL)
257 		goto improper;
258 	else
259 		cp++;
260 	if (strncmp(cp, "200", 3)) {
261 		warnx("Error retrieving file: %s", cp);
262 		goto cleanup_url_get;
263 	}
264 
265 	/*
266 	 * Read the rest of the header.
267 	 */
268 	memset(buf, 0, sizeof(buf));
269 	c = '\0';
270 	for (cp = buf; cp < buf + sizeof(buf); ) {
271 		if (read(s, cp, 1) != 1)
272 			goto improper;
273 		if (*cp == '\r')
274 			continue;
275 		if (*cp == '\n' && c == '\n')
276 			break;
277 		c = *cp;
278 		cp++;
279 	}
280 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
281 
282 	/*
283 	 * Look for the "Content-length: " header.
284 	 */
285 #define CONTENTLEN "Content-Length: "
286 	for (cp = buf; *cp != '\0'; cp++) {
287 		if (tolower(*cp) == 'c' &&
288 		    strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
289 			break;
290 	}
291 	if (*cp != '\0') {
292 		cp += sizeof(CONTENTLEN) - 1;
293 		ep = strchr(cp, '\n');
294 		if (ep == NULL)
295 			goto improper;
296 		else
297 			*ep = '\0';
298 		filesize = strtol(cp, &ep, 10);
299 		if (filesize < 1 || *ep != '\0')
300 			goto improper;
301 	} else
302 		filesize = -1;
303 
304 	/* Open the output file. */
305 	out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
306 	if (out < 0) {
307 		warn("Can't open %s", savefile);
308 		goto cleanup_url_get;
309 	}
310 
311 	/* Trap signals */
312 	oldintr = NULL;
313 	if (setjmp(httpabort)) {
314 		if (oldintr)
315 			(void)signal(SIGINT, oldintr);
316 		goto cleanup_url_get;
317 	}
318 	oldintr = signal(SIGINT, aborthttp);
319 
320 	bytes = 0;
321 	hashbytes = mark;
322 	progressmeter(-1);
323 
324 	/* Finally, suck down the file. */
325 	i = 0;
326 	while ((len = read(s, buf, sizeof(buf))) > 0) {
327 		bytes += len;
328 		for (cp = buf; len > 0; len -= i, cp += i) {
329 			if ((i = write(out, cp, len)) == -1) {
330 				warn("Writing %s", savefile);
331 				goto cleanup_url_get;
332 			}
333 			else if (i == 0)
334 				break;
335 		}
336 		if (hash && !progress) {
337 			while (bytes >= hashbytes) {
338 				(void)putchar('#');
339 				hashbytes += mark;
340 			}
341 			(void)fflush(stdout);
342 		}
343 	}
344 	if (hash && !progress && bytes > 0) {
345 		if (bytes < mark)
346 			(void)putchar('#');
347 		(void)putchar('\n');
348 		(void)fflush(stdout);
349 	}
350 	if (len != 0) {
351 		warn("Reading from socket");
352 		goto cleanup_url_get;
353 	}
354 	progressmeter(1);
355 	if (verbose)
356 		puts("Successfully retrieved file.");
357 	(void)signal(SIGINT, oldintr);
358 
359 	close(s);
360 	close(out);
361 	if (proxy)
362 		free(proxy);
363 	free(line);
364 	return (0);
365 
366 noftpautologin:
367 	warnx(
368 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
369 	goto cleanup_url_get;
370 
371 improper:
372 	warnx("Improper response from %s", host);
373 
374 cleanup_url_get:
375 	if (s != -1)
376 		close(s);
377 	if (proxy)
378 		free(proxy);
379 	free(line);
380 	return (-1);
381 }
382 
383 /*
384  * Abort a http retrieval
385  */
386 void
387 aborthttp(notused)
388 	int notused;
389 {
390 
391 	alarmtimer(0);
392 	puts("\nhttp fetch aborted.");
393 	(void)fflush(stdout);
394 	longjmp(httpabort, 1);
395 }
396 
397 /*
398  * Retrieve multiple files from the command line, transferring
399  * files of the form "host:path", "ftp://host/path" using the
400  * ftp protocol, and files of the form "http://host/path" using
401  * the http protocol.
402  * If path has a trailing "/", then return (-1);
403  * the path will be cd-ed into and the connection remains open,
404  * and the function will return -1 (to indicate the connection
405  * is alive).
406  * If an error occurs the return value will be the offset+1 in
407  * argv[] of the file that caused a problem (i.e, argv[x]
408  * returns x+1)
409  * Otherwise, 0 is returned if all files retrieved successfully.
410  */
411 int
412 auto_fetch(argc, argv)
413 	int argc;
414 	char *argv[];
415 {
416 	static char lasthost[MAXHOSTNAMELEN];
417 	char *xargv[5];
418 	char *cp, *line, *host, *dir, *file, *portnum;
419 	char *user, *pass;
420 	char *ftpproxy, *httpproxy;
421 	int rval, xargc;
422 	volatile int argpos;
423 	int dirhasglob, filehasglob;
424 	char rempath[MAXPATHLEN];
425 
426 	argpos = 0;
427 
428 	if (setjmp(toplevel)) {
429 		if (connected)
430 			disconnect(0, NULL);
431 		return (argpos + 1);
432 	}
433 	(void)signal(SIGINT, (sig_t)intr);
434 	(void)signal(SIGPIPE, (sig_t)lostpeer);
435 
436 	ftpproxy = getenv(FTP_PROXY);
437 	httpproxy = getenv(HTTP_PROXY);
438 
439 	/*
440 	 * Loop through as long as there's files to fetch.
441 	 */
442 	for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
443 		if (strchr(argv[argpos], ':') == NULL)
444 			break;
445 		host = dir = file = portnum = user = pass = NULL;
446 
447 		/*
448 		 * We muck with the string, so we make a copy.
449 		 */
450 		line = strdup(argv[argpos]);
451 		if (line == NULL)
452 			errx(1, "Can't allocate memory for auto-fetch.");
453 
454 		/*
455 		 * Try HTTP URL-style arguments first.
456 		 */
457 		if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
458 			if (url_get(line, httpproxy) == -1)
459 				rval = argpos + 1;
460 			continue;
461 		}
462 
463 		/*
464 		 * Try FTP URL-style arguments next. If ftpproxy is
465 		 * set, use url_get() instead of standard ftp.
466 		 * Finally, try host:file.
467 		 */
468 		host = line;
469 		if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
470 			if (ftpproxy) {
471 				if (url_get(line, ftpproxy) == -1)
472 					rval = argpos + 1;
473 				continue;
474 			}
475 			host += sizeof(FTP_URL) - 1;
476 			dir = strchr(host, '/');
477 
478 				/* look for [user:pass@]host[:port] */
479 			pass = strpbrk(host, ":@/");
480 			if (pass == NULL || *pass == '/') {
481 				pass = NULL;
482 				goto parsed_url;
483 			}
484 			if (pass == host || *pass == '@') {
485 bad_ftp_url:
486 				warnx("Invalid URL: %s", argv[argpos]);
487 				rval = argpos + 1;
488 				continue;
489 			}
490 			*pass++ = '\0';
491 			cp = strpbrk(pass, ":@/");
492 			if (cp == NULL || *cp == '/') {
493 				portnum = pass;
494 				pass = NULL;
495 				goto parsed_url;
496 			}
497 			if (EMPTYSTRING(cp) || *cp == ':')
498 				goto bad_ftp_url;
499 			*cp++ = '\0';
500 			user = host;
501 			if (EMPTYSTRING(user))
502 				goto bad_ftp_url;
503 			host = cp;
504 			portnum = strchr(host, ':');
505 			if (portnum != NULL)
506 				*portnum++ = '\0';
507 		} else {			/* classic style `host:file' */
508 			dir = strchr(host, ':');
509 		}
510 parsed_url:
511 		if (EMPTYSTRING(host)) {
512 			rval = argpos + 1;
513 			continue;
514 		}
515 
516 		/*
517 		 * If dir is NULL, the file wasn't specified
518 		 * (URL looked something like ftp://host)
519 		 */
520 		if (dir != NULL)
521 			*dir++ = '\0';
522 
523 		/*
524 		 * Extract the file and (if present) directory name.
525 		 */
526 		if (! EMPTYSTRING(dir)) {
527 			cp = strrchr(dir, '/');
528 			if (cp != NULL) {
529 				*cp++ = '\0';
530 				file = cp;
531 			} else {
532 				file = dir;
533 				dir = NULL;
534 			}
535 		}
536 		if (debug)
537 			printf("user %s:%s host %s port %s dir %s file %s\n",
538 			    user, pass, host, portnum, dir, file);
539 
540 		/*
541 		 * Set up the connection if we don't have one.
542 		 */
543 		if (strcmp(host, lasthost) != 0) {
544 			int oautologin;
545 
546 			(void)strcpy(lasthost, host);
547 			if (connected)
548 				disconnect(0, NULL);
549 			xargv[0] = __progname;
550 			xargv[1] = host;
551 			xargv[2] = NULL;
552 			xargc = 2;
553 			if (! EMPTYSTRING(portnum)) {
554 				xargv[2] = portnum;
555 				xargv[3] = NULL;
556 				xargc = 3;
557 			}
558 			oautologin = autologin;
559 			if (user != NULL)
560 				autologin = 0;
561 			setpeer(xargc, xargv);
562 			autologin = oautologin;
563 			if ((connected == 0)
564 			 || ((connected == 1) && !login(host, user, pass)) ) {
565 				warnx("Can't connect or login to host `%s'",
566 				    host);
567 				rval = argpos + 1;
568 				continue;
569 			}
570 
571 			/* Always use binary transfers. */
572 			setbinary(0, NULL);
573 		}
574 			/* cd back to '/' */
575 		xargv[0] = "cd";
576 		xargv[1] = "/";
577 		xargv[2] = NULL;
578 		cd(2, xargv);
579 		if (! dirchange) {
580 			rval = argpos + 1;
581 			continue;
582 		}
583 
584 		dirhasglob = filehasglob = 0;
585 		if (doglob) {
586 			if (! EMPTYSTRING(dir) &&
587 			    strpbrk(dir, "*?[]{}") != NULL)
588 				dirhasglob = 1;
589 			if (! EMPTYSTRING(file) &&
590 			    strpbrk(file, "*?[]{}") != NULL)
591 				filehasglob = 1;
592 		}
593 
594 		/* Change directories, if necessary. */
595 		if (! EMPTYSTRING(dir) && !dirhasglob) {
596 			xargv[0] = "cd";
597 			xargv[1] = dir;
598 			xargv[2] = NULL;
599 			cd(2, xargv);
600 			if (! dirchange) {
601 				rval = argpos + 1;
602 				continue;
603 			}
604 		}
605 
606 		if (EMPTYSTRING(file)) {
607 			rval = -1;
608 			continue;
609 		}
610 
611 		if (!verbose)
612 			printf("Retrieving %s/%s\n", dir ? dir : "", file);
613 
614 		if (dirhasglob) {
615 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
616 			file = rempath;
617 		}
618 
619 		/* Fetch the file(s). */
620 		xargv[0] = "get";
621 		xargv[1] = file;
622 		xargv[2] = NULL;
623 		if (dirhasglob || filehasglob) {
624 			int ointeractive;
625 
626 			ointeractive = interactive;
627 			interactive = 0;
628 			xargv[0] = "mget";
629 			mget(2, xargv);
630 			interactive = ointeractive;
631 		} else
632 			get(2, xargv);
633 
634 		if ((code / 100) != COMPLETE)
635 			rval = argpos + 1;
636 	}
637 	if (connected && rval != -1)
638 		disconnect(0, NULL);
639 	return (rval);
640 }
641