← Commit list

29bca12978

Daniel Stenberg
Daniel Stenberg
2026-02-23
SUCCESS Duration: 371s

Subject

ftp: split ftp_state_use_port into sub functions

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/ftp.c+338 -198
@@ -873,199 +873,228 @@ typedef enum {
DONE
} ftpport;
-static CURLcode ftp_state_use_port(struct Curl_easy *data,
- struct ftp_conn *ftpc,
- ftpport fcmd) /* start with this */
+/*
+ * Parse the CURLOPT_FTPPORT string
+ * "(ipv4|ipv6|domain|interface)?(:port(-range)?)?"
+ * and extract addr/addrlen and port_min/port_max.
+ */
+static CURLcode ftp_port_parse_string(struct Curl_easy *data,
+ struct connectdata *conn,
+ const char *string_ftpport,
+ struct Curl_sockaddr_storage *ss,
+ unsigned short *port_minp,
+ unsigned short *port_maxp,
+ const char **hostp,
+ char *hbuf, size_t hbuflen)
{
- CURLcode result = CURLE_FTP_PORT_FAILED;
- struct connectdata *conn = data->conn;
- curl_socket_t portsock = CURL_SOCKET_BAD;
- char myhost[MAX_IPADR_LEN + 1] = "";
-
- struct Curl_sockaddr_storage ss;
- const struct 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 USE_IPV6
- struct sockaddr_in6 * const sa6 = (void *)sa;
-#endif
- static const char mode[][5] = { "EPRT", "PORT" };
- int error;
- const char *host = NULL;
- const char *string_ftpport = data->set.str[STRING_FTPPORT];
- struct Curl_dns_entry *dns_entry = NULL;
- unsigned short port_min = 0;
- unsigned short port_max = 0;
- unsigned short port;
- bool possibly_non_local = TRUE;
- char buffer[STRERROR_LEN];
+ const char *ip_end = NULL;
const char *addr = NULL;
size_t addrlen = 0;
+ unsigned short port_min = 0;
+ unsigned short port_max = 0;
char ipstr[50];
+#ifndef USE_IPV6
+ (void)conn;
+ (void)ss;
+#endif
- /* Step 1, figure out what is requested,
- * accepted format :
- * (ipv4|ipv6|domain|interface)?(:port(-range)?)?
- */
+ /* default to nothing */
+ *hostp = NULL;
+ *port_minp = *port_maxp = 0;
- if(data->set.str[STRING_FTPPORT] &&
- (strlen(data->set.str[STRING_FTPPORT]) > 1)) {
- const char *ip_end = NULL;
+ if(!string_ftpport || (strlen(string_ftpport) <= 1))
+ goto done;
#ifdef USE_IPV6
- if(*string_ftpport == '[') {
- /* [ipv6]:port(-range) */
- const char *ip_start = string_ftpport + 1;
- ip_end = strchr(ip_start, ']');
- if(ip_end) {
- addrlen = ip_end - ip_start;
- addr = ip_start;
- }
+ if(*string_ftpport == '[') {
+ /* [ipv6]:port(-range) */
+ const char *ip_start = string_ftpport + 1;
+ ip_end = strchr(ip_start, ']');
+ if(ip_end) {
+ addrlen = ip_end - ip_start;
+ addr = ip_start;
}
- else
+ }
+ else
#endif
- if(*string_ftpport == ':') {
- /* :port */
- ip_end = string_ftpport;
- }
- else {
- ip_end = strchr(string_ftpport, ':');
- addr = string_ftpport;
- if(ip_end) {
- /* either ipv6 or (ipv4|domain|interface):port(-range) */
- addrlen = ip_end - string_ftpport;
+ if(*string_ftpport == ':') {
+ /* :port */
+ ip_end = string_ftpport;
+ }
+ else {
+ ip_end = strchr(string_ftpport, ':');
+ addr = string_ftpport;
+ if(ip_end) {
#ifdef USE_IPV6
- if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) {
- /* ipv6 */
- port_min = port_max = 0;
- ip_end = NULL; /* this got no port ! */
- }
+ struct sockaddr_in6 * const sa6 = (void *)ss;
#endif
- }
- else
- /* ipv4|interface */
+ /* either ipv6 or (ipv4|domain|interface):port(-range) */
+ addrlen = ip_end - string_ftpport;
+#ifdef USE_IPV6
+ if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) {
+ /* ipv6 */
+ port_min = port_max = 0;
addrlen = strlen(string_ftpport);
+ ip_end = NULL; /* this got no port ! */
+ }
+#endif
}
+ else
+ /* ipv4|interface */
+ addrlen = strlen(string_ftpport);
+ }
- /* parse the port */
- if(ip_end) {
- const char *portp = strchr(ip_end, ':');
- if(portp) {
- curl_off_t start;
- curl_off_t end;
- portp++;
- if(!curlx_str_number(&portp, &start, 0xffff)) {
- /* got the first number */
- port_min = (unsigned short)start;
- if(!curlx_str_single(&portp, '-')) {
- /* got the dash */
- if(!curlx_str_number(&portp, &end, 0xffff))
- /* got the second number */
- port_max = (unsigned short)end;
- }
- }
+ /* parse the port */
+ if(ip_end) {
+ const char *portp = strchr(ip_end, ':');
+ if(portp) {
+ curl_off_t start;
+ curl_off_t end;
+ portp++;
+ if(!curlx_str_number(&portp, &start, 0xffff)) {
+ port_min = (unsigned short)start;
+ if(!curlx_str_single(&portp, '-') &&
+ !curlx_str_number(&portp, &end, 0xffff))
+ port_max = (unsigned short)end;
else
port_max = port_min;
}
+ 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(addrlen) {
- const struct Curl_sockaddr_ex *remote_addr =
- Curl_conn_get_remote_addr(data, FIRSTSOCKET);
-
- DEBUGASSERT(remote_addr);
- if(!remote_addr)
- goto out;
- DEBUGASSERT(addr);
- if(addrlen >= sizeof(ipstr))
- goto out;
- memcpy(ipstr, addr, addrlen);
- ipstr[addrlen] = 0;
-
- /* attempt to get the address of the given interface name */
- switch(Curl_if2ip(remote_addr->family,
+ /* correct errors like :1234-1230 or :-4711 */
+ if(port_min > port_max)
+ port_min = port_max = 0;
+
+ if(addrlen) {
+ const struct Curl_sockaddr_ex *remote_addr =
+ Curl_conn_get_remote_addr(data, FIRSTSOCKET);
+
+ DEBUGASSERT(remote_addr);
+ DEBUGASSERT(addr);
+ if(!remote_addr || (addrlen >= sizeof(ipstr)) || (addrlen >= hbuflen))
+ return CURLE_FTP_PORT_FAILED;
+ memcpy(ipstr, addr, addrlen);
+ ipstr[addrlen] = 0;
+
+ /* attempt to get the address of the given interface name */
+ switch(Curl_if2ip(remote_addr->family,
#ifdef USE_IPV6
- Curl_ipv6_scope(&remote_addr->curl_sa_addr),
- conn->scope_id,
+ Curl_ipv6_scope(&remote_addr->curl_sa_addr),
+ conn->scope_id,
#endif
- ipstr, hbuf, sizeof(hbuf))) {
- case IF2IP_NOT_FOUND:
- /* not an interface, use the given string as hostname instead */
- host = ipstr;
- break;
- case IF2IP_AF_NOT_SUPPORTED:
- goto out;
- case IF2IP_FOUND:
- host = hbuf; /* use the hbuf for hostname */
- break;
- }
- }
- else
- /* there was only a port(-range) given, default the host */
- host = NULL;
- } /* data->set.ftpport */
-
- if(!host) {
- const char *r;
- /* not an interface and not a hostname, get default by extracting
- the IP from the control connection */
- sslen = sizeof(ss);
- if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) {
- failf(data, "getsockname() failed: %s",
- curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- goto out;
- }
- switch(sa->sa_family) {
-#ifdef USE_IPV6
- case AF_INET6:
- r = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf));
+ ipstr, hbuf, hbuflen)) {
+ case IF2IP_NOT_FOUND:
+ /* not an interface, use the string as hostname instead */
+ memcpy(hbuf, addr, addrlen);
+ hbuf[addrlen] = 0;
+ *hostp = hbuf;
break;
-#endif
- default:
- r = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf));
+ case IF2IP_AF_NOT_SUPPORTED:
+ return CURLE_FTP_PORT_FAILED;
+ case IF2IP_FOUND:
+ *hostp = hbuf; /* use the hbuf for hostname */
break;
}
- if(!r) {
- goto out;
- }
- host = hbuf; /* use this hostname */
- possibly_non_local = FALSE; /* we know it is local now */
}
+ /* else: only a port(-range) given, leave host as NULL */
- /* resolv ip/host to ip */
- res = NULL;
- result = Curl_resolv_blocking(data, host, 0, conn->ip_version, &dns_entry);
- if(!result) {
- DEBUGASSERT(dns_entry);
- res = dns_entry->addr;
+done:
+ *port_minp = port_min;
+ *port_maxp = port_max;
+ return CURLE_OK;
+}
+
+/*
+ * If no host was derived from the FTPPORT string, fall back to the IP address
+ * of the control connection's local socket.
+ */
+static CURLcode ftp_port_default_host(struct Curl_easy *data,
+ struct connectdata *conn,
+ struct Curl_sockaddr_storage *ss,
+ curl_socklen_t *sslenp,
+ const char **hostp,
+ char *hbuf, size_t hbuflen,
+ bool *non_localp)
+{
+ struct sockaddr *sa = (struct sockaddr *)ss;
+ struct sockaddr_in * const sa4 = (void *)sa;
+#ifdef USE_IPV6
+ struct sockaddr_in6 * const sa6 = (void *)sa;
+#endif
+ char buffer[STRERROR_LEN];
+ const char *r;
+
+ *sslenp = sizeof(*ss);
+ if(getsockname(conn->sock[FIRSTSOCKET], sa, sslenp)) {
+ failf(data, "getsockname() failed: %s",
+ curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+ return CURLE_FTP_PORT_FAILED;
}
+ switch(sa->sa_family) {
+#ifdef USE_IPV6
+ case AF_INET6:
+ r = curlx_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, hbuflen);
+ break;
+#endif
+ default:
+ r = curlx_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, hbuflen);
+ break;
+ }
+ if(!r)
+ return CURLE_FTP_PORT_FAILED;
+
+ *hostp = hbuf;
+ *non_localp = FALSE; /* we know it is local now */
+ return CURLE_OK;
+}
+
+/*
+ * Resolve the host string to a list of addresses.
+ */
+static CURLcode ftp_port_resolve_host(struct Curl_easy *data,
+ struct connectdata *conn,
+ const char *host,
+ struct Curl_dns_entry **dns_entryp,
+ const struct Curl_addrinfo **resp)
+{
+ CURLcode result;
- if(!res) {
+ *resp = NULL;
+ result = Curl_resolv_blocking(data, host, 0, conn->ip_version,
+ dns_entryp);
+ if(result)
failf(data, "failed to resolve the address provided to PORT: %s", host);
- goto out;
+ else {
+ DEBUGASSERT(*dns_entryp);
+ *resp = (*dns_entryp)->addr;
}
+ return result;
+}
- host = NULL;
+/*
+ * Open a TCP socket for the resolved address family.
+ */
+static CURLcode ftp_port_open_socket(struct Curl_easy *data,
+ struct connectdata *conn,
+ const struct Curl_addrinfo *res,
+ const struct Curl_addrinfo **aip,
+ curl_socket_t *portsockp)
+{
+ char buffer[STRERROR_LEN];
+ int error = 0;
+ const struct Curl_addrinfo *ai;
+ CURLcode result = CURLE_FTP_PORT_FAILED;
- /* step 2, create a socket for the requested address */
- error = 0;
for(ai = res; ai; ai = ai->ai_next) {
- result = Curl_socket_open(data, ai, NULL,
- Curl_conn_get_transport(data, conn), &portsock);
+ result =
+ Curl_socket_open(data, ai, NULL,
+ Curl_conn_get_transport(data, conn), portsockp);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
- goto out;
+ return result;
result = CURLE_FTP_PORT_FAILED;
error = SOCKERRNO;
continue;
@@ -1075,15 +1104,38 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
if(!ai) {
failf(data, "socket failure: %s",
curlx_strerror(error, buffer, sizeof(buffer)));
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
- CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket",
- FTP_CSTATE(ftpc));
+ *aip = ai;
+ return result;
+}
- /* step 3, bind to a suitable local address */
+/*
+ * Bind the socket to a local address and port within the requested range.
+ * Falls back to the control-connection address if the user-requested address
+ * is non-local.
+ */
+static CURLcode ftp_port_bind_socket(struct Curl_easy *data,
+ struct connectdata *conn,
+ curl_socket_t portsock,
+ const struct Curl_addrinfo *ai,
+ struct Curl_sockaddr_storage *ss,
+ curl_socklen_t *sslen_io,
+ unsigned short port_min,
+ unsigned short port_max,
+ bool non_local)
+{
+ struct sockaddr *sa = (struct sockaddr *)ss;
+ struct sockaddr_in * const sa4 = (void *)sa;
+#ifdef USE_IPV6
+ struct sockaddr_in6 * const sa6 = (void *)sa;
+#endif
+ char buffer[STRERROR_LEN];
+ unsigned short port;
+ int error;
memcpy(sa, ai->ai_addr, ai->ai_addrlen);
- sslen = ai->ai_addrlen;
+ *sslen_io = ai->ai_addrlen;
for(port = port_min; port <= port_max;) {
if(sa->sa_family == AF_INET)
@@ -1092,71 +1144,97 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
else
sa6->sin6_port = htons(port);
#endif
- /* Try binding the given address. */
- if(bind(portsock, sa, sslen)) {
- /* It failed. */
+ if(bind(portsock, sa, *sslen_io)) {
error = SOCKERRNO;
- if(possibly_non_local && (error == SOCKEADDRNOTAVAIL)) {
+ if(non_local && (error == SOCKEADDRNOTAVAIL)) {
/* The requested bind address is not local. Use the address used for
- * the control connection instead and restart the port loop
+ * the control connection instead and restart the port loop.
*/
infof(data, "bind(port=%hu) on non-local address failed: %s", port,
curlx_strerror(error, buffer, sizeof(buffer)));
- sslen = sizeof(ss);
- if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) {
+ *sslen_io = sizeof(*ss);
+ if(getsockname(conn->sock[FIRSTSOCKET], sa, sslen_io)) {
failf(data, "getsockname() failed: %s",
curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
port = port_min;
- possibly_non_local = FALSE; /* do not try this again */
+ non_local = FALSE; /* do not try this again */
continue;
}
if(error != SOCKEADDRINUSE && error != SOCKEACCES) {
failf(data, "bind(port=%hu) failed: %s", port,
curlx_strerror(error, buffer, sizeof(buffer)));
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
}
else
break;
- /* check if port is the maximum value here, because it might be 0xffff and
- then the increment below will wrap the 16-bit counter */
+ /* check if port is the maximum value here, because it might be 0xffff
+ and then the increment below will wrap the 16-bit counter */
if(port == port_max) {
- /* maybe all ports were in use already */
failf(data, "bind() failed, ran out of ports");
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
port++;
}
- /* get the name again after the bind() so that we can extract the
- port number it uses now */
- sslen = sizeof(ss);
- if(getsockname(portsock, sa, &sslen)) {
+ /* re-read the name so we can extract the actual port chosen */
+ *sslen_io = sizeof(*ss);
+ if(getsockname(portsock, sa, sslen_io)) {
failf(data, "getsockname() failed: %s",
curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
- CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d",
- FTP_CSTATE(ftpc), port);
+ CURL_TRC_FTP(data, "ftp_port_bind_socket(), socket bound to port %d",
+ port);
+ return CURLE_OK;
+}
- /* step 4, listen on the socket */
+/*
+ * Start listening on the data socket.
+ */
+static CURLcode ftp_port_listen(struct Curl_easy *data, curl_socket_t portsock)
+{
+ char buffer[STRERROR_LEN];
if(listen(portsock, 1)) {
failf(data, "socket failure: %s",
curlx_strerror(SOCKERRNO, buffer, sizeof(buffer)));
- goto out;
+ return CURLE_FTP_PORT_FAILED;
}
- CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d",
- FTP_CSTATE(ftpc), port);
+ CURL_TRC_FTP(data, "ftp_port_listen(), listening on port");
+ return CURLE_OK;
+}
- /* step 5, send the proper FTP command */
+/*
+ * Send the EPRT or PORT command to the server.
+ */
+static CURLcode ftp_port_send_command(struct Curl_easy *data,
+ struct ftp_conn *ftpc,
+ struct connectdata *conn,
+ struct Curl_sockaddr_storage *ss,
+ const struct Curl_addrinfo *ai,
+ ftpport fcmd)
+{
+ static const char mode[][5] = { "EPRT", "PORT" };
+ struct sockaddr *sa = (struct sockaddr *)ss;
+ struct sockaddr_in * const sa4 = (void *)sa;
+#ifdef USE_IPV6
+ struct sockaddr_in6 * const sa6 = (void *)sa;
+#endif
+ char myhost[MAX_IPADR_LEN + 1] = "";
+ unsigned short port;
+ CURLcode result;
- /* get a plain printable version of the numerical address to work with
- below */
+ /* Get a plain printable version of the numerical address to work with. This
+ logic uses the address provided by the FTPPORT option, which at times
+ might differ from the address in 'ss' used to bind to: when a user asks
+ the server to connect to a specific address knowing that it works, but
+ curl instead selects to listen to the local address because it cannot use
+ the provided address. FTP is strange. */
Curl_printable_address(ai, myhost, sizeof(myhost));
#ifdef USE_IPV6
@@ -1197,14 +1275,13 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
*
* EPRT |2|1080::8:800:200C:417A|5282|
*/
-
result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd],
sa->sa_family == AF_INET ? 1 : 2,
myhost, port);
if(result) {
failf(data, "Failure sending EPRT command: %s",
curl_easy_strerror(result));
- goto out;
+ return result;
}
break;
}
@@ -1230,7 +1307,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
if(result) {
failf(data, "Failure sending PORT command: %s",
curl_easy_strerror(result));
- goto out;
+ return result;
}
break;
}
@@ -1239,21 +1316,84 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
/* store which command was sent */
ftpc->count1 = fcmd;
ftp_state(data, ftpc, FTP_PORT);
+ return CURLE_OK;
+}
+
+/*
+ * ftp_state_use_port()
+ *
+ * Set up an active-mode FTP data connection (using PORT or EPRT) and start
+ * listening for the server's incoming connection on SECONDARYSOCKET.
+ */
+static CURLcode ftp_state_use_port(struct Curl_easy *data,
+ struct ftp_conn *ftpc,
+ ftpport fcmd) /* start with this */
+{
+ CURLcode result = CURLE_FTP_PORT_FAILED;
+ struct connectdata *conn = data->conn;
+ curl_socket_t portsock = CURL_SOCKET_BAD;
+
+ struct Curl_sockaddr_storage ss;
+ curl_socklen_t sslen;
+ char hbuf[NI_MAXHOST];
+ const char *host = NULL;
+ const char *string_ftpport = data->set.str[STRING_FTPPORT];
+ struct Curl_dns_entry *dns_entry = NULL;
+ const struct Curl_addrinfo *res = NULL;
+ const struct Curl_addrinfo *ai = NULL;
+ unsigned short port_min = 0;
+ unsigned short port_max = 0;
+ bool non_local = TRUE;
+
+ /* parse the FTPPORT string for address and port range */
+ result = ftp_port_parse_string(data, conn, string_ftpport,
+ &ss, &port_min, &port_max,
+ &host, hbuf, sizeof(hbuf));
+ if(!result && !host)
+ /* if no host was specified, use the control connection's local IP */
+ result = ftp_port_default_host(data, conn, &ss, &sslen, &host,
+ hbuf, sizeof(hbuf), &non_local);
+
+ /* resolve host string to address list */
+ if(!result)
+ result = ftp_port_resolve_host(data, conn, host, &dns_entry, &res);
+
+ /* Open a TCP socket for the data connection */
+ if(!result)
+ result = ftp_port_open_socket(data, conn, res, &ai, &portsock);
+ if(!result) {
+ CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket",
+ FTP_CSTATE(ftpc));
+
+ /* bind to a suitable local address / port */
+ result = ftp_port_bind_socket(data, conn, portsock, ai, &ss, &sslen,
+ port_min, port_max, non_local);
+ }
+
+ /* listen */
+ if(!result)
+ result = ftp_port_listen(data, portsock);
+
+ /* send the PORT / EPRT command */
+ if(!result)
+ result = ftp_port_send_command(data, ftpc, conn, &ss, ai, fcmd);
+
+ /* replace any filter on SECONDARY with one listening on this socket */
+ if(!result)
+ result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock);
- /* Replace any filter on SECONDARY with one listening on this socket */
- result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock);
if(!result)
portsock = CURL_SOCKET_BAD; /* now held in filter */
-out:
- /* If we looked up a dns_entry, now is the time to safely release it */
+ /* cleanup */
+
if(dns_entry)
Curl_resolv_unlink(data, &dns_entry);
if(result) {
ftp_state(data, ftpc, FTP_STOP);
}
else {
- /* successfully setup the list socket filter. Do we need more? */
+ /* successfully set up the listen socket filter. SSL needed? */
if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port &&
!Curl_conn_is_ssl(conn, SECONDARYSOCKET)) {
result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET);