xref: /freebsd/lib/libfetch/ftp.c (revision ec894321e300b2a5e1b82b89c0edbf9713ae4145)
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     if (isftpinfo(last_reply)) {
142 	while (!isftpreply(last_reply)) {
143 	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
144 		_fetch_syserr();
145 		return -1;
146 	    }
147 	}
148     }
149 
150     while (lr_length && isspace(last_reply[lr_length-1]))
151 	lr_length--;
152     last_reply[lr_length] = 0;
153 
154     if (!isftpreply(last_reply)) {
155 	_ftp_seterr(999);
156 	return -1;
157     }
158 
159     last_code = (last_reply[0] - '0') * 100
160 	+ (last_reply[1] - '0') * 10
161 	+ (last_reply[2] - '0');
162 
163     return last_code;
164 }
165 
166 /*
167  * Send a command and check reply
168  */
169 static int
170 _ftp_cmd(int cd, char *fmt, ...)
171 {
172     va_list ap;
173     struct iovec iov[2];
174     char *msg;
175     int r;
176 
177     va_start(ap, fmt);
178     vasprintf(&msg, fmt, ap);
179     va_end(ap);
180 
181     if (msg == NULL) {
182 	errno = ENOMEM;
183 	_fetch_syserr();
184 	return -1;
185     }
186     DEBUG(fprintf(stderr, "\033[1m>>> %s\n\033[m", msg));
187     iov[0].iov_base = msg;
188     iov[0].iov_len = strlen(msg);
189     iov[1].iov_base = ENDL;
190     iov[1].iov_len = sizeof ENDL;
191     r = writev(cd, iov, 2);
192     free(msg);
193     if (r == -1) {
194 	_fetch_syserr();
195 	return -1;
196     }
197 
198     return _ftp_chkerr(cd);
199 }
200 
201 /*
202  * Transfer file
203  */
204 static FILE *
205 _ftp_transfer(int cd, char *oper, char *file,
206 	      char *mode, off_t offset, char *flags)
207 {
208     struct sockaddr_storage sin;
209     struct sockaddr_in6 *sin6;
210     struct sockaddr_in *sin4;
211     int pasv, high, verbose;
212     int e, sd = -1;
213     socklen_t l;
214     char *s;
215     FILE *df;
216 
217     /* check flags */
218     pasv = (flags && strchr(flags, 'p'));
219     high = (flags && strchr(flags, 'h'));
220     verbose = (flags && strchr(flags, 'v'));
221 
222     /* passive mode */
223     if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
224 	pasv = (strncasecmp(s, "no", 2) != 0);
225 
226     /* change directory */
227     if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
228 	*s = 0;
229 	if (verbose)
230 	    _fetch_info("changing directory to %s", file);
231 	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
232 	    *s = '/';
233 	    if (e != -1)
234 		_ftp_seterr(e);
235 	    return NULL;
236 	}
237 	*s++ = '/';
238     } else {
239 	if (verbose)
240 	    _fetch_info("changing directory to /");
241 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
242 	    if (e != -1)
243 		_ftp_seterr(e);
244 	    return NULL;
245 	}
246     }
247 
248     /* s now points to file name */
249 
250     /* find our own address, bind, and listen */
251     l = sizeof sin;
252     if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
253 	goto sysouch;
254     if (sin.ss_family == AF_INET6)
255 	unmappedaddr((struct sockaddr_in6 *)&sin);
256 
257     /* open data socket */
258     if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
259 	_fetch_syserr();
260 	return NULL;
261     }
262 
263     if (pasv) {
264 	u_char addr[64];
265 	char *ln, *p;
266 	int i;
267 	int port;
268 
269 	/* send PASV command */
270 	if (verbose)
271 	    _fetch_info("setting passive mode");
272 	switch (sin.ss_family) {
273 	case AF_INET:
274 	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
275 		goto ouch;
276 	    break;
277 	case AF_INET6:
278 	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
279 		if (e == -1)
280 		    goto ouch;
281 		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
282 		    goto ouch;
283 	    }
284 	    break;
285 	default:
286 	    e = 999;		/* XXX: error code should be prepared */
287 	    goto ouch;
288 	}
289 
290 	/*
291 	 * Find address and port number. The reply to the PASV command
292          * is IMHO the one and only weak point in the FTP protocol.
293 	 */
294 	ln = last_reply;
295       	switch (e) {
296 	case FTP_PASSIVE_MODE:
297 	case FTP_LPASSIVE_MODE:
298 	    for (p = ln + 3; *p && !isdigit(*p); p++)
299 		/* nothing */ ;
300 	    if (!*p) {
301 		e = 999;
302 		goto ouch;
303 	    }
304 	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
305 	    for (i = 0; *p && i < l; i++, p++)
306 		addr[i] = strtol(p, &p, 10);
307 	    if (i < l) {
308 		e = 999;
309 		goto ouch;
310 	    }
311 	    break;
312 	case FTP_EPASSIVE_MODE:
313 	    for (p = ln + 3; *p && *p != '('; p++)
314 		/* nothing */ ;
315 	    if (!*p) {
316 		e = 999;
317 		goto ouch;
318 	    }
319 	    ++p;
320 	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
321 		       &port, &addr[3]) != 5 ||
322 		addr[0] != addr[1] ||
323 		addr[0] != addr[2] || addr[0] != addr[3]) {
324 		e = 999;
325 		goto ouch;
326 	    }
327 	    break;
328 	}
329 
330 	/* seek to required offset */
331 	if (offset)
332 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
333 		goto sysouch;
334 
335 	/* construct sockaddr for data socket */
336 	l = sizeof sin;
337 	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
338 	    goto sysouch;
339 	if (sin.ss_family == AF_INET6)
340 	    unmappedaddr((struct sockaddr_in6 *)&sin);
341 	switch (sin.ss_family) {
342 	case AF_INET6:
343 	    sin6 = (struct sockaddr_in6 *)&sin;
344 	    if (e == FTP_EPASSIVE_MODE)
345 		sin6->sin6_port = htons(port);
346 	    else {
347 		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
348 		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
349 	    }
350 	    break;
351 	case AF_INET:
352 	    sin4 = (struct sockaddr_in *)&sin;
353 	    if (e == FTP_EPASSIVE_MODE)
354 		sin4->sin_port = htons(port);
355 	    else {
356 		bcopy(addr, (char *)&sin4->sin_addr, 4);
357 		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
358 	    }
359 	    break;
360 	default:
361 	    e = 999;		/* XXX: error code should be prepared */
362 	    break;
363 	}
364 
365 	/* connect to data port */
366 	if (verbose)
367 	    _fetch_info("opening data connection");
368 	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
369 	    goto sysouch;
370 
371 	/* make the server initiate the transfer */
372 	if (verbose)
373 	    _fetch_info("initiating transfer");
374 	e = _ftp_cmd(cd, "%s %s", oper, s);
375 	if (e != FTP_OPEN_DATA_CONNECTION)
376 	    goto ouch;
377 
378     } else {
379 	u_int32_t a;
380 	u_short p;
381 	int arg, d;
382 	char *ap;
383 	char hname[INET6_ADDRSTRLEN];
384 
385 	switch (sin.ss_family) {
386 	case AF_INET6:
387 	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
388 #ifdef IPV6_PORTRANGE
389 	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
390 	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
391 			   (char *)&arg, sizeof(arg)) == -1)
392 		goto sysouch;
393 #endif
394 	    break;
395 	case AF_INET:
396 	    ((struct sockaddr_in *)&sin)->sin_port = 0;
397 	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
398 	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
399 			   (char *)&arg, sizeof arg) == -1)
400 		goto sysouch;
401 	    break;
402 	}
403 	if (verbose)
404 	    _fetch_info("binding data socket");
405 	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
406 	    goto sysouch;
407 	if (listen(sd, 1) == -1)
408 	    goto sysouch;
409 
410 	/* find what port we're on and tell the server */
411 	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
412 	    goto sysouch;
413 	switch (sin.ss_family) {
414 	case AF_INET:
415 	    sin4 = (struct sockaddr_in *)&sin;
416 	    a = ntohl(sin4->sin_addr.s_addr);
417 	    p = ntohs(sin4->sin_port);
418 	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
419 			 (a >> 24) & 0xff, (a >> 16) & 0xff,
420 			 (a >> 8) & 0xff, a & 0xff,
421 			 (p >> 8) & 0xff, p & 0xff);
422 	    break;
423 	case AF_INET6:
424 #define UC(b)	(((int)b)&0xff)
425 	    e = -1;
426 	    sin6 = (struct sockaddr_in6 *)&sin;
427 	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
428 			    hname, sizeof(hname),
429 			    NULL, 0, NI_NUMERICHOST) == 0) {
430 		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
431 			     htons(sin6->sin6_port));
432 		if (e == -1)
433 		    goto ouch;
434 	    }
435 	    if (e != FTP_OK) {
436 		ap = (char *)&sin6->sin6_addr;
437 		e = _ftp_cmd(cd,
438      "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
439 			     6, 16,
440 			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
441 			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
442 			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
443 			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
444 			     2,
445 			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
446 			     ntohs(sin6->sin6_port)        & 0xff);
447 	    }
448 	    break;
449 	default:
450 	    e = 999;		/* XXX: error code should be prepared */
451 	    goto ouch;
452 	}
453 	if (e != FTP_OK)
454 	    goto ouch;
455 
456 	/* seek to required offset */
457 	if (offset)
458 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
459 		goto sysouch;
460 
461 	/* make the server initiate the transfer */
462 	if (verbose)
463 	    _fetch_info("initiating transfer");
464 	e = _ftp_cmd(cd, "%s %s", oper, s);
465 	if (e != FTP_OPEN_DATA_CONNECTION)
466 	    goto ouch;
467 
468 	/* accept the incoming connection and go to town */
469 	if ((d = accept(sd, NULL, NULL)) == -1)
470 	    goto sysouch;
471 	close(sd);
472 	sd = d;
473     }
474 
475     if ((df = fdopen(sd, mode)) == NULL)
476 	goto sysouch;
477     return df;
478 
479 sysouch:
480     _fetch_syserr();
481     if (sd >= 0)
482 	close(sd);
483     return NULL;
484 
485 ouch:
486     if (e != -1)
487 	_ftp_seterr(e);
488     if (sd >= 0)
489 	close(sd);
490     return NULL;
491 }
492 
493 /*
494  * Log on to FTP server
495  */
496 static int
497 _ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
498 {
499     int cd, e, pp = 0, direct, verbose;
500 #ifdef INET6
501     int af = AF_UNSPEC;
502 #else
503     int af = AF_INET;
504 #endif
505     char *p, *q;
506     const char *logname;
507     char localhost[MAXHOSTNAMELEN];
508     char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
509 
510     direct = (flags && strchr(flags, 'd'));
511     verbose = (flags && strchr(flags, 'v'));
512     if ((flags && strchr(flags, '4')))
513 	af = AF_INET;
514     else if ((flags && strchr(flags, '6')))
515 	af = AF_INET6;
516 
517     /* check for proxy */
518     if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
519 	char c = 0;
520 
521 #ifdef INET6
522 	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
523 	    (*++q != '\0' && *q != ':'))
524 #endif
525 	    q = strchr(p, ':');
526 	if (q != NULL && *q == ':') {
527 	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
528 		/* XXX we should emit some kind of warning */
529 	    }
530 	    pp = atoi(q+1);
531 	    if (pp < 1 || pp > 65535) {
532 		/* XXX we should emit some kind of warning */
533 	    }
534 	}
535 	if (!pp) {
536 	    struct servent *se;
537 
538 	    if ((se = getservbyname("ftp", "tcp")) != NULL)
539 		pp = ntohs(se->s_port);
540 	    else
541 		pp = FTP_DEFAULT_PORT;
542 	}
543 	if (q) {
544 #ifdef INET6
545 	    if (q > p && *p == '[' && *(q - 1) == ']') {
546 		p++;
547 		q--;
548 	    }
549 #endif
550 	    c = *q;
551 	    *q = 0;
552 	}
553 	cd = _fetch_connect(p, pp, af, verbose);
554 	if (q)
555 	    *q = c;
556     } else {
557 	/* no proxy, go straight to target */
558 	cd = _fetch_connect(host, port, af, verbose);
559 	p = NULL;
560     }
561 
562     /* check connection */
563     if (cd == -1) {
564 	_fetch_syserr();
565 	return NULL;
566     }
567 
568     /* expect welcome message */
569     if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
570 	goto fouch;
571 
572     /* send user name and password */
573     if (!user || !*user)
574 	user = FTP_ANONYMOUS_USER;
575     e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
576 	  : _ftp_cmd(cd, "USER %s", user);
577 
578     /* did the server request a password? */
579     if (e == FTP_NEED_PASSWORD) {
580 	if (!pwd || !*pwd)
581 	    pwd = getenv("FTP_PASSWORD");
582 	if (!pwd || !*pwd) {
583 	    if ((logname = getlogin()) == 0)
584 		logname = FTP_ANONYMOUS_PASSWORD;
585 	    gethostname(localhost, sizeof localhost);
586 	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
587 	    pwd = pbuf;
588 	}
589 	e = _ftp_cmd(cd, "PASS %s", pwd);
590     }
591 
592     /* did the server request an account? */
593     if (e == FTP_NEED_ACCOUNT)
594 	goto fouch;
595 
596     /* we should be done by now */
597     if (e != FTP_LOGGED_IN)
598 	goto fouch;
599 
600     /* might as well select mode and type at once */
601 #ifdef FTP_FORCE_STREAM_MODE
602     if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
603 	goto fouch;
604 #endif
605     if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
606 	goto fouch;
607 
608     /* done */
609     return cd;
610 
611 fouch:
612     if (e != -1)
613 	_ftp_seterr(e);
614     close(cd);
615     return NULL;
616 }
617 
618 /*
619  * Disconnect from server
620  */
621 static void
622 _ftp_disconnect(int cd)
623 {
624     (void)_ftp_cmd(cd, "QUIT");
625     close(cd);
626 }
627 
628 /*
629  * Check if we're already connected
630  */
631 static int
632 _ftp_isconnected(struct url *url)
633 {
634     return (cached_socket
635 	    && (strcmp(url->host, cached_host.host) == 0)
636 	    && (strcmp(url->user, cached_host.user) == 0)
637 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
638 	    && (url->port == cached_host.port));
639 }
640 
641 /*
642  * Check the cache, reconnect if no luck
643  */
644 static int
645 _ftp_cached_connect(struct url *url, char *flags)
646 {
647     int e, cd;
648 
649     cd = -1;
650 
651     /* set default port */
652     if (!url->port) {
653 	struct servent *se;
654 
655 	if ((se = getservbyname("ftp", "tcp")) != NULL)
656 	    url->port = ntohs(se->s_port);
657 	else
658 	    url->port = FTP_DEFAULT_PORT;
659     }
660 
661     /* try to use previously cached connection */
662     if (_ftp_isconnected(url)) {
663 	e = _ftp_cmd(cached_socket, "NOOP");
664 	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
665 	    cd = cached_socket;
666     }
667 
668     /* connect to server */
669     if (cd == -1) {
670 	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
671 	if (cd == -1)
672 	    return -1;
673 	if (cached_socket)
674 	    _ftp_disconnect(cached_socket);
675 	cached_socket = cd;
676 	memcpy(&cached_host, url, sizeof *url);
677     }
678 
679     return cd;
680 }
681 
682 /*
683  * Get file
684  */
685 FILE *
686 fetchGetFTP(struct url *url, char *flags)
687 {
688     int cd;
689 
690     /* connect to server */
691     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
692 	return NULL;
693 
694     /* initiate the transfer */
695     return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
696 }
697 
698 /*
699  * Put file
700  */
701 FILE *
702 fetchPutFTP(struct url *url, char *flags)
703 {
704     int cd;
705 
706     /* connect to server */
707     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
708 	return NULL;
709 
710     /* initiate the transfer */
711     return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
712 			 url->doc, "w", url->offset, flags);
713 }
714 
715 /*
716  * Get file stats
717  */
718 int
719 fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
720 {
721     char *ln, *s;
722     struct tm tm;
723     time_t t;
724     int e, cd;
725 
726     us->size = -1;
727     us->atime = us->mtime = 0;
728 
729     /* connect to server */
730     if ((cd = _ftp_cached_connect(url, flags)) == NULL)
731 	return -1;
732 
733     /* change directory */
734     if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
735 	*s = 0;
736 	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
737 	    *s = '/';
738 	    goto ouch;
739 	}
740 	*s++ = '/';
741     } else {
742 	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
743 	    goto ouch;
744     }
745 
746     /* s now points to file name */
747 
748     if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
749 	goto ouch;
750     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
751 	/* nothing */ ;
752     for (us->size = 0; *ln && isdigit(*ln); ln++)
753 	us->size = us->size * 10 + *ln - '0';
754     if (*ln && !isspace(*ln)) {
755 	_ftp_seterr(999);
756 	return -1;
757     }
758     DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
759 
760     if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
761 	goto ouch;
762     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
763 	/* nothing */ ;
764     e = 999;
765     switch (strspn(ln, "0123456789")) {
766     case 14:
767 	break;
768     case 15:
769 	ln++;
770 	ln[0] = '2';
771 	ln[1] = '0';
772 	break;
773     default:
774 	goto ouch;
775     }
776     if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
777 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
778 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
779 	goto ouch;
780     tm.tm_mon--;
781     tm.tm_year -= 1900;
782     tm.tm_isdst = -1;
783     t = timegm(&tm);
784     if (t == (time_t)-1)
785 	t = time(NULL);
786     us->mtime = t;
787     us->atime = t;
788     DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
789 		  "%02d:%02d:%02d\033[m]\n",
790 		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
791 		  tm.tm_hour, tm.tm_min, tm.tm_sec));
792     return 0;
793 
794 ouch:
795     if (e != -1)
796 	_ftp_seterr(e);
797     return -1;
798 }
799 
800 /*
801  * List a directory
802  */
803 extern void warnx(char *, ...);
804 struct url_ent *
805 fetchListFTP(struct url *url, char *flags)
806 {
807     warnx("fetchListFTP(): not implemented");
808     return NULL;
809 }
810