Skip to content

Sunil Nimmagadda

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.