diff --git a/ftp.go b/ftp.go index e4d63c1..fc5c680 100644 --- a/ftp.go +++ b/ftp.go @@ -153,16 +153,66 @@ func (c *ServerConn) epsv() (port int, err error) { return } +// pasv issues an "PASV" command to get a port number for a data connection. +func (c *ServerConn) pasv() (port int, err error) { + _, line, err := c.cmd(StatusPassiveMode, "PASV") + if err != nil { + return + } + + // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + start := strings.Index(line, "(") + end := strings.LastIndex(line, ")") + if start == -1 || end == -1 { + err = errors.New("Invalid EPSV response format") + return + } + + // We have to split the response string + pasvData := strings.Split(line[start+1 : end], ",") + // Let's compute the port number + portPart1, err1 := strconv.Atoi(pasvData[4]) + if err1 != nil { + err = err1 + return + } + + portPart2, err2 := strconv.Atoi(pasvData[5]) + if err2 != nil { + err = err2 + return + } + + // Recompose port + port = portPart1 * 256 + portPart2 + return +} + // openDataConn creates a new FTP data connection. // // Currently, only EPSV is implemented but a fallback to PASV, and to a lesser // extent, PORT should be added. func (c *ServerConn) openDataConn() (net.Conn, error) { - port, err := c.epsv() - if err != nil { + var port int + var err error + + // If features contains nat6 or EPSV => EPSV + // else -> PASV + _,nat6Supported := c.features["nat6"]; + _,epsvSupported := c.features["EPSV"]; + if nat6Supported || epsvSupported { + port, err = c.epsv() + if err != nil { return nil, err + } + } else { + port, err = c.pasv() + if err != nil { + return nil, err + } } + // Build the new net address string addr := fmt.Sprintf("%s:%d", c.host, port)