Implement FTPS implicit FTP over TLS

This commit is contained in:
Catalin Tanasescu 2017-08-11 15:28:41 +03:00
parent 769512c448
commit 8c9122ed82
5 changed files with 100 additions and 14 deletions

View File

@ -6,6 +6,7 @@ go:
- 1.8.1 - 1.8.1
env: env:
- FTP_SERVER=vsftpd - FTP_SERVER=vsftpd
- FTP_SERVER=vsftpd_implicit_tls
- FTP_SERVER=proftpd - FTP_SERVER=proftpd
before_install: before_install:
- sudo $TRAVIS_BUILD_DIR/.travis/prepare.sh "$FTP_SERVER" - sudo $TRAVIS_BUILD_DIR/.travis/prepare.sh "$FTP_SERVER"

View File

@ -1,18 +1,31 @@
#!/bin/sh -e #!/bin/sh -e
mkdir --mode 0777 -p /var/ftp/incoming
case "$1" in case "$1" in
proftpd) proftpd)
mkdir -p /etc/proftpd/conf.d/ mkdir -p /etc/proftpd/conf.d/
cp $TRAVIS_BUILD_DIR/.travis/proftpd.conf /etc/proftpd/conf.d/ cp $TRAVIS_BUILD_DIR/.travis/proftpd.conf /etc/proftpd/conf.d/
apt-get install -qq "$1"
;; ;;
vsftpd) vsftpd)
cp $TRAVIS_BUILD_DIR/.travis/vsftpd.conf /etc/vsftpd.conf cp $TRAVIS_BUILD_DIR/.travis/vsftpd.conf /etc/vsftpd.conf
apt-get install -qq "$1"
;;
vsftpd_implicit_tls)
openssl req \
-new \
-newkey rsa:1024 \
-days 365 \
-nodes \
-x509 \
-subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=localhost" \
-keyout /etc/ssl/certs/vsftpd.pem \
-out /etc/ssl/certs/vsftpd.pem
cp $TRAVIS_BUILD_DIR/.travis/vsftpd_implicit_tls.conf /etc/vsftpd.conf
apt-get install -qq vsftpd
;; ;;
*) *)
echo "unknown software: $1" echo "unknown software: $1"
exit 1 exit 1
esac esac
mkdir --mode 0777 -p /var/ftp/incoming
apt-get install -qq "$1"

View File

@ -0,0 +1,33 @@
# Used by Travis CI
listen=NO
listen_ipv6=YES
write_enable=YES
dirmessage_enable=YES
secure_chroot_dir=/var/run/vsftpd/empty
anonymous_enable=YES
anon_root=/var/ftp
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
anon_umask=022
force_local_logins_ssl=YES
force_local_data_ssl=YES
ssl_enable=YES
implicit_ssl=YES
listen_port=21
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
rsa_cert_file=/etc/ssl/certs/vsftpd.pem
rsa_private_key_file=/etc/ssl/certs/vsftpd.pem
require_ssl_reuse=NO
ssl_ciphers=HIGH
xferlog_enable=yes
log_ftp_protocol=true
xferlog_file=/var/log/vsftpd.log
debug_ssl=true
allow_anon_ssl=true

View File

@ -2,8 +2,10 @@ package ftp
import ( import (
"bytes" "bytes"
"crypto/tls"
"io/ioutil" "io/ioutil"
"net/textproto" "net/textproto"
"os"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -14,20 +16,39 @@ const (
testDir = "mydir" testDir = "mydir"
) )
func isTLSServer() bool {
return os.Getenv("FTP_SERVER") == "vsftpd_implicit_tls"
}
func getConnection() (*ServerConn, error) {
if isTLSServer() {
return DialImplicitTLS("localhost:21", &tls.Config{InsecureSkipVerify: true})
} else {
return DialTimeout("localhost:21", 5*time.Second)
}
}
func TestConnPASV(t *testing.T) { func TestConnPASV(t *testing.T) {
testConn(t, true) testConn(t, true, false)
} }
func TestConnEPSV(t *testing.T) { func TestConnEPSV(t *testing.T) {
testConn(t, false) testConn(t, false, false)
} }
func testConn(t *testing.T, disableEPSV bool) { func TestConnTLS(t *testing.T) {
if !isTLSServer() {
t.Skip("skipping test in non TLS server env.")
}
testConn(t, false, true)
}
func testConn(t *testing.T, disableEPSV bool, implicitTLS bool) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
c, err := getConnection()
c, err := DialTimeout("localhost:21", 5*time.Second)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -207,6 +228,9 @@ func TestConnIPv6(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
if isTLSServer() {
t.Skip("skipping test in TLS mode.")
}
c, err := DialTimeout("[::1]:21", 5*time.Second) c, err := DialTimeout("[::1]:21", 5*time.Second)
if err != nil { if err != nil {
@ -228,7 +252,7 @@ func TestConnIPv6(t *testing.T) {
// TestConnect tests the legacy Connect function // TestConnect tests the legacy Connect function
func TestConnect(t *testing.T) { func TestConnect(t *testing.T) {
if testing.Short() { if testing.Short() || isTLSServer() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
@ -244,6 +268,9 @@ func TestTimeout(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
if isTLSServer() {
t.Skip("skipping test in TLS mode.")
}
c, err := DialTimeout("localhost:2121", 1*time.Second) c, err := DialTimeout("localhost:2121", 1*time.Second)
if err == nil { if err == nil {
@ -251,13 +278,12 @@ func TestTimeout(t *testing.T) {
c.Quit() c.Quit()
} }
} }
func TestWrongLogin(t *testing.T) { func TestWrongLogin(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
c, err := DialTimeout("localhost:21", 5*time.Second) c, err := getConnection()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -273,7 +299,7 @@ func TestDeleteDirRecur(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
c, err := DialTimeout("localhost:21", 5*time.Second) c, err := getConnection()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -355,7 +381,7 @@ func TestFileDeleteDirRecur(t *testing.T) {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
c, err := DialTimeout("localhost:21", 5*time.Second) c, err := getConnection()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -414,7 +440,7 @@ func TestMissingFolderDeleteDirRecur(t *testing.T) {
t.Skip("skipping test in short mode.") t.Skip("skipping test in short mode.")
} }
c, err := DialTimeout("localhost:21", 5*time.Second) c, err := getConnection()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

13
ftp.go
View File

@ -5,6 +5,7 @@ package ftp
import ( import (
"bufio" "bufio"
"crypto/tls"
"errors" "errors"
"io" "io"
"net" "net"
@ -62,6 +63,15 @@ func Dial(addr string) (*ServerConn, error) {
return DialTimeout(addr, 0) return DialTimeout(addr, 0)
} }
// Dial a ftps server with implicit TLS
func DialImplicitTLS(addr string, config *tls.Config) (*ServerConn, error) {
tconn, err := tls.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return dialServer(tconn, 0)
}
// DialTimeout initializes the connection to the specified ftp server address. // DialTimeout initializes the connection to the specified ftp server address.
// //
// It is generally followed by a call to Login() as most FTP commands require // It is generally followed by a call to Login() as most FTP commands require
@ -71,7 +81,10 @@ func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dialServer(tconn, timeout)
}
func dialServer(tconn net.Conn, timeout time.Duration) (*ServerConn, error) {
// Use the resolved IP address in case addr contains a domain name // Use the resolved IP address in case addr contains a domain name
// If we use the domain name, we might not resolve to the same IP. // If we use the domain name, we might not resolve to the same IP.
remoteAddr := tconn.RemoteAddr().String() remoteAddr := tconn.RemoteAddr().String()