diff --git a/main.go b/main.go index 15d316b..021a4d7 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ var ( connectCaPath = connect.Flag("ca", "Path to CA bundle (system default if unspecified).").ExistingFile() connectCert = connect.Flag("cert", "Client certificate chain for connecting to server (PEM).").ExistingFile() connectKey = connect.Flag("key", "Private key for client certificate, if not in same file (PEM).").ExistingFile() - connectStartTLS = connect.Flag("start-tls", "Enable StartTLS protocol (supports 'ldap', 'mysql', 'postgres' and 'smtp').").Short('t').PlaceHolder("PROTOCOL").Enum("mysql", "postgres", "psql", "smtp", "ldap") + connectStartTLS = connect.Flag("start-tls", "Enable StartTLS protocol ('ldap', 'mysql', 'postgres', 'smtp' or 'ftp').").Short('t').PlaceHolder("PROTOCOL").Enum("mysql", "postgres", "psql", "smtp", "ldap", "ftp") connectPem = connect.Flag("pem", "Write output as PEM blocks instead of human-readable format.").Short('m').Bool() connectJSON = connect.Flag("json", "Write output as machine-readable JSON format.").Short('j').Bool() diff --git a/starttls/ftp.go b/starttls/ftp.go new file mode 100644 index 0000000..3953fff --- /dev/null +++ b/starttls/ftp.go @@ -0,0 +1,73 @@ +/*- + * Copyright 2017 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package starttls + +import ( + "bufio" + "crypto/tls" + "errors" + "fmt" + "net" + "strings" +) + +func dumpAuthTLSFromFTP(address string, config *tls.Config) (*tls.ConnectionState, error) { + c, err := net.Dial("tcp", address) + if err != nil { + return nil, err + } + + conn := c.(*net.TCPConn) + status, err := readFTP(conn) + if err != nil { + return nil, err + } + if status != "220" { + return nil, fmt.Errorf("FTP server responded with status %s, was expecting 220", status) + } + + fmt.Fprintf(conn, "AUTH TLS\r\n") + status, err = readFTP(conn) + if err != nil { + return nil, err + } + if status != "234" { + return nil, fmt.Errorf("FTP server responded with status %s, was expecting 234", status) + } + + tlsConn := tls.Client(conn, config) + err = tlsConn.Handshake() + if err != nil { + return nil, err + } + + state := tlsConn.ConnectionState() + return &state, nil +} + +func readFTP(conn *net.TCPConn) (string, error) { + reader := bufio.NewReader(conn) + response, err := reader.ReadString('\n') + if err != nil { + return "", err + } + status := strings.Split(response, " ") + if len(status) == 0 { + return "", errors.New("garbled response from FTP server after AUTH TLS command") + } + return status[0], nil +} diff --git a/starttls/starttls.go b/starttls/starttls.go index a1308db..4c36039 100644 --- a/starttls/starttls.go +++ b/starttls/starttls.go @@ -121,6 +121,8 @@ func GetConnectionState(startTLSType, connectName, connectTo, clientCert, client panic("SMTP Connection isn't TLS after we successfully called StartTLS") } state = &smtpState + case "ftp": + state, err = dumpAuthTLSFromFTP(connectTo, tlsConfig) default: return nil, fmt.Errorf("error connecting: unknown StartTLS protocol '%s'\n", startTLSType) }