diff --git a/CHANGES b/CHANGES index 6c33e39dc..4eb548a49 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,16 @@ Changelog +Daniel Stenberg (29 Jun 2009) +- Markus Koetter made CURLOPT_FTPPORT (and curl's -P/--ftpport) support a port + range if given colon-separated after the host name/address part. Like + "192.168.0.1:2000-10000" + +- Modified the separators used for CURLOPT_CERTINFO in multi-part outputs. I + don't know how they got wrong in the first place, but using this output + format makes it possible to quite easily separate the string into an array + of multiple items. + Daniel Fandrich (16 June 2009) - Added a few more compiler warning options for gcc. diff --git a/RELEASE-NOTES b/RELEASE-NOTES index cbb650f11..fdcdd721c 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -9,7 +9,7 @@ Curl and libcurl 7.19.6 This release includes the following changes: - o + o CURLOPT_FTPPORT (and curl's -P/--ftpport) support port ranges This release includes the following bugfixes: @@ -41,6 +41,6 @@ advice from friends like these: Yang Tse, Daniel Fandrich, Kamil Dudka, Caolan McNamara, Frank McGeough, Andre Guibert de Bruet, Mike Crowe, Claes Jakobsson, John E. Malmberg, Aaron Oneal, Igor Novoseltsev, Eric Wong, Bill Hoffman, Daniel Steinberg, - Fabian Keil, Michal Marek, Reuven Wachtfogel + Fabian Keil, Michal Marek, Reuven Wachtfogel, Markus Koetter Thanks! (and sorry if I forgot to mention someone) diff --git a/docs/curl.1 b/docs/curl.1 index 9748d5bec..bdd293e07 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -953,6 +953,12 @@ connection If this option is used several times, the last one will be used. Disable the use of PORT with \fI--ftp-pasv\fP. Disable the attempt to use the EPRT command instead of PORT by using \fI--disable-eprt\fP. EPRT is really PORT++. + +Starting in 7.19.5, you can append \&":[start]-[end]\&" to the right of the +address, to tell curl what TCP port range to use. That means you specify a +port range, from a lower to a higher number. A single number works as well, +but do not that it increases the risk of failure since the port may not be +available. .IP "-q" If used as the first parameter on the command line, the \fIcurlrc\fP config file will not be read and used. See the \fI-K/--config\fP for details on the diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index eee750b57..e1ffeade1 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -1070,6 +1070,12 @@ be a plain IP address, a host name, a network interface name (under Unix) or just a '-' symbol to let the library use your system's default IP address. Default FTP operations are passive, and thus won't use PORT. +Starting in 7.19.5, you can append \&":[start]-[end]\&" to the right of the +address, to tell libcurl what TCP port range to use. That means you specify a +port range, from a lower to a higher number. A single number works as well, +but do not that it increases the risk of failure since the port may not be +available. + You disable PORT again and go back to using the passive version by setting this option to NULL. .IP CURLOPT_QUOTE diff --git a/lib/ftp.c b/lib/ftp.c index b46402be5..80841ee37 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -290,7 +290,8 @@ static void freedirs(struct ftp_conn *ftpc) */ static bool isBadFtpString(const char *string) { - return (bool)((NULL != strchr(string, '\r')) || (NULL != strchr(string, '\n'))); + return (bool)((NULL != strchr(string, '\r')) || + (NULL != strchr(string, '\n'))); } /*********************************************************************** @@ -882,36 +883,116 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, curl_socket_t portsock= CURL_SOCKET_BAD; char myhost[256] = ""; -#ifdef ENABLE_IPV6 - /****************************************************************** - * IPv6-specific section - */ struct Curl_sockaddr_storage ss; Curl_addrinfo *res, *ai; curl_socklen_t sslen; char hbuf[NI_MAXHOST]; struct sockaddr *sa=(struct sockaddr *)&ss; struct sockaddr_in * const sa4 = (void *)sa; +#ifdef ENABLE_IPV6 struct sockaddr_in6 * const sa6 = (void *)sa; +#endif char tmp[1024]; static const char mode[][5] = { "EPRT", "PORT" }; int rc; int error; char *host=NULL; + char *string_ftpport = data->set.str[STRING_FTPPORT]; struct Curl_dns_entry *h=NULL; - unsigned short port = 0; + unsigned short port_min = 0; + unsigned short port_max = 0; + unsigned short port; - /* Step 1, figure out what address that is requested */ + char *addr = NULL; + + /* Step 1, figure out what is requested, + * accepted format : + * (ipv4|ipv6|domain|interface)?(:port(-range)?)? + */ if(data->set.str[STRING_FTPPORT] && (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - /* attempt to get the address of the given interface name */ - if(!Curl_if2ip(conn->ip_addr->ai_family, data->set.str[STRING_FTPPORT], - hbuf, sizeof(hbuf))) - /* not an interface, use the given string as host name instead */ - host = data->set.str[STRING_FTPPORT]; - else - host = hbuf; /* use the hbuf for host name */ + +#ifdef ENABLE_IPV6 + size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? + INET6_ADDRSTRLEN : strlen(string_ftpport); +#else + size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? + INET_ADDRSTRLEN : strlen(string_ftpport); +#endif + char *ip_start = string_ftpport; + char *ip_end = NULL; + char *port_start = NULL; + char *port_sep = NULL; + + addr = malloc(addrlen); + memset(addr, 0, addrlen); + + +#ifdef ENABLE_IPV6 + if(*string_ftpport == '[') { + /* [ipv6]:port(-range) */ + ip_start = string_ftpport + 1; + if((ip_end = strchr(string_ftpport, ']')) != NULL ) + strncpy(addr, ip_start, ip_end - ip_start); + } else +#endif + if( *string_ftpport == ':') { + /* :port */ + ip_end = string_ftpport; + } else + if( (ip_end = strchr(string_ftpport, ':')) != NULL) { + /* either ipv6 or (ipv4|domain|interface):port(-range) */ +#ifdef ENABLE_IPV6 + if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) { + /* ipv6 */ + port_min = port_max = 0; + strcpy(addr, string_ftpport); + ip_end = NULL; /* this got no port ! */ + } else +#endif + { + /* (ipv4|domain|interface):port(-range) */ + strncpy(addr, string_ftpport, ip_end - ip_start ); + } + } else { + /* ipv4|interface */ + strcpy(addr, string_ftpport); + } + + /* parse the port */ + if( ip_end != NULL ) { + if((port_start = strchr(ip_end, ':')) != NULL) { + port_min = (unsigned short)strtol(port_start+1, NULL, 10); + if((port_sep = strchr(port_start, '-')) != NULL) { + port_max = (unsigned short)strtol(port_sep + 1, NULL, 10); + } + else + port_max = port_min; + } + } + + /* correct errors like: + * :1234-1230 + * :-4711 , in this case port_min is (unsigned)-1, + * therefore port_min > port_max for all cases + * but port_max = (unsigned)-1 + */ + if(port_min > port_max ) + port_min = port_max = 0; + + + if(*addr != '\0') { + /* attempt to get the address of the given interface name */ + if(!Curl_if2ip(conn->ip_addr->ai_family, addr, + hbuf, sizeof(hbuf))) + /* not an interface, use the given string as host name instead */ + host = addr; + else + host = hbuf; /* use the hbuf for host name */ + }else + /* there was only a port(-range) given, default the host */ + host = NULL; } /* data->set.ftpport */ if(!host) { @@ -922,20 +1003,24 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, if(getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&ss, &sslen)) { failf(data, "getsockname() failed: %s", Curl_strerror(conn, SOCKERRNO) ); + if (addr) + free(addr); return CURLE_FTP_PORT_FAILED; } - if(sslen > (curl_socklen_t)sizeof(ss)) - sslen = sizeof(ss); + sslen = sizeof(ss); rc = getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, 0, NIFLAGS); if(rc) { failf(data, "getnameinfo() returned %d", rc); + if (addr) + free(addr); return CURLE_FTP_PORT_FAILED; } host = hbuf; /* use this host name */ } + /* resolv ip/host to ip */ rc = Curl_resolv(conn, host, 0, &h); if(rc == CURLRESOLV_PENDING) rc = Curl_wait_for_resolv(conn, &h); @@ -948,6 +1033,13 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, else res = NULL; /* failure! */ + if (addr) + free(addr); + + if (res == NULL) { + failf(data, "Curl_resolv failed, we can not recover!"); + return CURLE_FTP_PORT_FAILED; + } /* step 2, create a socket for the requested address */ @@ -974,32 +1066,56 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* step 3, bind to a suitable local address */ - /* Try binding the given address. */ - if(bind(portsock, ai->ai_addr, ai->ai_addrlen)) { + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + sslen = ai->ai_addrlen; - /* It failed. Bind the address used for the control connection instead */ - sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - sclose(portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* set port number to zero to make bind() pick "any" */ - if(sa->sa_family == AF_INET) - sa4->sin_port = 0; + for( port = port_min; port <= port_max; ) { + if( sa->sa_family == AF_INET ) + sa4->sin_port = htons(port); +#ifdef ENABLE_IPV6 else - sa6->sin6_port = 0; + sa6->sin6_port = htons(port); +#endif + /* Try binding the given address. */ + if(bind(portsock, sa, sslen) ) { + /* It failed. */ + if(errno == EADDRNOTAVAIL) { - if(sslen > (curl_socklen_t)sizeof(ss)) - sslen = sizeof(ss); + /* The requested bind address is not local + * use the address used forthe control connection instead + * restart the port loop + */ + failf(data, "bind(port=%i) failed: %s", port, + Curl_strerror(conn, SOCKERRNO) ); - if(bind(portsock, sa, sslen)) { - failf(data, "bind failed: %s", Curl_strerror(conn, SOCKERRNO)); - sclose(portsock); - return CURLE_FTP_PORT_FAILED; - } + sslen = sizeof(ss); + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, SOCKERRNO) ); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + port = port_min; + continue; + }else + if(errno != EADDRINUSE && errno != EACCES) { + failf(data, "bind(port=%i) failed: %s", port, + Curl_strerror(conn, SOCKERRNO) ); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; + } + + } else + break; + + port++; + } + + /* maybe all ports were in use already*/ + if (port > port_max) { + failf(data, "bind() failed, we ran out of ports!"); + sclose(portsock); + return CURLE_FTP_PORT_FAILED; } /* get the name again after the bind() so that we can extract the @@ -1039,6 +1155,10 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* if disabled, goto next */ continue; + if((PORT == fcmd) && sa->sa_family != AF_INET) + /* PORT is ipv4 only */ + continue; + switch (sa->sa_family) { case AF_INET: port = ntohs(sa4->sin_port); @@ -1060,7 +1180,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, */ result = Curl_nbftpsendf(conn, "%s |%d|%s|%d|", mode[fcmd], - ai->ai_family == AF_INET?1:2, + sa->sa_family == AF_INET?1:2, myhost, port); if(result) return result; @@ -1070,9 +1190,6 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, char *source = myhost; char *dest = tmp; - if((PORT == fcmd) && ai->ai_family != AF_INET) - continue; - /* translate x.x.x.x to x,x,x,x */ while(source && *source) { if(*source == '.') @@ -1102,151 +1219,6 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, sclose(conn->sock[SECONDARYSOCKET]); conn->sock[SECONDARYSOCKET] = portsock; -#else - /****************************************************************** - * IPv4-specific section - */ - struct sockaddr_in sa; - unsigned short porttouse; - bool sa_filled_in = FALSE; - Curl_addrinfo *addr = NULL; - unsigned short ip[4]; - bool freeaddr = TRUE; - curl_socklen_t sslen = sizeof(sa); - const char *ftpportstr = data->set.str[STRING_FTPPORT]; - - (void)fcmd; /* not used in the IPv4 code */ - if(ftpportstr) { - struct in_addr in; - - /* First check if the given string is an IP address */ - if(Curl_inet_pton(AF_INET, ftpportstr, &in) > 0) { - /* this is an IPv4 address */ - addr = Curl_ip2addr(AF_INET, &in, ftpportstr, 0); - } - /* otherwise check if the given string is an interface */ - else if(Curl_if2ip(AF_INET, ftpportstr, myhost, sizeof(myhost))) { - /* The interface to IP conversion provided a dotted address */ - if(Curl_inet_pton(AF_INET, myhost, &in) > 0) - addr = Curl_ip2addr(AF_INET, &in, myhost, 0); - } - else if(strlen(ftpportstr)> 1) { - /* might be a host name! */ - struct Curl_dns_entry *h=NULL; - int rc = Curl_resolv(conn, ftpportstr, 0, &h); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - rc = Curl_wait_for_resolv(conn, &h); - if(h) { - addr = h->addr; - /* when we return from this function, we can forget about this entry - so we can unlock it now already */ - Curl_resolv_unlock(data, h); - - freeaddr = FALSE; /* make sure we don't free 'addr' in this function - since it points to a DNS cache entry! */ - } /* (h) */ - else { - infof(data, "Failed to resolve host name %s\n", ftpportstr); - } - } /* strlen */ - } /* ftpportstr */ - - if(!addr) { - /* pick a suitable default here */ - - if(getsockname(conn->sock[FIRSTSOCKET], - (struct sockaddr *)&sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - return CURLE_FTP_PORT_FAILED; - } - if(sslen > (curl_socklen_t)sizeof(sa)) - sslen = sizeof(sa); - - sa_filled_in = TRUE; /* the sa struct is filled in */ - } - - if(addr || sa_filled_in) { - portsock = socket(AF_INET, SOCK_STREAM, 0); - if(CURL_SOCKET_BAD != portsock) { - - /* we set the secondary socket variable to this for now, it - is only so that the cleanup function will close it in case - we fail before the true secondary stuff is made */ - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) - sclose(conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = portsock; - - if(!sa_filled_in) { - memcpy(&sa, addr->ai_addr, sslen); - sa.sin_addr.s_addr = INADDR_ANY; - } - - sa.sin_port = 0; - sslen = sizeof(sa); - - if(bind(portsock, (struct sockaddr *)&sa, sslen) == 0) { - /* we succeeded to bind */ - struct sockaddr_in add; - curl_socklen_t socksize = sizeof(add); - - if(getsockname(portsock, (struct sockaddr *) &add, - &socksize)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - return CURLE_FTP_PORT_FAILED; - } - porttouse = ntohs(add.sin_port); - - if( listen(portsock, 1) < 0 ) { - failf(data, "listen(2) failed on socket"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "bind(2) failed on socket"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "socket(2) failed (%s)"); - return CURLE_FTP_PORT_FAILED; - } - } - else { - failf(data, "couldn't find IP address to use"); - return CURLE_FTP_PORT_FAILED; - } - - if(sa_filled_in) - Curl_inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr, - myhost, sizeof(myhost)); - else - Curl_printable_address(addr, myhost, sizeof(myhost)); - - if(4 == sscanf(myhost, "%hu.%hu.%hu.%hu", - &ip[0], &ip[1], &ip[2], &ip[3])) { - - infof(data, "Telling server to connect to %d.%d.%d.%d:%d\n", - ip[0], ip[1], ip[2], ip[3], porttouse); - - result=Curl_nbftpsendf(conn, "PORT %d,%d,%d,%d,%d,%d", - ip[0], ip[1], ip[2], ip[3], - porttouse >> 8, porttouse & 255); - if(result) - return result; - } - else - return CURLE_FTP_PORT_FAILED; - - if(freeaddr) - Curl_freeaddrinfo(addr); - - ftpc->count1 = PORT; - -#endif /* end of ipv4-specific code */ - /* this tcpconnect assignment below is a hackish work-around to make the multi interface with active FTP work - as it will not wait for a (passive) connect in Curl_is_connected().