Index: Makefile =================================================================== RCS file: /cvs/src/libexec/ftpd/Makefile,v retrieving revision 1.22 diff -u -r1.22 Makefile --- Makefile 5 Jan 2004 02:55:28 -0000 1.22 +++ Makefile 27 Mar 2005 02:16:03 -0000 @@ -20,7 +20,7 @@ # not really used CPPFLAGS+=-DINET6 -LDADD+= -lutil +LDADD+= -lutil -lm DPADD+= ${LIBUTIL} .if (${TCP_WRAPPERS:L} == "yes") Index: ftpd.8 =================================================================== RCS file: /cvs/src/libexec/ftpd/ftpd.8,v retrieving revision 1.61 diff -u -r1.61 ftpd.8 --- ftpd.8 20 Nov 2003 12:32:34 -0000 1.61 +++ ftpd.8 27 Mar 2005 02:16:03 -0000 @@ -39,6 +39,7 @@ .Sh SYNOPSIS .Nm ftpd .Op Fl 46ADdlMnPSU +.Op Fl p Ar low:high .Op Fl T Ar maxtimeout .Op Fl t Ar timeout .Op Fl u Ar mask @@ -114,6 +115,15 @@ and requires it use the same source address as the connection came from. This prevents the "FTP bounce attack" against services on both the local machine and other local machines. +.It Fl p Ar low:high +tells +.Nm +in PASV mode to only bind to ports between +.Ar low +and +.Ar high +inclusive. Be warned, setting the range to be too small +may result in failed data connection attempts on busy servers. .It Fl S With this option set, .Nm Index: ftpd.c =================================================================== RCS file: /cvs/src/libexec/ftpd/ftpd.c,v retrieving revision 1.153 diff -u -r1.153 ftpd.c --- ftpd.c 12 Dec 2003 19:45:22 -0000 1.153 +++ ftpd.c 27 Mar 2005 02:16:03 -0000 @@ -103,6 +103,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,8 @@ int usedefault = 1; /* for data transfers */ int pdata = -1; /* for passive mode */ int family = AF_UNSPEC; +int pasv_low = 0; +int pasv_high = 0; volatile sig_atomic_t transflag; off_t file_size; off_t byte_count; @@ -227,6 +230,7 @@ syslog(LOG_INFO, "%s %s%s = %qd bytes", \ cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \ } +#define PASV_PORT() (random() % (pasv_high - pasv_low) + pasv_low) static void ack(char *); static void sigurg(int); @@ -267,13 +271,13 @@ return (guest ? path+1 : path); } -char *argstr = "AdDhnlMSt:T:u:UvP46"; +char *argstr = "AdDhnlMSt:T:u:UvPp:46"; static void usage(void) { syslog(LOG_ERR, - "usage: ftpd [-46ADdlMnPSU] [-T maxtimeout] [-t timeout] [-u mask]"); + "usage: ftpd [-46ADdlMnPSU] [-p low:high] [-T maxtimeout] [-t timeout] [-u mask]"); exit(2); } @@ -326,6 +330,35 @@ anon_ok = 0; break; + case 'p': + { +#define PORT_MAX IPPORT_HILASTAUTO + long pl, ph; + char *slop; + + pl = strtol(optarg, &slop, 0); + if (*slop != ':') + pl = -1; + else { + ph = strtol(slop + 1, &slop, 0); + if (*slop != '\0') + pl = -1; + } + + if (pl <= 0 || pl > PORT_MAX || ph <= pl || + ph > PORT_MAX) { + syslog(LOG_ERR, + "ftpd: %s is a bad port range for -p, aborting..", + optarg); + exit(2); + + } else { + pasv_low = pl; + pasv_high = ph; + } + break; + } + case 'S': stats = 1; break; @@ -2180,7 +2213,7 @@ passive(void) { socklen_t len; - int on; + int on, try, maxtries; u_char *p, *a; if (pw == NULL) { @@ -2204,16 +2237,35 @@ return; } - on = IP_PORTRANGE_HIGH; - if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, - (char *)&on, sizeof(on)) < 0) - goto pasv_error; - pasv_addr = ctrl_addr; pasv_addr.su_sin.sin_port = 0; - if (bind(pdata, (struct sockaddr *)&pasv_addr, - pasv_addr.su_len) < 0) - goto pasv_error; + + if (pasv_low > 0) { + pasv_addr.su_sin.sin_port = PASV_PORT(); + maxtries = (pasv_high - pasv_low) * (int)log(pasv_high - pasv_low); + } else { + on = IP_PORTRANGE_HIGH; + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + (char *)&on, sizeof(on)) < 0) + goto pasv_error; + } + + /* Try to bind to a random port. + * Iff we're using a specified range, there's a possibility + * the port we generate is already in use. If it is, we + * try another in the range. We should only get EADDRINUSE + * if we use this method. + */ + try = 0; + while (bind(pdata, (struct sockaddr *)&pasv_addr, + pasv_addr.su_len) < 0) { + if(errno == EADDRINUSE && try < maxtries) { + pasv_addr.su_sin.sin_port = PASV_PORT(); + try++; + } else + goto pasv_error; + + } len = sizeof(pasv_addr); if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) @@ -2297,7 +2349,7 @@ long_passive(char *cmd, int pf) { socklen_t len; - int on; + int on, try, maxtries; u_char *p, *a; if (!logged_in) { @@ -2336,25 +2388,59 @@ return; } + pasv_addr = ctrl_addr; + pasv_addr.su_port = 0; + switch (ctrl_addr.su_family) { case AF_INET: - on = IP_PORTRANGE_HIGH; - if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, - (char *)&on, sizeof(on)) < 0) - goto pasv_error; + if ( pasv_low > 0) { + pasv_addr.su_sin.sin_port = PASV_PORT(); + maxtries = (pasv_high - pasv_low) * + (int)log(pasv_high - pasv_low); + } else { + on = IP_PORTRANGE_HIGH; + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + (char *)&on, sizeof(on)) < 0) + goto pasv_error; + } break; case AF_INET6: - on = IPV6_PORTRANGE_HIGH; - if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, - (char *)&on, sizeof(on)) < 0) - goto pasv_error; + if ( pasv_high > 0) { + pasv_addr.su_sin6.sin6_port = PASV_PORT(); + maxtries = (pasv_high - pasv_low) * + (int)log(pasv_high - pasv_low); + } else { + on = IPV6_PORTRANGE_HIGH; + if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, + (char *)&on, sizeof(on)) < 0) + goto pasv_error; + } break; } - pasv_addr = ctrl_addr; - pasv_addr.su_port = 0; - if (bind(pdata, (struct sockaddr *) &pasv_addr, pasv_addr.su_len) < 0) - goto pasv_error; + /* Try to bind to a random port. + * Iff we're using a specified range, there's a possibility + * the port we generate is already in use. If it is, we + * try another in the range. We should only get EADDRINUSE + * if we use this method. + */ + try = 0; + while (bind(pdata, (struct sockaddr *)&pasv_addr, + pasv_addr.su_len) < 0) { + if(errno == EADDRINUSE && try < maxtries) { + switch (ctrl_addr.su_family) { + case AF_INET: + pasv_addr.su_sin.sin_port = PASV_PORT(); + break; + case AF_INET6: + pasv_addr.su_sin6.sin6_port = PASV_PORT(); + break; + } + try++; + } else + goto pasv_error; + } + len = pasv_addr.su_len; if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) goto pasv_error;