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