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