xref: /freebsd/lib/libfetch/ftp.c (revision fa5dce6c26dd469fecbb32d1ff555c0e6e78f610)
1 /*-
2  * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  */
30 
31 /*
32  * Portions of this code were taken from or based on ftpio.c:
33  *
34  * ----------------------------------------------------------------------------
35  * "THE BEER-WARE LICENSE" (Revision 42):
36  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37  * can do whatever you want with this stuff. If we meet some day, and you think
38  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39  * ----------------------------------------------------------------------------
40  *
41  * Major Changelog:
42  *
43  * Dag-Erling Co�dan Sm�rgrav
44  * 9 Jun 1998
45  *
46  * Incorporated into libfetch
47  *
48  * Jordan K. Hubbard
49  * 17 Jan 1996
50  *
51  * Turned inside out. Now returns xfers as new file ids, not as a special
52  * `state' of FTP_t
53  *
54  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55  *
56  */
57 
58 #include <sys/param.h>
59 #include <sys/socket.h>
60 #include <sys/uio.h>
61 #include <netinet/in.h>
62 
63 #include <ctype.h>
64 #include <errno.h>
65 #include <netdb.h>
66 #include <stdarg.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <time.h>
71 #include <unistd.h>
72 
73 #include "fetch.h"
74 #include "common.h"
75 #include "ftperr.h"
76 
77 #define FTP_ANONYMOUS_USER	"ftp"
78 #define FTP_ANONYMOUS_PASSWORD	"ftp"
79 #define FTP_DEFAULT_PORT 21
80 
81 #define FTP_OPEN_DATA_CONNECTION	150
82 #define FTP_OK				200
83 #define FTP_FILE_STATUS			213
84 #define FTP_SERVICE_READY		220
85 #define FTP_PASSIVE_MODE		227
86 #define FTP_LPASSIVE_MODE		228
87 #define FTP_EPASSIVE_MODE		229
88 #define FTP_LOGGED_IN			230
89 #define FTP_FILE_ACTION_OK		250
90 #define FTP_NEED_PASSWORD		331
91 #define FTP_NEED_ACCOUNT		332
92 #define FTP_FILE_OK			350
93 #define FTP_SYNTAX_ERROR		500
94 
95 static char ENDL[2] = "\r\n";
96 
97 static struct url cached_host;
98 static int cached_socket;
99 
100 static char *last_reply;
101 static size_t lr_size, lr_length;
102 static int last_code;
103 
104 #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105 			 && isdigit(foo[2]) \
106                          && (foo[3] == ' ' || foo[3] == '\0'))
107 #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
108 			&& isdigit(foo[2]) && foo[3] == '-')
109 
110 /* translate IPv4 mapped IPv6 address to IPv4 address */
111 static void
112 unmappedaddr(struct sockaddr_in6 *sin6)
113 {
114     struct sockaddr_in *sin4;
115     u_int32_t addr;
116     int port;
117 
118     if (sin6->sin6_family != AF_INET6 ||
119 	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
120 	return;
121     sin4 = (struct sockaddr_in *)sin6;
122     addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
123     port = sin6->sin6_port;
124     memset(sin4, 0, sizeof(struct sockaddr_in));
125     sin4->sin_addr.s_addr = addr;
126     sin4->sin_port = port;
127     sin4->sin_family = AF_INET;
128     sin4->sin_len = sizeof(struct sockaddr_in);
129 }
130 
131 /*
132  * Get server response
133  */
134 static int
135 _ftp_chkerr(int cd)
136 {
137     if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
138 	_fetch_syserr();
139 	return -1;
140     }
141 #ifndef NDEBUG
142     _fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
143 #endif
144     if (isftpinfo(last_reply)) {
145 	while (!isftpreply(last_reply)) {
146 	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
147 		_fetch_syserr();
148 		return -1;
149 	    }
150 #ifndef NDEBUG
151 	    _fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
152 #endif
153 	}
154     }
155 
156     while (lr_length && isspace(last_reply[lr_length-1]))
157 	lr_length--;
158     last_reply[lr_length] = 0;
159 
160     if (!isftpreply(last_reply)) {
161 	_ftp_seterr(999);
162 	return -1;
163     }
164 
165     last_code = (last_reply[0] - '0') * 100
166 	+ (last_reply[1] - '0') * 10
167 	+ (last_reply[2] - '0');
168 
169     return last_code;
170 }
171 
172 /*
173  * Send a command and check reply
174  */
175 static int
176 _ftp_cmd(int cd, char *fmt, ...)
177 {
178     va_list ap;
179     struct iovec iov[2];
180     char *msg;
181     int r;
182 
183     va_start(ap, fmt);
184     vasprintf(&msg, fmt, ap);
185     va_end(ap);
186 
187     if (msg == NULL) {
188 	errno = ENOMEM;
189 	_fetch_syserr();
190 	return -1;
191     }
192 #ifndef NDEBUG
193     _fetch_info("sending '%s'", msg);
194 #endif
195     iov[0].iov_base = msg;
196     iov[0].iov_len = strlen(msg);
197     iov[1].iov_base = ENDL;
198     iov[1].iov_len = sizeof ENDL;
199     r = writev(cd, iov, 2);
200     free(msg);
201     if (r == -1) {
202 	_fetch_syserr();
203 	return -1;
204     }
205 
206     return _ftp_chkerr(cd);
207 }
208 
209 /*
210  * Transfer file
211  */
212 static FILE *
213 _ftp_transfer(int cd, char *oper, char *file,
214 	      char *mode, off_t offset, char *flags)
215 {
216     struct sockaddr_storage sin;
217     struct sockaddr_in6 *sin6;
218     struct sockaddr_in *sin4;
219     int pasv, high, verbose;
220     int e, sd = -1;
221     socklen_t l;
222     char *s;
223     FILE *df;
224 
225     /* check flags */
226     pasv = (flags && strchr(flags, 'p'));
227     high = (flags && strchr(flags, 'h'));
228     verbose = (flags && strchr(flags, 'v'));
229 
230     /* passive mode */
231     if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
232 	pasv = (strncasecmp(s, "no", 2) != 0);
233 
234     /* change directory */
235     if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
236 	*s = 0;
237 	if (verbose)
238 	    _fetch_info("changing directory to %s", file);
239 	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
240 	    *s = '/';
241 	    if (e != -1)
242 		_ftp_seterr(e);
243 	    return NULL;
244 	}
245 	*s++ = '/';
246     } else {
247 	if (verbose)
248 	    _fetch_info("changing directory to /");
249 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
250 	    if (e != -1)
251 		_ftp_seterr(e);
252 	    return NULL;
253 	}
254     }
255 
256     /* s now points to file name */
257 
258     /* find our own address, bind, and listen */
259     l = sizeof sin;
260     if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
261 	goto sysouch;
262     if (sin.ss_family == AF_INET6)
263 	unmappedaddr((struct sockaddr_in6 *)&sin);
264 
265     /* open data socket */
266     if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
267 	_fetch_syserr();
268 	return NULL;
269     }
270 
271     if (pasv) {
272 	u_char addr[64];
273 	char *ln, *p;
274 	int i;
275 	int port;
276 
277 	/* send PASV command */
278 	if (verbose)
279 	    _fetch_info("setting passive mode");
280 	switch (sin.ss_family) {
281 	case AF_INET:
282 	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
283 		goto ouch;
284 	    break;
285 	case AF_INET6:
286 	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
287 		if (e == -1)
288 		    goto ouch;
289 		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
290 		    goto ouch;
291 	    }
292 	    break;
293 	default:
294 	    e = 999;		/* XXX: error code should be prepared */
295 	    goto ouch;
296 	}
297 
298 	/*
299 	 * Find address and port number. The reply to the PASV command
300          * is IMHO the one and only weak point in the FTP protocol.
301 	 */
302 	ln = last_reply;
303       	switch (e) {
304 	case FTP_PASSIVE_MODE:
305 	case FTP_LPASSIVE_MODE:
306 	    for (p = ln + 3; *p && !isdigit(*p); p++)
307 		/* nothing */ ;
308 	    if (!*p) {
309 		e = 999;
310 		goto ouch;
311 	    }
312 	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
313 	    for (i = 0; *p && i < l; i++, p++)
314 		addr[i] = strtol(p, &p, 10);
315 	    if (i < l) {
316 		e = 999;
317 		goto ouch;
318 	    }
319 	    break;
320 	case FTP_EPASSIVE_MODE:
321 	    for (p = ln + 3; *p && *p != '('; p++)
322 		/* nothing */ ;
323 	    if (!*p) {
324 		e = 999;
325 		goto ouch;
326 	    }
327 	    ++p;
328 	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
329 		       &port, &addr[3]) != 5 ||
330 		addr[0] != addr[1] ||
331 		addr[0] != addr[2] || addr[0] != addr[3]) {
332 		e = 999;
333 		goto ouch;
334 	    }
335 	    break;
336 	}
337 
338 	/* seek to required offset */
339 	if (offset)
340 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
341 		goto sysouch;
342 
343 	/* construct sockaddr for data socket */
344 	l = sizeof sin;
345 	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
346 	    goto sysouch;
347 	if (sin.ss_family == AF_INET6)
348 	    unmappedaddr((struct sockaddr_in6 *)&sin);
349 	switch (sin.ss_family) {
350 	case AF_INET6:
351 	    sin6 = (struct sockaddr_in6 *)&sin;
352 	    if (e == FTP_EPASSIVE_MODE)
353 		sin6->sin6_port = htons(port);
354 	    else {
355 		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
356 		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
357 	    }
358 	    break;
359 	case AF_INET:
360 	    sin4 = (struct sockaddr_in *)&sin;
361 	    if (e == FTP_EPASSIVE_MODE)
362 		sin4->sin_port = htons(port);
363 	    else {
364 		bcopy(addr, (char *)&sin4->sin_addr, 4);
365 		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
366 	    }
367 	    break;
368 	default:
369 	    e = 999;		/* XXX: error code should be prepared */
370 	    break;
371 	}
372 
373 	/* connect to data port */
374 	if (verbose)
375 	    _fetch_info("opening data connection");
376 	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
377 	    goto sysouch;
378 
379 	/* make the server initiate the transfer */
380 	if (verbose)
381 	    _fetch_info("initiating transfer");
382 	e = _ftp_cmd(cd, "%s %s", oper, s);
383 	if (e != FTP_OPEN_DATA_CONNECTION)
384 	    goto ouch;
385 
386     } else {
387 	u_int32_t a;
388 	u_short p;
389 	int arg, d;
390 	char *ap;
391 	char hname[INET6_ADDRSTRLEN];
392 
393 	switch (sin.ss_family) {
394 	case AF_INET6:
395 	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
396 #ifdef IPV6_PORTRANGE
397 	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
398 	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
399 			   (char *)&arg, sizeof(arg)) == -1)
400 		goto sysouch;
401 #endif
402 	    break;
403 	case AF_INET:
404 	    ((struct sockaddr_in *)&sin)->sin_port = 0;
405 	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
406 	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
407 			   (char *)&arg, sizeof arg) == -1)
408 		goto sysouch;
409 	    break;
410 	}
411 	if (verbose)
412 	    _fetch_info("binding data socket");
413 	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
414 	    goto sysouch;
415 	if (listen(sd, 1) == -1)
416 	    goto sysouch;
417 
418 	/* find what port we're on and tell the server */
419 	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
420 	    goto sysouch;
421 	switch (sin.ss_family) {
422 	case AF_INET:
423 	    sin4 = (struct sockaddr_in *)&sin;
424 	    a = ntohl(sin4->sin_addr.s_addr);
425 	    p = ntohs(sin4->sin_port);
426 	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
427 			 (a >> 24) & 0xff, (a >> 16) & 0xff,
428 			 (a >> 8) & 0xff, a & 0xff,
429 			 (p >> 8) & 0xff, p & 0xff);
430 	    break;
431 	case AF_INET6:
432 #define UC(b)	(((int)b)&0xff)
433 	    e = -1;
434 	    sin6 = (struct sockaddr_in6 *)&sin;
435 	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
436 			    hname, sizeof(hname),
437 			    NULL, 0, NI_NUMERICHOST) == 0) {
438 		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
439 			     htons(sin6->sin6_port));
440 		if (e == -1)
441 		    goto ouch;
442 	    }
443 	    if (e != FTP_OK) {
444 		ap = (char *)&sin6->sin6_addr;
445 		e = _ftp_cmd(cd,
446      "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
447 			     6, 16,
448 			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
449 			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
450 			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
451 			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
452 			     2,
453 			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
454 			     ntohs(sin6->sin6_port)        & 0xff);
455 	    }
456 	    break;
457 	default:
458 	    e = 999;		/* XXX: error code should be prepared */
459 	    goto ouch;
460 	}
461 	if (e != FTP_OK)
462 	    goto ouch;
463 
464 	/* seek to required offset */
465 	if (offset)
466 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
467 		goto sysouch;
468 
469 	/* make the server initiate the transfer */
470 	if (verbose)
471 	    _fetch_info("initiating transfer");
472 	e = _ftp_cmd(cd, "%s %s", oper, s);
473 	if (e != FTP_OPEN_DATA_CONNECTION)
474 	    goto ouch;
475 
476 	/* accept the incoming connection and go to town */
477 	if ((d = accept(sd, NULL, NULL)) == -1)
478 	    goto sysouch;
479 	close(sd);
480 	sd = d;
481     }
482 
483     if ((df = fdopen(sd, mode)) == NULL)
484 	goto sysouch;
485     return df;
486 
487 sysouch:
488     _fetch_syserr();
489     if (sd >= 0)
490 	close(sd);
491     return NULL;
492 
493 ouch:
494     if (e != -1)
495 	_ftp_seterr(e);
496     if (sd >= 0)
497 	close(sd);
498     return NULL;
499 }
500 
501 /*
502  * Log on to FTP server
503  */
504 static int
505 _ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
506 {
507     int cd, e, pp = 0, direct, verbose;
508 #ifdef INET6
509     int af = AF_UNSPEC;
510 #else
511     int af = AF_INET;
512 #endif
513     char *p, *q;
514     const char *logname;
515     char localhost[MAXHOSTNAMELEN];
516     char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
517 
518     direct = (flags && strchr(flags, 'd'));
519     verbose = (flags && strchr(flags, 'v'));
520     if ((flags && strchr(flags, '4')))
521 	af = AF_INET;
522     else if ((flags && strchr(flags, '6')))
523 	af = AF_INET6;
524 
525     /* check for proxy */
526     if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
527 	char c = 0;
528 
529 #ifdef INET6
530 	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
531 	    (*++q != '\0' && *q != ':'))
532 #endif
533 	    q = strchr(p, ':');
534 	if (q != NULL && *q == ':') {
535 	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
536 		/* XXX we should emit some kind of warning */
537 	    }
538 	    pp = atoi(q+1);
539 	    if (pp < 1 || pp > 65535) {
540 		/* XXX we should emit some kind of warning */
541 	    }
542 	}
543 	if (!pp) {
544 	    struct servent *se;
545 
546 	    if ((se = getservbyname("ftp", "tcp")) != NULL)
547 		pp = ntohs(se->s_port);
548 	    else
549 		pp = FTP_DEFAULT_PORT;
550 	}
551 	if (q) {
552 #ifdef INET6
553 	    if (q > p && *p == '[' && *(q - 1) == ']') {
554 		p++;
555 		q--;
556 	    }
557 #endif
558 	    c = *q;
559 	    *q = 0;
560 	}
561 	cd = _fetch_connect(p, pp, af, verbose);
562 	if (q)
563 	    *q = c;
564     } else {
565 	/* no proxy, go straight to target */
566 	cd = _fetch_connect(host, port, af, verbose);
567 	p = NULL;
568     }
569 
570     /* check connection */
571     if (cd == -1) {
572 	_fetch_syserr();
573 	return NULL;
574     }
575 
576     /* expect welcome message */
577     if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
578 	goto fouch;
579 
580     /* send user name and password */
581     if (!user || !*user)
582 	user = FTP_ANONYMOUS_USER;
583     e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
584 	  : _ftp_cmd(cd, "USER %s", user);
585 
586     /* did the server request a password? */
587     if (e == FTP_NEED_PASSWORD) {
588 	if (!pwd || !*pwd)
589 	    pwd = getenv("FTP_PASSWORD");
590 	if (!pwd || !*pwd) {
591 	    if ((logname = getlogin()) == 0)
592 		logname = FTP_ANONYMOUS_PASSWORD;
593 	    gethostname(localhost, sizeof localhost);
594 	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
595 	    pwd = pbuf;
596 	}
597 	e = _ftp_cmd(cd, "PASS %s", pwd);
598     }
599 
600     /* did the server request an account? */
601     if (e == FTP_NEED_ACCOUNT)
602 	goto fouch;
603 
604     /* we should be done by now */
605     if (e != FTP_LOGGED_IN)
606 	goto fouch;
607 
608     /* might as well select mode and type at once */
609 #ifdef FTP_FORCE_STREAM_MODE
610     if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
611 	goto fouch;
612 #endif
613     if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
614 	goto fouch;
615 
616     /* done */
617     return cd;
618 
619 fouch:
620     if (e != -1)
621 	_ftp_seterr(e);
622     close(cd);
623     return NULL;
624 }
625 
626 /*
627  * Disconnect from server
628  */
629 static void
630 _ftp_disconnect(int cd)
631 {
632     (void)_ftp_cmd(cd, "QUIT");
633     close(cd);
634 }
635 
636 /*
637  * Check if we're already connected
638  */
639 static int
640 _ftp_isconnected(struct url *url)
641 {
642     return (cached_socket
643 	    && (strcmp(url->host, cached_host.host) == 0)
644 	    && (strcmp(url->user, cached_host.user) == 0)
645 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
646 	    && (url->port == cached_host.port));
647 }
648 
649 /*
650  * Check the cache, reconnect if no luck
651  */
652 static int
653 _ftp_cached_connect(struct url *url, char *flags)
654 {
655     int e, cd;
656 
657     cd = -1;
658 
659     /* set default port */
660     if (!url->port) {
661 	struct servent *se;
662 
663 	if ((se = getservbyname("ftp", "tcp")) != NULL)
664 	    url->port = ntohs(se->s_port);
665 	else
666 	    url->port = FTP_DEFAULT_PORT;
667     }
668 
669     /* try to use previously cached connection */
670     if (_ftp_isconnected(url)) {
671 	e = _ftp_cmd(cached_socket, "NOOP");
672 	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
673 	    cd = cached_socket;
674     }
675 
676     /* connect to server */
677     if (cd == -1) {
678 	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
679 	if (cd == -1)
680 	    return -1;
681 	if (cached_socket)
682 	    _ftp_disconnect(cached_socket);
683 	cached_socket = cd;
684 	memcpy(&cached_host, url, sizeof *url);
685     }
686 
687     return cd;
688 }
689 
690 /*
691  * Get file
692  */
693 FILE *
694 fetchGetFTP(struct url *url, char *flags)
695 {
696     int cd;
697 
698     /* connect to server */
699     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
700 	return NULL;
701 
702     /* initiate the transfer */
703     return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
704 }
705 
706 /*
707  * Put file
708  */
709 FILE *
710 fetchPutFTP(struct url *url, char *flags)
711 {
712     int cd;
713 
714     /* connect to server */
715     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
716 	return NULL;
717 
718     /* initiate the transfer */
719     return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
720 			 url->doc, "w", url->offset, flags);
721 }
722 
723 /*
724  * Get file stats
725  */
726 int
727 fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
728 {
729     char *ln, *s;
730     struct tm tm;
731     time_t t;
732     int e, cd;
733 
734     us->size = -1;
735     us->atime = us->mtime = 0;
736 
737     /* connect to server */
738     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
739 	return -1;
740 
741     /* change directory */
742     if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
743 	*s = 0;
744 	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
745 	    *s = '/';
746 	    goto ouch;
747 	}
748 	*s++ = '/';
749     } else {
750 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
751 	    goto ouch;
752     }
753 
754     /* s now points to file name */
755 
756     if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
757 	goto ouch;
758     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
759 	/* nothing */ ;
760     for (us->size = 0; *ln && isdigit(*ln); ln++)
761 	us->size = us->size * 10 + *ln - '0';
762     if (*ln && !isspace(*ln)) {
763 	_ftp_seterr(999);
764 	return -1;
765     }
766     DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
767 
768     if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
769 	goto ouch;
770     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
771 	/* nothing */ ;
772     e = 999;
773     switch (strspn(ln, "0123456789")) {
774     case 14:
775 	break;
776     case 15:
777 	ln++;
778 	ln[0] = '2';
779 	ln[1] = '0';
780 	break;
781     default:
782 	goto ouch;
783     }
784     if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
785 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
786 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
787 	goto ouch;
788     tm.tm_mon--;
789     tm.tm_year -= 1900;
790     tm.tm_isdst = -1;
791     t = timegm(&tm);
792     if (t == (time_t)-1)
793 	t = time(NULL);
794     us->mtime = t;
795     us->atime = t;
796     DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
797 		  "%02d:%02d:%02d\033[m]\n",
798 		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
799 		  tm.tm_hour, tm.tm_min, tm.tm_sec));
800     return 0;
801 
802 ouch:
803     if (e != -1)
804 	_ftp_seterr(e);
805     return -1;
806 }
807 
808 /*
809  * List a directory
810  */
811 extern void warnx(char *, ...);
812 struct url_ent *
813 fetchListFTP(struct url *url, char *flags)
814 {
815     warnx("fetchListFTP(): not implemented");
816     return NULL;
817 }
818