sftpserver.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package sftpserver
  2. import (
  3. "errors"
  4. "io"
  5. "io/ioutil"
  6. "log"
  7. "net"
  8. "sync"
  9. "github.com/pkg/sftp"
  10. uuid "github.com/satori/go.uuid"
  11. "golang.org/x/crypto/ssh"
  12. user "imuslab.com/arozos/mod/user"
  13. )
  14. type SFTPConfig struct {
  15. ListeningIP string
  16. KeyFile string
  17. UserManager *user.UserHandler
  18. }
  19. type Instance struct {
  20. Closed bool
  21. closer chan bool
  22. ConnectedClients sync.Map
  23. }
  24. //Create a new SFTP Server
  25. //listeningIP in the format of 0.0.0.0:2022
  26. func NewSFTPServer(sftpConfig *SFTPConfig) (*Instance, error) {
  27. // An SSH server is represented by a ServerConfig, which holds
  28. // certificate details and handles authentication of ServerConns.
  29. config := &ssh.ServerConfig{
  30. PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  31. // Should use constant-time compare (or better, salt+hash) in
  32. // a production setting.
  33. //fmt.Printf("[SFTP] %s Logged in\n", c.User())
  34. ok := sftpConfig.UserManager.GetAuthAgent().ValidateUsernameAndPassword(c.User(), string(pass))
  35. if !ok {
  36. return nil, errors.New("[SFTP] Password rejected for " + c.User())
  37. }
  38. return nil, nil
  39. },
  40. }
  41. privateBytes, err := ioutil.ReadFile(sftpConfig.KeyFile)
  42. if err != nil {
  43. return nil, err
  44. }
  45. private, err := ssh.ParsePrivateKey(privateBytes)
  46. if err != nil {
  47. return nil, err
  48. }
  49. config.AddHostKey(private)
  50. // Once a ServerConfig has been configured, connections can be accepted.
  51. listener, err := net.Listen("tcp", sftpConfig.ListeningIP)
  52. if err != nil {
  53. return nil, err
  54. }
  55. log.Printf("[SFTP] Listening on %v\n", listener.Addr())
  56. //Setup a closer handler for this instance
  57. closeChan := make(chan bool)
  58. thisServerInstance := Instance{
  59. closer: closeChan,
  60. Closed: false,
  61. ConnectedClients: sync.Map{},
  62. }
  63. go func() {
  64. <-closeChan
  65. //Kick all the client off
  66. thisServerInstance.ConnectedClients.Range(func(key, value interface{}) bool {
  67. value.(chan bool) <- true
  68. return true
  69. })
  70. //Close the listener
  71. listener.Close()
  72. }()
  73. //Start the ssh server listener in go routine
  74. go func() error {
  75. for {
  76. nConn, err := listener.Accept()
  77. if err != nil {
  78. return err
  79. }
  80. go func(nConn net.Conn) error {
  81. // Before use, a handshake must be performed on the incoming
  82. // net.Conn.
  83. cx, chans, reqs, err := ssh.NewServerConn(nConn, config)
  84. if err != nil {
  85. return err
  86. }
  87. log.Println("[SFTP] User Connected: ", cx.User())
  88. userinfo, err := sftpConfig.UserManager.GetUserInfoFromUsername(cx.User())
  89. if err != nil {
  90. return err
  91. }
  92. // The incoming Request channel must be serviced.
  93. go ssh.DiscardRequests(reqs)
  94. // Service the incoming Channel channel.
  95. for newChannel := range chans {
  96. // Channels have a type, depending on the application level
  97. // protocol intended. In the case of an SFTP session, this is "subsystem"
  98. // with a payload string of "<length=4>sftp"
  99. //fmt.Println("Incoming channel: %s\n", newChannel.ChannelType())
  100. if newChannel.ChannelType() != "session" {
  101. newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
  102. //fmt.Println("Unknown channel type: %s\n", newChannel.ChannelType())
  103. continue
  104. }
  105. channel, requests, err := newChannel.Accept()
  106. if err != nil {
  107. return err
  108. }
  109. //fmt.Println("Channel accepted\n")
  110. // Sessions have out-of-band requests such as "shell",
  111. // "pty-req" and "env". Here we handle only the
  112. // "subsystem" request.
  113. go func(in <-chan *ssh.Request) {
  114. for req := range in {
  115. //fmt.Println("Request: %v\n", req.Type)
  116. ok := false
  117. switch req.Type {
  118. case "subsystem":
  119. //fmt.Println("Subsystem: %s\n", req.Payload[4:])
  120. if string(req.Payload[4:]) == "sftp" {
  121. ok = true
  122. }
  123. }
  124. //fmt.Println(" - accepted: %v\n", ok)
  125. req.Reply(ok, nil)
  126. }
  127. }(requests)
  128. //Create a virtual SSH Server that contains all this user's fsh
  129. root := GetNewSFTPRoot(userinfo.Username, userinfo.GetAllFileSystemHandler())
  130. server := sftp.NewRequestServer(channel, root)
  131. //Create a channel for kicking the user off
  132. kickChan := make(chan bool)
  133. channelId := uuid.NewV4().String()
  134. thisServerInstance.ConnectedClients.Store(channelId, kickChan)
  135. go func() {
  136. //Close the server
  137. gratefully := <-kickChan
  138. if gratefully {
  139. server.Close()
  140. }
  141. //Remove this channel from array
  142. thisServerInstance.ConnectedClients.Delete(channelId)
  143. }()
  144. if err := server.Serve(); err == io.EOF {
  145. kickChan <- true
  146. //server.Close()
  147. log.Print("sftp client exited session.")
  148. } else if err != nil {
  149. kickChan <- false
  150. log.Println("sftp server completed with error:", err)
  151. }
  152. }
  153. return nil
  154. }(nConn)
  155. }
  156. }()
  157. return &thisServerInstance, nil
  158. }
  159. func (i *Instance) Close() {
  160. i.closer <- true
  161. i.Closed = true
  162. log.Println("[SFTP] SFTP Server Closed")
  163. }