ftp.c revision 69043
1189251Ssam/*-
2189251Ssam * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3214734Srpaulo * All rights reserved.
4189251Ssam *
5189251Ssam * Redistribution and use in source and binary forms, with or without
6189251Ssam * modification, are permitted provided that the following conditions
7189251Ssam * are met:
8189251Ssam * 1. Redistributions of source code must retain the above copyright
9189251Ssam *    notice, this list of conditions and the following disclaimer
10189251Ssam *    in this position and unchanged.
11189251Ssam * 2. Redistributions in binary form must reproduce the above copyright
12189251Ssam *    notice, this list of conditions and the following disclaimer in the
13189251Ssam *    documentation and/or other materials provided with the distribution.
14189251Ssam * 3. The name of the author may not be used to endorse or promote products
15189251Ssam *    derived from this software without specific prior written permission
16189251Ssam *
17189251Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18214734Srpaulo * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19214734Srpaulo * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20189251Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21189251Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22189251Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23189251Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24189251Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25189251Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26189251Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27189251Ssam *
28189251Ssam * $FreeBSD: head/lib/libfetch/ftp.c 69043 2000-11-22 14:44:48Z des $
29189251Ssam */
30189251Ssam
31189251Ssam/*
32189251Ssam * Portions of this code were taken from or based on ftpio.c:
33189251Ssam *
34189251Ssam * ----------------------------------------------------------------------------
35189251Ssam * "THE BEER-WARE LICENSE" (Revision 42):
36189251Ssam * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37189251Ssam * can do whatever you want with this stuff. If we meet some day, and you think
38189251Ssam * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39189251Ssam * ----------------------------------------------------------------------------
40189251Ssam *
41189251Ssam * Major Changelog:
42189251Ssam *
43189251Ssam * Dag-Erling Co�dan Sm�rgrav
44189251Ssam * 9 Jun 1998
45189251Ssam *
46189251Ssam * Incorporated into libfetch
47189251Ssam *
48209158Srpaulo * Jordan K. Hubbard
49209158Srpaulo * 17 Jan 1996
50209158Srpaulo *
51209158Srpaulo * Turned inside out. Now returns xfers as new file ids, not as a special
52209158Srpaulo * `state' of FTP_t
53209158Srpaulo *
54209158Srpaulo * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55209158Srpaulo *
56209158Srpaulo */
57209158Srpaulo
58209158Srpaulo#include <sys/param.h>
59209158Srpaulo#include <sys/socket.h>
60189251Ssam#include <netinet/in.h>
61189251Ssam
62189251Ssam#include <ctype.h>
63189251Ssam#include <errno.h>
64189251Ssam#include <fcntl.h>
65189251Ssam#include <netdb.h>
66189251Ssam#include <stdarg.h>
67189251Ssam#include <stdio.h>
68189251Ssam#include <stdlib.h>
69189251Ssam#include <string.h>
70189251Ssam#include <time.h>
71189251Ssam#include <unistd.h>
72189251Ssam
73189251Ssam#include "fetch.h"
74189251Ssam#include "common.h"
75189251Ssam#include "ftperr.h"
76189251Ssam
77209158Srpaulo#define FTP_ANONYMOUS_USER	"ftp"
78189251Ssam#define FTP_ANONYMOUS_PASSWORD	"ftp"
79189251Ssam
80189251Ssam#define FTP_CONNECTION_ALREADY_OPEN	125
81189251Ssam#define FTP_OPEN_DATA_CONNECTION	150
82189251Ssam#define FTP_OK				200
83189251Ssam#define FTP_FILE_STATUS			213
84189251Ssam#define FTP_SERVICE_READY		220
85189251Ssam#define FTP_TRANSFER_COMPLETE		226
86189251Ssam#define FTP_PASSIVE_MODE		227
87189251Ssam#define FTP_LPASSIVE_MODE		228
88189251Ssam#define FTP_EPASSIVE_MODE		229
89189251Ssam#define FTP_LOGGED_IN			230
90189251Ssam#define FTP_FILE_ACTION_OK		250
91189251Ssam#define FTP_NEED_PASSWORD		331
92189251Ssam#define FTP_NEED_ACCOUNT		332
93189251Ssam#define FTP_FILE_OK			350
94189251Ssam#define FTP_SYNTAX_ERROR		500
95189251Ssam#define FTP_PROTOCOL_ERROR		999
96189251Ssam
97189251Ssamstatic struct url cached_host;
98209158Srpaulostatic int cached_socket;
99189251Ssam
100189251Ssamstatic char *last_reply;
101189251Ssamstatic size_t lr_size, lr_length;
102189251Ssamstatic int last_code;
103189251Ssam
104189251Ssam#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105189251Ssam			 && isdigit(foo[2]) \
106189251Ssam                         && (foo[3] == ' ' || foo[3] == '\0'))
107189251Ssam#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
108189251Ssam			&& isdigit(foo[2]) && foo[3] == '-')
109189251Ssam
110189251Ssam/* translate IPv4 mapped IPv6 address to IPv4 address */
111189251Ssamstatic void
112189251Ssamunmappedaddr(struct sockaddr_in6 *sin6)
113189251Ssam{
114189251Ssam    struct sockaddr_in *sin4;
115189251Ssam    u_int32_t addr;
116189251Ssam    int port;
117189251Ssam
118189251Ssam    if (sin6->sin6_family != AF_INET6 ||
119189251Ssam	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
120189251Ssam	return;
121189251Ssam    sin4 = (struct sockaddr_in *)sin6;
122189251Ssam    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
123189251Ssam    port = sin6->sin6_port;
124189251Ssam    memset(sin4, 0, sizeof(struct sockaddr_in));
125189251Ssam    sin4->sin_addr.s_addr = addr;
126189251Ssam    sin4->sin_port = port;
127189251Ssam    sin4->sin_family = AF_INET;
128189251Ssam    sin4->sin_len = sizeof(struct sockaddr_in);
129189251Ssam}
130189251Ssam
131189251Ssam/*
132189251Ssam * Get server response
133189251Ssam */
134189251Ssamstatic int
135189251Ssam_ftp_chkerr(int cd)
136189251Ssam{
137189251Ssam    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
138189251Ssam	_fetch_syserr();
139189251Ssam	return -1;
140189251Ssam    }
141189251Ssam    if (isftpinfo(last_reply)) {
142189251Ssam	while (!isftpreply(last_reply)) {
143189251Ssam	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
144189251Ssam		_fetch_syserr();
145189251Ssam		return -1;
146189251Ssam	    }
147189251Ssam	}
148189251Ssam    }
149189251Ssam
150189251Ssam    while (lr_length && isspace(last_reply[lr_length-1]))
151189251Ssam	lr_length--;
152189251Ssam    last_reply[lr_length] = 0;
153189251Ssam
154189251Ssam    if (!isftpreply(last_reply)) {
155189251Ssam	_ftp_seterr(FTP_PROTOCOL_ERROR);
156189251Ssam	return -1;
157189251Ssam    }
158189251Ssam
159189251Ssam    last_code = (last_reply[0] - '0') * 100
160189251Ssam	+ (last_reply[1] - '0') * 10
161189251Ssam	+ (last_reply[2] - '0');
162189251Ssam
163189251Ssam    return last_code;
164189251Ssam}
165189251Ssam
166189251Ssam/*
167189251Ssam * Send a command and check reply
168189251Ssam */
169189251Ssamstatic int
170189251Ssam_ftp_cmd(int cd, char *fmt, ...)
171189251Ssam{
172214734Srpaulo    va_list ap;
173214734Srpaulo    size_t len;
174189251Ssam    char *msg;
175189251Ssam    int r;
176189251Ssam
177189251Ssam    va_start(ap, fmt);
178214734Srpaulo    len = vasprintf(&msg, fmt, ap);
179214734Srpaulo    va_end(ap);
180189251Ssam
181189251Ssam    if (msg == NULL) {
182189251Ssam	errno = ENOMEM;
183189251Ssam	_fetch_syserr();
184189251Ssam	return -1;
185189251Ssam    }
186189251Ssam
187189251Ssam    r = _fetch_putln(cd, msg, len);
188189251Ssam    free(msg);
189189251Ssam
190189251Ssam    if (r == -1) {
191189251Ssam	_fetch_syserr();
192189251Ssam	return -1;
193189251Ssam    }
194189251Ssam
195189251Ssam    return _ftp_chkerr(cd);
196189251Ssam}
197189251Ssam
198189251Ssam/*
199189251Ssam * Return a pointer to the filename part of a path
200189251Ssam */
201189251Ssamstatic char *
202189251Ssam_ftp_filename(char *file)
203189251Ssam{
204189251Ssam    char *s;
205189251Ssam
206189251Ssam    if ((s = strrchr(file, '/')) == NULL)
207189251Ssam	return file;
208189251Ssam    else
209189251Ssam	return s + 1;
210189251Ssam}
211189251Ssam
212189251Ssam/*
213189251Ssam * Change working directory to the directory that contains the
214189251Ssam * specified file.
215189251Ssam */
216189251Ssamstatic int
217189251Ssam_ftp_cwd(int cd, char *file)
218189251Ssam{
219189251Ssam    char *s;
220189251Ssam    int e;
221189251Ssam
222189251Ssam    if ((s = strrchr(file, '/')) == NULL || s == file) {
223189251Ssam	e = _ftp_cmd(cd, "CWD /");
224189251Ssam    } else {
225189251Ssam	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
226189251Ssam    }
227189251Ssam    if (e != FTP_FILE_ACTION_OK) {
228189251Ssam	_ftp_seterr(e);
229189251Ssam	return -1;
230189251Ssam    }
231189251Ssam    return 0;
232189251Ssam}
233189251Ssam
234189251Ssam/*
235189251Ssam * Request and parse file stats
236189251Ssam */
237189251Ssamstatic int
238189251Ssam_ftp_stat(int cd, char *file, struct url_stat *us)
239189251Ssam{
240189251Ssam    char *ln, *s;
241189251Ssam    struct tm tm;
242189251Ssam    time_t t;
243189251Ssam    int e;
244189251Ssam
245189251Ssam    us->size = -1;
246189251Ssam    us->atime = us->mtime = 0;
247189251Ssam
248189251Ssam    if ((s = strrchr(file, '/')) == NULL)
249189251Ssam	s = file;
250189251Ssam    else
251189251Ssam	++s;
252189251Ssam
253189251Ssam    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
254189251Ssam	_ftp_seterr(e);
255189251Ssam	return -1;
256189251Ssam    }
257189251Ssam    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
258189251Ssam	/* nothing */ ;
259189251Ssam    for (us->size = 0; *ln && isdigit(*ln); ln++)
260189251Ssam	us->size = us->size * 10 + *ln - '0';
261189251Ssam    if (*ln && !isspace(*ln)) {
262189251Ssam	_ftp_seterr(FTP_PROTOCOL_ERROR);
263189251Ssam	return -1;
264189251Ssam    }
265189251Ssam    if (us->size == 0)
266189251Ssam	us->size = -1;
267189251Ssam    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
268189251Ssam
269189251Ssam    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
270189251Ssam	_ftp_seterr(e);
271189251Ssam	return -1;
272189251Ssam    }
273189251Ssam    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
274189251Ssam	/* nothing */ ;
275189251Ssam    switch (strspn(ln, "0123456789")) {
276189251Ssam    case 14:
277189251Ssam	break;
278189251Ssam    case 15:
279189251Ssam	ln++;
280189251Ssam	ln[0] = '2';
281189251Ssam	ln[1] = '0';
282189251Ssam	break;
283189251Ssam    default:
284189251Ssam	_ftp_seterr(FTP_PROTOCOL_ERROR);
285189251Ssam	return -1;
286189251Ssam    }
287189251Ssam    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
288189251Ssam	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
289189251Ssam	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
290189251Ssam	_ftp_seterr(FTP_PROTOCOL_ERROR);
291189251Ssam	return -1;
292189251Ssam    }
293189251Ssam    tm.tm_mon--;
294189251Ssam    tm.tm_year -= 1900;
295189251Ssam    tm.tm_isdst = -1;
296189251Ssam    t = timegm(&tm);
297189251Ssam    if (t == (time_t)-1)
298189251Ssam	t = time(NULL);
299189251Ssam    us->mtime = t;
300189251Ssam    us->atime = t;
301189251Ssam    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
302189251Ssam		  "%02d:%02d:%02d\033[m]\n",
303189251Ssam		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
304189251Ssam		  tm.tm_hour, tm.tm_min, tm.tm_sec));
305189251Ssam    return 0;
306189251Ssam}
307189251Ssam
308189251Ssam/*
309189251Ssam * I/O functions for FTP
310189251Ssam */
311189251Ssamstruct ftpio {
312189251Ssam    int		 csd;		/* Control socket descriptor */
313189251Ssam    int		 dsd;		/* Data socket descriptor */
314189251Ssam    int		 dir;		/* Direction */
315189251Ssam    int		 eof;		/* EOF reached */
316189251Ssam    int		 err;		/* Error code */
317189251Ssam};
318189251Ssam
319189251Ssamstatic int	_ftp_readfn(void *, char *, int);
320189251Ssamstatic int	_ftp_writefn(void *, const char *, int);
321214734Srpaulostatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
322189251Ssamstatic int	_ftp_closefn(void *);
323214734Srpaulo
324189251Ssamstatic int
325214734Srpaulo_ftp_readfn(void *v, char *buf, int len)
326214734Srpaulo{
327214734Srpaulo    struct ftpio *io;
328214734Srpaulo    int r;
329189251Ssam
330189251Ssam    io = (struct ftpio *)v;
331189251Ssam    if (io == NULL) {
332189251Ssam	errno = EBADF;
333214734Srpaulo	return -1;
334189251Ssam    }
335189251Ssam    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
336189251Ssam	errno = EBADF;
337189251Ssam	return -1;
338189251Ssam    }
339214734Srpaulo    if (io->err) {
340189251Ssam	errno = io->err;
341189251Ssam	return -1;
342189251Ssam    }
343189251Ssam    if (io->eof)
344189251Ssam	return 0;
345189251Ssam    r = read(io->dsd, buf, len);
346189251Ssam    if (r > 0)
347189251Ssam	return r;
348189251Ssam    if (r == 0) {
349189251Ssam	io->eof = 1;
350189251Ssam	return _ftp_closefn(v);
351189251Ssam    }
352189251Ssam    io->err = errno;
353189251Ssam    return -1;
354189251Ssam}
355189251Ssam
356189251Ssamstatic int
357189251Ssam_ftp_writefn(void *v, const char *buf, int len)
358214734Srpaulo{
359189251Ssam    struct ftpio *io;
360189251Ssam    int w;
361189251Ssam
362189251Ssam    io = (struct ftpio *)v;
363189251Ssam    if (io == NULL) {
364214734Srpaulo	errno = EBADF;
365189251Ssam	return -1;
366189251Ssam    }
367189251Ssam    if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
368189251Ssam	errno = EBADF;
369189251Ssam	return -1;
370189251Ssam    }
371189251Ssam    if (io->err) {
372189251Ssam	errno = io->err;
373189251Ssam	return -1;
374189251Ssam    }
375189251Ssam    w = write(io->dsd, buf, len);
376189251Ssam    if (w >= 0)
377189251Ssam	return w;
378189251Ssam    io->err = errno;
379189251Ssam    return -1;
380189251Ssam}
381189251Ssam
382189251Ssamstatic fpos_t
383189251Ssam_ftp_seekfn(void *v, fpos_t pos, int whence)
384189251Ssam{
385189251Ssam    struct ftpio *io;
386189251Ssam
387189251Ssam    io = (struct ftpio *)v;
388189251Ssam    if (io == NULL) {
389214734Srpaulo	errno = EBADF;
390214734Srpaulo	return -1;
391214734Srpaulo    }
392189251Ssam    errno = ESPIPE;
393189251Ssam    return -1;
394189251Ssam}
395214734Srpaulo
396189251Ssamstatic int
397214734Srpaulo_ftp_closefn(void *v)
398189251Ssam{
399189251Ssam    struct ftpio *io;
400189251Ssam    int r;
401189251Ssam
402189251Ssam    io = (struct ftpio *)v;
403189251Ssam    if (io == NULL) {
404189251Ssam	errno = EBADF;
405189251Ssam	return -1;
406189251Ssam    }
407189251Ssam    if (io->dir == -1)
408214734Srpaulo	return 0;
409189251Ssam    if (io->csd == -1 || io->dsd == -1) {
410189251Ssam	errno = EBADF;
411189251Ssam	return -1;
412189251Ssam    }
413189251Ssam    close(io->dsd);
414189251Ssam    io->dir = -1;
415189251Ssam    io->dsd = -1;
416189251Ssam    DEBUG(fprintf(stderr, "Waiting for final status\n"));
417189251Ssam    if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE)
418189251Ssam	io->err = r;
419189251Ssam    else
420189251Ssam	io->err = 0;
421189251Ssam    close(io->csd);
422189251Ssam    io->csd = -1;
423189251Ssam    return io->err ? -1 : 0;
424189251Ssam}
425189251Ssam
426189251Ssamstatic FILE *
427189251Ssam_ftp_setup(int csd, int dsd, int mode)
428189251Ssam{
429189251Ssam    struct ftpio *io;
430189251Ssam    FILE *f;
431214734Srpaulo
432189251Ssam    if ((io = malloc(sizeof *io)) == NULL)
433214734Srpaulo	return NULL;
434214734Srpaulo    io->csd = dup(csd);
435189251Ssam    io->dsd = dsd;
436214734Srpaulo    io->dir = mode;
437214734Srpaulo    io->eof = io->err = 0;
438189251Ssam    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
439189251Ssam    if (f == NULL)
440189251Ssam	free(io);
441189251Ssam    return f;
442189251Ssam}
443189251Ssam
444189251Ssam/*
445189251Ssam * Transfer file
446214734Srpaulo */
447189251Ssamstatic FILE *
448189251Ssam_ftp_transfer(int cd, char *oper, char *file,
449189251Ssam	      int mode, off_t offset, char *flags)
450189251Ssam{
451214734Srpaulo    struct sockaddr_storage sin;
452189251Ssam    struct sockaddr_in6 *sin6;
453189251Ssam    struct sockaddr_in *sin4;
454189251Ssam    int pasv, high, verbose;
455189251Ssam    int e, sd = -1;
456189251Ssam    socklen_t l;
457189251Ssam    char *s;
458214734Srpaulo    FILE *df;
459214734Srpaulo
460214734Srpaulo    /* check flags */
461189251Ssam    pasv = CHECK_FLAG('p');
462189251Ssam    high = CHECK_FLAG('h');
463189251Ssam    verbose = CHECK_FLAG('v');
464214734Srpaulo
465189251Ssam    /* passive mode */
466189251Ssam    if (!pasv)
467189251Ssam	pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL ||
468189251Ssam		strncasecmp(s, "no", 2) != 0);
469189251Ssam
470189251Ssam    /* find our own address, bind, and listen */
471189251Ssam    l = sizeof sin;
472189251Ssam    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
473189251Ssam	goto sysouch;
474189251Ssam    if (sin.ss_family == AF_INET6)
475189251Ssam	unmappedaddr((struct sockaddr_in6 *)&sin);
476189251Ssam
477189251Ssam    /* open data socket */
478189251Ssam    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
479189251Ssam	_fetch_syserr();
480189251Ssam	return NULL;
481189251Ssam    }
482189251Ssam
483189251Ssam    if (pasv) {
484189251Ssam	u_char addr[64];
485189251Ssam	char *ln, *p;
486189251Ssam	int i;
487214734Srpaulo	int port;
488214734Srpaulo
489214734Srpaulo	/* send PASV command */
490214734Srpaulo	if (verbose)
491189251Ssam	    _fetch_info("setting passive mode");
492189251Ssam	switch (sin.ss_family) {
493214734Srpaulo	case AF_INET:
494214734Srpaulo	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
495189251Ssam		goto ouch;
496189251Ssam	    break;
497189251Ssam	case AF_INET6:
498189251Ssam	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
499189251Ssam		if (e == -1)
500189251Ssam		    goto ouch;
501189251Ssam		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
502189251Ssam		    goto ouch;
503189251Ssam	    }
504189251Ssam	    break;
505189251Ssam	default:
506189251Ssam	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
507189251Ssam	    goto ouch;
508189251Ssam	}
509214734Srpaulo
510189251Ssam	/*
511189251Ssam	 * Find address and port number. The reply to the PASV command
512189251Ssam         * is IMHO the one and only weak point in the FTP protocol.
513189251Ssam	 */
514189251Ssam	ln = last_reply;
515189251Ssam      	switch (e) {
516189251Ssam	case FTP_PASSIVE_MODE:
517189251Ssam	case FTP_LPASSIVE_MODE:
518189251Ssam	    for (p = ln + 3; *p && !isdigit(*p); p++)
519189251Ssam		/* nothing */ ;
520189251Ssam	    if (!*p) {
521189251Ssam		e = FTP_PROTOCOL_ERROR;
522189251Ssam		goto ouch;
523189251Ssam	    }
524189251Ssam	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
525189251Ssam	    for (i = 0; *p && i < l; i++, p++)
526189251Ssam		addr[i] = strtol(p, &p, 10);
527189251Ssam	    if (i < l) {
528189251Ssam		e = FTP_PROTOCOL_ERROR;
529189251Ssam		goto ouch;
530189251Ssam	    }
531189251Ssam	    break;
532189251Ssam	case FTP_EPASSIVE_MODE:
533189251Ssam	    for (p = ln + 3; *p && *p != '('; p++)
534189251Ssam		/* nothing */ ;
535214734Srpaulo	    if (!*p) {
536189251Ssam		e = FTP_PROTOCOL_ERROR;
537189251Ssam		goto ouch;
538214734Srpaulo	    }
539214734Srpaulo	    ++p;
540214734Srpaulo	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
541189251Ssam		       &port, &addr[3]) != 5 ||
542189251Ssam		addr[0] != addr[1] ||
543189251Ssam		addr[0] != addr[2] || addr[0] != addr[3]) {
544189251Ssam		e = FTP_PROTOCOL_ERROR;
545189251Ssam		goto ouch;
546189251Ssam	    }
547189251Ssam	    break;
548189251Ssam	}
549189251Ssam
550189251Ssam	/* seek to required offset */
551189251Ssam	if (offset)
552189251Ssam	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
553189251Ssam		goto sysouch;
554189251Ssam
555189251Ssam	/* construct sockaddr for data socket */
556189251Ssam	l = sizeof sin;
557189251Ssam	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
558189251Ssam	    goto sysouch;
559189251Ssam	if (sin.ss_family == AF_INET6)
560189251Ssam	    unmappedaddr((struct sockaddr_in6 *)&sin);
561189251Ssam	switch (sin.ss_family) {
562189251Ssam	case AF_INET6:
563189251Ssam	    sin6 = (struct sockaddr_in6 *)&sin;
564189251Ssam	    if (e == FTP_EPASSIVE_MODE)
565189251Ssam		sin6->sin6_port = htons(port);
566189251Ssam	    else {
567189251Ssam		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
568189251Ssam		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
569189251Ssam	    }
570189251Ssam	    break;
571189251Ssam	case AF_INET:
572189251Ssam	    sin4 = (struct sockaddr_in *)&sin;
573189251Ssam	    if (e == FTP_EPASSIVE_MODE)
574189251Ssam		sin4->sin_port = htons(port);
575189251Ssam	    else {
576189251Ssam		bcopy(addr, (char *)&sin4->sin_addr, 4);
577189251Ssam		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
578189251Ssam	    }
579189251Ssam	    break;
580189251Ssam	default:
581189251Ssam	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
582189251Ssam	    break;
583189251Ssam	}
584189251Ssam
585189251Ssam	/* connect to data port */
586189251Ssam	if (verbose)
587189251Ssam	    _fetch_info("opening data connection");
588214734Srpaulo	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
589189251Ssam	    goto sysouch;
590189251Ssam
591189251Ssam	/* make the server initiate the transfer */
592189251Ssam	if (verbose)
593189251Ssam	    _fetch_info("initiating transfer");
594214734Srpaulo	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
595189251Ssam	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
596189251Ssam	    goto ouch;
597189251Ssam
598189251Ssam    } else {
599189251Ssam	u_int32_t a;
600189251Ssam	u_short p;
601189251Ssam	int arg, d;
602189251Ssam	char *ap;
603189251Ssam	char hname[INET6_ADDRSTRLEN];
604189251Ssam
605189251Ssam	switch (sin.ss_family) {
606189251Ssam	case AF_INET6:
607189251Ssam	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
608189251Ssam#ifdef IPV6_PORTRANGE
609189251Ssam	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
610189251Ssam	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
611189251Ssam			   (char *)&arg, sizeof(arg)) == -1)
612189251Ssam		goto sysouch;
613189251Ssam#endif
614189251Ssam	    break;
615189251Ssam	case AF_INET:
616189251Ssam	    ((struct sockaddr_in *)&sin)->sin_port = 0;
617189251Ssam	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
618189251Ssam	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
619189251Ssam			   (char *)&arg, sizeof arg) == -1)
620189251Ssam		goto sysouch;
621189251Ssam	    break;
622189251Ssam	}
623189251Ssam	if (verbose)
624189251Ssam	    _fetch_info("binding data socket");
625189251Ssam	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
626189251Ssam	    goto sysouch;
627189251Ssam	if (listen(sd, 1) == -1)
628189251Ssam	    goto sysouch;
629189251Ssam
630189251Ssam	/* find what port we're on and tell the server */
631189251Ssam	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
632189251Ssam	    goto sysouch;
633214734Srpaulo	switch (sin.ss_family) {
634189251Ssam	case AF_INET:
635189251Ssam	    sin4 = (struct sockaddr_in *)&sin;
636189251Ssam	    a = ntohl(sin4->sin_addr.s_addr);
637189251Ssam	    p = ntohs(sin4->sin_port);
638189251Ssam	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
639189251Ssam			 (a >> 24) & 0xff, (a >> 16) & 0xff,
640214734Srpaulo			 (a >> 8) & 0xff, a & 0xff,
641189251Ssam			 (p >> 8) & 0xff, p & 0xff);
642189251Ssam	    break;
643189251Ssam	case AF_INET6:
644189251Ssam#define UC(b)	(((int)b)&0xff)
645189251Ssam	    e = -1;
646189251Ssam	    sin6 = (struct sockaddr_in6 *)&sin;
647189251Ssam	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
648189251Ssam			    hname, sizeof(hname),
649189251Ssam			    NULL, 0, NI_NUMERICHOST) == 0) {
650189251Ssam		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
651189251Ssam			     htons(sin6->sin6_port));
652189251Ssam		if (e == -1)
653189251Ssam		    goto ouch;
654189251Ssam	    }
655189251Ssam	    if (e != FTP_OK) {
656189251Ssam		ap = (char *)&sin6->sin6_addr;
657189251Ssam		e = _ftp_cmd(cd,
658189251Ssam     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
659189251Ssam			     6, 16,
660189251Ssam			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
661189251Ssam			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
662189251Ssam			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
663189251Ssam			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
664189251Ssam			     2,
665189251Ssam			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
666189251Ssam			     ntohs(sin6->sin6_port)        & 0xff);
667189251Ssam	    }
668189251Ssam	    break;
669189251Ssam	default:
670189251Ssam	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
671189251Ssam	    goto ouch;
672189251Ssam	}
673189251Ssam	if (e != FTP_OK)
674189251Ssam	    goto ouch;
675189251Ssam
676189251Ssam	/* seek to required offset */
677189251Ssam	if (offset)
678189251Ssam	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
679189251Ssam		goto sysouch;
680189251Ssam
681189251Ssam	/* make the server initiate the transfer */
682189251Ssam	if (verbose)
683189251Ssam	    _fetch_info("initiating transfer");
684189251Ssam	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
685189251Ssam	if (e != FTP_OPEN_DATA_CONNECTION)
686189251Ssam	    goto ouch;
687189251Ssam
688189251Ssam	/* accept the incoming connection and go to town */
689189251Ssam	if ((d = accept(sd, NULL, NULL)) == -1)
690189251Ssam	    goto sysouch;
691189251Ssam	close(sd);
692189251Ssam	sd = d;
693189251Ssam    }
694189251Ssam
695189251Ssam    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
696189251Ssam	goto sysouch;
697189251Ssam    return df;
698189251Ssam
699189251Ssamsysouch:
700189251Ssam    _fetch_syserr();
701189251Ssam    if (sd >= 0)
702189251Ssam	close(sd);
703189251Ssam    return NULL;
704189251Ssam
705189251Ssamouch:
706189251Ssam    if (e != -1)
707189251Ssam	_ftp_seterr(e);
708189251Ssam    if (sd >= 0)
709189251Ssam	close(sd);
710189251Ssam    return NULL;
711189251Ssam}
712189251Ssam
713189251Ssam/*
714189251Ssam * Log on to FTP server
715189251Ssam */
716189251Ssamstatic int
717189251Ssam_ftp_connect(struct url *url, struct url *purl, char *flags)
718189251Ssam{
719189251Ssam    int cd, e, direct, verbose;
720189251Ssam#ifdef INET6
721189251Ssam    int af = AF_UNSPEC;
722189251Ssam#else
723189251Ssam    int af = AF_INET;
724189251Ssam#endif
725189251Ssam    const char *logname;
726189251Ssam    char *user, *pwd;
727189251Ssam    char localhost[MAXHOSTNAMELEN];
728189251Ssam    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
729189251Ssam
730189251Ssam    direct = CHECK_FLAG('d');
731189251Ssam    verbose = CHECK_FLAG('v');
732189251Ssam    if (CHECK_FLAG('4'))
733189251Ssam	af = AF_INET;
734189251Ssam    else if (CHECK_FLAG('6'))
735189251Ssam	af = AF_INET6;
736189251Ssam
737189251Ssam    if (direct)
738189251Ssam	purl = NULL;
739189251Ssam
740189251Ssam    /* check for proxy */
741189251Ssam    if (purl) {
742189251Ssam	/* XXX proxy authentication! */
743189251Ssam	cd = _fetch_connect(purl->host, purl->port, af, verbose);
744189251Ssam    } else {
745189251Ssam	/* no proxy, go straight to target */
746189251Ssam	cd = _fetch_connect(url->host, url->port, af, verbose);
747189251Ssam	purl = NULL;
748189251Ssam    }
749189251Ssam
750189251Ssam    /* check connection */
751189251Ssam    if (cd == -1) {
752189251Ssam	_fetch_syserr();
753189251Ssam	return NULL;
754189251Ssam    }
755189251Ssam
756189251Ssam    /* expect welcome message */
757189251Ssam    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
758189251Ssam	goto fouch;
759189251Ssam
760189251Ssam    /* XXX FTP_AUTH, and maybe .netrc */
761189251Ssam
762189251Ssam    /* send user name and password */
763189251Ssam    user = url->user;
764189251Ssam    if (!user || !*user)
765189251Ssam	user = FTP_ANONYMOUS_USER;
766189251Ssam    if (purl && url->port == _fetch_default_port(url->scheme))
767189251Ssam	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
768189251Ssam    else if (purl)
769189251Ssam	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
770189251Ssam    else
771189251Ssam	e = _ftp_cmd(cd, "USER %s", user);
772189251Ssam
773189251Ssam    /* did the server request a password? */
774189251Ssam    if (e == FTP_NEED_PASSWORD) {
775189251Ssam	pwd = url->pwd;
776189251Ssam	if (!pwd || !*pwd)
777189251Ssam	    pwd = getenv("FTP_PASSWORD");
778189251Ssam	if (!pwd || !*pwd) {
779189251Ssam	    if ((logname = getlogin()) == 0)
780189251Ssam		logname = FTP_ANONYMOUS_PASSWORD;
781189251Ssam	    gethostname(localhost, sizeof localhost);
782189251Ssam	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
783189251Ssam	    pwd = pbuf;
784189251Ssam	}
785189251Ssam	e = _ftp_cmd(cd, "PASS %s", pwd);
786189251Ssam    }
787189251Ssam
788189251Ssam    /* did the server request an account? */
789189251Ssam    if (e == FTP_NEED_ACCOUNT)
790189251Ssam	goto fouch;
791189251Ssam
792214734Srpaulo    /* we should be done by now */
793189251Ssam    if (e != FTP_LOGGED_IN)
794189251Ssam	goto fouch;
795189251Ssam
796189251Ssam    /* might as well select mode and type at once */
797189251Ssam#ifdef FTP_FORCE_STREAM_MODE
798189251Ssam    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
799189251Ssam	goto fouch;
800189251Ssam#endif
801189251Ssam    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
802189251Ssam	goto fouch;
803189251Ssam
804189251Ssam    /* done */
805189251Ssam    return cd;
806189251Ssam
807189251Ssamfouch:
808189251Ssam    if (e != -1)
809189251Ssam	_ftp_seterr(e);
810189251Ssam    close(cd);
811189251Ssam    return NULL;
812189251Ssam}
813189251Ssam
814189251Ssam/*
815189251Ssam * Disconnect from server
816189251Ssam */
817189251Ssamstatic void
818214734Srpaulo_ftp_disconnect(int cd)
819214734Srpaulo{
820189251Ssam    (void)_ftp_cmd(cd, "QUIT");
821189251Ssam    close(cd);
822189251Ssam}
823189251Ssam
824189251Ssam/*
825189251Ssam * Check if we're already connected
826189251Ssam */
827189251Ssamstatic int
828189251Ssam_ftp_isconnected(struct url *url)
829189251Ssam{
830189251Ssam    return (cached_socket
831189251Ssam	    && (strcmp(url->host, cached_host.host) == 0)
832189251Ssam	    && (strcmp(url->user, cached_host.user) == 0)
833189251Ssam	    && (strcmp(url->pwd, cached_host.pwd) == 0)
834214734Srpaulo	    && (url->port == cached_host.port));
835189251Ssam}
836189251Ssam
837189251Ssam/*
838189251Ssam * Check the cache, reconnect if no luck
839189251Ssam */
840189251Ssamstatic int
841189251Ssam_ftp_cached_connect(struct url *url, struct url *purl, char *flags)
842189251Ssam{
843189251Ssam    int e, cd;
844189251Ssam
845189251Ssam    cd = -1;
846189251Ssam
847189251Ssam    /* set default port */
848189251Ssam    if (!url->port)
849189251Ssam	url->port = _fetch_default_port(url->scheme);
850189251Ssam
851214734Srpaulo    /* try to use previously cached connection */
852189251Ssam    if (_ftp_isconnected(url)) {
853189251Ssam	e = _ftp_cmd(cached_socket, "NOOP");
854214734Srpaulo	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
855189251Ssam	    return cached_socket;
856189251Ssam    }
857189251Ssam
858214734Srpaulo    /* connect to server */
859214734Srpaulo    if ((cd = _ftp_connect(url, purl, flags)) == -1)
860189251Ssam	return -1;
861189251Ssam    if (cached_socket)
862189251Ssam	_ftp_disconnect(cached_socket);
863189251Ssam    cached_socket = cd;
864189251Ssam    memcpy(&cached_host, url, sizeof *url);
865189251Ssam    return cd;
866189251Ssam}
867189251Ssam
868189251Ssam/*
869189251Ssam * Check the proxy settings
870189251Ssam */
871189251Ssamstatic struct url *
872189251Ssam_ftp_get_proxy(void)
873189251Ssam{
874189251Ssam    struct url *purl;
875189251Ssam    char *p;
876189251Ssam
877189251Ssam    if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) &&
878189251Ssam	*p && (purl = fetchParseURL(p)) != NULL) {
879189251Ssam	if (!*purl->scheme)
880189251Ssam	    strcpy(purl->scheme, SCHEME_HTTP);
881189251Ssam	if (!purl->port)
882189251Ssam	    purl->port = _fetch_default_proxy_port(purl->scheme);
883189251Ssam	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
884189251Ssam	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
885189251Ssam	    return purl;
886214734Srpaulo	fetchFreeURL(purl);
887214734Srpaulo    }
888214734Srpaulo    return NULL;
889189251Ssam}
890189251Ssam
891189251Ssam/*
892189251Ssam * Get and stat file
893189251Ssam */
894189251SsamFILE *
895189251SsamfetchXGetFTP(struct url *url, struct url_stat *us, char *flags)
896189251Ssam{
897189251Ssam    struct url *purl;
898189251Ssam    int cd;
899189251Ssam
900189251Ssam    /* get the proxy URL, and check if we should use HTTP instead */
901189251Ssam    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
902189251Ssam	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
903189251Ssam	    return _http_request(url, "GET", us, purl, flags);
904189251Ssam    } else {
905189251Ssam	purl = NULL;
906189251Ssam    }
907189251Ssam
908189251Ssam    /* connect to server */
909189251Ssam    cd = _ftp_cached_connect(url, purl, flags);
910189251Ssam    if (purl)
911189251Ssam	fetchFreeURL(purl);
912189251Ssam    if (cd == NULL)
913189251Ssam	return NULL;
914189251Ssam
915189251Ssam    /* change directory */
916189251Ssam    if (_ftp_cwd(cd, url->doc) == -1)
917189251Ssam	return NULL;
918189251Ssam
919189251Ssam    /* stat file */
920189251Ssam    if (us && _ftp_stat(cd, url->doc, us) == -1
921189251Ssam	&& fetchLastErrCode != FETCH_PROTO
922189251Ssam	&& fetchLastErrCode != FETCH_UNAVAIL)
923189251Ssam	return NULL;
924189251Ssam
925189251Ssam    /* initiate the transfer */
926189251Ssam    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
927189251Ssam}
928189251Ssam
929189251Ssam/*
930189251Ssam * Get file
931189251Ssam */
932189251SsamFILE *
933189251SsamfetchGetFTP(struct url *url, char *flags)
934189251Ssam{
935189251Ssam    return fetchXGetFTP(url, NULL, flags);
936189251Ssam}
937189251Ssam
938189251Ssam/*
939189251Ssam * Put file
940189251Ssam */
941189251SsamFILE *
942189251SsamfetchPutFTP(struct url *url, char *flags)
943189251Ssam{
944189251Ssam    struct url *purl;
945189251Ssam    int cd;
946189251Ssam
947189251Ssam    /* get the proxy URL, and check if we should use HTTP instead */
948189251Ssam    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
949189251Ssam	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
950189251Ssam	    /* XXX HTTP PUT is not implemented, so try without the proxy */
951189251Ssam	    purl = NULL;
952189251Ssam    } else {
953189251Ssam	purl = NULL;
954189251Ssam    }
955189251Ssam
956189251Ssam    /* connect to server */
957189251Ssam    cd = _ftp_cached_connect(url, purl, flags);
958189251Ssam    if (purl)
959189251Ssam	fetchFreeURL(purl);
960189251Ssam    if (cd == NULL)
961189251Ssam	return NULL;
962189251Ssam
963189251Ssam    /* change directory */
964189251Ssam    if (_ftp_cwd(cd, url->doc) == -1)
965189251Ssam	return NULL;
966189251Ssam
967189251Ssam    /* initiate the transfer */
968189251Ssam    return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR",
969189251Ssam			 url->doc, O_WRONLY, url->offset, flags);
970189251Ssam}
971189251Ssam
972189251Ssam/*
973189251Ssam * Get file stats
974189251Ssam */
975189251Ssamint
976189251SsamfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
977189251Ssam{
978189251Ssam    struct url *purl;
979189251Ssam    int cd;
980189251Ssam
981189251Ssam    /* get the proxy URL, and check if we should use HTTP instead */
982189251Ssam    if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) {
983189251Ssam	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
984189251Ssam	    FILE *f;
985189251Ssam
986189251Ssam	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
987189251Ssam		return -1;
988189251Ssam	    fclose(f);
989189251Ssam	    return 0;
990189251Ssam	}
991189251Ssam    } else {
992189251Ssam	purl = NULL;
993189251Ssam    }
994189251Ssam
995189251Ssam    /* connect to server */
996189251Ssam    cd = _ftp_cached_connect(url, purl, flags);
997189251Ssam    if (purl)
998189251Ssam	fetchFreeURL(purl);
999189251Ssam    if (cd == NULL)
1000189251Ssam	return NULL;
1001189251Ssam
1002189251Ssam    /* change directory */
1003189251Ssam    if (_ftp_cwd(cd, url->doc) == -1)
1004189251Ssam	return -1;
1005189251Ssam
1006189251Ssam    /* stat file */
1007189251Ssam    return _ftp_stat(cd, url->doc, us);
1008189251Ssam}
1009189251Ssam
1010189251Ssam/*
1011189251Ssam * List a directory
1012189251Ssam */
1013189251Ssamextern void warnx(char *, ...);
1014189251Ssamstruct url_ent *
1015189251SsamfetchListFTP(struct url *url, char *flags)
1016189251Ssam{
1017189251Ssam    warnx("fetchListFTP(): not implemented");
1018189251Ssam    return NULL;
1019189251Ssam}
1020189251Ssam