Remove tm.h and xm.h handling, as it wasn't used. Use nm.h only when needed.
[dragonfly.git] / lib / libftpio / ftpio.c
blob1c8e49472821fa6ac368b9091eba28d87af7ec22
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 * ----------------------------------------------------------------------------
9 * Major Changelog:
11 * Jordan K. Hubbard
12 * 17 Jan 1996
14 * Turned inside out. Now returns xfers as new file ids, not as a special
15 * `state' of FTP_t
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 $
22 #include <sys/param.h>
23 #include <sys/socket.h>
25 #include <netinet/in.h>
27 #include <arpa/inet.h>
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>
40 #define SUCCESS 0
41 #define FAILURE -1
43 #ifndef TRUE
44 #define TRUE (1)
45 #define FALSE (0)
46 #endif
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)
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 *);
76 int networkInit(void);
79 /* Global status variable - ick */
80 int FtpTimedOut;
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
95 /* FTP unhappy status codes */
96 #define FTP_TIMED_OUT 421
98 /* Placeholder in case we want to do any pre-init stuff at some point */
99 int
100 networkInit(void)
102 return SUCCESS; /* XXX dummy function for now XXX */
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)
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;
127 ftpAscii(FILE *fp)
129 FTP_t ftp = fcookie(fp);
130 int i;
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;
142 ftpBinary(FILE *fp)
144 FTP_t ftp = fcookie(fp);
145 int i;
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;
155 void
156 ftpVerbose(FILE *fp, int status)
158 FTP_t ftp = fcookie(fp);
159 ftp->is_verbose = status;
163 ftpChdir(FILE *fp, char *dir)
165 int i;
166 FTP_t ftp = fcookie(fp);
168 i = cmd(ftp, "CWD %s", dir);
169 if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY))
170 return i;
171 return SUCCESS;
175 ftpErrno(FILE *fp)
177 FTP_t ftp = fcookie(fp);
178 return ftp->error;
181 const char *
182 ftpErrString(int error)
184 int k;
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");
197 off_t
198 ftpGetSize(FILE *fp, const char *name)
200 int i;
201 char p[BUFSIZ], *cp, *ep;
202 FTP_t ftp = fcookie(fp);
203 off_t size;
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);
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;
223 time_t
224 ftpGetModtime(FILE *fp, const char *name)
226 char p[BUFSIZ], *cp;
227 struct tm t;
228 time_t t0 = time (0);
229 FTP_t ftp = fcookie(fp);
230 int i;
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;
256 FILE *
257 ftpGet(FILE *fp, const char *file, off_t *seekto)
259 FILE *fp2;
260 FTP_t ftp = fcookie(fp);
262 check_passive(fp);
263 if (ftpBinary(fp) != SUCCESS)
264 return NULL;
266 if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS)
267 return fp2;
268 return NULL;
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)
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
283 FILE *
284 ftpLoginAf(const char *host, int af, const char *user, const char *passwd,
285 int port, int verbose, int *retcode)
287 FTP_t n;
288 FILE *fp;
290 if (retcode)
291 *retcode = 0;
292 if (networkInit() != SUCCESS)
293 return NULL;
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! */
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;
309 case ETIMEDOUT:
310 *retcode = FTP_TIMED_OUT;
311 break;
313 default:
314 *retcode = n->error;
315 break;
318 return fp;
321 FILE *
322 ftpPut(FILE *fp, const char *file)
324 FILE *fp2;
325 FTP_t ftp = fcookie(fp);
327 check_passive(fp);
328 if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS)
329 return fp2;
330 return NULL;
334 ftpPassive(FILE *fp, int st)
336 FTP_t ftp = fcookie(fp);
338 ftp->is_passive = !!st; /* normalize "st" to zero or one */
339 return SUCCESS;
342 FILE *
343 ftpGetURL(const char *url, const char *user, const char *passwd, int *retcode)
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
352 FILE *
353 ftpGetURLAf(const char *url, int af, const char *user, const char *passwd,
354 int *retcode)
356 char host[255], name[255];
357 int port;
358 FILE *fp2;
359 static FILE *fp = NULL;
360 static char *prev_host;
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;
376 else
377 return fp2;
379 else {
380 /* It's a different host now, flush old */
381 fclose(fp);
382 free(prev_host);
383 prev_host = NULL;
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;
396 else
397 prev_host = strdup(host);
398 return fp2;
401 return NULL;
404 FILE *
405 ftpPutURL(const char *url, const char *user, const char *passwd, int *retcode)
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
415 FILE *
416 ftpPutURLAf(const char *url, int af, const char *user, const char *passwd,
417 int *retcode)
419 char host[255], name[255];
420 int port;
421 static FILE *fp = NULL;
422 FILE *fp2;
424 if (retcode)
425 *retcode = 0;
426 if (fp) { /* Close previous managed connection */
427 fclose(fp);
428 fp = NULL;
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;
441 return fp2;
444 return NULL;
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)
453 char *name, *host, *cp, url[BUFSIZ];
454 int port;
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);
467 else
468 port = 0; /* use default */
469 if (port_ret)
470 *port_ret = port;
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);
479 if (name && name_ret) {
480 if (strlen(name) >= name_len)
481 return FAILURE;
482 strcpy(name_ret, name);
484 return SUCCESS;
487 static FTP_t
488 ftp_new(void)
490 FTP_t ftp;
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;
505 static int
506 ftp_read_method(void *vp, char *buf, int nbytes)
508 int i, fd;
509 FTP_t n = (FTP_t)vp;
511 fd = n->fd_ctrl;
512 i = (fd >= 0) ? read(fd, buf, nbytes) : EOF;
513 return i;
516 static int
517 ftp_write_method(void *vp, const char *buf, int nbytes)
519 int i, fd;
520 FTP_t n = (FTP_t)vp;
522 fd = n->fd_ctrl;
523 i = (fd >= 0) ? write(fd, buf, nbytes) : EOF;
524 return i;
527 static int
528 ftp_close_method(void *n)
530 int i;
532 i = ftp_close((FTP_t)n);
533 free(n);
534 return i;
538 * This function checks whether the FTP_PASSIVE_MODE environment
539 * variable is set, and, if so, enforces the desired mode.
541 static void
542 check_passive(FILE *fp)
544 const char *cp = getenv("FTP_PASSIVE_MODE");
546 if (cp != NULL)
547 ftpPassive(fp, strncasecmp(cp, "no", 2));
550 static void
551 ftp_timeout(int sig __unused)
553 FtpTimedOut = TRUE;
554 /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */
557 static void
558 ftp_set_timeout(void)
560 struct sigaction new;
561 char *cp;
562 int ival;
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);
575 static void
576 ftp_clear_timeout(void)
578 struct sigaction new;
580 alarm(0);
581 sigemptyset(&new.sa_mask);
582 new.sa_flags = 0;
583 new.sa_handler = SIG_DFL;
584 sigaction(SIGALRM, &new, NULL);
587 static int
588 writes(int fd, const char *s)
590 int n, i = strlen(s);
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;
600 static char *
601 get_a_line(FTP_t ftp)
603 static char buf[BUFSIZ];
604 int i,j;
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;
621 i++;
623 /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */
624 return buf;
627 static int
628 get_a_number(FTP_t ftp, char **q)
630 char *p;
631 int i = -1, j;
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;
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;
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;
662 static int
663 ftp_close(FTP_t ftp)
665 int i, rcode;
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;
680 else if (ftp->con_state == quit)
681 rcode = SUCCESS;
682 return rcode;
685 static int
686 botch(const char *func __unused, const char *botch_state __unused)
688 /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */
689 return FAILURE;
692 static int
693 cmd(FTP_t ftp, const char *fmt, ...)
695 char p[BUFSIZ];
696 int i;
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);
704 if (ftp->con_state == init)
705 return botch("cmd", "open");
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;
715 while ((i = get_a_number(ftp, NULL)) == FTP_HAPPY_COMMENT);
716 return i;
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)
723 char pbuf[10];
724 struct addrinfo hints, *res, *res0;
725 int err;
726 int s;
727 int i;
729 if (networkInit() != SUCCESS)
730 return FAILURE;
732 if (ftp->con_state != init) {
733 ftp_close(ftp);
734 ftp->error = -1;
735 return FAILURE;
738 if (!user)
739 user = "ftp";
741 if (!passwd)
742 passwd = "setup@";
744 if (!port)
745 port = 21;
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;
759 s = -1;
760 for (res = res0; res; res = res->ai_next) {
761 ai_unmapped(res);
762 ftp->addrtype = res->ai_family;
764 if ((s = socket(res->ai_family, res->ai_socktype,
765 res->ai_protocol)) < 0)
766 continue;
768 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
769 close(s);
770 s = -1;
771 continue;
774 break;
776 freeaddrinfo(res0);
777 if (s < 0) {
778 ftp->error = errno;
779 return FAILURE;
782 ftp->fd_ctrl = s;
783 ftp->con_state = isopen;
784 ftp->is_verbose = verbose;
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;
795 return SUCCESS;
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)
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;
811 if (!fp)
812 return FAILURE;
813 *fp = NULL;
815 if (ftp->con_state != isopen)
816 return botch("ftp_file_op", "open");
818 if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) {
819 ftp->error = errno;
820 return FAILURE;
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;
833 i = get_a_number(ftp, &q);
834 if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) {
835 ftp_close(ftp);
836 return i;
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;
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;
858 i = get_a_number(ftp, &q);
859 if (check_code(ftp, i, FTP_LPASSIVE_HAPPY)) {
860 ftp_close(ftp);
861 return i;
863 cmdstr = "LPSV";
864 } else
865 cmdstr = "EPSV";
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;
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);
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);
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;
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;
907 sinlen = sizeof(sin);
908 if (getpeername(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen) < 0) {
909 ftp_close(ftp);
910 return FAILURE;
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;
925 if (connect(s, (struct sockaddr *)&sin, sin.sin4.sin_len) < 0) {
926 (void)close(s);
927 return FAILURE;
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;
939 i = cmd(ftp, "%s %s", operation, file);
940 if (i < 0 || i > 299) {
941 close(s);
942 ftp->error = i;
943 return i;
945 *fp = fdopen(s, mode);
947 else {
948 int fd,portrange;
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;
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;
969 #endif
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;
979 i = sizeof sin;
980 getsockname(s,(struct sockaddr *)&sin,&i);
981 if (listen(s, 1) < 0) {
982 close(s);
983 return FAILURE;
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;
999 } else {
1000 #define UC(b) (((int)b)&0xff)
1001 char *a;
1002 char hname[INET6_ADDRSTRLEN];
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;
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]),
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;
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;
1038 else if (i != 350)
1039 *seekto = (off_t)0;
1041 i = cmd(ftp, "%s %s", operation, file);
1042 if (i < 0 || i > 299) {
1043 close(s);
1044 ftp->error = i;
1045 return FAILURE;
1047 fd = accept(s, 0, 0);
1048 if (fd < 0) {
1049 close(s);
1050 ftp->error = 401;
1051 return FAILURE;
1053 close(s);
1054 *fp = fdopen(fd, mode);
1056 if (*fp)
1057 return SUCCESS;
1058 else
1059 return FAILURE;
1062 static void
1063 ai_unmapped(struct addrinfo *ai)
1065 struct sockaddr_in6 *sin6;
1066 struct sockaddr_in sin;
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;
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;
1084 ai->ai_family = AF_INET;
1085 memcpy(ai->ai_addr, &sin, sin.sin_len);
1086 ai->ai_addrlen = sin.sin_len;