Annotation of src/usr.bin/ftp/fetch.c, Revision 1.56
1.56 ! fgsch 1: /* $OpenBSD: fetch.c,v 1.55 2005/07/18 02:55:59 fgsch Exp $ */
1.15 millert 2: /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */
1.1 millert 3:
4: /*-
5: * Copyright (c) 1997 The NetBSD Foundation, Inc.
6: * All rights reserved.
7: *
8: * This code is derived from software contributed to The NetBSD Foundation
9: * by Jason Thorpe and Luke Mewburn.
10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * 3. All advertising materials mentioning features or use of this software
20: * must display the following acknowledgement:
21: * This product includes software developed by the NetBSD
22: * Foundation, Inc. and its contributors.
23: * 4. Neither the name of The NetBSD Foundation nor the names of its
24: * contributors may be used to endorse or promote products derived
25: * from this software without specific prior written permission.
26: *
27: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1.15 millert 30: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1.1 millert 32: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37: * POSSIBILITY OF SUCH DAMAGE.
38: */
39:
1.48 deraadt 40: #if !defined(lint) && !defined(SMALL)
1.56 ! fgsch 41: static char rcsid[] = "$OpenBSD: fetch.c,v 1.55 2005/07/18 02:55:59 fgsch Exp $";
1.48 deraadt 42: #endif /* not lint and not SMALL */
1.1 millert 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>
1.22 deraadt 51: #include <sys/stat.h>
1.1 millert 52:
53: #include <netinet/in.h>
54:
55: #include <arpa/ftp.h>
56: #include <arpa/inet.h>
57:
58: #include <ctype.h>
59: #include <err.h>
1.17 millert 60: #include <libgen.h>
1.1 millert 61: #include <netdb.h>
62: #include <fcntl.h>
1.3 millert 63: #include <signal.h>
1.1 millert 64: #include <stdio.h>
1.19 deraadt 65: #include <errno.h>
1.1 millert 66: #include <stdlib.h>
67: #include <string.h>
68: #include <unistd.h>
1.40 fgsch 69: #include <util.h>
1.1 millert 70:
71: #include "ftp_var.h"
72:
1.38 millert 73: static int url_get(const char *, const char *, const char *);
74: void aborthttp(int);
75: void abortfile(int);
1.42 deraadt 76: char hextochar(const char *);
77: char *urldecode(const char *);
1.14 millert 78:
1.1 millert 79: #define FTP_URL "ftp://" /* ftp URL prefix */
80: #define HTTP_URL "http://" /* http URL prefix */
1.22 deraadt 81: #define FILE_URL "file:" /* file URL prefix */
1.6 millert 82: #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */
1.1 millert 83: #define HTTP_PROXY "http_proxy" /* env var with http proxy location */
84:
85:
86: #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0'))
87:
1.37 heko 88: static const char *at_encoding_warning =
1.53 deraadt 89: "Extra `@' characters in usernames and passwords should be encoded as %%40";
1.37 heko 90:
1.1 millert 91: jmp_buf httpabort;
92:
1.54 fgsch 93: static int redirect_loop;
94:
1.1 millert 95: /*
1.6 millert 96: * Retrieve URL, via the proxy in $proxyvar if necessary.
1.1 millert 97: * Modifies the string argument given.
98: * Returns -1 on failure, 0 on success
99: */
1.14 millert 100: static int
1.50 deraadt 101: url_get(const char *origline, const char *proxyenv, const char *outfile)
1.1 millert 102: {
1.53 deraadt 103: char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *ep, *portnum, *path;
104: char *hosttail, *cause = "unknown", *line, *host, *port, *buf = NULL;
105: int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1;
1.25 itojun 106: struct addrinfo hints, *res0, *res;
1.34 millert 107: const char * volatile savefile;
1.53 deraadt 108: char * volatile proxy = NULL;
109: volatile int s = -1, out;
1.14 millert 110: volatile sig_t oldintr;
1.53 deraadt 111: FILE *fin = NULL;
1.1 millert 112: off_t hashbytes;
1.53 deraadt 113: size_t len;
1.14 millert 114:
115: line = strdup(origline);
116: if (line == NULL)
117: errx(1, "Can't allocate memory to parse URL");
1.7 millert 118: if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
1.6 millert 119: host = line + sizeof(HTTP_URL) - 1;
1.14 millert 120: else if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1.6 millert 121: host = line + sizeof(FTP_URL) - 1;
1.14 millert 122: isftpurl = 1;
1.22 deraadt 123: } else if (strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
124: host = line + sizeof(FILE_URL) - 1;
125: isfileurl = 1;
1.14 millert 126: } else
127: errx(1, "url_get: Invalid URL '%s'", line);
1.6 millert 128:
1.22 deraadt 129: if (isfileurl) {
130: path = host;
131: } else {
132: path = strchr(host, '/'); /* find path */
133: if (EMPTYSTRING(path)) {
134: if (isftpurl)
135: goto noftpautologin;
136: warnx("Invalid URL (no `/' after host): %s", origline);
137: goto cleanup_url_get;
138: }
139: *path++ = '\0';
140: if (EMPTYSTRING(path)) {
141: if (isftpurl)
142: goto noftpautologin;
143: warnx("Invalid URL (no file after host): %s", origline);
144: goto cleanup_url_get;
145: }
1.14 millert 146: }
1.1 millert 147:
1.17 millert 148: if (outfile)
149: savefile = outfile;
1.1 millert 150: else
1.17 millert 151: savefile = basename(path);
152:
1.14 millert 153: if (EMPTYSTRING(savefile)) {
154: if (isftpurl)
155: goto noftpautologin;
156: warnx("Invalid URL (no file after directory): %s", origline);
1.6 millert 157: goto cleanup_url_get;
1.14 millert 158: }
1.1 millert 159:
160: if (proxyenv != NULL) { /* use proxy */
161: proxy = strdup(proxyenv);
162: if (proxy == NULL)
1.14 millert 163: errx(1, "Can't allocate memory for proxy URL.");
1.7 millert 164: if (strncasecmp(proxy, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
1.6 millert 165: host = proxy + sizeof(HTTP_URL) - 1;
1.7 millert 166: else if (strncasecmp(proxy, FTP_URL, sizeof(FTP_URL) - 1) == 0)
1.6 millert 167: host = proxy + sizeof(FTP_URL) - 1;
168: else {
1.14 millert 169: warnx("Malformed proxy URL: %s", proxyenv);
1.6 millert 170: goto cleanup_url_get;
171: }
1.14 millert 172: if (EMPTYSTRING(host)) {
173: warnx("Malformed proxy URL: %s", proxyenv);
1.6 millert 174: goto cleanup_url_get;
1.14 millert 175: }
1.1 millert 176: *--path = '/'; /* add / back to real path */
177: path = strchr(host, '/'); /* remove trailing / on host */
1.42 deraadt 178: if (!EMPTYSTRING(path))
1.1 millert 179: *path++ = '\0';
180: path = line;
181: }
182:
1.22 deraadt 183: if (isfileurl) {
184: struct stat st;
185:
186: s = open(path, O_RDONLY);
187: if (s == -1) {
188: warn("Can't open file %s", path);
189: goto cleanup_url_get;
190: }
191:
192: if (fstat(s, &st) == -1)
193: filesize = -1;
194: else
195: filesize = st.st_size;
196:
197: /* Open the output file. */
198: if (strcmp(savefile, "-") != 0) {
1.55 fgsch 199: out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
200: 0666);
1.22 deraadt 201: if (out < 0) {
202: warn("Can't open %s", savefile);
203: goto cleanup_url_get;
204: }
205: } else
206: out = fileno(stdout);
207:
208: /* Trap signals */
209: oldintr = NULL;
210: if (setjmp(httpabort)) {
211: if (oldintr)
212: (void)signal(SIGINT, oldintr);
213: goto cleanup_url_get;
214: }
215: oldintr = signal(SIGINT, abortfile);
1.42 deraadt 216:
1.22 deraadt 217: bytes = 0;
218: hashbytes = mark;
219: progressmeter(-1);
1.40 fgsch 220:
221: if ((buf = malloc(4096)) == NULL)
1.47 deraadt 222: errx(1, "Can't allocate memory for transfer buffer");
1.42 deraadt 223:
1.22 deraadt 224: /* Finally, suck down the file. */
225: i = 0;
1.40 fgsch 226: while ((len = read(s, buf, 4096)) > 0) {
1.22 deraadt 227: bytes += len;
228: for (cp = buf; len > 0; len -= i, cp += i) {
229: if ((i = write(out, cp, len)) == -1) {
230: warn("Writing %s", savefile);
231: goto cleanup_url_get;
232: }
233: else if (i == 0)
234: break;
235: }
236: if (hash && !progress) {
237: while (bytes >= hashbytes) {
238: (void)putc('#', ttyout);
239: hashbytes += mark;
240: }
241: (void)fflush(ttyout);
242: }
243: }
244: if (hash && !progress && bytes > 0) {
245: if (bytes < mark)
246: (void)putc('#', ttyout);
247: (void)putc('\n', ttyout);
248: (void)fflush(ttyout);
249: }
250: if (len != 0) {
251: warn("Reading from file");
252: goto cleanup_url_get;
253: }
254: progressmeter(1);
255: if (verbose)
256: fputs("Successfully retrieved file.\n", ttyout);
257: (void)signal(SIGINT, oldintr);
1.42 deraadt 258:
1.40 fgsch 259: rval = 0;
260: goto cleanup_url_get;
1.22 deraadt 261: }
262:
1.28 itojun 263: if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
264: (hosttail[1] == '\0' || hosttail[1] == ':')) {
265: host++;
266: *hosttail++ = '\0';
267: } else
268: hosttail = host;
269:
270: portnum = strrchr(hosttail, ':'); /* find portnum */
1.1 millert 271: if (portnum != NULL)
272: *portnum++ = '\0';
273:
274: if (debug)
1.10 deraadt 275: fprintf(ttyout, "host %s, port %s, path %s, save as %s.\n",
1.1 millert 276: host, portnum, path, savefile);
277:
1.25 itojun 278: memset(&hints, 0, sizeof(hints));
1.39 deraadt 279: hints.ai_family = family;
1.25 itojun 280: hints.ai_socktype = SOCK_STREAM;
281: port = portnum ? portnum : httpport;
282: error = getaddrinfo(host, port, &hints, &res0);
1.30 deraadt 283: if (error == EAI_SERVICE && port == httpport) {
284: /*
285: * If the services file is corrupt/missing, fall back
286: * on our hard-coded defines.
287: */
288: snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
289: error = getaddrinfo(host, pbuf, &hints, &res0);
290: }
1.25 itojun 291: if (error) {
292: warnx("%s: %s", gai_strerror(error), host);
293: goto cleanup_url_get;
1.1 millert 294: }
295:
1.25 itojun 296: s = -1;
297: for (res = res0; res; res = res->ai_next) {
1.44 itojun 298: if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
299: sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
300: strlcpy(hbuf, "(unknown)", sizeof(hbuf));
1.41 deraadt 301: if (verbose)
1.44 itojun 302: fprintf(ttyout, "Trying %s...\n", hbuf);
1.14 millert 303:
1.25 itojun 304: s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
305: if (s == -1) {
306: cause = "socket";
307: continue;
1.1 millert 308: }
309:
1.25 itojun 310: again:
311: if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
312: if (errno == EINTR)
313: goto again;
314: close(s);
315: s = -1;
316: cause = "connect";
1.19 deraadt 317: continue;
318: }
1.25 itojun 319:
1.29 itojun 320: /* get port in numeric */
321: if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
322: pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
323: port = pbuf;
324: else
325: port = NULL;
326:
1.25 itojun 327: break;
328: }
329: freeaddrinfo(res0);
330: if (s < 0) {
1.33 millert 331: warn("%s", cause);
1.6 millert 332: goto cleanup_url_get;
1.1 millert 333: }
334:
1.40 fgsch 335: fin = fdopen(s, "r+");
336:
1.55 fgsch 337: if (verbose)
338: fprintf(ttyout, "Requesting %s", origline);
1.1 millert 339: /*
1.40 fgsch 340: * Construct and send the request. Proxy requests don't want leading /.
1.1 millert 341: */
1.32 itojun 342: if (proxy) {
1.55 fgsch 343: if (verbose)
344: fprintf(ttyout, " (via %s)\n", proxyenv);
1.32 itojun 345: /*
346: * Host: directive must use the destination host address for
347: * the original URI (path). We do not attach it at this moment.
348: */
1.55 fgsch 349: fprintf(fin, "GET %s HTTP/1.0\r\n%s\r\n\r\n", path,
350: HTTP_USER_AGENT);
1.28 itojun 351: } else {
1.56 ! fgsch 352: fprintf(fin, "GET /%s HTTP/1.0\r\nHost: ", path);
1.32 itojun 353: if (strchr(host, ':')) {
354: char *h, *p;
355:
1.55 fgsch 356: /*
357: * strip off scoped address portion, since it's
358: * local to node
359: */
1.32 itojun 360: h = strdup(host);
361: if (h == NULL)
362: errx(1, "Can't allocate memory.");
363: if ((p = strchr(h, '%')) != NULL)
364: *p = '\0';
1.55 fgsch 365: fprintf(fin, "[%s]", h);
1.32 itojun 366: free(h);
1.55 fgsch 367: } else
368: fprintf(fin, "%s", host);
369:
370: /*
371: * Send port number only if it's specified and does not equal
372: * 80. Some broken HTTP servers get confused if you explicitly
373: * send them the port number.
374: */
375: if (port && strcmp(port, "80") != 0)
376: fprintf(fin, ":%s", port);
1.56 ! fgsch 377: fprintf(fin, "\r\n%s\r\n\r\n", HTTP_USER_AGENT);
1.55 fgsch 378: if (verbose)
379: fprintf(ttyout, "\n");
1.28 itojun 380: }
1.40 fgsch 381: if (fflush(fin) == EOF) {
1.14 millert 382: warn("Writing HTTP request");
1.6 millert 383: goto cleanup_url_get;
1.1 millert 384: }
1.40 fgsch 385:
386: if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
387: warn("Receiving HTTP reply");
388: goto cleanup_url_get;
1.1 millert 389: }
1.40 fgsch 390:
391: while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
392: buf[--len] = '\0';
393: if (debug)
394: fprintf(ttyout, "received '%s'\n", buf);
395:
1.1 millert 396: cp = strchr(buf, ' ');
397: if (cp == NULL)
398: goto improper;
399: else
400: cp++;
1.40 fgsch 401: if (strncmp(cp, "301", 3) == 0 || strncmp(cp, "302", 3) == 0) {
402: isredirect++;
1.54 fgsch 403: if (redirect_loop++ > 10) {
404: warnx("Too many redirections requested");
405: goto cleanup_url_get;
406: }
1.40 fgsch 407: } else if (strncmp(cp, "200", 3)) {
1.1 millert 408: warnx("Error retrieving file: %s", cp);
1.6 millert 409: goto cleanup_url_get;
1.1 millert 410: }
411:
412: /*
413: * Read the rest of the header.
414: */
1.40 fgsch 415: free(buf);
416: filesize = -1;
417:
418: while (1) {
419: if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
420: warn("Receiving HTTP reply");
421: goto cleanup_url_get;
422: }
423: while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
424: buf[--len] = '\0';
425: if (len == 0)
1.1 millert 426: break;
1.40 fgsch 427: if (debug)
428: fprintf(ttyout, "received '%s'\n", buf);
1.1 millert 429:
1.40 fgsch 430: /* Look for some headers */
431: cp = buf;
1.1 millert 432: #define CONTENTLEN "Content-Length: "
1.40 fgsch 433: if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
434: cp += sizeof(CONTENTLEN) - 1;
435: filesize = strtol(cp, &ep, 10);
436: if (filesize < 1 || *ep != '\0')
437: goto improper;
438: #define LOCATION "Location: "
439: } else if (isredirect &&
440: strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
441: cp += sizeof(LOCATION) - 1;
442: if (verbose)
443: fprintf(ttyout, "Redirected to %s\n", cp);
444: if (fin != NULL)
445: fclose(fin);
446: else if (s != -1)
447: close(s);
448: if (proxy)
449: free(proxy);
450: free(line);
451: rval = url_get(cp, proxyenv, outfile);
452: if (buf)
453: free(buf);
454: return (rval);
455: }
1.1 millert 456: }
457:
1.17 millert 458: /* Open the output file. */
459: if (strcmp(savefile, "-") != 0) {
1.10 deraadt 460: out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
461: if (out < 0) {
462: warn("Can't open %s", savefile);
463: goto cleanup_url_get;
464: }
465: } else
1.17 millert 466: out = fileno(stdout);
1.1 millert 467:
468: /* Trap signals */
469: oldintr = NULL;
470: if (setjmp(httpabort)) {
471: if (oldintr)
1.2 millert 472: (void)signal(SIGINT, oldintr);
1.6 millert 473: goto cleanup_url_get;
1.1 millert 474: }
475: oldintr = signal(SIGINT, aborthttp);
476:
477: bytes = 0;
478: hashbytes = mark;
479: progressmeter(-1);
1.43 millert 480:
481: free(buf);
1.1 millert 482:
483: /* Finally, suck down the file. */
1.40 fgsch 484: if ((buf = malloc(4096)) == NULL)
1.47 deraadt 485: errx(1, "Can't allocate memory for transfer buffer");
1.1 millert 486: i = 0;
1.40 fgsch 487: while ((len = fread(buf, sizeof(char), 4096, fin)) > 0) {
1.1 millert 488: bytes += len;
489: for (cp = buf; len > 0; len -= i, cp += i) {
490: if ((i = write(out, cp, len)) == -1) {
491: warn("Writing %s", savefile);
1.6 millert 492: goto cleanup_url_get;
1.1 millert 493: }
494: else if (i == 0)
495: break;
496: }
497: if (hash && !progress) {
498: while (bytes >= hashbytes) {
1.10 deraadt 499: (void)putc('#', ttyout);
1.1 millert 500: hashbytes += mark;
501: }
1.10 deraadt 502: (void)fflush(ttyout);
1.1 millert 503: }
504: }
505: if (hash && !progress && bytes > 0) {
506: if (bytes < mark)
1.10 deraadt 507: (void)putc('#', ttyout);
508: (void)putc('\n', ttyout);
509: (void)fflush(ttyout);
1.1 millert 510: }
511: if (len != 0) {
512: warn("Reading from socket");
1.6 millert 513: goto cleanup_url_get;
1.1 millert 514: }
515: progressmeter(1);
1.24 deraadt 516: if (filesize != -1 && len == 0 && bytes != filesize) {
517: if (verbose)
518: fputs("Read short file.\n", ttyout);
519: goto cleanup_url_get;
520: }
521:
1.1 millert 522: if (verbose)
1.10 deraadt 523: fputs("Successfully retrieved file.\n", ttyout);
1.2 millert 524: (void)signal(SIGINT, oldintr);
1.1 millert 525:
1.40 fgsch 526: rval = 0;
527: goto cleanup_url_get;
1.1 millert 528:
1.14 millert 529: noftpautologin:
530: warnx(
531: "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
532: goto cleanup_url_get;
533:
1.1 millert 534: improper:
1.8 millert 535: warnx("Improper response from %s", host);
1.14 millert 536:
1.6 millert 537: cleanup_url_get:
1.40 fgsch 538: if (fin != NULL)
539: fclose(fin);
540: else if (s != -1)
1.1 millert 541: close(s);
1.40 fgsch 542: if (buf)
543: free(buf);
1.1 millert 544: if (proxy)
545: free(proxy);
1.14 millert 546: free(line);
1.40 fgsch 547: return (rval);
1.1 millert 548: }
549:
550: /*
551: * Abort a http retrieval
552: */
1.51 deraadt 553: /* ARGSUSED */
1.1 millert 554: void
1.51 deraadt 555: aborthttp(int signo)
1.1 millert 556: {
557:
558: alarmtimer(0);
1.10 deraadt 559: fputs("\nhttp fetch aborted.\n", ttyout);
560: (void)fflush(ttyout);
1.1 millert 561: longjmp(httpabort, 1);
562: }
563:
564: /*
1.22 deraadt 565: * Abort a http retrieval
566: */
1.51 deraadt 567: /* ARGSUSED */
1.22 deraadt 568: void
1.51 deraadt 569: abortfile(int signo)
1.22 deraadt 570: {
571:
572: alarmtimer(0);
573: fputs("\nfile fetch aborted.\n", ttyout);
574: (void)fflush(ttyout);
575: longjmp(httpabort, 1);
576: }
577:
578: /*
1.1 millert 579: * Retrieve multiple files from the command line, transferring
580: * files of the form "host:path", "ftp://host/path" using the
581: * ftp protocol, and files of the form "http://host/path" using
582: * the http protocol.
1.2 millert 583: * If path has a trailing "/", then return (-1);
1.1 millert 584: * the path will be cd-ed into and the connection remains open,
585: * and the function will return -1 (to indicate the connection
586: * is alive).
587: * If an error occurs the return value will be the offset+1 in
588: * argv[] of the file that caused a problem (i.e, argv[x]
589: * returns x+1)
590: * Otherwise, 0 is returned if all files retrieved successfully.
591: */
592: int
1.50 deraadt 593: auto_fetch(int argc, char *argv[], char *outfile)
1.1 millert 594: {
595: char *xargv[5];
596: char *cp, *line, *host, *dir, *file, *portnum;
1.52 henning 597: char *user, *pass, *pathstart;
1.6 millert 598: char *ftpproxy, *httpproxy;
1.14 millert 599: int rval, xargc;
600: volatile int argpos;
1.49 krw 601: int dirhasglob, filehasglob, oautologin;
1.14 millert 602: char rempath[MAXPATHLEN];
1.1 millert 603:
604: argpos = 0;
605:
606: if (setjmp(toplevel)) {
607: if (connected)
608: disconnect(0, NULL);
1.2 millert 609: return (argpos + 1);
1.1 millert 610: }
1.3 millert 611: (void)signal(SIGINT, (sig_t)intr);
612: (void)signal(SIGPIPE, (sig_t)lostpeer);
1.1 millert 613:
1.45 millert 614: if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
615: ftpproxy = NULL;
616: if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
617: httpproxy = NULL;
1.6 millert 618:
1.1 millert 619: /*
620: * Loop through as long as there's files to fetch.
621: */
622: for (rval = 0; (rval == 0) && (argpos < argc); free(line), argpos++) {
623: if (strchr(argv[argpos], ':') == NULL)
624: break;
1.8 millert 625: host = dir = file = portnum = user = pass = NULL;
1.1 millert 626:
627: /*
628: * We muck with the string, so we make a copy.
629: */
630: line = strdup(argv[argpos]);
631: if (line == NULL)
632: errx(1, "Can't allocate memory for auto-fetch.");
633:
634: /*
635: * Try HTTP URL-style arguments first.
636: */
1.22 deraadt 637: if (strncasecmp(line, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
638: strncasecmp(line, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
1.54 fgsch 639: redirect_loop = 0;
1.17 millert 640: if (url_get(line, httpproxy, outfile) == -1)
1.1 millert 641: rval = argpos + 1;
642: continue;
643: }
644:
645: /*
1.6 millert 646: * Try FTP URL-style arguments next. If ftpproxy is
647: * set, use url_get() instead of standard ftp.
648: * Finally, try host:file.
1.1 millert 649: */
650: host = line;
1.7 millert 651: if (strncasecmp(line, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1.37 heko 652: char *passend, *passagain, *userend;
1.31 itojun 653:
1.6 millert 654: if (ftpproxy) {
1.17 millert 655: if (url_get(line, ftpproxy, outfile) == -1)
1.6 millert 656: rval = argpos + 1;
657: continue;
658: }
1.1 millert 659: host += sizeof(FTP_URL) - 1;
1.8 millert 660: dir = strchr(host, '/');
1.1 millert 661:
1.8 millert 662: /* Look for [user:pass@]host[:port] */
1.31 itojun 663:
664: /* check if we have "user:pass@" */
1.37 heko 665: userend = strchr(host, ':');
1.31 itojun 666: passend = strchr(host, '@');
667: if (passend && userend && userend < passend &&
668: (!dir || passend < dir)) {
669: user = host;
670: pass = userend + 1;
671: host = passend + 1;
672: *userend = *passend = '\0';
1.37 heko 673: passagain = strchr(host, '@');
1.42 deraadt 674: if (strchr(pass, '@') != NULL ||
1.37 heko 675: (passagain != NULL && passagain < dir)) {
676: warnx(at_encoding_warning);
677: goto bad_ftp_url;
1.42 deraadt 678: }
1.31 itojun 679:
680: if (EMPTYSTRING(user) || EMPTYSTRING(pass)) {
1.11 millert 681: bad_ftp_url:
1.31 itojun 682: warnx("Invalid URL: %s", argv[argpos]);
683: rval = argpos + 1;
684: continue;
685: }
1.37 heko 686: user = urldecode(user);
687: pass = urldecode(pass);
1.8 millert 688: }
1.31 itojun 689:
690: #ifdef INET6
691: /* check [host]:port, or [host] */
692: if (host[0] == '[') {
693: cp = strchr(host, ']');
694: if (cp && (!dir || cp < dir)) {
695: if (cp + 1 == dir || cp[1] == ':') {
696: host++;
697: *cp++ = '\0';
698: } else
699: cp = NULL;
700: } else
701: cp = host;
1.25 itojun 702: } else
703: cp = host;
1.31 itojun 704: #else
705: cp = host;
1.25 itojun 706: #endif
1.31 itojun 707:
708: /* split off host[:port] if there is */
709: if (cp) {
710: portnum = strchr(cp, ':');
1.52 henning 711: pathstart = strchr(cp, '/');
712: /* : in path is not a port # indicator */
713: if (portnum && pathstart &&
714: pathstart < portnum)
715: portnum = NULL;
716:
1.31 itojun 717: if (!portnum)
718: ;
719: else {
720: if (!dir)
721: ;
722: else if (portnum + 1 < dir) {
723: *portnum++ = '\0';
724: /*
725: * XXX should check if portnum
726: * is decimal number
727: */
728: } else {
729: /* empty portnum */
730: goto bad_ftp_url;
731: }
732: }
733: } else
734: portnum = NULL;
1.8 millert 735: } else { /* classic style `host:file' */
736: dir = strchr(host, ':');
737: }
1.1 millert 738: if (EMPTYSTRING(host)) {
739: rval = argpos + 1;
740: continue;
741: }
742:
743: /*
1.9 millert 744: * If dir is NULL, the file wasn't specified
1.1 millert 745: * (URL looked something like ftp://host)
746: */
1.8 millert 747: if (dir != NULL)
748: *dir++ = '\0';
1.1 millert 749:
750: /*
751: * Extract the file and (if present) directory name.
752: */
1.42 deraadt 753: if (!EMPTYSTRING(dir)) {
1.8 millert 754: cp = strrchr(dir, '/');
1.1 millert 755: if (cp != NULL) {
756: *cp++ = '\0';
757: file = cp;
758: } else {
759: file = dir;
760: dir = NULL;
761: }
762: }
763: if (debug)
1.42 deraadt 764: fprintf(ttyout,
765: "user %s:%s host %s port %s dir %s file %s\n",
1.8 millert 766: user, pass, host, portnum, dir, file);
1.1 millert 767:
768: /*
1.49 krw 769: * Set up the connection.
1.1 millert 770: */
1.49 krw 771: if (connected)
772: disconnect(0, NULL);
773: xargv[0] = __progname;
774: xargv[1] = host;
1.8 millert 775: xargv[2] = NULL;
1.49 krw 776: xargc = 2;
777: if (!EMPTYSTRING(portnum)) {
778: xargv[2] = portnum;
779: xargv[3] = NULL;
780: xargc = 3;
781: }
782: oautologin = autologin;
783: if (user != NULL)
784: autologin = 0;
785: setpeer(xargc, xargv);
786: autologin = oautologin;
787: if ((connected == 0) ||
788: ((connected == 1) && !ftp_login(host, user, pass))) {
789: warnx("Can't connect or login to host `%s'", host);
1.8 millert 790: rval = argpos + 1;
791: continue;
1.1 millert 792: }
1.49 krw 793:
794: /* Always use binary transfers. */
795: setbinary(0, NULL);
1.1 millert 796:
1.4 millert 797: dirhasglob = filehasglob = 0;
798: if (doglob) {
1.42 deraadt 799: if (!EMPTYSTRING(dir) &&
1.4 millert 800: strpbrk(dir, "*?[]{}") != NULL)
801: dirhasglob = 1;
1.42 deraadt 802: if (!EMPTYSTRING(file) &&
1.4 millert 803: strpbrk(file, "*?[]{}") != NULL)
804: filehasglob = 1;
805: }
806:
1.1 millert 807: /* Change directories, if necessary. */
1.42 deraadt 808: if (!EMPTYSTRING(dir) && !dirhasglob) {
1.1 millert 809: xargv[0] = "cd";
810: xargv[1] = dir;
811: xargv[2] = NULL;
812: cd(2, xargv);
1.42 deraadt 813: if (!dirchange) {
1.1 millert 814: rval = argpos + 1;
815: continue;
816: }
817: }
818:
819: if (EMPTYSTRING(file)) {
820: rval = -1;
821: continue;
822: }
823:
1.21 marc 824: if (verbose)
1.10 deraadt 825: fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1.1 millert 826:
1.4 millert 827: if (dirhasglob) {
828: snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
829: file = rempath;
830: }
831:
832: /* Fetch the file(s). */
1.10 deraadt 833: xargc = 2;
1.1 millert 834: xargv[0] = "get";
835: xargv[1] = file;
836: xargv[2] = NULL;
1.4 millert 837: if (dirhasglob || filehasglob) {
838: int ointeractive;
839:
840: ointeractive = interactive;
841: interactive = 0;
842: xargv[0] = "mget";
1.10 deraadt 843: mget(xargc, xargv);
1.5 millert 844: interactive = ointeractive;
1.10 deraadt 845: } else {
1.17 millert 846: if (outfile != NULL) {
847: xargv[2] = outfile;
848: xargv[3] = NULL;
1.10 deraadt 849: xargc++;
850: }
851: get(xargc, xargv);
852: }
1.1 millert 853:
1.4 millert 854: if ((code / 100) != COMPLETE)
1.1 millert 855: rval = argpos + 1;
856: }
857: if (connected && rval != -1)
858: disconnect(0, NULL);
859: return (rval);
1.37 heko 860: }
861:
862: char *
1.50 deraadt 863: urldecode(const char *str)
1.37 heko 864: {
1.53 deraadt 865: char *ret, c;
866: int i, reallen;
1.37 heko 867:
1.53 deraadt 868: if (str == NULL)
869: return NULL;
870: if ((ret = malloc(strlen(str)+1)) == NULL)
871: err(1, "Can't allocate memory for URL decoding");
872: for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
873: c = str[i];
874: if (c == '+') {
875: *ret = ' ';
876: continue;
877: }
878: /* Can't use strtol here because next char after %xx may be
879: * a digit. */
880: if (c == '%' && isxdigit(str[i+1]) && isxdigit(str[i+2])) {
881: *ret = hextochar(&str[i+1]);
882: i+=2;
883: continue;
884: }
885: *ret = c;
886: }
887: *ret = '\0';
888:
889: return ret-reallen;
1.37 heko 890: }
891:
892: char
1.50 deraadt 893: hextochar(const char *str)
1.37 heko 894: {
1.53 deraadt 895: char c, ret;
1.37 heko 896:
1.53 deraadt 897: c = str[0];
898: ret = c;
899: if (isalpha(c))
900: ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
901: else
902: ret -= '0';
903: ret *= 16;
904:
905: c = str[1];
906: ret += c;
907: if (isalpha(c))
908: ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
909: else
910: ret -= '0';
911: return ret;
1.25 itojun 912: }
913:
914: int
1.50 deraadt 915: isurl(const char *p)
1.25 itojun 916: {
1.27 millert 917:
1.26 deraadt 918: if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
919: strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1.27 millert 920: strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
921: strstr(p, ":/"))
922: return (1);
923: return (0);
1.1 millert 924: }