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