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.3 2003/10/04 20:36:44 hmp 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(const char *, const char *);
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(const char *origline, const char *proxyenv)
93 struct addrinfo hints;
94 struct addrinfo *res0, *res;
95 char nameinfo[2 * INET6_ADDRSTRLEN + 1];
100 char c, *cp, *ep, *http_buffer, *portnum, *path, buf[4096];
101 const char *savefile;
102 char *line, *proxy, *host;
103 volatile sig_t oldintr;
112 #ifdef __GNUC__ /* XXX: to shut up gcc warnings */
118 line = strdup(origline);
120 errx(1, "Can't allocate memory to parse URL");
121 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
122 host = line + sizeof(HTTP_URL) - 1;
123 else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
124 host = line + sizeof(FTP_URL) - 1;
127 errx(1, "url_get: Invalid URL '%s'", line);
129 path = strchr(host, '/'); /* find path */
130 if (EMPTYSTRING(path)) {
133 warnx("Invalid URL (no `/' after host): %s", origline);
134 goto cleanup_url_get;
137 if (EMPTYSTRING(path)) {
140 warnx("Invalid URL (no file after host): %s", origline);
141 goto cleanup_url_get;
144 savefile = strrchr(path, '/'); /* find savefile */
145 if (savefile != NULL)
149 if (EMPTYSTRING(savefile)) {
152 warnx("Invalid URL (no file after directory): %s", origline);
153 goto cleanup_url_get;
156 if (proxyenv != NULL) { /* use proxy */
157 proxy = strdup(proxyenv);
159 errx(1, "Can't allocate memory for proxy URL.");
160 if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
161 host = proxy + sizeof(HTTP_URL) - 1;
162 else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
163 host = proxy + sizeof(FTP_URL) - 1;
165 warnx("Malformed proxy URL: %s", proxyenv);
166 goto cleanup_url_get;
168 if (EMPTYSTRING(host)) {
169 warnx("Malformed proxy URL: %s", proxyenv);
170 goto cleanup_url_get;
172 *--path = '/'; /* add / back to real path */
173 path = strchr(host, '/'); /* remove trailing / on host */
174 if (! EMPTYSTRING(path))
179 if (*host == '[' && (portnum = strrchr(host, ']'))) { /* IPv6 URL */
187 portnum = strrchr(host, ':'); /* find portnum */
193 printf("host %s, port %s, path %s, save as %s.\n",
194 host, portnum, path, savefile);
196 if (! EMPTYSTRING(portnum)) {
201 memset(&hints, 0, sizeof(hints));
202 hints.ai_family = family;
203 hints.ai_socktype = SOCK_STREAM;
204 error = getaddrinfo(host, port, &hints, &res);
207 warnx("%s: %s", host, gai_strerror(error));
208 if (error == EAI_SYSTEM)
209 warnx("%s: %s", host, strerror(errno));
210 goto cleanup_url_get;
216 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
221 warn("Can't create socket");
222 goto cleanup_url_get;
226 struct addrinfo *bindres;
229 for (bindres = bindres0;
231 bindres = bindres->ai_next)
232 if (bindres->ai_family == res->ai_family)
236 binderr = bind(s, bindres->ai_addr, bindres->ai_addrlen);
244 getnameinfo(bindres->ai_addr, bindres->ai_addrlen,
245 nameinfo, sizeof(nameinfo), NULL, 0,
247 /* XXX check error? */
248 warn("Can't bind to %s", nameinfo);
249 goto cleanup_url_get;
253 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
259 warn("Can't connect to %s", host);
260 goto cleanup_url_get;
269 * Construct and send the request. We're expecting a return
270 * status of "200". Proxy requests don't want leading /.
273 printf("Requesting %s\n", origline);
274 len = asprintf(&http_buffer,
275 "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host);
277 printf("Requesting %s (via %s)\n", origline, proxyenv);
278 len = asprintf(&http_buffer,
279 "GET %s HTTP/1.0\r\n\r\n", path);
281 if (len < 0 || http_buffer == NULL) {
282 warnx("Failed to format HTTP request");
283 goto cleanup_url_get;
285 if (write(s, http_buffer, len) < len) {
286 warn("Writing HTTP request");
288 goto cleanup_url_get;
291 memset(buf, 0, sizeof(buf));
292 for (cp = buf; cp < buf + sizeof(buf); ) {
293 if (read(s, cp, 1) != 1)
301 buf[sizeof(buf) - 1] = '\0'; /* sanity */
302 cp = strchr(buf, ' ');
307 if (strncmp(cp, "200", 3)) {
308 warnx("Error retrieving file: %s", cp);
309 goto cleanup_url_get;
313 * Read the rest of the header.
315 memset(buf, 0, sizeof(buf));
317 for (cp = buf; cp < buf + sizeof(buf); ) {
318 if (read(s, cp, 1) != 1)
322 if (*cp == '\n' && c == '\n')
327 buf[sizeof(buf) - 1] = '\0'; /* sanity */
330 * Look for the "Content-length: " header.
332 #define CONTENTLEN "Content-Length: "
333 for (cp = buf; *cp != '\0'; cp++) {
334 if (tolower((unsigned char)*cp) == 'c' &&
335 strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0)
339 cp += sizeof(CONTENTLEN) - 1;
340 ep = strchr(cp, '\n');
345 filesize = strtol(cp, &ep, 10);
346 if (filesize < 1 || *ep != '\0')
351 /* Open the output file. */
352 out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
354 warn("Can't open %s", savefile);
355 goto cleanup_url_get;
360 if (setjmp(httpabort)) {
362 (void)signal(SIGINT, oldintr);
363 goto cleanup_url_get;
365 oldintr = signal(SIGINT, aborthttp);
371 /* Finally, suck down the file. */
373 while ((len = read(s, buf, sizeof(buf))) > 0) {
375 for (cp = buf; len > 0; len -= i, cp += i) {
376 if ((i = write(out, cp, len)) == -1) {
377 warn("Writing %s", savefile);
378 goto cleanup_url_get;
383 if (hash && !progress) {
384 while (bytes >= hashbytes) {
388 (void)fflush(stdout);
391 if (hash && !progress && bytes > 0) {
395 (void)fflush(stdout);
398 warn("Reading from socket");
399 goto cleanup_url_get;
403 puts("Successfully retrieved file.");
404 (void)signal(SIGINT, oldintr);
415 "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
416 goto cleanup_url_get;
419 warnx("Improper response from %s", host);
433 * Abort a http retrieval
436 aborthttp(int notused)
440 puts("\nhttp fetch aborted.");
441 (void)fflush(stdout);
442 longjmp(httpabort, 1);
446 * Retrieve multiple files from the command line, transferring
447 * files of the form "host:path", "ftp://host/path" using the
448 * ftp protocol, and files of the form "http://host/path" using
450 * If path has a trailing "/", then return (-1);
451 * the path will be cd-ed into and the connection remains open,
452 * and the function will return -1 (to indicate the connection
454 * If an error occurs the return value will be the offset+1 in
455 * argv[] of the file that caused a problem (i.e, argv[x]
457 * Otherwise, 0 is returned if all files retrieved successfully.
460 auto_fetch(int argc, char **argv)
462 static char lasthost[MAXHOSTNAMELEN];
464 char *cp, *line, *host, *dir, *file, *portnum;
466 char *ftpproxy, *httpproxy;
469 int dirhasglob, filehasglob;
470 char rempath[MAXPATHLEN];
474 if (setjmp(toplevel)) {
479 (void)signal(SIGINT, (sig_t)intr);
480 (void)signal(SIGPIPE, (sig_t)lostpeer);
482 ftpproxy = getenv(FTP_PROXY);
483 httpproxy = getenv(HTTP_PROXY);
486 * Loop through as long as there's files to fetch.
488 for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
489 if (strchr(argv[argpos], ':') == NULL)
491 host = dir = file = portnum = user = pass = NULL;
494 * We muck with the string, so we make a copy.
496 line = strdup(argv[argpos]);
498 errx(1, "Can't allocate memory for auto-fetch.");
501 * Try HTTP URL-style arguments first.
503 if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
504 if (url_get(line, httpproxy) == -1)
510 * Try FTP URL-style arguments next. If ftpproxy is
511 * set, use url_get() instead of standard ftp.
512 * Finally, try host:file.
515 if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
517 if (url_get(line, ftpproxy) == -1)
521 host += sizeof(FTP_URL) - 1;
522 dir = strchr(host, '/');
524 /* look for [user:pass@]host[:port] */
525 pass = strpbrk(host, ":@/");
526 if (pass == NULL || *pass == '/') {
530 if (pass == host || *pass == '@') {
532 warnx("Invalid URL: %s", argv[argpos]);
537 cp = strpbrk(pass, ":@/");
538 if (cp == NULL || *cp == '/') {
543 if (EMPTYSTRING(cp) || *cp == ':')
547 if (EMPTYSTRING(user))
550 portnum = strchr(host, ':');
553 } else { /* classic style `host:file' */
557 (end_brace = strrchr(host, ']')) != NULL) {
561 dir = strchr(end_brace + 1, ':');
563 dir = strchr(host, ':');
566 if (EMPTYSTRING(host)) {
572 * If dir is NULL, the file wasn't specified
573 * (URL looked something like ftp://host)
579 * Extract the file and (if present) directory name.
581 if (! EMPTYSTRING(dir)) {
582 cp = strrchr(dir, '/');
592 printf("user %s:%s host %s port %s dir %s file %s\n",
593 user, pass, host, portnum, dir, file);
596 * Set up the connection if we don't have one.
598 if (strcmp(host, lasthost) != 0) {
601 (void)strcpy(lasthost, host);
604 xargv[0] = __progname;
608 if (! EMPTYSTRING(portnum)) {
613 oautologin = autologin;
616 setpeer(xargc, xargv);
617 autologin = oautologin;
619 || ((connected == 1) && !login(host, user, pass)) ) {
620 warnx("Can't connect or login to host `%s'",
626 /* Always use binary transfers. */
639 dirhasglob = filehasglob = 0;
641 if (! EMPTYSTRING(dir) &&
642 strpbrk(dir, "*?[]{}") != NULL)
644 if (! EMPTYSTRING(file) &&
645 strpbrk(file, "*?[]{}") != NULL)
649 /* Change directories, if necessary. */
650 if (! EMPTYSTRING(dir) && !dirhasglob) {
661 if (EMPTYSTRING(file)) {
667 printf("Retrieving %s/%s\n", dir ? dir : "", file);
670 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
674 /* Fetch the file(s). */
678 if (dirhasglob || filehasglob) {
681 ointeractive = interactive;
685 interactive = ointeractive;
689 if ((code / 100) != COMPLETE)
692 if (connected && rval != -1)
700 char *path, pton_buf[16];
702 if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0
703 || strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
706 if (*p == '[' && (path = strrchr(p, ']')) != NULL) /*IPv6 addr in []*/
707 return (*(++path) == ':') ? 1 : 0;
709 if (inet_pton(AF_INET6, p, pton_buf) == 1) /* raw IPv6 addr */
712 if (strchr(p, ':') != NULL) /* else, if ':' exist */