mysql.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. // +build !nomysql
  2. package dataprovider
  3. import (
  4. "context"
  5. "database/sql"
  6. "fmt"
  7. "strings"
  8. "time"
  9. // we import go-sql-driver/mysql here to be able to disable MySQL support using a build tag
  10. _ "github.com/go-sql-driver/mysql"
  11. "github.com/drakkan/sftpgo/logger"
  12. "github.com/drakkan/sftpgo/version"
  13. "github.com/drakkan/sftpgo/vfs"
  14. )
  15. const (
  16. mysqlUsersTableSQL = "CREATE TABLE `{{users}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, " +
  17. "`username` varchar(255) NOT NULL UNIQUE, `password` varchar(255) NULL, `public_keys` longtext NULL, " +
  18. "`home_dir` varchar(255) NOT NULL, `uid` integer NOT NULL, `gid` integer NOT NULL, `max_sessions` integer NOT NULL, " +
  19. " `quota_size` bigint NOT NULL, `quota_files` integer NOT NULL, `permissions` longtext NOT NULL, " +
  20. "`used_quota_size` bigint NOT NULL, `used_quota_files` integer NOT NULL, `last_quota_update` bigint NOT NULL, " +
  21. "`upload_bandwidth` integer NOT NULL, `download_bandwidth` integer NOT NULL, `expiration_date` bigint(20) NOT NULL, " +
  22. "`last_login` bigint(20) NOT NULL, `status` int(11) NOT NULL, `filters` longtext DEFAULT NULL, " +
  23. "`filesystem` longtext DEFAULT NULL);"
  24. mysqlSchemaTableSQL = "CREATE TABLE `{{schema_version}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `version` integer NOT NULL);"
  25. mysqlV2SQL = "ALTER TABLE `{{users}}` ADD COLUMN `virtual_folders` longtext NULL;"
  26. mysqlV3SQL = "ALTER TABLE `{{users}}` MODIFY `password` longtext NULL;"
  27. mysqlV4SQL = "CREATE TABLE `{{folders}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `path` varchar(512) NOT NULL UNIQUE," +
  28. "`used_quota_size` bigint NOT NULL, `used_quota_files` integer NOT NULL, `last_quota_update` bigint NOT NULL);" +
  29. "ALTER TABLE `{{users}}` MODIFY `home_dir` varchar(512) NOT NULL;" +
  30. "ALTER TABLE `{{users}}` DROP COLUMN `virtual_folders`;" +
  31. "CREATE TABLE `{{folders_mapping}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `virtual_path` varchar(512) NOT NULL, " +
  32. "`quota_size` bigint NOT NULL, `quota_files` integer NOT NULL, `folder_id` integer NOT NULL, `user_id` integer NOT NULL);" +
  33. "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `unique_mapping` UNIQUE (`user_id`, `folder_id`);" +
  34. "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_folder_id_fk_folders_id` FOREIGN KEY (`folder_id`) REFERENCES `{{folders}}` (`id`) ON DELETE CASCADE;" +
  35. "ALTER TABLE `{{folders_mapping}}` ADD CONSTRAINT `folders_mapping_user_id_fk_users_id` FOREIGN KEY (`user_id`) REFERENCES `{{users}}` (`id`) ON DELETE CASCADE;"
  36. mysqlV6SQL = "ALTER TABLE `{{users}}` ADD COLUMN `additional_info` longtext NULL;"
  37. mysqlV6DownSQL = "ALTER TABLE `{{users}}` DROP COLUMN `additional_info`;"
  38. mysqlV7SQL = "CREATE TABLE `{{admins}}` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `username` varchar(255) NOT NULL UNIQUE, " +
  39. "`password` varchar(255) NOT NULL, `email` varchar(255) NULL, `status` integer NOT NULL, `permissions` longtext NOT NULL, " +
  40. "`filters` longtext NULL, `additional_info` longtext NULL);"
  41. mysqlV7DownSQL = "DROP TABLE `{{admins}}` CASCADE;"
  42. mysqlV8SQL = "ALTER TABLE `{{folders}}` ADD COLUMN `name` varchar(255) NULL;" +
  43. "ALTER TABLE `{{folders}}` MODIFY `path` varchar(512) NULL;" +
  44. "ALTER TABLE `{{folders}}` DROP INDEX `path`;" +
  45. "UPDATE `{{folders}}` f1 SET name = CONCAT('folder',f1.id);" +
  46. "ALTER TABLE `{{folders}}` MODIFY `name` varchar(255) NOT NULL;" +
  47. "ALTER TABLE `{{folders}}` ADD CONSTRAINT `name` UNIQUE (`name`);"
  48. mysqlV8DownSQL = "ALTER TABLE `{{folders}}` DROP COLUMN `name`;" +
  49. "ALTER TABLE `{{folders}}` MODIFY `path` varchar(512) NOT NULL;" +
  50. "ALTER TABLE `{{folders}}` ADD CONSTRAINT `path` UNIQUE (`path`);"
  51. )
  52. // MySQLProvider auth provider for MySQL/MariaDB database
  53. type MySQLProvider struct {
  54. dbHandle *sql.DB
  55. }
  56. func init() {
  57. version.AddFeature("+mysql")
  58. }
  59. func initializeMySQLProvider() error {
  60. var err error
  61. logSender = fmt.Sprintf("dataprovider_%v", MySQLDataProviderName)
  62. dbHandle, err := sql.Open("mysql", getMySQLConnectionString(false))
  63. if err == nil {
  64. providerLog(logger.LevelDebug, "mysql database handle created, connection string: %#v, pool size: %v",
  65. getMySQLConnectionString(true), config.PoolSize)
  66. dbHandle.SetMaxOpenConns(config.PoolSize)
  67. if config.PoolSize > 0 {
  68. dbHandle.SetMaxIdleConns(config.PoolSize)
  69. } else {
  70. dbHandle.SetMaxIdleConns(2)
  71. }
  72. dbHandle.SetConnMaxLifetime(240 * time.Second)
  73. provider = &MySQLProvider{dbHandle: dbHandle}
  74. } else {
  75. providerLog(logger.LevelWarn, "error creating mysql database handler, connection string: %#v, error: %v",
  76. getMySQLConnectionString(true), err)
  77. }
  78. return err
  79. }
  80. func getMySQLConnectionString(redactedPwd bool) string {
  81. var connectionString string
  82. if config.ConnectionString == "" {
  83. password := config.Password
  84. if redactedPwd {
  85. password = "[redacted]"
  86. }
  87. connectionString = fmt.Sprintf("%v:%v@tcp([%v]:%v)/%v?charset=utf8&interpolateParams=true&timeout=10s&tls=%v&writeTimeout=10s&readTimeout=10s",
  88. config.Username, password, config.Host, config.Port, config.Name, getSSLMode())
  89. } else {
  90. connectionString = config.ConnectionString
  91. }
  92. return connectionString
  93. }
  94. func (p *MySQLProvider) checkAvailability() error {
  95. return sqlCommonCheckAvailability(p.dbHandle)
  96. }
  97. func (p *MySQLProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
  98. return sqlCommonValidateUserAndPass(username, password, ip, protocol, p.dbHandle)
  99. }
  100. func (p *MySQLProvider) validateUserAndPubKey(username string, publicKey []byte) (User, string, error) {
  101. return sqlCommonValidateUserAndPubKey(username, publicKey, p.dbHandle)
  102. }
  103. func (p *MySQLProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
  104. return sqlCommonUpdateQuota(username, filesAdd, sizeAdd, reset, p.dbHandle)
  105. }
  106. func (p *MySQLProvider) getUsedQuota(username string) (int, int64, error) {
  107. return sqlCommonGetUsedQuota(username, p.dbHandle)
  108. }
  109. func (p *MySQLProvider) updateLastLogin(username string) error {
  110. return sqlCommonUpdateLastLogin(username, p.dbHandle)
  111. }
  112. func (p *MySQLProvider) userExists(username string) (User, error) {
  113. return sqlCommonGetUserByUsername(username, p.dbHandle)
  114. }
  115. func (p *MySQLProvider) addUser(user *User) error {
  116. return sqlCommonAddUser(user, p.dbHandle)
  117. }
  118. func (p *MySQLProvider) updateUser(user *User) error {
  119. return sqlCommonUpdateUser(user, p.dbHandle)
  120. }
  121. func (p *MySQLProvider) deleteUser(user *User) error {
  122. return sqlCommonDeleteUser(user, p.dbHandle)
  123. }
  124. func (p *MySQLProvider) dumpUsers() ([]User, error) {
  125. return sqlCommonDumpUsers(p.dbHandle)
  126. }
  127. func (p *MySQLProvider) getUsers(limit int, offset int, order string) ([]User, error) {
  128. return sqlCommonGetUsers(limit, offset, order, p.dbHandle)
  129. }
  130. func (p *MySQLProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) {
  131. return sqlCommonDumpFolders(p.dbHandle)
  132. }
  133. func (p *MySQLProvider) getFolders(limit, offset int, order string) ([]vfs.BaseVirtualFolder, error) {
  134. return sqlCommonGetFolders(limit, offset, order, p.dbHandle)
  135. }
  136. func (p *MySQLProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, error) {
  137. ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
  138. defer cancel()
  139. return sqlCommonGetFolderByName(ctx, name, p.dbHandle)
  140. }
  141. func (p *MySQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
  142. return sqlCommonAddFolder(folder, p.dbHandle)
  143. }
  144. func (p *MySQLProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
  145. return sqlCommonUpdateFolder(folder, p.dbHandle)
  146. }
  147. func (p *MySQLProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
  148. return sqlCommonDeleteFolder(folder, p.dbHandle)
  149. }
  150. func (p *MySQLProvider) updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error {
  151. return sqlCommonUpdateFolderQuota(name, filesAdd, sizeAdd, reset, p.dbHandle)
  152. }
  153. func (p *MySQLProvider) getUsedFolderQuota(name string) (int, int64, error) {
  154. return sqlCommonGetFolderUsedQuota(name, p.dbHandle)
  155. }
  156. func (p *MySQLProvider) adminExists(username string) (Admin, error) {
  157. return sqlCommonGetAdminByUsername(username, p.dbHandle)
  158. }
  159. func (p *MySQLProvider) addAdmin(admin *Admin) error {
  160. return sqlCommonAddAdmin(admin, p.dbHandle)
  161. }
  162. func (p *MySQLProvider) updateAdmin(admin *Admin) error {
  163. return sqlCommonUpdateAdmin(admin, p.dbHandle)
  164. }
  165. func (p *MySQLProvider) deleteAdmin(admin *Admin) error {
  166. return sqlCommonDeleteAdmin(admin, p.dbHandle)
  167. }
  168. func (p *MySQLProvider) getAdmins(limit int, offset int, order string) ([]Admin, error) {
  169. return sqlCommonGetAdmins(limit, offset, order, p.dbHandle)
  170. }
  171. func (p *MySQLProvider) dumpAdmins() ([]Admin, error) {
  172. return sqlCommonDumpAdmins(p.dbHandle)
  173. }
  174. func (p *MySQLProvider) validateAdminAndPass(username, password, ip string) (Admin, error) {
  175. return sqlCommonValidateAdminAndPass(username, password, ip, p.dbHandle)
  176. }
  177. func (p *MySQLProvider) close() error {
  178. return p.dbHandle.Close()
  179. }
  180. func (p *MySQLProvider) reloadConfig() error {
  181. return nil
  182. }
  183. // initializeDatabase creates the initial database structure
  184. func (p *MySQLProvider) initializeDatabase() error {
  185. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, false)
  186. if err == nil && dbVersion.Version > 0 {
  187. return ErrNoInitRequired
  188. }
  189. sqlUsers := strings.Replace(mysqlUsersTableSQL, "{{users}}", sqlTableUsers, 1)
  190. ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
  191. defer cancel()
  192. tx, err := p.dbHandle.BeginTx(ctx, nil)
  193. if err != nil {
  194. return err
  195. }
  196. _, err = tx.Exec(sqlUsers)
  197. if err != nil {
  198. return err
  199. }
  200. _, err = tx.Exec(strings.Replace(mysqlSchemaTableSQL, "{{schema_version}}", sqlTableSchemaVersion, 1))
  201. if err != nil {
  202. return err
  203. }
  204. _, err = tx.Exec(strings.Replace(initialDBVersionSQL, "{{schema_version}}", sqlTableSchemaVersion, 1))
  205. if err != nil {
  206. return err
  207. }
  208. return tx.Commit()
  209. }
  210. func (p *MySQLProvider) migrateDatabase() error {
  211. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  212. if err != nil {
  213. return err
  214. }
  215. if dbVersion.Version == sqlDatabaseVersion {
  216. providerLog(logger.LevelDebug, "sql database is up to date, current version: %v", dbVersion.Version)
  217. return ErrNoInitRequired
  218. }
  219. switch dbVersion.Version {
  220. case 1:
  221. return updateMySQLDatabaseFromV1(p.dbHandle)
  222. case 2:
  223. return updateMySQLDatabaseFromV2(p.dbHandle)
  224. case 3:
  225. return updateMySQLDatabaseFromV3(p.dbHandle)
  226. case 4:
  227. return updateMySQLDatabaseFromV4(p.dbHandle)
  228. case 5:
  229. return updateMySQLDatabaseFromV5(p.dbHandle)
  230. case 6:
  231. return updateMySQLDatabaseFromV6(p.dbHandle)
  232. case 7:
  233. return updateMySQLDatabaseFromV7(p.dbHandle)
  234. default:
  235. if dbVersion.Version > sqlDatabaseVersion {
  236. providerLog(logger.LevelWarn, "database version %v is newer than the supported: %v", dbVersion.Version,
  237. sqlDatabaseVersion)
  238. logger.WarnToConsole("database version %v is newer than the supported: %v", dbVersion.Version,
  239. sqlDatabaseVersion)
  240. return nil
  241. }
  242. return fmt.Errorf("Database version not handled: %v", dbVersion.Version)
  243. }
  244. }
  245. //nolint:dupl
  246. func (p *MySQLProvider) revertDatabase(targetVersion int) error {
  247. dbVersion, err := sqlCommonGetDatabaseVersion(p.dbHandle, true)
  248. if err != nil {
  249. return err
  250. }
  251. if dbVersion.Version == targetVersion {
  252. return fmt.Errorf("current version match target version, nothing to do")
  253. }
  254. switch dbVersion.Version {
  255. case 8:
  256. err = downgradeMySQLDatabaseFrom8To7(p.dbHandle)
  257. if err != nil {
  258. return err
  259. }
  260. err = downgradeMySQLDatabaseFrom7To6(p.dbHandle)
  261. if err != nil {
  262. return err
  263. }
  264. err = downgradeMySQLDatabaseFrom6To5(p.dbHandle)
  265. if err != nil {
  266. return err
  267. }
  268. return downgradeMySQLDatabaseFrom5To4(p.dbHandle)
  269. case 7:
  270. err = downgradeMySQLDatabaseFrom7To6(p.dbHandle)
  271. if err != nil {
  272. return err
  273. }
  274. err = downgradeMySQLDatabaseFrom6To5(p.dbHandle)
  275. if err != nil {
  276. return err
  277. }
  278. return downgradeMySQLDatabaseFrom5To4(p.dbHandle)
  279. case 6:
  280. err = downgradeMySQLDatabaseFrom6To5(p.dbHandle)
  281. if err != nil {
  282. return err
  283. }
  284. return downgradeMySQLDatabaseFrom5To4(p.dbHandle)
  285. case 5:
  286. return downgradeMySQLDatabaseFrom5To4(p.dbHandle)
  287. default:
  288. return fmt.Errorf("Database version not handled: %v", dbVersion.Version)
  289. }
  290. }
  291. func updateMySQLDatabaseFromV1(dbHandle *sql.DB) error {
  292. err := updateMySQLDatabaseFrom1To2(dbHandle)
  293. if err != nil {
  294. return err
  295. }
  296. return updateMySQLDatabaseFromV2(dbHandle)
  297. }
  298. func updateMySQLDatabaseFromV2(dbHandle *sql.DB) error {
  299. err := updateMySQLDatabaseFrom2To3(dbHandle)
  300. if err != nil {
  301. return err
  302. }
  303. return updateMySQLDatabaseFromV3(dbHandle)
  304. }
  305. func updateMySQLDatabaseFromV3(dbHandle *sql.DB) error {
  306. err := updateMySQLDatabaseFrom3To4(dbHandle)
  307. if err != nil {
  308. return err
  309. }
  310. return updateMySQLDatabaseFromV4(dbHandle)
  311. }
  312. func updateMySQLDatabaseFromV4(dbHandle *sql.DB) error {
  313. err := updateMySQLDatabaseFrom4To5(dbHandle)
  314. if err != nil {
  315. return err
  316. }
  317. return updateMySQLDatabaseFromV5(dbHandle)
  318. }
  319. func updateMySQLDatabaseFromV5(dbHandle *sql.DB) error {
  320. err := updateMySQLDatabaseFrom5To6(dbHandle)
  321. if err != nil {
  322. return err
  323. }
  324. return updateMySQLDatabaseFromV6(dbHandle)
  325. }
  326. func updateMySQLDatabaseFromV6(dbHandle *sql.DB) error {
  327. err := updateMySQLDatabaseFrom6To7(dbHandle)
  328. if err != nil {
  329. return err
  330. }
  331. return updateMySQLDatabaseFromV7(dbHandle)
  332. }
  333. func updateMySQLDatabaseFromV7(dbHandle *sql.DB) error {
  334. return updateMySQLDatabaseFrom7To8(dbHandle)
  335. }
  336. func updateMySQLDatabaseFrom1To2(dbHandle *sql.DB) error {
  337. logger.InfoToConsole("updating database version: 1 -> 2")
  338. providerLog(logger.LevelInfo, "updating database version: 1 -> 2")
  339. sql := strings.Replace(mysqlV2SQL, "{{users}}", sqlTableUsers, 1)
  340. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 2)
  341. }
  342. func updateMySQLDatabaseFrom2To3(dbHandle *sql.DB) error {
  343. logger.InfoToConsole("updating database version: 2 -> 3")
  344. providerLog(logger.LevelInfo, "updating database version: 2 -> 3")
  345. sql := strings.Replace(mysqlV3SQL, "{{users}}", sqlTableUsers, 1)
  346. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 3)
  347. }
  348. func updateMySQLDatabaseFrom3To4(dbHandle *sql.DB) error {
  349. return sqlCommonUpdateDatabaseFrom3To4(mysqlV4SQL, dbHandle)
  350. }
  351. func updateMySQLDatabaseFrom4To5(dbHandle *sql.DB) error {
  352. return sqlCommonUpdateDatabaseFrom4To5(dbHandle)
  353. }
  354. func updateMySQLDatabaseFrom5To6(dbHandle *sql.DB) error {
  355. logger.InfoToConsole("updating database version: 5 -> 6")
  356. providerLog(logger.LevelInfo, "updating database version: 5 -> 6")
  357. sql := strings.Replace(mysqlV6SQL, "{{users}}", sqlTableUsers, 1)
  358. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 6)
  359. }
  360. func updateMySQLDatabaseFrom6To7(dbHandle *sql.DB) error {
  361. logger.InfoToConsole("updating database version: 6 -> 7")
  362. providerLog(logger.LevelInfo, "updating database version: 6 -> 7")
  363. sql := strings.Replace(mysqlV7SQL, "{{admins}}", sqlTableAdmins, 1)
  364. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 7)
  365. }
  366. func updateMySQLDatabaseFrom7To8(dbHandle *sql.DB) error {
  367. logger.InfoToConsole("updating database version: 7 -> 8")
  368. providerLog(logger.LevelInfo, "updating database version: 7 -> 8")
  369. sql := strings.ReplaceAll(mysqlV8SQL, "{{folders}}", sqlTableFolders)
  370. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 8)
  371. }
  372. func downgradeMySQLDatabaseFrom8To7(dbHandle *sql.DB) error {
  373. logger.InfoToConsole("downgrading database version: 8 -> 7")
  374. providerLog(logger.LevelInfo, "downgrading database version: 8 -> 7")
  375. sql := strings.ReplaceAll(mysqlV8DownSQL, "{{folders}}", sqlTableFolders)
  376. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 7)
  377. }
  378. func downgradeMySQLDatabaseFrom7To6(dbHandle *sql.DB) error {
  379. logger.InfoToConsole("downgrading database version: 7 -> 6")
  380. providerLog(logger.LevelInfo, "downgrading database version: 7 -> 6")
  381. sql := strings.Replace(mysqlV7DownSQL, "{{admins}}", sqlTableAdmins, 1)
  382. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 6)
  383. }
  384. func downgradeMySQLDatabaseFrom6To5(dbHandle *sql.DB) error {
  385. logger.InfoToConsole("downgrading database version: 6 -> 5")
  386. providerLog(logger.LevelInfo, "downgrading database version: 6 -> 5")
  387. sql := strings.Replace(mysqlV6DownSQL, "{{users}}", sqlTableUsers, 1)
  388. return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 5)
  389. }
  390. func downgradeMySQLDatabaseFrom5To4(dbHandle *sql.DB) error {
  391. return sqlCommonDowngradeDatabaseFrom5To4(dbHandle)
  392. }