server.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package sftpd
  2. import (
  3. "crypto/rand"
  4. "crypto/rsa"
  5. "crypto/x509"
  6. "encoding/hex"
  7. "encoding/json"
  8. "encoding/pem"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "net"
  14. "os"
  15. "path/filepath"
  16. "strconv"
  17. "sync"
  18. "time"
  19. "github.com/drakkan/sftpgo/dataprovider"
  20. "github.com/drakkan/sftpgo/logger"
  21. "github.com/drakkan/sftpgo/utils"
  22. "github.com/pkg/sftp"
  23. "golang.org/x/crypto/ssh"
  24. )
  25. const defaultPrivateKeyName = "id_rsa"
  26. // Configuration for the SFTP server
  27. type Configuration struct {
  28. // Identification string used by the server
  29. Banner string `json:"banner" mapstructure:"banner"`
  30. // The port used for serving SFTP requests
  31. BindPort int `json:"bind_port" mapstructure:"bind_port"`
  32. // The address to listen on. A blank value means listen on all available network interfaces.
  33. BindAddress string `json:"bind_address" mapstructure:"bind_address"`
  34. // Maximum idle timeout as minutes. If a client is idle for a time that exceeds this setting it will be disconnected
  35. IdleTimeout int `json:"idle_timeout" mapstructure:"idle_timeout"`
  36. // Maximum number of authentication attempts permitted per connection.
  37. // If set to a negative number, the number of attempts are unlimited.
  38. // If set to zero, the number of attempts are limited to 6.
  39. MaxAuthTries int `json:"max_auth_tries" mapstructure:"max_auth_tries"`
  40. // Umask for new files
  41. Umask string `json:"umask" mapstructure:"umask"`
  42. // UploadMode 0 means standard, the files are uploaded directly to the requested path.
  43. // 1 means atomic: the files are uploaded to a temporary path and renamed to the requested path
  44. // when the client ends the upload. Atomic mode avoid problems such as a web server that
  45. // serves partial files when the files are being uploaded.
  46. UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
  47. // Actions to execute on SFTP create, download, delete and rename
  48. Actions Actions `json:"actions" mapstructure:"actions"`
  49. // Keys are a list of host keys
  50. Keys []Key `json:"keys" mapstructure:"keys"`
  51. }
  52. // Key contains information about host keys
  53. type Key struct {
  54. // The private key path relative to the configuration directory or absolute
  55. PrivateKey string `json:"private_key" mapstructure:"private_key"`
  56. }
  57. // Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
  58. func (c Configuration) Initialize(configDir string) error {
  59. umask, err := strconv.ParseUint(c.Umask, 8, 8)
  60. if err == nil {
  61. utils.SetUmask(int(umask), c.Umask)
  62. } else {
  63. logger.Warn(logSender, "error reading umask, please fix your config file: %v", err)
  64. logger.WarnToConsole("error reading umask, please fix your config file: %v", err)
  65. }
  66. serverConfig := &ssh.ServerConfig{
  67. NoClientAuth: false,
  68. MaxAuthTries: c.MaxAuthTries,
  69. PasswordCallback: func(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  70. sp, err := c.validatePasswordCredentials(conn, pass)
  71. if err != nil {
  72. return nil, errors.New("could not validate credentials")
  73. }
  74. return sp, nil
  75. },
  76. PublicKeyCallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
  77. sp, err := c.validatePublicKeyCredentials(conn, string(pubKey.Marshal()))
  78. if err != nil {
  79. return nil, errors.New("could not validate credentials")
  80. }
  81. return sp, nil
  82. },
  83. ServerVersion: "SSH-2.0-" + c.Banner,
  84. }
  85. err = c.checkHostKeys(configDir)
  86. if err != nil {
  87. return err
  88. }
  89. for _, k := range c.Keys {
  90. privateFile := k.PrivateKey
  91. if !filepath.IsAbs(privateFile) {
  92. privateFile = filepath.Join(configDir, privateFile)
  93. }
  94. logger.Info(logSender, "Loading private key: %s", privateFile)
  95. privateBytes, err := ioutil.ReadFile(privateFile)
  96. if err != nil {
  97. return err
  98. }
  99. private, err := ssh.ParsePrivateKey(privateBytes)
  100. if err != nil {
  101. return err
  102. }
  103. // Add private key to the server configuration.
  104. serverConfig.AddHostKey(private)
  105. }
  106. listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort))
  107. if err != nil {
  108. logger.Warn(logSender, "error starting listener on address %s:%d: %v", c.BindAddress, c.BindPort, err)
  109. return err
  110. }
  111. actions = c.Actions
  112. uploadMode = c.UploadMode
  113. logger.Info(logSender, "server listener registered address: %v", listener.Addr().String())
  114. if c.IdleTimeout > 0 {
  115. startIdleTimer(time.Duration(c.IdleTimeout) * time.Minute)
  116. }
  117. for {
  118. conn, _ := listener.Accept()
  119. if conn != nil {
  120. go c.AcceptInboundConnection(conn, serverConfig)
  121. }
  122. }
  123. }
  124. // AcceptInboundConnection handles an inbound connection to the server instance and determines if the request should be served or not.
  125. func (c Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.ServerConfig) {
  126. defer conn.Close()
  127. // Before beginning a handshake must be performed on the incoming net.Conn
  128. sconn, chans, reqs, err := ssh.NewServerConn(conn, config)
  129. if err != nil {
  130. logger.Warn(logSender, "failed to accept an incoming connection: %v", err)
  131. return
  132. }
  133. defer sconn.Close()
  134. logger.Debug(logSender, "accepted inbound connection, ip: %v", conn.RemoteAddr().String())
  135. go ssh.DiscardRequests(reqs)
  136. for newChannel := range chans {
  137. // If its not a session channel we just move on because its not something we
  138. // know how to handle at this point.
  139. if newChannel.ChannelType() != "session" {
  140. logger.Debug(logSender, "received an unknown channel type: %v", newChannel.ChannelType())
  141. newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
  142. continue
  143. }
  144. channel, requests, err := newChannel.Accept()
  145. if err != nil {
  146. logger.Warn(logSender, "could not accept a channel: %v", err)
  147. continue
  148. }
  149. // Channels have a type that is dependent on the protocol. For SFTP this is "subsystem"
  150. // with a payload that (should) be "sftp". Discard anything else we receive ("pty", "shell", etc)
  151. go func(in <-chan *ssh.Request) {
  152. for req := range in {
  153. ok := false
  154. switch req.Type {
  155. case "subsystem":
  156. if string(req.Payload[4:]) == "sftp" {
  157. ok = true
  158. }
  159. }
  160. req.Reply(ok, nil)
  161. }
  162. }(requests)
  163. var user dataprovider.User
  164. err = json.Unmarshal([]byte(sconn.Permissions.Extensions["user"]), &user)
  165. if err != nil {
  166. logger.Warn(logSender, "Unable to deserialize user info, cannot serve connection: %v", err)
  167. return
  168. }
  169. connectionID := hex.EncodeToString(sconn.SessionID())
  170. // Create a new handler for the currently logged in user's server.
  171. handler := c.createHandler(sconn, user, connectionID)
  172. // Create the server instance for the channel using the handler we created above.
  173. server := sftp.NewRequestServer(channel, handler)
  174. if err := server.Serve(); err == io.EOF {
  175. logger.Debug(logSender, "connection closed, id: %v", connectionID)
  176. server.Close()
  177. } else if err != nil {
  178. logger.Error(logSender, "sftp connection closed with error id %v: %v", connectionID, err)
  179. }
  180. removeConnection(connectionID)
  181. }
  182. }
  183. func (c Configuration) createHandler(conn *ssh.ServerConn, user dataprovider.User, connectionID string) sftp.Handlers {
  184. connection := Connection{
  185. ID: connectionID,
  186. User: user,
  187. ClientVersion: string(conn.ClientVersion()),
  188. RemoteAddr: conn.RemoteAddr(),
  189. StartTime: time.Now(),
  190. lastActivity: time.Now(),
  191. lock: new(sync.Mutex),
  192. sshConn: conn,
  193. }
  194. addConnection(connectionID, connection)
  195. return sftp.Handlers{
  196. FileGet: connection,
  197. FilePut: connection,
  198. FileCmd: connection,
  199. FileList: connection,
  200. }
  201. }
  202. func loginUser(user dataprovider.User) (*ssh.Permissions, error) {
  203. if !filepath.IsAbs(user.HomeDir) {
  204. logger.Warn(logSender, "user %v has invalid home dir: %v. Home dir must be an absolute path, login not allowed",
  205. user.Username, user.HomeDir)
  206. return nil, fmt.Errorf("Cannot login user with invalid home dir: %v", user.HomeDir)
  207. }
  208. if _, err := os.Stat(user.HomeDir); os.IsNotExist(err) {
  209. logger.Debug(logSender, "home directory \"%v\" for user %v does not exist, try to create", user.HomeDir, user.Username)
  210. err := os.MkdirAll(user.HomeDir, 0777)
  211. if err == nil {
  212. utils.SetPathPermissions(user.HomeDir, user.GetUID(), user.GetGID())
  213. }
  214. }
  215. if user.MaxSessions > 0 {
  216. activeSessions := getActiveSessions(user.Username)
  217. if activeSessions >= user.MaxSessions {
  218. logger.Debug(logSender, "authentication refused for user: %v, too many open sessions: %v/%v", user.Username,
  219. activeSessions, user.MaxSessions)
  220. return nil, fmt.Errorf("Too many open sessions: %v", activeSessions)
  221. }
  222. }
  223. json, err := json.Marshal(user)
  224. if err != nil {
  225. logger.Warn(logSender, "error serializing user info: %v, authentication rejected", err)
  226. return nil, err
  227. }
  228. p := &ssh.Permissions{}
  229. p.Extensions = make(map[string]string)
  230. p.Extensions["user"] = string(json)
  231. return p, nil
  232. }
  233. // If no host keys are defined we try to use or generate the default one.
  234. func (c *Configuration) checkHostKeys(configDir string) error {
  235. var err error
  236. if len(c.Keys) == 0 {
  237. autoFile := filepath.Join(configDir, defaultPrivateKeyName)
  238. if _, err = os.Stat(autoFile); os.IsNotExist(err) {
  239. logger.Info(logSender, "No host keys configured and %s does not exist; creating new private key for server", autoFile)
  240. logger.InfoToConsole("No host keys configured and %s does not exist; creating new private key for server", autoFile)
  241. err = c.generatePrivateKey(autoFile)
  242. }
  243. c.Keys = append(c.Keys, Key{PrivateKey: defaultPrivateKeyName})
  244. }
  245. return err
  246. }
  247. func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKey string) (*ssh.Permissions, error) {
  248. var err error
  249. var user dataprovider.User
  250. if user, err = dataprovider.CheckUserAndPubKey(dataProvider, conn.User(), pubKey); err == nil {
  251. return loginUser(user)
  252. }
  253. return nil, err
  254. }
  255. func (c Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  256. var err error
  257. var user dataprovider.User
  258. if user, err = dataprovider.CheckUserAndPass(dataProvider, conn.User(), string(pass)); err == nil {
  259. return loginUser(user)
  260. }
  261. return nil, err
  262. }
  263. // Generates a private key that will be used by the SFTP server.
  264. func (c Configuration) generatePrivateKey(file string) error {
  265. key, err := rsa.GenerateKey(rand.Reader, 4096)
  266. if err != nil {
  267. return err
  268. }
  269. o, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  270. if err != nil {
  271. return err
  272. }
  273. defer o.Close()
  274. pkey := &pem.Block{
  275. Type: "RSA PRIVATE KEY",
  276. Bytes: x509.MarshalPKCS1PrivateKey(key),
  277. }
  278. if err := pem.Encode(o, pkey); err != nil {
  279. return err
  280. }
  281. return nil
  282. }