ftp.c revision 85093
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 */
2837535Sdes
2984203Sdillon#include <sys/cdefs.h>
3084203Sdillon__FBSDID("$FreeBSD: head/lib/libfetch/ftp.c 85093 2001-10-18 08:29:26Z des $");
3184203Sdillon
3237535Sdes/*
3337571Sdes * Portions of this code were taken from or based on ftpio.c:
3437535Sdes *
3537535Sdes * ----------------------------------------------------------------------------
3637535Sdes * "THE BEER-WARE LICENSE" (Revision 42):
3737535Sdes * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
3837535Sdes * can do whatever you want with this stuff. If we meet some day, and you think
3937535Sdes * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
4037535Sdes * ----------------------------------------------------------------------------
4137535Sdes *
4237535Sdes * Major Changelog:
4337535Sdes *
4437535Sdes * Dag-Erling Co�dan Sm�rgrav
4537535Sdes * 9 Jun 1998
4637535Sdes *
4737535Sdes * Incorporated into libfetch
4837535Sdes *
4937535Sdes * Jordan K. Hubbard
5037535Sdes * 17 Jan 1996
5137535Sdes *
5237535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special
5337535Sdes * `state' of FTP_t
5437535Sdes *
5537535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
5637535Sdes *
5737535Sdes */
5837535Sdes
5941862Sdes#include <sys/param.h>
6037535Sdes#include <sys/socket.h>
6137535Sdes#include <netinet/in.h>
6237535Sdes
6337535Sdes#include <ctype.h>
6475891Sarchie#include <err.h>
6555557Sdes#include <errno.h>
6667430Sdes#include <fcntl.h>
6760188Sdes#include <netdb.h>
6837573Sdes#include <stdarg.h>
6937535Sdes#include <stdio.h>
7037571Sdes#include <stdlib.h>
7137535Sdes#include <string.h>
7241869Sdes#include <time.h>
7337571Sdes#include <unistd.h>
7437535Sdes
7537535Sdes#include "fetch.h"
7640939Sdes#include "common.h"
7741862Sdes#include "ftperr.h"
7837535Sdes
7970795Sdes#define FTP_ANONYMOUS_USER	"anonymous"
8037535Sdes
8164883Sdes#define FTP_CONNECTION_ALREADY_OPEN	125
8237573Sdes#define FTP_OPEN_DATA_CONNECTION	150
8337573Sdes#define FTP_OK				200
8441869Sdes#define FTP_FILE_STATUS			213
8541863Sdes#define FTP_SERVICE_READY		220
8667890Sdes#define FTP_TRANSFER_COMPLETE		226
8737573Sdes#define FTP_PASSIVE_MODE		227
8860737Sume#define FTP_LPASSIVE_MODE		228
8960737Sume#define FTP_EPASSIVE_MODE		229
9037573Sdes#define FTP_LOGGED_IN			230
9137573Sdes#define FTP_FILE_ACTION_OK		250
9237573Sdes#define FTP_NEED_PASSWORD		331
9337573Sdes#define FTP_NEED_ACCOUNT		332
9460188Sdes#define FTP_FILE_OK			350
9555557Sdes#define FTP_SYNTAX_ERROR		500
9663336Sdes#define FTP_PROTOCOL_ERROR		999
9737573Sdes
9840975Sdesstatic struct url cached_host;
9955557Sdesstatic int cached_socket;
10037535Sdes
10155557Sdesstatic char *last_reply;
10255557Sdesstatic size_t lr_size, lr_length;
10355557Sdesstatic int last_code;
10437571Sdes
10555557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10660707Sdes			 && isdigit(foo[2]) \
10760707Sdes                         && (foo[3] == ' ' || foo[3] == '\0'))
10855557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10955557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
11055557Sdes
11160737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */
11260737Sumestatic void
11360737Sumeunmappedaddr(struct sockaddr_in6 *sin6)
11460737Sume{
11560737Sume    struct sockaddr_in *sin4;
11660737Sume    u_int32_t addr;
11760737Sume    int port;
11860737Sume
11960737Sume    if (sin6->sin6_family != AF_INET6 ||
12060737Sume	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
12160737Sume	return;
12260737Sume    sin4 = (struct sockaddr_in *)sin6;
12360737Sume    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
12460737Sume    port = sin6->sin6_port;
12560737Sume    memset(sin4, 0, sizeof(struct sockaddr_in));
12660737Sume    sin4->sin_addr.s_addr = addr;
12760737Sume    sin4->sin_port = port;
12860737Sume    sin4->sin_family = AF_INET;
12960737Sume    sin4->sin_len = sizeof(struct sockaddr_in);
13060737Sume}
13160737Sume
13237571Sdes/*
13355557Sdes * Get server response
13437535Sdes */
13537535Sdesstatic int
13655557Sdes_ftp_chkerr(int cd)
13737535Sdes{
13862215Sdes    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
13962215Sdes	_fetch_syserr();
14062215Sdes	return -1;
14162215Sdes    }
14262215Sdes    if (isftpinfo(last_reply)) {
14369044Sdes	while (lr_length && !isftpreply(last_reply)) {
14469043Sdes	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
14562215Sdes		_fetch_syserr();
14662215Sdes		return -1;
14762215Sdes	    }
14862215Sdes	}
14962215Sdes    }
15055557Sdes
15155557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
15255557Sdes	lr_length--;
15355557Sdes    last_reply[lr_length] = 0;
15437573Sdes
15555557Sdes    if (!isftpreply(last_reply)) {
15663336Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
15737535Sdes	return -1;
15837571Sdes    }
15937535Sdes
16055557Sdes    last_code = (last_reply[0] - '0') * 100
16155557Sdes	+ (last_reply[1] - '0') * 10
16255557Sdes	+ (last_reply[2] - '0');
16355557Sdes
16455557Sdes    return last_code;
16537535Sdes}
16637535Sdes
16737535Sdes/*
16837573Sdes * Send a command and check reply
16937535Sdes */
17037535Sdesstatic int
17175891Sarchie_ftp_cmd(int cd, const char *fmt, ...)
17237535Sdes{
17337573Sdes    va_list ap;
17462982Sdes    size_t len;
17555557Sdes    char *msg;
17655557Sdes    int r;
17737573Sdes
17837573Sdes    va_start(ap, fmt);
17962982Sdes    len = vasprintf(&msg, fmt, ap);
18055557Sdes    va_end(ap);
18155557Sdes
18255557Sdes    if (msg == NULL) {
18355557Sdes	errno = ENOMEM;
18455557Sdes	_fetch_syserr();
18555557Sdes	return -1;
18655557Sdes    }
18762982Sdes
18862982Sdes    r = _fetch_putln(cd, msg, len);
18955557Sdes    free(msg);
19062982Sdes
19155557Sdes    if (r == -1) {
19255557Sdes	_fetch_syserr();
19355557Sdes	return -1;
19455557Sdes    }
19537571Sdes
19655557Sdes    return _ftp_chkerr(cd);
19737535Sdes}
19837535Sdes
19937535Sdes/*
20063340Sdes * Return a pointer to the filename part of a path
20163340Sdes */
20275891Sarchiestatic const char *
20375891Sarchie_ftp_filename(const char *file)
20463340Sdes{
20563340Sdes    char *s;
20663340Sdes
20763340Sdes    if ((s = strrchr(file, '/')) == NULL)
20863340Sdes	return file;
20963340Sdes    else
21063340Sdes	return s + 1;
21163340Sdes}
21263340Sdes
21363340Sdes/*
21463340Sdes * Change working directory to the directory that contains the
21563340Sdes * specified file.
21663340Sdes */
21763340Sdesstatic int
21875891Sarchie_ftp_cwd(int cd, const char *file)
21963340Sdes{
22063340Sdes    char *s;
22163340Sdes    int e;
22263340Sdes
22363585Sdes    if ((s = strrchr(file, '/')) == NULL || s == file) {
22463340Sdes	e = _ftp_cmd(cd, "CWD /");
22563340Sdes    } else {
22663340Sdes	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
22763340Sdes    }
22863340Sdes    if (e != FTP_FILE_ACTION_OK) {
22963340Sdes	_ftp_seterr(e);
23063340Sdes	return -1;
23163340Sdes    }
23263340Sdes    return 0;
23363340Sdes}
23463340Sdes
23563340Sdes/*
23663340Sdes * Request and parse file stats
23763340Sdes */
23863340Sdesstatic int
23975891Sarchie_ftp_stat(int cd, const char *file, struct url_stat *us)
24063340Sdes{
24175891Sarchie    char *ln;
24275891Sarchie    const char *s;
24363340Sdes    struct tm tm;
24463340Sdes    time_t t;
24563340Sdes    int e;
24663340Sdes
24763392Sdes    us->size = -1;
24863392Sdes    us->atime = us->mtime = 0;
24963392Sdes
25063340Sdes    if ((s = strrchr(file, '/')) == NULL)
25163340Sdes	s = file;
25263340Sdes    else
25363340Sdes	++s;
25463340Sdes
25563340Sdes    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
25663340Sdes	_ftp_seterr(e);
25763340Sdes	return -1;
25863340Sdes    }
25963340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
26063340Sdes	/* nothing */ ;
26163340Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
26263340Sdes	us->size = us->size * 10 + *ln - '0';
26363340Sdes    if (*ln && !isspace(*ln)) {
26463340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
26575292Sdes	us->size = -1;
26663340Sdes	return -1;
26763340Sdes    }
26863847Sdes    if (us->size == 0)
26963847Sdes	us->size = -1;
27085093Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", (long long)us->size));
27163340Sdes
27263340Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
27363340Sdes	_ftp_seterr(e);
27463340Sdes	return -1;
27563340Sdes    }
27663340Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
27763340Sdes	/* nothing */ ;
27863340Sdes    switch (strspn(ln, "0123456789")) {
27963340Sdes    case 14:
28063340Sdes	break;
28163340Sdes    case 15:
28263340Sdes	ln++;
28363340Sdes	ln[0] = '2';
28463340Sdes	ln[1] = '0';
28563340Sdes	break;
28663340Sdes    default:
28763340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
28863340Sdes	return -1;
28963340Sdes    }
29063340Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
29163340Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
29263340Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
29363340Sdes	_ftp_seterr(FTP_PROTOCOL_ERROR);
29463340Sdes	return -1;
29563340Sdes    }
29663340Sdes    tm.tm_mon--;
29763340Sdes    tm.tm_year -= 1900;
29863340Sdes    tm.tm_isdst = -1;
29963340Sdes    t = timegm(&tm);
30063340Sdes    if (t == (time_t)-1)
30163340Sdes	t = time(NULL);
30263340Sdes    us->mtime = t;
30363340Sdes    us->atime = t;
30463340Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
30563340Sdes		  "%02d:%02d:%02d\033[m]\n",
30663340Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
30763340Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
30863340Sdes    return 0;
30963340Sdes}
31063340Sdes
31163340Sdes/*
31267430Sdes * I/O functions for FTP
31367430Sdes */
31467430Sdesstruct ftpio {
31567430Sdes    int		 csd;		/* Control socket descriptor */
31667430Sdes    int		 dsd;		/* Data socket descriptor */
31767430Sdes    int		 dir;		/* Direction */
31867430Sdes    int		 eof;		/* EOF reached */
31967430Sdes    int		 err;		/* Error code */
32067430Sdes};
32167430Sdes
32267430Sdesstatic int	_ftp_readfn(void *, char *, int);
32367430Sdesstatic int	_ftp_writefn(void *, const char *, int);
32467430Sdesstatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
32567430Sdesstatic int	_ftp_closefn(void *);
32667430Sdes
32767430Sdesstatic int
32867430Sdes_ftp_readfn(void *v, char *buf, int len)
32967430Sdes{
33067430Sdes    struct ftpio *io;
33167430Sdes    int r;
33267430Sdes
33367430Sdes    io = (struct ftpio *)v;
33467890Sdes    if (io == NULL) {
33567890Sdes	errno = EBADF;
33667890Sdes	return -1;
33767890Sdes    }
33867430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
33967430Sdes	errno = EBADF;
34067430Sdes	return -1;
34167430Sdes    }
34267430Sdes    if (io->err) {
34367430Sdes	errno = io->err;
34467430Sdes	return -1;
34567430Sdes    }
34667430Sdes    if (io->eof)
34767430Sdes	return 0;
34867430Sdes    r = read(io->dsd, buf, len);
34967430Sdes    if (r > 0)
35067430Sdes	return r;
35167430Sdes    if (r == 0) {
35267430Sdes	io->eof = 1;
35378071Sdes	return 0;
35467430Sdes    }
35573934Sdes    if (errno != EINTR)
35673934Sdes	io->err = errno;
35767430Sdes    return -1;
35867430Sdes}
35967430Sdes
36067430Sdesstatic int
36167430Sdes_ftp_writefn(void *v, const char *buf, int len)
36267430Sdes{
36367430Sdes    struct ftpio *io;
36467430Sdes    int w;
36567430Sdes
36667430Sdes    io = (struct ftpio *)v;
36767890Sdes    if (io == NULL) {
36867890Sdes	errno = EBADF;
36967890Sdes	return -1;
37067890Sdes    }
37167430Sdes    if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
37267430Sdes	errno = EBADF;
37367430Sdes	return -1;
37467430Sdes    }
37567430Sdes    if (io->err) {
37667430Sdes	errno = io->err;
37767430Sdes	return -1;
37867430Sdes    }
37967430Sdes    w = write(io->dsd, buf, len);
38067430Sdes    if (w >= 0)
38167430Sdes	return w;
38273934Sdes    if (errno != EINTR)
38373934Sdes	io->err = errno;
38467430Sdes    return -1;
38567430Sdes}
38667430Sdes
38767430Sdesstatic fpos_t
38885093Sdes_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused)
38967430Sdes{
39067890Sdes    struct ftpio *io;
39167890Sdes
39267890Sdes    io = (struct ftpio *)v;
39367890Sdes    if (io == NULL) {
39467890Sdes	errno = EBADF;
39567890Sdes	return -1;
39667890Sdes    }
39767430Sdes    errno = ESPIPE;
39867430Sdes    return -1;
39967430Sdes}
40067430Sdes
40167430Sdesstatic int
40267430Sdes_ftp_closefn(void *v)
40367430Sdes{
40467430Sdes    struct ftpio *io;
40567890Sdes    int r;
40667430Sdes
40767430Sdes    io = (struct ftpio *)v;
40867890Sdes    if (io == NULL) {
40967890Sdes	errno = EBADF;
41067890Sdes	return -1;
41167890Sdes    }
41267430Sdes    if (io->dir == -1)
41367430Sdes	return 0;
41467430Sdes    if (io->csd == -1 || io->dsd == -1) {
41567430Sdes	errno = EBADF;
41667430Sdes	return -1;
41767430Sdes    }
41867707Sdes    close(io->dsd);
41967430Sdes    io->dir = -1;
42067430Sdes    io->dsd = -1;
42167707Sdes    DEBUG(fprintf(stderr, "Waiting for final status\n"));
42277234Sdes    r = _ftp_chkerr(io->csd);
42367430Sdes    close(io->csd);
42477234Sdes    free(io);
42577234Sdes    return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1;
42667430Sdes}
42767430Sdes
42867430Sdesstatic FILE *
42967430Sdes_ftp_setup(int csd, int dsd, int mode)
43067430Sdes{
43167430Sdes    struct ftpio *io;
43267430Sdes    FILE *f;
43367430Sdes
43467430Sdes    if ((io = malloc(sizeof *io)) == NULL)
43567430Sdes	return NULL;
43667430Sdes    io->csd = dup(csd);
43767430Sdes    io->dsd = dsd;
43867430Sdes    io->dir = mode;
43967430Sdes    io->eof = io->err = 0;
44067430Sdes    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
44167430Sdes    if (f == NULL)
44267430Sdes	free(io);
44367430Sdes    return f;
44467430Sdes}
44567430Sdes
44667430Sdes/*
44737608Sdes * Transfer file
44837535Sdes */
44937535Sdesstatic FILE *
45075891Sarchie_ftp_transfer(int cd, const char *oper, const char *file,
45175891Sarchie	      int mode, off_t offset, const char *flags)
45237535Sdes{
45385093Sdes    struct sockaddr_storage sa;
45460737Sume    struct sockaddr_in6 *sin6;
45560737Sume    struct sockaddr_in *sin4;
45674716Sdes    int low, pasv, verbose;
45755544Sdes    int e, sd = -1;
45855544Sdes    socklen_t l;
45937573Sdes    char *s;
46037573Sdes    FILE *df;
46155544Sdes
46255544Sdes    /* check flags */
46374716Sdes    low = CHECK_FLAG('l');
46467892Sdes    pasv = CHECK_FLAG('p');
46567892Sdes    verbose = CHECK_FLAG('v');
46655544Sdes
46760951Sdes    /* passive mode */
46867259Sdes    if (!pasv)
46969670Sdes	pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
47067259Sdes		strncasecmp(s, "no", 2) != 0);
47160951Sdes
47260737Sume    /* find our own address, bind, and listen */
47385093Sdes    l = sizeof sa;
47485093Sdes    if (getsockname(cd, (struct sockaddr *)&sa, &l) == -1)
47560737Sume	goto sysouch;
47685093Sdes    if (sa.ss_family == AF_INET6)
47785093Sdes	unmappedaddr((struct sockaddr_in6 *)&sa);
47860737Sume
47937573Sdes    /* open data socket */
48085093Sdes    if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
48140939Sdes	_fetch_syserr();
48237573Sdes	return NULL;
48337573Sdes    }
48437573Sdes
48537573Sdes    if (pasv) {
48660737Sume	u_char addr[64];
48737573Sdes	char *ln, *p;
48885093Sdes	unsigned int i;
48960737Sume	int port;
49037573Sdes
49137573Sdes	/* send PASV command */
49255544Sdes	if (verbose)
49355544Sdes	    _fetch_info("setting passive mode");
49485093Sdes	switch (sa.ss_family) {
49560737Sume	case AF_INET:
49660737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
49760737Sume		goto ouch;
49860737Sume	    break;
49960737Sume	case AF_INET6:
50060737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
50160737Sume		if (e == -1)
50260737Sume		    goto ouch;
50360737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
50460737Sume		    goto ouch;
50560737Sume	    }
50660737Sume	    break;
50760737Sume	default:
50863336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
50937573Sdes	    goto ouch;
51060737Sume	}
51137573Sdes
51255544Sdes	/*
51355544Sdes	 * Find address and port number. The reply to the PASV command
51455544Sdes         * is IMHO the one and only weak point in the FTP protocol.
51555544Sdes	 */
51655557Sdes	ln = last_reply;
51762888Sume      	switch (e) {
51860737Sume	case FTP_PASSIVE_MODE:
51960737Sume	case FTP_LPASSIVE_MODE:
52062888Sume	    for (p = ln + 3; *p && !isdigit(*p); p++)
52162888Sume		/* nothing */ ;
52262888Sume	    if (!*p) {
52363336Sdes		e = FTP_PROTOCOL_ERROR;
52462888Sume		goto ouch;
52562888Sume	    }
52660737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
52760737Sume	    for (i = 0; *p && i < l; i++, p++)
52860737Sume		addr[i] = strtol(p, &p, 10);
52960737Sume	    if (i < l) {
53063336Sdes		e = FTP_PROTOCOL_ERROR;
53160737Sume		goto ouch;
53260737Sume	    }
53360737Sume	    break;
53460737Sume	case FTP_EPASSIVE_MODE:
53562888Sume	    for (p = ln + 3; *p && *p != '('; p++)
53662888Sume		/* nothing */ ;
53762888Sume	    if (!*p) {
53863336Sdes		e = FTP_PROTOCOL_ERROR;
53962888Sume		goto ouch;
54062888Sume	    }
54162888Sume	    ++p;
54260737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
54360737Sume		       &port, &addr[3]) != 5 ||
54460737Sume		addr[0] != addr[1] ||
54560737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
54663336Sdes		e = FTP_PROTOCOL_ERROR;
54760737Sume		goto ouch;
54860737Sume	    }
54960737Sume	    break;
55060737Sume	}
55137573Sdes
55260188Sdes	/* seek to required offset */
55360188Sdes	if (offset)
55460188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
55560188Sdes		goto sysouch;
55660188Sdes
55737573Sdes	/* construct sockaddr for data socket */
55885093Sdes	l = sizeof sa;
55985093Sdes	if (getpeername(cd, (struct sockaddr *)&sa, &l) == -1)
56037573Sdes	    goto sysouch;
56185093Sdes	if (sa.ss_family == AF_INET6)
56285093Sdes	    unmappedaddr((struct sockaddr_in6 *)&sa);
56385093Sdes	switch (sa.ss_family) {
56460737Sume	case AF_INET6:
56585093Sdes	    sin6 = (struct sockaddr_in6 *)&sa;
56660737Sume	    if (e == FTP_EPASSIVE_MODE)
56760737Sume		sin6->sin6_port = htons(port);
56860737Sume	    else {
56960737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
57060737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
57160737Sume	    }
57260737Sume	    break;
57360737Sume	case AF_INET:
57485093Sdes	    sin4 = (struct sockaddr_in *)&sa;
57560737Sume	    if (e == FTP_EPASSIVE_MODE)
57660737Sume		sin4->sin_port = htons(port);
57760737Sume	    else {
57860737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
57960737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
58060737Sume	    }
58160737Sume	    break;
58260737Sume	default:
58363336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
58460737Sume	    break;
58560737Sume	}
58637573Sdes
58737573Sdes	/* connect to data port */
58855544Sdes	if (verbose)
58955544Sdes	    _fetch_info("opening data connection");
59085093Sdes	if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
59137573Sdes	    goto sysouch;
59260188Sdes
59337573Sdes	/* make the server initiate the transfer */
59461866Sdes	if (verbose)
59561866Sdes	    _fetch_info("initiating transfer");
59663340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
59764883Sdes	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
59837573Sdes	    goto ouch;
59937573Sdes
60037573Sdes    } else {
60137573Sdes	u_int32_t a;
60237573Sdes	u_short p;
60355544Sdes	int arg, d;
60460737Sume	char *ap;
60560737Sume	char hname[INET6_ADDRSTRLEN];
60637573Sdes
60785093Sdes	switch (sa.ss_family) {
60860737Sume	case AF_INET6:
60985093Sdes	    ((struct sockaddr_in6 *)&sa)->sin6_port = 0;
61060737Sume#ifdef IPV6_PORTRANGE
61174716Sdes	    arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
61260737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
61360737Sume			   (char *)&arg, sizeof(arg)) == -1)
61460737Sume		goto sysouch;
61560737Sume#endif
61660737Sume	    break;
61760737Sume	case AF_INET:
61885093Sdes	    ((struct sockaddr_in *)&sa)->sin_port = 0;
61974716Sdes	    arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
62060737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
62160737Sume			   (char *)&arg, sizeof arg) == -1)
62260737Sume		goto sysouch;
62360737Sume	    break;
62460737Sume	}
62555544Sdes	if (verbose)
62655544Sdes	    _fetch_info("binding data socket");
62785093Sdes	if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
62837573Sdes	    goto sysouch;
62938394Sdes	if (listen(sd, 1) == -1)
63037573Sdes	    goto sysouch;
63137573Sdes
63237573Sdes	/* find what port we're on and tell the server */
63385093Sdes	if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1)
63437573Sdes	    goto sysouch;
63585093Sdes	switch (sa.ss_family) {
63660737Sume	case AF_INET:
63785093Sdes	    sin4 = (struct sockaddr_in *)&sa;
63860737Sume	    a = ntohl(sin4->sin_addr.s_addr);
63960737Sume	    p = ntohs(sin4->sin_port);
64060737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
64160737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
64260737Sume			 (a >> 8) & 0xff, a & 0xff,
64360737Sume			 (p >> 8) & 0xff, p & 0xff);
64460737Sume	    break;
64560737Sume	case AF_INET6:
64660737Sume#define UC(b)	(((int)b)&0xff)
64760737Sume	    e = -1;
64885093Sdes	    sin6 = (struct sockaddr_in6 *)&sa;
64985093Sdes	    if (getnameinfo((struct sockaddr *)&sa, sa.ss_len,
65060737Sume			    hname, sizeof(hname),
65160737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
65260737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
65360737Sume			     htons(sin6->sin6_port));
65460737Sume		if (e == -1)
65560737Sume		    goto ouch;
65660737Sume	    }
65760737Sume	    if (e != FTP_OK) {
65860737Sume		ap = (char *)&sin6->sin6_addr;
65960737Sume		e = _ftp_cmd(cd,
66060737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
66160737Sume			     6, 16,
66260737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
66360737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
66460737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
66560737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
66660737Sume			     2,
66760737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
66860737Sume			     ntohs(sin6->sin6_port)        & 0xff);
66960737Sume	    }
67060737Sume	    break;
67160737Sume	default:
67263336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
67360737Sume	    goto ouch;
67460737Sume	}
67541869Sdes	if (e != FTP_OK)
67637573Sdes	    goto ouch;
67737573Sdes
67862256Sdes	/* seek to required offset */
67962256Sdes	if (offset)
68062256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
68162256Sdes		goto sysouch;
68262256Sdes
68337573Sdes	/* make the server initiate the transfer */
68455544Sdes	if (verbose)
68555544Sdes	    _fetch_info("initiating transfer");
68663340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
68741869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
68837573Sdes	    goto ouch;
68937573Sdes
69037573Sdes	/* accept the incoming connection and go to town */
69138394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
69237573Sdes	    goto sysouch;
69337573Sdes	close(sd);
69437573Sdes	sd = d;
69537573Sdes    }
69637573Sdes
69767430Sdes    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
69837573Sdes	goto sysouch;
69937573Sdes    return df;
70037573Sdes
70137573Sdessysouch:
70240939Sdes    _fetch_syserr();
70360737Sume    if (sd >= 0)
70460737Sume	close(sd);
70541869Sdes    return NULL;
70641869Sdes
70737573Sdesouch:
70855557Sdes    if (e != -1)
70955557Sdes	_ftp_seterr(e);
71060737Sume    if (sd >= 0)
71160737Sume	close(sd);
71237535Sdes    return NULL;
71337535Sdes}
71437535Sdes
71537571Sdes/*
71677238Sdes * Authenticate
71777238Sdes */
71877238Sdesstatic int
71977238Sdes_ftp_authenticate(int cd, struct url *url, struct url *purl)
72077238Sdes{
72185093Sdes    const char *user, *pwd, *logname;
72277238Sdes    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
72377238Sdes    int e, len;
72477238Sdes
72577238Sdes    /* XXX FTP_AUTH, and maybe .netrc */
72677238Sdes
72777238Sdes    /* send user name and password */
72877238Sdes    user = url->user;
72977238Sdes    if (!user || !*user)
73077238Sdes	user = getenv("FTP_LOGIN");
73177238Sdes    if (!user || !*user)
73277238Sdes	user = FTP_ANONYMOUS_USER;
73377238Sdes    if (purl && url->port == _fetch_default_port(url->scheme))
73477238Sdes	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
73577238Sdes    else if (purl)
73677238Sdes	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
73777238Sdes    else
73877238Sdes	e = _ftp_cmd(cd, "USER %s", user);
73977238Sdes
74077238Sdes    /* did the server request a password? */
74177238Sdes    if (e == FTP_NEED_PASSWORD) {
74277238Sdes	pwd = url->pwd;
74377238Sdes	if (!pwd || !*pwd)
74477238Sdes	    pwd = getenv("FTP_PASSWORD");
74577238Sdes	if (!pwd || !*pwd) {
74677238Sdes	    if ((logname = getlogin()) == 0)
74777238Sdes		logname = FTP_ANONYMOUS_USER;
74881985Sbrian	    if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0)
74981972Sbrian		len = 0;
75081978Sbrian	    else if (len > MAXLOGNAME)
75181978Sbrian	        len = MAXLOGNAME;
75277238Sdes	    gethostname(pbuf + len, sizeof pbuf - len);
75377238Sdes	    pwd = pbuf;
75477238Sdes	}
75577238Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
75677238Sdes    }
75777238Sdes
75877238Sdes    return e;
75977238Sdes}
76077238Sdes
76177238Sdes/*
76237571Sdes * Log on to FTP server
76337535Sdes */
76455557Sdesstatic int
76575891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags)
76637571Sdes{
76767043Sdes    int cd, e, direct, verbose;
76860737Sume#ifdef INET6
76960737Sume    int af = AF_UNSPEC;
77060737Sume#else
77160737Sume    int af = AF_INET;
77260737Sume#endif
77337571Sdes
77467892Sdes    direct = CHECK_FLAG('d');
77567892Sdes    verbose = CHECK_FLAG('v');
77667892Sdes    if (CHECK_FLAG('4'))
77760737Sume	af = AF_INET;
77867892Sdes    else if (CHECK_FLAG('6'))
77960737Sume	af = AF_INET6;
78060737Sume
78167043Sdes    if (direct)
78267043Sdes	purl = NULL;
78367043Sdes
78437608Sdes    /* check for proxy */
78567043Sdes    if (purl) {
78667043Sdes	/* XXX proxy authentication! */
78767043Sdes	cd = _fetch_connect(purl->host, purl->port, af, verbose);
78837608Sdes    } else {
78937608Sdes	/* no proxy, go straight to target */
79067043Sdes	cd = _fetch_connect(url->host, url->port, af, verbose);
79167043Sdes	purl = NULL;
79237608Sdes    }
79337608Sdes
79437608Sdes    /* check connection */
79555557Sdes    if (cd == -1) {
79640939Sdes	_fetch_syserr();
79737571Sdes	return NULL;
79837571Sdes    }
79937608Sdes
80037571Sdes    /* expect welcome message */
80155557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
80237571Sdes	goto fouch;
80337571Sdes
80477238Sdes    /* authenticate */
80577238Sdes    if ((e = _ftp_authenticate(cd, url, purl)) != FTP_LOGGED_IN)
80641863Sdes	goto fouch;
80737608Sdes
80837571Sdes    /* might as well select mode and type at once */
80937571Sdes#ifdef FTP_FORCE_STREAM_MODE
81055557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
81141869Sdes	goto fouch;
81237571Sdes#endif
81355557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
81441869Sdes	goto fouch;
81537571Sdes
81637571Sdes    /* done */
81755557Sdes    return cd;
81837571Sdes
81937571Sdesfouch:
82055557Sdes    if (e != -1)
82155557Sdes	_ftp_seterr(e);
82255557Sdes    close(cd);
82337571Sdes    return NULL;
82437571Sdes}
82537571Sdes
82637571Sdes/*
82737571Sdes * Disconnect from server
82837571Sdes */
82937571Sdesstatic void
83055557Sdes_ftp_disconnect(int cd)
83137571Sdes{
83255557Sdes    (void)_ftp_cmd(cd, "QUIT");
83355557Sdes    close(cd);
83437571Sdes}
83537571Sdes
83637571Sdes/*
83737571Sdes * Check if we're already connected
83837571Sdes */
83937571Sdesstatic int
84040975Sdes_ftp_isconnected(struct url *url)
84137571Sdes{
84237571Sdes    return (cached_socket
84337571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
84437571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
84537571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
84637571Sdes	    && (url->port == cached_host.port));
84737571Sdes}
84837571Sdes
84937608Sdes/*
85041869Sdes * Check the cache, reconnect if no luck
85137608Sdes */
85255557Sdesstatic int
85375891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags)
85437535Sdes{
85555557Sdes    int e, cd;
85637535Sdes
85755557Sdes    cd = -1;
85841869Sdes
85937571Sdes    /* set default port */
86063842Sdes    if (!url->port)
86168551Sdes	url->port = _fetch_default_port(url->scheme);
86237535Sdes
86341863Sdes    /* try to use previously cached connection */
86455557Sdes    if (_ftp_isconnected(url)) {
86555557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
86655557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
86767043Sdes	    return cached_socket;
86855557Sdes    }
86937571Sdes
87037571Sdes    /* connect to server */
87167043Sdes    if ((cd = _ftp_connect(url, purl, flags)) == -1)
87267043Sdes	return -1;
87367043Sdes    if (cached_socket)
87467043Sdes	_ftp_disconnect(cached_socket);
87567043Sdes    cached_socket = cd;
87667043Sdes    memcpy(&cached_host, url, sizeof *url);
87755557Sdes    return cd;
87837535Sdes}
87937535Sdes
88037571Sdes/*
88167043Sdes * Check the proxy settings
88263713Sdes */
88367043Sdesstatic struct url *
88467043Sdes_ftp_get_proxy(void)
88563713Sdes{
88667043Sdes    struct url *purl;
88763713Sdes    char *p;
88867043Sdes
88973932Sdes    if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
89073932Sdes	 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
89167043Sdes	*p && (purl = fetchParseURL(p)) != NULL) {
89269272Sdes	if (!*purl->scheme) {
89373932Sdes	    if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
89469272Sdes		strcpy(purl->scheme, SCHEME_FTP);
89569272Sdes	    else
89669272Sdes		strcpy(purl->scheme, SCHEME_HTTP);
89769272Sdes	}
89867043Sdes	if (!purl->port)
89968551Sdes	    purl->port = _fetch_default_proxy_port(purl->scheme);
90067043Sdes	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
90167043Sdes	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
90267043Sdes	    return purl;
90367043Sdes	fetchFreeURL(purl);
90467043Sdes    }
90567043Sdes    return NULL;
90663713Sdes}
90763713Sdes
90863713Sdes/*
90963340Sdes * Get and stat file
91037571Sdes */
91137535SdesFILE *
91275891SarchiefetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
91337608Sdes{
91467043Sdes    struct url *purl;
91555557Sdes    int cd;
91663713Sdes
91767043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
91867892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
91967043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
92067043Sdes	    return _http_request(url, "GET", us, purl, flags);
92167043Sdes    } else {
92267043Sdes	purl = NULL;
92367043Sdes    }
92455557Sdes
92541869Sdes    /* connect to server */
92667043Sdes    cd = _ftp_cached_connect(url, purl, flags);
92767043Sdes    if (purl)
92867043Sdes	fetchFreeURL(purl);
92967043Sdes    if (cd == NULL)
93041869Sdes	return NULL;
93141869Sdes
93263340Sdes    /* change directory */
93363340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
93463340Sdes	return NULL;
93563340Sdes
93663340Sdes    /* stat file */
93763392Sdes    if (us && _ftp_stat(cd, url->doc, us) == -1
93863910Sdes	&& fetchLastErrCode != FETCH_PROTO
93963392Sdes	&& fetchLastErrCode != FETCH_UNAVAIL)
94063340Sdes	return NULL;
94163340Sdes
94241869Sdes    /* initiate the transfer */
94367430Sdes    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
94437608Sdes}
94537608Sdes
94641869Sdes/*
94763340Sdes * Get file
94863340Sdes */
94963340SdesFILE *
95075891SarchiefetchGetFTP(struct url *url, const char *flags)
95163340Sdes{
95263340Sdes    return fetchXGetFTP(url, NULL, flags);
95363340Sdes}
95463340Sdes
95563340Sdes/*
95641869Sdes * Put file
95741869Sdes */
95837608SdesFILE *
95975891SarchiefetchPutFTP(struct url *url, const char *flags)
96037535Sdes{
96167043Sdes    struct url *purl;
96255557Sdes    int cd;
96341869Sdes
96467043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
96567892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
96667043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
96767043Sdes	    /* XXX HTTP PUT is not implemented, so try without the proxy */
96867043Sdes	    purl = NULL;
96967043Sdes    } else {
97067043Sdes	purl = NULL;
97167043Sdes    }
97263713Sdes
97341869Sdes    /* connect to server */
97467043Sdes    cd = _ftp_cached_connect(url, purl, flags);
97567043Sdes    if (purl)
97667043Sdes	fetchFreeURL(purl);
97767043Sdes    if (cd == NULL)
97841869Sdes	return NULL;
97941869Sdes
98063340Sdes    /* change directory */
98163340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
98263340Sdes	return NULL;
98363340Sdes
98441869Sdes    /* initiate the transfer */
98567892Sdes    return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR",
98667430Sdes			 url->doc, O_WRONLY, url->offset, flags);
98737535Sdes}
98840975Sdes
98941869Sdes/*
99041869Sdes * Get file stats
99141869Sdes */
99240975Sdesint
99375891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
99440975Sdes{
99567043Sdes    struct url *purl;
99663340Sdes    int cd;
99741869Sdes
99867043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
99967892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
100067043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
100167043Sdes	    FILE *f;
100267043Sdes
100367043Sdes	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
100467043Sdes		return -1;
100567043Sdes	    fclose(f);
100667043Sdes	    return 0;
100767043Sdes	}
100867043Sdes    } else {
100967043Sdes	purl = NULL;
101067043Sdes    }
101163713Sdes
101241869Sdes    /* connect to server */
101367043Sdes    cd = _ftp_cached_connect(url, purl, flags);
101467043Sdes    if (purl)
101567043Sdes	fetchFreeURL(purl);
101667043Sdes    if (cd == NULL)
101767043Sdes	return NULL;
101867043Sdes
101941869Sdes    /* change directory */
102063340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
102141869Sdes	return -1;
102241869Sdes
102363340Sdes    /* stat file */
102463340Sdes    return _ftp_stat(cd, url->doc, us);
102540975Sdes}
102641989Sdes
102741989Sdes/*
102841989Sdes * List a directory
102941989Sdes */
103041989Sdesstruct url_ent *
103185093SdesfetchListFTP(struct url *url __unused, const char *flags __unused)
103241989Sdes{
103361866Sdes    warnx("fetchListFTP(): not implemented");
103461866Sdes    return NULL;
103541989Sdes}
1036