diff options
Diffstat (limited to 'lib/libftpio/ftpio.c')
-rw-r--r-- | lib/libftpio/ftpio.c | 1070 |
1 files changed, 1070 insertions, 0 deletions
diff --git a/lib/libftpio/ftpio.c b/lib/libftpio/ftpio.c new file mode 100644 index 0000000..313f42b --- /dev/null +++ b/lib/libftpio/ftpio.c @@ -0,0 +1,1070 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> 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 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <ftpio.h> +#include <netdb.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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, char *s); +static __inline char *get_a_line(FTP_t ftp); +static int get_a_number(FTP_t ftp, char **q); +static int botch(char *func, char *botch_state); +static int cmd(FTP_t ftp, const char *fmt, ...); +static int ftp_login_session(FTP_t ftp, char *host, int af, char *user, char *passwd, int port, int verbose); +static int ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto); +static int ftp_close(FTP_t ftp); +static int get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret); +static void ftp_timeout(int sig); +static void ftp_set_timeout(void); +static void ftp_clear_timeout(void); +static void ai_unmapped(struct addrinfo *); + + +/* 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 + +/* + * XXX + * gross! evil! bad! We really need an access primitive for cookie in stdio itself. + * it's too convenient a hook to bury and it's already exported through funopen as it is, so... + * XXX + */ +#define fcookie(fp) ((fp)->_cookie) + +/* Placeholder in case we want to do any pre-init stuff at some point */ +int +networkInit() +{ + 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, char *name) +{ + int i; + char p[BUFSIZ], *cp, *ep; + FTP_t ftp = fcookie(fp); + off_t size; + + check_passive(fp); + sprintf(p, "SIZE %s\r\n", name); + 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, char *name) +{ + char p[BUFSIZ], *cp; + struct tm t; + time_t t0 = time (0); + FTP_t ftp = fcookie(fp); + int i; + + check_passive(fp); + sprintf(p, "MDTM %s\r\n", name); + 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, 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(char *host, char *user, 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(char *host, int af, char *user, 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! */ + fp->_file = n->fd_ctrl; + } + 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, 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(char *url, char *user, 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(char *url, int af, char *user, 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, &port, 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(char *url, char *user, 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(char *url, int af, char *user, 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, &port, 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(char *url_in, char *host_ret, int *port_ret, char *name_ret) +{ + 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) + strcpy(host_ret, host); + if (name && name_ret) + 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) +{ + 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, 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 __inline 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(char *func, char *botch_state) +{ + /* 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); + (void)vsnprintf(p, sizeof p, fmt, ap); + 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, char *host, int af, + char *user, 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; + + snprintf(pbuf, sizeof(pbuf), "%d", port); + 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) { + (void)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, char *operation, char *file, FILE **fp, 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; + 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 %d", *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 %d,%d,%d,%d,%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 %d", *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; +} |