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