sqlite.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // +build !nosqlite
  2. package dataprovider
  3. import (
  4. "context"
  5. "crypto/x509"
  6. "database/sql"
  7. "errors"
  8. "fmt"
  9. "path/filepath"
  10. "strings"
  11. // we import go-sqlite3 here to be able to disable SQLite support using a build tag
  12. _ "github.com/mattn/go-sqlite3"
  13. "github.com/drakkan/sftpgo/logger"
  14. "github.com/drakkan/sftpgo/utils"
  15. "github.com/drakkan/sftpgo/version"
  16. "github.com/drakkan/sftpgo/vfs"
  17. )
  18. const (
  19. sqliteInitialSQL = `CREATE TABLE "{{schema_version}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "version" integer NOT NULL);
  20. CREATE TABLE "{{admins}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE,
  21. "password" varchar(255) NOT NULL, "email" varchar(255) NULL, "status" integer NOT NULL, "permissions" text NOT NULL,
  22. "filters" text NULL, "additional_info" text NULL);
  23. CREATE TABLE "{{folders}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE,
  24. "path" varchar(512) NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL,
  25. "last_quota_update" bigint NOT NULL);
  26. CREATE TABLE "{{users}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE,
  27. "password" text NULL, "public_keys" text NULL, "home_dir" varchar(512) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL,
  28. "max_sessions" integer NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL,
  29. "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL,
  30. "upload_bandwidth" integer NOT NULL, "download_bandwidth" integer NOT NULL, "expiration_date" bigint NOT NULL,
  31. "last_login" bigint NOT NULL, "status" integer NOT NULL, "filters" text NULL, "filesystem" text NULL,
  32. "additional_info" text NULL);
  33. CREATE TABLE "{{folders_mapping}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "virtual_path" varchar(512) NOT NULL,
  34. "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "folder_id" integer NOT NULL REFERENCES "{{folders}}" ("id")
  35. ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, "user_id" integer NOT NULL REFERENCES "{{users}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
  36. CONSTRAINT "{{prefix}}unique_mapping" UNIQUE ("user_id", "folder_id"));
  37. CREATE INDEX "{{prefix}}folders_mapping_folder_id_idx" ON "{{folders_mapping}}" ("folder_id");
  38. CREATE INDEX "{{prefix}}folders_mapping_user_id_idx" ON "{{folders_mapping}}" ("user_id");
  39. INSERT INTO {{schema_version}} (version) VALUES (8);
  40. `
  41. sqliteV9SQL = `ALTER TABLE "{{admins}}" ADD COLUMN "description" varchar(512) NULL;
  42. ALTER TABLE "{{folders}}" ADD COLUMN "description" varchar(512) NULL;
  43. ALTER TABLE "{{folders}}" ADD COLUMN "filesystem" text NULL;
  44. ALTER TABLE "{{users}}" ADD COLUMN "description" varchar(512) NULL;
  45. `
  46. sqliteV9DownSQL = `CREATE TABLE "new__users" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "status" integer NOT NULL,
  47. "expiration_date" bigint NOT NULL, "username" varchar(255) NOT NULL UNIQUE, "password" text NULL, "public_keys" text NULL,
  48. "home_dir" varchar(512) NOT NULL, "uid" integer NOT NULL, "gid" integer NOT NULL, "max_sessions" integer NOT NULL,
  49. "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL, "permissions" text NOT NULL, "used_quota_size" bigint NOT NULL,
  50. "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL, "upload_bandwidth" integer NOT NULL,
  51. "download_bandwidth" integer NOT NULL, "last_login" bigint NOT NULL, "filters" text NULL, "filesystem" text NULL,
  52. "additional_info" text NULL);
  53. INSERT INTO "new__users" ("id", "status", "expiration_date", "username", "password", "public_keys", "home_dir", "uid", "gid",
  54. "max_sessions", "quota_size", "quota_files", "permissions", "used_quota_size", "used_quota_files", "last_quota_update",
  55. "upload_bandwidth", "download_bandwidth", "last_login", "filters", "filesystem", "additional_info")
  56. SELECT "id", "status", "expiration_date", "username", "password", "public_keys", "home_dir", "uid", "gid", "max_sessions",
  57. "quota_size", "quota_files", "permissions", "used_quota_size", "used_quota_files", "last_quota_update", "upload_bandwidth",
  58. "download_bandwidth", "last_login", "filters", "filesystem", "additional_info" FROM "{{users}}";
  59. DROP TABLE "{{users}}";
  60. ALTER TABLE "new__users" RENAME TO "{{users}}";
  61. CREATE TABLE "new__admins" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "username" varchar(255) NOT NULL UNIQUE,
  62. "password" varchar(255) NOT NULL, "email" varchar(255) NULL, "status" integer NOT NULL, "permissions" text NOT NULL,
  63. "filters" text NULL, "additional_info" text NULL);
  64. INSERT INTO "new__admins" ("id", "username", "password", "email", "status", "permissions", "filters", "additional_info")
  65. SELECT "id", "username", "password", "email", "status", "permissions", "filters", "additional_info" FROM "{{admins}}";
  66. DROP TABLE "{{admins}}";
  67. ALTER TABLE "new__admins" RENAME TO "{{admins}}";
  68. CREATE TABLE "new__folders" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL UNIQUE,
  69. "path" varchar(512) NULL, "used_quota_size" bigint NOT NULL, "used_quota_files" integer NOT NULL, "last_quota_update" bigint NOT NULL);
  70. INSERT INTO "new__folders" ("id", "name", "path", "used_quota_size", "used_quota_files", "last_quota_update")
  71. SELECT "id", "name", "path", "used_quota_size", "used_quota_files", "last_quota_update" FROM "{{folders}}";
  72. DROP TABLE "{{folders}}";
  73. ALTER TABLE "new__folders" RENAME TO "{{folders}}";
  74. `
  75. )
  76. // SQLiteProvider auth provider for SQLite database
  77. type SQLiteProvider struct {
  78. dbHandle *sql.DB
  79. }
  80. func init() {
  81. version.AddFeature("+sqlite")
  82. }
  83. func initializeSQLiteProvider(basePath string) error {
  84. var err error
  85. var connectionString string
  86. if config.ConnectionString == "" {
  87. dbPath := config.Name
  88. if !utils.IsFileInputValid(dbPath) {
  89. return fmt.Errorf("invalid database path: %#v", dbPath)
  90. }
  91. if !filepath.IsAbs(dbPath) {
  92. dbPath = filepath.Join(basePath, dbPath)
  93. }
  94. connectionString = fmt.Sprintf("file:%v?cache=shared&_foreign_keys=1", dbPath)
  95. } else {
  96. connectionString = config.ConnectionString
  97. }
  98. dbHandle, err := sql.Open("sqlite3", connectionString)
  99. if err == nil {
  100. providerLog(logger.LevelDebug, "sqlite database handle created, connection string: %#v", connectionString)
  101. dbHandle.SetMaxOpenConns(1)
  102. provider = &SQLiteProvider{dbHandle: dbHandle}
  103. } else {
  104. providerLog(logger.LevelWarn, "error creating sqlite database handler, connection string: %#v, error: %v",
  105. connectionString, err)
  106. }
  107. return err
  108. }
  109. func (p *SQLiteProvider) checkAvailability() error {
  110. return sqlCommonCheckAvailability(p.dbHandle)
  111. }
  112. func (p *SQLiteProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
  113. return sqlCommonValidateUserAndPass(username, password, ip, protocol, p.dbHandle)
  114. }
  115. func (p *SQLiteProvider) validateUserAndTLSCert(username, protocol string, tlsCert *x509.Certificate) (User, error) {
  116. return sqlCommonValidateUserAndTLSCertificate(username, protocol, tlsCert, p.dbHandle)
  117. }
  118. func (p *SQLiteProvider) validateUserAndPubKey(username string, publicKey []byte) (User, string, error) {
  119. return sqlCommonValidateUserAndPubKey(username, publicKey, p.dbHandle)
  120. }
  121. func (p *SQLiteProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
  122. return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
  123. }
  124. func (p *SQLiteProvider) getUsedQuota(username string) (int, int64, error) {
  125. return sqlCommonGetUsedQuota(username, p.dbHandle)
  126. }
  127. func (p *SQLiteProvider) updateLastLogin(username string) error {
  128. return sqlCommonUpdateLastLogin(username, p.dbHandle)
  129. }
  130. func (p *SQLiteProvider) userExists(username string) (User, error) {
  131. return sqlCommonGetUserByUsername(username, p.dbHandle)
  132. }
  133. func (p *SQLiteProvider) addUser(user *User) error {
  134. return sqlCommonAddUser(user, p.dbHandle)
  135. }
  136. func (p *SQLiteProvider) updateUser(user *User) error {
  137. return sqlCommonUpdateUser(user, p.dbHandle)
  138. }
  139. func (p *SQLiteProvider) deleteUser(user *User) error {
  140. return sqlCommonDeleteUser(user, p.dbHandle)
  141. }
  142. func (p *SQLiteProvider) dumpUsers() ([]User, error) {
  143. return sqlCommonDumpUsers(p.dbHandle)
  144. }
  145. func (p *SQLiteProvider) getUsers(limit int, offset int, order string) ([]User, error) {
  146. return sqlCommonGetUsers(limit, offset, order, p.dbHandle)
  147. }
  148. func (p *SQLiteProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) {
  149. return sqlCommonDumpFolders(p.dbHandle)
  150. }
  151. func (p *SQLiteProvider) getFolders(limit, offset int, order string) ([]vfs.BaseVirtualFolder, error) {
  152. return sqlCommonGetFolders(limit, offset, order, p.dbHandle)
  153. }
  154. func (p *SQLiteProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, error) {
  155. ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
  156. defer cancel()
  157. return sqlCommonGetFolderByName(ctx, name, p.dbHandle)
  158. }
  159. func (p *SQLiteProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
  160. return sqlCommonAddFolder(folder, p.dbHandle)
  161. }
  162. func (p *SQLiteProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
  163. return sqlCommonUpdateFolder(folder, p.dbHandle)
  164. }
  165. func (p *SQLiteProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
  166. return sqlCommonDeleteFolder(folder, p.dbHandle)
  167. }
  168. func (p *SQLiteProvider) updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error {
  169. return sqlCommonUpdateFolderQuota(name, filesAdd, sizeAdd, reset, p.dbHandle)
  170. }
  171. func (p *SQLiteProvider) getUsedFolderQuota(name string) (int, int64, error) {
  172. return sqlCommonGetFolderUsedQuota(name, p.dbHandle)
  173. }
  174. func (p *SQLiteProvider) adminExists(username string) (Admin, error) {
  175. return sqlCommonGetAdminByUsername(username, p.dbHandle)
  176. }
  177. func (p *SQLiteProvider) addAdmin(admin *Admin) error {
  178. return sqlCommonAddAdmin(admin, p.dbHandle)
  179. }
  180. func (p *SQLiteProvider) updateAdmin(admin *Admin) error {
  181. return sqlCommonUpdateAdmin(admin, p.dbHandle)
  182. }
  183. func (p *SQLiteProvider) deleteAdmin(admin *Admin) error {
  184. return sqlCommonDeleteAdmin(admin, p.dbHandle)
  185. }
  186. func (p *SQLiteProvider) getAdmins(limit int, offset int, order string) ([]Admin, error) {
  187. return sqlCommonGetAdmins(limit, offset, order, p.dbHandle)
  188. }
  189. func (p *SQLiteProvider) dumpAdmins() ([]Admin, error) {
  190. return sqlCommonDumpAdmins(p.dbHandle)
  191. }
  192. func (p *SQLiteProvider) validateAdminAndPass(username, password, ip string) (Admin, error) {
  193. return sqlCommonValidateAdminAndPass(username, password, ip, p.dbHandle)
  194. }
  195. func (p *SQLiteProvider) close() error {
  196. return p.dbHandle.Close()
  197. }
  198. func (p *SQLiteProvider) reloadConfig() error {
  199. return nil
  200. }
  201. // initializeDatabase creates the initial database structure
  202. func (p *SQLiteProvider) initializeDatabase() error {
  203. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, false)
  204. if err == nil && dbVersion.Version > 0 {
  205. return ErrNoInitRequired
  206. }
  207. initialSQL := strings.ReplaceAll(sqliteInitialSQL, "{{schema_version}}", sqlTableSchemaVersion)
  208. initialSQL = strings.ReplaceAll(initialSQL, "{{admins}}", sqlTableAdmins)
  209. initialSQL = strings.ReplaceAll(initialSQL, "{{folders}}", sqlTableFolders)
  210. initialSQL = strings.ReplaceAll(initialSQL, "{{users}}", sqlTableUsers)
  211. initialSQL = strings.ReplaceAll(initialSQL, "{{folders_mapping}}", sqlTableFoldersMapping)
  212. initialSQL = strings.ReplaceAll(initialSQL, "{{prefix}}", config.SQLTablesPrefix)
  213. return sqlCommonExecSQLAndUpdateDBVersion(p.dbHandle, []string{initialSQL}, 8)
  214. }
  215. func (p *SQLiteProvider) migrateDatabase() error {
  216. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  217. if err != nil {
  218. return err
  219. }
  220. switch version := dbVersion.Version; {
  221. case version == sqlDatabaseVersion:
  222. providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", version)
  223. return ErrNoInitRequired
  224. case version < 8:
  225. err = fmt.Errorf("database version %v is too old, please see the upgrading docs", version)
  226. providerLog(logger.LevelError, "%v", err)
  227. logger.ErrorToConsole("%v", err)
  228. return err
  229. case version == 8:
  230. return updateSQLiteDatabaseFromV8(p.dbHandle)
  231. case version == 9:
  232. return updateSQLiteDatabaseFromV9(p.dbHandle)
  233. default:
  234. if version > sqlDatabaseVersion {
  235. providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
  236. sqlDatabaseVersion)
  237. logger.WarnToConsole("database version %v is newer than the supported one: %v", version,
  238. sqlDatabaseVersion)
  239. return nil
  240. }
  241. return fmt.Errorf("database version not handled: %v", version)
  242. }
  243. }
  244. func (p *SQLiteProvider) revertDatabase(targetVersion int) error {
  245. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  246. if err != nil {
  247. return err
  248. }
  249. if dbVersion.Version == targetVersion {
  250. return errors.New("current version match target version, nothing to do")
  251. }
  252. switch dbVersion.Version {
  253. case 9:
  254. return downgradeSQLiteDatabaseFromV9(p.dbHandle)
  255. case 10:
  256. return downgradeSQLiteDatabaseFromV10(p.dbHandle)
  257. default:
  258. return fmt.Errorf("database version not handled: %v", dbVersion.Version)
  259. }
  260. }
  261. func updateSQLiteDatabaseFromV8(dbHandle *sql.DB) error {
  262. if err := updateSQLiteDatabaseFrom8To9(dbHandle); err != nil {
  263. return err
  264. }
  265. return updateSQLiteDatabaseFromV9(dbHandle)
  266. }
  267. func updateSQLiteDatabaseFromV9(dbHandle *sql.DB) error {
  268. return updateSQLiteDatabaseFrom9To10(dbHandle)
  269. }
  270. func downgradeSQLiteDatabaseFromV9(dbHandle *sql.DB) error {
  271. return downgradeSQLiteDatabaseFrom9To8(dbHandle)
  272. }
  273. func downgradeSQLiteDatabaseFromV10(dbHandle *sql.DB) error {
  274. if err := downgradeSQLiteDatabaseFrom10To9(dbHandle); err != nil {
  275. return err
  276. }
  277. return downgradeSQLiteDatabaseFromV9(dbHandle)
  278. }
  279. func updateSQLiteDatabaseFrom8To9(dbHandle *sql.DB) error {
  280. logger.InfoToConsole("updating database version: 8 -> 9")
  281. providerLog(logger.LevelInfo, "updating database version: 8 -> 9")
  282. sql := strings.ReplaceAll(sqliteV9SQL, "{{users}}", sqlTableUsers)
  283. sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
  284. sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
  285. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 9)
  286. }
  287. func downgradeSQLiteDatabaseFrom9To8(dbHandle *sql.DB) error {
  288. logger.InfoToConsole("downgrading database version: 9 -> 8")
  289. providerLog(logger.LevelInfo, "downgrading database version: 9 -> 8")
  290. if err := setPragmaFK(dbHandle, "OFF"); err != nil {
  291. return err
  292. }
  293. sql := strings.ReplaceAll(sqliteV9DownSQL, "{{users}}", sqlTableUsers)
  294. sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
  295. sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
  296. if err := sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 8); err != nil {
  297. return err
  298. }
  299. return setPragmaFK(dbHandle, "ON")
  300. }
  301. func updateSQLiteDatabaseFrom9To10(dbHandle *sql.DB) error {
  302. return sqlCommonUpdateDatabaseFrom9To10(dbHandle)
  303. }
  304. func downgradeSQLiteDatabaseFrom10To9(dbHandle *sql.DB) error {
  305. return sqlCommonDowngradeDatabaseFrom10To9(dbHandle)
  306. }
  307. func setPragmaFK(dbHandle *sql.DB, value string) error {
  308. ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
  309. defer cancel()
  310. sql := fmt.Sprintf("PRAGMA foreign_keys=%v;", value)
  311. _, err := dbHandle.ExecContext(ctx, sql)
  312. return err
  313. }