fetch.c revision 1.147 1 /* $NetBSD: fetch.c,v 1.147 2004/06/06 01:37:41 christos Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2003 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Luke Mewburn.
9 *
10 * This code is derived from software contributed to The NetBSD Foundation
11 * by Scott Aaron Bamford.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement:
23 * This product includes software developed by the NetBSD
24 * Foundation, Inc. and its contributors.
25 * 4. Neither the name of The NetBSD Foundation nor the names of its
26 * contributors may be used to endorse or promote products derived
27 * from this software without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
30 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
31 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
33 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 * POSSIBILITY OF SUCH DAMAGE.
40 */
41
42 #include <sys/cdefs.h>
43 #ifndef lint
44 __RCSID("$NetBSD: fetch.c,v 1.147 2004/06/06 01:37:41 christos Exp $");
45 #endif /* not lint */
46
47 /*
48 * FTP User Program -- Command line file retrieval
49 */
50
51 #include <sys/types.h>
52 #include <sys/param.h>
53 #include <sys/socket.h>
54 #include <sys/stat.h>
55 #include <sys/time.h>
56
57 #include <netinet/in.h>
58
59 #include <arpa/ftp.h>
60 #include <arpa/inet.h>
61
62 #include <ctype.h>
63 #include <err.h>
64 #include <errno.h>
65 #include <netdb.h>
66 #include <fcntl.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <unistd.h>
71 #include <time.h>
72
73 #include "ftp_var.h"
74 #include "version.h"
75
76 typedef enum {
77 UNKNOWN_URL_T=-1,
78 HTTP_URL_T,
79 FTP_URL_T,
80 FILE_URL_T,
81 CLASSIC_URL_T
82 } url_t;
83
84 void aborthttp(int);
85 #ifndef NO_AUTH
86 static int auth_url(const char *, char **, const char *, const char *);
87 static void base64_encode(const u_char *, size_t, u_char *);
88 #endif
89 static int go_fetch(const char *);
90 static int fetch_ftp(const char *);
91 static int fetch_url(const char *, const char *, char *, char *);
92 static int parse_url(const char *, const char *, url_t *, char **,
93 char **, char **, char **, in_port_t *, char **);
94 static void url_decode(char *);
95
96 static int redirect_loop;
97
98
99 #define ABOUT_URL "about:" /* propaganda */
100 #define FILE_URL "file://" /* file URL prefix */
101 #define FTP_URL "ftp://" /* ftp URL prefix */
102 #define HTTP_URL "http://" /* http URL prefix */
103
104
105 #ifndef NO_AUTH
106 /*
107 * Generate authorization response based on given authentication challenge.
108 * Returns -1 if an error occurred, otherwise 0.
109 * Sets response to a malloc(3)ed string; caller should free.
110 */
111 static int
112 auth_url(const char *challenge, char **response, const char *guser,
113 const char *gpass)
114 {
115 char *cp, *ep, *clear, *line, *realm, *scheme;
116 char user[BUFSIZ], *pass;
117 int rval;
118 size_t len, clen, rlen;
119
120 *response = NULL;
121 clear = realm = scheme = NULL;
122 rval = -1;
123 line = xstrdup(challenge);
124 cp = line;
125
126 if (debug)
127 fprintf(ttyout, "auth_url: challenge `%s'\n", challenge);
128
129 scheme = strsep(&cp, " ");
130 #define SCHEME_BASIC "Basic"
131 if (strncasecmp(scheme, SCHEME_BASIC, sizeof(SCHEME_BASIC) - 1) != 0) {
132 warnx("Unsupported WWW Authentication challenge - `%s'",
133 challenge);
134 goto cleanup_auth_url;
135 }
136 cp += strspn(cp, " ");
137
138 #define REALM "realm=\""
139 if (strncasecmp(cp, REALM, sizeof(REALM) - 1) == 0)
140 cp += sizeof(REALM) - 1;
141 else {
142 warnx("Unsupported WWW Authentication challenge - `%s'",
143 challenge);
144 goto cleanup_auth_url;
145 }
146 if ((ep = strchr(cp, '\"')) != NULL) {
147 size_t len = ep - cp;
148
149 realm = (char *)xmalloc(len + 1);
150 (void)strlcpy(realm, cp, len + 1);
151 } else {
152 warnx("Unsupported WWW Authentication challenge - `%s'",
153 challenge);
154 goto cleanup_auth_url;
155 }
156
157 if (guser != NULL)
158 (void)strlcpy(user, guser, sizeof(user));
159 else {
160 fprintf(ttyout, "Username for `%s': ", realm);
161 (void)fflush(ttyout);
162 if (fgets(user, sizeof(user) - 1, stdin) == NULL) {
163 clearerr(stdin);
164 goto cleanup_auth_url;
165 }
166 user[strlen(user) - 1] = '\0';
167 }
168 if (gpass != NULL)
169 pass = (char *)gpass;
170 else
171 pass = getpass("Password: ");
172
173 clen = strlen(user) + strlen(pass) + 2; /* user + ":" + pass + "\0" */
174 clear = (char *)xmalloc(clen);
175 (void)strlcpy(clear, user, clen);
176 (void)strlcat(clear, ":", clen);
177 (void)strlcat(clear, pass, clen);
178 if (gpass == NULL)
179 memset(pass, 0, strlen(pass));
180
181 /* scheme + " " + enc + "\0" */
182 rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
183 *response = (char *)xmalloc(rlen);
184 (void)strlcpy(*response, scheme, rlen);
185 len = strlcat(*response, " ", rlen);
186 base64_encode(clear, clen, (u_char *)*response + len);
187 memset(clear, 0, clen);
188 rval = 0;
189
190 cleanup_auth_url:
191 FREEPTR(clear);
192 FREEPTR(line);
193 FREEPTR(realm);
194 return (rval);
195 }
196
197 /*
198 * Encode len bytes starting at clear using base64 encoding into encoded,
199 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
200 */
201 static void
202 base64_encode(const u_char *clear, size_t len, u_char *encoded)
203 {
204 static const u_char enc[] =
205 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
206 u_char *cp;
207 int i;
208
209 cp = encoded;
210 for (i = 0; i < len; i += 3) {
211 *(cp++) = enc[((clear[i + 0] >> 2))];
212 *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
213 | ((clear[i + 1] >> 4) & 0x0f)];
214 *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
215 | ((clear[i + 2] >> 6) & 0x03)];
216 *(cp++) = enc[((clear[i + 2] ) & 0x3f)];
217 }
218 *cp = '\0';
219 while (i-- > len)
220 *(--cp) = '=';
221 }
222 #endif
223
224 /*
225 * Decode %xx escapes in given string, `in-place'.
226 */
227 static void
228 url_decode(char *url)
229 {
230 unsigned char *p, *q;
231
232 if (EMPTYSTRING(url))
233 return;
234 p = q = (unsigned char *)url;
235
236 #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
237 while (*p) {
238 if (p[0] == '%'
239 && p[1] && isxdigit((unsigned char)p[1])
240 && p[2] && isxdigit((unsigned char)p[2])) {
241 *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
242 p+=3;
243 } else
244 *q++ = *p++;
245 }
246 *q = '\0';
247 }
248
249
250 /*
251 * Parse URL of form:
252 * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
253 * Returns -1 if a parse error occurred, otherwise 0.
254 * It's the caller's responsibility to url_decode() the returned
255 * user, pass and path.
256 *
257 * Sets type to url_t, each of the given char ** pointers to a
258 * malloc(3)ed strings of the relevant section, and port to
259 * the number given, or ftpport if ftp://, or httpport if http://.
260 *
261 * If <host> is surrounded by `[' and ']', it's parsed as an
262 * IPv6 address (as per RFC 2732).
263 *
264 * XXX: this is not totally RFC 1738 compliant; <path> will have the
265 * leading `/' unless it's an ftp:// URL, as this makes things easier
266 * for file:// and http:// URLs. ftp:// URLs have the `/' between the
267 * host and the URL-path removed, but any additional leading slashes
268 * in the URL-path are retained (because they imply that we should
269 * later do "CWD" with a null argument).
270 *
271 * Examples:
272 * input URL output path
273 * --------- -----------
274 * "ftp://host" NULL
275 * "http://host/" NULL
276 * "file://host/dir/file" "dir/file"
277 * "ftp://host/" ""
278 * "ftp://host//" NULL
279 * "ftp://host//dir/file" "/dir/file"
280 */
281 static int
282 parse_url(const char *url, const char *desc, url_t *type,
283 char **user, char **pass, char **host, char **port,
284 in_port_t *portnum, char **path)
285 {
286 const char *origurl;
287 char *cp, *ep, *thost, *tport;
288 size_t len;
289
290 if (url == NULL || desc == NULL || type == NULL || user == NULL
291 || pass == NULL || host == NULL || port == NULL || portnum == NULL
292 || path == NULL)
293 errx(1, "parse_url: invoked with NULL argument!");
294
295 origurl = url;
296 *type = UNKNOWN_URL_T;
297 *user = *pass = *host = *port = *path = NULL;
298 *portnum = 0;
299 tport = NULL;
300
301 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
302 url += sizeof(HTTP_URL) - 1;
303 *type = HTTP_URL_T;
304 *portnum = HTTP_PORT;
305 tport = httpport;
306 } else if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
307 url += sizeof(FTP_URL) - 1;
308 *type = FTP_URL_T;
309 *portnum = FTP_PORT;
310 tport = ftpport;
311 } else if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
312 url += sizeof(FILE_URL) - 1;
313 *type = FILE_URL_T;
314 } else {
315 warnx("Invalid %s `%s'", desc, url);
316 cleanup_parse_url:
317 FREEPTR(*user);
318 FREEPTR(*pass);
319 FREEPTR(*host);
320 FREEPTR(*port);
321 FREEPTR(*path);
322 return (-1);
323 }
324
325 if (*url == '\0')
326 return (0);
327
328 /* find [user[:pass]@]host[:port] */
329 ep = strchr(url, '/');
330 if (ep == NULL)
331 thost = xstrdup(url);
332 else {
333 len = ep - url;
334 thost = (char *)xmalloc(len + 1);
335 (void)strlcpy(thost, url, len + 1);
336 if (*type == FTP_URL_T) /* skip first / for ftp URLs */
337 ep++;
338 *path = xstrdup(ep);
339 }
340
341 cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */
342 if (cp != NULL) {
343 if (*type == FTP_URL_T)
344 anonftp = 0; /* disable anonftp */
345 *user = thost;
346 *cp = '\0';
347 thost = xstrdup(cp + 1);
348 cp = strchr(*user, ':');
349 if (cp != NULL) {
350 *cp = '\0';
351 *pass = xstrdup(cp + 1);
352 }
353 }
354
355 #ifdef INET6
356 /*
357 * Check if thost is an encoded IPv6 address, as per
358 * RFC 2732:
359 * `[' ipv6-address ']'
360 */
361 if (*thost == '[') {
362 cp = thost + 1;
363 if ((ep = strchr(cp, ']')) == NULL ||
364 (ep[1] != '\0' && ep[1] != ':')) {
365 warnx("Invalid address `%s' in %s `%s'",
366 thost, desc, origurl);
367 goto cleanup_parse_url;
368 }
369 len = ep - cp; /* change `[xyz]' -> `xyz' */
370 memmove(thost, thost + 1, len);
371 thost[len] = '\0';
372 if (! isipv6addr(thost)) {
373 warnx("Invalid IPv6 address `%s' in %s `%s'",
374 thost, desc, origurl);
375 goto cleanup_parse_url;
376 }
377 cp = ep + 1;
378 if (*cp == ':')
379 cp++;
380 else
381 cp = NULL;
382 } else
383 #endif /* INET6 */
384 if ((cp = strchr(thost, ':')) != NULL)
385 *cp++ = '\0';
386 *host = thost;
387
388 /* look for [:port] */
389 if (cp != NULL) {
390 long nport;
391
392 nport = parseport(cp, -1);
393 if (nport == -1) {
394 warnx("Unknown port `%s' in %s `%s'",
395 cp, desc, origurl);
396 goto cleanup_parse_url;
397 }
398 *portnum = nport;
399 tport = cp;
400 }
401
402 if (tport != NULL)
403 *port = xstrdup(tport);
404 if (*path == NULL)
405 *path = xstrdup("/");
406
407 if (debug)
408 fprintf(ttyout,
409 "parse_url: user `%s' pass `%s' host %s port %s(%d) "
410 "path `%s'\n",
411 *user ? *user : "<null>", *pass ? *pass : "<null>",
412 *host ? *host : "<null>", *port ? *port : "<null>",
413 *portnum ? *portnum : -1, *path ? *path : "<null>");
414
415 return (0);
416 }
417
418 sigjmp_buf httpabort;
419
420 /*
421 * Retrieve URL, via a proxy if necessary, using HTTP.
422 * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
423 * http_proxy as appropriate.
424 * Supports HTTP redirects.
425 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
426 * is still open (e.g, ftp xfer with trailing /)
427 */
428 static int
429 fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
430 {
431 struct addrinfo hints, *res, *res0 = NULL;
432 int error;
433 char hbuf[NI_MAXHOST];
434 volatile sigfunc oldintr, oldintp;
435 volatile int s;
436 struct stat sb;
437 int ischunked, isproxy, rval, hcode;
438 size_t len;
439 static size_t bufsize;
440 static char *xferbuf;
441 char *cp, *ep, *buf, *savefile;
442 char *auth, *location, *message;
443 char *user, *pass, *host, *port, *path, *decodedpath;
444 char *puser, *ppass, *useragent;
445 off_t hashbytes, rangestart, rangeend, entitylen;
446 int (*closefunc)(FILE *);
447 FILE *fin, *fout;
448 time_t mtime;
449 url_t urltype;
450 in_port_t portnum;
451
452 oldintr = oldintp = NULL;
453 closefunc = NULL;
454 fin = fout = NULL;
455 s = -1;
456 buf = savefile = NULL;
457 auth = location = message = NULL;
458 ischunked = isproxy = hcode = 0;
459 rval = 1;
460 user = pass = host = path = decodedpath = puser = ppass = NULL;
461
462 #ifdef __GNUC__ /* shut up gcc warnings */
463 (void)&closefunc;
464 (void)&fin;
465 (void)&fout;
466 (void)&buf;
467 (void)&savefile;
468 (void)&rval;
469 (void)&isproxy;
470 (void)&hcode;
471 (void)&ischunked;
472 (void)&message;
473 (void)&location;
474 (void)&auth;
475 (void)&decodedpath;
476 #endif
477
478 if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port,
479 &portnum, &path) == -1)
480 goto cleanup_fetch_url;
481
482 if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
483 && strcasecmp(host, "localhost") != 0) {
484 warnx("No support for non local file URL `%s'", url);
485 goto cleanup_fetch_url;
486 }
487
488 if (EMPTYSTRING(path)) {
489 if (urltype == FTP_URL_T) {
490 rval = fetch_ftp(url);
491 goto cleanup_fetch_url;
492 }
493 if (urltype != HTTP_URL_T || outfile == NULL) {
494 warnx("Invalid URL (no file after host) `%s'", url);
495 goto cleanup_fetch_url;
496 }
497 }
498
499 decodedpath = xstrdup(path);
500 url_decode(decodedpath);
501
502 if (outfile)
503 savefile = xstrdup(outfile);
504 else {
505 cp = strrchr(decodedpath, '/'); /* find savefile */
506 if (cp != NULL)
507 savefile = xstrdup(cp + 1);
508 else
509 savefile = xstrdup(decodedpath);
510 }
511 if (EMPTYSTRING(savefile)) {
512 if (urltype == FTP_URL_T) {
513 rval = fetch_ftp(url);
514 goto cleanup_fetch_url;
515 }
516 warnx("no file after directory (you must specify an "
517 "output file) `%s'", url);
518 goto cleanup_fetch_url;
519 } else {
520 if (debug)
521 fprintf(ttyout, "got savefile as `%s'\n", savefile);
522 }
523
524 restart_point = 0;
525 filesize = -1;
526 rangestart = rangeend = entitylen = -1;
527 mtime = -1;
528 if (restartautofetch) {
529 if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
530 stat(savefile, &sb) == 0)
531 restart_point = sb.st_size;
532 }
533 if (urltype == FILE_URL_T) { /* file:// URLs */
534 direction = "copied";
535 fin = fopen(decodedpath, "r");
536 if (fin == NULL) {
537 warn("Cannot open file `%s'", decodedpath);
538 goto cleanup_fetch_url;
539 }
540 if (fstat(fileno(fin), &sb) == 0) {
541 mtime = sb.st_mtime;
542 filesize = sb.st_size;
543 }
544 if (restart_point) {
545 if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
546 warn("Can't lseek to restart `%s'",
547 decodedpath);
548 goto cleanup_fetch_url;
549 }
550 }
551 if (verbose) {
552 fprintf(ttyout, "Copying %s", decodedpath);
553 if (restart_point)
554 fprintf(ttyout, " (restarting at " LLF ")",
555 (LLT)restart_point);
556 fputs("\n", ttyout);
557 }
558 } else { /* ftp:// or http:// URLs */
559 char *leading;
560 int hasleading;
561
562 if (proxyenv == NULL) {
563 if (urltype == HTTP_URL_T)
564 proxyenv = getoptionvalue("http_proxy");
565 else if (urltype == FTP_URL_T)
566 proxyenv = getoptionvalue("ftp_proxy");
567 }
568 direction = "retrieved";
569 if (! EMPTYSTRING(proxyenv)) { /* use proxy */
570 url_t purltype;
571 char *phost, *ppath;
572 char *pport, *no_proxy;
573
574 isproxy = 1;
575
576 /* check URL against list of no_proxied sites */
577 no_proxy = getoptionvalue("no_proxy");
578 if (! EMPTYSTRING(no_proxy)) {
579 char *np, *np_copy;
580 long np_port;
581 size_t hlen, plen;
582
583 np_copy = xstrdup(no_proxy);
584 hlen = strlen(host);
585 while ((cp = strsep(&np_copy, " ,")) != NULL) {
586 if (*cp == '\0')
587 continue;
588 if ((np = strrchr(cp, ':')) != NULL) {
589 *np = '\0';
590 np_port =
591 strtol(np + 1, &ep, 10);
592 if (*ep != '\0')
593 continue;
594 if (np_port != portnum)
595 continue;
596 }
597 plen = strlen(cp);
598 if (hlen < plen)
599 continue;
600 if (strncasecmp(host + hlen - plen,
601 cp, plen) == 0) {
602 isproxy = 0;
603 break;
604 }
605 }
606 FREEPTR(np_copy);
607 if (isproxy == 0 && urltype == FTP_URL_T) {
608 rval = fetch_ftp(url);
609 goto cleanup_fetch_url;
610 }
611 }
612
613 if (isproxy) {
614 if (parse_url(proxyenv, "proxy URL", &purltype,
615 &puser, &ppass, &phost, &pport, &portnum,
616 &ppath) == -1)
617 goto cleanup_fetch_url;
618
619 if ((purltype != HTTP_URL_T
620 && purltype != FTP_URL_T) ||
621 EMPTYSTRING(phost) ||
622 (! EMPTYSTRING(ppath)
623 && strcmp(ppath, "/") != 0)) {
624 warnx("Malformed proxy URL `%s'",
625 proxyenv);
626 FREEPTR(phost);
627 FREEPTR(pport);
628 FREEPTR(ppath);
629 goto cleanup_fetch_url;
630 }
631 if (isipv6addr(host) &&
632 strchr(host, '%') != NULL) {
633 warnx(
634 "Scoped address notation `%s' disallowed via web proxy",
635 host);
636 FREEPTR(phost);
637 FREEPTR(pport);
638 FREEPTR(ppath);
639 goto cleanup_fetch_url;
640 }
641
642 FREEPTR(host);
643 host = phost;
644 FREEPTR(port);
645 port = pport;
646 FREEPTR(path);
647 path = xstrdup(url);
648 FREEPTR(ppath);
649 }
650 } /* ! EMPTYSTRING(proxyenv) */
651
652 memset(&hints, 0, sizeof(hints));
653 hints.ai_flags = 0;
654 hints.ai_family = family;
655 hints.ai_socktype = SOCK_STREAM;
656 hints.ai_protocol = 0;
657 error = getaddrinfo(host, NULL, &hints, &res0);
658 if (error) {
659 warnx("%s", gai_strerror(error));
660 goto cleanup_fetch_url;
661 }
662 if (res0->ai_canonname)
663 host = res0->ai_canonname;
664
665 s = -1;
666 for (res = res0; res; res = res->ai_next) {
667 /*
668 * see comment in hookup()
669 */
670 ai_unmapped(res);
671 if (getnameinfo(res->ai_addr, res->ai_addrlen,
672 hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
673 strlcpy(hbuf, "invalid", sizeof(hbuf));
674
675 if (verbose && res != res0)
676 fprintf(ttyout, "Trying %s...\n", hbuf);
677
678 ((struct sockaddr_in *)res->ai_addr)->sin_port =
679 htons(portnum);
680 s = socket(res->ai_family, SOCK_STREAM,
681 res->ai_protocol);
682 if (s < 0) {
683 warn("Can't create socket");
684 continue;
685 }
686
687 if (xconnect(s, res->ai_addr, res->ai_addrlen) < 0) {
688 warn("Connect to address `%s'", hbuf);
689 close(s);
690 s = -1;
691 continue;
692 }
693
694 /* success */
695 break;
696 }
697 freeaddrinfo(res0);
698
699 if (s < 0) {
700 warn("Can't connect to %s", host);
701 goto cleanup_fetch_url;
702 }
703
704 fin = fdopen(s, "r+");
705 /*
706 * Construct and send the request.
707 */
708 if (verbose)
709 fprintf(ttyout, "Requesting %s\n", url);
710 leading = " (";
711 hasleading = 0;
712 if (isproxy) {
713 if (verbose) {
714 fprintf(ttyout, "%svia %s:%s", leading,
715 host, port);
716 leading = ", ";
717 hasleading++;
718 }
719 fprintf(fin, "GET %s HTTP/1.0\r\n", path);
720 if (flushcache)
721 fprintf(fin, "Pragma: no-cache\r\n");
722 } else {
723 fprintf(fin, "GET %s HTTP/1.1\r\n", path);
724 if (strchr(host, ':')) {
725 char *h, *p;
726
727 /*
728 * strip off IPv6 scope identifier, since it is
729 * local to the node
730 */
731 h = xstrdup(host);
732 if (isipv6addr(h) &&
733 (p = strchr(h, '%')) != NULL) {
734 *p = '\0';
735 }
736 fprintf(fin, "Host: [%s]", h);
737 free(h);
738 } else
739 fprintf(fin, "Host: %s", host);
740 if (portnum != HTTP_PORT)
741 fprintf(fin, ":%u", portnum);
742 fprintf(fin, "\r\n");
743 fprintf(fin, "Accept: */*\r\n");
744 fprintf(fin, "Connection: close\r\n");
745 if (restart_point) {
746 fputs(leading, ttyout);
747 fprintf(fin, "Range: bytes=" LLF "-\r\n",
748 (LLT)restart_point);
749 fprintf(ttyout, "restarting at " LLF,
750 (LLT)restart_point);
751 leading = ", ";
752 hasleading++;
753 }
754 if (flushcache)
755 fprintf(fin, "Cache-Control: no-cache\r\n");
756 }
757 if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
758 fprintf(fin, "User-Agent: %s\r\n", useragent);
759 } else {
760 fprintf(fin, "User-Agent: %s/%s\r\n",
761 FTP_PRODUCT, FTP_VERSION);
762 }
763 if (wwwauth) {
764 if (verbose) {
765 fprintf(ttyout, "%swith authorization",
766 leading);
767 leading = ", ";
768 hasleading++;
769 }
770 fprintf(fin, "Authorization: %s\r\n", wwwauth);
771 }
772 if (proxyauth) {
773 if (verbose) {
774 fprintf(ttyout,
775 "%swith proxy authorization", leading);
776 leading = ", ";
777 hasleading++;
778 }
779 fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
780 }
781 if (verbose && hasleading)
782 fputs(")\n", ttyout);
783 fprintf(fin, "\r\n");
784 if (fflush(fin) == EOF) {
785 warn("Writing HTTP request");
786 goto cleanup_fetch_url;
787 }
788
789 /* Read the response */
790 if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
791 warn("Receiving HTTP reply");
792 goto cleanup_fetch_url;
793 }
794 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
795 buf[--len] = '\0';
796 if (debug)
797 fprintf(ttyout, "received `%s'\n", buf);
798
799 /* Determine HTTP response code */
800 cp = strchr(buf, ' ');
801 if (cp == NULL)
802 goto improper;
803 else
804 cp++;
805 hcode = strtol(cp, &ep, 10);
806 if (*ep != '\0' && !isspace((unsigned char)*ep))
807 goto improper;
808 message = xstrdup(cp);
809
810 /* Read the rest of the header. */
811 FREEPTR(buf);
812 while (1) {
813 if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
814 == NULL) {
815 warn("Receiving HTTP reply");
816 goto cleanup_fetch_url;
817 }
818 while (len > 0 &&
819 (buf[len-1] == '\r' || buf[len-1] == '\n'))
820 buf[--len] = '\0';
821 if (len == 0)
822 break;
823 if (debug)
824 fprintf(ttyout, "received `%s'\n", buf);
825
826 /* Look for some headers */
827 cp = buf;
828
829 #define CONTENTLEN "Content-Length: "
830 if (strncasecmp(cp, CONTENTLEN,
831 sizeof(CONTENTLEN) - 1) == 0) {
832 cp += sizeof(CONTENTLEN) - 1;
833 filesize = STRTOLL(cp, &ep, 10);
834 if (filesize < 0 || *ep != '\0')
835 goto improper;
836 if (debug)
837 fprintf(ttyout,
838 "parsed len as: " LLF "\n",
839 (LLT)filesize);
840
841 #define CONTENTRANGE "Content-Range: bytes "
842 } else if (strncasecmp(cp, CONTENTRANGE,
843 sizeof(CONTENTRANGE) - 1) == 0) {
844 cp += sizeof(CONTENTRANGE) - 1;
845 if (*cp == '*') {
846 ep = cp + 1;
847 }
848 else {
849 rangestart = STRTOLL(cp, &ep, 10);
850 if (rangestart < 0 || *ep != '-')
851 goto improper;
852 cp = ep + 1;
853 rangeend = STRTOLL(cp, &ep, 10);
854 if (rangeend < 0 || rangeend < rangestart)
855 goto improper;
856 }
857 if (*ep != '/')
858 goto improper;
859 cp = ep + 1;
860 if (*cp == '*') {
861 ep = cp + 1;
862 }
863 else {
864 entitylen = STRTOLL(cp, &ep, 10);
865 if (entitylen < 0)
866 goto improper;
867 }
868 if (*ep != '\0')
869 goto improper;
870
871 if (debug) {
872 fprintf(ttyout, "parsed range as: ");
873 if (rangestart == -1)
874 fprintf(ttyout, "*");
875 else
876 fprintf(ttyout, LLF "-" LLF,
877 (LLT)rangestart,
878 (LLT)rangeend);
879 fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
880 }
881 if (! restart_point) {
882 warnx(
883 "Received unexpected Content-Range header");
884 goto cleanup_fetch_url;
885 }
886
887 #define LASTMOD "Last-Modified: "
888 } else if (strncasecmp(cp, LASTMOD,
889 sizeof(LASTMOD) - 1) == 0) {
890 struct tm parsed;
891 char *t;
892
893 cp += sizeof(LASTMOD) - 1;
894 /* RFC 1123 */
895 if ((t = strptime(cp,
896 "%a, %d %b %Y %H:%M:%S GMT",
897 &parsed))
898 /* RFC 850 */
899 || (t = strptime(cp,
900 "%a, %d-%b-%y %H:%M:%S GMT",
901 &parsed))
902 /* asctime */
903 || (t = strptime(cp,
904 "%a, %b %d %H:%M:%S %Y",
905 &parsed))) {
906 parsed.tm_isdst = -1;
907 if (*t == '\0')
908 mtime = timegm(&parsed);
909 if (debug && mtime != -1) {
910 fprintf(ttyout,
911 "parsed date as: %s",
912 ctime(&mtime));
913 }
914 }
915
916 #define LOCATION "Location: "
917 } else if (strncasecmp(cp, LOCATION,
918 sizeof(LOCATION) - 1) == 0) {
919 cp += sizeof(LOCATION) - 1;
920 location = xstrdup(cp);
921 if (debug)
922 fprintf(ttyout,
923 "parsed location as: %s\n", cp);
924
925 #define TRANSENC "Transfer-Encoding: "
926 } else if (strncasecmp(cp, TRANSENC,
927 sizeof(TRANSENC) - 1) == 0) {
928 cp += sizeof(TRANSENC) - 1;
929 if (strcasecmp(cp, "binary") == 0) {
930 warnx(
931 "Bogus transfer encoding - `%s' (fetching anyway)",
932 cp);
933 continue;
934 }
935 if (strcasecmp(cp, "chunked") != 0) {
936 warnx(
937 "Unsupported transfer encoding - `%s'",
938 cp);
939 goto cleanup_fetch_url;
940 }
941 ischunked++;
942 if (debug)
943 fprintf(ttyout,
944 "using chunked encoding\n");
945
946 #define PROXYAUTH "Proxy-Authenticate: "
947 } else if (strncasecmp(cp, PROXYAUTH,
948 sizeof(PROXYAUTH) - 1) == 0) {
949 cp += sizeof(PROXYAUTH) - 1;
950 FREEPTR(auth);
951 auth = xstrdup(cp);
952 if (debug)
953 fprintf(ttyout,
954 "parsed proxy-auth as: %s\n", cp);
955
956 #define WWWAUTH "WWW-Authenticate: "
957 } else if (strncasecmp(cp, WWWAUTH,
958 sizeof(WWWAUTH) - 1) == 0) {
959 cp += sizeof(WWWAUTH) - 1;
960 FREEPTR(auth);
961 auth = xstrdup(cp);
962 if (debug)
963 fprintf(ttyout,
964 "parsed www-auth as: %s\n", cp);
965
966 }
967
968 }
969 /* finished parsing header */
970 FREEPTR(buf);
971
972 switch (hcode) {
973 case 200:
974 break;
975 case 206:
976 if (! restart_point) {
977 warnx("Not expecting partial content header");
978 goto cleanup_fetch_url;
979 }
980 break;
981 case 300:
982 case 301:
983 case 302:
984 case 303:
985 case 305:
986 if (EMPTYSTRING(location)) {
987 warnx(
988 "No redirection Location provided by server");
989 goto cleanup_fetch_url;
990 }
991 if (redirect_loop++ > 5) {
992 warnx("Too many redirections requested");
993 goto cleanup_fetch_url;
994 }
995 if (hcode == 305) {
996 if (verbose)
997 fprintf(ttyout, "Redirected via %s\n",
998 location);
999 rval = fetch_url(url, location,
1000 proxyauth, wwwauth);
1001 } else {
1002 if (verbose)
1003 fprintf(ttyout, "Redirected to %s\n",
1004 location);
1005 rval = go_fetch(location);
1006 }
1007 goto cleanup_fetch_url;
1008 #ifndef NO_AUTH
1009 case 401:
1010 case 407:
1011 {
1012 char **authp;
1013 char *auser, *apass;
1014
1015 fprintf(ttyout, "%s\n", message);
1016 if (EMPTYSTRING(auth)) {
1017 warnx(
1018 "No authentication challenge provided by server");
1019 goto cleanup_fetch_url;
1020 }
1021 if (hcode == 401) {
1022 authp = &wwwauth;
1023 auser = user;
1024 apass = pass;
1025 } else {
1026 authp = &proxyauth;
1027 auser = puser;
1028 apass = ppass;
1029 }
1030 if (*authp != NULL) {
1031 char reply[10];
1032
1033 fprintf(ttyout,
1034 "Authorization failed. Retry (y/n)? ");
1035 if (fgets(reply, sizeof(reply), stdin)
1036 == NULL) {
1037 clearerr(stdin);
1038 goto cleanup_fetch_url;
1039 } else {
1040 if (tolower(reply[0]) != 'y')
1041 goto cleanup_fetch_url;
1042 }
1043 auser = NULL;
1044 apass = NULL;
1045 }
1046 if (auth_url(auth, authp, auser, apass) == 0) {
1047 rval = fetch_url(url, proxyenv,
1048 proxyauth, wwwauth);
1049 memset(*authp, 0, strlen(*authp));
1050 FREEPTR(*authp);
1051 }
1052 goto cleanup_fetch_url;
1053 }
1054 #endif
1055 default:
1056 if (message)
1057 warnx("Error retrieving file - `%s'", message);
1058 else
1059 warnx("Unknown error retrieving file");
1060 goto cleanup_fetch_url;
1061 }
1062 } /* end of ftp:// or http:// specific setup */
1063
1064 /* Open the output file. */
1065 if (strcmp(savefile, "-") == 0) {
1066 fout = stdout;
1067 } else if (*savefile == '|') {
1068 oldintp = xsignal(SIGPIPE, SIG_IGN);
1069 fout = popen(savefile + 1, "w");
1070 if (fout == NULL) {
1071 warn("Can't run `%s'", savefile + 1);
1072 goto cleanup_fetch_url;
1073 }
1074 closefunc = pclose;
1075 } else {
1076 if ((rangeend != -1 && rangeend <= restart_point) ||
1077 (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
1078 /* already done */
1079 if (verbose)
1080 fprintf(ttyout, "already done\n");
1081 rval = 0;
1082 goto cleanup_fetch_url;
1083 }
1084 if (restart_point && rangestart != -1) {
1085 if (entitylen != -1)
1086 filesize = entitylen;
1087 if (rangestart != restart_point) {
1088 warnx(
1089 "Size of `%s' differs from save file `%s'",
1090 url, savefile);
1091 goto cleanup_fetch_url;
1092 }
1093 fout = fopen(savefile, "a");
1094 } else
1095 fout = fopen(savefile, "w");
1096 if (fout == NULL) {
1097 warn("Can't open `%s'", savefile);
1098 goto cleanup_fetch_url;
1099 }
1100 closefunc = fclose;
1101 }
1102
1103 /* Trap signals */
1104 if (sigsetjmp(httpabort, 1))
1105 goto cleanup_fetch_url;
1106 (void)xsignal(SIGQUIT, psummary);
1107 oldintr = xsignal(SIGINT, aborthttp);
1108
1109 if (rcvbuf_size > bufsize) {
1110 if (xferbuf)
1111 (void)free(xferbuf);
1112 bufsize = rcvbuf_size;
1113 xferbuf = xmalloc(bufsize);
1114 }
1115
1116 bytes = 0;
1117 hashbytes = mark;
1118 progressmeter(-1);
1119
1120 /* Finally, suck down the file. */
1121 do {
1122 long chunksize;
1123
1124 chunksize = 0;
1125 /* read chunksize */
1126 if (ischunked) {
1127 if (fgets(xferbuf, bufsize, fin) == NULL) {
1128 warnx("Unexpected EOF reading chunksize");
1129 goto cleanup_fetch_url;
1130 }
1131 chunksize = strtol(xferbuf, &ep, 16);
1132
1133 /*
1134 * XXX: Work around bug in Apache 1.3.9 and
1135 * 1.3.11, which incorrectly put trailing
1136 * space after the chunksize.
1137 */
1138 while (*ep == ' ')
1139 ep++;
1140
1141 if (strcmp(ep, "\r\n") != 0) {
1142 warnx("Unexpected data following chunksize");
1143 goto cleanup_fetch_url;
1144 }
1145 if (debug)
1146 fprintf(ttyout, "got chunksize of " LLF "\n",
1147 (LLT)chunksize);
1148 if (chunksize == 0)
1149 break;
1150 }
1151 /* transfer file or chunk */
1152 while (1) {
1153 struct timeval then, now, td;
1154 off_t bufrem;
1155
1156 if (rate_get)
1157 (void)gettimeofday(&then, NULL);
1158 bufrem = rate_get ? rate_get : bufsize;
1159 if (ischunked)
1160 bufrem = MIN(chunksize, bufrem);
1161 while (bufrem > 0) {
1162 len = fread(xferbuf, sizeof(char),
1163 MIN(bufsize, bufrem), fin);
1164 if (len <= 0)
1165 goto chunkdone;
1166 bytes += len;
1167 bufrem -= len;
1168 if (fwrite(xferbuf, sizeof(char), len, fout)
1169 != len) {
1170 warn("Writing `%s'", savefile);
1171 goto cleanup_fetch_url;
1172 }
1173 if (hash && !progress) {
1174 while (bytes >= hashbytes) {
1175 (void)putc('#', ttyout);
1176 hashbytes += mark;
1177 }
1178 (void)fflush(ttyout);
1179 }
1180 if (ischunked) {
1181 chunksize -= len;
1182 if (chunksize <= 0)
1183 break;
1184 }
1185 }
1186 if (rate_get) {
1187 while (1) {
1188 (void)gettimeofday(&now, NULL);
1189 timersub(&now, &then, &td);
1190 if (td.tv_sec > 0)
1191 break;
1192 usleep(1000000 - td.tv_usec);
1193 }
1194 }
1195 if (ischunked && chunksize <= 0)
1196 break;
1197 }
1198 /* read CRLF after chunk*/
1199 chunkdone:
1200 if (ischunked) {
1201 if (fgets(xferbuf, bufsize, fin) == NULL)
1202 break;
1203 if (strcmp(xferbuf, "\r\n") != 0) {
1204 warnx("Unexpected data following chunk");
1205 goto cleanup_fetch_url;
1206 }
1207 }
1208 } while (ischunked);
1209 if (hash && !progress && bytes > 0) {
1210 if (bytes < mark)
1211 (void)putc('#', ttyout);
1212 (void)putc('\n', ttyout);
1213 }
1214 if (ferror(fin)) {
1215 warn("Reading file");
1216 goto cleanup_fetch_url;
1217 }
1218 progressmeter(1);
1219 (void)fflush(fout);
1220 if (closefunc == fclose && mtime != -1) {
1221 struct timeval tval[2];
1222
1223 (void)gettimeofday(&tval[0], NULL);
1224 tval[1].tv_sec = mtime;
1225 tval[1].tv_usec = 0;
1226 (*closefunc)(fout);
1227 fout = NULL;
1228
1229 if (utimes(savefile, tval) == -1) {
1230 fprintf(ttyout,
1231 "Can't change modification time to %s",
1232 asctime(localtime(&mtime)));
1233 }
1234 }
1235 if (bytes > 0)
1236 ptransfer(0);
1237 bytes = 0;
1238
1239 rval = 0;
1240 goto cleanup_fetch_url;
1241
1242 improper:
1243 warnx("Improper response from `%s'", host);
1244
1245 cleanup_fetch_url:
1246 if (oldintr)
1247 (void)xsignal(SIGINT, oldintr);
1248 if (oldintp)
1249 (void)xsignal(SIGPIPE, oldintp);
1250 if (fin != NULL)
1251 fclose(fin);
1252 else if (s != -1)
1253 close(s);
1254 if (closefunc != NULL && fout != NULL)
1255 (*closefunc)(fout);
1256 FREEPTR(savefile);
1257 FREEPTR(user);
1258 FREEPTR(pass);
1259 FREEPTR(host);
1260 FREEPTR(port);
1261 FREEPTR(path);
1262 FREEPTR(decodedpath);
1263 FREEPTR(puser);
1264 FREEPTR(ppass);
1265 FREEPTR(buf);
1266 FREEPTR(auth);
1267 FREEPTR(location);
1268 FREEPTR(message);
1269 return (rval);
1270 }
1271
1272 /*
1273 * Abort a HTTP retrieval
1274 */
1275 void
1276 aborthttp(int notused)
1277 {
1278 char msgbuf[100];
1279 int len;
1280
1281 alarmtimer(0);
1282 len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
1283 write(fileno(ttyout), msgbuf, len);
1284 siglongjmp(httpabort, 1);
1285 }
1286
1287 /*
1288 * Retrieve ftp URL or classic ftp argument using FTP.
1289 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1290 * is still open (e.g, ftp xfer with trailing /)
1291 */
1292 static int
1293 fetch_ftp(const char *url)
1294 {
1295 char *cp, *xargv[5], rempath[MAXPATHLEN];
1296 char *host, *path, *dir, *file, *user, *pass;
1297 char *port;
1298 int dirhasglob, filehasglob, oautologin, rval, type, xargc;
1299 in_port_t portnum;
1300 url_t urltype;
1301
1302 host = path = dir = file = user = pass = NULL;
1303 port = NULL;
1304 rval = 1;
1305 type = TYPE_I;
1306
1307 if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1308 if ((parse_url(url, "URL", &urltype, &user, &pass,
1309 &host, &port, &portnum, &path) == -1) ||
1310 (user != NULL && *user == '\0') ||
1311 EMPTYSTRING(host)) {
1312 warnx("Invalid URL `%s'", url);
1313 goto cleanup_fetch_ftp;
1314 }
1315 url_decode(user);
1316 url_decode(pass);
1317 /*
1318 * Note: Don't url_decode(path) here. We need to keep the
1319 * distinction between "/" and "%2F" until later.
1320 */
1321
1322 /* check for trailing ';type=[aid]' */
1323 if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1324 if (strcasecmp(cp, ";type=a") == 0)
1325 type = TYPE_A;
1326 else if (strcasecmp(cp, ";type=i") == 0)
1327 type = TYPE_I;
1328 else if (strcasecmp(cp, ";type=d") == 0) {
1329 warnx(
1330 "Directory listing via a URL is not supported");
1331 goto cleanup_fetch_ftp;
1332 } else {
1333 warnx("Invalid suffix `%s' in URL `%s'", cp,
1334 url);
1335 goto cleanup_fetch_ftp;
1336 }
1337 *cp = 0;
1338 }
1339 } else { /* classic style `[user@]host:[file]' */
1340 urltype = CLASSIC_URL_T;
1341 host = xstrdup(url);
1342 cp = strchr(host, '@');
1343 if (cp != NULL) {
1344 *cp = '\0';
1345 user = host;
1346 anonftp = 0; /* disable anonftp */
1347 host = xstrdup(cp + 1);
1348 }
1349 cp = strchr(host, ':');
1350 if (cp != NULL) {
1351 *cp = '\0';
1352 path = xstrdup(cp + 1);
1353 }
1354 }
1355 if (EMPTYSTRING(host))
1356 goto cleanup_fetch_ftp;
1357
1358 /* Extract the file and (if present) directory name. */
1359 dir = path;
1360 if (! EMPTYSTRING(dir)) {
1361 /*
1362 * If we are dealing with classic `[user@]host:[path]' syntax,
1363 * then a path of the form `/file' (resulting from input of the
1364 * form `host:/file') means that we should do "CWD /" before
1365 * retrieving the file. So we set dir="/" and file="file".
1366 *
1367 * But if we are dealing with URLs like `ftp://host/path' then
1368 * a path of the form `/file' (resulting from a URL of the form
1369 * `ftp://host//file') means that we should do `CWD ' (with an
1370 * empty argument) before retrieving the file. So we set
1371 * dir="" and file="file".
1372 *
1373 * If the path does not contain / at all, we set dir=NULL.
1374 * (We get a path without any slashes if we are dealing with
1375 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1376 *
1377 * In all other cases, we set dir to a string that does not
1378 * include the final '/' that separates the dir part from the
1379 * file part of the path. (This will be the empty string if
1380 * and only if we are dealing with a path of the form `/file'
1381 * resulting from an URL of the form `ftp://host//file'.)
1382 */
1383 cp = strrchr(dir, '/');
1384 if (cp == dir && urltype == CLASSIC_URL_T) {
1385 file = cp + 1;
1386 dir = "/";
1387 } else if (cp != NULL) {
1388 *cp++ = '\0';
1389 file = cp;
1390 } else {
1391 file = dir;
1392 dir = NULL;
1393 }
1394 } else
1395 dir = NULL;
1396 if (urltype == FTP_URL_T && file != NULL) {
1397 url_decode(file);
1398 /* but still don't url_decode(dir) */
1399 }
1400 if (debug)
1401 fprintf(ttyout,
1402 "fetch_ftp: user `%s' pass `%s' host %s port %s "
1403 "path `%s' dir `%s' file `%s'\n",
1404 user ? user : "<null>", pass ? pass : "<null>",
1405 host ? host : "<null>", port ? port : "<null>",
1406 path ? path : "<null>",
1407 dir ? dir : "<null>", file ? file : "<null>");
1408
1409 dirhasglob = filehasglob = 0;
1410 if (doglob && urltype == CLASSIC_URL_T) {
1411 if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
1412 dirhasglob = 1;
1413 if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
1414 filehasglob = 1;
1415 }
1416
1417 /* Set up the connection */
1418 if (connected)
1419 disconnect(0, NULL);
1420 xargv[0] = (char *)getprogname(); /* XXX discards const */
1421 xargv[1] = host;
1422 xargv[2] = NULL;
1423 xargc = 2;
1424 if (port) {
1425 xargv[2] = port;
1426 xargv[3] = NULL;
1427 xargc = 3;
1428 }
1429 oautologin = autologin;
1430 /* don't autologin in setpeer(), use ftp_login() below */
1431 autologin = 0;
1432 setpeer(xargc, xargv);
1433 autologin = oautologin;
1434 if ((connected == 0) ||
1435 (connected == 1 && !ftp_login(host, user, pass))) {
1436 warnx("Can't connect or login to host `%s'", host);
1437 goto cleanup_fetch_ftp;
1438 }
1439
1440 switch (type) {
1441 case TYPE_A:
1442 setascii(1, xargv);
1443 break;
1444 case TYPE_I:
1445 setbinary(1, xargv);
1446 break;
1447 default:
1448 errx(1, "fetch_ftp: unknown transfer type %d", type);
1449 }
1450
1451 /*
1452 * Change directories, if necessary.
1453 *
1454 * Note: don't use EMPTYSTRING(dir) below, because
1455 * dir=="" means something different from dir==NULL.
1456 */
1457 if (dir != NULL && !dirhasglob) {
1458 char *nextpart;
1459
1460 /*
1461 * If we are dealing with a classic `[user@]host:[path]'
1462 * (urltype is CLASSIC_URL_T) then we have a raw directory
1463 * name (not encoded in any way) and we can change
1464 * directories in one step.
1465 *
1466 * If we are dealing with an `ftp://host/path' URL
1467 * (urltype is FTP_URL_T), then RFC 1738 says we need to
1468 * send a separate CWD command for each unescaped "/"
1469 * in the path, and we have to interpret %hex escaping
1470 * *after* we find the slashes. It's possible to get
1471 * empty components here, (from multiple adjacent
1472 * slashes in the path) and RFC 1738 says that we should
1473 * still do `CWD ' (with a null argument) in such cases.
1474 *
1475 * Many ftp servers don't support `CWD ', so if there's an
1476 * error performing that command, bail out with a descriptive
1477 * message.
1478 *
1479 * Examples:
1480 *
1481 * host: dir="", urltype=CLASSIC_URL_T
1482 * logged in (to default directory)
1483 * host:file dir=NULL, urltype=CLASSIC_URL_T
1484 * "RETR file"
1485 * host:dir/ dir="dir", urltype=CLASSIC_URL_T
1486 * "CWD dir", logged in
1487 * ftp://host/ dir="", urltype=FTP_URL_T
1488 * logged in (to default directory)
1489 * ftp://host/dir/ dir="dir", urltype=FTP_URL_T
1490 * "CWD dir", logged in
1491 * ftp://host/file dir=NULL, urltype=FTP_URL_T
1492 * "RETR file"
1493 * ftp://host//file dir="", urltype=FTP_URL_T
1494 * "CWD ", "RETR file"
1495 * host:/file dir="/", urltype=CLASSIC_URL_T
1496 * "CWD /", "RETR file"
1497 * ftp://host///file dir="/", urltype=FTP_URL_T
1498 * "CWD ", "CWD ", "RETR file"
1499 * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T
1500 * "CWD /", "RETR file"
1501 * ftp://host/foo/file dir="foo", urltype=FTP_URL_T
1502 * "CWD foo", "RETR file"
1503 * ftp://host/foo/bar/file dir="foo/bar"
1504 * "CWD foo", "CWD bar", "RETR file"
1505 * ftp://host//foo/bar/file dir="/foo/bar"
1506 * "CWD ", "CWD foo", "CWD bar", "RETR file"
1507 * ftp://host/foo//bar/file dir="foo//bar"
1508 * "CWD foo", "CWD ", "CWD bar", "RETR file"
1509 * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar"
1510 * "CWD /", "CWD foo", "CWD bar", "RETR file"
1511 * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar"
1512 * "CWD /foo", "CWD bar", "RETR file"
1513 * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
1514 * "CWD /foo/bar", "RETR file"
1515 * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL
1516 * "RETR /foo/bar/file"
1517 *
1518 * Note that we don't need `dir' after this point.
1519 */
1520 do {
1521 if (urltype == FTP_URL_T) {
1522 nextpart = strchr(dir, '/');
1523 if (nextpart) {
1524 *nextpart = '\0';
1525 nextpart++;
1526 }
1527 url_decode(dir);
1528 } else
1529 nextpart = NULL;
1530 if (debug)
1531 fprintf(ttyout, "dir `%s', nextpart `%s'\n",
1532 dir ? dir : "<null>",
1533 nextpart ? nextpart : "<null>");
1534 if (urltype == FTP_URL_T || *dir != '\0') {
1535 xargv[0] = "cd";
1536 xargv[1] = dir;
1537 xargv[2] = NULL;
1538 dirchange = 0;
1539 cd(2, xargv);
1540 if (! dirchange) {
1541 if (*dir == '\0' && code == 500)
1542 fprintf(stderr,
1543 "\n"
1544 "ftp: The `CWD ' command (without a directory), which is required by\n"
1545 " RFC 1738 to support the empty directory in the URL pathname (`//'),\n"
1546 " conflicts with the server's conformance to RFC 959.\n"
1547 " Try the same URL without the `//' in the URL pathname.\n"
1548 "\n");
1549 goto cleanup_fetch_ftp;
1550 }
1551 }
1552 dir = nextpart;
1553 } while (dir != NULL);
1554 }
1555
1556 if (EMPTYSTRING(file)) {
1557 rval = -1;
1558 goto cleanup_fetch_ftp;
1559 }
1560
1561 if (dirhasglob) {
1562 (void)strlcpy(rempath, dir, sizeof(rempath));
1563 (void)strlcat(rempath, "/", sizeof(rempath));
1564 (void)strlcat(rempath, file, sizeof(rempath));
1565 file = rempath;
1566 }
1567
1568 /* Fetch the file(s). */
1569 xargc = 2;
1570 xargv[0] = "get";
1571 xargv[1] = file;
1572 xargv[2] = NULL;
1573 if (dirhasglob || filehasglob) {
1574 int ointeractive;
1575
1576 ointeractive = interactive;
1577 interactive = 0;
1578 xargv[0] = "mget";
1579 mget(xargc, xargv);
1580 interactive = ointeractive;
1581 } else {
1582 if (outfile == NULL) {
1583 cp = strrchr(file, '/'); /* find savefile */
1584 if (cp != NULL)
1585 outfile = cp + 1;
1586 else
1587 outfile = file;
1588 }
1589 xargv[2] = (char *)outfile;
1590 xargv[3] = NULL;
1591 xargc++;
1592 if (restartautofetch)
1593 reget(xargc, xargv);
1594 else
1595 get(xargc, xargv);
1596 }
1597
1598 if ((code / 100) == COMPLETE)
1599 rval = 0;
1600
1601 cleanup_fetch_ftp:
1602 FREEPTR(host);
1603 FREEPTR(path);
1604 FREEPTR(user);
1605 FREEPTR(pass);
1606 return (rval);
1607 }
1608
1609 /*
1610 * Retrieve the given file to outfile.
1611 * Supports arguments of the form:
1612 * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else
1613 * call fetch_ftp()
1614 * "http://host/path" call fetch_url() to use HTTP
1615 * "file:///path" call fetch_url() to copy
1616 * "about:..." print a message
1617 *
1618 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1619 * is still open (e.g, ftp xfer with trailing /)
1620 */
1621 static int
1622 go_fetch(const char *url)
1623 {
1624 char *proxy;
1625
1626 #ifndef NO_ABOUT
1627 /*
1628 * Check for about:*
1629 */
1630 if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
1631 url += sizeof(ABOUT_URL) -1;
1632 if (strcasecmp(url, "ftp") == 0 ||
1633 strcasecmp(url, "tnftp") == 0) {
1634 fputs(
1635 "This version of ftp has been enhanced by Luke Mewburn <lukem (at) NetBSD.org>\n"
1636 "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout);
1637 } else if (strcasecmp(url, "lukem") == 0) {
1638 fputs(
1639 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1640 "Please email feedback to <lukem (at) NetBSD.org>.\n", ttyout);
1641 } else if (strcasecmp(url, "netbsd") == 0) {
1642 fputs(
1643 "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1644 "For more information, see http://www.NetBSD.org/\n", ttyout);
1645 } else if (strcasecmp(url, "version") == 0) {
1646 fprintf(ttyout, "Version: %s %s%s\n",
1647 FTP_PRODUCT, FTP_VERSION,
1648 #ifdef INET6
1649 ""
1650 #else
1651 " (-IPv6)"
1652 #endif
1653 );
1654 } else {
1655 fprintf(ttyout, "`%s' is an interesting topic.\n", url);
1656 }
1657 fputs("\n", ttyout);
1658 return (0);
1659 }
1660 #endif
1661
1662 /*
1663 * Check for file:// and http:// URLs.
1664 */
1665 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1666 strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0)
1667 return (fetch_url(url, NULL, NULL, NULL));
1668
1669 /*
1670 * Try FTP URL-style and host:file arguments next.
1671 * If ftpproxy is set with an FTP URL, use fetch_url()
1672 * Othewise, use fetch_ftp().
1673 */
1674 proxy = getoptionvalue("ftp_proxy");
1675 if (!EMPTYSTRING(proxy) &&
1676 strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0)
1677 return (fetch_url(url, NULL, NULL, NULL));
1678
1679 return (fetch_ftp(url));
1680 }
1681
1682 /*
1683 * Retrieve multiple files from the command line,
1684 * calling go_fetch() for each file.
1685 *
1686 * If an ftp path has a trailing "/", the path will be cd-ed into and
1687 * the connection remains open, and the function will return -1
1688 * (to indicate the connection is alive).
1689 * If an error occurs the return value will be the offset+1 in
1690 * argv[] of the file that caused a problem (i.e, argv[x]
1691 * returns x+1)
1692 * Otherwise, 0 is returned if all files retrieved successfully.
1693 */
1694 int
1695 auto_fetch(int argc, char *argv[])
1696 {
1697 volatile int argpos;
1698 int rval;
1699
1700 argpos = 0;
1701
1702 if (sigsetjmp(toplevel, 1)) {
1703 if (connected)
1704 disconnect(0, NULL);
1705 return (argpos + 1);
1706 }
1707 (void)xsignal(SIGINT, intr);
1708 (void)xsignal(SIGPIPE, lostpeer);
1709
1710 /*
1711 * Loop through as long as there's files to fetch.
1712 */
1713 for (rval = 0; (rval == 0) && (argpos < argc); argpos++) {
1714 if (strchr(argv[argpos], ':') == NULL)
1715 break;
1716 redirect_loop = 0;
1717 if (!anonftp)
1718 anonftp = 2; /* Handle "automatic" transfers. */
1719 rval = go_fetch(argv[argpos]);
1720 if (outfile != NULL && strcmp(outfile, "-") != 0
1721 && outfile[0] != '|')
1722 outfile = NULL;
1723 if (rval > 0)
1724 rval = argpos + 1;
1725 }
1726
1727 if (connected && rval != -1)
1728 disconnect(0, NULL);
1729 return (rval);
1730 }
1731
1732
1733 int
1734 auto_put(int argc, char **argv, const char *uploadserver)
1735 {
1736 char *uargv[4], *path, *pathsep;
1737 int uargc, rval, len;
1738
1739 uargc = 0;
1740 uargv[uargc++] = "mput";
1741 uargv[uargc++] = argv[0];
1742 uargv[2] = uargv[3] = NULL;
1743 pathsep = NULL;
1744 rval = 1;
1745
1746 if (debug)
1747 fprintf(ttyout, "auto_put: target `%s'\n", uploadserver);
1748
1749 path = xstrdup(uploadserver);
1750 len = strlen(path);
1751 if (path[len - 1] != '/' && path[len - 1] != ':') {
1752 /*
1753 * make sure we always pass a directory to auto_fetch
1754 */
1755 if (argc > 1) { /* more than one file to upload */
1756 int len;
1757
1758 len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
1759 free(path);
1760 path = (char *)xmalloc(len);
1761 (void)strlcpy(path, uploadserver, len);
1762 (void)strlcat(path, "/", len);
1763 } else { /* single file to upload */
1764 uargv[0] = "put";
1765 pathsep = strrchr(path, '/');
1766 if (pathsep == NULL) {
1767 pathsep = strrchr(path, ':');
1768 if (pathsep == NULL) {
1769 warnx("Invalid URL `%s'", path);
1770 goto cleanup_auto_put;
1771 }
1772 pathsep++;
1773 uargv[2] = xstrdup(pathsep);
1774 pathsep[0] = '/';
1775 } else
1776 uargv[2] = xstrdup(pathsep + 1);
1777 pathsep[1] = '\0';
1778 uargc++;
1779 }
1780 }
1781 if (debug)
1782 fprintf(ttyout, "auto_put: URL `%s' argv[2] `%s'\n",
1783 path, uargv[2] ? uargv[2] : "<null>");
1784
1785 /* connect and cwd */
1786 rval = auto_fetch(1, &path);
1787 free(path);
1788 if(rval >= 0)
1789 goto cleanup_auto_put;
1790
1791 /* XXX : is this the best way? */
1792 if (uargc == 3) {
1793 uargv[1] = argv[0];
1794 put(uargc, uargv);
1795 goto cleanup_auto_put;
1796 }
1797
1798 for(; argv[0] != NULL; argv++) {
1799 uargv[1] = argv[0];
1800 mput(uargc, uargv);
1801 }
1802 rval = 0;
1803
1804 cleanup_auto_put:
1805 FREEPTR(uargv[2]);
1806 return (rval);
1807 }
1808