fetch.c revision 1.149 1 /* $NetBSD: fetch.c,v 1.149 2004/07/20 11:22:27 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2004 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.149 2004/07/20 11:22:27 lukem 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 if (hcode == 401) {
1016 authp = &wwwauth;
1017 auser = user;
1018 apass = pass;
1019 } else {
1020 authp = &proxyauth;
1021 auser = puser;
1022 apass = ppass;
1023 }
1024 if (verbose || *authp == NULL ||
1025 auser == NULL || apass == NULL)
1026 fprintf(ttyout, "%s\n", message);
1027 if (EMPTYSTRING(auth)) {
1028 warnx(
1029 "No authentication challenge provided by server");
1030 goto cleanup_fetch_url;
1031 }
1032 if (*authp != NULL) {
1033 char reply[10];
1034
1035 fprintf(ttyout,
1036 "Authorization failed. Retry (y/n)? ");
1037 if (fgets(reply, sizeof(reply), stdin)
1038 == NULL) {
1039 clearerr(stdin);
1040 goto cleanup_fetch_url;
1041 } else {
1042 if (tolower(reply[0]) != 'y')
1043 goto cleanup_fetch_url;
1044 }
1045 auser = NULL;
1046 apass = NULL;
1047 }
1048 if (auth_url(auth, authp, auser, apass) == 0) {
1049 rval = fetch_url(url, proxyenv,
1050 proxyauth, wwwauth);
1051 memset(*authp, 0, strlen(*authp));
1052 FREEPTR(*authp);
1053 }
1054 goto cleanup_fetch_url;
1055 }
1056 #endif
1057 default:
1058 if (message)
1059 warnx("Error retrieving file - `%s'", message);
1060 else
1061 warnx("Unknown error retrieving file");
1062 goto cleanup_fetch_url;
1063 }
1064 } /* end of ftp:// or http:// specific setup */
1065
1066 /* Open the output file. */
1067 if (strcmp(savefile, "-") == 0) {
1068 fout = stdout;
1069 } else if (*savefile == '|') {
1070 oldintp = xsignal(SIGPIPE, SIG_IGN);
1071 fout = popen(savefile + 1, "w");
1072 if (fout == NULL) {
1073 warn("Can't run `%s'", savefile + 1);
1074 goto cleanup_fetch_url;
1075 }
1076 closefunc = pclose;
1077 } else {
1078 if ((rangeend != -1 && rangeend <= restart_point) ||
1079 (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
1080 /* already done */
1081 if (verbose)
1082 fprintf(ttyout, "already done\n");
1083 rval = 0;
1084 goto cleanup_fetch_url;
1085 }
1086 if (restart_point && rangestart != -1) {
1087 if (entitylen != -1)
1088 filesize = entitylen;
1089 if (rangestart != restart_point) {
1090 warnx(
1091 "Size of `%s' differs from save file `%s'",
1092 url, savefile);
1093 goto cleanup_fetch_url;
1094 }
1095 fout = fopen(savefile, "a");
1096 } else
1097 fout = fopen(savefile, "w");
1098 if (fout == NULL) {
1099 warn("Can't open `%s'", savefile);
1100 goto cleanup_fetch_url;
1101 }
1102 closefunc = fclose;
1103 }
1104
1105 /* Trap signals */
1106 if (sigsetjmp(httpabort, 1))
1107 goto cleanup_fetch_url;
1108 (void)xsignal(SIGQUIT, psummary);
1109 oldintr = xsignal(SIGINT, aborthttp);
1110
1111 if (rcvbuf_size > bufsize) {
1112 if (xferbuf)
1113 (void)free(xferbuf);
1114 bufsize = rcvbuf_size;
1115 xferbuf = xmalloc(bufsize);
1116 }
1117
1118 bytes = 0;
1119 hashbytes = mark;
1120 progressmeter(-1);
1121
1122 /* Finally, suck down the file. */
1123 do {
1124 long chunksize;
1125
1126 chunksize = 0;
1127 /* read chunksize */
1128 if (ischunked) {
1129 if (fgets(xferbuf, bufsize, fin) == NULL) {
1130 warnx("Unexpected EOF reading chunksize");
1131 goto cleanup_fetch_url;
1132 }
1133 chunksize = strtol(xferbuf, &ep, 16);
1134
1135 /*
1136 * XXX: Work around bug in Apache 1.3.9 and
1137 * 1.3.11, which incorrectly put trailing
1138 * space after the chunksize.
1139 */
1140 while (*ep == ' ')
1141 ep++;
1142
1143 if (strcmp(ep, "\r\n") != 0) {
1144 warnx("Unexpected data following chunksize");
1145 goto cleanup_fetch_url;
1146 }
1147 if (debug)
1148 fprintf(ttyout, "got chunksize of " LLF "\n",
1149 (LLT)chunksize);
1150 if (chunksize == 0)
1151 break;
1152 }
1153 /* transfer file or chunk */
1154 while (1) {
1155 struct timeval then, now, td;
1156 off_t bufrem;
1157
1158 if (rate_get)
1159 (void)gettimeofday(&then, NULL);
1160 bufrem = rate_get ? rate_get : bufsize;
1161 if (ischunked)
1162 bufrem = MIN(chunksize, bufrem);
1163 while (bufrem > 0) {
1164 len = fread(xferbuf, sizeof(char),
1165 MIN(bufsize, bufrem), fin);
1166 if (len <= 0)
1167 goto chunkdone;
1168 bytes += len;
1169 bufrem -= len;
1170 if (fwrite(xferbuf, sizeof(char), len, fout)
1171 != len) {
1172 warn("Writing `%s'", savefile);
1173 goto cleanup_fetch_url;
1174 }
1175 if (hash && !progress) {
1176 while (bytes >= hashbytes) {
1177 (void)putc('#', ttyout);
1178 hashbytes += mark;
1179 }
1180 (void)fflush(ttyout);
1181 }
1182 if (ischunked) {
1183 chunksize -= len;
1184 if (chunksize <= 0)
1185 break;
1186 }
1187 }
1188 if (rate_get) {
1189 while (1) {
1190 (void)gettimeofday(&now, NULL);
1191 timersub(&now, &then, &td);
1192 if (td.tv_sec > 0)
1193 break;
1194 usleep(1000000 - td.tv_usec);
1195 }
1196 }
1197 if (ischunked && chunksize <= 0)
1198 break;
1199 }
1200 /* read CRLF after chunk*/
1201 chunkdone:
1202 if (ischunked) {
1203 if (fgets(xferbuf, bufsize, fin) == NULL)
1204 break;
1205 if (strcmp(xferbuf, "\r\n") != 0) {
1206 warnx("Unexpected data following chunk");
1207 goto cleanup_fetch_url;
1208 }
1209 }
1210 } while (ischunked);
1211 if (hash && !progress && bytes > 0) {
1212 if (bytes < mark)
1213 (void)putc('#', ttyout);
1214 (void)putc('\n', ttyout);
1215 }
1216 if (ferror(fin)) {
1217 warn("Reading file");
1218 goto cleanup_fetch_url;
1219 }
1220 progressmeter(1);
1221 (void)fflush(fout);
1222 if (closefunc == fclose && mtime != -1) {
1223 struct timeval tval[2];
1224
1225 (void)gettimeofday(&tval[0], NULL);
1226 tval[1].tv_sec = mtime;
1227 tval[1].tv_usec = 0;
1228 (*closefunc)(fout);
1229 fout = NULL;
1230
1231 if (utimes(savefile, tval) == -1) {
1232 fprintf(ttyout,
1233 "Can't change modification time to %s",
1234 asctime(localtime(&mtime)));
1235 }
1236 }
1237 if (bytes > 0)
1238 ptransfer(0);
1239 bytes = 0;
1240
1241 rval = 0;
1242 goto cleanup_fetch_url;
1243
1244 improper:
1245 warnx("Improper response from `%s'", host);
1246
1247 cleanup_fetch_url:
1248 if (oldintr)
1249 (void)xsignal(SIGINT, oldintr);
1250 if (oldintp)
1251 (void)xsignal(SIGPIPE, oldintp);
1252 if (fin != NULL)
1253 fclose(fin);
1254 else if (s != -1)
1255 close(s);
1256 if (closefunc != NULL && fout != NULL)
1257 (*closefunc)(fout);
1258 FREEPTR(savefile);
1259 FREEPTR(user);
1260 FREEPTR(pass);
1261 FREEPTR(host);
1262 FREEPTR(port);
1263 FREEPTR(path);
1264 FREEPTR(decodedpath);
1265 FREEPTR(puser);
1266 FREEPTR(ppass);
1267 FREEPTR(buf);
1268 FREEPTR(auth);
1269 FREEPTR(location);
1270 FREEPTR(message);
1271 return (rval);
1272 }
1273
1274 /*
1275 * Abort a HTTP retrieval
1276 */
1277 void
1278 aborthttp(int notused)
1279 {
1280 char msgbuf[100];
1281 int len;
1282
1283 sigint_raised = 1;
1284 alarmtimer(0);
1285 len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
1286 write(fileno(ttyout), msgbuf, len);
1287 siglongjmp(httpabort, 1);
1288 }
1289
1290 /*
1291 * Retrieve ftp URL or classic ftp argument using FTP.
1292 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1293 * is still open (e.g, ftp xfer with trailing /)
1294 */
1295 static int
1296 fetch_ftp(const char *url)
1297 {
1298 char *cp, *xargv[5], rempath[MAXPATHLEN];
1299 char *host, *path, *dir, *file, *user, *pass;
1300 char *port;
1301 int dirhasglob, filehasglob, oautologin, rval, type, xargc;
1302 in_port_t portnum;
1303 url_t urltype;
1304
1305 host = path = dir = file = user = pass = NULL;
1306 port = NULL;
1307 rval = 1;
1308 type = TYPE_I;
1309
1310 if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1311 if ((parse_url(url, "URL", &urltype, &user, &pass,
1312 &host, &port, &portnum, &path) == -1) ||
1313 (user != NULL && *user == '\0') ||
1314 EMPTYSTRING(host)) {
1315 warnx("Invalid URL `%s'", url);
1316 goto cleanup_fetch_ftp;
1317 }
1318 url_decode(user);
1319 url_decode(pass);
1320 /*
1321 * Note: Don't url_decode(path) here. We need to keep the
1322 * distinction between "/" and "%2F" until later.
1323 */
1324
1325 /* check for trailing ';type=[aid]' */
1326 if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1327 if (strcasecmp(cp, ";type=a") == 0)
1328 type = TYPE_A;
1329 else if (strcasecmp(cp, ";type=i") == 0)
1330 type = TYPE_I;
1331 else if (strcasecmp(cp, ";type=d") == 0) {
1332 warnx(
1333 "Directory listing via a URL is not supported");
1334 goto cleanup_fetch_ftp;
1335 } else {
1336 warnx("Invalid suffix `%s' in URL `%s'", cp,
1337 url);
1338 goto cleanup_fetch_ftp;
1339 }
1340 *cp = 0;
1341 }
1342 } else { /* classic style `[user@]host:[file]' */
1343 urltype = CLASSIC_URL_T;
1344 host = xstrdup(url);
1345 cp = strchr(host, '@');
1346 if (cp != NULL) {
1347 *cp = '\0';
1348 user = host;
1349 anonftp = 0; /* disable anonftp */
1350 host = xstrdup(cp + 1);
1351 }
1352 cp = strchr(host, ':');
1353 if (cp != NULL) {
1354 *cp = '\0';
1355 path = xstrdup(cp + 1);
1356 }
1357 }
1358 if (EMPTYSTRING(host))
1359 goto cleanup_fetch_ftp;
1360
1361 /* Extract the file and (if present) directory name. */
1362 dir = path;
1363 if (! EMPTYSTRING(dir)) {
1364 /*
1365 * If we are dealing with classic `[user@]host:[path]' syntax,
1366 * then a path of the form `/file' (resulting from input of the
1367 * form `host:/file') means that we should do "CWD /" before
1368 * retrieving the file. So we set dir="/" and file="file".
1369 *
1370 * But if we are dealing with URLs like `ftp://host/path' then
1371 * a path of the form `/file' (resulting from a URL of the form
1372 * `ftp://host//file') means that we should do `CWD ' (with an
1373 * empty argument) before retrieving the file. So we set
1374 * dir="" and file="file".
1375 *
1376 * If the path does not contain / at all, we set dir=NULL.
1377 * (We get a path without any slashes if we are dealing with
1378 * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1379 *
1380 * In all other cases, we set dir to a string that does not
1381 * include the final '/' that separates the dir part from the
1382 * file part of the path. (This will be the empty string if
1383 * and only if we are dealing with a path of the form `/file'
1384 * resulting from an URL of the form `ftp://host//file'.)
1385 */
1386 cp = strrchr(dir, '/');
1387 if (cp == dir && urltype == CLASSIC_URL_T) {
1388 file = cp + 1;
1389 dir = "/";
1390 } else if (cp != NULL) {
1391 *cp++ = '\0';
1392 file = cp;
1393 } else {
1394 file = dir;
1395 dir = NULL;
1396 }
1397 } else
1398 dir = NULL;
1399 if (urltype == FTP_URL_T && file != NULL) {
1400 url_decode(file);
1401 /* but still don't url_decode(dir) */
1402 }
1403 if (debug)
1404 fprintf(ttyout,
1405 "fetch_ftp: user `%s' pass `%s' host %s port %s "
1406 "path `%s' dir `%s' file `%s'\n",
1407 user ? user : "<null>", pass ? pass : "<null>",
1408 host ? host : "<null>", port ? port : "<null>",
1409 path ? path : "<null>",
1410 dir ? dir : "<null>", file ? file : "<null>");
1411
1412 dirhasglob = filehasglob = 0;
1413 if (doglob && urltype == CLASSIC_URL_T) {
1414 if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
1415 dirhasglob = 1;
1416 if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
1417 filehasglob = 1;
1418 }
1419
1420 /* Set up the connection */
1421 if (connected)
1422 disconnect(0, NULL);
1423 xargv[0] = (char *)getprogname(); /* XXX discards const */
1424 xargv[1] = host;
1425 xargv[2] = NULL;
1426 xargc = 2;
1427 if (port) {
1428 xargv[2] = port;
1429 xargv[3] = NULL;
1430 xargc = 3;
1431 }
1432 oautologin = autologin;
1433 /* don't autologin in setpeer(), use ftp_login() below */
1434 autologin = 0;
1435 setpeer(xargc, xargv);
1436 autologin = oautologin;
1437 if ((connected == 0) ||
1438 (connected == 1 && !ftp_login(host, user, pass))) {
1439 warnx("Can't connect or login to host `%s'", host);
1440 goto cleanup_fetch_ftp;
1441 }
1442
1443 switch (type) {
1444 case TYPE_A:
1445 setascii(1, xargv);
1446 break;
1447 case TYPE_I:
1448 setbinary(1, xargv);
1449 break;
1450 default:
1451 errx(1, "fetch_ftp: unknown transfer type %d", type);
1452 }
1453
1454 /*
1455 * Change directories, if necessary.
1456 *
1457 * Note: don't use EMPTYSTRING(dir) below, because
1458 * dir=="" means something different from dir==NULL.
1459 */
1460 if (dir != NULL && !dirhasglob) {
1461 char *nextpart;
1462
1463 /*
1464 * If we are dealing with a classic `[user@]host:[path]'
1465 * (urltype is CLASSIC_URL_T) then we have a raw directory
1466 * name (not encoded in any way) and we can change
1467 * directories in one step.
1468 *
1469 * If we are dealing with an `ftp://host/path' URL
1470 * (urltype is FTP_URL_T), then RFC 1738 says we need to
1471 * send a separate CWD command for each unescaped "/"
1472 * in the path, and we have to interpret %hex escaping
1473 * *after* we find the slashes. It's possible to get
1474 * empty components here, (from multiple adjacent
1475 * slashes in the path) and RFC 1738 says that we should
1476 * still do `CWD ' (with a null argument) in such cases.
1477 *
1478 * Many ftp servers don't support `CWD ', so if there's an
1479 * error performing that command, bail out with a descriptive
1480 * message.
1481 *
1482 * Examples:
1483 *
1484 * host: dir="", urltype=CLASSIC_URL_T
1485 * logged in (to default directory)
1486 * host:file dir=NULL, urltype=CLASSIC_URL_T
1487 * "RETR file"
1488 * host:dir/ dir="dir", urltype=CLASSIC_URL_T
1489 * "CWD dir", logged in
1490 * ftp://host/ dir="", urltype=FTP_URL_T
1491 * logged in (to default directory)
1492 * ftp://host/dir/ dir="dir", urltype=FTP_URL_T
1493 * "CWD dir", logged in
1494 * ftp://host/file dir=NULL, urltype=FTP_URL_T
1495 * "RETR file"
1496 * ftp://host//file dir="", urltype=FTP_URL_T
1497 * "CWD ", "RETR file"
1498 * host:/file dir="/", urltype=CLASSIC_URL_T
1499 * "CWD /", "RETR file"
1500 * ftp://host///file dir="/", urltype=FTP_URL_T
1501 * "CWD ", "CWD ", "RETR file"
1502 * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T
1503 * "CWD /", "RETR file"
1504 * ftp://host/foo/file dir="foo", urltype=FTP_URL_T
1505 * "CWD foo", "RETR file"
1506 * ftp://host/foo/bar/file dir="foo/bar"
1507 * "CWD foo", "CWD bar", "RETR file"
1508 * ftp://host//foo/bar/file dir="/foo/bar"
1509 * "CWD ", "CWD foo", "CWD bar", "RETR file"
1510 * ftp://host/foo//bar/file dir="foo//bar"
1511 * "CWD foo", "CWD ", "CWD bar", "RETR file"
1512 * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar"
1513 * "CWD /", "CWD foo", "CWD bar", "RETR file"
1514 * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar"
1515 * "CWD /foo", "CWD bar", "RETR file"
1516 * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
1517 * "CWD /foo/bar", "RETR file"
1518 * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL
1519 * "RETR /foo/bar/file"
1520 *
1521 * Note that we don't need `dir' after this point.
1522 */
1523 do {
1524 if (urltype == FTP_URL_T) {
1525 nextpart = strchr(dir, '/');
1526 if (nextpart) {
1527 *nextpart = '\0';
1528 nextpart++;
1529 }
1530 url_decode(dir);
1531 } else
1532 nextpart = NULL;
1533 if (debug)
1534 fprintf(ttyout, "dir `%s', nextpart `%s'\n",
1535 dir ? dir : "<null>",
1536 nextpart ? nextpart : "<null>");
1537 if (urltype == FTP_URL_T || *dir != '\0') {
1538 xargv[0] = "cd";
1539 xargv[1] = dir;
1540 xargv[2] = NULL;
1541 dirchange = 0;
1542 cd(2, xargv);
1543 if (! dirchange) {
1544 if (*dir == '\0' && code == 500)
1545 fprintf(stderr,
1546 "\n"
1547 "ftp: The `CWD ' command (without a directory), which is required by\n"
1548 " RFC 1738 to support the empty directory in the URL pathname (`//'),\n"
1549 " conflicts with the server's conformance to RFC 959.\n"
1550 " Try the same URL without the `//' in the URL pathname.\n"
1551 "\n");
1552 goto cleanup_fetch_ftp;
1553 }
1554 }
1555 dir = nextpart;
1556 } while (dir != NULL);
1557 }
1558
1559 if (EMPTYSTRING(file)) {
1560 rval = -1;
1561 goto cleanup_fetch_ftp;
1562 }
1563
1564 if (dirhasglob) {
1565 (void)strlcpy(rempath, dir, sizeof(rempath));
1566 (void)strlcat(rempath, "/", sizeof(rempath));
1567 (void)strlcat(rempath, file, sizeof(rempath));
1568 file = rempath;
1569 }
1570
1571 /* Fetch the file(s). */
1572 xargc = 2;
1573 xargv[0] = "get";
1574 xargv[1] = file;
1575 xargv[2] = NULL;
1576 if (dirhasglob || filehasglob) {
1577 int ointeractive;
1578
1579 ointeractive = interactive;
1580 interactive = 0;
1581 xargv[0] = "mget";
1582 mget(xargc, xargv);
1583 interactive = ointeractive;
1584 } else {
1585 if (outfile == NULL) {
1586 cp = strrchr(file, '/'); /* find savefile */
1587 if (cp != NULL)
1588 outfile = cp + 1;
1589 else
1590 outfile = file;
1591 }
1592 xargv[2] = (char *)outfile;
1593 xargv[3] = NULL;
1594 xargc++;
1595 if (restartautofetch)
1596 reget(xargc, xargv);
1597 else
1598 get(xargc, xargv);
1599 }
1600
1601 if ((code / 100) == COMPLETE)
1602 rval = 0;
1603
1604 cleanup_fetch_ftp:
1605 FREEPTR(host);
1606 FREEPTR(path);
1607 FREEPTR(user);
1608 FREEPTR(pass);
1609 return (rval);
1610 }
1611
1612 /*
1613 * Retrieve the given file to outfile.
1614 * Supports arguments of the form:
1615 * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else
1616 * call fetch_ftp()
1617 * "http://host/path" call fetch_url() to use HTTP
1618 * "file:///path" call fetch_url() to copy
1619 * "about:..." print a message
1620 *
1621 * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1622 * is still open (e.g, ftp xfer with trailing /)
1623 */
1624 static int
1625 go_fetch(const char *url)
1626 {
1627 char *proxy;
1628
1629 #ifndef NO_ABOUT
1630 /*
1631 * Check for about:*
1632 */
1633 if (strncasecmp(url, ABOUT_URL, sizeof(ABOUT_URL) - 1) == 0) {
1634 url += sizeof(ABOUT_URL) -1;
1635 if (strcasecmp(url, "ftp") == 0 ||
1636 strcasecmp(url, "tnftp") == 0) {
1637 fputs(
1638 "This version of ftp has been enhanced by Luke Mewburn <lukem (at) NetBSD.org>\n"
1639 "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout);
1640 } else if (strcasecmp(url, "lukem") == 0) {
1641 fputs(
1642 "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1643 "Please email feedback to <lukem (at) NetBSD.org>.\n", ttyout);
1644 } else if (strcasecmp(url, "netbsd") == 0) {
1645 fputs(
1646 "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1647 "For more information, see http://www.NetBSD.org/\n", ttyout);
1648 } else if (strcasecmp(url, "version") == 0) {
1649 fprintf(ttyout, "Version: %s %s%s\n",
1650 FTP_PRODUCT, FTP_VERSION,
1651 #ifdef INET6
1652 ""
1653 #else
1654 " (-IPv6)"
1655 #endif
1656 );
1657 } else {
1658 fprintf(ttyout, "`%s' is an interesting topic.\n", url);
1659 }
1660 fputs("\n", ttyout);
1661 return (0);
1662 }
1663 #endif
1664
1665 /*
1666 * Check for file:// and http:// URLs.
1667 */
1668 if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1669 strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0)
1670 return (fetch_url(url, NULL, NULL, NULL));
1671
1672 /*
1673 * Try FTP URL-style and host:file arguments next.
1674 * If ftpproxy is set with an FTP URL, use fetch_url()
1675 * Othewise, use fetch_ftp().
1676 */
1677 proxy = getoptionvalue("ftp_proxy");
1678 if (!EMPTYSTRING(proxy) &&
1679 strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0)
1680 return (fetch_url(url, NULL, NULL, NULL));
1681
1682 return (fetch_ftp(url));
1683 }
1684
1685 /*
1686 * Retrieve multiple files from the command line,
1687 * calling go_fetch() for each file.
1688 *
1689 * If an ftp path has a trailing "/", the path will be cd-ed into and
1690 * the connection remains open, and the function will return -1
1691 * (to indicate the connection is alive).
1692 * If the transfer was interrupted with SIGINT, the return value is EXIT_SIGINT.
1693 * If an error occurs the return value will be the offset+1 in
1694 * argv[] of the file that caused a problem (i.e, argv[x]
1695 * returns x+1)
1696 * Otherwise, 0 is returned if all files retrieved successfully.
1697 */
1698 int
1699 auto_fetch(int argc, char *argv[])
1700 {
1701 volatile int argpos;
1702 int rval;
1703
1704 argpos = 0;
1705
1706 sigint_raised = 0;
1707 if (sigsetjmp(toplevel, 1)) {
1708 if (connected)
1709 disconnect(0, NULL);
1710 if (rval > 0)
1711 rval = argpos + 1;
1712 if (sigint_raised)
1713 rval = EXIT_SIGINT;
1714 return (rval);
1715 }
1716 (void)xsignal(SIGINT, intr);
1717 (void)xsignal(SIGPIPE, lostpeer);
1718
1719 /*
1720 * Loop through as long as there's files to fetch.
1721 */
1722 for (rval = 0; (rval == 0) && (argpos < argc); argpos++) {
1723 if (strchr(argv[argpos], ':') == NULL)
1724 break;
1725 redirect_loop = 0;
1726 if (!anonftp)
1727 anonftp = 2; /* Handle "automatic" transfers. */
1728 rval = go_fetch(argv[argpos]);
1729 if (outfile != NULL && strcmp(outfile, "-") != 0
1730 && outfile[0] != '|')
1731 outfile = NULL;
1732 if (rval > 0)
1733 rval = argpos + 1;
1734 if (sigint_raised)
1735 rval = EXIT_SIGINT;
1736 }
1737
1738 if (connected && rval != -1)
1739 disconnect(0, NULL);
1740 return (rval);
1741 }
1742
1743
1744 int
1745 auto_put(int argc, char **argv, const char *uploadserver)
1746 {
1747 char *uargv[4], *path, *pathsep;
1748 int uargc, rval, len;
1749
1750 uargc = 0;
1751 uargv[uargc++] = "mput";
1752 uargv[uargc++] = argv[0];
1753 uargv[2] = uargv[3] = NULL;
1754 pathsep = NULL;
1755 rval = 1;
1756
1757 if (debug)
1758 fprintf(ttyout, "auto_put: target `%s'\n", uploadserver);
1759
1760 path = xstrdup(uploadserver);
1761 len = strlen(path);
1762 if (path[len - 1] != '/' && path[len - 1] != ':') {
1763 /*
1764 * make sure we always pass a directory to auto_fetch
1765 */
1766 if (argc > 1) { /* more than one file to upload */
1767 int len;
1768
1769 len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
1770 free(path);
1771 path = (char *)xmalloc(len);
1772 (void)strlcpy(path, uploadserver, len);
1773 (void)strlcat(path, "/", len);
1774 } else { /* single file to upload */
1775 uargv[0] = "put";
1776 pathsep = strrchr(path, '/');
1777 if (pathsep == NULL) {
1778 pathsep = strrchr(path, ':');
1779 if (pathsep == NULL) {
1780 warnx("Invalid URL `%s'", path);
1781 goto cleanup_auto_put;
1782 }
1783 pathsep++;
1784 uargv[2] = xstrdup(pathsep);
1785 pathsep[0] = '/';
1786 } else
1787 uargv[2] = xstrdup(pathsep + 1);
1788 pathsep[1] = '\0';
1789 uargc++;
1790 }
1791 }
1792 if (debug)
1793 fprintf(ttyout, "auto_put: URL `%s' argv[2] `%s'\n",
1794 path, uargv[2] ? uargv[2] : "<null>");
1795
1796 /* connect and cwd */
1797 rval = auto_fetch(1, &path);
1798 free(path);
1799 if(rval >= 0)
1800 goto cleanup_auto_put;
1801
1802 /* XXX : is this the best way? */
1803 if (uargc == 3) {
1804 uargv[1] = argv[0];
1805 put(uargc, uargv);
1806 goto cleanup_auto_put;
1807 }
1808
1809 for(; argv[0] != NULL; argv++) {
1810 uargv[1] = argv[0];
1811 mput(uargc, uargv);
1812 }
1813 rval = 0;
1814
1815 cleanup_auto_put:
1816 FREEPTR(uargv[2]);
1817 return (rval);
1818 }
1819