Implement FTPS implicit FTP over TLS
This commit is contained in:
parent
769512c448
commit
8c9122ed82
@ -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"
|
||||||
|
@ -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"
|
|
||||||
|
33
.travis/vsftpd_implicit_tls.conf
Normal file
33
.travis/vsftpd_implicit_tls.conf
Normal 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
|
@ -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
13
ftp.go
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user