]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - lib/libftpio/ftpio.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / lib / libftpio / ftpio.c
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 */
51 static FTP_t    ftp_new(void);
52 static void     check_passive(FILE *fp);
53 static int      ftp_read_method(void *n, char *buf, int nbytes);
54 static int      ftp_write_method(void *n, const char *buf, int nbytes);
55 static int      ftp_close_method(void *n);
56 static int      writes(int fd, char *s);
57 static __inline char *get_a_line(FTP_t ftp);
58 static int      get_a_number(FTP_t ftp, char **q);
59 static int      botch(char *func, char *botch_state);
60 static int      cmd(FTP_t ftp, const char *fmt, ...);
61 static int      ftp_login_session(FTP_t ftp, char *host, int af, char *user, char *passwd, int port, int verbose);
62 static int      ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto);
63 static int      ftp_close(FTP_t ftp);
64 static int      get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret);
65 static void     ftp_timeout(int sig);
66 static void     ftp_set_timeout(void);
67 static void     ftp_clear_timeout(void);
68 static void     ai_unmapped(struct addrinfo *);
69
70
71 /* Global status variable - ick */
72 int 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 */ 
99 int
100 networkInit()
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, 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
222 time_t
223 ftpGetModtime(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
254 FILE *
255 ftpGet(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 */
270 FILE *
271 ftpLogin(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
280 FILE *
281 ftpLoginAf(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
317 FILE *
318 ftpPut(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
329 int
330 ftpPassive(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
338 FILE *
339 ftpGetURL(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
348 FILE *
349 ftpGetURLAf(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
398 FILE *
399 ftpPutURL(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
409 FILE *
410 ftpPutURLAf(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. */
441 static int
442 get_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
472 static FTP_t
473 ftp_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
490 static int
491 ftp_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
501 static int
502 ftp_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
512 static int
513 ftp_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  */
526 static void
527 check_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
535 static void
536 ftp_timeout(int sig)
537 {
538     FtpTimedOut = TRUE;
539     /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */
540 }
541
542 static void
543 ftp_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
560 static void
561 ftp_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
572 static int
573 writes(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
585 static __inline char *
586 get_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
612 static int
613 get_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
647 static int
648 ftp_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
670 static int
671 botch(char *func, char *botch_state)
672 {
673     /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */
674     return FAILURE;
675 }
676
677 static int
678 cmd(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
703 static int
704 ftp_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
781 static int
782 ftp_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)) {
995 try_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
1044 static void
1045 ai_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 }