ftp.c revision 77234
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 77234 2001-05-26 17:23:38Z 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>
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"));
42177234Sdes    r = _ftp_chkerr(io->csd);
42267430Sdes    close(io->csd);
42377234Sdes    free(io);
42477234Sdes    return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1;
42567430Sdes}
42667430Sdes
42767430Sdesstatic FILE *
42867430Sdes_ftp_setup(int csd, int dsd, int mode)
42967430Sdes{
43067430Sdes    struct ftpio *io;
43167430Sdes    FILE *f;
43267430Sdes
43367430Sdes    if ((io = malloc(sizeof *io)) == NULL)
43467430Sdes	return NULL;
43567430Sdes    io->csd = dup(csd);
43667430Sdes    io->dsd = dsd;
43767430Sdes    io->dir = mode;
43867430Sdes    io->eof = io->err = 0;
43967430Sdes    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
44067430Sdes    if (f == NULL)
44167430Sdes	free(io);
44267430Sdes    return f;
44367430Sdes}
44467430Sdes
44567430Sdes/*
44637608Sdes * Transfer file
44737535Sdes */
44837535Sdesstatic FILE *
44975891Sarchie_ftp_transfer(int cd, const char *oper, const char *file,
45075891Sarchie	      int mode, off_t offset, const char *flags)
45137535Sdes{
45260737Sume    struct sockaddr_storage sin;
45360737Sume    struct sockaddr_in6 *sin6;
45460737Sume    struct sockaddr_in *sin4;
45574716Sdes    int low, pasv, verbose;
45655544Sdes    int e, sd = -1;
45755544Sdes    socklen_t l;
45837573Sdes    char *s;
45937573Sdes    FILE *df;
46055544Sdes
46155544Sdes    /* check flags */
46274716Sdes    low = CHECK_FLAG('l');
46367892Sdes    pasv = CHECK_FLAG('p');
46467892Sdes    verbose = CHECK_FLAG('v');
46555544Sdes
46660951Sdes    /* passive mode */
46767259Sdes    if (!pasv)
46869670Sdes	pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
46967259Sdes		strncasecmp(s, "no", 2) != 0);
47060951Sdes
47160737Sume    /* find our own address, bind, and listen */
47260737Sume    l = sizeof sin;
47360737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
47460737Sume	goto sysouch;
47560737Sume    if (sin.ss_family == AF_INET6)
47660737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
47760737Sume
47837573Sdes    /* open data socket */
47960737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
48040939Sdes	_fetch_syserr();
48137573Sdes	return NULL;
48237573Sdes    }
48337573Sdes
48437573Sdes    if (pasv) {
48560737Sume	u_char addr[64];
48637573Sdes	char *ln, *p;
48737573Sdes	int i;
48860737Sume	int port;
48937573Sdes
49037573Sdes	/* send PASV command */
49155544Sdes	if (verbose)
49255544Sdes	    _fetch_info("setting passive mode");
49360737Sume	switch (sin.ss_family) {
49460737Sume	case AF_INET:
49560737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
49660737Sume		goto ouch;
49760737Sume	    break;
49860737Sume	case AF_INET6:
49960737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
50060737Sume		if (e == -1)
50160737Sume		    goto ouch;
50260737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
50360737Sume		    goto ouch;
50460737Sume	    }
50560737Sume	    break;
50660737Sume	default:
50763336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
50837573Sdes	    goto ouch;
50960737Sume	}
51037573Sdes
51155544Sdes	/*
51255544Sdes	 * Find address and port number. The reply to the PASV command
51355544Sdes         * is IMHO the one and only weak point in the FTP protocol.
51455544Sdes	 */
51555557Sdes	ln = last_reply;
51662888Sume      	switch (e) {
51760737Sume	case FTP_PASSIVE_MODE:
51860737Sume	case FTP_LPASSIVE_MODE:
51962888Sume	    for (p = ln + 3; *p && !isdigit(*p); p++)
52062888Sume		/* nothing */ ;
52162888Sume	    if (!*p) {
52263336Sdes		e = FTP_PROTOCOL_ERROR;
52362888Sume		goto ouch;
52462888Sume	    }
52560737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
52660737Sume	    for (i = 0; *p && i < l; i++, p++)
52760737Sume		addr[i] = strtol(p, &p, 10);
52860737Sume	    if (i < l) {
52963336Sdes		e = FTP_PROTOCOL_ERROR;
53060737Sume		goto ouch;
53160737Sume	    }
53260737Sume	    break;
53360737Sume	case FTP_EPASSIVE_MODE:
53462888Sume	    for (p = ln + 3; *p && *p != '('; p++)
53562888Sume		/* nothing */ ;
53662888Sume	    if (!*p) {
53763336Sdes		e = FTP_PROTOCOL_ERROR;
53862888Sume		goto ouch;
53962888Sume	    }
54062888Sume	    ++p;
54160737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
54260737Sume		       &port, &addr[3]) != 5 ||
54360737Sume		addr[0] != addr[1] ||
54460737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
54563336Sdes		e = FTP_PROTOCOL_ERROR;
54660737Sume		goto ouch;
54760737Sume	    }
54860737Sume	    break;
54960737Sume	}
55037573Sdes
55160188Sdes	/* seek to required offset */
55260188Sdes	if (offset)
55360188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
55460188Sdes		goto sysouch;
55560188Sdes
55637573Sdes	/* construct sockaddr for data socket */
55760188Sdes	l = sizeof sin;
55855557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
55937573Sdes	    goto sysouch;
56060737Sume	if (sin.ss_family == AF_INET6)
56160737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
56260737Sume	switch (sin.ss_family) {
56360737Sume	case AF_INET6:
56460737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
56560737Sume	    if (e == FTP_EPASSIVE_MODE)
56660737Sume		sin6->sin6_port = htons(port);
56760737Sume	    else {
56860737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
56960737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
57060737Sume	    }
57160737Sume	    break;
57260737Sume	case AF_INET:
57360737Sume	    sin4 = (struct sockaddr_in *)&sin;
57460737Sume	    if (e == FTP_EPASSIVE_MODE)
57560737Sume		sin4->sin_port = htons(port);
57660737Sume	    else {
57760737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
57860737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
57960737Sume	    }
58060737Sume	    break;
58160737Sume	default:
58263336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
58360737Sume	    break;
58460737Sume	}
58537573Sdes
58637573Sdes	/* connect to data port */
58755544Sdes	if (verbose)
58855544Sdes	    _fetch_info("opening data connection");
58960737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
59037573Sdes	    goto sysouch;
59160188Sdes
59237573Sdes	/* make the server initiate the transfer */
59361866Sdes	if (verbose)
59461866Sdes	    _fetch_info("initiating transfer");
59563340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
59664883Sdes	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
59737573Sdes	    goto ouch;
59837573Sdes
59937573Sdes    } else {
60037573Sdes	u_int32_t a;
60137573Sdes	u_short p;
60255544Sdes	int arg, d;
60360737Sume	char *ap;
60460737Sume	char hname[INET6_ADDRSTRLEN];
60537573Sdes
60660737Sume	switch (sin.ss_family) {
60760737Sume	case AF_INET6:
60860737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
60960737Sume#ifdef IPV6_PORTRANGE
61074716Sdes	    arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
61160737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
61260737Sume			   (char *)&arg, sizeof(arg)) == -1)
61360737Sume		goto sysouch;
61460737Sume#endif
61560737Sume	    break;
61660737Sume	case AF_INET:
61760737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
61874716Sdes	    arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
61960737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
62060737Sume			   (char *)&arg, sizeof arg) == -1)
62160737Sume		goto sysouch;
62260737Sume	    break;
62360737Sume	}
62455544Sdes	if (verbose)
62555544Sdes	    _fetch_info("binding data socket");
62660737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
62737573Sdes	    goto sysouch;
62838394Sdes	if (listen(sd, 1) == -1)
62937573Sdes	    goto sysouch;
63037573Sdes
63137573Sdes	/* find what port we're on and tell the server */
63238394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
63337573Sdes	    goto sysouch;
63460737Sume	switch (sin.ss_family) {
63560737Sume	case AF_INET:
63660737Sume	    sin4 = (struct sockaddr_in *)&sin;
63760737Sume	    a = ntohl(sin4->sin_addr.s_addr);
63860737Sume	    p = ntohs(sin4->sin_port);
63960737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
64060737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
64160737Sume			 (a >> 8) & 0xff, a & 0xff,
64260737Sume			 (p >> 8) & 0xff, p & 0xff);
64360737Sume	    break;
64460737Sume	case AF_INET6:
64560737Sume#define UC(b)	(((int)b)&0xff)
64660737Sume	    e = -1;
64760737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
64860737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
64960737Sume			    hname, sizeof(hname),
65060737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
65160737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
65260737Sume			     htons(sin6->sin6_port));
65360737Sume		if (e == -1)
65460737Sume		    goto ouch;
65560737Sume	    }
65660737Sume	    if (e != FTP_OK) {
65760737Sume		ap = (char *)&sin6->sin6_addr;
65860737Sume		e = _ftp_cmd(cd,
65960737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
66060737Sume			     6, 16,
66160737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
66260737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
66360737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
66460737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
66560737Sume			     2,
66660737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
66760737Sume			     ntohs(sin6->sin6_port)        & 0xff);
66860737Sume	    }
66960737Sume	    break;
67060737Sume	default:
67163336Sdes	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
67260737Sume	    goto ouch;
67360737Sume	}
67441869Sdes	if (e != FTP_OK)
67537573Sdes	    goto ouch;
67637573Sdes
67762256Sdes	/* seek to required offset */
67862256Sdes	if (offset)
67962256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
68062256Sdes		goto sysouch;
68162256Sdes
68237573Sdes	/* make the server initiate the transfer */
68355544Sdes	if (verbose)
68455544Sdes	    _fetch_info("initiating transfer");
68563340Sdes	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
68641869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
68737573Sdes	    goto ouch;
68837573Sdes
68937573Sdes	/* accept the incoming connection and go to town */
69038394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
69137573Sdes	    goto sysouch;
69237573Sdes	close(sd);
69337573Sdes	sd = d;
69437573Sdes    }
69537573Sdes
69667430Sdes    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
69737573Sdes	goto sysouch;
69837573Sdes    return df;
69937573Sdes
70037573Sdessysouch:
70140939Sdes    _fetch_syserr();
70260737Sume    if (sd >= 0)
70360737Sume	close(sd);
70441869Sdes    return NULL;
70541869Sdes
70637573Sdesouch:
70755557Sdes    if (e != -1)
70855557Sdes	_ftp_seterr(e);
70960737Sume    if (sd >= 0)
71060737Sume	close(sd);
71137535Sdes    return NULL;
71237535Sdes}
71337535Sdes
71437571Sdes/*
71537571Sdes * Log on to FTP server
71637535Sdes */
71755557Sdesstatic int
71875891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags)
71937571Sdes{
72067043Sdes    int cd, e, direct, verbose;
72160737Sume#ifdef INET6
72260737Sume    int af = AF_UNSPEC;
72360737Sume#else
72460737Sume    int af = AF_INET;
72560737Sume#endif
72660791Sume    const char *logname;
72775891Sarchie    const char *user;
72875891Sarchie    const char *pwd;
72960791Sume    char localhost[MAXHOSTNAMELEN];
73060791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
73137571Sdes
73267892Sdes    direct = CHECK_FLAG('d');
73367892Sdes    verbose = CHECK_FLAG('v');
73467892Sdes    if (CHECK_FLAG('4'))
73560737Sume	af = AF_INET;
73667892Sdes    else if (CHECK_FLAG('6'))
73760737Sume	af = AF_INET6;
73860737Sume
73967043Sdes    if (direct)
74067043Sdes	purl = NULL;
74167043Sdes
74237608Sdes    /* check for proxy */
74367043Sdes    if (purl) {
74467043Sdes	/* XXX proxy authentication! */
74567043Sdes	cd = _fetch_connect(purl->host, purl->port, af, verbose);
74637608Sdes    } else {
74737608Sdes	/* no proxy, go straight to target */
74867043Sdes	cd = _fetch_connect(url->host, url->port, af, verbose);
74967043Sdes	purl = NULL;
75037608Sdes    }
75137608Sdes
75237608Sdes    /* check connection */
75355557Sdes    if (cd == -1) {
75440939Sdes	_fetch_syserr();
75537571Sdes	return NULL;
75637571Sdes    }
75737608Sdes
75837571Sdes    /* expect welcome message */
75955557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
76037571Sdes	goto fouch;
76167043Sdes
76267043Sdes    /* XXX FTP_AUTH, and maybe .netrc */
76337571Sdes
76437571Sdes    /* send user name and password */
76567043Sdes    user = url->user;
76637608Sdes    if (!user || !*user)
76770273Sdes	user = getenv("FTP_LOGIN");
76870273Sdes    if (!user || !*user)
76937608Sdes	user = FTP_ANONYMOUS_USER;
77068551Sdes    if (purl && url->port == _fetch_default_port(url->scheme))
77167055Sdes	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
77267043Sdes    else if (purl)
77367055Sdes	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
77463712Sdes    else
77567055Sdes	e = _ftp_cmd(cd, "USER %s", user);
77637608Sdes
77737608Sdes    /* did the server request a password? */
77837608Sdes    if (e == FTP_NEED_PASSWORD) {
77967043Sdes	pwd = url->pwd;
78037608Sdes	if (!pwd || !*pwd)
78160791Sume	    pwd = getenv("FTP_PASSWORD");
78260791Sume	if (!pwd || !*pwd) {
78360791Sume	    if ((logname = getlogin()) == 0)
78470795Sdes		logname = FTP_ANONYMOUS_USER;
78560791Sume	    gethostname(localhost, sizeof localhost);
78660791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
78760791Sume	    pwd = pbuf;
78860791Sume	}
78955557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
79037608Sdes    }
79137608Sdes
79237608Sdes    /* did the server request an account? */
79341869Sdes    if (e == FTP_NEED_ACCOUNT)
79441863Sdes	goto fouch;
79537608Sdes
79637608Sdes    /* we should be done by now */
79741869Sdes    if (e != FTP_LOGGED_IN)
79837571Sdes	goto fouch;
79937571Sdes
80037571Sdes    /* might as well select mode and type at once */
80137571Sdes#ifdef FTP_FORCE_STREAM_MODE
80255557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
80341869Sdes	goto fouch;
80437571Sdes#endif
80555557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
80641869Sdes	goto fouch;
80737571Sdes
80837571Sdes    /* done */
80955557Sdes    return cd;
81037571Sdes
81137571Sdesfouch:
81255557Sdes    if (e != -1)
81355557Sdes	_ftp_seterr(e);
81455557Sdes    close(cd);
81537571Sdes    return NULL;
81637571Sdes}
81737571Sdes
81837571Sdes/*
81937571Sdes * Disconnect from server
82037571Sdes */
82137571Sdesstatic void
82255557Sdes_ftp_disconnect(int cd)
82337571Sdes{
82455557Sdes    (void)_ftp_cmd(cd, "QUIT");
82555557Sdes    close(cd);
82637571Sdes}
82737571Sdes
82837571Sdes/*
82937571Sdes * Check if we're already connected
83037571Sdes */
83137571Sdesstatic int
83240975Sdes_ftp_isconnected(struct url *url)
83337571Sdes{
83437571Sdes    return (cached_socket
83537571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
83637571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
83737571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
83837571Sdes	    && (url->port == cached_host.port));
83937571Sdes}
84037571Sdes
84137608Sdes/*
84241869Sdes * Check the cache, reconnect if no luck
84337608Sdes */
84455557Sdesstatic int
84575891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags)
84637535Sdes{
84755557Sdes    int e, cd;
84837535Sdes
84955557Sdes    cd = -1;
85041869Sdes
85137571Sdes    /* set default port */
85263842Sdes    if (!url->port)
85368551Sdes	url->port = _fetch_default_port(url->scheme);
85437535Sdes
85541863Sdes    /* try to use previously cached connection */
85655557Sdes    if (_ftp_isconnected(url)) {
85755557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
85855557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
85967043Sdes	    return cached_socket;
86055557Sdes    }
86137571Sdes
86237571Sdes    /* connect to server */
86367043Sdes    if ((cd = _ftp_connect(url, purl, flags)) == -1)
86467043Sdes	return -1;
86567043Sdes    if (cached_socket)
86667043Sdes	_ftp_disconnect(cached_socket);
86767043Sdes    cached_socket = cd;
86867043Sdes    memcpy(&cached_host, url, sizeof *url);
86955557Sdes    return cd;
87037535Sdes}
87137535Sdes
87237571Sdes/*
87367043Sdes * Check the proxy settings
87463713Sdes */
87567043Sdesstatic struct url *
87667043Sdes_ftp_get_proxy(void)
87763713Sdes{
87867043Sdes    struct url *purl;
87963713Sdes    char *p;
88067043Sdes
88173932Sdes    if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
88273932Sdes	 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
88367043Sdes	*p && (purl = fetchParseURL(p)) != NULL) {
88469272Sdes	if (!*purl->scheme) {
88573932Sdes	    if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
88669272Sdes		strcpy(purl->scheme, SCHEME_FTP);
88769272Sdes	    else
88869272Sdes		strcpy(purl->scheme, SCHEME_HTTP);
88969272Sdes	}
89067043Sdes	if (!purl->port)
89168551Sdes	    purl->port = _fetch_default_proxy_port(purl->scheme);
89267043Sdes	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
89367043Sdes	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
89467043Sdes	    return purl;
89567043Sdes	fetchFreeURL(purl);
89667043Sdes    }
89767043Sdes    return NULL;
89863713Sdes}
89963713Sdes
90063713Sdes/*
90163340Sdes * Get and stat file
90237571Sdes */
90337535SdesFILE *
90475891SarchiefetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
90537608Sdes{
90667043Sdes    struct url *purl;
90755557Sdes    int cd;
90863713Sdes
90967043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
91067892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
91167043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
91267043Sdes	    return _http_request(url, "GET", us, purl, flags);
91367043Sdes    } else {
91467043Sdes	purl = NULL;
91567043Sdes    }
91655557Sdes
91741869Sdes    /* connect to server */
91867043Sdes    cd = _ftp_cached_connect(url, purl, flags);
91967043Sdes    if (purl)
92067043Sdes	fetchFreeURL(purl);
92167043Sdes    if (cd == NULL)
92241869Sdes	return NULL;
92341869Sdes
92463340Sdes    /* change directory */
92563340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
92663340Sdes	return NULL;
92763340Sdes
92863340Sdes    /* stat file */
92963392Sdes    if (us && _ftp_stat(cd, url->doc, us) == -1
93063910Sdes	&& fetchLastErrCode != FETCH_PROTO
93163392Sdes	&& fetchLastErrCode != FETCH_UNAVAIL)
93263340Sdes	return NULL;
93363340Sdes
93441869Sdes    /* initiate the transfer */
93567430Sdes    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
93637608Sdes}
93737608Sdes
93841869Sdes/*
93963340Sdes * Get file
94063340Sdes */
94163340SdesFILE *
94275891SarchiefetchGetFTP(struct url *url, const char *flags)
94363340Sdes{
94463340Sdes    return fetchXGetFTP(url, NULL, flags);
94563340Sdes}
94663340Sdes
94763340Sdes/*
94841869Sdes * Put file
94941869Sdes */
95037608SdesFILE *
95175891SarchiefetchPutFTP(struct url *url, const char *flags)
95237535Sdes{
95367043Sdes    struct url *purl;
95455557Sdes    int cd;
95541869Sdes
95667043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
95767892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
95867043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
95967043Sdes	    /* XXX HTTP PUT is not implemented, so try without the proxy */
96067043Sdes	    purl = NULL;
96167043Sdes    } else {
96267043Sdes	purl = NULL;
96367043Sdes    }
96463713Sdes
96541869Sdes    /* connect to server */
96667043Sdes    cd = _ftp_cached_connect(url, purl, flags);
96767043Sdes    if (purl)
96867043Sdes	fetchFreeURL(purl);
96967043Sdes    if (cd == NULL)
97041869Sdes	return NULL;
97141869Sdes
97263340Sdes    /* change directory */
97363340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
97463340Sdes	return NULL;
97563340Sdes
97641869Sdes    /* initiate the transfer */
97767892Sdes    return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR",
97867430Sdes			 url->doc, O_WRONLY, url->offset, flags);
97937535Sdes}
98040975Sdes
98141869Sdes/*
98241869Sdes * Get file stats
98341869Sdes */
98440975Sdesint
98575891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
98640975Sdes{
98767043Sdes    struct url *purl;
98863340Sdes    int cd;
98941869Sdes
99067043Sdes    /* get the proxy URL, and check if we should use HTTP instead */
99167892Sdes    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
99267043Sdes	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
99367043Sdes	    FILE *f;
99467043Sdes
99567043Sdes	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
99667043Sdes		return -1;
99767043Sdes	    fclose(f);
99867043Sdes	    return 0;
99967043Sdes	}
100067043Sdes    } else {
100167043Sdes	purl = NULL;
100267043Sdes    }
100363713Sdes
100441869Sdes    /* connect to server */
100567043Sdes    cd = _ftp_cached_connect(url, purl, flags);
100667043Sdes    if (purl)
100767043Sdes	fetchFreeURL(purl);
100867043Sdes    if (cd == NULL)
100967043Sdes	return NULL;
101067043Sdes
101141869Sdes    /* change directory */
101263340Sdes    if (_ftp_cwd(cd, url->doc) == -1)
101341869Sdes	return -1;
101441869Sdes
101563340Sdes    /* stat file */
101663340Sdes    return _ftp_stat(cd, url->doc, us);
101740975Sdes}
101841989Sdes
101941989Sdes/*
102041989Sdes * List a directory
102141989Sdes */
102241989Sdesstruct url_ent *
102375891SarchiefetchListFTP(struct url *url, const char *flags)
102441989Sdes{
102561866Sdes    warnx("fetchListFTP(): not implemented");
102661866Sdes    return NULL;
102741989Sdes}
1028