1138 lines · 24.8 KB · Go
RawDownload
// 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 (	"fmt"	"log"	"strconv"	"strings") 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{},		"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{},		"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")} type commandAppe struct{} func (cmd commandAppe) IsExtend() bool {	return false} func (cmd commandAppe) RequireParam() bool {	return false} func (cmd commandAppe) RequireAuth() bool {	return true} func (cmd commandAppe) Execute(conn *Conn, param string) {	conn.appendData = true	conn.writeMessage(202, "Obsolete")} 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)	err := conn.driver.ChangeDir(path)	if err == nil {		conn.namePrefix = path		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 filetype 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)	err := conn.driver.DeleteFile(path)	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)+")")} // 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) {	addr := conn.passiveListenIP()	lastIdx := strings.LastIndex(addr, ":")	if lastIdx <= 0 {		conn.writeMessage(425, "Data connection failed")		return	} 	socket, err := newPassiveSocket(addr[:lastIdx], conn.PassivePort, conn.logger, conn.sessionID, conn.tlsConfig)	if err != nil {		log.Println(err)		conn.writeMessage(425, "Data connection failed")		return	}	conn.dataConn = socket	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 directorytype 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)	err := conn.driver.MakeDir(path)	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")

Lines 1–500 of 1138.