You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

database.go 27KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. // Copyright (c) 2012-2014 Jeremy Latt
  2. // Copyright (c) 2016 Daniel Oaks <daniel@danieloaks.net>
  3. // released under the MIT license
  4. package irc
  5. import (
  6. "encoding/base64"
  7. "encoding/json"
  8. "fmt"
  9. "log"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/oragono/oragono/irc/modes"
  15. "github.com/oragono/oragono/irc/utils"
  16. "github.com/tidwall/buntdb"
  17. )
  18. const (
  19. // 'version' of the database schema
  20. keySchemaVersion = "db.version"
  21. // latest schema of the db
  22. latestDbSchema = 19
  23. keyCloakSecret = "crypto.cloak_secret"
  24. )
  25. type SchemaChanger func(*Config, *buntdb.Tx) error
  26. type SchemaChange struct {
  27. InitialVersion int // the change will take this version
  28. TargetVersion int // and transform it into this version
  29. Changer SchemaChanger
  30. }
  31. func checkDBReadyForInit(path string) error {
  32. _, err := os.Stat(path)
  33. if err == nil {
  34. return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
  35. } else if !os.IsNotExist(err) {
  36. return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
  37. }
  38. return nil
  39. }
  40. // InitDB creates the database, implementing the `oragono initdb` command.
  41. func InitDB(path string) error {
  42. if err := checkDBReadyForInit(path); err != nil {
  43. return err
  44. }
  45. if err := initializeDB(path); err != nil {
  46. return fmt.Errorf("Could not save datastore: %w", err)
  47. }
  48. return nil
  49. }
  50. // internal database initialization code
  51. func initializeDB(path string) error {
  52. store, err := buntdb.Open(path)
  53. if err != nil {
  54. return err
  55. }
  56. defer store.Close()
  57. err = store.Update(func(tx *buntdb.Tx) error {
  58. // set schema version
  59. tx.Set(keySchemaVersion, strconv.Itoa(latestDbSchema), nil)
  60. tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
  61. return nil
  62. })
  63. return err
  64. }
  65. // OpenDatabase returns an existing database, performing a schema version check.
  66. func OpenDatabase(config *Config) (*buntdb.DB, error) {
  67. return openDatabaseInternal(config, config.Datastore.AutoUpgrade)
  68. }
  69. // open the database, giving it at most one chance to auto-upgrade the schema
  70. func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB, err error) {
  71. db, err = buntdb.Open(config.Datastore.Path)
  72. if err != nil {
  73. return
  74. }
  75. defer func() {
  76. if err != nil && db != nil {
  77. db.Close()
  78. db = nil
  79. }
  80. }()
  81. // read the current version string
  82. var version int
  83. err = db.View(func(tx *buntdb.Tx) (err error) {
  84. vStr, err := tx.Get(keySchemaVersion)
  85. if err == nil {
  86. version, err = strconv.Atoi(vStr)
  87. }
  88. return err
  89. })
  90. if err != nil {
  91. return
  92. }
  93. if version == latestDbSchema {
  94. // success
  95. return
  96. }
  97. // XXX quiesce the DB so we can be sure it's safe to make a backup copy
  98. db.Close()
  99. db = nil
  100. if allowAutoupgrade {
  101. err = performAutoUpgrade(version, config)
  102. if err != nil {
  103. return
  104. }
  105. // successful autoupgrade, let's try this again:
  106. return openDatabaseInternal(config, false)
  107. } else {
  108. err = &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  109. return
  110. }
  111. }
  112. func performAutoUpgrade(currentVersion int, config *Config) (err error) {
  113. path := config.Datastore.Path
  114. log.Printf("attempting to auto-upgrade schema from version %d to %d\n", currentVersion, latestDbSchema)
  115. timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
  116. backupPath := fmt.Sprintf("%s.v%d.%s.bak", path, currentVersion, timestamp)
  117. log.Printf("making a backup of current database at %s\n", backupPath)
  118. err = utils.CopyFile(path, backupPath)
  119. if err != nil {
  120. return err
  121. }
  122. err = UpgradeDB(config)
  123. if err != nil {
  124. // database upgrade is a single transaction, so we don't need to restore the backup;
  125. // we can just delete it
  126. os.Remove(backupPath)
  127. }
  128. return err
  129. }
  130. // UpgradeDB upgrades the datastore to the latest schema.
  131. func UpgradeDB(config *Config) (err error) {
  132. // #715: test that the database exists
  133. _, err = os.Stat(config.Datastore.Path)
  134. if err != nil {
  135. return err
  136. }
  137. store, err := buntdb.Open(config.Datastore.Path)
  138. if err != nil {
  139. return err
  140. }
  141. defer store.Close()
  142. var version int
  143. err = store.Update(func(tx *buntdb.Tx) error {
  144. for {
  145. vStr, _ := tx.Get(keySchemaVersion)
  146. version, _ = strconv.Atoi(vStr)
  147. if version == latestDbSchema {
  148. // success!
  149. break
  150. }
  151. change, ok := getSchemaChange(version)
  152. if !ok {
  153. // unable to upgrade to the desired version, roll back
  154. return &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
  155. }
  156. log.Printf("attempting to update schema from version %d\n", version)
  157. err := change.Changer(config, tx)
  158. if err != nil {
  159. return err
  160. }
  161. _, _, err = tx.Set(keySchemaVersion, strconv.Itoa(change.TargetVersion), nil)
  162. if err != nil {
  163. return err
  164. }
  165. log.Printf("successfully updated schema to version %d\n", change.TargetVersion)
  166. }
  167. return nil
  168. })
  169. if err != nil {
  170. log.Printf("database upgrade failed and was rolled back: %v\n", err)
  171. }
  172. return err
  173. }
  174. func LoadCloakSecret(db *buntdb.DB) (result string) {
  175. db.View(func(tx *buntdb.Tx) error {
  176. result, _ = tx.Get(keyCloakSecret)
  177. return nil
  178. })
  179. return
  180. }
  181. func StoreCloakSecret(db *buntdb.DB, secret string) {
  182. db.Update(func(tx *buntdb.Tx) error {
  183. tx.Set(keyCloakSecret, secret, nil)
  184. return nil
  185. })
  186. }
  187. func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
  188. // == version 1 -> 2 ==
  189. // account key changes and account.verified key bugfix.
  190. var keysToRemove []string
  191. newKeys := make(map[string]string)
  192. tx.AscendKeys("account *", func(key, value string) bool {
  193. keysToRemove = append(keysToRemove, key)
  194. splitkey := strings.Split(key, " ")
  195. // work around bug
  196. if splitkey[2] == "exists" {
  197. // manually create new verified key
  198. newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
  199. newKeys[newVerifiedKey] = "1"
  200. } else if splitkey[1] == "%s" {
  201. return true
  202. }
  203. newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
  204. newKeys[newKey] = value
  205. return true
  206. })
  207. for _, key := range keysToRemove {
  208. tx.Delete(key)
  209. }
  210. for key, value := range newKeys {
  211. tx.Set(key, value, nil)
  212. }
  213. return nil
  214. }
  215. // 1. channel founder names should be casefolded
  216. // 2. founder should be explicitly granted the ChannelFounder user mode
  217. // 3. explicitly initialize stored channel modes to the server default values
  218. func schemaChangeV2ToV3(config *Config, tx *buntdb.Tx) error {
  219. var channels []string
  220. prefix := "channel.exists "
  221. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  222. if !strings.HasPrefix(key, prefix) {
  223. return false
  224. }
  225. chname := strings.TrimPrefix(key, prefix)
  226. channels = append(channels, chname)
  227. return true
  228. })
  229. // founder names should be casefolded
  230. // founder should be explicitly granted the ChannelFounder user mode
  231. for _, channel := range channels {
  232. founderKey := "channel.founder " + channel
  233. founder, _ := tx.Get(founderKey)
  234. if founder != "" {
  235. founder, err := CasefoldName(founder)
  236. if err == nil {
  237. tx.Set(founderKey, founder, nil)
  238. accountToUmode := map[string]modes.Mode{
  239. founder: modes.ChannelFounder,
  240. }
  241. atustr, _ := json.Marshal(accountToUmode)
  242. tx.Set("channel.accounttoumode "+channel, string(atustr), nil)
  243. }
  244. }
  245. }
  246. // explicitly store the channel modes
  247. defaultModes := config.Channels.defaultModes
  248. modeStrings := make([]string, len(defaultModes))
  249. for i, mode := range defaultModes {
  250. modeStrings[i] = string(mode)
  251. }
  252. defaultModeString := strings.Join(modeStrings, "")
  253. for _, channel := range channels {
  254. tx.Set("channel.modes "+channel, defaultModeString, nil)
  255. }
  256. return nil
  257. }
  258. // 1. ban info format changed (from `legacyBanInfo` below to `IPBanInfo`)
  259. // 2. dlines against individual IPs are normalized into dlines against the appropriate /128 network
  260. func schemaChangeV3ToV4(config *Config, tx *buntdb.Tx) error {
  261. type ipRestrictTime struct {
  262. Duration time.Duration
  263. Expires time.Time
  264. }
  265. type legacyBanInfo struct {
  266. Reason string `json:"reason"`
  267. OperReason string `json:"oper_reason"`
  268. OperName string `json:"oper_name"`
  269. Time *ipRestrictTime `json:"time"`
  270. }
  271. now := time.Now()
  272. legacyToNewInfo := func(old legacyBanInfo) (new_ IPBanInfo) {
  273. new_.Reason = old.Reason
  274. new_.OperReason = old.OperReason
  275. new_.OperName = old.OperName
  276. if old.Time == nil {
  277. new_.TimeCreated = now
  278. new_.Duration = 0
  279. } else {
  280. new_.TimeCreated = old.Time.Expires.Add(-1 * old.Time.Duration)
  281. new_.Duration = old.Time.Duration
  282. }
  283. return
  284. }
  285. var keysToDelete []string
  286. prefix := "bans.dline "
  287. dlines := make(map[string]IPBanInfo)
  288. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  289. if !strings.HasPrefix(key, prefix) {
  290. return false
  291. }
  292. keysToDelete = append(keysToDelete, key)
  293. var lbinfo legacyBanInfo
  294. id := strings.TrimPrefix(key, prefix)
  295. err := json.Unmarshal([]byte(value), &lbinfo)
  296. if err != nil {
  297. log.Printf("error unmarshaling legacy dline: %v\n", err)
  298. return true
  299. }
  300. // legacy keys can be either an IP or a CIDR
  301. hostNet, err := utils.NormalizedNetFromString(id)
  302. if err != nil {
  303. log.Printf("error unmarshaling legacy dline network: %v\n", err)
  304. return true
  305. }
  306. dlines[utils.NetToNormalizedString(hostNet)] = legacyToNewInfo(lbinfo)
  307. return true
  308. })
  309. setOptions := func(info IPBanInfo) *buntdb.SetOptions {
  310. if info.Duration == 0 {
  311. return nil
  312. }
  313. ttl := info.TimeCreated.Add(info.Duration).Sub(now)
  314. return &buntdb.SetOptions{Expires: true, TTL: ttl}
  315. }
  316. // store the new dlines
  317. for id, info := range dlines {
  318. b, err := json.Marshal(info)
  319. if err != nil {
  320. log.Printf("error marshaling migrated dline: %v\n", err)
  321. continue
  322. }
  323. tx.Set(fmt.Sprintf("bans.dlinev2 %s", id), string(b), setOptions(info))
  324. }
  325. // same operations against klines
  326. prefix = "bans.kline "
  327. klines := make(map[string]IPBanInfo)
  328. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  329. if !strings.HasPrefix(key, prefix) {
  330. return false
  331. }
  332. keysToDelete = append(keysToDelete, key)
  333. mask := strings.TrimPrefix(key, prefix)
  334. var lbinfo legacyBanInfo
  335. err := json.Unmarshal([]byte(value), &lbinfo)
  336. if err != nil {
  337. log.Printf("error unmarshaling legacy kline: %v\n", err)
  338. return true
  339. }
  340. klines[mask] = legacyToNewInfo(lbinfo)
  341. return true
  342. })
  343. for mask, info := range klines {
  344. b, err := json.Marshal(info)
  345. if err != nil {
  346. log.Printf("error marshaling migrated kline: %v\n", err)
  347. continue
  348. }
  349. tx.Set(fmt.Sprintf("bans.klinev2 %s", mask), string(b), setOptions(info))
  350. }
  351. // clean up all the old entries
  352. for _, key := range keysToDelete {
  353. tx.Delete(key)
  354. }
  355. return nil
  356. }
  357. // create new key tracking channels that belong to an account
  358. func schemaChangeV4ToV5(config *Config, tx *buntdb.Tx) error {
  359. founderToChannels := make(map[string][]string)
  360. prefix := "channel.founder "
  361. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  362. if !strings.HasPrefix(key, prefix) {
  363. return false
  364. }
  365. channel := strings.TrimPrefix(key, prefix)
  366. founderToChannels[value] = append(founderToChannels[value], channel)
  367. return true
  368. })
  369. for founder, channels := range founderToChannels {
  370. tx.Set(fmt.Sprintf("account.channels %s", founder), strings.Join(channels, ","), nil)
  371. }
  372. return nil
  373. }
  374. // custom nick enforcement was a separate db key, now it's part of settings
  375. func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
  376. accountToEnforcement := make(map[string]NickEnforcementMethod)
  377. prefix := "account.customenforcement "
  378. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  379. if !strings.HasPrefix(key, prefix) {
  380. return false
  381. }
  382. account := strings.TrimPrefix(key, prefix)
  383. method, err := nickReservationFromString(value)
  384. if err == nil {
  385. accountToEnforcement[account] = method
  386. } else {
  387. log.Printf("skipping corrupt custom enforcement value for %s\n", account)
  388. }
  389. return true
  390. })
  391. for account, method := range accountToEnforcement {
  392. var settings AccountSettings
  393. settings.NickEnforcement = method
  394. text, err := json.Marshal(settings)
  395. if err != nil {
  396. return err
  397. }
  398. tx.Delete(prefix + account)
  399. tx.Set(fmt.Sprintf("account.settings %s", account), string(text), nil)
  400. }
  401. return nil
  402. }
  403. type maskInfoV7 struct {
  404. TimeCreated time.Time
  405. CreatorNickmask string
  406. CreatorAccount string
  407. }
  408. func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
  409. now := time.Now().UTC()
  410. var channels []string
  411. prefix := "channel.exists "
  412. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  413. if !strings.HasPrefix(key, prefix) {
  414. return false
  415. }
  416. channels = append(channels, strings.TrimPrefix(key, prefix))
  417. return true
  418. })
  419. converter := func(key string) {
  420. oldRawValue, err := tx.Get(key)
  421. if err != nil {
  422. return
  423. }
  424. var masks []string
  425. err = json.Unmarshal([]byte(oldRawValue), &masks)
  426. if err != nil {
  427. return
  428. }
  429. newCookedValue := make(map[string]maskInfoV7)
  430. for _, mask := range masks {
  431. normalizedMask, err := CanonicalizeMaskWildcard(mask)
  432. if err != nil {
  433. continue
  434. }
  435. newCookedValue[normalizedMask] = maskInfoV7{
  436. TimeCreated: now,
  437. CreatorNickmask: "*",
  438. CreatorAccount: "*",
  439. }
  440. }
  441. newRawValue, err := json.Marshal(newCookedValue)
  442. if err != nil {
  443. return
  444. }
  445. tx.Set(key, string(newRawValue), nil)
  446. }
  447. prefixes := []string{
  448. "channel.banlist %s",
  449. "channel.exceptlist %s",
  450. "channel.invitelist %s",
  451. }
  452. for _, channel := range channels {
  453. for _, prefix := range prefixes {
  454. converter(fmt.Sprintf(prefix, channel))
  455. }
  456. }
  457. return nil
  458. }
  459. type accountSettingsLegacyV7 struct {
  460. AutoreplayLines *int
  461. NickEnforcement NickEnforcementMethod
  462. AllowBouncer MulticlientAllowedSetting
  463. AutoreplayJoins bool
  464. }
  465. type accountSettingsLegacyV8 struct {
  466. AutoreplayLines *int
  467. NickEnforcement NickEnforcementMethod
  468. AllowBouncer MulticlientAllowedSetting
  469. ReplayJoins ReplayJoinsSetting
  470. }
  471. // #616: change autoreplay-joins to replay-joins
  472. func schemaChangeV7ToV8(config *Config, tx *buntdb.Tx) error {
  473. prefix := "account.settings "
  474. var accounts, blobs []string
  475. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  476. var legacy accountSettingsLegacyV7
  477. var current accountSettingsLegacyV8
  478. if !strings.HasPrefix(key, prefix) {
  479. return false
  480. }
  481. account := strings.TrimPrefix(key, prefix)
  482. err := json.Unmarshal([]byte(value), &legacy)
  483. if err != nil {
  484. log.Printf("corrupt record for %s: %v\n", account, err)
  485. return true
  486. }
  487. current.AutoreplayLines = legacy.AutoreplayLines
  488. current.NickEnforcement = legacy.NickEnforcement
  489. current.AllowBouncer = legacy.AllowBouncer
  490. if legacy.AutoreplayJoins {
  491. current.ReplayJoins = ReplayJoinsAlways
  492. } else {
  493. current.ReplayJoins = ReplayJoinsCommandsOnly
  494. }
  495. blob, err := json.Marshal(current)
  496. if err != nil {
  497. log.Printf("could not marshal record for %s: %v\n", account, err)
  498. return true
  499. }
  500. accounts = append(accounts, account)
  501. blobs = append(blobs, string(blob))
  502. return true
  503. })
  504. for i, account := range accounts {
  505. tx.Set(prefix+account, blobs[i], nil)
  506. }
  507. return nil
  508. }
  509. type accountCredsLegacyV8 struct {
  510. Version uint
  511. PassphraseSalt []byte // legacy field, not used by v1 and later
  512. PassphraseHash []byte
  513. Certificate string
  514. }
  515. type accountCredsLegacyV9 struct {
  516. Version uint
  517. PassphraseSalt []byte // legacy field, not used by v1 and later
  518. PassphraseHash []byte
  519. Certfps []string
  520. }
  521. // #530: support multiple client certificate fingerprints
  522. func schemaChangeV8ToV9(config *Config, tx *buntdb.Tx) error {
  523. prefix := "account.credentials "
  524. var accounts, blobs []string
  525. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  526. var legacy accountCredsLegacyV8
  527. var current accountCredsLegacyV9
  528. if !strings.HasPrefix(key, prefix) {
  529. return false
  530. }
  531. account := strings.TrimPrefix(key, prefix)
  532. err := json.Unmarshal([]byte(value), &legacy)
  533. if err != nil {
  534. log.Printf("corrupt record for %s: %v\n", account, err)
  535. return true
  536. }
  537. current.Version = legacy.Version
  538. current.PassphraseSalt = legacy.PassphraseSalt // ugh can't get rid of this
  539. current.PassphraseHash = legacy.PassphraseHash
  540. if legacy.Certificate != "" {
  541. current.Certfps = []string{legacy.Certificate}
  542. }
  543. blob, err := json.Marshal(current)
  544. if err != nil {
  545. log.Printf("could not marshal record for %s: %v\n", account, err)
  546. return true
  547. }
  548. accounts = append(accounts, account)
  549. blobs = append(blobs, string(blob))
  550. return true
  551. })
  552. for i, account := range accounts {
  553. tx.Set(prefix+account, blobs[i], nil)
  554. }
  555. return nil
  556. }
  557. // #836: account registration time at nanosecond resolution
  558. // (mostly to simplify testing)
  559. func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
  560. prefix := "account.registered.time "
  561. var accounts, times []string
  562. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  563. if !strings.HasPrefix(key, prefix) {
  564. return false
  565. }
  566. account := strings.TrimPrefix(key, prefix)
  567. accounts = append(accounts, account)
  568. times = append(times, value)
  569. return true
  570. })
  571. for i, account := range accounts {
  572. time, err := strconv.ParseInt(times[i], 10, 64)
  573. if err != nil {
  574. log.Printf("corrupt registration time entry for %s: %v\n", account, err)
  575. continue
  576. }
  577. time = time * 1000000000
  578. tx.Set(prefix+account, strconv.FormatInt(time, 10), nil)
  579. }
  580. return nil
  581. }
  582. // #952: move the cloak secret into the database,
  583. // generate a new one if necessary
  584. func schemaChangeV10ToV11(config *Config, tx *buntdb.Tx) error {
  585. cloakSecret := config.Server.Cloaks.LegacySecretValue
  586. if cloakSecret == "" || cloakSecret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
  587. cloakSecret = utils.GenerateSecretKey()
  588. }
  589. _, _, err := tx.Set(keyCloakSecret, cloakSecret, nil)
  590. return err
  591. }
  592. // #1027: NickEnforcementTimeout (2) was removed,
  593. // NickEnforcementStrict was 3 and is now 2
  594. func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
  595. prefix := "account.settings "
  596. var accounts, rawSettings []string
  597. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  598. if !strings.HasPrefix(key, prefix) {
  599. return false
  600. }
  601. account := strings.TrimPrefix(key, prefix)
  602. accounts = append(accounts, account)
  603. rawSettings = append(rawSettings, value)
  604. return true
  605. })
  606. for i, account := range accounts {
  607. var settings AccountSettings
  608. err := json.Unmarshal([]byte(rawSettings[i]), &settings)
  609. if err != nil {
  610. log.Printf("corrupt account settings entry for %s: %v\n", account, err)
  611. continue
  612. }
  613. // upgrade NickEnforcementTimeout (which was 2) to NickEnforcementStrict (currently 2),
  614. // fix up the old value of NickEnforcementStrict (3) to the current value (2)
  615. if int(settings.NickEnforcement) == 3 {
  616. settings.NickEnforcement = NickEnforcementMethod(2)
  617. text, err := json.Marshal(settings)
  618. if err != nil {
  619. return err
  620. }
  621. tx.Set(prefix+account, string(text), nil)
  622. }
  623. }
  624. return nil
  625. }
  626. type accountCredsLegacyV13 struct {
  627. Version CredentialsVersion
  628. PassphraseHash []byte
  629. Certfps []string
  630. }
  631. // see #212 / #284. this packs the legacy salts into a single passphrase hash,
  632. // allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
  633. func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
  634. salt, err := tx.Get("crypto.salt")
  635. if err != nil {
  636. return nil // no change required
  637. }
  638. tx.Delete("crypto.salt")
  639. rawSalt, err := base64.StdEncoding.DecodeString(salt)
  640. if err != nil {
  641. return nil // just throw away the creds at this point
  642. }
  643. prefix := "account.credentials "
  644. var accounts []string
  645. var credentials []accountCredsLegacyV13
  646. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  647. if !strings.HasPrefix(key, prefix) {
  648. return false
  649. }
  650. account := strings.TrimPrefix(key, prefix)
  651. var credsOld accountCredsLegacyV9
  652. err = json.Unmarshal([]byte(value), &credsOld)
  653. if err != nil {
  654. return true
  655. }
  656. // skip if these aren't legacy creds!
  657. if credsOld.Version != 0 {
  658. return true
  659. }
  660. var credsNew accountCredsLegacyV13
  661. credsNew.Version = 0 // mark hash for migration
  662. credsNew.Certfps = credsOld.Certfps
  663. credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
  664. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
  665. credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
  666. accounts = append(accounts, account)
  667. credentials = append(credentials, credsNew)
  668. return true
  669. })
  670. for i, account := range accounts {
  671. bytesOut, err := json.Marshal(credentials[i])
  672. if err != nil {
  673. return err
  674. }
  675. _, _, err = tx.Set(prefix+account, string(bytesOut), nil)
  676. if err != nil {
  677. return err
  678. }
  679. }
  680. return nil
  681. }
  682. // channel registration time and topic set time at nanosecond resolution
  683. func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
  684. prefix := "channel.registered.time "
  685. var channels, times []string
  686. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  687. if !strings.HasPrefix(key, prefix) {
  688. return false
  689. }
  690. channel := strings.TrimPrefix(key, prefix)
  691. channels = append(channels, channel)
  692. times = append(times, value)
  693. return true
  694. })
  695. billion := int64(time.Second)
  696. for i, channel := range channels {
  697. regTime, err := strconv.ParseInt(times[i], 10, 64)
  698. if err != nil {
  699. log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
  700. continue
  701. }
  702. regTime = regTime * billion
  703. tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
  704. topicTimeKey := "channel.topic.settime " + channel
  705. topicSetAt, err := tx.Get(topicTimeKey)
  706. if err == nil {
  707. if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
  708. tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
  709. }
  710. }
  711. }
  712. return nil
  713. }
  714. // #1327: delete any invalid klines
  715. func schemaChangeV14ToV15(config *Config, tx *buntdb.Tx) error {
  716. prefix := "bans.klinev2 "
  717. var keys []string
  718. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  719. if !strings.HasPrefix(key, prefix) {
  720. return false
  721. }
  722. if key != strings.TrimSpace(key) {
  723. keys = append(keys, key)
  724. }
  725. return true
  726. })
  727. // don't bother trying to fix these up
  728. for _, key := range keys {
  729. tx.Delete(key)
  730. }
  731. return nil
  732. }
  733. // #1330: delete any stale realname records
  734. func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
  735. prefix := "account.realname "
  736. verifiedPrefix := "account.verified "
  737. var keys []string
  738. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  739. if !strings.HasPrefix(key, prefix) {
  740. return false
  741. }
  742. acct := strings.TrimPrefix(key, prefix)
  743. verifiedKey := verifiedPrefix + acct
  744. _, verifiedErr := tx.Get(verifiedKey)
  745. if verifiedErr != nil {
  746. keys = append(keys, key)
  747. }
  748. return true
  749. })
  750. for _, key := range keys {
  751. tx.Delete(key)
  752. }
  753. return nil
  754. }
  755. // #1346: remove vhost request queue
  756. func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
  757. prefix := "vhostQueue "
  758. var keys []string
  759. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  760. if !strings.HasPrefix(key, prefix) {
  761. return false
  762. }
  763. keys = append(keys, key)
  764. return true
  765. })
  766. for _, key := range keys {
  767. tx.Delete(key)
  768. }
  769. return nil
  770. }
  771. // #1274: we used to suspend accounts by deleting their "verified" key,
  772. // now we save some metadata under a new key
  773. func schemaChangeV17ToV18(config *Config, tx *buntdb.Tx) error {
  774. now := time.Now().UTC()
  775. exists := "account.exists "
  776. suspended := "account.suspended "
  777. verif := "account.verified "
  778. verifCode := "account.verificationcode "
  779. var accounts []string
  780. tx.AscendGreaterOrEqual("", exists, func(key, value string) bool {
  781. if !strings.HasPrefix(key, exists) {
  782. return false
  783. }
  784. account := strings.TrimPrefix(key, exists)
  785. _, verifiedErr := tx.Get(verif + account)
  786. _, verifCodeErr := tx.Get(verifCode + account)
  787. if verifiedErr != nil && verifCodeErr != nil {
  788. // verified key not present, but there's no code either,
  789. // this is a suspension
  790. accounts = append(accounts, account)
  791. }
  792. return true
  793. })
  794. type accountSuspensionV18 struct {
  795. TimeCreated time.Time
  796. Duration time.Duration
  797. OperName string
  798. Reason string
  799. }
  800. for _, account := range accounts {
  801. var sus accountSuspensionV18
  802. sus.TimeCreated = now
  803. sus.OperName = "*"
  804. sus.Reason = "[unknown]"
  805. susBytes, err := json.Marshal(sus)
  806. if err != nil {
  807. return err
  808. }
  809. tx.Set(suspended+account, string(susBytes), nil)
  810. }
  811. return nil
  812. }
  813. // #1345: persist the channel-user modes of always-on clients
  814. func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
  815. channelToAmodesCache := make(map[string]map[string]modes.Mode)
  816. joinedto := "account.joinedto "
  817. var accounts []string
  818. var channels [][]string
  819. tx.AscendGreaterOrEqual("", joinedto, func(key, value string) bool {
  820. if !strings.HasPrefix(key, joinedto) {
  821. return false
  822. }
  823. accounts = append(accounts, strings.TrimPrefix(key, joinedto))
  824. var ch []string
  825. if value != "" {
  826. ch = strings.Split(value, ",")
  827. }
  828. channels = append(channels, ch)
  829. return true
  830. })
  831. for i := 0; i < len(accounts); i++ {
  832. account := accounts[i]
  833. channels := channels[i]
  834. tx.Delete(joinedto + account)
  835. newValue := make(map[string]string, len(channels))
  836. for _, channel := range channels {
  837. chcfname, err := CasefoldChannel(channel)
  838. if err != nil {
  839. continue
  840. }
  841. // get amodes from the channelToAmodesCache, fill if necessary
  842. amodes, ok := channelToAmodesCache[chcfname]
  843. if !ok {
  844. amodeStr, _ := tx.Get("channel.accounttoumode " + chcfname)
  845. if amodeStr != "" {
  846. jErr := json.Unmarshal([]byte(amodeStr), &amodes)
  847. if jErr != nil {
  848. log.Printf("error retrieving amodes for %s: %v\n", channel, jErr)
  849. amodes = nil
  850. }
  851. }
  852. // setting/using the nil value here is ok
  853. channelToAmodesCache[chcfname] = amodes
  854. }
  855. if mode, ok := amodes[account]; ok {
  856. newValue[channel] = string(mode)
  857. } else {
  858. newValue[channel] = ""
  859. }
  860. }
  861. newValueBytes, jErr := json.Marshal(newValue)
  862. if jErr != nil {
  863. log.Printf("couldn't serialize new mode values for v19: %v\n", jErr)
  864. continue
  865. }
  866. tx.Set("account.channeltomodes "+account, string(newValueBytes), nil)
  867. }
  868. return nil
  869. }
  870. func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
  871. for _, change := range allChanges {
  872. if initialVersion == change.InitialVersion {
  873. return change, true
  874. }
  875. }
  876. return
  877. }
  878. var allChanges = []SchemaChange{
  879. {
  880. InitialVersion: 1,
  881. TargetVersion: 2,
  882. Changer: schemaChangeV1toV2,
  883. },
  884. {
  885. InitialVersion: 2,
  886. TargetVersion: 3,
  887. Changer: schemaChangeV2ToV3,
  888. },
  889. {
  890. InitialVersion: 3,
  891. TargetVersion: 4,
  892. Changer: schemaChangeV3ToV4,
  893. },
  894. {
  895. InitialVersion: 4,
  896. TargetVersion: 5,
  897. Changer: schemaChangeV4ToV5,
  898. },
  899. {
  900. InitialVersion: 5,
  901. TargetVersion: 6,
  902. Changer: schemaChangeV5ToV6,
  903. },
  904. {
  905. InitialVersion: 6,
  906. TargetVersion: 7,
  907. Changer: schemaChangeV6ToV7,
  908. },
  909. {
  910. InitialVersion: 7,
  911. TargetVersion: 8,
  912. Changer: schemaChangeV7ToV8,
  913. },
  914. {
  915. InitialVersion: 8,
  916. TargetVersion: 9,
  917. Changer: schemaChangeV8ToV9,
  918. },
  919. {
  920. InitialVersion: 9,
  921. TargetVersion: 10,
  922. Changer: schemaChangeV9ToV10,
  923. },
  924. {
  925. InitialVersion: 10,
  926. TargetVersion: 11,
  927. Changer: schemaChangeV10ToV11,
  928. },
  929. {
  930. InitialVersion: 11,
  931. TargetVersion: 12,
  932. Changer: schemaChangeV11ToV12,
  933. },
  934. {
  935. InitialVersion: 12,
  936. TargetVersion: 13,
  937. Changer: schemaChangeV12ToV13,
  938. },
  939. {
  940. InitialVersion: 13,
  941. TargetVersion: 14,
  942. Changer: schemaChangeV13ToV14,
  943. },
  944. {
  945. InitialVersion: 14,
  946. TargetVersion: 15,
  947. Changer: schemaChangeV14ToV15,
  948. },
  949. {
  950. InitialVersion: 15,
  951. TargetVersion: 16,
  952. Changer: schemaChangeV15ToV16,
  953. },
  954. {
  955. InitialVersion: 16,
  956. TargetVersion: 17,
  957. Changer: schemaChangeV16ToV17,
  958. },
  959. {
  960. InitialVersion: 17,
  961. TargetVersion: 18,
  962. Changer: schemaChangeV17ToV18,
  963. },
  964. {
  965. InitialVersion: 18,
  966. TargetVersion: 19,
  967. Changer: schemaChangeV18To19,
  968. },
  969. }