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