1 /* $NetBSD: fetch.c,v 1.16.2.1 1997/11/18 01:00:22 mellon Exp $ */
4 * Copyright (c) 1997 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason Thorpe and Luke Mewburn.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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.
38 * $NetBSD: fetch.c,v 1.16.2.1 1997/11/18 01:00:22 mellon Exp $
39 * $FreeBSD: src/usr.bin/ftp/fetch.c,v 1.12.2.6 2002/10/19 12:50:26 roam Exp $
40 * $DragonFly: src/usr.bin/ftp/Attic/fetch.c,v 1.2 2003/06/17 04:29:26 dillon Exp $
43 #include <sys/cdefs.h>
46 * FTP User Program -- Command line file retrieval
49 #include <sys/types.h>
50 #include <sys/param.h>
51 #include <sys/socket.h>
53 #include <netinet/in.h>
56 #include <arpa/inet.h>
71 static int url_get __P((const char *, const char *));
72 void aborthttp __P((int));
75 #define FTP_URL "ftp://" /* ftp URL prefix */
76 #define HTTP_URL "http://" /* http URL prefix */
77 #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */
78 #define HTTP_PROXY "http_proxy" /* env var with http proxy location */
81 #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0'))
86 * Retrieve URL, via the proxy in $proxyvar if necessary.
87 * Modifies the string argument given.
88 * Returns -1 on failure, 0 on success
91 url_get(origline, proxyenv)
95 struct addrinfo hints;
96 struct addrinfo *res0, *res;
97 char nameinfo[2 * INET6_ADDRSTRLEN + 1];
102 char c, *cp, *ep, *http_buffer, *portnum, *path, buf[4096];
103 const char *savefile;
104 char *line, *proxy, *host;
105 volatile sig_t oldintr;
114 #ifdef __GNUC__ /* XXX: to shut up gcc warnings */
120 line = strdup(origline);
122 errx(1, "Can't allocate memory to parse URL");
123 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
124 host = line + sizeof(HTTP_URL) - 1;
125 else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
126 host = line + sizeof(FTP_URL) - 1;
129 errx(1, "url_get: Invalid URL '%s'", line);
131 path = strchr(host, '/'); /* find path */
132 if (EMPTYSTRING(path)) {
135 warnx("Invalid URL (no `/' after host): %s", origline);
136 goto cleanup_url_get;
139 if (EMPTYSTRING(path)) {
142 warnx("Invalid URL (no file after host): %s", origline);
143 goto cleanup_url_get;
146 savefile = strrchr(path, '/'); /* find savefile */
147 if (savefile != NULL)
151 if (EMPTYSTRING(savefile)) {
154 warnx("Invalid URL (no file after directory): %s", origline);
155 goto cleanup_url_get;
158 if (proxyenv != NULL) { /* use proxy */
159 proxy = strdup(proxyenv);
161 errx(1, "Can't allocate memory for proxy URL.");
162 if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
163 host = proxy + sizeof(HTTP_URL) - 1;
164 else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
165 host = proxy + sizeof(FTP_URL) - 1;
167 warnx("Malformed proxy URL: %s", proxyenv);
168 goto cleanup_url_get;
170 if (EMPTYSTRING(host)) {
171 warnx("Malformed proxy URL: %s", proxyenv);
172 goto cleanup_url_get;
174 *--path = '/'; /* add / back to real path */
175 path = strchr(host, '/'); /* remove trailing / on host */
176 if (! EMPTYSTRING(path))
181 if (*host == '[' && (portnum = strrchr(host, ']'))) { /* IPv6 URL */
189 portnum = strrchr(host, ':'); /* find portnum */
195 printf("host %s, port %s, path %s, save as %s.\n",
196 host, portnum, path, savefile);
198 if (! EMPTYSTRING(portnum)) {
203 memset(&hints, 0, sizeof(hints));
204 hints.ai_family = family;
205 hints.ai_socktype = SOCK_STREAM;
206 error = getaddrinfo(host, port, &hints, &res);
209 warnx("%s: %s", host, gai_strerror(error));
210 if (error == EAI_SYSTEM)
211 warnx("%s: %s", host, strerror(errno));
212 goto cleanup_url_get;
218 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
223 warn("Can't create socket");
224 goto cleanup_url_get;
228 struct addrinfo *bindres;
231 for (bindres = bindres0;
233 bindres = bindres->ai_next)
234 if (bindres->ai_family == res->ai_family)
238 binderr = bind(s, bindres->ai_addr, bindres->ai_addrlen);
246 getnameinfo(bindres->ai_addr, bindres->ai_addrlen,
247 nameinfo, sizeof(nameinfo), NULL, 0,
249 /* XXX check error? */
250 warn("Can't bind to %s", nameinfo);
251 goto cleanup_url_get;
255 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
261 warn("Can't connect to %s", host);
262 goto cleanup_url_get;
271 * Construct and send the request. We're expecting a return
272 * status of "200". Proxy requests don't want leading /.
275 printf("Requesting %s\n", origline);
276 len = asprintf(&http_buffer,
277 "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host);
279 printf("Requesting %s (via %s)\n", origline, proxyenv);
280 len = asprintf(&http_buffer,
281 "GET %s HTTP/1.0\r\n\r\n", path);
283 if (len < 0 || http_buffer == NULL) {
284 warnx("Failed to format HTTP request");
285 goto cleanup_url_get;
287 if (write(s, http_buffer, len) < len) {
288 warn("Writing HTTP request");
290 goto cleanup_url_get;
293 memset(buf, 0, sizeof(buf));
294 for (cp = buf; cp < buf + sizeof(buf); ) {
295 if (read(s, cp, 1) != 1)
303 buf[sizeof(buf) - 1] = '\0'; /* sanity */
304 cp = strchr(buf, ' ');
309 if (strncmp(cp, "200", 3)) {
310 warnx("Error retrieving file: %s", cp);
311 goto cleanup_url_get;
315 * Read the rest of the header.
317 memset(buf, 0, sizeof(buf));
319 for (cp = buf; cp < buf + sizeof(buf); ) {
320 if (read(s, cp, 1) != 1)
324 if (*cp == '\n' && c == '\n')
329 buf[sizeof(buf) - 1] = '\0'; /* sanity */
332 * Look for the "Content-length: " header.
334 #define CONTENTLEN "Content-Length: "
335 for (cp = buf; *cp != '\0'; cp++) {
336 if (tolower((unsigned char)*cp) == 'c' &&
337 strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
341 cp += sizeof(CONTENTLEN) - 1;
342 ep = strchr(cp, '\n');
347 filesize = strtol(cp, &ep, 10);
348 if (filesize < 1 || *ep != '\0')
353 /* Open the output file. */
354 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
356 warn("Can't open %s", savefile);
357 goto cleanup_url_get;
362 if (setjmp(httpabort)) {
364 (void)signal(SIGINT, oldintr);
365 goto cleanup_url_get;
367 oldintr = signal(SIGINT, aborthttp);
373 /* Finally, suck down the file. */
375 while ((len = read(s, buf, sizeof(buf))) > 0) {
377 for (cp = buf; len > 0; len -= i, cp += i) {
378 if ((i = write(out, cp, len)) == -1) {
379 warn("Writing %s", savefile);
380 goto cleanup_url_get;
385 if (hash && !progress) {
386 while (bytes >= hashbytes) {
390 (void)fflush(stdout);
393 if (hash && !progress && bytes > 0) {
397 (void)fflush(stdout);
400 warn("Reading from socket");
401 goto cleanup_url_get;
405 puts("Successfully retrieved file.");
406 (void)signal(SIGINT, oldintr);
417 "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
418 goto cleanup_url_get;
421 warnx("Improper response from %s", host);
435 * Abort a http retrieval
443 puts("\nhttp fetch aborted.");
444 (void)fflush(stdout);
445 longjmp(httpabort, 1);
449 * Retrieve multiple files from the command line, transferring
450 * files of the form "host:path", "ftp://host/path" using the
451 * ftp protocol, and files of the form "http://host/path" using
453 * If path has a trailing "/", then return (-1);
454 * the path will be cd-ed into and the connection remains open,
455 * and the function will return -1 (to indicate the connection
457 * If an error occurs the return value will be the offset+1 in
458 * argv[] of the file that caused a problem (i.e, argv[x]
460 * Otherwise, 0 is returned if all files retrieved successfully.
463 auto_fetch(argc, argv)
467 static char lasthost[MAXHOSTNAMELEN];
469 char *cp, *line, *host, *dir, *file, *portnum;
471 char *ftpproxy, *httpproxy;
474 int dirhasglob, filehasglob;
475 char rempath[MAXPATHLEN];
479 if (setjmp(toplevel)) {
484 (void)signal(SIGINT, (sig_t)intr);
485 (void)signal(SIGPIPE, (sig_t)lostpeer);
487 ftpproxy = getenv(FTP_PROXY);
488 httpproxy = getenv(HTTP_PROXY);
491 * Loop through as long as there's files to fetch.
493 for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
494 if (strchr(argv[argpos], ':') == NULL)
496 host = dir = file = portnum = user = pass = NULL;
499 * We muck with the string, so we make a copy.
501 line = strdup(argv[argpos]);
503 errx(1, "Can't allocate memory for auto-fetch.");
506 * Try HTTP URL-style arguments first.
508 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
509 if (url_get(line, httpproxy) == -1)
515 * Try FTP URL-style arguments next. If ftpproxy is
516 * set, use url_get() instead of standard ftp.
517 * Finally, try host:file.
520 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
522 if (url_get(line, ftpproxy) == -1)
526 host += sizeof(FTP_URL) - 1;
527 dir = strchr(host, '/');
529 /* look for [user:pass@]host[:port] */
530 pass = strpbrk(host, ":@/");
531 if (pass == NULL || *pass == '/') {
535 if (pass == host || *pass == '@') {
537 warnx("Invalid URL: %s", argv[argpos]);
542 cp = strpbrk(pass, ":@/");
543 if (cp == NULL || *cp == '/') {
548 if (EMPTYSTRING(cp) || *cp == ':')
552 if (EMPTYSTRING(user))
555 portnum = strchr(host, ':');
558 } else { /* classic style `host:file' */
562 (end_brace = strrchr(host, ']')) != NULL) {
566 dir = strchr(end_brace + 1, ':');
568 dir = strchr(host, ':');
571 if (EMPTYSTRING(host)) {
577 * If dir is NULL, the file wasn't specified
578 * (URL looked something like ftp://host)
584 * Extract the file and (if present) directory name.
586 if (! EMPTYSTRING(dir)) {
587 cp = strrchr(dir, '/');
597 printf("user %s:%s host %s port %s dir %s file %s\n",
598 user, pass, host, portnum, dir, file);
601 * Set up the connection if we don't have one.
603 if (strcmp(host, lasthost) != 0) {
606 (void)strcpy(lasthost, host);
609 xargv[0] = __progname;
613 if (! EMPTYSTRING(portnum)) {
618 oautologin = autologin;
621 setpeer(xargc, xargv);
622 autologin = oautologin;
624 || ((connected == 1) && !login(host, user, pass)) ) {
625 warnx("Can't connect or login to host `%s'",
631 /* Always use binary transfers. */
644 dirhasglob = filehasglob = 0;
646 if (! EMPTYSTRING(dir) &&
647 strpbrk(dir, "*?[]{}") != NULL)
649 if (! EMPTYSTRING(file) &&
650 strpbrk(file, "*?[]{}") != NULL)
654 /* Change directories, if necessary. */
655 if (! EMPTYSTRING(dir) && !dirhasglob) {
666 if (EMPTYSTRING(file)) {
672 printf("Retrieving %s/%s\n", dir ? dir : "", file);
675 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
679 /* Fetch the file(s). */
683 if (dirhasglob || filehasglob) {
686 ointeractive = interactive;
690 interactive = ointeractive;
694 if ((code / 100) != COMPLETE)
697 if (connected && rval != -1)
706 char *path, pton_buf[16];
708 if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0
709 || strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
712 if (*p == '[' && (path = strrchr(p, ']')) != NULL) /*IPv6 addr in []*/
713 return (*(++path) == ':') ? 1 : 0;
715 if (inet_pton(AF_INET6, p, pton_buf) == 1) /* raw IPv6 addr */
718 if (strchr(p, ':') != NULL) /* else, if ':' exist */