ftp.c revision 69272
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 69272 2000-11-27 13:42:56Z 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>
6467430Sdes#include <fcntl.h>
6560188Sdes#include <netdb.h>
6637573Sdes#include <stdarg.h>
6737535Sdes#include <stdio.h>
6837571Sdes#include <stdlib.h>
6937535Sdes#include <string.h>
7041869Sdes#include <time.h>
7137571Sdes#include <unistd.h>
7237535Sdes
7337535Sdes#include "fetch.h"
7440939Sdes#include "common.h"
7541862Sdes#include "ftperr.h"
7637535Sdes
7737535Sdes#define FTP_ANONYMOUS_USER	"ftp"
7837535Sdes#define FTP_ANONYMOUS_PASSWORD	"ftp"
7937535Sdes
8064883Sdes#define FTP_CONNECTION_ALREADY_OPEN	125
8137573Sdes#define FTP_OPEN_DATA_CONNECTION	150
8237573Sdes#define FTP_OK				200
8341869Sdes#define FTP_FILE_STATUS			213
8441863Sdes#define FTP_SERVICE_READY		220
8567890Sdes#define FTP_TRANSFER_COMPLETE		226
8637573Sdes#define FTP_PASSIVE_MODE		227
8760737Sume#define FTP_LPASSIVE_MODE		228
8860737Sume#define FTP_EPASSIVE_MODE		229
8937573Sdes#define FTP_LOGGED_IN			230
9037573Sdes#define FTP_FILE_ACTION_OK		250
9137573Sdes#define FTP_NEED_PASSWORD		331
9237573Sdes#define FTP_NEED_ACCOUNT		332
9360188Sdes#define FTP_FILE_OK			350
9455557Sdes#define FTP_SYNTAX_ERROR		500
9563336Sdes#define FTP_PROTOCOL_ERROR		999
9637573Sdes
9740975Sdesstatic struct url cached_host;
9855557Sdesstatic int cached_socket;
9937535Sdes
10055557Sdesstatic char *last_reply;
10155557Sdesstatic size_t lr_size, lr_length;
10255557Sdesstatic int last_code;
10337571Sdes
10455557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10560707Sdes			 && isdigit(foo[2]) \
10660707Sdes                         && (foo[3] == ' ' || foo[3] == '\0'))
10755557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10855557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
10955557Sdes
11060737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */
11160737Sumestatic void
11260737Sumeunmappedaddr(struct sockaddr_in6 *sin6)
11360737Sume{
11460737Sume    struct sockaddr_in *sin4;
11560737Sume    u_int32_t addr;
11660737Sume    int port;
11760737Sume
11860737Sume    if (sin6->sin6_family != AF_INET6 ||
11960737Sume	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
12060737Sume	return;
12160737Sume    sin4 = (struct sockaddr_in *)sin6;
12260737Sume    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
12360737Sume    port = sin6->sin6_port;
12460737Sume    memset(sin4, 0, sizeof(struct sockaddr_in));
12560737Sume    sin4->sin_addr.s_addr = addr;
12660737Sume    sin4->sin_port = port;
12760737Sume    sin4->sin_family = AF_INET;
12860737Sume    sin4->sin_len = sizeof(struct sockaddr_in);
12960737Sume}
13060737Sume
13137571Sdes/*
13255557Sdes * Get server response
13337535Sdes */
13437535Sdesstatic int
13555557Sdes_ftp_chkerr(int cd)
13637535Sdes{
13762215Sdes    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
13862215Sdes	_fetch_syserr();
13962215Sdes	return -1;
14062215Sdes    }
14162215Sdes    if (isftpinfo(last_reply)) {
14269044Sdes	while (lr_length && !isftpreply(last_reply)) {
14369043Sdes	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
14462215Sdes		_fetch_syserr();
14562215Sdes		return -1;
14662215Sdes	    }
14762215Sdes	}
14862215Sdes    }
14955557Sdes
15055557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
15155557Sdes	lr_length--;
15255557Sdes    last_reply[lr_length] = 0;
15337573Sdes
15455557Sdes    if (!isftpreply(last_reply)) {
15563336Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
15637535Sdes	return -1;
15737571Sdes    }
15837535Sdes
15955557Sdes    last_code = (last_reply[0] - '0') * 100
16055557Sdes	+ (last_reply[1] - '0') * 10
16155557Sdes	+ (last_reply[2] - '0');
16255557Sdes
16355557Sdes    return last_code;
16437535Sdes}
16537535Sdes
16637535Sdes/*
16737573Sdes * Send a command and check reply
16837535Sdes */
16937535Sdesstatic int
17055557Sdes_ftp_cmd(int cd, char *fmt, ...)
17137535Sdes{
17237573Sdes    va_list ap;
17362982Sdes    size_t len;
17455557Sdes    char *msg;
17555557Sdes    int r;
17637573Sdes
17737573Sdes    va_start(ap, fmt);
17862982Sdes    len = vasprintf(&msg, fmt, ap);
17955557Sdes    va_end(ap);
18055557Sdes
18155557Sdes    if (msg == NULL) {
18255557Sdes	errno = ENOMEM;
18355557Sdes	_fetch_syserr();
18455557Sdes	return -1;
18555557Sdes    }
18662982Sdes
18762982Sdes    r = _fetch_putln(cd, msg, len);
18855557Sdes    free(msg);
18962982Sdes
19055557Sdes    if (r == -1) {
19155557Sdes	_fetch_syserr();
19255557Sdes	return -1;
19355557Sdes    }
19437571Sdes
19555557Sdes    return _ftp_chkerr(cd);
19637535Sdes}
19737535Sdes
19837535Sdes/*
19963340Sdes * Return a pointer to the filename part of a path
20063340Sdes */
20163340Sdesstatic char *
20263340Sdes_ftp_filename(char *file)
20363340Sdes{
20463340Sdes    char *s;
20563340Sdes
20663340Sdes    if ((s = strrchr(file, '/')) == NULL)
20763340Sdes	return file;
20863340Sdes    else
20963340Sdes	return s + 1;
21063340Sdes}
21163340Sdes
21263340Sdes/*
21363340Sdes * Change working directory to the directory that contains the
21463340Sdes * specified file.
21563340Sdes */
21663340Sdesstatic int
21763340Sdes_ftp_cwd(int cd, char *file)
21863340Sdes{
21963340Sdes    char *s;
22063340Sdes    int e;
22163340Sdes
22263585Sdes    if ((s = strrchr(file, '/')) == NULL || s == file) {
22363340Sdes	e = _ftp_cmd(cd, "CWD /");
22463340Sdes    } else {
22563340Sdes	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
22663340Sdes    }
22763340Sdes    if (e != FTP_FILE_ACTION_OK) {
22863340Sdes	_ftp_seterr(e);
22963340Sdes	return -1;
23063340Sdes    }
23163340Sdes    return 0;
23263340Sdes}
23363340Sdes
23463340Sdes/*
23563340Sdes * Request and parse file stats
23663340Sdes */
23763340Sdesstatic int
23863340Sdes_ftp_stat(int cd, char *file, struct url_stat *us)
23963340Sdes{
24063340Sdes    char *ln, *s;
24163340Sdes    struct tm tm;
24263340Sdes    time_t t;
24363340Sdes    int e;
24463340Sdes
24563392Sdes    us->size = -1;
24663392Sdes    us->atime = us->mtime = 0;
24763392Sdes
24863340Sdes    if ((s = strrchr(file, '/')) == NULL)
24963340Sdes	s = file;
25063340Sdes    else
25163340Sdes	++s;
25263340Sdes
25363340Sdes    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
25463340Sdes	_ftp_seterr(e);
25563340Sdes	return -1;
25663340Sdes    }
25763340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
25863340Sdes	/* nothing */ ;
25963340Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
26063340Sdes	us->size = us->size * 10 + *ln - '0';
26163340Sdes    if (*ln && !isspace(*ln)) {
26263340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
26363340Sdes	return -1;
26463340Sdes    }
26563847Sdes    if (us->size == 0)
26663847Sdes	us->size = -1;
26763340Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
26863340Sdes
26963340Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
27063340Sdes	_ftp_seterr(e);
27163340Sdes	return -1;
27263340Sdes    }
27363340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
27463340Sdes	/* nothing */ ;
27563340Sdes    switch (strspn(ln, "0123456789")) {
27663340Sdes    case 14:
27763340Sdes	break;
27863340Sdes    case 15:
27963340Sdes	ln++;
28063340Sdes	ln[0] = '2';
28163340Sdes	ln[1] = '0';
28263340Sdes	break;
28363340Sdes    default:
28463340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
28563340Sdes	return -1;
28663340Sdes    }
28763340Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
28863340Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
28963340Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
29063340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
29163340Sdes	return -1;
29263340Sdes    }
29363340Sdes    tm.tm_mon--;
29463340Sdes    tm.tm_year -= 1900;
29563340Sdes    tm.tm_isdst = -1;
29663340Sdes    t = timegm(&tm);
29763340Sdes    if (t == (time_t)-1)
29863340Sdes	t = time(NULL);
29963340Sdes    us->mtime = t;
30063340Sdes    us->atime = t;
30163340Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
30263340Sdes		  "%02d:%02d:%02d\033[m]\n",
30363340Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
30463340Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
30563340Sdes    return 0;
30663340Sdes}
30763340Sdes
30863340Sdes/*
30967430Sdes * I/O functions for FTP
31067430Sdes */
31167430Sdesstruct ftpio {
31267430Sdes    int		 csd;		/* Control socket descriptor */
31367430Sdes    int		 dsd;		/* Data socket descriptor */
31467430Sdes    int		 dir;		/* Direction */
31567430Sdes    int		 eof;		/* EOF reached */
31667430Sdes    int		 err;		/* Error code */
31767430Sdes};
31867430Sdes
31967430Sdesstatic int	_ftp_readfn(void *, char *, int);
32067430Sdesstatic int	_ftp_writefn(void *, const char *, int);
32167430Sdesstatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
32267430Sdesstatic int	_ftp_closefn(void *);
32367430Sdes
32467430Sdesstatic int
32567430Sdes_ftp_readfn(void *v, char *buf, int len)
32667430Sdes{
32767430Sdes    struct ftpio *io;
32867430Sdes    int r;
32967430Sdes
33067430Sdes    io = (struct ftpio *)v;
33167890Sdes    if (io == NULL) {
33267890Sdes	errno = EBADF;
33367890Sdes	return -1;
33467890Sdes    }
33567430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
33667430Sdes	errno = EBADF;
33767430Sdes	return -1;
33867430Sdes    }
33967430Sdes    if (io->err) {
34067430Sdes	errno = io->err;
34167430Sdes	return -1;
34267430Sdes    }
34367430Sdes    if (io->eof)
34467430Sdes	return 0;
34567430Sdes    r = read(io->dsd, buf, len);
34667430Sdes    if (r > 0)
34767430Sdes	return r;
34867430Sdes    if (r == 0) {
34967430Sdes	io->eof = 1;
35067430Sdes	return _ftp_closefn(v);
35167430Sdes    }
35267430Sdes    io->err = errno;
35367430Sdes    return -1;
35467430Sdes}
35567430Sdes
35667430Sdesstatic int
35767430Sdes_ftp_writefn(void *v, const char *buf, int len)
35867430Sdes{
35967430Sdes    struct ftpio *io;
36067430Sdes    int w;
36167430Sdes
36267430Sdes    io = (struct ftpio *)v;
36367890Sdes    if (io == NULL) {
36467890Sdes	errno = EBADF;
36567890Sdes	return -1;
36667890Sdes    }
36767430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
36867430Sdes	errno = EBADF;
36967430Sdes	return -1;
37067430Sdes    }
37167430Sdes    if (io->err) {
37267430Sdes	errno = io->err;
37367430Sdes	return -1;
37467430Sdes    }
37567430Sdes    w = write(io->dsd, buf, len);
37667430Sdes    if (w >= 0)
37767430Sdes	return w;
37867430Sdes    io->err = errno;
37967430Sdes    return -1;
38067430Sdes}
38167430Sdes
38267430Sdesstatic fpos_t
38367430Sdes_ftp_seekfn(void *v, fpos_t pos, int whence)
38467430Sdes{
38567890Sdes    struct ftpio *io;
38667890Sdes
38767890Sdes    io = (struct ftpio *)v;
38867890Sdes    if (io == NULL) {
38967890Sdes	errno = EBADF;
39067890Sdes	return -1;
39167890Sdes    }
39267430Sdes    errno = ESPIPE;
39367430Sdes    return -1;
39467430Sdes}
39567430Sdes
39667430Sdesstatic int
39767430Sdes_ftp_closefn(void *v)
39867430Sdes{
39967430Sdes    struct ftpio *io;
40067890Sdes    int r;
40167430Sdes
40267430Sdes    io = (struct ftpio *)v;
40367890Sdes    if (io == NULL) {
40467890Sdes	errno = EBADF;
40567890Sdes	return -1;
40667890Sdes    }
40767430Sdes    if (io->dir == -1)
40867430Sdes	return 0;
40967430Sdes    if (io->csd == -1 || io->dsd == -1) {
41067430Sdes	errno = EBADF;
41167430Sdes	return -1;
41267430Sdes    }
41367707Sdes    close(io->dsd);
41467430Sdes    io->dir = -1;
41567430Sdes    io->dsd = -1;
41667707Sdes    DEBUG(fprintf(stderr, "Waiting for final status\n"));
41767890Sdes    if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE)
41867890Sdes	io->err = r;
41967890Sdes    else
42067890Sdes	io->err = 0;
42167430Sdes    close(io->csd);
42267430Sdes    io->csd = -1;
42367430Sdes    return io->err ? -1 : 0;
42467430Sdes}
42567430Sdes
42667430Sdesstatic FILE *
42767430Sdes_ftp_setup(int csd, int dsd, int mode)
42867430Sdes{
42967430Sdes    struct ftpio *io;
43067430Sdes    FILE *f;
43167430Sdes
43267430Sdes    if ((io = malloc(sizeof *io)) == NULL)
43367430Sdes	return NULL;
43467430Sdes    io->csd = dup(csd);
43567430Sdes    io->dsd = dsd;
43667430Sdes    io->dir = mode;
43767430Sdes    io->eof = io->err = 0;
43867430Sdes    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
43967430Sdes    if (f == NULL)
44067430Sdes	free(io);
44167430Sdes    return f;
44267430Sdes}
44367430Sdes
44467430Sdes/*
44537608Sdes * Transfer file
44637535Sdes */
44737535Sdesstatic FILE *
44860188Sdes_ftp_transfer(int cd, char *oper, char *file,
44967430Sdes	      int mode, off_t offset, char *flags)
45037535Sdes{
45160737Sume    struct sockaddr_storage sin;
45260737Sume    struct sockaddr_in6 *sin6;
45360737Sume    struct sockaddr_in *sin4;
45455544Sdes    int pasv, high, verbose;
45555544Sdes    int e, sd = -1;
45655544Sdes    socklen_t l;
45737573Sdes    char *s;
45837573Sdes    FILE *df;
45955544Sdes
46055544Sdes    /* check flags */
46167892Sdes    pasv = CHECK_FLAG('p');
46267892Sdes    high = CHECK_FLAG('h');
46367892Sdes    verbose = CHECK_FLAG('v');
46455544Sdes
46560951Sdes    /* passive mode */
46667259Sdes    if (!pasv)
46767259Sdes	pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL ||
46867259Sdes		strncasecmp(s, "no", 2) != 0);
46960951Sdes
47060737Sume    /* find our own address, bind, and listen */
47160737Sume    l = sizeof sin;
47260737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
47360737Sume	goto sysouch;
47460737Sume    if (sin.ss_family == AF_INET6)
47560737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
47660737Sume
47737573Sdes    /* open data socket */
47860737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
47940939Sdes	_fetch_syserr();
48037573Sdes	return NULL;
48137573Sdes    }
48237573Sdes
48337573Sdes    if (pasv) {
48460737Sume	u_char addr[64];
48537573Sdes	char *ln, *p;
48637573Sdes	int i;
48760737Sume	int port;
48837573Sdes
48937573Sdes	/* send PASV command */
49055544Sdes	if (verbose)
49155544Sdes	    _fetch_info("setting passive mode");
49260737Sume	switch (sin.ss_family) {
49360737Sume	case AF_INET:
49460737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
49560737Sume		goto ouch;
49660737Sume	    break;
49760737Sume	case AF_INET6:
49860737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
49960737Sume		if (e == -1)
50060737Sume		    goto ouch;
50160737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
50260737Sume		    goto ouch;
50360737Sume	    }
50460737Sume	    break;
50560737Sume	default:
50663336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
50737573Sdes	    goto ouch;
50860737Sume	}
50937573Sdes
51055544Sdes	/*
51155544Sdes	 * Find address and port number. The reply to the PASV command
51255544Sdes         * is IMHO the one and only weak point in the FTP protocol.
51355544Sdes	 */
51455557Sdes	ln = last_reply;
51562888Sume      	switch (e) {
51660737Sume	case FTP_PASSIVE_MODE:
51760737Sume	case FTP_LPASSIVE_MODE:
51862888Sume	    for (p = ln + 3; *p && !isdigit(*p); p++)
51962888Sume		/* nothing */ ;
52062888Sume	    if (!*p) {
52163336Sdes		e = FTP_PROTOCOL_ERROR;
52262888Sume		goto ouch;
52362888Sume	    }
52460737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
52560737Sume	    for (i = 0; *p && i < l; i++, p++)
52660737Sume		addr[i] = strtol(p, &p, 10);
52760737Sume	    if (i < l) {
52863336Sdes		e = FTP_PROTOCOL_ERROR;
52960737Sume		goto ouch;
53060737Sume	    }
53160737Sume	    break;
53260737Sume	case FTP_EPASSIVE_MODE:
53362888Sume	    for (p = ln + 3; *p && *p != '('; p++)
53462888Sume		/* nothing */ ;
53562888Sume	    if (!*p) {
53663336Sdes		e = FTP_PROTOCOL_ERROR;
53762888Sume		goto ouch;
53862888Sume	    }
53962888Sume	    ++p;
54060737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
54160737Sume		       &port, &addr[3]) != 5 ||
54260737Sume		addr[0] != addr[1] ||
54360737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
54463336Sdes		e = FTP_PROTOCOL_ERROR;
54560737Sume		goto ouch;
54660737Sume	    }
54760737Sume	    break;
54860737Sume	}
54937573Sdes
55060188Sdes	/* seek to required offset */
55160188Sdes	if (offset)
55260188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
55360188Sdes		goto sysouch;
55460188Sdes
55537573Sdes	/* construct sockaddr for data socket */
55660188Sdes	l = sizeof sin;
55755557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
55837573Sdes	    goto sysouch;
55960737Sume	if (sin.ss_family == AF_INET6)
56060737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
56160737Sume	switch (sin.ss_family) {
56260737Sume	case AF_INET6:
56360737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
56460737Sume	    if (e == FTP_EPASSIVE_MODE)
56560737Sume		sin6->sin6_port = htons(port);
56660737Sume	    else {
56760737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
56860737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
56960737Sume	    }
57060737Sume	    break;
57160737Sume	case AF_INET:
57260737Sume	    sin4 = (struct sockaddr_in *)&sin;
57360737Sume	    if (e == FTP_EPASSIVE_MODE)
57460737Sume		sin4->sin_port = htons(port);
57560737Sume	    else {
57660737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
57760737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
57860737Sume	    }
57960737Sume	    break;
58060737Sume	default:
58163336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
58260737Sume	    break;
58360737Sume	}
58437573Sdes
58537573Sdes	/* connect to data port */
58655544Sdes	if (verbose)
58755544Sdes	    _fetch_info("opening data connection");
58860737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
58937573Sdes	    goto sysouch;
59060188Sdes
59137573Sdes	/* make the server initiate the transfer */
59261866Sdes	if (verbose)
59361866Sdes	    _fetch_info("initiating transfer");
59463340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
59564883Sdes	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
59637573Sdes	    goto ouch;
59737573Sdes
59837573Sdes    } else {
59937573Sdes	u_int32_t a;
60037573Sdes	u_short p;
60155544Sdes	int arg, d;
60260737Sume	char *ap;
60360737Sume	char hname[INET6_ADDRSTRLEN];
60437573Sdes
60560737Sume	switch (sin.ss_family) {
60660737Sume	case AF_INET6:
60760737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
60860737Sume#ifdef IPV6_PORTRANGE
60960737Sume	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
61060737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
61160737Sume			   (char *)&arg, sizeof(arg)) == -1)
61260737Sume		goto sysouch;
61360737Sume#endif
61460737Sume	    break;
61560737Sume	case AF_INET:
61660737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
61760737Sume	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
61860737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
61960737Sume			   (char *)&arg, sizeof arg) == -1)
62060737Sume		goto sysouch;
62160737Sume	    break;
62260737Sume	}
62355544Sdes	if (verbose)
62455544Sdes	    _fetch_info("binding data socket");
62560737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
62637573Sdes	    goto sysouch;
62738394Sdes	if (listen(sd, 1) == -1)
62837573Sdes	    goto sysouch;
62937573Sdes
63037573Sdes	/* find what port we're on and tell the server */
63138394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
63237573Sdes	    goto sysouch;
63360737Sume	switch (sin.ss_family) {
63460737Sume	case AF_INET:
63560737Sume	    sin4 = (struct sockaddr_in *)&sin;
63660737Sume	    a = ntohl(sin4->sin_addr.s_addr);
63760737Sume	    p = ntohs(sin4->sin_port);
63860737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
63960737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
64060737Sume			 (a >> 8) & 0xff, a & 0xff,
64160737Sume			 (p >> 8) & 0xff, p & 0xff);
64260737Sume	    break;
64360737Sume	case AF_INET6:
64460737Sume#define UC(b)	(((int)b)&0xff)
64560737Sume	    e = -1;
64660737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
64760737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
64860737Sume			    hname, sizeof(hname),
64960737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
65060737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
65160737Sume			     htons(sin6->sin6_port));
65260737Sume		if (e == -1)
65360737Sume		    goto ouch;
65460737Sume	    }
65560737Sume	    if (e != FTP_OK) {
65660737Sume		ap = (char *)&sin6->sin6_addr;
65760737Sume		e = _ftp_cmd(cd,
65860737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
65960737Sume			     6, 16,
66060737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
66160737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
66260737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
66360737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
66460737Sume			     2,
66560737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
66660737Sume			     ntohs(sin6->sin6_port)        & 0xff);
66760737Sume	    }
66860737Sume	    break;
66960737Sume	default:
67063336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
67160737Sume	    goto ouch;
67260737Sume	}
67341869Sdes	if (e != FTP_OK)
67437573Sdes	    goto ouch;
67537573Sdes
67662256Sdes	/* seek to required offset */
67762256Sdes	if (offset)
67862256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
67962256Sdes		goto sysouch;
68062256Sdes
68137573Sdes	/* make the server initiate the transfer */
68255544Sdes	if (verbose)
68355544Sdes	    _fetch_info("initiating transfer");
68463340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
68541869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
68637573Sdes	    goto ouch;
68737573Sdes
68837573Sdes	/* accept the incoming connection and go to town */
68938394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
69037573Sdes	    goto sysouch;
69137573Sdes	close(sd);
69237573Sdes	sd = d;
69337573Sdes    }
69437573Sdes
69567430Sdes    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
69637573Sdes	goto sysouch;
69737573Sdes    return df;
69837573Sdes
69937573Sdessysouch:
70040939Sdes    _fetch_syserr();
70160737Sume    if (sd >= 0)
70260737Sume	close(sd);
70341869Sdes    return NULL;
70441869Sdes
70537573Sdesouch:
70655557Sdes    if (e != -1)
70755557Sdes	_ftp_seterr(e);
70860737Sume    if (sd >= 0)
70960737Sume	close(sd);
71037535Sdes    return NULL;
71137535Sdes}
71237535Sdes
71337571Sdes/*
71437571Sdes * Log on to FTP server
71537535Sdes */
71655557Sdesstatic int
71767043Sdes_ftp_connect(struct url *url, struct url *purl, char *flags)
71837571Sdes{
71967043Sdes    int cd, e, direct, verbose;
72060737Sume#ifdef INET6
72160737Sume    int af = AF_UNSPEC;
72260737Sume#else
72360737Sume    int af = AF_INET;
72460737Sume#endif
72560791Sume    const char *logname;
72667043Sdes    char *user, *pwd;
72760791Sume    char localhost[MAXHOSTNAMELEN];
72860791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
72937571Sdes
73067892Sdes    direct = CHECK_FLAG('d');
73167892Sdes    verbose = CHECK_FLAG('v');
73267892Sdes    if (CHECK_FLAG('4'))
73360737Sume	af = AF_INET;
73467892Sdes    else if (CHECK_FLAG('6'))
73560737Sume	af = AF_INET6;
73660737Sume
73767043Sdes    if (direct)
73867043Sdes	purl = NULL;
73967043Sdes
74037608Sdes    /* check for proxy */
74167043Sdes    if (purl) {
74267043Sdes	/* XXX proxy authentication! */
74367043Sdes	cd = _fetch_connect(purl->host, purl->port, af, verbose);
74437608Sdes    } else {
74537608Sdes	/* no proxy, go straight to target */
74667043Sdes	cd = _fetch_connect(url->host, url->port, af, verbose);
74767043Sdes	purl = NULL;
74837608Sdes    }
74937608Sdes
75037608Sdes    /* check connection */
75155557Sdes    if (cd == -1) {
75240939Sdes	_fetch_syserr();
75337571Sdes	return NULL;
75437571Sdes    }
75537608Sdes
75637571Sdes    /* expect welcome message */
75755557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
75837571Sdes	goto fouch;
75967043Sdes
76067043Sdes    /* XXX FTP_AUTH, and maybe .netrc */
76137571Sdes
76237571Sdes    /* send user name and password */
76367043Sdes    user = url->user;
76437608Sdes    if (!user || !*user)
76537608Sdes	user = FTP_ANONYMOUS_USER;
76668551Sdes    if (purl && url->port == _fetch_default_port(url->scheme))
76767055Sdes	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
76867043Sdes    else if (purl)
76967055Sdes	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
77063712Sdes    else
77167055Sdes	e = _ftp_cmd(cd, "USER %s", user);
77237608Sdes
77337608Sdes    /* did the server request a password? */
77437608Sdes    if (e == FTP_NEED_PASSWORD) {
77567043Sdes	pwd = url->pwd;
77637608Sdes	if (!pwd || !*pwd)
77760791Sume	    pwd = getenv("FTP_PASSWORD");
77860791Sume	if (!pwd || !*pwd) {
77960791Sume	    if ((logname = getlogin()) == 0)
78060791Sume		logname = FTP_ANONYMOUS_PASSWORD;
78160791Sume	    gethostname(localhost, sizeof localhost);
78260791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
78360791Sume	    pwd = pbuf;
78460791Sume	}
78555557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
78637608Sdes    }
78737608Sdes
78837608Sdes    /* did the server request an account? */
78941869Sdes    if (e == FTP_NEED_ACCOUNT)
79041863Sdes	goto fouch;
79137608Sdes
79237608Sdes    /* we should be done by now */
79341869Sdes    if (e != FTP_LOGGED_IN)
79437571Sdes	goto fouch;
79537571Sdes
79637571Sdes    /* might as well select mode and type at once */
79737571Sdes#ifdef FTP_FORCE_STREAM_MODE
79855557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
79941869Sdes	goto fouch;
80037571Sdes#endif
80155557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
80241869Sdes	goto fouch;
80337571Sdes
80437571Sdes    /* done */
80555557Sdes    return cd;
80637571Sdes
80737571Sdesfouch:
80855557Sdes    if (e != -1)
80955557Sdes	_ftp_seterr(e);
81055557Sdes    close(cd);
81137571Sdes    return NULL;
81237571Sdes}
81337571Sdes
81437571Sdes/*
81537571Sdes * Disconnect from server
81637571Sdes */
81737571Sdesstatic void
81855557Sdes_ftp_disconnect(int cd)
81937571Sdes{
82055557Sdes    (void)_ftp_cmd(cd, "QUIT");
82155557Sdes    close(cd);
82237571Sdes}
82337571Sdes
82437571Sdes/*
82537571Sdes * Check if we're already connected
82637571Sdes */
82737571Sdesstatic int
82840975Sdes_ftp_isconnected(struct url *url)
82937571Sdes{
83037571Sdes    return (cached_socket
83137571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
83237571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
83337571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
83437571Sdes	    && (url->port == cached_host.port));
83537571Sdes}
83637571Sdes
83737608Sdes/*
83841869Sdes * Check the cache, reconnect if no luck
83937608Sdes */
84055557Sdesstatic int
84167043Sdes_ftp_cached_connect(struct url *url, struct url *purl, char *flags)
84237535Sdes{
84355557Sdes    int e, cd;
84437535Sdes
84555557Sdes    cd = -1;
84641869Sdes
84737571Sdes    /* set default port */
84863842Sdes    if (!url->port)
84968551Sdes	url->port = _fetch_default_port(url->scheme);
85037535Sdes
85141863Sdes    /* try to use previously cached connection */
85255557Sdes    if (_ftp_isconnected(url)) {
85355557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
85455557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
85567043Sdes	    return cached_socket;
85655557Sdes    }
85737571Sdes
85837571Sdes    /* connect to server */
85967043Sdes    if ((cd = _ftp_connect(url, purl, flags)) == -1)
86067043Sdes	return -1;
86167043Sdes    if (cached_socket)
86267043Sdes	_ftp_disconnect(cached_socket);
86367043Sdes    cached_socket = cd;
86467043Sdes    memcpy(&cached_host, url, sizeof *url);
86555557Sdes    return cd;
86637535Sdes}
86737535Sdes
86837571Sdes/*
86967043Sdes * Check the proxy settings
87063713Sdes */
87167043Sdesstatic struct url *
87267043Sdes_ftp_get_proxy(void)
87363713Sdes{
87467043Sdes    struct url *purl;
87563713Sdes    char *p;
87667043Sdes
87767043Sdes    if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) &&
87867043Sdes	*p && (purl = fetchParseURL(p)) != NULL) {
87969272Sdes	if (!*purl->scheme) {
88069272Sdes	    if (getenv("FTP_PROXY"))
88169272Sdes		strcpy(purl->scheme, SCHEME_FTP);
88269272Sdes	    else
88369272Sdes		strcpy(purl->scheme, SCHEME_HTTP);
88469272Sdes	}
88567043Sdes	if (!purl->port)
88668551Sdes	    purl->port = _fetch_default_proxy_port(purl->scheme);
88767043Sdes	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
88867043Sdes	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
88967043Sdes	    return purl;
89067043Sdes	fetchFreeURL(purl);
89167043Sdes    }
89267043Sdes    return NULL;
89363713Sdes}
89463713Sdes
89563713Sdes/*
89663340Sdes * Get and stat file
89737571Sdes */
89837535SdesFILE *
89963340SdesfetchXGetFTP(struct url *url, struct url_stat *us, char *flags)
90037608Sdes{
90167043Sdes    struct url *purl;
90255557Sdes    int cd;
90363713Sdes
90467043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
90567892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
90667043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
90767043Sdes	    return _http_request(url, "GET", us, purl, flags);
90867043Sdes    } else {
90967043Sdes	purl = NULL;
91067043Sdes    }
91155557Sdes
91241869Sdes    /* connect to server */
91367043Sdes    cd = _ftp_cached_connect(url, purl, flags);
91467043Sdes    if (purl)
91567043Sdes	fetchFreeURL(purl);
91667043Sdes    if (cd == NULL)
91741869Sdes	return NULL;
91841869Sdes
91963340Sdes    /* change directory */
92063340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
92163340Sdes	return NULL;
92263340Sdes
92363340Sdes    /* stat file */
92463392Sdes    if (us && _ftp_stat(cd, url->doc, us) == -1
92563910Sdes	&& fetchLastErrCode != FETCH_PROTO
92663392Sdes	&& fetchLastErrCode != FETCH_UNAVAIL)
92763340Sdes	return NULL;
92863340Sdes
92941869Sdes    /* initiate the transfer */
93067430Sdes    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
93137608Sdes}
93237608Sdes
93341869Sdes/*
93463340Sdes * Get file
93563340Sdes */
93663340SdesFILE *
93763340SdesfetchGetFTP(struct url *url, char *flags)
93863340Sdes{
93963340Sdes    return fetchXGetFTP(url, NULL, flags);
94063340Sdes}
94163340Sdes
94263340Sdes/*
94341869Sdes * Put file
94441869Sdes */
94537608SdesFILE *
94640975SdesfetchPutFTP(struct url *url, char *flags)
94737535Sdes{
94867043Sdes    struct url *purl;
94955557Sdes    int cd;
95041869Sdes
95167043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
95267892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
95367043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
95467043Sdes	    /* XXX HTTP PUT is not implemented, so try without the proxy */
95567043Sdes	    purl = NULL;
95667043Sdes    } else {
95767043Sdes	purl = NULL;
95867043Sdes    }
95963713Sdes
96041869Sdes    /* connect to server */
96167043Sdes    cd = _ftp_cached_connect(url, purl, flags);
96267043Sdes    if (purl)
96367043Sdes	fetchFreeURL(purl);
96467043Sdes    if (cd == NULL)
96541869Sdes	return NULL;
96641869Sdes
96763340Sdes    /* change directory */
96863340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
96963340Sdes	return NULL;
97063340Sdes
97141869Sdes    /* initiate the transfer */
97267892Sdes    return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR",
97367430Sdes			 url->doc, O_WRONLY, url->offset, flags);
97437535Sdes}
97540975Sdes
97641869Sdes/*
97741869Sdes * Get file stats
97841869Sdes */
97940975Sdesint
98040975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
98140975Sdes{
98267043Sdes    struct url *purl;
98363340Sdes    int cd;
98441869Sdes
98567043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
98667892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
98767043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
98867043Sdes	    FILE *f;
98967043Sdes
99067043Sdes	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
99167043Sdes		return -1;
99267043Sdes	    fclose(f);
99367043Sdes	    return 0;
99467043Sdes	}
99567043Sdes    } else {
99667043Sdes	purl = NULL;
99767043Sdes    }
99863713Sdes
99941869Sdes    /* connect to server */
100067043Sdes    cd = _ftp_cached_connect(url, purl, flags);
100167043Sdes    if (purl)
100267043Sdes	fetchFreeURL(purl);
100367043Sdes    if (cd == NULL)
100467043Sdes	return NULL;
100567043Sdes
100641869Sdes    /* change directory */
100763340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
100841869Sdes	return -1;
100941869Sdes
101063340Sdes    /* stat file */
101163340Sdes    return _ftp_stat(cd, url->doc, us);
101240975Sdes}
101341989Sdes
101441989Sdes/*
101541989Sdes * List a directory
101641989Sdes */
101741989Sdesextern void warnx(char *, ...);
101841989Sdesstruct url_ent *
101941989SdesfetchListFTP(struct url *url, char *flags)
102041989Sdes{
102161866Sdes    warnx("fetchListFTP(): not implemented");
102261866Sdes    return NULL;
102341989Sdes}
1024