Fallback
An excerpt from ftp(1)…
"-A Force active mode FTP. By default, ftp will try to use passive
mode FTP and fall back to active mode if passive is not supported
by the server. This option causes ftp to always use an active
connection."
The modes and commands that start them…
Active: PORT, LPRT, EPRT
Passive: PASV, LPSV, EPSV
The fallback mechanism involving PASV/PORT, LPSV/LPRT and EPSV/EPRT commands was difficult to follow from the original implementation at that time. The ifdefs and IPv4, IPv6 split were hindering readability.
EPSV/EPRT commands from rfc2428 are standard and supersede other variants. If all the mirrors support them, the fallback becomes as simple as…
if (activemode)
data_fd = ftp_eprt(ctrl_fp);
else if ((data_fd = ftp_epsv(ctrl_fp)) == -1)
data_fd = ftp_eprt(ctrl_fp);
where…
activemode
is off by default unless -A is specified,
ftp_eprt()
implements FTP active mode,
ftp_epsv()
implements FTP passive mode.
Piping a newline separated list of FTP mirrors over stdin to the following
code shows that all the mirrors do support EPSV/EPRT (barring the mirrors that timeout
and refused connection
).
package main
import (
"bufio"
"fmt"
"net/textproto"
"os"
)
func main() {
ch := make(chan string)
input := bufio.NewScanner(os.Stdin)
n := 0
for input.Scan() {
go epsv(input.Text(), ch)
n++
}
for i := 0; i < n; i++ {
fmt.Println(<-ch)
}
}
func epsv(mirror string, ch chan<- string) {
conn, err := textproto.Dial("tcp", mirror+":21")
if err != nil {
ch <- fmt.Sprintf("%s: %s", mirror, err)
return
}
defer conn.Cmd("QUIT")
defer conn.Close()
// greeting
_, _, err = conn.Reader.ReadResponse(2)
if err != nil {
ch <- fmt.Sprintf("%s: %s", mirror, err)
return
}
cmds := []struct {
cmd string
code int
}{
{"USER anonymous", 3},
{"PASS foo@bar.net", 2},
{"EPSV", 2},
}
for _, c := range cmds {
if err = FTPCmd(conn, c.cmd, c.code); err != nil {
ch <- fmt.Sprintf("%s: %s", mirror, err)
return
}
}
ch <- fmt.Sprintf("%s: done", mirror)
}
func FTPCmd(conn *textproto.Conn, cmd string, expectCode int) (err error) {
if _, err = conn.Cmd(cmd); err != nil {
return err
}
_, _, err = conn.Reader.ReadResponse(expectCode)
return err
}
A new and cleaner implementation followed.