diff --git a/info.go b/info.go new file mode 100644 index 0000000..aab86ff --- /dev/null +++ b/info.go @@ -0,0 +1,55 @@ +package main + +import ( + "os" + "time" +) + +type FileInfo struct { + name string + bytes int64 + links int64 + mode os.FileMode +} + +func (info *FileInfo) Name() string { + return info.name +} + +func (info *FileInfo) Size() int64 { + return info.bytes +} + +func (info *FileInfo) Mode() os.FileMode { + return info.mode +} + +func (info *FileInfo) ModTime() time.Time { + return time.Now() +} + +func (info *FileInfo) IsDir() bool { + return (info.mode | os.ModeDir) == os.ModeDir +} + +func (info *FileInfo) Sys() interface{} { + return nil +} + +func NewDirItem(name string, bytes int64, links int64) os.FileInfo { + d := new(FileInfo) + d.name = name + d.bytes = int64(bytes) + d.links = int64(links) + d.mode = os.ModeDir | 0777 + return d +} + +func NewFileItem(name string, bytes int64, links int64) os.FileInfo { + f := new(FileInfo) + f.name = name + f.bytes = int64(bytes) + f.links = int64(links) + f.mode = 0666 + return f +} diff --git a/list.go b/list.go new file mode 100644 index 0000000..1cbac41 --- /dev/null +++ b/list.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + + "github.com/jehiah/go-strftime" +) + +type listFormatter struct { + files []os.FileInfo +} + +func newListFormatter(files []os.FileInfo) *listFormatter { + f := new(listFormatter) + f.files = files + return f +} + +// Short returns a string that lists the collection of files by name only, +// one per line +func (formatter *listFormatter) Short() string { + output := "" + for _, file := range formatter.files { + output += file.Name() + "\r\n" + } + output += "\r\n" + return output +} + +// Detailed returns a string that lists the collection of files with extra +// detail, one per line +func (formatter *listFormatter) Detailed() string { + output := "" + for _, file := range formatter.files { + output += fmt.Sprintf("%-13s %s %-8s %-8s %8d %s %s\r\n", file.Mode().String(), "1", "1000", "1000", int(file.Size()), strftime.Format("%b %m %Y", file.ModTime()), file.Name()) + } + return output +} diff --git a/main.go b/main.go index d702035..384c8c0 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,16 @@ package main import ( "fmt" "net" + "os" + "path/filepath" "strconv" "strings" + + "github.com/ncw/swift" ) func main() { - l, err := net.Listen("tcp", ":8021") + l, err := net.Listen("tcp4", ":21") if err != nil { panic(err) @@ -23,6 +27,9 @@ func main() { } go handle(conn) } + //56652e9028ded5ea5d4772ba80e578ce + //storage_21_1 + //https://api.clodo.ru/ } func handle(c net.Conn) { @@ -64,10 +71,20 @@ func handle(c net.Conn) { return case "SIZE": s.cmdServerSize(params) - case "CWD": + case "CWD", "XCWD": s.cmdServerCwd(params) case "RETR": s.cmdServerRetr(params) + case "PORT": + s.cmdServerPort(params) + case "EPRT": + s.cmdServerEprt(params) + case "LPRT": + s.cmdServerLprt(params) + case "LPSV": + s.cmdServerLpsv(params) + case "FEAT": + s.cmdServerFeat(params) default: fmt.Printf("%s\n", line) } @@ -76,58 +93,104 @@ func handle(c net.Conn) { func (s *Conn) cmdServerUser(args []string) { fmt.Printf("cmdServerUser: %s\n", args) + // s.user = args[0] s.ctrl.PrintfLine("331 Password required for %s", args[0]) } func (s *Conn) cmdServerPass(args []string) { fmt.Printf("cmdServerPass: %s\n", args) + // s.token = args[0] + s.sw = &swift.Connection{UserName: s.user, ApiKey: s.token, AuthUrl: s.api, AuthVersion: 1} + err := s.sw.Authenticate() + if err != nil { + fmt.Printf(err.Error()) + s.ctrl.PrintfLine("530 Login incorrect") + return + } + s.ctrl.PrintfLine("230 Logged on") - //530 Login incorrect. } func (s *Conn) cmdServerPwd(args []string) { fmt.Printf("cmdServerPwd: %s\n", args) - s.ctrl.PrintfLine(`257 "/" is current directory.`) + if s.path == "" { + s.path = "/" + } + s.ctrl.PrintfLine(`257 "%s" is current directory.`, s.path) } func (s *Conn) cmdServerEpsv(args []string) { fmt.Printf("cmdServerEpsv: %s\n", args) - ln, err := net.Listen("tcp4", "37.139.40.30:0") - if err != nil { - - } - s.ln = ln - _, port, _ := net.SplitHostPort(ln.Addr().String()) - s.ctrl.PrintfLine("229 Entering Extended Passive Mode (|||%s|)", port) - go func() { - for { - conn, err := s.ln.Accept() - if err != nil { - return - } - s.data = conn + if s.ln == nil { + ln, err := net.Listen("tcp4", fmt.Sprintf(":%d", s.port)) + if err != nil { + fmt.Printf(err.Error()) + s.ctrl.PrintfLine("425 Data connection failed") } - }() + s.ln = ln + } + if s.port == 0 { + _, port, _ := net.SplitHostPort(s.ln.Addr().String()) + s.port, _ = strconv.Atoi(port) + } + s.ctrl.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", s.port) } func (s *Conn) cmdServerType(args []string) { fmt.Printf("cmdServerType: %s\n", args) - switch args[0] { - default: - s.ctrl.PrintfLine("200 Type set to %s", args[0]) - } + s.mode = args[0] + s.ctrl.PrintfLine("200 Type set to %s", s.mode) } func (s *Conn) cmdServerList(args []string) { fmt.Printf("cmdServerList: %s\n", args) - s.ctrl.PrintfLine(`150 Opening data channel for directory listing of "%s"`, args[0]) - n, err := s.data.Write([]byte("-rw-r--r-- 1 ftp ftp 4 Apr 16 2008 TEST\n")) + s.ctrl.PrintfLine(`150 Opening data channel for directory listing of "%s"`, strings.Join(args, "")) + c, err := s.newPassiveSocket() + if err != nil { + fmt.Printf(err.Error()) + s.ctrl.PrintfLine("425 Data connection failed") + return + } + var n int + var files []os.FileInfo + + if s.path == "/" { + cnts, err := s.sw.ContainersAll(nil) + if err != nil { + fmt.Printf(err.Error()) + return + } + files = append(files, NewDirItem(".", 4096, 0), NewDirItem("..", 4096, 0)) + for _, cnt := range cnts { + it := NewDirItem(cnt.Name, cnt.Bytes, cnt.Count) + files = append(files, it) + } + } else { + if idx := strings.Index(s.path, "/"); idx != -1 { + s.container = s.path[idx+1:] + } else { + s.container = s.path + } + objs, err := s.sw.ObjectsAll(s.container, nil) + if err != nil { + fmt.Printf(err.Error()) + return + } + files = append(files, NewDirItem(".", 4096, 0), NewDirItem("..", 4096, 0)) + for _, obj := range objs { + it := NewFileItem(obj.Name, obj.Bytes, 1) + files = append(files, it) + } + } + + ls := newListFormatter(files) + n, err = c.Write([]byte(ls.Detailed())) if err != nil { fmt.Printf(err.Error()) } - s.data.Close() + c.Close() s.ctrl.PrintfLine(`226 Closing data connection, sent %d bytes`, n) - s.ctrl.PrintfLine("226 Transfer complete.") + // s.ctrl.PrintfLine("226 Transfer complete.") } func (s *Conn) cmdServerSyst(args []string) { @@ -136,9 +199,7 @@ func (s *Conn) cmdServerSyst(args []string) { } func (s *Conn) cmdServerQuit(args []string) { - if s.data != nil { - s.data.Close() - } + s.ctrl.PrintfLine("221 Goodbye") if s.ln != nil { s.ln.Close() } @@ -149,26 +210,24 @@ func (s *Conn) cmdServerQuit(args []string) { func (s *Conn) cmdServerPasv(args []string) { fmt.Printf("cmdServerPasv: %s\n", args) - - ln, err := net.Listen("tcp4", "37.139.40.30:0") - if err != nil { - + if s.ln == nil { + ln, err := net.Listen("tcp4", fmt.Sprintf(":%d", s.port)) + if err != nil { + fmt.Printf("%s\n", err.Error()) + s.ctrl.PrintfLine("425 Data connection failed") + } + s.ln = ln } - s.ln = ln - host, port, _ := net.SplitHostPort(ln.Addr().String()) + host, port, _ := net.SplitHostPort(s.ln.Addr().String()) p0, _ := strconv.Atoi(port) p1 := p0 / 256 p2 := p0 - p1*256 + s.port, _ = strconv.Atoi(port) s.ctrl.PrintfLine("227 Entering Passive Mode (%s,%d,%d)", strings.Replace(host, ".", ",", -1), p1, p2) - go func() { - for { - conn, err := s.ln.Accept() - if err != nil { - return - } - s.data = conn - } - }() +} + +func (s *Conn) newPassiveSocket() (net.Conn, error) { + return s.ln.Accept() } func (s *Conn) cmdServerSize(args []string) { @@ -178,17 +237,71 @@ func (s *Conn) cmdServerSize(args []string) { func (s *Conn) cmdServerCwd(args []string) { fmt.Printf("cmdServerCwd: %s\n", args) - s.ctrl.PrintfLine("250 Okay") + if len(args) == 0 { + s.path = "/" + } else { + s.path = filepath.Clean(args[0]) + } + s.ctrl.PrintfLine(`250 "%s" is current directory.`, s.path) + +} + +func (s *Conn) cmdServerPort(args []string) { + fmt.Printf("cmdServerPort: %s\n", args) + s.ctrl.PrintfLine("501 Data connection failed") +} + +func (s *Conn) cmdServerEprt(args []string) { + fmt.Printf("cmdServerEprt: %s\n", args) + s.ctrl.PrintfLine("501 Data connection failed") +} + +func (s *Conn) cmdServerLprt(args []string) { + fmt.Printf("cmdServerLprt: %s\n", args) + s.ctrl.PrintfLine("501 Data connection failed") +} + +func (s *Conn) cmdServerLpsv(args []string) { + fmt.Printf("cmdServerLpsv: %s\n", args) + s.ctrl.PrintfLine("501 Data connection failed") +} + +func (s *Conn) cmdServerFeat(args []string) { + fmt.Printf("cmdServerFeat: %s\n", args) + s.ctrl.PrintfLine("501 Data connection failed") + return + s.ctrl.PrintfLine(`211-Extensions supported: + SIZE + RETR + CWD + PWD + QUIT + SYST + PASV + EPSV + LIST + TYPE + USER + PASS + UTF8 +211 END`) } func (s *Conn) cmdServerRetr(args []string) { fmt.Printf("cmdServerRetr: %s\n", args) s.ctrl.PrintfLine(`150 Opening data channel for file contents "%s"`, args[0]) - n, err := s.data.Write([]byte("test\n")) + c, err := s.newPassiveSocket() if err != nil { - fmt.Printf(err.Error()) + s.ctrl.PrintfLine("425 Data connection failed") + return } - s.data.Close() + n, err := c.Write([]byte("test\n")) + if err != nil { + s.ctrl.PrintfLine("425 Data connection failed") + fmt.Printf(err.Error()) + return + } + c.Close() s.ctrl.PrintfLine(`226 Closing data connection, sent %d bytes`, n) s.ctrl.PrintfLine("226 Transfer complete.") diff --git a/server.go b/server.go index 06776e5..0d7659a 100644 --- a/server.go +++ b/server.go @@ -3,12 +3,22 @@ package main import ( "net" "net/textproto" + + "github.com/ncw/swift" ) type Conn struct { - ctrl *textproto.Conn - data net.Conn - ln net.Listener + ctrl *textproto.Conn + data net.Conn + ln net.Listener + port int + mode string + path string + sw *swift.Connection + user string + token string + container string + api string } func (c *Conn) Close() error { @@ -16,5 +26,5 @@ func (c *Conn) Close() error { } func NewServer(c net.Conn) (*Conn, error) { - return &Conn{ctrl: textproto.NewConn(c)}, nil + return &Conn{api: "https://api.clodo.ru", user: "storage_21_1", token: "56652e9028ded5ea5d4772ba80e578ce", ctrl: textproto.NewConn(c)}, nil }