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