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