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 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  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/ergochat/ergo/irc/modes"
  15. "github.com/ergochat/ergo/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 = 20
  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. // #1490: start tracking join times for always-on clients
  871. func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
  872. type joinData struct {
  873. Modes string
  874. JoinTime int64
  875. }
  876. var accounts []string
  877. var data []string
  878. now := time.Now().UnixNano()
  879. prefix := "account.channeltomodes "
  880. tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
  881. if !strings.HasPrefix(key, prefix) {
  882. return false
  883. }
  884. accounts = append(accounts, strings.TrimPrefix(key, prefix))
  885. data = append(data, value)
  886. return true
  887. })
  888. for i, account := range accounts {
  889. var existingMap map[string]string
  890. err := json.Unmarshal([]byte(data[i]), &existingMap)
  891. if err != nil {
  892. return err
  893. }
  894. newMap := make(map[string]joinData)
  895. for channel, modeStr := range existingMap {
  896. newMap[channel] = joinData{
  897. Modes: modeStr,
  898. JoinTime: now,
  899. }
  900. }
  901. serialized, err := json.Marshal(newMap)
  902. if err != nil {
  903. return err
  904. }
  905. tx.Set(prefix+account, string(serialized), nil)
  906. }
  907. return nil
  908. }
  909. func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
  910. for _, change := range allChanges {
  911. if initialVersion == change.InitialVersion {
  912. return change, true
  913. }
  914. }
  915. return
  916. }
  917. var allChanges = []SchemaChange{
  918. {
  919. InitialVersion: 1,
  920. TargetVersion: 2,
  921. Changer: schemaChangeV1toV2,
  922. },
  923. {
  924. InitialVersion: 2,
  925. TargetVersion: 3,
  926. Changer: schemaChangeV2ToV3,
  927. },
  928. {
  929. InitialVersion: 3,
  930. TargetVersion: 4,
  931. Changer: schemaChangeV3ToV4,
  932. },
  933. {
  934. InitialVersion: 4,
  935. TargetVersion: 5,
  936. Changer: schemaChangeV4ToV5,
  937. },
  938. {
  939. InitialVersion: 5,
  940. TargetVersion: 6,
  941. Changer: schemaChangeV5ToV6,
  942. },
  943. {
  944. InitialVersion: 6,
  945. TargetVersion: 7,
  946. Changer: schemaChangeV6ToV7,
  947. },
  948. {
  949. InitialVersion: 7,
  950. TargetVersion: 8,
  951. Changer: schemaChangeV7ToV8,
  952. },
  953. {
  954. InitialVersion: 8,
  955. TargetVersion: 9,
  956. Changer: schemaChangeV8ToV9,
  957. },
  958. {
  959. InitialVersion: 9,
  960. TargetVersion: 10,
  961. Changer: schemaChangeV9ToV10,
  962. },
  963. {
  964. InitialVersion: 10,
  965. TargetVersion: 11,
  966. Changer: schemaChangeV10ToV11,
  967. },
  968. {
  969. InitialVersion: 11,
  970. TargetVersion: 12,
  971. Changer: schemaChangeV11ToV12,
  972. },
  973. {
  974. InitialVersion: 12,
  975. TargetVersion: 13,
  976. Changer: schemaChangeV12ToV13,
  977. },
  978. {
  979. InitialVersion: 13,
  980. TargetVersion: 14,
  981. Changer: schemaChangeV13ToV14,
  982. },
  983. {
  984. InitialVersion: 14,
  985. TargetVersion: 15,
  986. Changer: schemaChangeV14ToV15,
  987. },
  988. {
  989. InitialVersion: 15,
  990. TargetVersion: 16,
  991. Changer: schemaChangeV15ToV16,
  992. },
  993. {
  994. InitialVersion: 16,
  995. TargetVersion: 17,
  996. Changer: schemaChangeV16ToV17,
  997. },
  998. {
  999. InitialVersion: 17,
  1000. TargetVersion: 18,
  1001. Changer: schemaChangeV17ToV18,
  1002. },
  1003. {
  1004. InitialVersion: 18,
  1005. TargetVersion: 19,
  1006. Changer: schemaChangeV18To19,
  1007. },
  1008. {
  1009. InitialVersion: 19,
  1010. TargetVersion: 20,
  1011. Changer: schemaChangeV19To20,
  1012. },
  1013. }