ftp.c revision 75891
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 75891 2001-04-24 00:06:21Z archie $
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>
6375891Sarchie#include <err.h>
6455557Sdes#include <errno.h>
6567430Sdes#include <fcntl.h>
6660188Sdes#include <netdb.h>
6737573Sdes#include <stdarg.h>
6837535Sdes#include <stdio.h>
6937571Sdes#include <stdlib.h>
7037535Sdes#include <string.h>
7141869Sdes#include <time.h>
7237571Sdes#include <unistd.h>
7337535Sdes
7437535Sdes#include "fetch.h"
7540939Sdes#include "common.h"
7641862Sdes#include "ftperr.h"
7737535Sdes
7870795Sdes#define FTP_ANONYMOUS_USER	"anonymous"
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
17075891Sarchie_ftp_cmd(int cd, const 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 */
20175891Sarchiestatic const char *
20275891Sarchie_ftp_filename(const 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
21775891Sarchie_ftp_cwd(int cd, const 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
23875891Sarchie_ftp_stat(int cd, const char *file, struct url_stat *us)
23963340Sdes{
24075891Sarchie    char *ln;
24175891Sarchie    const char *s;
24263340Sdes    struct tm tm;
24363340Sdes    time_t t;
24463340Sdes    int e;
24563340Sdes
24663392Sdes    us->size = -1;
24763392Sdes    us->atime = us->mtime = 0;
24863392Sdes
24963340Sdes    if ((s = strrchr(file, '/')) == NULL)
25063340Sdes	s = file;
25163340Sdes    else
25263340Sdes	++s;
25363340Sdes
25463340Sdes    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
25563340Sdes	_ftp_seterr(e);
25663340Sdes	return -1;
25763340Sdes    }
25863340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
25963340Sdes	/* nothing */ ;
26063340Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
26163340Sdes	us->size = us->size * 10 + *ln - '0';
26263340Sdes    if (*ln && !isspace(*ln)) {
26363340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
26475292Sdes	us->size = -1;
26563340Sdes	return -1;
26663340Sdes    }
26763847Sdes    if (us->size == 0)
26863847Sdes	us->size = -1;
26963340Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
27063340Sdes
27163340Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
27263340Sdes	_ftp_seterr(e);
27363340Sdes	return -1;
27463340Sdes    }
27563340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
27663340Sdes	/* nothing */ ;
27763340Sdes    switch (strspn(ln, "0123456789")) {
27863340Sdes    case 14:
27963340Sdes	break;
28063340Sdes    case 15:
28163340Sdes	ln++;
28263340Sdes	ln[0] = '2';
28363340Sdes	ln[1] = '0';
28463340Sdes	break;
28563340Sdes    default:
28663340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
28763340Sdes	return -1;
28863340Sdes    }
28963340Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
29063340Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
29163340Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
29263340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
29363340Sdes	return -1;
29463340Sdes    }
29563340Sdes    tm.tm_mon--;
29663340Sdes    tm.tm_year -= 1900;
29763340Sdes    tm.tm_isdst = -1;
29863340Sdes    t = timegm(&tm);
29963340Sdes    if (t == (time_t)-1)
30063340Sdes	t = time(NULL);
30163340Sdes    us->mtime = t;
30263340Sdes    us->atime = t;
30363340Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
30463340Sdes		  "%02d:%02d:%02d\033[m]\n",
30563340Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
30663340Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
30763340Sdes    return 0;
30863340Sdes}
30963340Sdes
31063340Sdes/*
31167430Sdes * I/O functions for FTP
31267430Sdes */
31367430Sdesstruct ftpio {
31467430Sdes    int		 csd;		/* Control socket descriptor */
31567430Sdes    int		 dsd;		/* Data socket descriptor */
31667430Sdes    int		 dir;		/* Direction */
31767430Sdes    int		 eof;		/* EOF reached */
31867430Sdes    int		 err;		/* Error code */
31967430Sdes};
32067430Sdes
32167430Sdesstatic int	_ftp_readfn(void *, char *, int);
32267430Sdesstatic int	_ftp_writefn(void *, const char *, int);
32367430Sdesstatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
32467430Sdesstatic int	_ftp_closefn(void *);
32567430Sdes
32667430Sdesstatic int
32767430Sdes_ftp_readfn(void *v, char *buf, int len)
32867430Sdes{
32967430Sdes    struct ftpio *io;
33067430Sdes    int r;
33167430Sdes
33267430Sdes    io = (struct ftpio *)v;
33367890Sdes    if (io == NULL) {
33467890Sdes	errno = EBADF;
33567890Sdes	return -1;
33667890Sdes    }
33767430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
33867430Sdes	errno = EBADF;
33967430Sdes	return -1;
34067430Sdes    }
34167430Sdes    if (io->err) {
34267430Sdes	errno = io->err;
34367430Sdes	return -1;
34467430Sdes    }
34567430Sdes    if (io->eof)
34667430Sdes	return 0;
34767430Sdes    r = read(io->dsd, buf, len);
34867430Sdes    if (r > 0)
34967430Sdes	return r;
35067430Sdes    if (r == 0) {
35167430Sdes	io->eof = 1;
35267430Sdes	return _ftp_closefn(v);
35367430Sdes    }
35473934Sdes    if (errno != EINTR)
35573934Sdes	io->err = errno;
35667430Sdes    return -1;
35767430Sdes}
35867430Sdes
35967430Sdesstatic int
36067430Sdes_ftp_writefn(void *v, const char *buf, int len)
36167430Sdes{
36267430Sdes    struct ftpio *io;
36367430Sdes    int w;
36467430Sdes
36567430Sdes    io = (struct ftpio *)v;
36667890Sdes    if (io == NULL) {
36767890Sdes	errno = EBADF;
36867890Sdes	return -1;
36967890Sdes    }
37067430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
37167430Sdes	errno = EBADF;
37267430Sdes	return -1;
37367430Sdes    }
37467430Sdes    if (io->err) {
37567430Sdes	errno = io->err;
37667430Sdes	return -1;
37767430Sdes    }
37867430Sdes    w = write(io->dsd, buf, len);
37967430Sdes    if (w >= 0)
38067430Sdes	return w;
38173934Sdes    if (errno != EINTR)
38273934Sdes	io->err = errno;
38367430Sdes    return -1;
38467430Sdes}
38567430Sdes
38667430Sdesstatic fpos_t
38767430Sdes_ftp_seekfn(void *v, fpos_t pos, int whence)
38867430Sdes{
38967890Sdes    struct ftpio *io;
39067890Sdes
39167890Sdes    io = (struct ftpio *)v;
39267890Sdes    if (io == NULL) {
39367890Sdes	errno = EBADF;
39467890Sdes	return -1;
39567890Sdes    }
39667430Sdes    errno = ESPIPE;
39767430Sdes    return -1;
39867430Sdes}
39967430Sdes
40067430Sdesstatic int
40167430Sdes_ftp_closefn(void *v)
40267430Sdes{
40367430Sdes    struct ftpio *io;
40467890Sdes    int r;
40567430Sdes
40667430Sdes    io = (struct ftpio *)v;
40767890Sdes    if (io == NULL) {
40867890Sdes	errno = EBADF;
40967890Sdes	return -1;
41067890Sdes    }
41167430Sdes    if (io->dir == -1)
41267430Sdes	return 0;
41367430Sdes    if (io->csd == -1 || io->dsd == -1) {
41467430Sdes	errno = EBADF;
41567430Sdes	return -1;
41667430Sdes    }
41767707Sdes    close(io->dsd);
41867430Sdes    io->dir = -1;
41967430Sdes    io->dsd = -1;
42067707Sdes    DEBUG(fprintf(stderr, "Waiting for final status\n"));
42167890Sdes    if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE)
42267890Sdes	io->err = r;
42367890Sdes    else
42467890Sdes	io->err = 0;
42567430Sdes    close(io->csd);
42667430Sdes    io->csd = -1;
42767430Sdes    return io->err ? -1 : 0;
42867430Sdes}
42967430Sdes
43067430Sdesstatic FILE *
43167430Sdes_ftp_setup(int csd, int dsd, int mode)
43267430Sdes{
43367430Sdes    struct ftpio *io;
43467430Sdes    FILE *f;
43567430Sdes
43667430Sdes    if ((io = malloc(sizeof *io)) == NULL)
43767430Sdes	return NULL;
43867430Sdes    io->csd = dup(csd);
43967430Sdes    io->dsd = dsd;
44067430Sdes    io->dir = mode;
44167430Sdes    io->eof = io->err = 0;
44267430Sdes    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
44367430Sdes    if (f == NULL)
44467430Sdes	free(io);
44567430Sdes    return f;
44667430Sdes}
44767430Sdes
44867430Sdes/*
44937608Sdes * Transfer file
45037535Sdes */
45137535Sdesstatic FILE *
45275891Sarchie_ftp_transfer(int cd, const char *oper, const char *file,
45375891Sarchie	      int mode, off_t offset, const char *flags)
45437535Sdes{
45560737Sume    struct sockaddr_storage sin;
45660737Sume    struct sockaddr_in6 *sin6;
45760737Sume    struct sockaddr_in *sin4;
45874716Sdes    int low, pasv, verbose;
45955544Sdes    int e, sd = -1;
46055544Sdes    socklen_t l;
46137573Sdes    char *s;
46237573Sdes    FILE *df;
46355544Sdes
46455544Sdes    /* check flags */
46574716Sdes    low = CHECK_FLAG('l');
46667892Sdes    pasv = CHECK_FLAG('p');
46767892Sdes    verbose = CHECK_FLAG('v');
46855544Sdes
46960951Sdes    /* passive mode */
47067259Sdes    if (!pasv)
47169670Sdes	pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
47267259Sdes		strncasecmp(s, "no", 2) != 0);
47360951Sdes
47460737Sume    /* find our own address, bind, and listen */
47560737Sume    l = sizeof sin;
47660737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
47760737Sume	goto sysouch;
47860737Sume    if (sin.ss_family == AF_INET6)
47960737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
48060737Sume
48137573Sdes    /* open data socket */
48260737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
48340939Sdes	_fetch_syserr();
48437573Sdes	return NULL;
48537573Sdes    }
48637573Sdes
48737573Sdes    if (pasv) {
48860737Sume	u_char addr[64];
48937573Sdes	char *ln, *p;
49037573Sdes	int i;
49160737Sume	int port;
49237573Sdes
49337573Sdes	/* send PASV command */
49455544Sdes	if (verbose)
49555544Sdes	    _fetch_info("setting passive mode");
49660737Sume	switch (sin.ss_family) {
49760737Sume	case AF_INET:
49860737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
49960737Sume		goto ouch;
50060737Sume	    break;
50160737Sume	case AF_INET6:
50260737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
50360737Sume		if (e == -1)
50460737Sume		    goto ouch;
50560737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
50660737Sume		    goto ouch;
50760737Sume	    }
50860737Sume	    break;
50960737Sume	default:
51063336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
51137573Sdes	    goto ouch;
51260737Sume	}
51337573Sdes
51455544Sdes	/*
51555544Sdes	 * Find address and port number. The reply to the PASV command
51655544Sdes         * is IMHO the one and only weak point in the FTP protocol.
51755544Sdes	 */
51855557Sdes	ln = last_reply;
51962888Sume      	switch (e) {
52060737Sume	case FTP_PASSIVE_MODE:
52160737Sume	case FTP_LPASSIVE_MODE:
52262888Sume	    for (p = ln + 3; *p && !isdigit(*p); p++)
52362888Sume		/* nothing */ ;
52462888Sume	    if (!*p) {
52563336Sdes		e = FTP_PROTOCOL_ERROR;
52662888Sume		goto ouch;
52762888Sume	    }
52860737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
52960737Sume	    for (i = 0; *p && i < l; i++, p++)
53060737Sume		addr[i] = strtol(p, &p, 10);
53160737Sume	    if (i < l) {
53263336Sdes		e = FTP_PROTOCOL_ERROR;
53360737Sume		goto ouch;
53460737Sume	    }
53560737Sume	    break;
53660737Sume	case FTP_EPASSIVE_MODE:
53762888Sume	    for (p = ln + 3; *p && *p != '('; p++)
53862888Sume		/* nothing */ ;
53962888Sume	    if (!*p) {
54063336Sdes		e = FTP_PROTOCOL_ERROR;
54162888Sume		goto ouch;
54262888Sume	    }
54362888Sume	    ++p;
54460737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
54560737Sume		       &port, &addr[3]) != 5 ||
54660737Sume		addr[0] != addr[1] ||
54760737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
54863336Sdes		e = FTP_PROTOCOL_ERROR;
54960737Sume		goto ouch;
55060737Sume	    }
55160737Sume	    break;
55260737Sume	}
55337573Sdes
55460188Sdes	/* seek to required offset */
55560188Sdes	if (offset)
55660188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
55760188Sdes		goto sysouch;
55860188Sdes
55937573Sdes	/* construct sockaddr for data socket */
56060188Sdes	l = sizeof sin;
56155557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
56237573Sdes	    goto sysouch;
56360737Sume	if (sin.ss_family == AF_INET6)
56460737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
56560737Sume	switch (sin.ss_family) {
56660737Sume	case AF_INET6:
56760737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
56860737Sume	    if (e == FTP_EPASSIVE_MODE)
56960737Sume		sin6->sin6_port = htons(port);
57060737Sume	    else {
57160737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
57260737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
57360737Sume	    }
57460737Sume	    break;
57560737Sume	case AF_INET:
57660737Sume	    sin4 = (struct sockaddr_in *)&sin;
57760737Sume	    if (e == FTP_EPASSIVE_MODE)
57860737Sume		sin4->sin_port = htons(port);
57960737Sume	    else {
58060737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
58160737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
58260737Sume	    }
58360737Sume	    break;
58460737Sume	default:
58563336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
58660737Sume	    break;
58760737Sume	}
58837573Sdes
58937573Sdes	/* connect to data port */
59055544Sdes	if (verbose)
59155544Sdes	    _fetch_info("opening data connection");
59260737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
59337573Sdes	    goto sysouch;
59460188Sdes
59537573Sdes	/* make the server initiate the transfer */
59661866Sdes	if (verbose)
59761866Sdes	    _fetch_info("initiating transfer");
59863340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
59964883Sdes	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
60037573Sdes	    goto ouch;
60137573Sdes
60237573Sdes    } else {
60337573Sdes	u_int32_t a;
60437573Sdes	u_short p;
60555544Sdes	int arg, d;
60660737Sume	char *ap;
60760737Sume	char hname[INET6_ADDRSTRLEN];
60837573Sdes
60960737Sume	switch (sin.ss_family) {
61060737Sume	case AF_INET6:
61160737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
61260737Sume#ifdef IPV6_PORTRANGE
61374716Sdes	    arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
61460737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
61560737Sume			   (char *)&arg, sizeof(arg)) == -1)
61660737Sume		goto sysouch;
61760737Sume#endif
61860737Sume	    break;
61960737Sume	case AF_INET:
62060737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
62174716Sdes	    arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
62260737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
62360737Sume			   (char *)&arg, sizeof arg) == -1)
62460737Sume		goto sysouch;
62560737Sume	    break;
62660737Sume	}
62755544Sdes	if (verbose)
62855544Sdes	    _fetch_info("binding data socket");
62960737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
63037573Sdes	    goto sysouch;
63138394Sdes	if (listen(sd, 1) == -1)
63237573Sdes	    goto sysouch;
63337573Sdes
63437573Sdes	/* find what port we're on and tell the server */
63538394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
63637573Sdes	    goto sysouch;
63760737Sume	switch (sin.ss_family) {
63860737Sume	case AF_INET:
63960737Sume	    sin4 = (struct sockaddr_in *)&sin;
64060737Sume	    a = ntohl(sin4->sin_addr.s_addr);
64160737Sume	    p = ntohs(sin4->sin_port);
64260737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
64360737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
64460737Sume			 (a >> 8) & 0xff, a & 0xff,
64560737Sume			 (p >> 8) & 0xff, p & 0xff);
64660737Sume	    break;
64760737Sume	case AF_INET6:
64860737Sume#define UC(b)	(((int)b)&0xff)
64960737Sume	    e = -1;
65060737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
65160737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
65260737Sume			    hname, sizeof(hname),
65360737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
65460737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
65560737Sume			     htons(sin6->sin6_port));
65660737Sume		if (e == -1)
65760737Sume		    goto ouch;
65860737Sume	    }
65960737Sume	    if (e != FTP_OK) {
66060737Sume		ap = (char *)&sin6->sin6_addr;
66160737Sume		e = _ftp_cmd(cd,
66260737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
66360737Sume			     6, 16,
66460737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
66560737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
66660737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
66760737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
66860737Sume			     2,
66960737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
67060737Sume			     ntohs(sin6->sin6_port)        & 0xff);
67160737Sume	    }
67260737Sume	    break;
67360737Sume	default:
67463336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
67560737Sume	    goto ouch;
67660737Sume	}
67741869Sdes	if (e != FTP_OK)
67837573Sdes	    goto ouch;
67937573Sdes
68062256Sdes	/* seek to required offset */
68162256Sdes	if (offset)
68262256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
68362256Sdes		goto sysouch;
68462256Sdes
68537573Sdes	/* make the server initiate the transfer */
68655544Sdes	if (verbose)
68755544Sdes	    _fetch_info("initiating transfer");
68863340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
68941869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
69037573Sdes	    goto ouch;
69137573Sdes
69237573Sdes	/* accept the incoming connection and go to town */
69338394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
69437573Sdes	    goto sysouch;
69537573Sdes	close(sd);
69637573Sdes	sd = d;
69737573Sdes    }
69837573Sdes
69967430Sdes    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
70037573Sdes	goto sysouch;
70137573Sdes    return df;
70237573Sdes
70337573Sdessysouch:
70440939Sdes    _fetch_syserr();
70560737Sume    if (sd >= 0)
70660737Sume	close(sd);
70741869Sdes    return NULL;
70841869Sdes
70937573Sdesouch:
71055557Sdes    if (e != -1)
71155557Sdes	_ftp_seterr(e);
71260737Sume    if (sd >= 0)
71360737Sume	close(sd);
71437535Sdes    return NULL;
71537535Sdes}
71637535Sdes
71737571Sdes/*
71837571Sdes * Log on to FTP server
71937535Sdes */
72055557Sdesstatic int
72175891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags)
72237571Sdes{
72367043Sdes    int cd, e, direct, verbose;
72460737Sume#ifdef INET6
72560737Sume    int af = AF_UNSPEC;
72660737Sume#else
72760737Sume    int af = AF_INET;
72860737Sume#endif
72960791Sume    const char *logname;
73075891Sarchie    const char *user;
73175891Sarchie    const char *pwd;
73260791Sume    char localhost[MAXHOSTNAMELEN];
73360791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
73437571Sdes
73567892Sdes    direct = CHECK_FLAG('d');
73667892Sdes    verbose = CHECK_FLAG('v');
73767892Sdes    if (CHECK_FLAG('4'))
73860737Sume	af = AF_INET;
73967892Sdes    else if (CHECK_FLAG('6'))
74060737Sume	af = AF_INET6;
74160737Sume
74267043Sdes    if (direct)
74367043Sdes	purl = NULL;
74467043Sdes
74537608Sdes    /* check for proxy */
74667043Sdes    if (purl) {
74767043Sdes	/* XXX proxy authentication! */
74867043Sdes	cd = _fetch_connect(purl->host, purl->port, af, verbose);
74937608Sdes    } else {
75037608Sdes	/* no proxy, go straight to target */
75167043Sdes	cd = _fetch_connect(url->host, url->port, af, verbose);
75267043Sdes	purl = NULL;
75337608Sdes    }
75437608Sdes
75537608Sdes    /* check connection */
75655557Sdes    if (cd == -1) {
75740939Sdes	_fetch_syserr();
75837571Sdes	return NULL;
75937571Sdes    }
76037608Sdes
76137571Sdes    /* expect welcome message */
76255557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
76337571Sdes	goto fouch;
76467043Sdes
76567043Sdes    /* XXX FTP_AUTH, and maybe .netrc */
76637571Sdes
76737571Sdes    /* send user name and password */
76867043Sdes    user = url->user;
76937608Sdes    if (!user || !*user)
77070273Sdes	user = getenv("FTP_LOGIN");
77170273Sdes    if (!user || !*user)
77237608Sdes	user = FTP_ANONYMOUS_USER;
77368551Sdes    if (purl && url->port == _fetch_default_port(url->scheme))
77467055Sdes	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
77567043Sdes    else if (purl)
77667055Sdes	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
77763712Sdes    else
77867055Sdes	e = _ftp_cmd(cd, "USER %s", user);
77937608Sdes
78037608Sdes    /* did the server request a password? */
78137608Sdes    if (e == FTP_NEED_PASSWORD) {
78267043Sdes	pwd = url->pwd;
78337608Sdes	if (!pwd || !*pwd)
78460791Sume	    pwd = getenv("FTP_PASSWORD");
78560791Sume	if (!pwd || !*pwd) {
78660791Sume	    if ((logname = getlogin()) == 0)
78770795Sdes		logname = FTP_ANONYMOUS_USER;
78860791Sume	    gethostname(localhost, sizeof localhost);
78960791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
79060791Sume	    pwd = pbuf;
79160791Sume	}
79255557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
79337608Sdes    }
79437608Sdes
79537608Sdes    /* did the server request an account? */
79641869Sdes    if (e == FTP_NEED_ACCOUNT)
79741863Sdes	goto fouch;
79837608Sdes
79937608Sdes    /* we should be done by now */
80041869Sdes    if (e != FTP_LOGGED_IN)
80137571Sdes	goto fouch;
80237571Sdes
80337571Sdes    /* might as well select mode and type at once */
80437571Sdes#ifdef FTP_FORCE_STREAM_MODE
80555557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
80641869Sdes	goto fouch;
80737571Sdes#endif
80855557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
80941869Sdes	goto fouch;
81037571Sdes
81137571Sdes    /* done */
81255557Sdes    return cd;
81337571Sdes
81437571Sdesfouch:
81555557Sdes    if (e != -1)
81655557Sdes	_ftp_seterr(e);
81755557Sdes    close(cd);
81837571Sdes    return NULL;
81937571Sdes}
82037571Sdes
82137571Sdes/*
82237571Sdes * Disconnect from server
82337571Sdes */
82437571Sdesstatic void
82555557Sdes_ftp_disconnect(int cd)
82637571Sdes{
82755557Sdes    (void)_ftp_cmd(cd, "QUIT");
82855557Sdes    close(cd);
82937571Sdes}
83037571Sdes
83137571Sdes/*
83237571Sdes * Check if we're already connected
83337571Sdes */
83437571Sdesstatic int
83540975Sdes_ftp_isconnected(struct url *url)
83637571Sdes{
83737571Sdes    return (cached_socket
83837571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
83937571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
84037571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
84137571Sdes	    && (url->port == cached_host.port));
84237571Sdes}
84337571Sdes
84437608Sdes/*
84541869Sdes * Check the cache, reconnect if no luck
84637608Sdes */
84755557Sdesstatic int
84875891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags)
84937535Sdes{
85055557Sdes    int e, cd;
85137535Sdes
85255557Sdes    cd = -1;
85341869Sdes
85437571Sdes    /* set default port */
85563842Sdes    if (!url->port)
85668551Sdes	url->port = _fetch_default_port(url->scheme);
85737535Sdes
85841863Sdes    /* try to use previously cached connection */
85955557Sdes    if (_ftp_isconnected(url)) {
86055557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
86155557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
86267043Sdes	    return cached_socket;
86355557Sdes    }
86437571Sdes
86537571Sdes    /* connect to server */
86667043Sdes    if ((cd = _ftp_connect(url, purl, flags)) == -1)
86767043Sdes	return -1;
86867043Sdes    if (cached_socket)
86967043Sdes	_ftp_disconnect(cached_socket);
87067043Sdes    cached_socket = cd;
87167043Sdes    memcpy(&cached_host, url, sizeof *url);
87255557Sdes    return cd;
87337535Sdes}
87437535Sdes
87537571Sdes/*
87667043Sdes * Check the proxy settings
87763713Sdes */
87867043Sdesstatic struct url *
87967043Sdes_ftp_get_proxy(void)
88063713Sdes{
88167043Sdes    struct url *purl;
88263713Sdes    char *p;
88367043Sdes
88473932Sdes    if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
88573932Sdes	 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
88667043Sdes	*p && (purl = fetchParseURL(p)) != NULL) {
88769272Sdes	if (!*purl->scheme) {
88873932Sdes	    if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
88969272Sdes		strcpy(purl->scheme, SCHEME_FTP);
89069272Sdes	    else
89169272Sdes		strcpy(purl->scheme, SCHEME_HTTP);
89269272Sdes	}
89367043Sdes	if (!purl->port)
89468551Sdes	    purl->port = _fetch_default_proxy_port(purl->scheme);
89567043Sdes	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
89667043Sdes	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
89767043Sdes	    return purl;
89867043Sdes	fetchFreeURL(purl);
89967043Sdes    }
90067043Sdes    return NULL;
90163713Sdes}
90263713Sdes
90363713Sdes/*
90463340Sdes * Get and stat file
90537571Sdes */
90637535SdesFILE *
90775891SarchiefetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
90837608Sdes{
90967043Sdes    struct url *purl;
91055557Sdes    int cd;
91163713Sdes
91267043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
91367892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
91467043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
91567043Sdes	    return _http_request(url, "GET", us, purl, flags);
91667043Sdes    } else {
91767043Sdes	purl = NULL;
91867043Sdes    }
91955557Sdes
92041869Sdes    /* connect to server */
92167043Sdes    cd = _ftp_cached_connect(url, purl, flags);
92267043Sdes    if (purl)
92367043Sdes	fetchFreeURL(purl);
92467043Sdes    if (cd == NULL)
92541869Sdes	return NULL;
92641869Sdes
92763340Sdes    /* change directory */
92863340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
92963340Sdes	return NULL;
93063340Sdes
93163340Sdes    /* stat file */
93263392Sdes    if (us && _ftp_stat(cd, url->doc, us) == -1
93363910Sdes	&& fetchLastErrCode != FETCH_PROTO
93463392Sdes	&& fetchLastErrCode != FETCH_UNAVAIL)
93563340Sdes	return NULL;
93663340Sdes
93741869Sdes    /* initiate the transfer */
93867430Sdes    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
93937608Sdes}
94037608Sdes
94141869Sdes/*
94263340Sdes * Get file
94363340Sdes */
94463340SdesFILE *
94575891SarchiefetchGetFTP(struct url *url, const char *flags)
94663340Sdes{
94763340Sdes    return fetchXGetFTP(url, NULL, flags);
94863340Sdes}
94963340Sdes
95063340Sdes/*
95141869Sdes * Put file
95241869Sdes */
95337608SdesFILE *
95475891SarchiefetchPutFTP(struct url *url, const char *flags)
95537535Sdes{
95667043Sdes    struct url *purl;
95755557Sdes    int cd;
95841869Sdes
95967043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
96067892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
96167043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
96267043Sdes	    /* XXX HTTP PUT is not implemented, so try without the proxy */
96367043Sdes	    purl = NULL;
96467043Sdes    } else {
96567043Sdes	purl = NULL;
96667043Sdes    }
96763713Sdes
96841869Sdes    /* connect to server */
96967043Sdes    cd = _ftp_cached_connect(url, purl, flags);
97067043Sdes    if (purl)
97167043Sdes	fetchFreeURL(purl);
97267043Sdes    if (cd == NULL)
97341869Sdes	return NULL;
97441869Sdes
97563340Sdes    /* change directory */
97663340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
97763340Sdes	return NULL;
97863340Sdes
97941869Sdes    /* initiate the transfer */
98067892Sdes    return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR",
98167430Sdes			 url->doc, O_WRONLY, url->offset, flags);
98237535Sdes}
98340975Sdes
98441869Sdes/*
98541869Sdes * Get file stats
98641869Sdes */
98740975Sdesint
98875891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
98940975Sdes{
99067043Sdes    struct url *purl;
99163340Sdes    int cd;
99241869Sdes
99367043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
99467892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
99567043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
99667043Sdes	    FILE *f;
99767043Sdes
99867043Sdes	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
99967043Sdes		return -1;
100067043Sdes	    fclose(f);
100167043Sdes	    return 0;
100267043Sdes	}
100367043Sdes    } else {
100467043Sdes	purl = NULL;
100567043Sdes    }
100663713Sdes
100741869Sdes    /* connect to server */
100867043Sdes    cd = _ftp_cached_connect(url, purl, flags);
100967043Sdes    if (purl)
101067043Sdes	fetchFreeURL(purl);
101167043Sdes    if (cd == NULL)
101267043Sdes	return NULL;
101367043Sdes
101441869Sdes    /* change directory */
101563340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
101641869Sdes	return -1;
101741869Sdes
101863340Sdes    /* stat file */
101963340Sdes    return _ftp_stat(cd, url->doc, us);
102040975Sdes}
102141989Sdes
102241989Sdes/*
102341989Sdes * List a directory
102441989Sdes */
102541989Sdesstruct url_ent *
102675891SarchiefetchListFTP(struct url *url, const char *flags)
102741989Sdes{
102861866Sdes    warnx("fetchListFTP(): not implemented");
102961866Sdes    return NULL;
103041989Sdes}
1031