xref: /freebsd/lib/libfetch/ftp.c (revision 6efb30c8d0f5d094d8fcbf11f38a2731078f8735)
14ca1ab94SDag-Erling Smørgrav /*-
24ca1ab94SDag-Erling Smørgrav  * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
34ca1ab94SDag-Erling Smørgrav  * All rights reserved.
44ca1ab94SDag-Erling Smørgrav  *
54ca1ab94SDag-Erling Smørgrav  * Redistribution and use in source and binary forms, with or without
64ca1ab94SDag-Erling Smørgrav  * modification, are permitted provided that the following conditions
74ca1ab94SDag-Erling Smørgrav  * are met:
84ca1ab94SDag-Erling Smørgrav  * 1. Redistributions of source code must retain the above copyright
94ca1ab94SDag-Erling Smørgrav  *    notice, this list of conditions and the following disclaimer
104ca1ab94SDag-Erling Smørgrav  *    in this position and unchanged.
114ca1ab94SDag-Erling Smørgrav  * 2. Redistributions in binary form must reproduce the above copyright
124ca1ab94SDag-Erling Smørgrav  *    notice, this list of conditions and the following disclaimer in the
134ca1ab94SDag-Erling Smørgrav  *    documentation and/or other materials provided with the distribution.
144ca1ab94SDag-Erling Smørgrav  * 3. The name of the author may not be used to endorse or promote products
154ca1ab94SDag-Erling Smørgrav  *    derived from this software without specific prior written permission
164ca1ab94SDag-Erling Smørgrav  *
174ca1ab94SDag-Erling Smørgrav  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
184ca1ab94SDag-Erling Smørgrav  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
194ca1ab94SDag-Erling Smørgrav  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
204ca1ab94SDag-Erling Smørgrav  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
214ca1ab94SDag-Erling Smørgrav  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
224ca1ab94SDag-Erling Smørgrav  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
234ca1ab94SDag-Erling Smørgrav  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
244ca1ab94SDag-Erling Smørgrav  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
254ca1ab94SDag-Erling Smørgrav  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
264ca1ab94SDag-Erling Smørgrav  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
274ca1ab94SDag-Erling Smørgrav  *
287f3dea24SPeter Wemm  * $FreeBSD$
294ca1ab94SDag-Erling Smørgrav  */
304ca1ab94SDag-Erling Smørgrav 
314ca1ab94SDag-Erling Smørgrav /*
328e3986eaSDag-Erling Smørgrav  * Portions of this code were taken from or based on ftpio.c:
334ca1ab94SDag-Erling Smørgrav  *
344ca1ab94SDag-Erling Smørgrav  * ----------------------------------------------------------------------------
354ca1ab94SDag-Erling Smørgrav  * "THE BEER-WARE LICENSE" (Revision 42):
364ca1ab94SDag-Erling Smørgrav  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
374ca1ab94SDag-Erling Smørgrav  * can do whatever you want with this stuff. If we meet some day, and you think
384ca1ab94SDag-Erling Smørgrav  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
394ca1ab94SDag-Erling Smørgrav  * ----------------------------------------------------------------------------
404ca1ab94SDag-Erling Smørgrav  *
414ca1ab94SDag-Erling Smørgrav  * Major Changelog:
424ca1ab94SDag-Erling Smørgrav  *
434ca1ab94SDag-Erling Smørgrav  * Dag-Erling Co�dan Sm�rgrav
444ca1ab94SDag-Erling Smørgrav  * 9 Jun 1998
454ca1ab94SDag-Erling Smørgrav  *
464ca1ab94SDag-Erling Smørgrav  * Incorporated into libfetch
474ca1ab94SDag-Erling Smørgrav  *
484ca1ab94SDag-Erling Smørgrav  * Jordan K. Hubbard
494ca1ab94SDag-Erling Smørgrav  * 17 Jan 1996
504ca1ab94SDag-Erling Smørgrav  *
514ca1ab94SDag-Erling Smørgrav  * Turned inside out. Now returns xfers as new file ids, not as a special
524ca1ab94SDag-Erling Smørgrav  * `state' of FTP_t
534ca1ab94SDag-Erling Smørgrav  *
544ca1ab94SDag-Erling Smørgrav  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
554ca1ab94SDag-Erling Smørgrav  *
564ca1ab94SDag-Erling Smørgrav  */
574ca1ab94SDag-Erling Smørgrav 
580fba3a00SDag-Erling Smørgrav #include <sys/param.h>
594ca1ab94SDag-Erling Smørgrav #include <sys/socket.h>
60fc6e9e65SDag-Erling Smørgrav #include <sys/uio.h>
614ca1ab94SDag-Erling Smørgrav #include <netinet/in.h>
624ca1ab94SDag-Erling Smørgrav 
634ca1ab94SDag-Erling Smørgrav #include <ctype.h>
64fc6e9e65SDag-Erling Smørgrav #include <errno.h>
6532425dafSDag-Erling Smørgrav #include <netdb.h>
66346298f0SDag-Erling Smørgrav #include <stdarg.h>
674ca1ab94SDag-Erling Smørgrav #include <stdio.h>
688e3986eaSDag-Erling Smørgrav #include <stdlib.h>
694ca1ab94SDag-Erling Smørgrav #include <string.h>
705aea254fSDag-Erling Smørgrav #include <time.h>
718e3986eaSDag-Erling Smørgrav #include <unistd.h>
724ca1ab94SDag-Erling Smørgrav 
734ca1ab94SDag-Erling Smørgrav #include "fetch.h"
74842a95ccSDag-Erling Smørgrav #include "common.h"
750fba3a00SDag-Erling Smørgrav #include "ftperr.h"
764ca1ab94SDag-Erling Smørgrav 
774ca1ab94SDag-Erling Smørgrav #define FTP_ANONYMOUS_USER	"ftp"
784ca1ab94SDag-Erling Smørgrav #define FTP_ANONYMOUS_PASSWORD	"ftp"
79346298f0SDag-Erling Smørgrav #define FTP_DEFAULT_PORT 21
80346298f0SDag-Erling Smørgrav 
81346298f0SDag-Erling Smørgrav #define FTP_OPEN_DATA_CONNECTION	150
82346298f0SDag-Erling Smørgrav #define FTP_OK				200
835aea254fSDag-Erling Smørgrav #define FTP_FILE_STATUS			213
843b7a6740SDag-Erling Smørgrav #define FTP_SERVICE_READY		220
85346298f0SDag-Erling Smørgrav #define FTP_PASSIVE_MODE		227
86346298f0SDag-Erling Smørgrav #define FTP_LOGGED_IN			230
87346298f0SDag-Erling Smørgrav #define FTP_FILE_ACTION_OK		250
88346298f0SDag-Erling Smørgrav #define FTP_NEED_PASSWORD		331
89346298f0SDag-Erling Smørgrav #define FTP_NEED_ACCOUNT		332
9032425dafSDag-Erling Smørgrav #define FTP_FILE_OK			350
91fc6e9e65SDag-Erling Smørgrav #define FTP_SYNTAX_ERROR		500
924ca1ab94SDag-Erling Smørgrav 
93fc6e9e65SDag-Erling Smørgrav static char ENDL[2] = "\r\n";
948e3986eaSDag-Erling Smørgrav 
95d8acd8dcSDag-Erling Smørgrav static struct url cached_host;
96fc6e9e65SDag-Erling Smørgrav static int cached_socket;
974ca1ab94SDag-Erling Smørgrav 
98fc6e9e65SDag-Erling Smørgrav static char *last_reply;
99fc6e9e65SDag-Erling Smørgrav static size_t lr_size, lr_length;
100fc6e9e65SDag-Erling Smørgrav static int last_code;
101fc6e9e65SDag-Erling Smørgrav 
102fc6e9e65SDag-Erling Smørgrav #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
1036efb30c8SDag-Erling Smørgrav 			 && isdigit(foo[2]) \
1046efb30c8SDag-Erling Smørgrav                          && (foo[3] == ' ' || foo[3] == '\0'))
105fc6e9e65SDag-Erling Smørgrav #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
106fc6e9e65SDag-Erling Smørgrav 			&& isdigit(foo[2]) && foo[3] == '-')
1074ca1ab94SDag-Erling Smørgrav 
1084ca1ab94SDag-Erling Smørgrav /*
109fc6e9e65SDag-Erling Smørgrav  * Get server response
1108e3986eaSDag-Erling Smørgrav  */
1118e3986eaSDag-Erling Smørgrav static int
112fc6e9e65SDag-Erling Smørgrav _ftp_chkerr(int cd)
1138e3986eaSDag-Erling Smørgrav {
1148e3986eaSDag-Erling Smørgrav     do {
115fc6e9e65SDag-Erling Smørgrav 	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
116842a95ccSDag-Erling Smørgrav 	    _fetch_syserr();
1178e3986eaSDag-Erling Smørgrav 	    return -1;
1188e3986eaSDag-Erling Smørgrav 	}
119346298f0SDag-Erling Smørgrav #ifndef NDEBUG
120fc6e9e65SDag-Erling Smørgrav 	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
121346298f0SDag-Erling Smørgrav #endif
122fc6e9e65SDag-Erling Smørgrav     } while (isftpinfo(last_reply));
123346298f0SDag-Erling Smørgrav 
124fc6e9e65SDag-Erling Smørgrav     while (lr_length && isspace(last_reply[lr_length-1]))
125fc6e9e65SDag-Erling Smørgrav 	lr_length--;
126fc6e9e65SDag-Erling Smørgrav     last_reply[lr_length] = 0;
127fc6e9e65SDag-Erling Smørgrav 
128fc6e9e65SDag-Erling Smørgrav     if (!isftpreply(last_reply)) {
129fc6e9e65SDag-Erling Smørgrav 	_ftp_seterr(999);
1308e3986eaSDag-Erling Smørgrav 	return -1;
1318e3986eaSDag-Erling Smørgrav     }
1328e3986eaSDag-Erling Smørgrav 
133fc6e9e65SDag-Erling Smørgrav     last_code = (last_reply[0] - '0') * 100
134fc6e9e65SDag-Erling Smørgrav 	+ (last_reply[1] - '0') * 10
135fc6e9e65SDag-Erling Smørgrav 	+ (last_reply[2] - '0');
136fc6e9e65SDag-Erling Smørgrav 
137fc6e9e65SDag-Erling Smørgrav     return last_code;
1388e3986eaSDag-Erling Smørgrav }
1398e3986eaSDag-Erling Smørgrav 
1408e3986eaSDag-Erling Smørgrav /*
141346298f0SDag-Erling Smørgrav  * Send a command and check reply
1424ca1ab94SDag-Erling Smørgrav  */
1434ca1ab94SDag-Erling Smørgrav static int
144fc6e9e65SDag-Erling Smørgrav _ftp_cmd(int cd, char *fmt, ...)
1454ca1ab94SDag-Erling Smørgrav {
146346298f0SDag-Erling Smørgrav     va_list ap;
147fc6e9e65SDag-Erling Smørgrav     struct iovec iov[2];
148fc6e9e65SDag-Erling Smørgrav     char *msg;
149fc6e9e65SDag-Erling Smørgrav     int r;
1508e3986eaSDag-Erling Smørgrav 
151346298f0SDag-Erling Smørgrav     va_start(ap, fmt);
152fc6e9e65SDag-Erling Smørgrav     vasprintf(&msg, fmt, ap);
153346298f0SDag-Erling Smørgrav     va_end(ap);
154346298f0SDag-Erling Smørgrav 
155fc6e9e65SDag-Erling Smørgrav     if (msg == NULL) {
156fc6e9e65SDag-Erling Smørgrav 	errno = ENOMEM;
157fc6e9e65SDag-Erling Smørgrav 	_fetch_syserr();
158fc6e9e65SDag-Erling Smørgrav 	return -1;
159fc6e9e65SDag-Erling Smørgrav     }
160fc6e9e65SDag-Erling Smørgrav #ifndef NDEBUG
161fc6e9e65SDag-Erling Smørgrav     _fetch_info("sending '%s'", msg);
162fc6e9e65SDag-Erling Smørgrav #endif
163fc6e9e65SDag-Erling Smørgrav     iov[0].iov_base = msg;
164fc6e9e65SDag-Erling Smørgrav     iov[0].iov_len = strlen(msg);
165fc6e9e65SDag-Erling Smørgrav     iov[1].iov_base = ENDL;
16632425dafSDag-Erling Smørgrav     iov[1].iov_len = sizeof ENDL;
167fc6e9e65SDag-Erling Smørgrav     r = writev(cd, iov, 2);
168fc6e9e65SDag-Erling Smørgrav     free(msg);
169fc6e9e65SDag-Erling Smørgrav     if (r == -1) {
170fc6e9e65SDag-Erling Smørgrav 	_fetch_syserr();
171fc6e9e65SDag-Erling Smørgrav 	return -1;
172fc6e9e65SDag-Erling Smørgrav     }
173fc6e9e65SDag-Erling Smørgrav 
174fc6e9e65SDag-Erling Smørgrav     return _ftp_chkerr(cd);
1754ca1ab94SDag-Erling Smørgrav }
1764ca1ab94SDag-Erling Smørgrav 
1774ca1ab94SDag-Erling Smørgrav /*
178f62e5228SDag-Erling Smørgrav  * Transfer file
1794ca1ab94SDag-Erling Smørgrav  */
1804ca1ab94SDag-Erling Smørgrav static FILE *
18132425dafSDag-Erling Smørgrav _ftp_transfer(int cd, char *oper, char *file,
18232425dafSDag-Erling Smørgrav 	      char *mode, off_t offset, char *flags)
1834ca1ab94SDag-Erling Smørgrav {
184346298f0SDag-Erling Smørgrav     struct sockaddr_in sin;
185f5f109a0SDag-Erling Smørgrav     int pasv, high, verbose;
186f5f109a0SDag-Erling Smørgrav     int e, sd = -1;
187f5f109a0SDag-Erling Smørgrav     socklen_t l;
188346298f0SDag-Erling Smørgrav     char *s;
189346298f0SDag-Erling Smørgrav     FILE *df;
1908e3986eaSDag-Erling Smørgrav 
191f5f109a0SDag-Erling Smørgrav     /* check flags */
192f5f109a0SDag-Erling Smørgrav     pasv = (flags && strchr(flags, 'p'));
193f5f109a0SDag-Erling Smørgrav     high = (flags && strchr(flags, 'h'));
194f5f109a0SDag-Erling Smørgrav     verbose = (flags && strchr(flags, 'v'));
195f5f109a0SDag-Erling Smørgrav 
1964ca1ab94SDag-Erling Smørgrav     /* change directory */
197346298f0SDag-Erling Smørgrav     if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
198346298f0SDag-Erling Smørgrav 	*s = 0;
199f5f109a0SDag-Erling Smørgrav 	if (verbose)
200f5f109a0SDag-Erling Smørgrav 	    _fetch_info("changing directory to %s", file);
201fc6e9e65SDag-Erling Smørgrav 	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
202346298f0SDag-Erling Smørgrav 	    *s = '/';
203fc6e9e65SDag-Erling Smørgrav 	    if (e != -1)
2045aea254fSDag-Erling Smørgrav 		_ftp_seterr(e);
2054ca1ab94SDag-Erling Smørgrav 	    return NULL;
2064ca1ab94SDag-Erling Smørgrav 	}
207346298f0SDag-Erling Smørgrav 	*s++ = '/';
2084ca1ab94SDag-Erling Smørgrav     } else {
209f5f109a0SDag-Erling Smørgrav 	if (verbose)
210f5f109a0SDag-Erling Smørgrav 	    _fetch_info("changing directory to /");
211fc6e9e65SDag-Erling Smørgrav 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
212fc6e9e65SDag-Erling Smørgrav 	    if (e != -1)
2135aea254fSDag-Erling Smørgrav 		_ftp_seterr(e);
2144ca1ab94SDag-Erling Smørgrav 	    return NULL;
2154ca1ab94SDag-Erling Smørgrav 	}
2165aea254fSDag-Erling Smørgrav     }
2174ca1ab94SDag-Erling Smørgrav 
218346298f0SDag-Erling Smørgrav     /* s now points to file name */
219346298f0SDag-Erling Smørgrav 
220346298f0SDag-Erling Smørgrav     /* open data socket */
221ecc91352SDag-Erling Smørgrav     if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
222842a95ccSDag-Erling Smørgrav 	_fetch_syserr();
223346298f0SDag-Erling Smørgrav 	return NULL;
224346298f0SDag-Erling Smørgrav     }
225346298f0SDag-Erling Smørgrav 
226346298f0SDag-Erling Smørgrav     if (pasv) {
227346298f0SDag-Erling Smørgrav 	u_char addr[6];
228346298f0SDag-Erling Smørgrav 	char *ln, *p;
229346298f0SDag-Erling Smørgrav 	int i;
230346298f0SDag-Erling Smørgrav 
231346298f0SDag-Erling Smørgrav 	/* send PASV command */
232f5f109a0SDag-Erling Smørgrav 	if (verbose)
233f5f109a0SDag-Erling Smørgrav 	    _fetch_info("setting passive mode");
234fc6e9e65SDag-Erling Smørgrav 	if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
235346298f0SDag-Erling Smørgrav 	    goto ouch;
236346298f0SDag-Erling Smørgrav 
237f5f109a0SDag-Erling Smørgrav 	/*
238f5f109a0SDag-Erling Smørgrav 	 * Find address and port number. The reply to the PASV command
239f5f109a0SDag-Erling Smørgrav          * is IMHO the one and only weak point in the FTP protocol.
240f5f109a0SDag-Erling Smørgrav 	 */
241fc6e9e65SDag-Erling Smørgrav 	ln = last_reply;
2426efb30c8SDag-Erling Smørgrav 	for (p = ln + 3; *p && !isdigit(*p); p++)
243346298f0SDag-Erling Smørgrav 	    /* nothing */ ;
2446efb30c8SDag-Erling Smørgrav 	for (i = 0; *p, i < 6; i++, p++)
245346298f0SDag-Erling Smørgrav 	    addr[i] = strtol(p, &p, 10);
2466efb30c8SDag-Erling Smørgrav 	if (i < 6) {
2476efb30c8SDag-Erling Smørgrav 	    e = 999;
2486efb30c8SDag-Erling Smørgrav 	    goto ouch;
249346298f0SDag-Erling Smørgrav 	}
250346298f0SDag-Erling Smørgrav 
25132425dafSDag-Erling Smørgrav 	/* seek to required offset */
25232425dafSDag-Erling Smørgrav 	if (offset)
25332425dafSDag-Erling Smørgrav 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
25432425dafSDag-Erling Smørgrav 		goto sysouch;
25532425dafSDag-Erling Smørgrav 
256346298f0SDag-Erling Smørgrav 	/* construct sockaddr for data socket */
25732425dafSDag-Erling Smørgrav 	l = sizeof sin;
258fc6e9e65SDag-Erling Smørgrav 	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
259346298f0SDag-Erling Smørgrav 	    goto sysouch;
260346298f0SDag-Erling Smørgrav 	bcopy(addr, (char *)&sin.sin_addr, 4);
261346298f0SDag-Erling Smørgrav 	bcopy(addr + 4, (char *)&sin.sin_port, 2);
262346298f0SDag-Erling Smørgrav 
263346298f0SDag-Erling Smørgrav 	/* connect to data port */
264f5f109a0SDag-Erling Smørgrav 	if (verbose)
265f5f109a0SDag-Erling Smørgrav 	    _fetch_info("opening data connection");
26632425dafSDag-Erling Smørgrav 	if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1)
267346298f0SDag-Erling Smørgrav 	    goto sysouch;
268346298f0SDag-Erling Smørgrav 
269346298f0SDag-Erling Smørgrav 	/* make the server initiate the transfer */
270f5f109a0SDag-Erling Smørgrav 	if (verbose)
271f5f109a0SDag-Erling Smørgrav 	    _fetch_info("initiating transfer");
272fc6e9e65SDag-Erling Smørgrav 	e = _ftp_cmd(cd, "%s %s", oper, s);
2735aea254fSDag-Erling Smørgrav 	if (e != FTP_OPEN_DATA_CONNECTION)
274346298f0SDag-Erling Smørgrav 	    goto ouch;
275346298f0SDag-Erling Smørgrav 
276346298f0SDag-Erling Smørgrav     } else {
277346298f0SDag-Erling Smørgrav 	u_int32_t a;
278346298f0SDag-Erling Smørgrav 	u_short p;
279f5f109a0SDag-Erling Smørgrav 	int arg, d;
280346298f0SDag-Erling Smørgrav 
281346298f0SDag-Erling Smørgrav 	/* find our own address, bind, and listen */
28232425dafSDag-Erling Smørgrav 	l = sizeof sin;
283fc6e9e65SDag-Erling Smørgrav 	if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
284346298f0SDag-Erling Smørgrav 	    goto sysouch;
285346298f0SDag-Erling Smørgrav 	sin.sin_port = 0;
286f5f109a0SDag-Erling Smørgrav 	arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
287f5f109a0SDag-Erling Smørgrav 	if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
28832425dafSDag-Erling Smørgrav 		       (char *)&arg, sizeof arg) == -1)
289f5f109a0SDag-Erling Smørgrav 	    goto sysouch;
290f5f109a0SDag-Erling Smørgrav 	if (verbose)
291f5f109a0SDag-Erling Smørgrav 	    _fetch_info("binding data socket");
292ecc91352SDag-Erling Smørgrav 	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
293346298f0SDag-Erling Smørgrav 	    goto sysouch;
294ecc91352SDag-Erling Smørgrav 	if (listen(sd, 1) == -1)
295346298f0SDag-Erling Smørgrav 	    goto sysouch;
296346298f0SDag-Erling Smørgrav 
297346298f0SDag-Erling Smørgrav 	/* find what port we're on and tell the server */
298ecc91352SDag-Erling Smørgrav 	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
299346298f0SDag-Erling Smørgrav 	    goto sysouch;
300346298f0SDag-Erling Smørgrav 	a = ntohl(sin.sin_addr.s_addr);
301346298f0SDag-Erling Smørgrav 	p = ntohs(sin.sin_port);
302fc6e9e65SDag-Erling Smørgrav 	e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
3035aea254fSDag-Erling Smørgrav 		     (a >> 24) & 0xff, (a >> 16) & 0xff,
3045aea254fSDag-Erling Smørgrav 		     (a >> 8) & 0xff, a & 0xff,
3055aea254fSDag-Erling Smørgrav 		     (p >> 8) & 0xff, p & 0xff);
3065aea254fSDag-Erling Smørgrav 	if (e != FTP_OK)
307346298f0SDag-Erling Smørgrav 	    goto ouch;
308346298f0SDag-Erling Smørgrav 
309346298f0SDag-Erling Smørgrav 	/* make the server initiate the transfer */
310f5f109a0SDag-Erling Smørgrav 	if (verbose)
311f5f109a0SDag-Erling Smørgrav 	    _fetch_info("initiating transfer");
312fc6e9e65SDag-Erling Smørgrav 	e = _ftp_cmd(cd, "%s %s", oper, s);
3135aea254fSDag-Erling Smørgrav 	if (e != FTP_OPEN_DATA_CONNECTION)
314346298f0SDag-Erling Smørgrav 	    goto ouch;
315346298f0SDag-Erling Smørgrav 
316346298f0SDag-Erling Smørgrav 	/* accept the incoming connection and go to town */
317ecc91352SDag-Erling Smørgrav 	if ((d = accept(sd, NULL, NULL)) == -1)
318346298f0SDag-Erling Smørgrav 	    goto sysouch;
319346298f0SDag-Erling Smørgrav 	close(sd);
320346298f0SDag-Erling Smørgrav 	sd = d;
321346298f0SDag-Erling Smørgrav     }
322346298f0SDag-Erling Smørgrav 
323f62e5228SDag-Erling Smørgrav     if ((df = fdopen(sd, mode)) == NULL)
324346298f0SDag-Erling Smørgrav 	goto sysouch;
325346298f0SDag-Erling Smørgrav     return df;
326346298f0SDag-Erling Smørgrav 
327346298f0SDag-Erling Smørgrav sysouch:
328842a95ccSDag-Erling Smørgrav     _fetch_syserr();
3295aea254fSDag-Erling Smørgrav     close(sd);
3305aea254fSDag-Erling Smørgrav     return NULL;
3315aea254fSDag-Erling Smørgrav 
332346298f0SDag-Erling Smørgrav ouch:
333fc6e9e65SDag-Erling Smørgrav     if (e != -1)
3345aea254fSDag-Erling Smørgrav 	_ftp_seterr(e);
335346298f0SDag-Erling Smørgrav     close(sd);
3364ca1ab94SDag-Erling Smørgrav     return NULL;
3374ca1ab94SDag-Erling Smørgrav }
3384ca1ab94SDag-Erling Smørgrav 
3398e3986eaSDag-Erling Smørgrav /*
3408e3986eaSDag-Erling Smørgrav  * Log on to FTP server
3414ca1ab94SDag-Erling Smørgrav  */
342fc6e9e65SDag-Erling Smørgrav static int
343f5f109a0SDag-Erling Smørgrav _ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
3448e3986eaSDag-Erling Smørgrav {
34532425dafSDag-Erling Smørgrav     int cd, e, pp = 0, direct, verbose;
346f62e5228SDag-Erling Smørgrav     char *p, *q;
3478e3986eaSDag-Erling Smørgrav 
348f5f109a0SDag-Erling Smørgrav     direct = (flags && strchr(flags, 'd'));
349f5f109a0SDag-Erling Smørgrav     verbose = (flags && strchr(flags, 'v'));
350f5f109a0SDag-Erling Smørgrav 
351f62e5228SDag-Erling Smørgrav     /* check for proxy */
352f5f109a0SDag-Erling Smørgrav     if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
353f62e5228SDag-Erling Smørgrav 	if ((q = strchr(p, ':')) != NULL) {
35432425dafSDag-Erling Smørgrav 	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
35532425dafSDag-Erling Smørgrav 		/* XXX we should emit some kind of warning */
35632425dafSDag-Erling Smørgrav 	    }
357f62e5228SDag-Erling Smørgrav 	    pp = atoi(q+1);
35832425dafSDag-Erling Smørgrav 	    if (pp < 1 || pp > 65535) {
35932425dafSDag-Erling Smørgrav 		/* XXX we should emit some kind of warning */
36032425dafSDag-Erling Smørgrav 	    }
36132425dafSDag-Erling Smørgrav 	}
36232425dafSDag-Erling Smørgrav 	if (!pp) {
36332425dafSDag-Erling Smørgrav 	    struct servent *se;
36432425dafSDag-Erling Smørgrav 
36532425dafSDag-Erling Smørgrav 	    if ((se = getservbyname("ftp", "tcp")) != NULL)
36632425dafSDag-Erling Smørgrav 		pp = ntohs(se->s_port);
36732425dafSDag-Erling Smørgrav 	    else
36832425dafSDag-Erling Smørgrav 		pp = FTP_DEFAULT_PORT;
369f62e5228SDag-Erling Smørgrav 	}
370f62e5228SDag-Erling Smørgrav 	if (q)
371f62e5228SDag-Erling Smørgrav 	    *q = 0;
372fc6e9e65SDag-Erling Smørgrav 	cd = _fetch_connect(p, pp, verbose);
373f62e5228SDag-Erling Smørgrav 	if (q)
374f62e5228SDag-Erling Smørgrav 	    *q = ':';
375f62e5228SDag-Erling Smørgrav     } else {
376f62e5228SDag-Erling Smørgrav 	/* no proxy, go straight to target */
377fc6e9e65SDag-Erling Smørgrav 	cd = _fetch_connect(host, port, verbose);
378f5f109a0SDag-Erling Smørgrav 	p = NULL;
379f62e5228SDag-Erling Smørgrav     }
380f62e5228SDag-Erling Smørgrav 
381f62e5228SDag-Erling Smørgrav     /* check connection */
382fc6e9e65SDag-Erling Smørgrav     if (cd == -1) {
383842a95ccSDag-Erling Smørgrav 	_fetch_syserr();
3848e3986eaSDag-Erling Smørgrav 	return NULL;
3858e3986eaSDag-Erling Smørgrav     }
386f62e5228SDag-Erling Smørgrav 
3878e3986eaSDag-Erling Smørgrav     /* expect welcome message */
388fc6e9e65SDag-Erling Smørgrav     if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
3898e3986eaSDag-Erling Smørgrav 	goto fouch;
3908e3986eaSDag-Erling Smørgrav 
3918e3986eaSDag-Erling Smørgrav     /* send user name and password */
392f62e5228SDag-Erling Smørgrav     if (!user || !*user)
393f62e5228SDag-Erling Smørgrav 	user = FTP_ANONYMOUS_USER;
394fc6e9e65SDag-Erling Smørgrav     e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
395fc6e9e65SDag-Erling Smørgrav 	  : _ftp_cmd(cd, "USER %s", user);
396f62e5228SDag-Erling Smørgrav 
397f62e5228SDag-Erling Smørgrav     /* did the server request a password? */
398f62e5228SDag-Erling Smørgrav     if (e == FTP_NEED_PASSWORD) {
399f62e5228SDag-Erling Smørgrav 	if (!pwd || !*pwd)
400f62e5228SDag-Erling Smørgrav 	    pwd = FTP_ANONYMOUS_PASSWORD;
401fc6e9e65SDag-Erling Smørgrav 	e = _ftp_cmd(cd, "PASS %s", pwd);
402f62e5228SDag-Erling Smørgrav     }
403f62e5228SDag-Erling Smørgrav 
404f62e5228SDag-Erling Smørgrav     /* did the server request an account? */
4055aea254fSDag-Erling Smørgrav     if (e == FTP_NEED_ACCOUNT)
4063b7a6740SDag-Erling Smørgrav 	goto fouch;
407f62e5228SDag-Erling Smørgrav 
408f62e5228SDag-Erling Smørgrav     /* we should be done by now */
4095aea254fSDag-Erling Smørgrav     if (e != FTP_LOGGED_IN)
4108e3986eaSDag-Erling Smørgrav 	goto fouch;
4118e3986eaSDag-Erling Smørgrav 
4128e3986eaSDag-Erling Smørgrav     /* might as well select mode and type at once */
4138e3986eaSDag-Erling Smørgrav #ifdef FTP_FORCE_STREAM_MODE
414fc6e9e65SDag-Erling Smørgrav     if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
4155aea254fSDag-Erling Smørgrav 	goto fouch;
4168e3986eaSDag-Erling Smørgrav #endif
417fc6e9e65SDag-Erling Smørgrav     if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
4185aea254fSDag-Erling Smørgrav 	goto fouch;
4198e3986eaSDag-Erling Smørgrav 
4208e3986eaSDag-Erling Smørgrav     /* done */
421fc6e9e65SDag-Erling Smørgrav     return cd;
4228e3986eaSDag-Erling Smørgrav 
4238e3986eaSDag-Erling Smørgrav fouch:
424fc6e9e65SDag-Erling Smørgrav     if (e != -1)
4255aea254fSDag-Erling Smørgrav 	_ftp_seterr(e);
426fc6e9e65SDag-Erling Smørgrav     close(cd);
4278e3986eaSDag-Erling Smørgrav     return NULL;
4288e3986eaSDag-Erling Smørgrav }
4298e3986eaSDag-Erling Smørgrav 
4308e3986eaSDag-Erling Smørgrav /*
4318e3986eaSDag-Erling Smørgrav  * Disconnect from server
4328e3986eaSDag-Erling Smørgrav  */
4338e3986eaSDag-Erling Smørgrav static void
434fc6e9e65SDag-Erling Smørgrav _ftp_disconnect(int cd)
4358e3986eaSDag-Erling Smørgrav {
436fc6e9e65SDag-Erling Smørgrav     (void)_ftp_cmd(cd, "QUIT");
437fc6e9e65SDag-Erling Smørgrav     close(cd);
4388e3986eaSDag-Erling Smørgrav }
4398e3986eaSDag-Erling Smørgrav 
4408e3986eaSDag-Erling Smørgrav /*
4418e3986eaSDag-Erling Smørgrav  * Check if we're already connected
4428e3986eaSDag-Erling Smørgrav  */
4438e3986eaSDag-Erling Smørgrav static int
444d8acd8dcSDag-Erling Smørgrav _ftp_isconnected(struct url *url)
4458e3986eaSDag-Erling Smørgrav {
4468e3986eaSDag-Erling Smørgrav     return (cached_socket
4478e3986eaSDag-Erling Smørgrav 	    && (strcmp(url->host, cached_host.host) == 0)
4488e3986eaSDag-Erling Smørgrav 	    && (strcmp(url->user, cached_host.user) == 0)
4498e3986eaSDag-Erling Smørgrav 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
4508e3986eaSDag-Erling Smørgrav 	    && (url->port == cached_host.port));
4518e3986eaSDag-Erling Smørgrav }
4528e3986eaSDag-Erling Smørgrav 
453f62e5228SDag-Erling Smørgrav /*
4545aea254fSDag-Erling Smørgrav  * Check the cache, reconnect if no luck
455f62e5228SDag-Erling Smørgrav  */
456fc6e9e65SDag-Erling Smørgrav static int
4575aea254fSDag-Erling Smørgrav _ftp_cached_connect(struct url *url, char *flags)
4584ca1ab94SDag-Erling Smørgrav {
459fc6e9e65SDag-Erling Smørgrav     int e, cd;
4605aea254fSDag-Erling Smørgrav 
461fc6e9e65SDag-Erling Smørgrav     cd = -1;
4628e3986eaSDag-Erling Smørgrav 
4638e3986eaSDag-Erling Smørgrav     /* set default port */
46432425dafSDag-Erling Smørgrav     if (!url->port) {
46532425dafSDag-Erling Smørgrav 	struct servent *se;
46632425dafSDag-Erling Smørgrav 
46732425dafSDag-Erling Smørgrav 	if ((se = getservbyname("ftp", "tcp")) != NULL)
46832425dafSDag-Erling Smørgrav 	    url->port = ntohs(se->s_port);
46932425dafSDag-Erling Smørgrav 	else
470346298f0SDag-Erling Smørgrav 	    url->port = FTP_DEFAULT_PORT;
47132425dafSDag-Erling Smørgrav     }
4728e3986eaSDag-Erling Smørgrav 
4733b7a6740SDag-Erling Smørgrav     /* try to use previously cached connection */
474fc6e9e65SDag-Erling Smørgrav     if (_ftp_isconnected(url)) {
475fc6e9e65SDag-Erling Smørgrav 	e = _ftp_cmd(cached_socket, "NOOP");
476fc6e9e65SDag-Erling Smørgrav 	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
477fc6e9e65SDag-Erling Smørgrav 	    cd = cached_socket;
478fc6e9e65SDag-Erling Smørgrav     }
4794ca1ab94SDag-Erling Smørgrav 
4808e3986eaSDag-Erling Smørgrav     /* connect to server */
481fc6e9e65SDag-Erling Smørgrav     if (cd == -1) {
482fc6e9e65SDag-Erling Smørgrav 	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
483fc6e9e65SDag-Erling Smørgrav 	if (cd == -1)
484fc6e9e65SDag-Erling Smørgrav 	    return -1;
4858e3986eaSDag-Erling Smørgrav 	if (cached_socket)
4868e3986eaSDag-Erling Smørgrav 	    _ftp_disconnect(cached_socket);
487fc6e9e65SDag-Erling Smørgrav 	cached_socket = cd;
48832425dafSDag-Erling Smørgrav 	memcpy(&cached_host, url, sizeof *url);
4898e3986eaSDag-Erling Smørgrav     }
4908e3986eaSDag-Erling Smørgrav 
491fc6e9e65SDag-Erling Smørgrav     return cd;
4928e3986eaSDag-Erling Smørgrav }
4938e3986eaSDag-Erling Smørgrav 
4948e3986eaSDag-Erling Smørgrav /*
4955aea254fSDag-Erling Smørgrav  * Get file
4968e3986eaSDag-Erling Smørgrav  */
4974ca1ab94SDag-Erling Smørgrav FILE *
498d8acd8dcSDag-Erling Smørgrav fetchGetFTP(struct url *url, char *flags)
499f62e5228SDag-Erling Smørgrav {
500fc6e9e65SDag-Erling Smørgrav     int cd;
5015aea254fSDag-Erling Smørgrav 
5025aea254fSDag-Erling Smørgrav     /* connect to server */
503fc6e9e65SDag-Erling Smørgrav     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
5045aea254fSDag-Erling Smørgrav 	return NULL;
5055aea254fSDag-Erling Smørgrav 
5065aea254fSDag-Erling Smørgrav     /* initiate the transfer */
50732425dafSDag-Erling Smørgrav     return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
508f62e5228SDag-Erling Smørgrav }
509f62e5228SDag-Erling Smørgrav 
5105aea254fSDag-Erling Smørgrav /*
5115aea254fSDag-Erling Smørgrav  * Put file
5125aea254fSDag-Erling Smørgrav  */
513f62e5228SDag-Erling Smørgrav FILE *
514d8acd8dcSDag-Erling Smørgrav fetchPutFTP(struct url *url, char *flags)
5154ca1ab94SDag-Erling Smørgrav {
516fc6e9e65SDag-Erling Smørgrav     int cd;
5175aea254fSDag-Erling Smørgrav 
5185aea254fSDag-Erling Smørgrav     /* connect to server */
519fc6e9e65SDag-Erling Smørgrav     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
5205aea254fSDag-Erling Smørgrav 	return NULL;
5215aea254fSDag-Erling Smørgrav 
5225aea254fSDag-Erling Smørgrav     /* initiate the transfer */
523fc6e9e65SDag-Erling Smørgrav     return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
52432425dafSDag-Erling Smørgrav 			 url->doc, "w", url->offset, flags);
5258e3986eaSDag-Erling Smørgrav }
526d8acd8dcSDag-Erling Smørgrav 
5275aea254fSDag-Erling Smørgrav /*
5285aea254fSDag-Erling Smørgrav  * Get file stats
5295aea254fSDag-Erling Smørgrav  */
530d8acd8dcSDag-Erling Smørgrav int
531d8acd8dcSDag-Erling Smørgrav fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
532d8acd8dcSDag-Erling Smørgrav {
5335aea254fSDag-Erling Smørgrav     char *ln, *s;
5345aea254fSDag-Erling Smørgrav     struct tm tm;
5355aea254fSDag-Erling Smørgrav     time_t t;
536fc6e9e65SDag-Erling Smørgrav     int e, cd;
5375aea254fSDag-Erling Smørgrav 
5380669702cSDag-Erling Smørgrav     us->size = -1;
5390669702cSDag-Erling Smørgrav     us->atime = us->mtime = 0;
5400669702cSDag-Erling Smørgrav 
5415aea254fSDag-Erling Smørgrav     /* connect to server */
542fc6e9e65SDag-Erling Smørgrav     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
5435aea254fSDag-Erling Smørgrav 	return -1;
5445aea254fSDag-Erling Smørgrav 
5455aea254fSDag-Erling Smørgrav     /* change directory */
5465aea254fSDag-Erling Smørgrav     if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
5475aea254fSDag-Erling Smørgrav 	*s = 0;
548fc6e9e65SDag-Erling Smørgrav 	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
5495aea254fSDag-Erling Smørgrav 	    *s = '/';
5505aea254fSDag-Erling Smørgrav 	    goto ouch;
5515aea254fSDag-Erling Smørgrav 	}
5525aea254fSDag-Erling Smørgrav 	*s++ = '/';
5535aea254fSDag-Erling Smørgrav     } else {
554fc6e9e65SDag-Erling Smørgrav 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
5555aea254fSDag-Erling Smørgrav 	    goto ouch;
5565aea254fSDag-Erling Smørgrav     }
5575aea254fSDag-Erling Smørgrav 
5585aea254fSDag-Erling Smørgrav     /* s now points to file name */
5595aea254fSDag-Erling Smørgrav 
560fc6e9e65SDag-Erling Smørgrav     if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
5615aea254fSDag-Erling Smørgrav 	goto ouch;
562fc6e9e65SDag-Erling Smørgrav     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
5635aea254fSDag-Erling Smørgrav 	/* nothing */ ;
5645aea254fSDag-Erling Smørgrav     for (us->size = 0; *ln && isdigit(*ln); ln++)
5655aea254fSDag-Erling Smørgrav 	us->size = us->size * 10 + *ln - '0';
5665aea254fSDag-Erling Smørgrav     if (*ln && !isspace(*ln)) {
567fc6e9e65SDag-Erling Smørgrav 	_ftp_seterr(999);
5685aea254fSDag-Erling Smørgrav 	return -1;
5695aea254fSDag-Erling Smørgrav     }
57089474d12SDag-Erling Smørgrav     DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
5715aea254fSDag-Erling Smørgrav 
572fc6e9e65SDag-Erling Smørgrav     if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
5735aea254fSDag-Erling Smørgrav 	goto ouch;
574fc6e9e65SDag-Erling Smørgrav     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
5755aea254fSDag-Erling Smørgrav 	/* nothing */ ;
57689474d12SDag-Erling Smørgrav     e = 999;
57789474d12SDag-Erling Smørgrav     switch (strspn(ln, "0123456789")) {
57889474d12SDag-Erling Smørgrav     case 14:
57989474d12SDag-Erling Smørgrav 	break;
58089474d12SDag-Erling Smørgrav     case 15:
58189474d12SDag-Erling Smørgrav 	ln++;
58289474d12SDag-Erling Smørgrav 	ln[0] = '2';
58389474d12SDag-Erling Smørgrav 	ln[1] = '0';
58489474d12SDag-Erling Smørgrav 	break;
58589474d12SDag-Erling Smørgrav     default:
58689474d12SDag-Erling Smørgrav 	goto ouch;
58789474d12SDag-Erling Smørgrav     }
58889474d12SDag-Erling Smørgrav     if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
5895aea254fSDag-Erling Smørgrav 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
59089474d12SDag-Erling Smørgrav 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
59189474d12SDag-Erling Smørgrav 	goto ouch;
5925aea254fSDag-Erling Smørgrav     tm.tm_mon--;
5935aea254fSDag-Erling Smørgrav     tm.tm_year -= 1900;
5945aea254fSDag-Erling Smørgrav     tm.tm_isdst = -1;
59589474d12SDag-Erling Smørgrav     t = timegm(&tm);
5965d32c97cSDag-Erling Smørgrav     if (t == (time_t)-1)
5975d32c97cSDag-Erling Smørgrav 	t = time(NULL);
5985d32c97cSDag-Erling Smørgrav     us->mtime = t;
5995d32c97cSDag-Erling Smørgrav     us->atime = t;
60089474d12SDag-Erling Smørgrav     DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
60189474d12SDag-Erling Smørgrav 		  "%02d:%02d:%02d\033[m]\n",
60289474d12SDag-Erling Smørgrav 		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
60389474d12SDag-Erling Smørgrav 		  tm.tm_hour, tm.tm_min, tm.tm_sec));
6045aea254fSDag-Erling Smørgrav     return 0;
6055aea254fSDag-Erling Smørgrav 
6065aea254fSDag-Erling Smørgrav ouch:
607fc6e9e65SDag-Erling Smørgrav     if (e != -1)
6085aea254fSDag-Erling Smørgrav 	_ftp_seterr(e);
609d8acd8dcSDag-Erling Smørgrav     return -1;
610d8acd8dcSDag-Erling Smørgrav }
611ce71b736SDag-Erling Smørgrav 
612ce71b736SDag-Erling Smørgrav /*
613ce71b736SDag-Erling Smørgrav  * List a directory
614ce71b736SDag-Erling Smørgrav  */
615ce71b736SDag-Erling Smørgrav extern void warnx(char *, ...);
616ce71b736SDag-Erling Smørgrav struct url_ent *
617ce71b736SDag-Erling Smørgrav fetchListFTP(struct url *url, char *flags)
618ce71b736SDag-Erling Smørgrav {
619ce71b736SDag-Erling Smørgrav     warnx("fetchListFTP(): not implemented");
620ce71b736SDag-Erling Smørgrav     return NULL;
621ce71b736SDag-Erling Smørgrav }
622