xref: /netbsd-src/usr.bin/ftp/fetch.c (revision a5a68ff5f29de57339ca14f6c671c0a87714f1f8)
1 /*	$NetBSD: fetch.c,v 1.16 1997/09/21 01:06:31 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.16 1997/09/21 01:06:31 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 	snprintf(buf, sizeof(buf), "GET %s%s HTTP/1.0\r\n\r\n",
239 	    proxy ? "" : "/", path);
240 	len = strlen(buf);
241 	if (write(s, buf, len) < len) {
242 		warn("Writing HTTP request");
243 		goto cleanup_url_get;
244 	}
245 	memset(buf, 0, sizeof(buf));
246 	for (cp = buf; cp < buf + sizeof(buf); ) {
247 		if (read(s, cp, 1) != 1)
248 			goto improper;
249 		if (*cp == '\r')
250 			continue;
251 		if (*cp == '\n')
252 			break;
253 		cp++;
254 	}
255 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
256 	cp = strchr(buf, ' ');
257 	if (cp == NULL)
258 		goto improper;
259 	else
260 		cp++;
261 	if (strncmp(cp, "200", 3)) {
262 		warnx("Error retrieving file: %s", cp);
263 		goto cleanup_url_get;
264 	}
265 
266 	/*
267 	 * Read the rest of the header.
268 	 */
269 	memset(buf, 0, sizeof(buf));
270 	c = '\0';
271 	for (cp = buf; cp < buf + sizeof(buf); ) {
272 		if (read(s, cp, 1) != 1)
273 			goto improper;
274 		if (*cp == '\r')
275 			continue;
276 		if (*cp == '\n' && c == '\n')
277 			break;
278 		c = *cp;
279 		cp++;
280 	}
281 	buf[sizeof(buf) - 1] = '\0';		/* sanity */
282 
283 	/*
284 	 * Look for the "Content-length: " header.
285 	 */
286 #define CONTENTLEN "Content-Length: "
287 	for (cp = buf; *cp != '\0'; cp++) {
288 		if (tolower(*cp) == 'c' &&
289 		    strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
290 			break;
291 	}
292 	if (*cp != '\0') {
293 		cp += sizeof(CONTENTLEN) - 1;
294 		ep = strchr(cp, '\n');
295 		if (ep == NULL)
296 			goto improper;
297 		else
298 			*ep = '\0';
299 		filesize = strtol(cp, &ep, 10);
300 		if (filesize < 1 || *ep != '\0')
301 			goto improper;
302 	} else
303 		filesize = -1;
304 
305 	/* Open the output file. */
306 	out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
307 	if (out < 0) {
308 		warn("Can't open %s", savefile);
309 		goto cleanup_url_get;
310 	}
311 
312 	/* Trap signals */
313 	oldintr = NULL;
314 	if (setjmp(httpabort)) {
315 		if (oldintr)
316 			(void)signal(SIGINT, oldintr);
317 		goto cleanup_url_get;
318 	}
319 	oldintr = signal(SIGINT, aborthttp);
320 
321 	bytes = 0;
322 	hashbytes = mark;
323 	progressmeter(-1);
324 
325 	/* Finally, suck down the file. */
326 	i = 0;
327 	while ((len = read(s, buf, sizeof(buf))) > 0) {
328 		bytes += len;
329 		for (cp = buf; len > 0; len -= i, cp += i) {
330 			if ((i = write(out, cp, len)) == -1) {
331 				warn("Writing %s", savefile);
332 				goto cleanup_url_get;
333 			}
334 			else if (i == 0)
335 				break;
336 		}
337 		if (hash && !progress) {
338 			while (bytes >= hashbytes) {
339 				(void)putchar('#');
340 				hashbytes += mark;
341 			}
342 			(void)fflush(stdout);
343 		}
344 	}
345 	if (hash && !progress && bytes > 0) {
346 		if (bytes < mark)
347 			(void)putchar('#');
348 		(void)putchar('\n');
349 		(void)fflush(stdout);
350 	}
351 	if (len != 0) {
352 		warn("Reading from socket");
353 		goto cleanup_url_get;
354 	}
355 	progressmeter(1);
356 	if (verbose)
357 		puts("Successfully retrieved file.");
358 	(void)signal(SIGINT, oldintr);
359 
360 	close(s);
361 	close(out);
362 	if (proxy)
363 		free(proxy);
364 	free(line);
365 	return (0);
366 
367 noftpautologin:
368 	warnx(
369 	    "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
370 	goto cleanup_url_get;
371 
372 improper:
373 	warnx("Improper response from %s", host);
374 
375 cleanup_url_get:
376 	if (s != -1)
377 		close(s);
378 	if (proxy)
379 		free(proxy);
380 	free(line);
381 	return (-1);
382 }
383 
384 /*
385  * Abort a http retrieval
386  */
387 void
388 aborthttp(notused)
389 	int notused;
390 {
391 
392 	alarmtimer(0);
393 	puts("\nhttp fetch aborted.");
394 	(void)fflush(stdout);
395 	longjmp(httpabort, 1);
396 }
397 
398 /*
399  * Retrieve multiple files from the command line, transferring
400  * files of the form "host:path", "ftp://host/path" using the
401  * ftp protocol, and files of the form "http://host/path" using
402  * the http protocol.
403  * If path has a trailing "/", then return (-1);
404  * the path will be cd-ed into and the connection remains open,
405  * and the function will return -1 (to indicate the connection
406  * is alive).
407  * If an error occurs the return value will be the offset+1 in
408  * argv[] of the file that caused a problem (i.e, argv[x]
409  * returns x+1)
410  * Otherwise, 0 is returned if all files retrieved successfully.
411  */
412 int
413 auto_fetch(argc, argv)
414 	int argc;
415 	char *argv[];
416 {
417 	static char lasthost[MAXHOSTNAMELEN];
418 	char *xargv[5];
419 	char *cp, *line, *host, *dir, *file, *portnum;
420 	char *user, *pass;
421 	char *ftpproxy, *httpproxy;
422 	int rval, xargc;
423 	volatile int argpos;
424 	int dirhasglob, filehasglob;
425 	char rempath[MAXPATHLEN];
426 
427 	argpos = 0;
428 
429 	if (setjmp(toplevel)) {
430 		if (connected)
431 			disconnect(0, NULL);
432 		return (argpos + 1);
433 	}
434 	(void)signal(SIGINT, (sig_t)intr);
435 	(void)signal(SIGPIPE, (sig_t)lostpeer);
436 
437 	ftpproxy = getenv(FTP_PROXY);
438 	httpproxy = getenv(HTTP_PROXY);
439 
440 	/*
441 	 * Loop through as long as there's files to fetch.
442 	 */
443 	for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
444 		if (strchr(argv[argpos], ':') == NULL)
445 			break;
446 		host = dir = file = portnum = user = pass = NULL;
447 
448 		/*
449 		 * We muck with the string, so we make a copy.
450 		 */
451 		line = strdup(argv[argpos]);
452 		if (line == NULL)
453 			errx(1, "Can't allocate memory for auto-fetch.");
454 
455 		/*
456 		 * Try HTTP URL-style arguments first.
457 		 */
458 		if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
459 			if (url_get(line, httpproxy) == -1)
460 				rval = argpos + 1;
461 			continue;
462 		}
463 
464 		/*
465 		 * Try FTP URL-style arguments next. If ftpproxy is
466 		 * set, use url_get() instead of standard ftp.
467 		 * Finally, try host:file.
468 		 */
469 		host = line;
470 		if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
471 			if (ftpproxy) {
472 				if (url_get(line, ftpproxy) == -1)
473 					rval = argpos + 1;
474 				continue;
475 			}
476 			host += sizeof(FTP_URL) - 1;
477 			dir = strchr(host, '/');
478 
479 				/* look for [user:pass@]host[:port] */
480 			pass = strpbrk(host, ":@/");
481 			if (pass == NULL || *pass == '/') {
482 				pass = NULL;
483 				goto parsed_url;
484 			}
485 			if (pass == host || *pass == '@') {
486 bad_ftp_url:
487 				warnx("Invalid URL: %s", argv[argpos]);
488 				rval = argpos + 1;
489 				continue;
490 			}
491 			*pass++ = '\0';
492 			cp = strpbrk(pass, ":@/");
493 			if (cp == NULL || *cp == '/') {
494 				portnum = pass;
495 				pass = NULL;
496 				goto parsed_url;
497 			}
498 			if (EMPTYSTRING(cp) || *cp == ':')
499 				goto bad_ftp_url;
500 			*cp++ = '\0';
501 			user = host;
502 			if (EMPTYSTRING(user))
503 				goto bad_ftp_url;
504 			host = cp;
505 			portnum = strchr(host, ':');
506 			if (portnum != NULL)
507 				*portnum++ = '\0';
508 		} else {			/* classic style `host:file' */
509 			dir = strchr(host, ':');
510 		}
511 parsed_url:
512 		if (EMPTYSTRING(host)) {
513 			rval = argpos + 1;
514 			continue;
515 		}
516 
517 		/*
518 		 * If dir is NULL, the file wasn't specified
519 		 * (URL looked something like ftp://host)
520 		 */
521 		if (dir != NULL)
522 			*dir++ = '\0';
523 
524 		/*
525 		 * Extract the file and (if present) directory name.
526 		 */
527 		if (! EMPTYSTRING(dir)) {
528 			cp = strrchr(dir, '/');
529 			if (cp != NULL) {
530 				*cp++ = '\0';
531 				file = cp;
532 			} else {
533 				file = dir;
534 				dir = NULL;
535 			}
536 		}
537 		if (debug)
538 			printf("user %s:%s host %s port %s dir %s file %s\n",
539 			    user, pass, host, portnum, dir, file);
540 
541 		/*
542 		 * Set up the connection if we don't have one.
543 		 */
544 		if (strcmp(host, lasthost) != 0) {
545 			int oautologin;
546 
547 			(void)strcpy(lasthost, host);
548 			if (connected)
549 				disconnect(0, NULL);
550 			xargv[0] = __progname;
551 			xargv[1] = host;
552 			xargv[2] = NULL;
553 			xargc = 2;
554 			if (! EMPTYSTRING(portnum)) {
555 				xargv[2] = portnum;
556 				xargv[3] = NULL;
557 				xargc = 3;
558 			}
559 			oautologin = autologin;
560 			if (user != NULL)
561 				autologin = 0;
562 			setpeer(xargc, xargv);
563 			autologin = oautologin;
564 			if ((connected == 0)
565 			 || ((connected == 1) && !login(host, user, pass)) ) {
566 				warnx("Can't connect or login to host `%s'",
567 				    host);
568 				rval = argpos + 1;
569 				continue;
570 			}
571 
572 			/* Always use binary transfers. */
573 			setbinary(0, NULL);
574 		}
575 			/* cd back to '/' */
576 		xargv[0] = "cd";
577 		xargv[1] = "/";
578 		xargv[2] = NULL;
579 		cd(2, xargv);
580 		if (! dirchange) {
581 			rval = argpos + 1;
582 			continue;
583 		}
584 
585 		dirhasglob = filehasglob = 0;
586 		if (doglob) {
587 			if (! EMPTYSTRING(dir) &&
588 			    strpbrk(dir, "*?[]{}") != NULL)
589 				dirhasglob = 1;
590 			if (! EMPTYSTRING(file) &&
591 			    strpbrk(file, "*?[]{}") != NULL)
592 				filehasglob = 1;
593 		}
594 
595 		/* Change directories, if necessary. */
596 		if (! EMPTYSTRING(dir) && !dirhasglob) {
597 			xargv[0] = "cd";
598 			xargv[1] = dir;
599 			xargv[2] = NULL;
600 			cd(2, xargv);
601 			if (! dirchange) {
602 				rval = argpos + 1;
603 				continue;
604 			}
605 		}
606 
607 		if (EMPTYSTRING(file)) {
608 			rval = -1;
609 			continue;
610 		}
611 
612 		if (!verbose)
613 			printf("Retrieving %s/%s\n", dir ? dir : "", file);
614 
615 		if (dirhasglob) {
616 			snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
617 			file = rempath;
618 		}
619 
620 		/* Fetch the file(s). */
621 		xargv[0] = "get";
622 		xargv[1] = file;
623 		xargv[2] = NULL;
624 		if (dirhasglob || filehasglob) {
625 			int ointeractive;
626 
627 			ointeractive = interactive;
628 			interactive = 0;
629 			xargv[0] = "mget";
630 			mget(2, xargv);
631 			interactive = ointeractive;
632 		} else
633 			get(2, xargv);
634 
635 		if ((code / 100) != COMPLETE)
636 			rval = argpos + 1;
637 	}
638 	if (connected && rval != -1)
639 		disconnect(0, NULL);
640 	return (rval);
641 }
642