| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- package dataprovider
- import (
- "encoding/binary"
- "encoding/json"
- "errors"
- "fmt"
- "path/filepath"
- "time"
- "github.com/drakkan/sftpgo/logger"
- "github.com/drakkan/sftpgo/utils"
- bolt "go.etcd.io/bbolt"
- )
- const (
- boltDatabaseVersion = 3
- )
- var (
- usersBucket = []byte("users")
- usersIDIdxBucket = []byte("users_id_idx")
- dbVersionBucket = []byte("db_version")
- dbVersionKey = []byte("version")
- )
- // BoltProvider auth provider for bolt key/value store
- type BoltProvider struct {
- dbHandle *bolt.DB
- }
- type compatUserV2 struct {
- ID int64 `json:"id"`
- Username string `json:"username"`
- Password string `json:"password,omitempty"`
- PublicKeys []string `json:"public_keys,omitempty"`
- HomeDir string `json:"home_dir"`
- UID int `json:"uid"`
- GID int `json:"gid"`
- MaxSessions int `json:"max_sessions"`
- QuotaSize int64 `json:"quota_size"`
- QuotaFiles int `json:"quota_files"`
- Permissions []string `json:"permissions"`
- UsedQuotaSize int64 `json:"used_quota_size"`
- UsedQuotaFiles int `json:"used_quota_files"`
- LastQuotaUpdate int64 `json:"last_quota_update"`
- UploadBandwidth int64 `json:"upload_bandwidth"`
- DownloadBandwidth int64 `json:"download_bandwidth"`
- ExpirationDate int64 `json:"expiration_date"`
- LastLogin int64 `json:"last_login"`
- Status int `json:"status"`
- }
- func initializeBoltProvider(basePath string) error {
- var err error
- logSender = BoltDataProviderName
- dbPath := config.Name
- if !utils.IsFileInputValid(dbPath) {
- return fmt.Errorf("Invalid database path: %#v", dbPath)
- }
- if !filepath.IsAbs(dbPath) {
- dbPath = filepath.Join(basePath, dbPath)
- }
- dbHandle, err := bolt.Open(dbPath, 0600, &bolt.Options{
- NoGrowSync: false,
- FreelistType: bolt.FreelistArrayType,
- Timeout: 5 * time.Second})
- if err == nil {
- providerLog(logger.LevelDebug, "bolt key store handle created")
- err = dbHandle.Update(func(tx *bolt.Tx) error {
- _, e := tx.CreateBucketIfNotExists(usersBucket)
- return e
- })
- if err != nil {
- providerLog(logger.LevelWarn, "error creating users bucket: %v", err)
- return err
- }
- err = dbHandle.Update(func(tx *bolt.Tx) error {
- _, e := tx.CreateBucketIfNotExists(usersIDIdxBucket)
- return e
- })
- if err != nil {
- providerLog(logger.LevelWarn, "error creating username idx bucket: %v", err)
- return err
- }
- err = dbHandle.Update(func(tx *bolt.Tx) error {
- _, e := tx.CreateBucketIfNotExists(dbVersionBucket)
- return e
- })
- if err != nil {
- providerLog(logger.LevelWarn, "error creating database version bucket: %v", err)
- return err
- }
- provider = BoltProvider{dbHandle: dbHandle}
- } else {
- providerLog(logger.LevelWarn, "error creating bolt key/value store handler: %v", err)
- }
- return err
- }
- func (p BoltProvider) checkAvailability() error {
- _, err := p.getUsers(1, 0, "ASC", "")
- return err
- }
- func (p BoltProvider) validateUserAndPass(username string, password string) (User, error) {
- var user User
- if len(password) == 0 {
- return user, errors.New("Credentials cannot be null or empty")
- }
- user, err := p.userExists(username)
- if err != nil {
- providerLog(logger.LevelWarn, "error authenticating user: %v, error: %v", username, err)
- return user, err
- }
- return checkUserAndPass(user, password)
- }
- func (p BoltProvider) validateUserAndPubKey(username string, pubKey string) (User, string, error) {
- var user User
- if len(pubKey) == 0 {
- return user, "", errors.New("Credentials cannot be null or empty")
- }
- user, err := p.userExists(username)
- if err != nil {
- providerLog(logger.LevelWarn, "error authenticating user: %v, error: %v", username, err)
- return user, "", err
- }
- return checkUserAndPubKey(user, pubKey)
- }
- func (p BoltProvider) getUserByID(ID int64) (User, error) {
- var user User
- err := p.dbHandle.View(func(tx *bolt.Tx) error {
- bucket, idxBucket, err := getBuckets(tx)
- if err != nil {
- return err
- }
- userIDAsBytes := itob(ID)
- username := idxBucket.Get(userIDAsBytes)
- if username == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("user with ID %v does not exist", ID)}
- }
- u := bucket.Get(username)
- if u == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("username %#v and ID: %v does not exist", string(username), ID)}
- }
- return json.Unmarshal(u, &user)
- })
- return user, err
- }
- func (p BoltProvider) updateLastLogin(username string) error {
- return p.dbHandle.Update(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- var u []byte
- if u = bucket.Get([]byte(username)); u == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("username %#v does not exist, unable to update last login", username)}
- }
- var user User
- err = json.Unmarshal(u, &user)
- if err != nil {
- return err
- }
- user.LastLogin = utils.GetTimeAsMsSinceEpoch(time.Now())
- buf, err := json.Marshal(user)
- if err != nil {
- return err
- }
- return bucket.Put([]byte(username), buf)
- })
- }
- func (p BoltProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
- return p.dbHandle.Update(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- var u []byte
- if u = bucket.Get([]byte(username)); u == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("username %#v does not exist, unable to update quota", username)}
- }
- var user User
- err = json.Unmarshal(u, &user)
- if err != nil {
- return err
- }
- if reset {
- user.UsedQuotaSize = sizeAdd
- user.UsedQuotaFiles = filesAdd
- } else {
- user.UsedQuotaSize += sizeAdd
- user.UsedQuotaFiles += filesAdd
- }
- user.LastQuotaUpdate = utils.GetTimeAsMsSinceEpoch(time.Now())
- buf, err := json.Marshal(user)
- if err != nil {
- return err
- }
- return bucket.Put([]byte(username), buf)
- })
- }
- func (p BoltProvider) getUsedQuota(username string) (int, int64, error) {
- user, err := p.userExists(username)
- if err != nil {
- providerLog(logger.LevelWarn, "unable to get quota for user %v error: %v", username, err)
- return 0, 0, err
- }
- return user.UsedQuotaFiles, user.UsedQuotaSize, err
- }
- func (p BoltProvider) userExists(username string) (User, error) {
- var user User
- err := p.dbHandle.View(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- u := bucket.Get([]byte(username))
- if u == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", username)}
- }
- return json.Unmarshal(u, &user)
- })
- return user, err
- }
- func (p BoltProvider) addUser(user User) error {
- err := validateUser(&user)
- if err != nil {
- return err
- }
- return p.dbHandle.Update(func(tx *bolt.Tx) error {
- bucket, idxBucket, err := getBuckets(tx)
- if err != nil {
- return err
- }
- if u := bucket.Get([]byte(user.Username)); u != nil {
- return fmt.Errorf("username %v already exists", user.Username)
- }
- id, err := bucket.NextSequence()
- if err != nil {
- return err
- }
- user.ID = int64(id)
- buf, err := json.Marshal(user)
- if err != nil {
- return err
- }
- userIDAsBytes := itob(user.ID)
- err = bucket.Put([]byte(user.Username), buf)
- if err != nil {
- return err
- }
- return idxBucket.Put(userIDAsBytes, []byte(user.Username))
- })
- }
- func (p BoltProvider) updateUser(user User) error {
- err := validateUser(&user)
- if err != nil {
- return err
- }
- return p.dbHandle.Update(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- if u := bucket.Get([]byte(user.Username)); u == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("username %v does not exist", user.Username)}
- }
- buf, err := json.Marshal(user)
- if err != nil {
- return err
- }
- return bucket.Put([]byte(user.Username), buf)
- })
- }
- func (p BoltProvider) deleteUser(user User) error {
- return p.dbHandle.Update(func(tx *bolt.Tx) error {
- bucket, idxBucket, err := getBuckets(tx)
- if err != nil {
- return err
- }
- userIDAsBytes := itob(user.ID)
- userName := idxBucket.Get(userIDAsBytes)
- if userName == nil {
- return &RecordNotFoundError{err: fmt.Sprintf("user with id %v does not exist", user.ID)}
- }
- err = bucket.Delete(userName)
- if err != nil {
- return err
- }
- return idxBucket.Delete(userIDAsBytes)
- })
- }
- func (p BoltProvider) dumpUsers() ([]User, error) {
- users := []User{}
- var err error
- err = p.dbHandle.View(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- cursor := bucket.Cursor()
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- var user User
- err = json.Unmarshal(v, &user)
- if err != nil {
- return err
- }
- err = addCredentialsToUser(&user)
- if err != nil {
- return err
- }
- users = append(users, user)
- }
- return err
- })
- return users, err
- }
- func (p BoltProvider) getUserWithUsername(username string) ([]User, error) {
- users := []User{}
- var user User
- user, err := p.userExists(username)
- if err == nil {
- users = append(users, HideUserSensitiveData(&user))
- return users, nil
- }
- if _, ok := err.(*RecordNotFoundError); ok {
- err = nil
- }
- return users, err
- }
- func (p BoltProvider) getUsers(limit int, offset int, order string, username string) ([]User, error) {
- users := []User{}
- var err error
- if limit <= 0 {
- return users, err
- }
- if len(username) > 0 {
- if offset == 0 {
- return p.getUserWithUsername(username)
- }
- return users, err
- }
- err = p.dbHandle.View(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- cursor := bucket.Cursor()
- itNum := 0
- if order == "ASC" {
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- itNum++
- if itNum <= offset {
- continue
- }
- var user User
- err = json.Unmarshal(v, &user)
- if err == nil {
- users = append(users, HideUserSensitiveData(&user))
- }
- if len(users) >= limit {
- break
- }
- }
- } else {
- for k, v := cursor.Last(); k != nil; k, v = cursor.Prev() {
- itNum++
- if itNum <= offset {
- continue
- }
- var user User
- err = json.Unmarshal(v, &user)
- if err == nil {
- users = append(users, HideUserSensitiveData(&user))
- }
- if len(users) >= limit {
- break
- }
- }
- }
- return err
- })
- return users, err
- }
- func (p BoltProvider) close() error {
- return p.dbHandle.Close()
- }
- func (p BoltProvider) reloadConfig() error {
- return nil
- }
- // initializeDatabase does nothing, no initilization is needed for bolt provider
- func (p BoltProvider) initializeDatabase() error {
- return errNoInitRequired
- }
- func (p BoltProvider) migrateDatabase() error {
- dbVersion, err := getBoltDatabaseVersion(p.dbHandle)
- if err != nil {
- return err
- }
- if dbVersion.Version == boltDatabaseVersion {
- providerLog(logger.LevelDebug, "bolt database is updated, current version: %v", dbVersion.Version)
- return nil
- }
- if dbVersion.Version == 1 {
- err = updateDatabaseFrom1To2(p.dbHandle)
- if err != nil {
- return err
- }
- return updateDatabaseFrom2To3(p.dbHandle)
- } else if dbVersion.Version == 2 {
- return updateDatabaseFrom2To3(p.dbHandle)
- }
- return nil
- }
- // itob returns an 8-byte big endian representation of v.
- func itob(v int64) []byte {
- b := make([]byte, 8)
- binary.BigEndian.PutUint64(b, uint64(v))
- return b
- }
- func getBuckets(tx *bolt.Tx) (*bolt.Bucket, *bolt.Bucket, error) {
- var err error
- bucket := tx.Bucket(usersBucket)
- idxBucket := tx.Bucket(usersIDIdxBucket)
- if bucket == nil || idxBucket == nil {
- err = fmt.Errorf("unable to find required buckets, bolt database structure not correcly defined")
- }
- return bucket, idxBucket, err
- }
- func updateDatabaseFrom1To2(dbHandle *bolt.DB) error {
- providerLog(logger.LevelInfo, "updating bolt database version: 1 -> 2")
- usernames, err := getBoltAvailableUsernames(dbHandle)
- if err != nil {
- return err
- }
- for _, u := range usernames {
- user, err := provider.userExists(u)
- if err != nil {
- return err
- }
- user.Status = 1
- err = provider.updateUser(user)
- if err != nil {
- return err
- }
- providerLog(logger.LevelInfo, "user %#v updated, \"status\" setted to 1", user.Username)
- }
- return updateBoltDatabaseVersion(dbHandle, 2)
- }
- func updateDatabaseFrom2To3(dbHandle *bolt.DB) error {
- providerLog(logger.LevelInfo, "updating bolt database version: 2 -> 3")
- users := []User{}
- err := dbHandle.View(func(tx *bolt.Tx) error {
- bucket, _, err := getBuckets(tx)
- if err != nil {
- return err
- }
- cursor := bucket.Cursor()
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- var compatUser compatUserV2
- err = json.Unmarshal(v, &compatUser)
- if err == nil {
- user := User{}
- user.ID = compatUser.ID
- user.Username = compatUser.Username
- user.Password = compatUser.Password
- user.PublicKeys = compatUser.PublicKeys
- user.HomeDir = compatUser.HomeDir
- user.UID = compatUser.UID
- user.GID = compatUser.GID
- user.MaxSessions = compatUser.MaxSessions
- user.QuotaSize = compatUser.QuotaSize
- user.QuotaFiles = compatUser.QuotaFiles
- user.Permissions = make(map[string][]string)
- user.Permissions["/"] = compatUser.Permissions
- user.UsedQuotaSize = compatUser.UsedQuotaSize
- user.UsedQuotaFiles = compatUser.UsedQuotaFiles
- user.LastQuotaUpdate = compatUser.LastQuotaUpdate
- user.UploadBandwidth = compatUser.UploadBandwidth
- user.DownloadBandwidth = compatUser.DownloadBandwidth
- user.ExpirationDate = compatUser.ExpirationDate
- user.LastLogin = compatUser.LastLogin
- user.Status = compatUser.Status
- users = append(users, user)
- }
- }
- return err
- })
- if err != nil {
- return err
- }
- for _, user := range users {
- err = provider.updateUser(user)
- if err != nil {
- return err
- }
- providerLog(logger.LevelInfo, "user %#v updated, \"permissions\" setted to %+v", user.Username, user.Permissions)
- }
- return updateBoltDatabaseVersion(dbHandle, 3)
- }
- func getBoltAvailableUsernames(dbHandle *bolt.DB) ([]string, error) {
- usernames := []string{}
- err := dbHandle.View(func(tx *bolt.Tx) error {
- _, idxBucket, err := getBuckets(tx)
- if err != nil {
- return err
- }
- cursor := idxBucket.Cursor()
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- usernames = append(usernames, string(v))
- }
- return nil
- })
- return usernames, err
- }
- func getBoltDatabaseVersion(dbHandle *bolt.DB) (schemaVersion, error) {
- var dbVersion schemaVersion
- err := dbHandle.View(func(tx *bolt.Tx) error {
- bucket := tx.Bucket(dbVersionBucket)
- if bucket == nil {
- return fmt.Errorf("unable to find database version bucket")
- }
- v := bucket.Get(dbVersionKey)
- if v == nil {
- dbVersion = schemaVersion{
- Version: 1,
- }
- return nil
- }
- return json.Unmarshal(v, &dbVersion)
- })
- return dbVersion, err
- }
- func updateBoltDatabaseVersion(dbHandle *bolt.DB, version int) error {
- err := dbHandle.Update(func(tx *bolt.Tx) error {
- bucket := tx.Bucket(dbVersionBucket)
- if bucket == nil {
- return fmt.Errorf("unable to find database version bucket")
- }
- newDbVersion := schemaVersion{
- Version: version,
- }
- buf, err := json.Marshal(newDbVersion)
- if err != nil {
- return err
- }
- return bucket.Put(dbVersionKey, buf)
- })
- return err
- }
|