/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * Major Changelog: * * Jordan K. Hubbard * 17 Jan 1996 * * Turned inside out. Now returns xfers as new file ids, not as a special * `state' of FTP_t * * $FreeBSD: src/lib/libftpio/ftpio.c,v 1.33.2.4 2002/07/25 15:25:32 ume Exp $ * $DragonFly: src/lib/libftpio/ftpio.c,v 1.8 2005/07/23 20:23:06 joerg Exp $ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUCCESS 0 #define FAILURE -1 #ifndef TRUE #define TRUE (1) #define FALSE (0) #endif /* How to see by a given code whether or not the connection has timed out */ #define FTP_TIMEOUT(code) (FtpTimedOut || code == FTP_TIMED_OUT) /* Internal routines - deal only with internal FTP_t type */ static FTP_t ftp_new(void); static void check_passive(FILE *fp); static int ftp_read_method(void *n, char *buf, int nbytes); static int ftp_write_method(void *n, const char *buf, int nbytes); static int ftp_close_method(void *n); static int writes(int fd, const char *s); static char *get_a_line(FTP_t ftp); static int get_a_number(FTP_t ftp, char **q); static int botch(const char *func, const char *botch_state); static int cmd(FTP_t ftp, const char *fmt, ...) __printflike(2, 3); static int ftp_login_session(FTP_t ftp, const char *host, int af, const char *user, const char *passwd, int port, int verbose); static int ftp_file_op(FTP_t ftp, const char *operation, const char *file, FILE **fp, const char *mode, off_t *seekto); static int ftp_close(FTP_t ftp); static int get_url_info(const char *url_in, char *host_ret, size_t host_len, int *port_ret, char *name_ret, size_t name_len); static void ftp_timeout(int sig); static void ftp_set_timeout(void); static void ftp_clear_timeout(void); static void ai_unmapped(struct addrinfo *); int networkInit(void); /* Global status variable - ick */ int FtpTimedOut; /* FTP happy status codes */ #define FTP_GENERALLY_HAPPY 200 #define FTP_ASCII_HAPPY FTP_GENERALLY_HAPPY #define FTP_BINARY_HAPPY FTP_GENERALLY_HAPPY #define FTP_PORT_HAPPY FTP_GENERALLY_HAPPY #define FTP_HAPPY_COMMENT 220 #define FTP_QUIT_HAPPY 221 #define FTP_TRANSFER_HAPPY 226 #define FTP_PASSIVE_HAPPY 227 #define FTP_LPASSIVE_HAPPY 228 #define FTP_EPASSIVE_HAPPY 229 #define FTP_CHDIR_HAPPY 250 /* FTP unhappy status codes */ #define FTP_TIMED_OUT 421 /* Placeholder in case we want to do any pre-init stuff at some point */ int networkInit(void) { return SUCCESS; /* XXX dummy function for now XXX */ } /* Check a return code with some lenience for back-dated garbage that might be in the buffer */ static int check_code(FTP_t ftp, int var, int preferred) { ftp->error = 0; while (1) { if (var == preferred) return 0; else if (var == FTP_TRANSFER_HAPPY) /* last operation succeeded */ var = get_a_number(ftp, NULL); else if (var == FTP_HAPPY_COMMENT) /* chit-chat */ var = get_a_number(ftp, NULL); else if (var == FTP_GENERALLY_HAPPY) /* general success code */ var = get_a_number(ftp, NULL); else { ftp->error = var; return 1; } } } int ftpAscii(FILE *fp) { FTP_t ftp = fcookie(fp); int i; if (!ftp->is_binary) return SUCCESS; i = cmd(ftp, "TYPE A"); if (i < 0 || check_code(ftp, i, FTP_ASCII_HAPPY)) return i; ftp->is_binary = FALSE; return SUCCESS; } int ftpBinary(FILE *fp) { FTP_t ftp = fcookie(fp); int i; if (ftp->is_binary) return SUCCESS; i = cmd(ftp, "TYPE I"); if (i < 0 || check_code(ftp, i, FTP_BINARY_HAPPY)) return i; ftp->is_binary = TRUE; return SUCCESS; } void ftpVerbose(FILE *fp, int status) { FTP_t ftp = fcookie(fp); ftp->is_verbose = status; } int ftpChdir(FILE *fp, char *dir) { int i; FTP_t ftp = fcookie(fp); i = cmd(ftp, "CWD %s", dir); if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY)) return i; return SUCCESS; } int ftpErrno(FILE *fp) { FTP_t ftp = fcookie(fp); return ftp->error; } const char * ftpErrString(int error) { int k; if (error == -1) return("connection in wrong state"); if (error < 100) /* XXX soon UNIX errnos will catch up with FTP protocol errnos */ return strerror(error); for (k = 0; k < ftpErrListLength; k++) if (ftpErrList[k].num == error) return(ftpErrList[k].string); return("Unknown error"); } off_t ftpGetSize(FILE *fp, const char *name) { int i; char p[BUFSIZ], *cp, *ep; FTP_t ftp = fcookie(fp); off_t size; check_passive(fp); if ((size_t)snprintf(p, sizeof(p), "SIZE %s\r\n", name) >= sizeof(p)) return (off_t)(-1); if (ftp->is_verbose) fprintf(stderr, "Sending %s", p); if (writes(ftp->fd_ctrl, p)) return (off_t)(-1); i = get_a_number(ftp, &cp); if (check_code(ftp, i, 213)) return (off_t)(-1); errno = 0; /* to check for ERANGE */ size = (off_t)strtoq(cp, &ep, 10); if (*ep != '\0' || errno == ERANGE) return (off_t)(-1); return size; } time_t ftpGetModtime(FILE *fp, const char *name) { char p[BUFSIZ], *cp; struct tm t; time_t t0 = time (0); FTP_t ftp = fcookie(fp); int i; check_passive(fp); if ((size_t)snprintf(p, sizeof(p), "MDTM %s\r\n", name) >= sizeof(p)) return (time_t)0; if (ftp->is_verbose) fprintf(stderr, "Sending %s", p); if (writes(ftp->fd_ctrl, p)) return (time_t)0; i = get_a_number(ftp, &cp); if (check_code(ftp, i, 213)) return (time_t)0; while (*cp && !isdigit(*cp)) cp++; if (!*cp) return (time_t)0; t0 = localtime (&t0)->tm_gmtoff; 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); t.tm_mon--; t.tm_year -= 1900; t.tm_isdst=-1; t.tm_gmtoff = 0; t0 += mktime (&t); return t0; } FILE * ftpGet(FILE *fp, const char *file, off_t *seekto) { FILE *fp2; FTP_t ftp = fcookie(fp); check_passive(fp); if (ftpBinary(fp) != SUCCESS) return NULL; if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS) return fp2; return NULL; } /* Returns a standard FILE pointer type representing an open control connection */ FILE * ftpLogin(const char *host, const char *user, const char *passwd, int port, int verbose, int *retcode) { #ifdef INET6 return ftpLoginAf(host, AF_UNSPEC, user, passwd, port, verbose, retcode); #else return ftpLoginAf(host, AF_INET, user, passwd, port, verbose, retcode); #endif } FILE * ftpLoginAf(const char *host, int af, const char *user, const char *passwd, int port, int verbose, int *retcode) { FTP_t n; FILE *fp; if (retcode) *retcode = 0; if (networkInit() != SUCCESS) return NULL; n = ftp_new(); fp = NULL; if (n && ftp_login_session(n, host, af, user, passwd, port, verbose) == SUCCESS) { fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method); /* BSD 4.4 function! */ } if (retcode) { if (!n) *retcode = (FtpTimedOut ? FTP_TIMED_OUT : -1); /* Poor attempt at mapping real errnos to FTP error codes */ else switch(n->error) { case EADDRNOTAVAIL: *retcode = FTP_TIMED_OUT; /* Actually no such host, but we have no way of saying that. :-( */ break; case ETIMEDOUT: *retcode = FTP_TIMED_OUT; break; default: *retcode = n->error; break; } } return fp; } FILE * ftpPut(FILE *fp, const char *file) { FILE *fp2; FTP_t ftp = fcookie(fp); check_passive(fp); if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS) return fp2; return NULL; } int ftpPassive(FILE *fp, int st) { FTP_t ftp = fcookie(fp); ftp->is_passive = !!st; /* normalize "st" to zero or one */ return SUCCESS; } FILE * ftpGetURL(const char *url, const char *user, const char *passwd, int *retcode) { #ifdef INET6 return ftpGetURLAf(url, AF_UNSPEC, user, passwd, retcode); #else return ftpGetURLAf(url, AF_INET, user, passwd, retcode); #endif } FILE * ftpGetURLAf(const char *url, int af, const char *user, const char *passwd, int *retcode) { char host[255], name[255]; int port; FILE *fp2; static FILE *fp = NULL; static char *prev_host; if (retcode) *retcode = 0; if (get_url_info(url, host, sizeof(host), &port, name, sizeof(name)) == SUCCESS) { if (fp && prev_host) { if (!strcmp(prev_host, host)) { /* Try to use cached connection */ fp2 = ftpGet(fp, name, NULL); if (!fp2) { /* Connection timed out or was no longer valid */ fclose(fp); free(prev_host); prev_host = NULL; } else return fp2; } else { /* It's a different host now, flush old */ fclose(fp); free(prev_host); prev_host = NULL; } } fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode); if (fp) { fp2 = ftpGet(fp, name, NULL); if (!fp2) { /* Connection timed out or was no longer valid */ if (retcode) *retcode = ftpErrno(fp); fclose(fp); fp = NULL; } else prev_host = strdup(host); return fp2; } } return NULL; } FILE * ftpPutURL(const char *url, const char *user, const char *passwd, int *retcode) { #ifdef INET6 return ftpPutURLAf(url, AF_UNSPEC, user, passwd, retcode); #else return ftpPutURLAf(url, AF_INET, user, passwd, retcode); #endif } FILE * ftpPutURLAf(const char *url, int af, const char *user, const char *passwd, int *retcode) { char host[255], name[255]; int port; static FILE *fp = NULL; FILE *fp2; if (retcode) *retcode = 0; if (fp) { /* Close previous managed connection */ fclose(fp); fp = NULL; } if (get_url_info(url, host, sizeof(host), &port, name, sizeof(name)) == SUCCESS) { fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode); if (fp) { fp2 = ftpPut(fp, name); if (!fp2) { if (retcode) *retcode = ftpErrno(fp); fclose(fp); fp = NULL; } return fp2; } } return NULL; } /* Internal workhorse function for dissecting URLs. Takes a URL as the first argument and returns the result of such disection in the host, user, passwd, port and name variables. */ static int get_url_info(const char *url_in, char *host_ret, size_t host_len, int *port_ret, char *name_ret, size_t name_len) { char *name, *host, *cp, url[BUFSIZ]; int port; name = host = NULL; /* XXX add http:// here or somewhere reasonable at some point XXX */ if (strncmp("ftp://", url_in, 6) != 0) return FAILURE; /* We like to stomp a lot on the URL string in dissecting it, so copy it first */ strncpy(url, url_in, BUFSIZ); host = url + 6; if ((cp = index(host, ':')) != NULL) { *(cp++) = '\0'; port = strtol(cp, 0, 0); } else port = 0; /* use default */ if (port_ret) *port_ret = port; if ((name = index(cp ? cp : host, '/')) != NULL) *(name++) = '\0'; if (host_ret) { if (strlen(host) >= host_len) return FAILURE; strcpy(host_ret, host); } if (name && name_ret) { if (strlen(name) >= name_len) return FAILURE; strcpy(name_ret, name); } return SUCCESS; } static FTP_t ftp_new(void) { FTP_t ftp; ftp = (FTP_t)malloc(sizeof *ftp); if (!ftp) return NULL; memset(ftp, 0, sizeof *ftp); ftp->fd_ctrl = -1; ftp->con_state = init; ftp->is_binary = FALSE; ftp->is_passive = FALSE; ftp->is_verbose = FALSE; ftp->error = 0; return ftp; } static int ftp_read_method(void *vp, char *buf, int nbytes) { int i, fd; FTP_t n = (FTP_t)vp; fd = n->fd_ctrl; i = (fd >= 0) ? read(fd, buf, nbytes) : EOF; return i; } static int ftp_write_method(void *vp, const char *buf, int nbytes) { int i, fd; FTP_t n = (FTP_t)vp; fd = n->fd_ctrl; i = (fd >= 0) ? write(fd, buf, nbytes) : EOF; return i; } static int ftp_close_method(void *n) { int i; i = ftp_close((FTP_t)n); free(n); return i; } /* * This function checks whether the FTP_PASSIVE_MODE environment * variable is set, and, if so, enforces the desired mode. */ static void check_passive(FILE *fp) { const char *cp = getenv("FTP_PASSIVE_MODE"); if (cp != NULL) ftpPassive(fp, strncasecmp(cp, "no", 2)); } static void ftp_timeout(int sig __unused) { FtpTimedOut = TRUE; /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */ } static void ftp_set_timeout(void) { struct sigaction new; char *cp; int ival; FtpTimedOut = FALSE; sigemptyset(&new.sa_mask); new.sa_flags = 0; new.sa_handler = ftp_timeout; sigaction(SIGALRM, &new, NULL); cp = getenv("FTP_TIMEOUT"); if (!cp || !(ival = atoi(cp))) ival = 120; alarm(ival); } static void ftp_clear_timeout(void) { struct sigaction new; alarm(0); sigemptyset(&new.sa_mask); new.sa_flags = 0; new.sa_handler = SIG_DFL; sigaction(SIGALRM, &new, NULL); } static int writes(int fd, const char *s) { int n, i = strlen(s); ftp_set_timeout(); n = write(fd, s, i); ftp_clear_timeout(); if (FtpTimedOut || i != n) return TRUE; return FALSE; } static char * get_a_line(FTP_t ftp) { static char buf[BUFSIZ]; int i,j; /* Debug("ftp_pkg: trying to read a line from %d", ftp->fd_ctrl); */ for(i = 0; i < BUFSIZ;) { ftp_set_timeout(); j = read(ftp->fd_ctrl, buf + i, 1); ftp_clear_timeout(); if (FtpTimedOut || j != 1) return NULL; if (buf[i] == '\r' || buf[i] == '\n') { if (!i) continue; buf[i] = '\0'; if (ftp->is_verbose == TRUE) fprintf(stderr, "%s\n",buf+4); return buf; } i++; } /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */ return buf; } static int get_a_number(FTP_t ftp, char **q) { char *p; int i = -1, j; while(1) { p = get_a_line(ftp); if (!p) { ftp_close(ftp); if (FtpTimedOut) return FTP_TIMED_OUT; return FAILURE; } if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2]))) continue; if (i == -1 && p[3] == '-') { i = strtol(p, 0, 0); continue; } if (p[3] != ' ' && p[3] != '\t') continue; j = strtol(p, 0, 0); if (i == -1) { if (q) *q = p+4; /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */ return j; } else if (j == i) { if (q) *q = p+4; /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */ return j; } } } static int ftp_close(FTP_t ftp) { int i, rcode; rcode = FAILURE; if (ftp->con_state == isopen) { ftp->con_state = quit; /* If last operation timed out, don't try to quit - just close */ if (ftp->error != FTP_TIMED_OUT) i = cmd(ftp, "QUIT"); else i = FTP_QUIT_HAPPY; if (!check_code(ftp, i, FTP_QUIT_HAPPY)) rcode = SUCCESS; close(ftp->fd_ctrl); ftp->fd_ctrl = -1; } else if (ftp->con_state == quit) rcode = SUCCESS; return rcode; } static int botch(const char *func __unused, const char *botch_state __unused) { /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */ return FAILURE; } static int cmd(FTP_t ftp, const char *fmt, ...) { char p[BUFSIZ]; int i; va_list ap; va_start(ap, fmt); if ((size_t)vsnprintf(p, sizeof p - 2, fmt, ap) >= sizeof(p) - 2) return FAILURE; va_end(ap); if (ftp->con_state == init) return botch("cmd", "open"); strcat(p, "\r\n"); if (ftp->is_verbose) fprintf(stderr, "Sending: %s", p); if (writes(ftp->fd_ctrl, p)) { if (FtpTimedOut) return FTP_TIMED_OUT; return FAILURE; } while ((i = get_a_number(ftp, NULL)) == FTP_HAPPY_COMMENT); return i; } static int ftp_login_session(FTP_t ftp, const char *host, int af, const char *user, const char *passwd, int port, int verbose) { char pbuf[10]; struct addrinfo hints, *res, *res0; int err; int s; int i; if (networkInit() != SUCCESS) return FAILURE; if (ftp->con_state != init) { ftp_close(ftp); ftp->error = -1; return FAILURE; } if (!user) user = "ftp"; if (!passwd) passwd = "setup@"; if (!port) port = 21; if ((size_t)snprintf(pbuf, sizeof(pbuf), "%d", port) >= sizeof(pbuf)) return FAILURE; memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; err = getaddrinfo(host, pbuf, &hints, &res0); if (err) { ftp->error = 0; return FAILURE; } s = -1; for (res = res0; res; res = res->ai_next) { ai_unmapped(res); ftp->addrtype = res->ai_family; if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) continue; if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { close(s); s = -1; continue; } break; } freeaddrinfo(res0); if (s < 0) { ftp->error = errno; return FAILURE; } ftp->fd_ctrl = s; ftp->con_state = isopen; ftp->is_verbose = verbose; i = cmd(ftp, "USER %s", user); if (i >= 300 && i < 400) i = cmd(ftp, "PASS %s", passwd); if (i >= 299 || i < 0) { ftp_close(ftp); if (i > 0) ftp->error = i; return FAILURE; } return SUCCESS; } static int ftp_file_op(FTP_t ftp, const char *operation, const char *file, FILE **fp, const char *mode, off_t *seekto) { int i,l,s; char *q; unsigned char addr[64]; union sockaddr_cmn { struct sockaddr_in sin4; struct sockaddr_in6 sin6; } sin; const char *cmdstr; if (!fp) return FAILURE; *fp = NULL; if (ftp->con_state != isopen) return botch("ftp_file_op", "open"); if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) { ftp->error = errno; return FAILURE; } if (ftp->is_passive) { if (ftp->addrtype == AF_INET) { if (ftp->is_verbose) fprintf(stderr, "Sending PASV\n"); if (writes(ftp->fd_ctrl, "PASV\r\n")) { ftp_close(ftp); if (FtpTimedOut) ftp->error = FTP_TIMED_OUT; return FTP_TIMED_OUT; } i = get_a_number(ftp, &q); if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) { ftp_close(ftp); return i; } cmdstr = "PASV"; } else { if (ftp->is_verbose) fprintf(stderr, "Sending EPSV\n"); if (writes(ftp->fd_ctrl, "EPSV\r\n")) { ftp_close(ftp); if (FtpTimedOut) ftp->error = FTP_TIMED_OUT; return FTP_TIMED_OUT; } i = get_a_number(ftp, &q); if (check_code(ftp, i, FTP_EPASSIVE_HAPPY)) { if (ftp->is_verbose) fprintf(stderr, "Sending LPSV\n"); if (writes(ftp->fd_ctrl, "LPSV\r\n")) { ftp_close(ftp); if (FtpTimedOut) ftp->error = FTP_TIMED_OUT; return FTP_TIMED_OUT; } i = get_a_number(ftp, &q); if (check_code(ftp, i, FTP_LPASSIVE_HAPPY)) { ftp_close(ftp); return i; } cmdstr = "LPSV"; } else cmdstr = "EPSV"; } if (strcmp(cmdstr, "PASV") == 0 || strcmp(cmdstr, "LPSV") == 0) { while (*q && !isdigit(*q)) q++; if (!*q) { ftp_close(ftp); return FAILURE; } q--; l = (ftp->addrtype == AF_INET ? 6 : 21); for (i = 0; i < l; i++) { q++; addr[i] = strtol(q, &q, 10); } sin.sin4.sin_family = ftp->addrtype; if (ftp->addrtype == AF_INET6) { sin.sin6.sin6_len = sizeof(struct sockaddr_in6); bcopy(addr + 2, (char *)&sin.sin6.sin6_addr, 16); bcopy(addr + 19, (char *)&sin.sin6.sin6_port, 2); } else { sin.sin4.sin_len = sizeof(struct sockaddr_in); bcopy(addr, (char *)&sin.sin4.sin_addr, 4); bcopy(addr + 4, (char *)&sin.sin4.sin_port, 2); } } else if (strcmp(cmdstr, "EPSV") == 0) { int port; int sinlen; while (*q && *q != '(') /* ) */ q++; if (!*q) { ftp_close(ftp); return FAILURE; } q++; if (sscanf(q, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], &port, &addr[3]) != 5 || addr[0] != addr[1] || addr[0] != addr[2] || addr[0] != addr[3]) { ftp_close(ftp); return FAILURE; } sinlen = sizeof(sin); if (getpeername(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen) < 0) { ftp_close(ftp); return FAILURE; } switch (sin.sin4.sin_family) { case AF_INET: sin.sin4.sin_port = htons(port); break; case AF_INET6: sin.sin6.sin6_port = htons(port); break; default: ftp_close(ftp); return FAILURE; } } if (connect(s, (struct sockaddr *)&sin, sin.sin4.sin_len) < 0) { (void)close(s); return FAILURE; } if (seekto && *seekto) { i = cmd(ftp, "REST %" PRId64, *seekto); if (i < 0 || FTP_TIMEOUT(i)) { close(s); ftp->error = i; *seekto = (off_t)0; return i; } } i = cmd(ftp, "%s %s", operation, file); if (i < 0 || i > 299) { close(s); ftp->error = i; return i; } *fp = fdopen(s, mode); } else { int fd,portrange; #ifdef IPV6_PORTRANGE if (ftp->addrtype == AF_INET6) { portrange = IPV6_PORTRANGE_HIGH; if (setsockopt(s, IPPROTO_IPV6, IPV6_PORTRANGE, (char *) &portrange, sizeof(portrange)) < 0) { close(s); return FAILURE; } } #endif #ifdef IP_PORTRANGE if (ftp->addrtype == AF_INET) { portrange = IP_PORTRANGE_HIGH; if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, (char *) &portrange, sizeof(portrange)) < 0) { close(s); return FAILURE; } } #endif i = sizeof sin; getsockname(ftp->fd_ctrl, (struct sockaddr *)&sin, &i); sin.sin4.sin_port = 0; i = ((struct sockaddr *)&sin)->sa_len; if (bind(s, (struct sockaddr *)&sin, i) < 0) { close(s); return FAILURE; } i = sizeof sin; getsockname(s,(struct sockaddr *)&sin,&i); if (listen(s, 1) < 0) { close(s); return FAILURE; } if (sin.sin4.sin_family == AF_INET) { u_long a; a = ntohl(sin.sin4.sin_addr.s_addr); i = cmd(ftp, "PORT %ld,%ld,%ld,%ld,%d,%d", (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, (ntohs(sin.sin4.sin_port) >> 8) & 0xff, ntohs(sin.sin4.sin_port) & 0xff); if (check_code(ftp, i, FTP_PORT_HAPPY)) { close(s); return i; } } else { #define UC(b) (((int)b)&0xff) char *a; char hname[INET6_ADDRSTRLEN]; sin.sin6.sin6_scope_id = 0; if (getnameinfo((struct sockaddr *)&sin, sin.sin6.sin6_len, hname, sizeof(hname), NULL, 0, NI_NUMERICHOST) != 0) { goto try_lprt; } i = cmd(ftp, "EPRT |%d|%s|%d|", 2, hname, htons(sin.sin6.sin6_port)); if (check_code(ftp, i, FTP_PORT_HAPPY)) { try_lprt: a = (char *)&sin.sin6.sin6_addr; i = cmd(ftp, "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 6, 16, UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]), UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]), UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]), UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]), 2, (ntohs(sin.sin4.sin_port) >> 8) & 0xff, ntohs(sin.sin4.sin_port) & 0xff); if (check_code(ftp, i, FTP_PORT_HAPPY)) { close(s); return i; } } } if (seekto && *seekto) { i = cmd(ftp, "REST %" PRId64, *seekto); if (i < 0 || FTP_TIMEOUT(i)) { close(s); ftp->error = i; return i; } else if (i != 350) *seekto = (off_t)0; } i = cmd(ftp, "%s %s", operation, file); if (i < 0 || i > 299) { close(s); ftp->error = i; return FAILURE; } fd = accept(s, 0, 0); if (fd < 0) { close(s); ftp->error = 401; return FAILURE; } close(s); *fp = fdopen(fd, mode); } if (*fp) return SUCCESS; else return FAILURE; } static void ai_unmapped(struct addrinfo *ai) { struct sockaddr_in6 *sin6; struct sockaddr_in sin; if (ai->ai_family != AF_INET6) return; if (ai->ai_addrlen != sizeof(struct sockaddr_in6) || sizeof(sin) > ai->ai_addrlen) return; sin6 = (struct sockaddr_in6 *)ai->ai_addr; if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) return; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_len = sizeof(struct sockaddr_in); memcpy(&sin.sin_addr, &sin6->sin6_addr.s6_addr[12], sizeof(sin.sin_addr)); sin.sin_port = sin6->sin6_port; ai->ai_family = AF_INET; memcpy(ai->ai_addr, &sin, sin.sin_len); ai->ai_addrlen = sin.sin_len; }