rclone/vendor/goftp.io/server/cmd.go

1247 lines
28 KiB
Go

// Copyright 2018 The goftp Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package server
import (
"encoding/binary"
"fmt"
"log"
"strconv"
"strings"
)
// Command represents a Command interface to a ftp command
type Command interface {
IsExtend() bool
RequireParam() bool
RequireAuth() bool
Execute(*Conn, string)
}
type commandMap map[string]Command
var (
commands = commandMap{
"ADAT": commandAdat{},
"ALLO": commandAllo{},
"APPE": commandAppe{},
"AUTH": commandAuth{},
"CDUP": commandCdup{},
"CWD": commandCwd{},
"CCC": commandCcc{},
"CONF": commandConf{},
"DELE": commandDele{},
"ENC": commandEnc{},
"EPRT": commandEprt{},
"EPSV": commandEpsv{},
"FEAT": commandFeat{},
"LIST": commandList{},
"LPRT": commandLprt{},
"NLST": commandNlst{},
"MDTM": commandMdtm{},
"MIC": commandMic{},
"MKD": commandMkd{},
"MODE": commandMode{},
"NOOP": commandNoop{},
"OPTS": commandOpts{},
"PASS": commandPass{},
"PASV": commandPasv{},
"PBSZ": commandPbsz{},
"PORT": commandPort{},
"PROT": commandProt{},
"PWD": commandPwd{},
"QUIT": commandQuit{},
"RETR": commandRetr{},
"REST": commandRest{},
"RNFR": commandRnfr{},
"RNTO": commandRnto{},
"RMD": commandRmd{},
"SIZE": commandSize{},
"STOR": commandStor{},
"STRU": commandStru{},
"SYST": commandSyst{},
"TYPE": commandType{},
"USER": commandUser{},
"XCUP": commandCdup{},
"XCWD": commandCwd{},
"XMKD": commandMkd{},
"XPWD": commandPwd{},
"XRMD": commandRmd{},
}
)
// commandAllo responds to the ALLO FTP command.
//
// This is essentially a ping from the client so we just respond with an
// basic OK message.
type commandAllo struct{}
func (cmd commandAllo) IsExtend() bool {
return false
}
func (cmd commandAllo) RequireParam() bool {
return false
}
func (cmd commandAllo) RequireAuth() bool {
return false
}
func (cmd commandAllo) Execute(conn *Conn, param string) {
conn.writeMessage(202, "Obsolete")
}
// commandAppe responds to the APPE FTP command. It allows the user to upload a
// new file but always append if file exists otherwise create one.
type commandAppe struct{}
func (cmd commandAppe) IsExtend() bool {
return false
}
func (cmd commandAppe) RequireParam() bool {
return true
}
func (cmd commandAppe) RequireAuth() bool {
return true
}
func (cmd commandAppe) Execute(conn *Conn, param string) {
targetPath := conn.buildPath(param)
conn.writeMessage(150, "Data transfer starting")
conn.server.notifiers.BeforePutFile(conn, targetPath)
size, err := conn.driver.PutFile(targetPath, conn.dataConn, true)
conn.server.notifiers.AfterFilePut(conn, targetPath, size, err)
if err == nil {
msg := fmt.Sprintf("OK, received %d bytes", size)
conn.writeMessage(226, msg)
} else {
conn.writeMessage(450, fmt.Sprint("error during transfer: ", err))
}
}
type commandOpts struct{}
func (cmd commandOpts) IsExtend() bool {
return false
}
func (cmd commandOpts) RequireParam() bool {
return false
}
func (cmd commandOpts) RequireAuth() bool {
return false
}
func (cmd commandOpts) Execute(conn *Conn, param string) {
parts := strings.Fields(param)
if len(parts) != 2 {
conn.writeMessage(550, "Unknow params")
return
}
if strings.ToUpper(parts[0]) != "UTF8" {
conn.writeMessage(550, "Unknow params")
return
}
if strings.ToUpper(parts[1]) == "ON" {
conn.writeMessage(200, "UTF8 mode enabled")
} else {
conn.writeMessage(550, "Unsupported non-utf8 mode")
}
}
type commandFeat struct{}
func (cmd commandFeat) IsExtend() bool {
return false
}
func (cmd commandFeat) RequireParam() bool {
return false
}
func (cmd commandFeat) RequireAuth() bool {
return false
}
var (
feats = "Extensions supported:\n%s"
featCmds = " UTF8\n"
)
func init() {
for k, v := range commands {
if v.IsExtend() {
featCmds = featCmds + " " + k + "\n"
}
}
}
func (cmd commandFeat) Execute(conn *Conn, param string) {
conn.writeMessageMultiline(211, conn.server.feats)
}
// cmdCdup responds to the CDUP FTP command.
//
// Allows the client change their current directory to the parent.
type commandCdup struct{}
func (cmd commandCdup) IsExtend() bool {
return false
}
func (cmd commandCdup) RequireParam() bool {
return false
}
func (cmd commandCdup) RequireAuth() bool {
return true
}
func (cmd commandCdup) Execute(conn *Conn, param string) {
otherCmd := &commandCwd{}
otherCmd.Execute(conn, "..")
}
// commandCwd responds to the CWD FTP command. It allows the client to change the
// current working directory.
type commandCwd struct{}
func (cmd commandCwd) IsExtend() bool {
return false
}
func (cmd commandCwd) RequireParam() bool {
return true
}
func (cmd commandCwd) RequireAuth() bool {
return true
}
func (cmd commandCwd) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
info, err := conn.driver.Stat(path)
if err != nil {
conn.writeMessage(550, fmt.Sprint("Directory change to ", path, " failed: ", err))
return
}
if !info.IsDir() {
conn.writeMessage(550, fmt.Sprint("Directory change to ", path, " is a file"))
return
}
conn.server.notifiers.BeforeChangeCurDir(conn, conn.curDir, path)
err = conn.changeCurDir(path)
conn.server.notifiers.AfterCurDirChanged(conn, conn.curDir, path, err)
if err == nil {
conn.writeMessage(250, "Directory changed to "+path)
} else {
conn.writeMessage(550, fmt.Sprint("Directory change to ", path, " failed: ", err))
}
}
// commandDele responds to the DELE FTP command. It allows the client to delete
// a file
type commandDele struct{}
func (cmd commandDele) IsExtend() bool {
return false
}
func (cmd commandDele) RequireParam() bool {
return true
}
func (cmd commandDele) RequireAuth() bool {
return true
}
func (cmd commandDele) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
conn.server.notifiers.BeforeDeleteFile(conn, path)
err := conn.driver.DeleteFile(path)
conn.server.notifiers.AfterFileDeleted(conn, path, err)
if err == nil {
conn.writeMessage(250, "File deleted")
} else {
conn.writeMessage(550, fmt.Sprint("File delete failed: ", err))
}
}
// commandEprt responds to the EPRT FTP command. It allows the client to
// request an active data socket with more options than the original PORT
// command. It mainly adds ipv6 support.
type commandEprt struct{}
func (cmd commandEprt) IsExtend() bool {
return true
}
func (cmd commandEprt) RequireParam() bool {
return true
}
func (cmd commandEprt) RequireAuth() bool {
return true
}
func (cmd commandEprt) Execute(conn *Conn, param string) {
delim := string(param[0:1])
parts := strings.Split(param, delim)
addressFamily, err := strconv.Atoi(parts[1])
host := parts[2]
port, err := strconv.Atoi(parts[3])
if addressFamily != 1 && addressFamily != 2 {
conn.writeMessage(522, "Network protocol not supported, use (1,2)")
return
}
socket, err := newActiveSocket(host, port, conn.logger, conn.sessionID)
if err != nil {
conn.writeMessage(425, "Data connection failed")
return
}
conn.dataConn = socket
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
}
// commandLprt responds to the LPRT FTP command. It allows the client to
// request an active data socket with more options than the original PORT
// command. FTP Operation Over Big Address Records.
type commandLprt struct{}
func (cmd commandLprt) IsExtend() bool {
return true
}
func (cmd commandLprt) RequireParam() bool {
return true
}
func (cmd commandLprt) RequireAuth() bool {
return true
}
func (cmd commandLprt) Execute(conn *Conn, param string) {
// No tests for this code yet
parts := strings.Split(param, ",")
addressFamily, err := strconv.Atoi(parts[0])
if addressFamily != 4 {
conn.writeMessage(522, "Network protocol not supported, use 4")
return
}
addressLength, err := strconv.Atoi(parts[1])
if addressLength != 4 {
conn.writeMessage(522, "Network IP length not supported, use 4")
return
}
host := strings.Join(parts[2:2+addressLength], ".")
portLength, err := strconv.Atoi(parts[2+addressLength])
portAddress := parts[3+addressLength : 3+addressLength+portLength]
// Convert string[] to byte[]
portBytes := make([]byte, portLength)
for i := range portAddress {
p, _ := strconv.Atoi(portAddress[i])
portBytes[i] = byte(p)
}
// convert the bytes to an int
port := int(binary.BigEndian.Uint16(portBytes))
// if the existing connection is on the same host/port don't reconnect
if conn.dataConn.Host() == host && conn.dataConn.Port() == port {
return
}
socket, err := newActiveSocket(host, port, conn.logger, conn.sessionID)
if err != nil {
conn.writeMessage(425, "Data connection failed")
return
}
conn.dataConn = socket
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
}
// commandEpsv responds to the EPSV FTP command. It allows the client to
// request a passive data socket with more options than the original PASV
// command. It mainly adds ipv6 support, although we don't support that yet.
type commandEpsv struct{}
func (cmd commandEpsv) IsExtend() bool {
return true
}
func (cmd commandEpsv) RequireParam() bool {
return false
}
func (cmd commandEpsv) RequireAuth() bool {
return true
}
func (cmd commandEpsv) Execute(conn *Conn, param string) {
socket, err := conn.newPassiveSocket()
if err != nil {
log.Println(err)
conn.writeMessage(425, "Data connection failed")
return
}
msg := fmt.Sprintf("Entering Extended Passive Mode (|||%d|)", socket.Port())
conn.writeMessage(229, msg)
}
// commandList responds to the LIST FTP command. It allows the client to retreive
// a detailed listing of the contents of a directory.
type commandList struct{}
func (cmd commandList) IsExtend() bool {
return false
}
func (cmd commandList) RequireParam() bool {
return false
}
func (cmd commandList) RequireAuth() bool {
return true
}
func (cmd commandList) Execute(conn *Conn, param string) {
path := conn.buildPath(parseListParam(param))
info, err := conn.driver.Stat(path)
if err != nil {
conn.writeMessage(550, err.Error())
return
}
if info == nil {
conn.logger.Printf(conn.sessionID, "%s: no such file or directory.\n", path)
return
}
var files []FileInfo
if info.IsDir() {
err = conn.driver.ListDir(path, func(f FileInfo) error {
files = append(files, f)
return nil
})
if err != nil {
conn.writeMessage(550, err.Error())
return
}
} else {
files = append(files, info)
}
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
conn.sendOutofbandData(listFormatter(files).Detailed())
}
func parseListParam(param string) (path string) {
if len(param) == 0 {
path = param
} else {
fields := strings.Fields(param)
i := 0
for _, field := range fields {
if !strings.HasPrefix(field, "-") {
break
}
i = strings.LastIndex(param, " "+field) + len(field) + 1
}
path = strings.TrimLeft(param[i:], " ") //Get all the path even with space inside
}
return path
}
// commandNlst responds to the NLST FTP command. It allows the client to
// retreive a list of filenames in the current directory.
type commandNlst struct{}
func (cmd commandNlst) IsExtend() bool {
return false
}
func (cmd commandNlst) RequireParam() bool {
return false
}
func (cmd commandNlst) RequireAuth() bool {
return true
}
func (cmd commandNlst) Execute(conn *Conn, param string) {
path := conn.buildPath(parseListParam(param))
info, err := conn.driver.Stat(path)
if err != nil {
conn.writeMessage(550, err.Error())
return
}
if !info.IsDir() {
conn.writeMessage(550, param+" is not a directory")
return
}
var files []FileInfo
err = conn.driver.ListDir(path, func(f FileInfo) error {
files = append(files, f)
return nil
})
if err != nil {
conn.writeMessage(550, err.Error())
return
}
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
conn.sendOutofbandData(listFormatter(files).Short())
}
// commandMdtm responds to the MDTM FTP command. It allows the client to
// retreive the last modified time of a file.
type commandMdtm struct{}
func (cmd commandMdtm) IsExtend() bool {
return false
}
func (cmd commandMdtm) RequireParam() bool {
return true
}
func (cmd commandMdtm) RequireAuth() bool {
return true
}
func (cmd commandMdtm) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
stat, err := conn.driver.Stat(path)
if err == nil {
conn.writeMessage(213, stat.ModTime().Format("20060102150405"))
} else {
conn.writeMessage(450, "File not available")
}
}
// commandMkd responds to the MKD FTP command. It allows the client to create
// a new directory
type commandMkd struct{}
func (cmd commandMkd) IsExtend() bool {
return false
}
func (cmd commandMkd) RequireParam() bool {
return true
}
func (cmd commandMkd) RequireAuth() bool {
return true
}
func (cmd commandMkd) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
conn.server.notifiers.BeforeCreateDir(conn, path)
err := conn.driver.MakeDir(path)
conn.server.notifiers.AfterDirCreated(conn, path, err)
if err == nil {
conn.writeMessage(257, "Directory created")
} else {
conn.writeMessage(550, fmt.Sprint("Action not taken: ", err))
}
}
// cmdMode responds to the MODE FTP command.
//
// the original FTP spec had various options for hosts to negotiate how data
// would be sent over the data socket, In reality these days (S)tream mode
// is all that is used for the mode - data is just streamed down the data
// socket unchanged.
type commandMode struct{}
func (cmd commandMode) IsExtend() bool {
return false
}
func (cmd commandMode) RequireParam() bool {
return true
}
func (cmd commandMode) RequireAuth() bool {
return true
}
func (cmd commandMode) Execute(conn *Conn, param string) {
if strings.ToUpper(param) == "S" {
conn.writeMessage(200, "OK")
} else {
conn.writeMessage(504, "MODE is an obsolete command")
}
}
// cmdNoop responds to the NOOP FTP command.
//
// This is essentially a ping from the client so we just respond with an
// basic 200 message.
type commandNoop struct{}
func (cmd commandNoop) IsExtend() bool {
return false
}
func (cmd commandNoop) RequireParam() bool {
return false
}
func (cmd commandNoop) RequireAuth() bool {
return false
}
func (cmd commandNoop) Execute(conn *Conn, param string) {
conn.writeMessage(200, "OK")
}
// commandPass respond to the PASS FTP command by asking the driver if the
// supplied username and password are valid
type commandPass struct{}
func (cmd commandPass) IsExtend() bool {
return false
}
func (cmd commandPass) RequireParam() bool {
return true
}
func (cmd commandPass) RequireAuth() bool {
return false
}
func (cmd commandPass) Execute(conn *Conn, param string) {
auth := conn.server.Auth
// If Driver implements Auth then call that instead of the Server version
if driverAuth, found := conn.driver.(Auth); found {
auth = driverAuth
}
ok, err := auth.CheckPasswd(conn.reqUser, param)
conn.server.notifiers.AfterUserLogin(conn, conn.reqUser, param, ok, err)
if err != nil {
conn.writeMessage(550, "Checking password error")
return
}
if ok {
conn.user = conn.reqUser
conn.reqUser = ""
conn.writeMessage(230, "Password ok, continue")
} else {
conn.writeMessage(530, "Incorrect password, not logged in")
}
}
// commandPasv responds to the PASV FTP command.
//
// The client is requesting us to open a new TCP listing socket and wait for them
// to connect to it.
type commandPasv struct{}
func (cmd commandPasv) IsExtend() bool {
return false
}
func (cmd commandPasv) RequireParam() bool {
return false
}
func (cmd commandPasv) RequireAuth() bool {
return true
}
func (cmd commandPasv) Execute(conn *Conn, param string) {
listenIP := conn.passiveListenIP()
// TODO: IPv6 for this command is not implemented
if strings.HasPrefix(listenIP, "::") {
conn.writeMessage(550, fmt.Sprint("Action not taken "))
return
}
socket, err := conn.newPassiveSocket()
if err != nil {
conn.writeMessage(425, "Data connection failed")
return
}
p1 := socket.Port() / 256
p2 := socket.Port() - (p1 * 256)
quads := strings.Split(listenIP, ".")
target := fmt.Sprintf("(%s,%s,%s,%s,%d,%d)", quads[0], quads[1], quads[2], quads[3], p1, p2)
msg := "Entering Passive Mode " + target
conn.writeMessage(227, msg)
}
// commandPort responds to the PORT FTP command.
//
// The client has opened a listening socket for sending out of band data and
// is requesting that we connect to it
type commandPort struct{}
func (cmd commandPort) IsExtend() bool {
return false
}
func (cmd commandPort) RequireParam() bool {
return true
}
func (cmd commandPort) RequireAuth() bool {
return true
}
func (cmd commandPort) Execute(conn *Conn, param string) {
nums := strings.Split(param, ",")
portOne, _ := strconv.Atoi(nums[4])
portTwo, _ := strconv.Atoi(nums[5])
port := (portOne * 256) + portTwo
host := nums[0] + "." + nums[1] + "." + nums[2] + "." + nums[3]
socket, err := newActiveSocket(host, port, conn.logger, conn.sessionID)
if err != nil {
conn.writeMessage(425, "Data connection failed")
return
}
conn.dataConn = socket
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
}
// commandPwd responds to the PWD FTP command.
//
// Tells the client what the current working directory is.
type commandPwd struct{}
func (cmd commandPwd) IsExtend() bool {
return false
}
func (cmd commandPwd) RequireParam() bool {
return false
}
func (cmd commandPwd) RequireAuth() bool {
return true
}
func (cmd commandPwd) Execute(conn *Conn, param string) {
conn.writeMessage(257, "\""+conn.curDir+"\" is the current directory")
}
// CommandQuit responds to the QUIT FTP command. The client has requested the
// connection be closed.
type commandQuit struct{}
func (cmd commandQuit) IsExtend() bool {
return false
}
func (cmd commandQuit) RequireParam() bool {
return false
}
func (cmd commandQuit) RequireAuth() bool {
return false
}
func (cmd commandQuit) Execute(conn *Conn, param string) {
conn.writeMessage(221, "Goodbye")
conn.Close()
}
// commandRetr responds to the RETR FTP command. It allows the client to
// download a file.
type commandRetr struct{}
func (cmd commandRetr) IsExtend() bool {
return false
}
func (cmd commandRetr) RequireParam() bool {
return true
}
func (cmd commandRetr) RequireAuth() bool {
return true
}
func (cmd commandRetr) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
defer func() {
conn.lastFilePos = 0
conn.appendData = false
}()
conn.server.notifiers.BeforeDownloadFile(conn, path)
size, data, err := conn.driver.GetFile(path, conn.lastFilePos)
if err == nil {
defer data.Close()
conn.writeMessage(150, fmt.Sprintf("Data transfer starting %d bytes", size))
err = conn.sendOutofBandDataWriter(data)
conn.server.notifiers.AfterFileDownloaded(conn, path, size, err)
if err != nil {
conn.writeMessage(551, "Error reading file")
}
} else {
conn.server.notifiers.AfterFileDownloaded(conn, path, size, err)
conn.writeMessage(551, "File not available")
}
}
type commandRest struct{}
func (cmd commandRest) IsExtend() bool {
return false
}
func (cmd commandRest) RequireParam() bool {
return true
}
func (cmd commandRest) RequireAuth() bool {
return true
}
func (cmd commandRest) Execute(conn *Conn, param string) {
var err error
conn.lastFilePos, err = strconv.ParseInt(param, 10, 64)
if err != nil {
conn.writeMessage(551, "File not available")
return
}
conn.appendData = true
conn.writeMessage(350, fmt.Sprint("Start transfer from ", conn.lastFilePos))
}
// commandRnfr responds to the RNFR FTP command. It's the first of two commands
// required for a client to rename a file.
type commandRnfr struct{}
func (cmd commandRnfr) IsExtend() bool {
return false
}
func (cmd commandRnfr) RequireParam() bool {
return true
}
func (cmd commandRnfr) RequireAuth() bool {
return true
}
func (cmd commandRnfr) Execute(conn *Conn, param string) {
conn.renameFrom = conn.buildPath(param)
conn.writeMessage(350, "Requested file action pending further information.")
}
// cmdRnto responds to the RNTO FTP command. It's the second of two commands
// required for a client to rename a file.
type commandRnto struct{}
func (cmd commandRnto) IsExtend() bool {
return false
}
func (cmd commandRnto) RequireParam() bool {
return true
}
func (cmd commandRnto) RequireAuth() bool {
return true
}
func (cmd commandRnto) Execute(conn *Conn, param string) {
toPath := conn.buildPath(param)
err := conn.driver.Rename(conn.renameFrom, toPath)
defer func() {
conn.renameFrom = ""
}()
if err == nil {
conn.writeMessage(250, "File renamed")
} else {
conn.writeMessage(550, fmt.Sprint("Action not taken: ", err))
}
}
// cmdRmd responds to the RMD FTP command. It allows the client to delete a
// directory.
type commandRmd struct{}
func (cmd commandRmd) IsExtend() bool {
return false
}
func (cmd commandRmd) RequireParam() bool {
return true
}
func (cmd commandRmd) RequireAuth() bool {
return true
}
func (cmd commandRmd) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
conn.server.notifiers.BeforeDeleteDir(conn, path)
err := conn.driver.DeleteDir(path)
conn.server.notifiers.AfterDirDeleted(conn, path, err)
if err == nil {
conn.writeMessage(250, "Directory deleted")
} else {
conn.writeMessage(550, fmt.Sprint("Directory delete failed: ", err))
}
}
type commandAdat struct{}
func (cmd commandAdat) IsExtend() bool {
return false
}
func (cmd commandAdat) RequireParam() bool {
return true
}
func (cmd commandAdat) RequireAuth() bool {
return true
}
func (cmd commandAdat) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
}
type commandAuth struct{}
func (cmd commandAuth) IsExtend() bool {
return false
}
func (cmd commandAuth) RequireParam() bool {
return true
}
func (cmd commandAuth) RequireAuth() bool {
return false
}
func (cmd commandAuth) Execute(conn *Conn, param string) {
if param == "TLS" && conn.tlsConfig != nil {
conn.writeMessage(234, "AUTH command OK")
err := conn.upgradeToTLS()
if err != nil {
conn.logger.Printf("Error upgrading connection to TLS %v", err.Error())
}
} else {
conn.writeMessage(550, "Action not taken")
}
}
type commandCcc struct{}
func (cmd commandCcc) IsExtend() bool {
return false
}
func (cmd commandCcc) RequireParam() bool {
return true
}
func (cmd commandCcc) RequireAuth() bool {
return true
}
func (cmd commandCcc) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
}
type commandEnc struct{}
func (cmd commandEnc) IsExtend() bool {
return false
}
func (cmd commandEnc) RequireParam() bool {
return true
}
func (cmd commandEnc) RequireAuth() bool {
return true
}
func (cmd commandEnc) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
}
type commandMic struct{}
func (cmd commandMic) IsExtend() bool {
return false
}
func (cmd commandMic) RequireParam() bool {
return true
}
func (cmd commandMic) RequireAuth() bool {
return true
}
func (cmd commandMic) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
}
type commandPbsz struct{}
func (cmd commandPbsz) IsExtend() bool {
return false
}
func (cmd commandPbsz) RequireParam() bool {
return true
}
func (cmd commandPbsz) RequireAuth() bool {
return false
}
func (cmd commandPbsz) Execute(conn *Conn, param string) {
if conn.tls && param == "0" {
conn.writeMessage(200, "OK")
} else {
conn.writeMessage(550, "Action not taken")
}
}
type commandProt struct{}
func (cmd commandProt) IsExtend() bool {
return false
}
func (cmd commandProt) RequireParam() bool {
return true
}
func (cmd commandProt) RequireAuth() bool {
return false
}
func (cmd commandProt) Execute(conn *Conn, param string) {
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{}
func (cmd commandConf) IsExtend() bool {
return false
}
func (cmd commandConf) RequireParam() bool {
return true
}
func (cmd commandConf) RequireAuth() bool {
return true
}
func (cmd commandConf) Execute(conn *Conn, param string) {
conn.writeMessage(550, "Action not taken")
}
// commandSize responds to the SIZE FTP command. It returns the size of the
// requested path in bytes.
type commandSize struct{}
func (cmd commandSize) IsExtend() bool {
return false
}
func (cmd commandSize) RequireParam() bool {
return true
}
func (cmd commandSize) RequireAuth() bool {
return true
}
func (cmd commandSize) Execute(conn *Conn, param string) {
path := conn.buildPath(param)
stat, err := conn.driver.Stat(path)
if err != nil {
log.Printf("Size: error(%s)", err)
conn.writeMessage(450, fmt.Sprint("path", path, "not found"))
} else {
conn.writeMessage(213, strconv.Itoa(int(stat.Size())))
}
}
// commandStor responds to the STOR FTP command. It allows the user to upload a
// new file.
type commandStor struct{}
func (cmd commandStor) IsExtend() bool {
return false
}
func (cmd commandStor) RequireParam() bool {
return true
}
func (cmd commandStor) RequireAuth() bool {
return true
}
func (cmd commandStor) Execute(conn *Conn, param string) {
targetPath := conn.buildPath(param)
conn.writeMessage(150, "Data transfer starting")
defer func() {
conn.appendData = false
}()
conn.server.notifiers.BeforePutFile(conn, targetPath)
size, err := conn.driver.PutFile(targetPath, conn.dataConn, conn.appendData)
conn.server.notifiers.AfterFilePut(conn, targetPath, size, err)
if err == nil {
msg := fmt.Sprintf("OK, received %d bytes", size)
conn.writeMessage(226, msg)
} else {
conn.writeMessage(450, fmt.Sprint("error during transfer: ", err))
}
}
// commandStru responds to the STRU FTP command.
//
// like the MODE and TYPE commands, stru[cture] dates back to a time when the
// FTP protocol was more aware of the content of the files it was transferring,
// and would sometimes be expected to translate things like EOL markers on the
// fly.
//
// These days files are sent unmodified, and F(ile) mode is the only one we
// really need to support.
type commandStru struct{}
func (cmd commandStru) IsExtend() bool {
return false
}
func (cmd commandStru) RequireParam() bool {
return true
}
func (cmd commandStru) RequireAuth() bool {
return true
}
func (cmd commandStru) Execute(conn *Conn, param string) {
if strings.ToUpper(param) == "F" {
conn.writeMessage(200, "OK")
} else {
conn.writeMessage(504, "STRU is an obsolete command")
}
}
// commandSyst responds to the SYST FTP command by providing a canned response.
type commandSyst struct{}
func (cmd commandSyst) IsExtend() bool {
return false
}
func (cmd commandSyst) RequireParam() bool {
return false
}
func (cmd commandSyst) RequireAuth() bool {
return true
}
func (cmd commandSyst) Execute(conn *Conn, param string) {
conn.writeMessage(215, "UNIX Type: L8")
}
// commandType responds to the TYPE FTP command.
//
// like the MODE and STRU commands, TYPE dates back to a time when the FTP
// protocol was more aware of the content of the files it was transferring, and
// would sometimes be expected to translate things like EOL markers on the fly.
//
// Valid options were A(SCII), I(mage), E(BCDIC) or LN (for local type). Since
// we plan to just accept bytes from the client unchanged, I think Image mode is
// adequate. The RFC requires we accept ASCII mode however, so accept it, but
// ignore it.
type commandType struct{}
func (cmd commandType) IsExtend() bool {
return false
}
func (cmd commandType) RequireParam() bool {
return false
}
func (cmd commandType) RequireAuth() bool {
return true
}
func (cmd commandType) Execute(conn *Conn, param string) {
if strings.ToUpper(param) == "A" {
conn.writeMessage(200, "Type set to ASCII")
} else if strings.ToUpper(param) == "I" {
conn.writeMessage(200, "Type set to binary")
} else {
conn.writeMessage(500, "Invalid type")
}
}
// commandUser responds to the USER FTP command by asking for the password
type commandUser struct{}
func (cmd commandUser) IsExtend() bool {
return false
}
func (cmd commandUser) RequireParam() bool {
return true
}
func (cmd commandUser) RequireAuth() bool {
return false
}
func (cmd commandUser) Execute(conn *Conn, param string) {
conn.reqUser = param
conn.server.notifiers.BeforeLoginUser(conn, conn.reqUser)
if conn.tls || conn.tlsConfig == nil {
conn.writeMessage(331, "User name ok, password required")
} else {
conn.writeMessage(534, "Unsecured login not allowed. AUTH TLS required")
}
}