Skip to content
This repository has been archived by the owner on Jul 8, 2020. It is now read-only.

Commit

Permalink
Merge branch 'notorca-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
lunny committed May 23, 2016
2 parents 60c0b22 + 5adf095 commit 08acff7
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 77 deletions.
34 changes: 28 additions & 6 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var (
"ENC": commandEnc{},
"EPRT": commandEprt{},
"EPSV": commandEpsv{},
//"FEAT": commandFeat{},
"FEAT": commandFeat{},
"LIST": commandList{},
"NLST": commandNlst{},
"MDTM": commandMdtm{},
Expand Down Expand Up @@ -175,6 +175,9 @@ func init() {
}

func (cmd commandFeat) Execute(conn *Conn, param string) {
if conn.tlsConfig != nil {
featCmds += " AUTH TLS\n PBSZ\n PROT\n"
}
conn.writeMessage(211, fmt.Sprintf(feats, featCmds))
}

Expand Down Expand Up @@ -314,7 +317,7 @@ func (cmd commandEpsv) Execute(conn *Conn, param string) {
return
}

socket, err := newPassiveSocket(addr.String()[:lastIdx], conn.logger)
socket, err := newPassiveSocket(addr.String()[:lastIdx], conn.logger, conn.tlsConfig)
if err != nil {
log.Error(err)
conn.writeMessage(425, "Data connection failed")
Expand Down Expand Up @@ -595,7 +598,7 @@ func (cmd commandPasv) Execute(conn *Conn, param string) {
conn.writeMessage(425, "Data connection failed")
return
}
socket, err := newPassiveSocket(parts[0], conn.logger)
socket, err := newPassiveSocket(parts[0], conn.logger, conn.tlsConfig)
if err != nil {
conn.writeMessage(425, "Data connection failed")
return
Expand Down Expand Up @@ -853,7 +856,16 @@ func (cmd commandAuth) RequireAuth() bool {
}

func (cmd commandAuth) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
log.Println(param, conn)
if param == "TLS" && conn.tlsConfig != nil {
conn.writeMessage(234, "AUTH command OK")
err := conn.upgradeToTLS()
if err != nil {
conn.logger.Printf("Error upgrading conection to TLS %v", err)
}
} else {
conn.writeMessage(550, "Action not taken")
}
}

type commandCcc struct{}
Expand Down Expand Up @@ -925,7 +937,11 @@ func (cmd commandPbsz) RequireAuth() bool {
}

func (cmd commandPbsz) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
if conn.tls && param == "0" {
conn.writeMessage(200, "OK")
} else {
conn.writeMessage(550, "Action not taken")
}
}

type commandProt struct{}
Expand All @@ -943,7 +959,13 @@ func (cmd commandProt) RequireAuth() bool {
}

func (cmd commandProt) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
if conn.tls && param == "P" {
conn.writeMessage(200, "OK")
} else if conn.tls {
conn.writeMessage(536, "Only P level is supported")
} else {
conn.writeMessage(550, "Action not taken")
}
}

type commandConf struct{}
Expand Down
87 changes: 55 additions & 32 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
Expand All @@ -26,13 +27,16 @@ type Conn struct {
auth Auth
logger *Logger
server *Server
sessionId string
tlsConfig *tls.Config
sessionID string
namePrefix string
reqUser string
user string
renameFrom string
lastFilePos int64
appendData bool
closed bool
tls bool
}

func (conn *Conn) LoginUser() string {
Expand All @@ -44,7 +48,7 @@ func (conn *Conn) IsLogin() bool {
}

// returns a random 20 char string that can be used as a unique session ID
func newSessionId() string {
func newSessionID() string {
hash := sha256.New()
_, err := io.CopyN(hash, rand.Reader, 50)
if err != nil {
Expand Down Expand Up @@ -76,6 +80,11 @@ func (conn *Conn) Serve() {
break
}
conn.receiveLine(line)
// QUIT command closes connection, break to avoid error on reading from
// closed socket
if conn.closed == true {
break
}
}
conn.Close()
conn.logger.Print("Connection Terminated")
Expand All @@ -84,32 +93,46 @@ func (conn *Conn) Serve() {
// Close will manually close this connection, even if the client isn't ready.
func (conn *Conn) Close() {
conn.conn.Close()
conn.closed = true
if conn.dataConn != nil {
conn.dataConn.Close()
conn.dataConn = nil
}
}

func (conn *Conn) upgradeToTLS() error {
conn.logger.Print("Upgrading connectiion to TLS")
tlsConn := tls.Server(conn.conn, conn.tlsConfig)
err := tlsConn.Handshake()
if err == nil {
conn.conn = tlsConn
conn.controlReader = bufio.NewReader(tlsConn)
conn.controlWriter = bufio.NewWriter(tlsConn)
conn.tls = true
}
return err
}

// receiveLine accepts a single line FTP command and co-ordinates an
// appropriate response.
func (Conn *Conn) receiveLine(line string) {
command, param := Conn.parseLine(line)
Conn.logger.PrintCommand(command, param)
func (conn *Conn) receiveLine(line string) {
command, param := conn.parseLine(line)
conn.logger.PrintCommand(command, param)
cmdObj := commands[strings.ToUpper(command)]
if cmdObj == nil {
Conn.writeMessage(500, "Command not found")
conn.writeMessage(500, "Command not found")
return
}
if cmdObj.RequireParam() && param == "" {
Conn.writeMessage(553, "action aborted, required param missing")
} else if cmdObj.RequireAuth() && Conn.user == "" {
Conn.writeMessage(530, "not logged in")
conn.writeMessage(553, "action aborted, required param missing")
} else if cmdObj.RequireAuth() && conn.user == "" {
conn.writeMessage(530, "not logged in")
} else {
cmdObj.Execute(Conn, param)
cmdObj.Execute(conn, param)
}
}

func (Conn *Conn) parseLine(line string) (string, string) {
func (conn *Conn) parseLine(line string) (string, string) {
params := strings.SplitN(strings.Trim(line, "\r\n"), " ", 2)
if len(params) == 1 {
return params[0], ""
Expand All @@ -118,11 +141,11 @@ func (Conn *Conn) parseLine(line string) (string, string) {
}

// writeMessage will send a standard FTP response back to the client.
func (Conn *Conn) writeMessage(code int, message string) (wrote int, err error) {
Conn.logger.PrintResponse(code, message)
func (conn *Conn) writeMessage(code int, message string) (wrote int, err error) {
conn.logger.PrintResponse(code, message)
line := fmt.Sprintf("%d %s\r\n", code, message)
wrote, err = Conn.controlWriter.WriteString(line)
Conn.controlWriter.Flush()
wrote, err = conn.controlWriter.WriteString(line)
conn.controlWriter.Flush()
return
}

Expand All @@ -143,43 +166,43 @@ func (Conn *Conn) writeMessage(code int, message string) (wrote int, err error)
// The driver implementation is responsible for deciding how to treat this path.
// Obviously they MUST NOT just read the path off disk. The probably want to
// prefix the path with something to scope the users access to a sandbox.
func (Conn *Conn) buildPath(filename string) (fullPath string) {
func (conn *Conn) buildPath(filename string) (fullPath string) {
if len(filename) > 0 && filename[0:1] == "/" {
fullPath = filepath.Clean(filename)
} else if len(filename) > 0 && filename != "-a" {
fullPath = filepath.Clean(Conn.namePrefix + "/" + filename)
fullPath = filepath.Clean(conn.namePrefix + "/" + filename)
} else {
fullPath = filepath.Clean(Conn.namePrefix)
fullPath = filepath.Clean(conn.namePrefix)
}
fullPath = strings.Replace(fullPath, "//", "/", -1)
return
}

// sendOutofbandData will send a string to the client via the currently open
// data socket. Assumes the socket is open and ready to be used.
func (Conn *Conn) sendOutofbandData(data []byte) {
func (conn *Conn) sendOutofbandData(data []byte) {
bytes := len(data)
if Conn.dataConn != nil {
Conn.dataConn.Write(data)
Conn.dataConn.Close()
Conn.dataConn = nil
if conn.dataConn != nil {
conn.dataConn.Write(data)
conn.dataConn.Close()
conn.dataConn = nil
}
message := "Closing data connection, sent " + strconv.Itoa(bytes) + " bytes"
Conn.writeMessage(226, message)
conn.writeMessage(226, message)
}

func (Conn *Conn) sendOutofBandDataWriter(data io.ReadCloser) error {
Conn.lastFilePos = 0
bytes, err := io.Copy(Conn.dataConn, data)
func (conn *Conn) sendOutofBandDataWriter(data io.ReadCloser) error {
conn.lastFilePos = 0
bytes, err := io.Copy(conn.dataConn, data)
if err != nil {
Conn.dataConn.Close()
Conn.dataConn = nil
conn.dataConn.Close()
conn.dataConn = nil
return err
}
message := "Closing data connection, sent " + strconv.Itoa(int(bytes)) + " bytes"
Conn.writeMessage(226, message)
Conn.dataConn.Close()
Conn.dataConn = nil
conn.writeMessage(226, message)
conn.dataConn.Close()
conn.dataConn = nil

return nil
}
12 changes: 6 additions & 6 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import (

// Use an instance of this to log in a standard format
type Logger struct {
sessionId string
sessionID string
}

func newLogger(id string) *Logger {
l := new(Logger)
l.sessionId = id
l.sessionID = id
return l
}

func (logger *Logger) Print(message interface{}) {
log.Printf("%s %s", logger.sessionId, message)
log.Printf("%s %s", logger.sessionID, message)
}

func (logger *Logger) Printf(format string, v ...interface{}) {
Expand All @@ -26,12 +26,12 @@ func (logger *Logger) Printf(format string, v ...interface{}) {

func (logger *Logger) PrintCommand(command string, params string) {
if command == "PASS" {
log.Printf("%s > PASS ****", logger.sessionId)
log.Printf("%s > PASS ****", logger.sessionID)
} else {
log.Printf("%s > %s %s", logger.sessionId, command, params)
log.Printf("%s > %s %s", logger.sessionID, command, params)
}
}

func (logger *Logger) PrintResponse(code int, message string) {
log.Printf("%s < %d %s", logger.sessionId, code, message)
log.Printf("%s < %d %s", logger.sessionID, code, message)
}
Loading

0 comments on commit 08acff7

Please sign in to comment.