1/*
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 * Major Changelog:
10 *
11 * Jordan K. Hubbard
12 * 17 Jan 1996
13 *
14 * Turned inside out. Now returns xfers as new file ids, not as a special
15 * `state' of FTP_t
16 */
17
18#include <sys/cdefs.h>
19__FBSDID("$FreeBSD$");
20
21#include <sys/types.h>
22#include <sys/socket.h>
23
24#include <netinet/in.h>
25
26#include <arpa/inet.h>
27
28#include <ctype.h>
29#include <errno.h>
30#include <ftpio.h>
31#include <netdb.h>
32#include <signal.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#define SUCCESS		 0
40#define FAILURE		-1
41
42#ifndef TRUE
43#define TRUE	(1)
44#define FALSE	(0)
45#endif
46
47/* How to see by a given code whether or not the connection has timed out */
48#define FTP_TIMEOUT(code)	(FtpTimedOut || code == FTP_TIMED_OUT)
49
50/* Internal routines - deal only with internal FTP_t type */
51static FTP_t	ftp_new(void);
52static void	check_passive(FILE *fp);
53static int	ftp_read_method(void *n, char *buf, int nbytes);
54static int	ftp_write_method(void *n, const char *buf, int nbytes);
55static int	ftp_close_method(void *n);
56static int	writes(int fd, char *s);
57static __inline char *get_a_line(FTP_t ftp);
58static int	get_a_number(FTP_t ftp, char **q);
59static int	botch(char *func, char *botch_state);
60static int	cmd(FTP_t ftp, const char *fmt, ...);
61static int	ftp_login_session(FTP_t ftp, char *host, int af, char *user, char *passwd, int port, int verbose);
62static int	ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto);
63static int	ftp_close(FTP_t ftp);
64static int	get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret);
65static void	ftp_timeout(int sig);
66static void	ftp_set_timeout(void);
67static void	ftp_clear_timeout(void);
68static void	ai_unmapped(struct addrinfo *);
69
70
71/* Global status variable - ick */
72int FtpTimedOut;
73
74/* FTP happy status codes */
75#define FTP_GENERALLY_HAPPY	200
76#define FTP_ASCII_HAPPY		FTP_GENERALLY_HAPPY
77#define FTP_BINARY_HAPPY	FTP_GENERALLY_HAPPY
78#define FTP_PORT_HAPPY		FTP_GENERALLY_HAPPY
79#define FTP_HAPPY_COMMENT	220
80#define FTP_QUIT_HAPPY		221
81#define FTP_TRANSFER_HAPPY	226
82#define FTP_PASSIVE_HAPPY	227
83#define FTP_LPASSIVE_HAPPY	228
84#define FTP_EPASSIVE_HAPPY	229
85#define FTP_CHDIR_HAPPY		250
86
87/* FTP unhappy status codes */
88#define FTP_TIMED_OUT		421
89
90/*
91 * XXX
92 * gross!  evil!  bad!  We really need an access primitive for cookie in stdio itself.
93 * it's too convenient a hook to bury and it's already exported through funopen as it is, so...
94 * XXX
95 */
96#define fcookie(fp)	((fp)->_cookie)
97
98/* Placeholder in case we want to do any pre-init stuff at some point */
99int
100networkInit()
101{
102    return SUCCESS;	/* XXX dummy function for now XXX */
103}
104
105/* Check a return code with some lenience for back-dated garbage that might be in the buffer */
106static int
107check_code(FTP_t ftp, int var, int preferred)
108{
109    ftp->error = 0;
110    while (1) {
111	if (var == preferred)
112	    return 0;
113	else if (var == FTP_TRANSFER_HAPPY)	/* last operation succeeded */
114	    var = get_a_number(ftp, NULL);
115	else if (var == FTP_HAPPY_COMMENT)	/* chit-chat */
116	    var = get_a_number(ftp, NULL);
117	else if (var == FTP_GENERALLY_HAPPY)	/* general success code */
118	    var = get_a_number(ftp, NULL);
119	else {
120	    ftp->error = var;
121	    return 1;
122	}
123    }
124}
125
126int
127ftpAscii(FILE *fp)
128{
129    FTP_t ftp = fcookie(fp);
130    int i;
131
132    if (!ftp->is_binary)
133	return SUCCESS;
134    i = cmd(ftp, "TYPE A");
135    if (i < 0 || check_code(ftp, i, FTP_ASCII_HAPPY))
136	return i;
137    ftp->is_binary = FALSE;
138    return SUCCESS;
139}
140
141int
142ftpBinary(FILE *fp)
143{
144    FTP_t ftp = fcookie(fp);
145    int i;
146
147    if (ftp->is_binary)
148	return SUCCESS;
149    i = cmd(ftp, "TYPE I");
150    if (i < 0 || check_code(ftp, i, FTP_BINARY_HAPPY))
151	return i;
152    ftp->is_binary = TRUE;
153    return SUCCESS;
154}
155void
156ftpVerbose(FILE *fp, int status)
157{
158    FTP_t ftp = fcookie(fp);
159    ftp->is_verbose = status;
160}
161
162int
163ftpChdir(FILE *fp, char *dir)
164{
165    int i;
166    FTP_t ftp = fcookie(fp);
167
168    i = cmd(ftp, "CWD %s", dir);
169    if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY))
170	return i;
171    return SUCCESS;
172}
173
174int
175ftpErrno(FILE *fp)
176{
177    FTP_t ftp = fcookie(fp);
178    return ftp->error;
179}
180
181const char *
182ftpErrString(int error)
183{
184    int	k;
185
186    if (error == -1)
187	return("connection in wrong state");
188    if (error < 100)
189	/* XXX soon UNIX errnos will catch up with FTP protocol errnos */
190	return strerror(error);
191    for (k = 0; k < ftpErrListLength; k++)
192      if (ftpErrList[k].num == error)
193	return(ftpErrList[k].string);
194    return("Unknown error");
195}
196
197off_t
198ftpGetSize(FILE *fp, char *name)
199{
200    int i;
201    char p[BUFSIZ], *cp, *ep;
202    FTP_t ftp = fcookie(fp);
203    off_t size;
204
205    check_passive(fp);
206    sprintf(p, "SIZE %s\r\n", name);
207    if (ftp->is_verbose)
208	fprintf(stderr, "Sending %s", p);
209    if (writes(ftp->fd_ctrl, p))
210	return (off_t)-1;
211    i = get_a_number(ftp, &cp);
212    if (check_code(ftp, i, 213))
213	return (off_t)-1;
214
215    errno = 0;				/* to check for ERANGE */
216    size = (off_t)strtoq(cp, &ep, 10);
217    if (*ep != '\0' || errno == ERANGE)
218	return (off_t)-1;
219    return size;
220}
221
222time_t
223ftpGetModtime(FILE *fp, char *name)
224{
225    char p[BUFSIZ], *cp;
226    struct tm t;
227    time_t t0 = time (0);
228    FTP_t ftp = fcookie(fp);
229    int i;
230
231    check_passive(fp);
232    sprintf(p, "MDTM %s\r\n", name);
233    if (ftp->is_verbose)
234	fprintf(stderr, "Sending %s", p);
235    if (writes(ftp->fd_ctrl, p))
236	return (time_t)0;
237    i = get_a_number(ftp, &cp);
238    if (check_code(ftp, i, 213))
239	return (time_t)0;
240    while (*cp && !isdigit(*cp))
241	cp++;
242    if (!*cp)
243	return (time_t)0;
244    t0 = localtime (&t0)->tm_gmtoff;
245    sscanf(cp, "%04d%02d%02d%02d%02d%02d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
246    t.tm_mon--;
247    t.tm_year -= 1900;
248    t.tm_isdst=-1;
249    t.tm_gmtoff = 0;
250    t0 += mktime (&t);
251    return t0;
252}
253
254FILE *
255ftpGet(FILE *fp, char *file, off_t *seekto)
256{
257    FILE *fp2;
258    FTP_t ftp = fcookie(fp);
259
260    check_passive(fp);
261    if (ftpBinary(fp) != SUCCESS)
262	return NULL;
263
264    if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS)
265	return fp2;
266    return NULL;
267}
268
269/* Returns a standard FILE pointer type representing an open control connection */
270FILE *
271ftpLogin(char *host, char *user, char *passwd, int port, int verbose, int *retcode)
272{
273#ifdef INET6
274    return ftpLoginAf(host, AF_UNSPEC, user, passwd, port, verbose, retcode);
275#else
276    return ftpLoginAf(host, AF_INET, user, passwd, port, verbose, retcode);
277#endif
278}
279
280FILE *
281ftpLoginAf(char *host, int af, char *user, char *passwd, int port, int verbose, int *retcode)
282{
283    FTP_t n;
284    FILE *fp;
285
286    if (retcode)
287	*retcode = 0;
288    if (networkInit() != SUCCESS)
289	return NULL;
290
291    n = ftp_new();
292    fp = NULL;
293    if (n && ftp_login_session(n, host, af, user, passwd, port, verbose) == SUCCESS) {
294	fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method);	/* BSD 4.4 function! */
295    }
296    if (retcode) {
297	if (!n)
298	    *retcode = (FtpTimedOut ? FTP_TIMED_OUT : -1);
299	/* Poor attempt at mapping real errnos to FTP error codes */
300	else switch(n->error) {
301	    case EADDRNOTAVAIL:
302		*retcode = FTP_TIMED_OUT;	/* Actually no such host, but we have no way of saying that. :-( */
303		break;
304
305            case ETIMEDOUT:
306		*retcode = FTP_TIMED_OUT;
307		break;
308
309	    default:
310		*retcode = n->error;
311		break;
312	}
313    }
314    return fp;
315}
316
317FILE *
318ftpPut(FILE *fp, char *file)
319{
320    FILE *fp2;
321    FTP_t ftp = fcookie(fp);
322
323    check_passive(fp);
324    if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS)
325	return fp2;
326    return NULL;
327}
328
329int
330ftpPassive(FILE *fp, int st)
331{
332    FTP_t ftp = fcookie(fp);
333
334    ftp->is_passive = !!st;	/* normalize "st" to zero or one */
335    return SUCCESS;
336}
337
338FILE *
339ftpGetURL(char *url, char *user, char *passwd, int *retcode)
340{
341#ifdef INET6
342    return ftpGetURLAf(url, AF_UNSPEC, user, passwd, retcode);
343#else
344    return ftpGetURLAf(url, AF_INET, user, passwd, retcode);
345#endif
346}
347
348FILE *
349ftpGetURLAf(char *url, int af, char *user, char *passwd, int *retcode)
350{
351    char host[255], name[255];
352    int port;
353    FILE *fp2;
354    static FILE *fp = NULL;
355    static char *prev_host;
356
357    if (retcode)
358	*retcode = 0;
359    if (get_url_info(url, host, &port, name) == SUCCESS) {
360	if (fp && prev_host) {
361	    if (!strcmp(prev_host, host)) {
362		/* Try to use cached connection */
363		fp2 = ftpGet(fp, name, NULL);
364		if (!fp2) {
365		    /* Connection timed out or was no longer valid */
366		    fclose(fp);
367		    free(prev_host);
368		    prev_host = NULL;
369		}
370		else
371		    return fp2;
372	    }
373	    else {
374		/* It's a different host now, flush old */
375		fclose(fp);
376		free(prev_host);
377		prev_host = NULL;
378	    }
379	}
380	fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
381	if (fp) {
382	    fp2 = ftpGet(fp, name, NULL);
383	    if (!fp2) {
384		/* Connection timed out or was no longer valid */
385		if (retcode)
386		    *retcode = ftpErrno(fp);
387		fclose(fp);
388		fp = NULL;
389	    }
390	    else
391		prev_host = strdup(host);
392	    return fp2;
393	}
394    }
395    return NULL;
396}
397
398FILE *
399ftpPutURL(char *url, char *user, char *passwd, int *retcode)
400{
401#ifdef INET6
402    return ftpPutURLAf(url, AF_UNSPEC, user, passwd, retcode);
403#else
404    return ftpPutURLAf(url, AF_INET, user, passwd, retcode);
405#endif
406
407}
408
409FILE *
410ftpPutURLAf(char *url, int af, char *user, char *passwd, int *retcode)
411{
412    char host[255], name[255];
413    int port;
414    static FILE *fp = NULL;
415    FILE *fp2;
416
417    if (retcode)
418	*retcode = 0;
419    if (fp) {	/* Close previous managed connection */
420	fclose(fp);
421	fp = NULL;
422    }
423    if (get_url_info(url, host, &port, name) == SUCCESS) {
424	fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
425	if (fp) {
426	    fp2 = ftpPut(fp, name);
427	    if (!fp2) {
428		if (retcode)
429		    *retcode = ftpErrno(fp);
430		fclose(fp);
431		fp = NULL;
432	    }
433	    return fp2;
434	}
435    }
436    return NULL;
437}
438
439/* Internal workhorse function for dissecting URLs.  Takes a URL as the first argument and returns the
440   result of such disection in the host, user, passwd, port and name variables. */
441static int
442get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret)
443{
444    char *name, *host, *cp, url[BUFSIZ];
445    int port;
446
447    name = host = NULL;
448    /* XXX add http:// here or somewhere reasonable at some point XXX */
449    if (strncmp("ftp://", url_in, 6) != 0)
450	return FAILURE;
451    /* We like to stomp a lot on the URL string in dissecting it, so copy it first */
452    strncpy(url, url_in, BUFSIZ);
453    host = url + 6;
454    if ((cp = index(host, ':')) != NULL) {
455	*(cp++) = '\0';
456	port = strtol(cp, 0, 0);
457    }
458    else
459	port = 0;	/* use default */
460    if (port_ret)
461	*port_ret = port;
462
463    if ((name = index(cp ? cp : host, '/')) != NULL)
464	*(name++) = '\0';
465    if (host_ret)
466	strcpy(host_ret, host);
467    if (name && name_ret)
468	strcpy(name_ret, name);
469    return SUCCESS;
470}
471
472static FTP_t
473ftp_new(void)
474{
475    FTP_t ftp;
476
477    ftp = (FTP_t)malloc(sizeof *ftp);
478    if (!ftp)
479	return NULL;
480    memset(ftp, 0, sizeof *ftp);
481    ftp->fd_ctrl = -1;
482    ftp->con_state = init;
483    ftp->is_binary = FALSE;
484    ftp->is_passive = FALSE;
485    ftp->is_verbose = FALSE;
486    ftp->error = 0;
487    return ftp;
488}
489
490static int
491ftp_read_method(void *vp, char *buf, int nbytes)
492{
493    int i, fd;
494    FTP_t n = (FTP_t)vp;
495
496    fd = n->fd_ctrl;
497    i = (fd >= 0) ? read(fd, buf, nbytes) : EOF;
498    return i;
499}
500
501static int
502ftp_write_method(void *vp, const char *buf, int nbytes)
503{
504    int i, fd;
505    FTP_t n = (FTP_t)vp;
506
507    fd = n->fd_ctrl;
508    i = (fd >= 0) ? write(fd, buf, nbytes) : EOF;
509    return i;
510}
511
512static int
513ftp_close_method(void *n)
514{
515    int i;
516
517    i = ftp_close((FTP_t)n);
518    free(n);
519    return i;
520}
521
522/*
523 * This function checks whether the FTP_PASSIVE_MODE environment
524 * variable is set, and, if so, enforces the desired mode.
525 */
526static void
527check_passive(FILE *fp)
528{
529    const char *cp = getenv("FTP_PASSIVE_MODE");
530
531    if (cp != NULL)
532    	ftpPassive(fp, strncasecmp(cp, "no", 2));
533}
534
535static void
536ftp_timeout(int sig)
537{
538    FtpTimedOut = TRUE;
539    /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */
540}
541
542static void
543ftp_set_timeout(void)
544{
545    struct sigaction new;
546    char *cp;
547    int ival;
548
549    FtpTimedOut = FALSE;
550    sigemptyset(&new.sa_mask);
551    new.sa_flags = 0;
552    new.sa_handler = ftp_timeout;
553    sigaction(SIGALRM, &new, NULL);
554    cp = getenv("FTP_TIMEOUT");
555    if (!cp || !(ival = atoi(cp)))
556	ival = 120;
557    alarm(ival);
558}
559
560static void
561ftp_clear_timeout(void)
562{
563    struct sigaction new;
564
565    alarm(0);
566    sigemptyset(&new.sa_mask);
567    new.sa_flags = 0;
568    new.sa_handler = SIG_DFL;
569    sigaction(SIGALRM, &new, NULL);
570}
571
572static int
573writes(int fd, char *s)
574{
575    int n, i = strlen(s);
576
577    ftp_set_timeout();
578    n = write(fd, s, i);
579    ftp_clear_timeout();
580    if (FtpTimedOut || i != n)
581	return TRUE;
582    return FALSE;
583}
584
585static __inline char *
586get_a_line(FTP_t ftp)
587{
588    static char buf[BUFSIZ];
589    int i,j;
590
591    /* Debug("ftp_pkg: trying to read a line from %d", ftp->fd_ctrl); */
592    for(i = 0; i < BUFSIZ;) {
593	ftp_set_timeout();
594	j = read(ftp->fd_ctrl, buf + i, 1);
595	ftp_clear_timeout();
596	if (FtpTimedOut || j != 1)
597	    return NULL;
598	if (buf[i] == '\r' || buf[i] == '\n') {
599	    if (!i)
600		continue;
601	    buf[i] = '\0';
602	    if (ftp->is_verbose == TRUE)
603		fprintf(stderr, "%s\n",buf+4);
604	    return buf;
605	}
606	i++;
607    }
608    /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */
609    return buf;
610}
611
612static int
613get_a_number(FTP_t ftp, char **q)
614{
615    char *p;
616    int i = -1, j;
617
618    while(1) {
619	p = get_a_line(ftp);
620	if (!p) {
621	    ftp_close(ftp);
622	    if (FtpTimedOut)
623		return FTP_TIMED_OUT;
624	    return FAILURE;
625	}
626	if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])))
627	    continue;
628	if (i == -1 && p[3] == '-') {
629	    i = strtol(p, 0, 0);
630	    continue;
631	}
632	if (p[3] != ' ' && p[3] != '\t')
633	    continue;
634	j = strtol(p, 0, 0);
635	if (i == -1) {
636	    if (q) *q = p+4;
637	    /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
638	    return j;
639	} else if (j == i) {
640	    if (q) *q = p+4;
641	    /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
642	    return j;
643	}
644    }
645}
646
647static int
648ftp_close(FTP_t ftp)
649{
650    int i, rcode;
651
652    rcode = FAILURE;
653    if (ftp->con_state == isopen) {
654	ftp->con_state = quit;
655	/* If last operation timed out, don't try to quit - just close */
656	if (ftp->error != FTP_TIMED_OUT)
657	    i = cmd(ftp, "QUIT");
658	else
659	    i = FTP_QUIT_HAPPY;
660	if (!check_code(ftp, i, FTP_QUIT_HAPPY))
661	    rcode = SUCCESS;
662	close(ftp->fd_ctrl);
663	ftp->fd_ctrl = -1;
664    }
665    else if (ftp->con_state == quit)
666	rcode = SUCCESS;
667    return rcode;
668}
669
670static int
671botch(char *func, char *botch_state)
672{
673    /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */
674    return FAILURE;
675}
676
677static int
678cmd(FTP_t ftp, const char *fmt, ...)
679{
680    char p[BUFSIZ];
681    int i;
682
683    va_list ap;
684    va_start(ap, fmt);
685    (void)vsnprintf(p, sizeof p, fmt, ap);
686    va_end(ap);
687
688    if (ftp->con_state == init)
689	return botch("cmd", "open");
690
691    strcat(p, "\r\n");
692    if (ftp->is_verbose)
693	fprintf(stderr, "Sending: %s", p);
694    if (writes(ftp->fd_ctrl, p)) {
695	if (FtpTimedOut)
696	    return FTP_TIMED_OUT;
697	return FAILURE;
698    }
699    while ((i = get_a_number(ftp, NULL)) == FTP_HAPPY_COMMENT);
700    return i;
701}
702
703static int
704ftp_login_session(FTP_t ftp, char *host, int af,
705		  char *user, char *passwd, int port, int verbose)
706{
707    char pbuf[10];
708    struct addrinfo	hints, *res, *res0;
709    int			err;
710    int 		s;
711    int			i;
712
713    if (networkInit() != SUCCESS)
714	return FAILURE;
715
716    if (ftp->con_state != init) {
717	ftp_close(ftp);
718	ftp->error = -1;
719	return FAILURE;
720    }
721
722    if (!user)
723	user = "ftp";
724
725    if (!passwd)
726	passwd = "setup@";
727
728    if (!port)
729	port = 21;
730
731    snprintf(pbuf, sizeof(pbuf), "%d", port);
732    memset(&hints, 0, sizeof(hints));
733    hints.ai_family = af;
734    hints.ai_socktype = SOCK_STREAM;
735    hints.ai_protocol = 0;
736    err = getaddrinfo(host, pbuf, &hints, &res0);
737    if (err) {
738	ftp->error = 0;
739	return FAILURE;
740    }
741
742    s = -1;
743    for (res = res0; res; res = res->ai_next) {
744	ai_unmapped(res);
745	ftp->addrtype = res->ai_family;
746
747	if ((s = socket(res->ai_family, res->ai_socktype,
748			res->ai_protocol)) < 0)
749	    continue;
750
751	if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
752	    (void)close(s);
753	    s = -1;
754	    continue;
755	}
756
757	break;
758    }
759    freeaddrinfo(res0);
760    if (s < 0) {
761	ftp->error = errno;
762	return FAILURE;
763    }
764
765    ftp->fd_ctrl = s;
766    ftp->con_state = isopen;
767    ftp->is_verbose = verbose;
768
769    i = cmd(ftp, "USER %s", user);
770    if (i >= 300 && i < 400)
771	i = cmd(ftp, "PASS %s", passwd);
772    if (i >= 299 || i < 0) {
773	ftp_close(ftp);
774	if (i > 0)
775	    ftp->error = i;
776	return FAILURE;
777    }
778    return SUCCESS;
779}
780
781static int
782ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto)
783{
784    socklen_t sinlen;
785    int i,l,s;
786    char *q;
787    unsigned char addr[64];
788    union sockaddr_cmn {
789	struct sockaddr_in sin4;
790	struct sockaddr_in6 sin6;
791    } sin;
792    char *cmdstr;
793
794    if (!fp)
795	return FAILURE;
796    *fp = NULL;
797
798    if (ftp->con_state != isopen)
799	return botch("ftp_file_op", "open");
800
801    if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) {
802	ftp->error = errno;
803	return FAILURE;
804    }
805
806    if (ftp->is_passive) {
807	if (ftp->addrtype == AF_INET) {
808            if (ftp->is_verbose)
809		fprintf(stderr, "Sending PASV\n");
810	    if (writes(ftp->fd_ctrl, "PASV\r\n")) {
811		ftp_close(ftp);
812		if (FtpTimedOut)
813		    ftp->error = FTP_TIMED_OUT;
814		return FTP_TIMED_OUT;
815	    }
816	    i = get_a_number(ftp, &q);
817	    if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) {
818		ftp_close(ftp);
819		return i;
820	    }
821	    cmdstr = "PASV";
822	} else {
823            if (ftp->is_verbose)
824		fprintf(stderr, "Sending EPSV\n");
825	    if (writes(ftp->fd_ctrl, "EPSV\r\n")) {
826		ftp_close(ftp);
827		if (FtpTimedOut)
828		    ftp->error = FTP_TIMED_OUT;
829		return FTP_TIMED_OUT;
830	    }
831	    i = get_a_number(ftp, &q);
832	    if (check_code(ftp, i, FTP_EPASSIVE_HAPPY)) {
833		if (ftp->is_verbose)
834		    fprintf(stderr, "Sending LPSV\n");
835		if (writes(ftp->fd_ctrl, "LPSV\r\n")) {
836		    ftp_close(ftp);
837		    if (FtpTimedOut)
838			ftp->error = FTP_TIMED_OUT;
839		    return FTP_TIMED_OUT;
840		}
841		i = get_a_number(ftp, &q);
842		if (check_code(ftp, i, FTP_LPASSIVE_HAPPY)) {
843		    ftp_close(ftp);
844		    return i;
845		}
846		cmdstr = "LPSV";
847	    } else
848		cmdstr = "EPSV";
849	}
850	if (strcmp(cmdstr, "PASV") == 0 || strcmp(cmdstr, "LPSV") == 0) {
851	    while (*q && !isdigit(*q))
852		q++;
853	    if (!*q) {
854		ftp_close(ftp);
855		return FAILURE;
856	    }
857	    q--;
858	    l = (ftp->addrtype == AF_INET ? 6 : 21);
859	    for (i = 0; i < l; i++) {
860		q++;
861		addr[i] = strtol(q, &q, 10);
862	    }
863
864	    sin.sin4.sin_family = ftp->addrtype;
865	    if (ftp->addrtype == AF_INET6) {
866		sin.sin6.sin6_len = sizeof(struct sockaddr_in6);
867		bcopy(addr + 2, (char *)&sin.sin6.sin6_addr, 16);
868		bcopy(addr + 19, (char *)&sin.sin6.sin6_port, 2);
869	    } else {
870		sin.sin4.sin_len = sizeof(struct sockaddr_in);
871		bcopy(addr, (char *)&sin.sin4.sin_addr, 4);
872		bcopy(addr + 4, (char *)&sin.sin4.sin_port, 2);
873	    }
874	} else if (strcmp(cmdstr, "EPSV") == 0) {
875	    int port;
876	    while (*q && *q != '(')		/* ) */
877		q++;
878	    if (!*q) {
879		ftp_close(ftp);
880		return FAILURE;
881	    }
882	    q++;
883	    if (sscanf(q, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
884		    &port, &addr[3]) != 5
885	     || addr[0] != addr[1] || addr[0] != addr[2] || addr[0] != addr[3]) {
886		ftp_close(ftp);
887		return FAILURE;
888	    }
889	    sinlen = sizeof(sin);
890	    if (getpeername(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen) < 0) {
891		ftp_close(ftp);
892		return FAILURE;
893	    }
894	    switch (sin.sin4.sin_family) {
895	    case AF_INET:
896		sin.sin4.sin_port = htons(port);
897		break;
898	    case AF_INET6:
899		sin.sin6.sin6_port = htons(port);
900		break;
901	    default:
902		ftp_close(ftp);
903		return FAILURE;
904	    }
905	}
906
907	if (connect(s, (struct sockaddr *)&sin, sin.sin4.sin_len) < 0) {
908	    (void)close(s);
909	    return FAILURE;
910	}
911
912	if (seekto && *seekto) {
913	    i = cmd(ftp, "REST %d", *seekto);
914	    if (i < 0 || FTP_TIMEOUT(i)) {
915		close(s);
916		ftp->error = i;
917		*seekto = (off_t)0;
918		return i;
919	    }
920	}
921	i = cmd(ftp, "%s %s", operation, file);
922	if (i < 0 || i > 299) {
923	    close(s);
924	    ftp->error = i;
925	    return i;
926	}
927	*fp = fdopen(s, mode);
928    }
929    else {
930	int fd,portrange;
931
932#ifdef IPV6_PORTRANGE
933	if (ftp->addrtype == AF_INET6) {
934		portrange = IPV6_PORTRANGE_HIGH;
935		if (setsockopt(s, IPPROTO_IPV6, IPV6_PORTRANGE, (char *)
936			       &portrange, sizeof(portrange)) < 0) {
937			close(s);
938			return FAILURE;
939		}
940	}
941#endif
942#ifdef IP_PORTRANGE
943	if (ftp->addrtype == AF_INET) {
944		portrange = IP_PORTRANGE_HIGH;
945		if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, (char *)
946			       &portrange, sizeof(portrange)) < 0) {
947			close(s);
948			return FAILURE;
949		}
950	}
951#endif
952
953	sinlen = sizeof sin;
954	getsockname(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen);
955	sin.sin4.sin_port = 0;
956	i = ((struct sockaddr *)&sin)->sa_len;
957	if (bind(s, (struct sockaddr *)&sin, i) < 0) {
958	    close(s);
959	    return FAILURE;
960	}
961	sinlen = sizeof sin;
962	getsockname(s, (struct sockaddr *)&sin, &sinlen);
963	if (listen(s, 1) < 0) {
964	    close(s);
965	    return FAILURE;
966	}
967	if (sin.sin4.sin_family == AF_INET) {
968            u_long a;
969	    a = ntohl(sin.sin4.sin_addr.s_addr);
970	    i = cmd(ftp, "PORT %d,%d,%d,%d,%d,%d",
971		    (a                   >> 24) & 0xff,
972		    (a                   >> 16) & 0xff,
973		    (a                   >>  8) & 0xff,
974		    a                           & 0xff,
975		    (ntohs(sin.sin4.sin_port) >>  8) & 0xff,
976		    ntohs(sin.sin4.sin_port)         & 0xff);
977	    if (check_code(ftp, i, FTP_PORT_HAPPY)) {
978		close(s);
979		return i;
980	    }
981	} else {
982#define UC(b)	(((int)b)&0xff)
983	    char *a;
984	    char hname[INET6_ADDRSTRLEN];
985
986	    sin.sin6.sin6_scope_id = 0;
987	    if (getnameinfo((struct sockaddr *)&sin, sin.sin6.sin6_len,
988			    hname, sizeof(hname),
989			    NULL, 0, NI_NUMERICHOST) != 0) {
990		goto try_lprt;
991	    }
992	    i = cmd(ftp, "EPRT |%d|%s|%d|", 2, hname,
993		    htons(sin.sin6.sin6_port));
994	    if (check_code(ftp, i, FTP_PORT_HAPPY)) {
995try_lprt:
996		a = (char *)&sin.sin6.sin6_addr;
997		i = cmd(ftp,
998"LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
999			6, 16,
1000			UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]),
1001			UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]),
1002			UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]),
1003			UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]),
1004			2,
1005			(ntohs(sin.sin4.sin_port) >>  8) & 0xff,
1006			ntohs(sin.sin4.sin_port)         & 0xff);
1007		if (check_code(ftp, i, FTP_PORT_HAPPY)) {
1008		    close(s);
1009		    return i;
1010		}
1011	    }
1012	}
1013	if (seekto && *seekto) {
1014	    i = cmd(ftp, "REST %d", *seekto);
1015	    if (i < 0 || FTP_TIMEOUT(i)) {
1016		close(s);
1017		ftp->error = i;
1018		return i;
1019	    }
1020	    else if (i != 350)
1021		*seekto = (off_t)0;
1022	}
1023	i = cmd(ftp, "%s %s", operation, file);
1024	if (i < 0 || i > 299) {
1025	    close(s);
1026	    ftp->error = i;
1027	    return FAILURE;
1028	}
1029	fd = accept(s, 0, 0);
1030	if (fd < 0) {
1031	    close(s);
1032	    ftp->error = 401;
1033	    return FAILURE;
1034	}
1035	close(s);
1036	*fp = fdopen(fd, mode);
1037    }
1038    if (*fp)
1039	return SUCCESS;
1040    else
1041	return FAILURE;
1042}
1043
1044static void
1045ai_unmapped(struct addrinfo *ai)
1046{
1047	struct sockaddr_in6 *sin6;
1048	struct sockaddr_in sin;
1049
1050	if (ai->ai_family != AF_INET6)
1051		return;
1052	if (ai->ai_addrlen != sizeof(struct sockaddr_in6) ||
1053	    sizeof(sin) > ai->ai_addrlen)
1054		return;
1055	sin6 = (struct sockaddr_in6 *)ai->ai_addr;
1056	if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
1057		return;
1058
1059	memset(&sin, 0, sizeof(sin));
1060	sin.sin_family = AF_INET;
1061	sin.sin_len = sizeof(struct sockaddr_in);
1062	memcpy(&sin.sin_addr, &sin6->sin6_addr.s6_addr[12],
1063	    sizeof(sin.sin_addr));
1064	sin.sin_port = sin6->sin6_port;
1065
1066	ai->ai_family = AF_INET;
1067	memcpy(ai->ai_addr, &sin, sin.sin_len);
1068	ai->ai_addrlen = sin.sin_len;
1069}
1070