dataprovider.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // Package dataprovider provides data access.
  2. // It abstract different data providers and exposes a common API.
  3. // Currently the supported data providers are: PostreSQL (9+), MySQL (4.1+) and SQLite 3.x
  4. package dataprovider
  5. import (
  6. "crypto/sha1"
  7. "crypto/sha256"
  8. "crypto/sha512"
  9. "crypto/subtle"
  10. "encoding/base64"
  11. "errors"
  12. "fmt"
  13. "hash"
  14. "path/filepath"
  15. "strconv"
  16. "strings"
  17. "github.com/alexedwards/argon2id"
  18. "golang.org/x/crypto/bcrypt"
  19. "golang.org/x/crypto/pbkdf2"
  20. "golang.org/x/crypto/ssh"
  21. "github.com/drakkan/sftpgo/logger"
  22. "github.com/drakkan/sftpgo/utils"
  23. )
  24. const (
  25. // SQLiteDataProviderName name for SQLite database provider
  26. SQLiteDataProviderName = "sqlite"
  27. // PGSSQLDataProviderName name for PostgreSQL database provider
  28. PGSSQLDataProviderName = "postgresql"
  29. // MySQLDataProviderName name for MySQL database provider
  30. MySQLDataProviderName = "mysql"
  31. // BoltDataProviderName name for bbolt key/value store provider
  32. BoltDataProviderName = "bolt"
  33. logSender = "dataProvider"
  34. argonPwdPrefix = "$argon2id$"
  35. bcryptPwdPrefix = "$2a$"
  36. pbkdf2SHA1Prefix = "$pbkdf2-sha1$"
  37. pbkdf2SHA256Prefix = "$pbkdf2-sha256$"
  38. pbkdf2SHA512Prefix = "$pbkdf2-sha512$"
  39. manageUsersDisabledError = "please set manage_users to 1 in sftpgo.conf to enable this method"
  40. trackQuotaDisabledError = "please enable track_quota in sftpgo.conf to use this method"
  41. )
  42. var (
  43. // SupportedProviders data provider configured in the sftpgo.conf file must match of these strings
  44. SupportedProviders = []string{SQLiteDataProviderName, PGSSQLDataProviderName, MySQLDataProviderName, BoltDataProviderName}
  45. config Config
  46. provider Provider
  47. sqlPlaceholders []string
  48. validPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermDelete, PermRename,
  49. PermCreateDirs, PermCreateSymlinks}
  50. hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
  51. pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
  52. )
  53. // Config provider configuration
  54. type Config struct {
  55. // Driver name, must be one of the SupportedProviders
  56. Driver string `json:"driver" mapstructure:"driver"`
  57. // Database name
  58. Name string `json:"name" mapstructure:"name"`
  59. // Database host
  60. Host string `json:"host" mapstructure:"host"`
  61. // Database port
  62. Port int `json:"port" mapstructure:"port"`
  63. // Database username
  64. Username string `json:"username" mapstructure:"username"`
  65. // Database password
  66. Password string `json:"password" mapstructure:"password"`
  67. // Used for drivers mysql and postgresql.
  68. // 0 disable SSL/TLS connections.
  69. // 1 require ssl.
  70. // 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql.
  71. // 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql.
  72. SSLMode int `json:"sslmode" mapstructure:"sslmode"`
  73. // Custom database connection string.
  74. // If not empty this connection string will be used instead of build one using the previous parameters
  75. ConnectionString string `json:"connection_string" mapstructure:"connection_string"`
  76. // Database table for SFTP users
  77. UsersTable string `json:"users_table" mapstructure:"users_table"`
  78. // Set to 0 to disable users management, 1 to enable
  79. ManageUsers int `json:"manage_users" mapstructure:"manage_users"`
  80. // Set the preferred way to track users quota between the following choices:
  81. // 0, disable quota tracking. REST API to scan user dir and update quota will do nothing
  82. // 1, quota is updated each time a user upload or delete a file even if the user has no quota restrictions
  83. // 2, quota is updated each time a user upload or delete a file but only for users with quota restrictions.
  84. // With this configuration the "quota scan" REST API can still be used to periodically update space usage
  85. // for users without quota restrictions
  86. TrackQuota int `json:"track_quota" mapstructure:"track_quota"`
  87. }
  88. // ValidationError raised if input data is not valid
  89. type ValidationError struct {
  90. err string
  91. }
  92. // Validation error details
  93. func (e *ValidationError) Error() string {
  94. return fmt.Sprintf("Validation error: %s", e.err)
  95. }
  96. // MethodDisabledError raised if a method is disabled in config file.
  97. // For example, if user management is disabled, this error is raised
  98. // every time an user operation is done using the REST API
  99. type MethodDisabledError struct {
  100. err string
  101. }
  102. // Method disabled error details
  103. func (e *MethodDisabledError) Error() string {
  104. return fmt.Sprintf("Method disabled error: %s", e.err)
  105. }
  106. // RecordNotFoundError raised if a requested user is not found
  107. type RecordNotFoundError struct {
  108. err string
  109. }
  110. func (e *RecordNotFoundError) Error() string {
  111. return fmt.Sprintf("Not found: %s", e.err)
  112. }
  113. // GetProvider returns the configured provider
  114. func GetProvider() Provider {
  115. return provider
  116. }
  117. // Provider interface that data providers must implement.
  118. type Provider interface {
  119. validateUserAndPass(username string, password string) (User, error)
  120. validateUserAndPubKey(username string, pubKey string) (User, error)
  121. updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
  122. getUsedQuota(username string) (int, int64, error)
  123. userExists(username string) (User, error)
  124. addUser(user User) error
  125. updateUser(user User) error
  126. deleteUser(user User) error
  127. getUsers(limit int, offset int, order string, username string) ([]User, error)
  128. getUserByID(ID int64) (User, error)
  129. }
  130. // Initialize the data provider.
  131. // An error is returned if the configured driver is invalid or if the data provider cannot be initialized
  132. func Initialize(cnf Config, basePath string) error {
  133. config = cnf
  134. sqlPlaceholders = getSQLPlaceholders()
  135. if config.Driver == SQLiteDataProviderName {
  136. return initializeSQLiteProvider(basePath)
  137. } else if config.Driver == PGSSQLDataProviderName {
  138. return initializePGSQLProvider()
  139. } else if config.Driver == MySQLDataProviderName {
  140. return initializeMySQLProvider()
  141. } else if config.Driver == BoltDataProviderName {
  142. return initializeBoltProvider(basePath)
  143. }
  144. return fmt.Errorf("Unsupported data provider: %v", config.Driver)
  145. }
  146. // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
  147. func CheckUserAndPass(p Provider, username string, password string) (User, error) {
  148. return p.validateUserAndPass(username, password)
  149. }
  150. // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
  151. func CheckUserAndPubKey(p Provider, username string, pubKey string) (User, error) {
  152. return p.validateUserAndPubKey(username, pubKey)
  153. }
  154. // UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
  155. // If reset is true filesAdd and sizeAdd indicates the total files and the total size instead of the difference.
  156. func UpdateUserQuota(p Provider, user User, filesAdd int, sizeAdd int64, reset bool) error {
  157. if config.TrackQuota == 0 {
  158. return &MethodDisabledError{err: trackQuotaDisabledError}
  159. } else if config.TrackQuota == 2 && !reset && !user.HasQuotaRestrictions() {
  160. return nil
  161. }
  162. return p.updateQuota(user.Username, filesAdd, sizeAdd, reset)
  163. }
  164. // GetUsedQuota returns the used quota for the given SFTP user.
  165. // TrackQuota must be >=1 to enable this method
  166. func GetUsedQuota(p Provider, username string) (int, int64, error) {
  167. if config.TrackQuota == 0 {
  168. return 0, 0, &MethodDisabledError{err: trackQuotaDisabledError}
  169. }
  170. return p.getUsedQuota(username)
  171. }
  172. // UserExists checks if the given SFTP username exists, returns an error if no match is found
  173. func UserExists(p Provider, username string) (User, error) {
  174. return p.userExists(username)
  175. }
  176. // AddUser adds a new SFTP user.
  177. // ManageUsers configuration must be set to 1 to enable this method
  178. func AddUser(p Provider, user User) error {
  179. if config.ManageUsers == 0 {
  180. return &MethodDisabledError{err: manageUsersDisabledError}
  181. }
  182. return p.addUser(user)
  183. }
  184. // UpdateUser updates an existing SFTP user.
  185. // ManageUsers configuration must be set to 1 to enable this method
  186. func UpdateUser(p Provider, user User) error {
  187. if config.ManageUsers == 0 {
  188. return &MethodDisabledError{err: manageUsersDisabledError}
  189. }
  190. return p.updateUser(user)
  191. }
  192. // DeleteUser deletes an existing SFTP user.
  193. // ManageUsers configuration must be set to 1 to enable this method
  194. func DeleteUser(p Provider, user User) error {
  195. if config.ManageUsers == 0 {
  196. return &MethodDisabledError{err: manageUsersDisabledError}
  197. }
  198. return p.deleteUser(user)
  199. }
  200. // GetUsers returns an array of users respecting limit and offset and filtered by username exact match if not empty
  201. func GetUsers(p Provider, limit int, offset int, order string, username string) ([]User, error) {
  202. return p.getUsers(limit, offset, order, username)
  203. }
  204. // GetUserByID returns the user with the given database ID if a match is found or an error
  205. func GetUserByID(p Provider, ID int64) (User, error) {
  206. return p.getUserByID(ID)
  207. }
  208. func validateUser(user *User) error {
  209. if len(user.Username) == 0 || len(user.HomeDir) == 0 {
  210. return &ValidationError{err: "Mandatory parameters missing"}
  211. }
  212. if len(user.Password) == 0 && len(user.PublicKeys) == 0 {
  213. return &ValidationError{err: "Please set password or at least a public_key"}
  214. }
  215. if len(user.Permissions) == 0 {
  216. return &ValidationError{err: "Please grant some permissions to this user"}
  217. }
  218. if !filepath.IsAbs(user.HomeDir) {
  219. return &ValidationError{err: fmt.Sprintf("home_dir must be an absolute path, actual value: %v", user.HomeDir)}
  220. }
  221. for _, p := range user.Permissions {
  222. if !utils.IsStringInSlice(p, validPerms) {
  223. return &ValidationError{err: fmt.Sprintf("Invalid permission: %v", p)}
  224. }
  225. }
  226. if len(user.Password) > 0 && !utils.IsStringPrefixInSlice(user.Password, hashPwdPrefixes) {
  227. pwd, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
  228. if err != nil {
  229. return err
  230. }
  231. user.Password = pwd
  232. }
  233. for i, k := range user.PublicKeys {
  234. _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  235. if err != nil {
  236. return &ValidationError{err: fmt.Sprintf("Could not parse key nr. %d: %s", i, err)}
  237. }
  238. }
  239. return nil
  240. }
  241. func checkUserAndPass(user User, password string) (User, error) {
  242. var err error
  243. if len(user.Password) == 0 {
  244. return user, errors.New("Credentials cannot be null or empty")
  245. }
  246. var match bool
  247. if strings.HasPrefix(user.Password, argonPwdPrefix) {
  248. match, err = argon2id.ComparePasswordAndHash(password, user.Password)
  249. if err != nil {
  250. logger.Warn(logSender, "", "error comparing password with argon hash: %v", err)
  251. return user, err
  252. }
  253. } else if strings.HasPrefix(user.Password, bcryptPwdPrefix) {
  254. if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
  255. logger.Warn(logSender, "", "error comparing password with bcrypt hash: %v", err)
  256. return user, err
  257. }
  258. match = true
  259. } else if utils.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
  260. match, err = comparePbkdf2PasswordAndHash(password, user.Password)
  261. if err != nil {
  262. logger.Warn(logSender, "", "error comparing password with pbkdf2 sha256 hash: %v", err)
  263. return user, err
  264. }
  265. }
  266. if !match {
  267. err = errors.New("Invalid credentials")
  268. }
  269. return user, err
  270. }
  271. func checkUserAndPubKey(user User, pubKey string) (User, error) {
  272. if len(user.PublicKeys) == 0 {
  273. return user, errors.New("Invalid credentials")
  274. }
  275. for i, k := range user.PublicKeys {
  276. storedPubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
  277. if err != nil {
  278. logger.Warn(logSender, "", "error parsing stored public key %d for user %v: %v", i, user.Username, err)
  279. return user, err
  280. }
  281. if string(storedPubKey.Marshal()) == pubKey {
  282. return user, nil
  283. }
  284. }
  285. return user, errors.New("Invalid credentials")
  286. }
  287. func comparePbkdf2PasswordAndHash(password, hashedPassword string) (bool, error) {
  288. vals := strings.Split(hashedPassword, "$")
  289. if len(vals) != 5 {
  290. return false, fmt.Errorf("pbkdf2: hash is not in the correct format")
  291. }
  292. var hashFunc func() hash.Hash
  293. var hashSize int
  294. if strings.HasPrefix(hashedPassword, pbkdf2SHA256Prefix) {
  295. hashSize = sha256.Size
  296. hashFunc = sha256.New
  297. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA512Prefix) {
  298. hashSize = sha512.Size
  299. hashFunc = sha512.New
  300. } else if strings.HasPrefix(hashedPassword, pbkdf2SHA1Prefix) {
  301. hashSize = sha1.Size
  302. hashFunc = sha1.New
  303. } else {
  304. return false, fmt.Errorf("pbkdf2: invalid or unsupported hash format %v", vals[1])
  305. }
  306. iterations, err := strconv.Atoi(vals[2])
  307. if err != nil {
  308. return false, err
  309. }
  310. salt := vals[3]
  311. expected := vals[4]
  312. df := pbkdf2.Key([]byte(password), []byte(salt), iterations, hashSize, hashFunc)
  313. buf := make([]byte, base64.StdEncoding.EncodedLen(len(df)))
  314. base64.StdEncoding.Encode(buf, df)
  315. return subtle.ConstantTimeCompare(buf, []byte(expected)) == 1, nil
  316. }
  317. func getSSLMode() string {
  318. if config.Driver == PGSSQLDataProviderName {
  319. if config.SSLMode == 0 {
  320. return "disable"
  321. } else if config.SSLMode == 1 {
  322. return "require"
  323. } else if config.SSLMode == 2 {
  324. return "verify-ca"
  325. } else if config.SSLMode == 3 {
  326. return "verify-full"
  327. }
  328. } else if config.Driver == MySQLDataProviderName {
  329. if config.SSLMode == 0 {
  330. return "false"
  331. } else if config.SSLMode == 1 {
  332. return "true"
  333. } else if config.SSLMode == 2 {
  334. return "skip-verify"
  335. } else if config.SSLMode == 3 {
  336. return "preferred"
  337. }
  338. }
  339. return ""
  340. }