// 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. Load more
// 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 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) 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 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) 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")