ftp.c revision 67043
137535Sdes/*-
237535Sdes * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
337535Sdes * All rights reserved.
437535Sdes *
537535Sdes * Redistribution and use in source and binary forms, with or without
637535Sdes * modification, are permitted provided that the following conditions
737535Sdes * are met:
837535Sdes * 1. Redistributions of source code must retain the above copyright
937535Sdes *    notice, this list of conditions and the following disclaimer
1037535Sdes *    in this position and unchanged.
1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright
1237535Sdes *    notice, this list of conditions and the following disclaimer in the
1337535Sdes *    documentation and/or other materials provided with the distribution.
1437535Sdes * 3. The name of the author may not be used to endorse or promote products
1537535Sdes *    derived from this software without specific prior written permission
1637535Sdes *
1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2737535Sdes *
2850476Speter * $FreeBSD: head/lib/libfetch/ftp.c 67043 2000-10-12 22:10:26Z des $
2937535Sdes */
3037535Sdes
3137535Sdes/*
3237571Sdes * Portions of this code were taken from or based on ftpio.c:
3337535Sdes *
3437535Sdes * ----------------------------------------------------------------------------
3537535Sdes * "THE BEER-WARE LICENSE" (Revision 42):
3637535Sdes * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
3737535Sdes * can do whatever you want with this stuff. If we meet some day, and you think
3837535Sdes * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
3937535Sdes * ----------------------------------------------------------------------------
4037535Sdes *
4137535Sdes * Major Changelog:
4237535Sdes *
4337535Sdes * Dag-Erling Co�dan Sm�rgrav
4437535Sdes * 9 Jun 1998
4537535Sdes *
4637535Sdes * Incorporated into libfetch
4737535Sdes *
4837535Sdes * Jordan K. Hubbard
4937535Sdes * 17 Jan 1996
5037535Sdes *
5137535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special
5237535Sdes * `state' of FTP_t
5337535Sdes *
5437535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
5537535Sdes *
5637535Sdes */
5737535Sdes
5841862Sdes#include <sys/param.h>
5937535Sdes#include <sys/socket.h>
6037535Sdes#include <netinet/in.h>
6137535Sdes
6237535Sdes#include <ctype.h>
6355557Sdes#include <errno.h>
6460188Sdes#include <netdb.h>
6537573Sdes#include <stdarg.h>
6637535Sdes#include <stdio.h>
6737571Sdes#include <stdlib.h>
6837535Sdes#include <string.h>
6941869Sdes#include <time.h>
7037571Sdes#include <unistd.h>
7137535Sdes
7237535Sdes#include "fetch.h"
7340939Sdes#include "common.h"
7441862Sdes#include "ftperr.h"
7537535Sdes
7637535Sdes#define FTP_ANONYMOUS_USER	"ftp"
7737535Sdes#define FTP_ANONYMOUS_PASSWORD	"ftp"
7837535Sdes
7964883Sdes#define FTP_CONNECTION_ALREADY_OPEN	125
8037573Sdes#define FTP_OPEN_DATA_CONNECTION	150
8137573Sdes#define FTP_OK				200
8241869Sdes#define FTP_FILE_STATUS			213
8341863Sdes#define FTP_SERVICE_READY		220
8437573Sdes#define FTP_PASSIVE_MODE		227
8560737Sume#define FTP_LPASSIVE_MODE		228
8660737Sume#define FTP_EPASSIVE_MODE		229
8737573Sdes#define FTP_LOGGED_IN			230
8837573Sdes#define FTP_FILE_ACTION_OK		250
8937573Sdes#define FTP_NEED_PASSWORD		331
9037573Sdes#define FTP_NEED_ACCOUNT		332
9160188Sdes#define FTP_FILE_OK			350
9255557Sdes#define FTP_SYNTAX_ERROR		500
9363336Sdes#define FTP_PROTOCOL_ERROR		999
9437573Sdes
9540975Sdesstatic struct url cached_host;
9655557Sdesstatic int cached_socket;
9737535Sdes
9855557Sdesstatic char *last_reply;
9955557Sdesstatic size_t lr_size, lr_length;
10055557Sdesstatic int last_code;
10137571Sdes
10255557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10360707Sdes			 && isdigit(foo[2]) \
10460707Sdes                         && (foo[3] == ' ' || foo[3] == '\0'))
10555557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10655557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
10755557Sdes
10860737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */
10960737Sumestatic void
11060737Sumeunmappedaddr(struct sockaddr_in6 *sin6)
11160737Sume{
11260737Sume    struct sockaddr_in *sin4;
11360737Sume    u_int32_t addr;
11460737Sume    int port;
11560737Sume
11660737Sume    if (sin6->sin6_family != AF_INET6 ||
11760737Sume	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
11860737Sume	return;
11960737Sume    sin4 = (struct sockaddr_in *)sin6;
12060737Sume    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
12160737Sume    port = sin6->sin6_port;
12260737Sume    memset(sin4, 0, sizeof(struct sockaddr_in));
12360737Sume    sin4->sin_addr.s_addr = addr;
12460737Sume    sin4->sin_port = port;
12560737Sume    sin4->sin_family = AF_INET;
12660737Sume    sin4->sin_len = sizeof(struct sockaddr_in);
12760737Sume}
12860737Sume
12937571Sdes/*
13055557Sdes * Get server response
13137535Sdes */
13237535Sdesstatic int
13355557Sdes_ftp_chkerr(int cd)
13437535Sdes{
13562215Sdes    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
13662215Sdes	_fetch_syserr();
13762215Sdes	return -1;
13862215Sdes    }
13962215Sdes    if (isftpinfo(last_reply)) {
14062215Sdes	while (!isftpreply(last_reply)) {
14162215Sdes	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
14262215Sdes		_fetch_syserr();
14362215Sdes		return -1;
14462215Sdes	    }
14562215Sdes	}
14662215Sdes    }
14755557Sdes
14855557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
14955557Sdes	lr_length--;
15055557Sdes    last_reply[lr_length] = 0;
15137573Sdes
15255557Sdes    if (!isftpreply(last_reply)) {
15363336Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
15437535Sdes	return -1;
15537571Sdes    }
15637535Sdes
15755557Sdes    last_code = (last_reply[0] - '0') * 100
15855557Sdes	+ (last_reply[1] - '0') * 10
15955557Sdes	+ (last_reply[2] - '0');
16055557Sdes
16155557Sdes    return last_code;
16237535Sdes}
16337535Sdes
16437535Sdes/*
16537573Sdes * Send a command and check reply
16637535Sdes */
16737535Sdesstatic int
16855557Sdes_ftp_cmd(int cd, char *fmt, ...)
16937535Sdes{
17037573Sdes    va_list ap;
17162982Sdes    size_t len;
17255557Sdes    char *msg;
17355557Sdes    int r;
17437573Sdes
17537573Sdes    va_start(ap, fmt);
17662982Sdes    len = vasprintf(&msg, fmt, ap);
17755557Sdes    va_end(ap);
17855557Sdes
17955557Sdes    if (msg == NULL) {
18055557Sdes	errno = ENOMEM;
18155557Sdes	_fetch_syserr();
18255557Sdes	return -1;
18355557Sdes    }
18462982Sdes
18562982Sdes    r = _fetch_putln(cd, msg, len);
18655557Sdes    free(msg);
18762982Sdes
18855557Sdes    if (r == -1) {
18955557Sdes	_fetch_syserr();
19055557Sdes	return -1;
19155557Sdes    }
19237571Sdes
19355557Sdes    return _ftp_chkerr(cd);
19437535Sdes}
19537535Sdes
19637535Sdes/*
19763340Sdes * Return a pointer to the filename part of a path
19863340Sdes */
19963340Sdesstatic char *
20063340Sdes_ftp_filename(char *file)
20163340Sdes{
20263340Sdes    char *s;
20363340Sdes
20463340Sdes    if ((s = strrchr(file, '/')) == NULL)
20563340Sdes	return file;
20663340Sdes    else
20763340Sdes	return s + 1;
20863340Sdes}
20963340Sdes
21063340Sdes/*
21163340Sdes * Change working directory to the directory that contains the
21263340Sdes * specified file.
21363340Sdes */
21463340Sdesstatic int
21563340Sdes_ftp_cwd(int cd, char *file)
21663340Sdes{
21763340Sdes    char *s;
21863340Sdes    int e;
21963340Sdes
22063585Sdes    if ((s = strrchr(file, '/')) == NULL || s == file) {
22163340Sdes	e = _ftp_cmd(cd, "CWD /");
22263340Sdes    } else {
22363340Sdes	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
22463340Sdes    }
22563340Sdes    if (e != FTP_FILE_ACTION_OK) {
22663340Sdes	_ftp_seterr(e);
22763340Sdes	return -1;
22863340Sdes    }
22963340Sdes    return 0;
23063340Sdes}
23163340Sdes
23263340Sdes/*
23363340Sdes * Request and parse file stats
23463340Sdes */
23563340Sdesstatic int
23663340Sdes_ftp_stat(int cd, char *file, struct url_stat *us)
23763340Sdes{
23863340Sdes    char *ln, *s;
23963340Sdes    struct tm tm;
24063340Sdes    time_t t;
24163340Sdes    int e;
24263340Sdes
24363392Sdes    us->size = -1;
24463392Sdes    us->atime = us->mtime = 0;
24563392Sdes
24663340Sdes    if ((s = strrchr(file, '/')) == NULL)
24763340Sdes	s = file;
24863340Sdes    else
24963340Sdes	++s;
25063340Sdes
25163340Sdes    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
25263340Sdes	_ftp_seterr(e);
25363340Sdes	return -1;
25463340Sdes    }
25563340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
25663340Sdes	/* nothing */ ;
25763340Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
25863340Sdes	us->size = us->size * 10 + *ln - '0';
25963340Sdes    if (*ln && !isspace(*ln)) {
26063340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
26163340Sdes	return -1;
26263340Sdes    }
26363847Sdes    if (us->size == 0)
26463847Sdes	us->size = -1;
26563340Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
26663340Sdes
26763340Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
26863340Sdes	_ftp_seterr(e);
26963340Sdes	return -1;
27063340Sdes    }
27163340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
27263340Sdes	/* nothing */ ;
27363340Sdes    switch (strspn(ln, "0123456789")) {
27463340Sdes    case 14:
27563340Sdes	break;
27663340Sdes    case 15:
27763340Sdes	ln++;
27863340Sdes	ln[0] = '2';
27963340Sdes	ln[1] = '0';
28063340Sdes	break;
28163340Sdes    default:
28263340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
28363340Sdes	return -1;
28463340Sdes    }
28563340Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
28663340Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
28763340Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
28863340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
28963340Sdes	return -1;
29063340Sdes    }
29163340Sdes    tm.tm_mon--;
29263340Sdes    tm.tm_year -= 1900;
29363340Sdes    tm.tm_isdst = -1;
29463340Sdes    t = timegm(&tm);
29563340Sdes    if (t == (time_t)-1)
29663340Sdes	t = time(NULL);
29763340Sdes    us->mtime = t;
29863340Sdes    us->atime = t;
29963340Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
30063340Sdes		  "%02d:%02d:%02d\033[m]\n",
30163340Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
30263340Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
30363340Sdes    return 0;
30463340Sdes}
30563340Sdes
30663340Sdes/*
30737608Sdes * Transfer file
30837535Sdes */
30937535Sdesstatic FILE *
31060188Sdes_ftp_transfer(int cd, char *oper, char *file,
31160188Sdes	      char *mode, off_t offset, char *flags)
31237535Sdes{
31360737Sume    struct sockaddr_storage sin;
31460737Sume    struct sockaddr_in6 *sin6;
31560737Sume    struct sockaddr_in *sin4;
31655544Sdes    int pasv, high, verbose;
31755544Sdes    int e, sd = -1;
31855544Sdes    socklen_t l;
31937573Sdes    char *s;
32037573Sdes    FILE *df;
32155544Sdes
32255544Sdes    /* check flags */
32355544Sdes    pasv = (flags && strchr(flags, 'p'));
32455544Sdes    high = (flags && strchr(flags, 'h'));
32555544Sdes    verbose = (flags && strchr(flags, 'v'));
32655544Sdes
32760951Sdes    /* passive mode */
32860951Sdes    if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
32960951Sdes	pasv = (strncasecmp(s, "no", 2) != 0);
33060951Sdes
33160737Sume    /* find our own address, bind, and listen */
33260737Sume    l = sizeof sin;
33360737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
33460737Sume	goto sysouch;
33560737Sume    if (sin.ss_family == AF_INET6)
33660737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
33760737Sume
33837573Sdes    /* open data socket */
33960737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
34040939Sdes	_fetch_syserr();
34137573Sdes	return NULL;
34237573Sdes    }
34337573Sdes
34437573Sdes    if (pasv) {
34560737Sume	u_char addr[64];
34637573Sdes	char *ln, *p;
34737573Sdes	int i;
34860737Sume	int port;
34937573Sdes
35037573Sdes	/* send PASV command */
35155544Sdes	if (verbose)
35255544Sdes	    _fetch_info("setting passive mode");
35360737Sume	switch (sin.ss_family) {
35460737Sume	case AF_INET:
35560737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
35660737Sume		goto ouch;
35760737Sume	    break;
35860737Sume	case AF_INET6:
35960737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
36060737Sume		if (e == -1)
36160737Sume		    goto ouch;
36260737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
36360737Sume		    goto ouch;
36460737Sume	    }
36560737Sume	    break;
36660737Sume	default:
36763336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
36837573Sdes	    goto ouch;
36960737Sume	}
37037573Sdes
37155544Sdes	/*
37255544Sdes	 * Find address and port number. The reply to the PASV command
37355544Sdes         * is IMHO the one and only weak point in the FTP protocol.
37455544Sdes	 */
37555557Sdes	ln = last_reply;
37662888Sume      	switch (e) {
37760737Sume	case FTP_PASSIVE_MODE:
37860737Sume	case FTP_LPASSIVE_MODE:
37962888Sume	    for (p = ln + 3; *p && !isdigit(*p); p++)
38062888Sume		/* nothing */ ;
38162888Sume	    if (!*p) {
38263336Sdes		e = FTP_PROTOCOL_ERROR;
38362888Sume		goto ouch;
38462888Sume	    }
38560737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
38660737Sume	    for (i = 0; *p && i < l; i++, p++)
38760737Sume		addr[i] = strtol(p, &p, 10);
38860737Sume	    if (i < l) {
38963336Sdes		e = FTP_PROTOCOL_ERROR;
39060737Sume		goto ouch;
39160737Sume	    }
39260737Sume	    break;
39360737Sume	case FTP_EPASSIVE_MODE:
39462888Sume	    for (p = ln + 3; *p && *p != '('; p++)
39562888Sume		/* nothing */ ;
39662888Sume	    if (!*p) {
39763336Sdes		e = FTP_PROTOCOL_ERROR;
39862888Sume		goto ouch;
39962888Sume	    }
40062888Sume	    ++p;
40160737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
40260737Sume		       &port, &addr[3]) != 5 ||
40360737Sume		addr[0] != addr[1] ||
40460737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
40563336Sdes		e = FTP_PROTOCOL_ERROR;
40660737Sume		goto ouch;
40760737Sume	    }
40860737Sume	    break;
40960737Sume	}
41037573Sdes
41160188Sdes	/* seek to required offset */
41260188Sdes	if (offset)
41360188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
41460188Sdes		goto sysouch;
41560188Sdes
41637573Sdes	/* construct sockaddr for data socket */
41760188Sdes	l = sizeof sin;
41855557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
41937573Sdes	    goto sysouch;
42060737Sume	if (sin.ss_family == AF_INET6)
42160737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
42260737Sume	switch (sin.ss_family) {
42360737Sume	case AF_INET6:
42460737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
42560737Sume	    if (e == FTP_EPASSIVE_MODE)
42660737Sume		sin6->sin6_port = htons(port);
42760737Sume	    else {
42860737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
42960737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
43060737Sume	    }
43160737Sume	    break;
43260737Sume	case AF_INET:
43360737Sume	    sin4 = (struct sockaddr_in *)&sin;
43460737Sume	    if (e == FTP_EPASSIVE_MODE)
43560737Sume		sin4->sin_port = htons(port);
43660737Sume	    else {
43760737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
43860737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
43960737Sume	    }
44060737Sume	    break;
44160737Sume	default:
44263336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
44360737Sume	    break;
44460737Sume	}
44537573Sdes
44637573Sdes	/* connect to data port */
44755544Sdes	if (verbose)
44855544Sdes	    _fetch_info("opening data connection");
44960737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
45037573Sdes	    goto sysouch;
45160188Sdes
45237573Sdes	/* make the server initiate the transfer */
45361866Sdes	if (verbose)
45461866Sdes	    _fetch_info("initiating transfer");
45563340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
45664883Sdes	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
45737573Sdes	    goto ouch;
45837573Sdes
45937573Sdes    } else {
46037573Sdes	u_int32_t a;
46137573Sdes	u_short p;
46255544Sdes	int arg, d;
46360737Sume	char *ap;
46460737Sume	char hname[INET6_ADDRSTRLEN];
46537573Sdes
46660737Sume	switch (sin.ss_family) {
46760737Sume	case AF_INET6:
46860737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
46960737Sume#ifdef IPV6_PORTRANGE
47060737Sume	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
47160737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
47260737Sume			   (char *)&arg, sizeof(arg)) == -1)
47360737Sume		goto sysouch;
47460737Sume#endif
47560737Sume	    break;
47660737Sume	case AF_INET:
47760737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
47860737Sume	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
47960737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
48060737Sume			   (char *)&arg, sizeof arg) == -1)
48160737Sume		goto sysouch;
48260737Sume	    break;
48360737Sume	}
48455544Sdes	if (verbose)
48555544Sdes	    _fetch_info("binding data socket");
48660737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
48737573Sdes	    goto sysouch;
48838394Sdes	if (listen(sd, 1) == -1)
48937573Sdes	    goto sysouch;
49037573Sdes
49137573Sdes	/* find what port we're on and tell the server */
49238394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
49337573Sdes	    goto sysouch;
49460737Sume	switch (sin.ss_family) {
49560737Sume	case AF_INET:
49660737Sume	    sin4 = (struct sockaddr_in *)&sin;
49760737Sume	    a = ntohl(sin4->sin_addr.s_addr);
49860737Sume	    p = ntohs(sin4->sin_port);
49960737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
50060737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
50160737Sume			 (a >> 8) & 0xff, a & 0xff,
50260737Sume			 (p >> 8) & 0xff, p & 0xff);
50360737Sume	    break;
50460737Sume	case AF_INET6:
50560737Sume#define UC(b)	(((int)b)&0xff)
50660737Sume	    e = -1;
50760737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
50860737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
50960737Sume			    hname, sizeof(hname),
51060737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
51160737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
51260737Sume			     htons(sin6->sin6_port));
51360737Sume		if (e == -1)
51460737Sume		    goto ouch;
51560737Sume	    }
51660737Sume	    if (e != FTP_OK) {
51760737Sume		ap = (char *)&sin6->sin6_addr;
51860737Sume		e = _ftp_cmd(cd,
51960737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
52060737Sume			     6, 16,
52160737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
52260737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
52360737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
52460737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
52560737Sume			     2,
52660737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
52760737Sume			     ntohs(sin6->sin6_port)        & 0xff);
52860737Sume	    }
52960737Sume	    break;
53060737Sume	default:
53163336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
53260737Sume	    goto ouch;
53360737Sume	}
53441869Sdes	if (e != FTP_OK)
53537573Sdes	    goto ouch;
53637573Sdes
53762256Sdes	/* seek to required offset */
53862256Sdes	if (offset)
53962256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
54062256Sdes		goto sysouch;
54162256Sdes
54237573Sdes	/* make the server initiate the transfer */
54355544Sdes	if (verbose)
54455544Sdes	    _fetch_info("initiating transfer");
54563340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
54641869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
54737573Sdes	    goto ouch;
54837573Sdes
54937573Sdes	/* accept the incoming connection and go to town */
55038394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
55137573Sdes	    goto sysouch;
55237573Sdes	close(sd);
55337573Sdes	sd = d;
55437573Sdes    }
55537573Sdes
55637608Sdes    if ((df = fdopen(sd, mode)) == NULL)
55737573Sdes	goto sysouch;
55837573Sdes    return df;
55937573Sdes
56037573Sdessysouch:
56140939Sdes    _fetch_syserr();
56260737Sume    if (sd >= 0)
56360737Sume	close(sd);
56441869Sdes    return NULL;
56541869Sdes
56637573Sdesouch:
56755557Sdes    if (e != -1)
56855557Sdes	_ftp_seterr(e);
56960737Sume    if (sd >= 0)
57060737Sume	close(sd);
57137535Sdes    return NULL;
57237535Sdes}
57337535Sdes
57437571Sdes/*
57563842Sdes * Return default port
57663842Sdes */
57763842Sdesstatic int
57863842Sdes_ftp_default_port(void)
57963842Sdes{
58063842Sdes    struct servent *se;
58163842Sdes
58267043Sdes    if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL)
58363842Sdes	return ntohs(se->s_port);
58463842Sdes    return FTP_DEFAULT_PORT;
58563842Sdes}
58663842Sdes
58763842Sdes/*
58837571Sdes * Log on to FTP server
58937535Sdes */
59055557Sdesstatic int
59167043Sdes_ftp_connect(struct url *url, struct url *purl, char *flags)
59237571Sdes{
59367043Sdes    int cd, e, direct, verbose;
59460737Sume#ifdef INET6
59560737Sume    int af = AF_UNSPEC;
59660737Sume#else
59760737Sume    int af = AF_INET;
59860737Sume#endif
59960791Sume    const char *logname;
60067043Sdes    char *user, *pwd;
60160791Sume    char localhost[MAXHOSTNAMELEN];
60260791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
60337571Sdes
60455544Sdes    direct = (flags && strchr(flags, 'd'));
60555544Sdes    verbose = (flags && strchr(flags, 'v'));
60660737Sume    if ((flags && strchr(flags, '4')))
60760737Sume	af = AF_INET;
60860737Sume    else if ((flags && strchr(flags, '6')))
60960737Sume	af = AF_INET6;
61060737Sume
61167043Sdes    if (direct)
61267043Sdes	purl = NULL;
61367043Sdes
61437608Sdes    /* check for proxy */
61567043Sdes    if (purl) {
61667043Sdes	/* XXX proxy authentication! */
61767043Sdes	cd = _fetch_connect(purl->host, purl->port, af, verbose);
61837608Sdes    } else {
61937608Sdes	/* no proxy, go straight to target */
62067043Sdes	cd = _fetch_connect(url->host, url->port, af, verbose);
62167043Sdes	purl = NULL;
62237608Sdes    }
62337608Sdes
62437608Sdes    /* check connection */
62555557Sdes    if (cd == -1) {
62640939Sdes	_fetch_syserr();
62737571Sdes	return NULL;
62837571Sdes    }
62937608Sdes
63037571Sdes    /* expect welcome message */
63155557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
63237571Sdes	goto fouch;
63367043Sdes
63467043Sdes    /* XXX FTP_AUTH, and maybe .netrc */
63537571Sdes
63637571Sdes    /* send user name and password */
63767043Sdes    user = url->user;
63837608Sdes    if (!user || !*user)
63937608Sdes	user = FTP_ANONYMOUS_USER;
64067043Sdes    if (purl && url->port == FTP_DEFAULT_PORT)
64167043Sdes	e = _ftp_cmd(cd, "USER %s@%s", url->user, url->host);
64267043Sdes    else if (purl)
64367043Sdes	e = _ftp_cmd(cd, "USER %s@%s@%d", url->user, url->host, url->port);
64463712Sdes    else
64567043Sdes	e = _ftp_cmd(cd, "USER %s", url->user);
64637608Sdes
64737608Sdes    /* did the server request a password? */
64837608Sdes    if (e == FTP_NEED_PASSWORD) {
64967043Sdes	pwd = url->pwd;
65037608Sdes	if (!pwd || !*pwd)
65160791Sume	    pwd = getenv("FTP_PASSWORD");
65260791Sume	if (!pwd || !*pwd) {
65360791Sume	    if ((logname = getlogin()) == 0)
65460791Sume		logname = FTP_ANONYMOUS_PASSWORD;
65560791Sume	    gethostname(localhost, sizeof localhost);
65660791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
65760791Sume	    pwd = pbuf;
65860791Sume	}
65955557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
66037608Sdes    }
66137608Sdes
66237608Sdes    /* did the server request an account? */
66341869Sdes    if (e == FTP_NEED_ACCOUNT)
66441863Sdes	goto fouch;
66537608Sdes
66637608Sdes    /* we should be done by now */
66741869Sdes    if (e != FTP_LOGGED_IN)
66837571Sdes	goto fouch;
66937571Sdes
67037571Sdes    /* might as well select mode and type at once */
67137571Sdes#ifdef FTP_FORCE_STREAM_MODE
67255557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
67341869Sdes	goto fouch;
67437571Sdes#endif
67555557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
67641869Sdes	goto fouch;
67737571Sdes
67837571Sdes    /* done */
67955557Sdes    return cd;
68037571Sdes
68137571Sdesfouch:
68255557Sdes    if (e != -1)
68355557Sdes	_ftp_seterr(e);
68455557Sdes    close(cd);
68537571Sdes    return NULL;
68637571Sdes}
68737571Sdes
68837571Sdes/*
68937571Sdes * Disconnect from server
69037571Sdes */
69137571Sdesstatic void
69255557Sdes_ftp_disconnect(int cd)
69337571Sdes{
69455557Sdes    (void)_ftp_cmd(cd, "QUIT");
69555557Sdes    close(cd);
69637571Sdes}
69737571Sdes
69837571Sdes/*
69937571Sdes * Check if we're already connected
70037571Sdes */
70137571Sdesstatic int
70240975Sdes_ftp_isconnected(struct url *url)
70337571Sdes{
70437571Sdes    return (cached_socket
70537571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
70637571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
70737571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
70837571Sdes	    && (url->port == cached_host.port));
70937571Sdes}
71037571Sdes
71137608Sdes/*
71241869Sdes * Check the cache, reconnect if no luck
71337608Sdes */
71455557Sdesstatic int
71567043Sdes_ftp_cached_connect(struct url *url, struct url *purl, char *flags)
71637535Sdes{
71755557Sdes    int e, cd;
71837535Sdes
71955557Sdes    cd = -1;
72041869Sdes
72137571Sdes    /* set default port */
72263842Sdes    if (!url->port)
72363842Sdes	url->port = _ftp_default_port();
72437535Sdes
72541863Sdes    /* try to use previously cached connection */
72655557Sdes    if (_ftp_isconnected(url)) {
72755557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
72855557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
72967043Sdes	    return cached_socket;
73055557Sdes    }
73137571Sdes
73237571Sdes    /* connect to server */
73367043Sdes    if ((cd = _ftp_connect(url, purl, flags)) == -1)
73467043Sdes	return -1;
73567043Sdes    if (cached_socket)
73667043Sdes	_ftp_disconnect(cached_socket);
73767043Sdes    cached_socket = cd;
73867043Sdes    memcpy(&cached_host, url, sizeof *url);
73955557Sdes    return cd;
74037535Sdes}
74137535Sdes
74237571Sdes/*
74367043Sdes * Check the proxy settings
74463713Sdes */
74567043Sdesstatic struct url *
74667043Sdes_ftp_get_proxy(void)
74763713Sdes{
74867043Sdes    struct url *purl;
74963713Sdes    char *p;
75067043Sdes
75167043Sdes    if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) &&
75267043Sdes	*p && (purl = fetchParseURL(p)) != NULL) {
75367043Sdes	if (!*purl->scheme)
75467043Sdes	    strcpy(purl->scheme, SCHEME_FTP);
75567043Sdes	if (!purl->port)
75667043Sdes	    purl->port = _ftp_default_port();
75767043Sdes	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
75867043Sdes	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
75967043Sdes	    return purl;
76067043Sdes	fetchFreeURL(purl);
76167043Sdes    }
76267043Sdes    return NULL;
76363713Sdes}
76463713Sdes
76563713Sdes/*
76663340Sdes * Get and stat file
76737571Sdes */
76837535SdesFILE *
76963340SdesfetchXGetFTP(struct url *url, struct url_stat *us, char *flags)
77037608Sdes{
77167043Sdes    struct url *purl;
77255557Sdes    int cd;
77363713Sdes
77467043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
77567043Sdes    if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
77667043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
77767043Sdes	    return _http_request(url, "GET", us, purl, flags);
77867043Sdes    } else {
77967043Sdes	purl = NULL;
78067043Sdes    }
78155557Sdes
78241869Sdes    /* connect to server */
78367043Sdes    cd = _ftp_cached_connect(url, purl, flags);
78467043Sdes    if (purl)
78567043Sdes	fetchFreeURL(purl);
78667043Sdes    if (cd == NULL)
78741869Sdes	return NULL;
78841869Sdes
78963340Sdes    /* change directory */
79063340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
79163340Sdes	return NULL;
79263340Sdes
79363340Sdes    /* stat file */
79463392Sdes    if (us && _ftp_stat(cd, url->doc, us) == -1
79563910Sdes	&& fetchLastErrCode != FETCH_PROTO
79663392Sdes	&& fetchLastErrCode != FETCH_UNAVAIL)
79763340Sdes	return NULL;
79863340Sdes
79941869Sdes    /* initiate the transfer */
80060188Sdes    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
80137608Sdes}
80237608Sdes
80341869Sdes/*
80463340Sdes * Get file
80563340Sdes */
80663340SdesFILE *
80763340SdesfetchGetFTP(struct url *url, char *flags)
80863340Sdes{
80963340Sdes    return fetchXGetFTP(url, NULL, flags);
81063340Sdes}
81163340Sdes
81263340Sdes/*
81341869Sdes * Put file
81441869Sdes */
81537608SdesFILE *
81640975SdesfetchPutFTP(struct url *url, char *flags)
81737535Sdes{
81867043Sdes    struct url *purl;
81955557Sdes    int cd;
82041869Sdes
82167043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
82267043Sdes    if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
82367043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
82467043Sdes	    /* XXX HTTP PUT is not implemented, so try without the proxy */
82567043Sdes	    purl = NULL;
82667043Sdes    } else {
82767043Sdes	purl = NULL;
82867043Sdes    }
82963713Sdes
83041869Sdes    /* connect to server */
83167043Sdes    cd = _ftp_cached_connect(url, purl, flags);
83267043Sdes    if (purl)
83367043Sdes	fetchFreeURL(purl);
83467043Sdes    if (cd == NULL)
83541869Sdes	return NULL;
83641869Sdes
83763340Sdes    /* change directory */
83863340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
83963340Sdes	return NULL;
84063340Sdes
84141869Sdes    /* initiate the transfer */
84255557Sdes    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
84360188Sdes			 url->doc, "w", url->offset, flags);
84437535Sdes}
84540975Sdes
84641869Sdes/*
84741869Sdes * Get file stats
84841869Sdes */
84940975Sdesint
85040975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
85140975Sdes{
85267043Sdes    struct url *purl;
85363340Sdes    int cd;
85441869Sdes
85567043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
85667043Sdes    if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
85767043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
85867043Sdes	    FILE *f;
85967043Sdes
86067043Sdes	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
86167043Sdes		return -1;
86267043Sdes	    fclose(f);
86367043Sdes	    return 0;
86467043Sdes	}
86567043Sdes    } else {
86667043Sdes	purl = NULL;
86767043Sdes    }
86863713Sdes
86941869Sdes    /* connect to server */
87067043Sdes    cd = _ftp_cached_connect(url, purl, flags);
87167043Sdes    if (purl)
87267043Sdes	fetchFreeURL(purl);
87367043Sdes    if (cd == NULL)
87467043Sdes	return NULL;
87567043Sdes
87641869Sdes    /* change directory */
87763340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
87841869Sdes	return -1;
87941869Sdes
88063340Sdes    /* stat file */
88163340Sdes    return _ftp_stat(cd, url->doc, us);
88240975Sdes}
88341989Sdes
88441989Sdes/*
88541989Sdes * List a directory
88641989Sdes */
88741989Sdesextern void warnx(char *, ...);
88841989Sdesstruct url_ent *
88941989SdesfetchListFTP(struct url *url, char *flags)
89041989Sdes{
89161866Sdes    warnx("fetchListFTP(): not implemented");
89261866Sdes    return NULL;
89341989Sdes}
894