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.

accounts.go 37KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "net/smtp"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "sync/atomic"
  13. "time"
  14. "unicode"
  15. "github.com/oragono/oragono/irc/caps"
  16. "github.com/oragono/oragono/irc/passwd"
  17. "github.com/oragono/oragono/irc/utils"
  18. "github.com/tidwall/buntdb"
  19. )
  20. const (
  21. keyAccountExists = "account.exists %s"
  22. keyAccountVerified = "account.verified %s"
  23. keyAccountCallback = "account.callback %s"
  24. keyAccountVerificationCode = "account.verificationcode %s"
  25. keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
  26. keyAccountRegTime = "account.registered.time %s"
  27. keyAccountCredentials = "account.credentials %s"
  28. keyAccountAdditionalNicks = "account.additionalnicks %s"
  29. keyAccountEnforcement = "account.customenforcement %s"
  30. keyAccountVHost = "account.vhost %s"
  31. keyCertToAccount = "account.creds.certfp %s"
  32. keyVHostQueueAcctToId = "vhostQueue %s"
  33. vhostRequestIdx = "vhostQueue"
  34. )
  35. // everything about accounts is persistent; therefore, the database is the authoritative
  36. // source of truth for all account information. anything on the heap is just a cache
  37. type AccountManager struct {
  38. // XXX these are up here so they can be aligned to a 64-bit boundary, please forgive me
  39. // autoincrementing ID for vhost requests:
  40. vhostRequestID uint64
  41. vhostRequestPendingCount uint64
  42. sync.RWMutex // tier 2
  43. serialCacheUpdateMutex sync.Mutex // tier 3
  44. vHostUpdateMutex sync.Mutex // tier 3
  45. server *Server
  46. // track clients logged in to accounts
  47. accountToClients map[string][]*Client
  48. nickToAccount map[string]string
  49. skeletonToAccount map[string]string
  50. accountToMethod map[string]NickReservationMethod
  51. }
  52. func NewAccountManager(server *Server) *AccountManager {
  53. am := AccountManager{
  54. accountToClients: make(map[string][]*Client),
  55. nickToAccount: make(map[string]string),
  56. skeletonToAccount: make(map[string]string),
  57. accountToMethod: make(map[string]NickReservationMethod),
  58. server: server,
  59. }
  60. am.buildNickToAccountIndex()
  61. am.initVHostRequestQueue()
  62. return &am
  63. }
  64. func (am *AccountManager) buildNickToAccountIndex() {
  65. if !am.server.AccountConfig().NickReservation.Enabled {
  66. return
  67. }
  68. nickToAccount := make(map[string]string)
  69. skeletonToAccount := make(map[string]string)
  70. accountToMethod := make(map[string]NickReservationMethod)
  71. existsPrefix := fmt.Sprintf(keyAccountExists, "")
  72. am.serialCacheUpdateMutex.Lock()
  73. defer am.serialCacheUpdateMutex.Unlock()
  74. err := am.server.store.View(func(tx *buntdb.Tx) error {
  75. err := tx.AscendGreaterOrEqual("", existsPrefix, func(key, value string) bool {
  76. if !strings.HasPrefix(key, existsPrefix) {
  77. return false
  78. }
  79. account := strings.TrimPrefix(key, existsPrefix)
  80. if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, account)); err == nil {
  81. nickToAccount[account] = account
  82. accountName, err := tx.Get(fmt.Sprintf(keyAccountName, account))
  83. if err != nil {
  84. am.server.logger.Error("internal", "missing account name for", account)
  85. } else {
  86. skeleton, _ := Skeleton(accountName)
  87. skeletonToAccount[skeleton] = account
  88. }
  89. }
  90. if rawNicks, err := tx.Get(fmt.Sprintf(keyAccountAdditionalNicks, account)); err == nil {
  91. additionalNicks := unmarshalReservedNicks(rawNicks)
  92. for _, nick := range additionalNicks {
  93. cfnick, _ := CasefoldName(nick)
  94. nickToAccount[cfnick] = account
  95. skeleton, _ := Skeleton(nick)
  96. skeletonToAccount[skeleton] = account
  97. }
  98. }
  99. if methodStr, err := tx.Get(fmt.Sprintf(keyAccountEnforcement, account)); err == nil {
  100. method, err := nickReservationFromString(methodStr)
  101. if err == nil {
  102. accountToMethod[account] = method
  103. }
  104. }
  105. return true
  106. })
  107. return err
  108. })
  109. if err != nil {
  110. am.server.logger.Error("internal", "couldn't read reserved nicks", err.Error())
  111. } else {
  112. am.Lock()
  113. am.nickToAccount = nickToAccount
  114. am.skeletonToAccount = skeletonToAccount
  115. am.accountToMethod = accountToMethod
  116. am.Unlock()
  117. }
  118. }
  119. func (am *AccountManager) initVHostRequestQueue() {
  120. if !am.server.AccountConfig().VHosts.Enabled {
  121. return
  122. }
  123. am.vHostUpdateMutex.Lock()
  124. defer am.vHostUpdateMutex.Unlock()
  125. // the db maps the account name to the autoincrementing integer ID of its request
  126. // create an numerically ordered index on ID, so we can list the oldest requests
  127. // finally, collect the integer id of the newest request and the total request count
  128. var total uint64
  129. var lastIDStr string
  130. err := am.server.store.Update(func(tx *buntdb.Tx) error {
  131. err := tx.CreateIndex(vhostRequestIdx, fmt.Sprintf(keyVHostQueueAcctToId, "*"), buntdb.IndexInt)
  132. if err != nil {
  133. return err
  134. }
  135. return tx.Descend(vhostRequestIdx, func(key, value string) bool {
  136. if lastIDStr == "" {
  137. lastIDStr = value
  138. }
  139. total++
  140. return true
  141. })
  142. })
  143. if err != nil {
  144. am.server.logger.Error("internal", "could not create vhost queue index", err.Error())
  145. }
  146. lastID, _ := strconv.ParseUint(lastIDStr, 10, 64)
  147. am.server.logger.Debug("services", fmt.Sprintf("vhost queue length is %d, autoincrementing id is %d", total, lastID))
  148. atomic.StoreUint64(&am.vhostRequestID, lastID)
  149. atomic.StoreUint64(&am.vhostRequestPendingCount, total)
  150. }
  151. func (am *AccountManager) NickToAccount(nick string) string {
  152. cfnick, err := CasefoldName(nick)
  153. if err != nil {
  154. return ""
  155. }
  156. am.RLock()
  157. defer am.RUnlock()
  158. return am.nickToAccount[cfnick]
  159. }
  160. // Given a nick, looks up the account that owns it and the method (none/timeout/strict)
  161. // used to enforce ownership.
  162. func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickReservationMethod) {
  163. config := am.server.Config()
  164. if !config.Accounts.NickReservation.Enabled {
  165. return "", NickReservationNone
  166. }
  167. am.RLock()
  168. defer am.RUnlock()
  169. // given an account, combine stored enforcement method with the config settings
  170. // to compute the actual enforcement method
  171. finalEnforcementMethod := func(account_ string) (result NickReservationMethod) {
  172. result = am.accountToMethod[account_]
  173. // if they don't have a custom setting, or customization is disabled, use the default
  174. if result == NickReservationOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
  175. result = config.Accounts.NickReservation.Method
  176. }
  177. if result == NickReservationOptional {
  178. // enforcement was explicitly enabled neither in the config or by the user
  179. result = NickReservationNone
  180. }
  181. return
  182. }
  183. nickAccount := am.nickToAccount[cfnick]
  184. skelAccount := am.skeletonToAccount[skeleton]
  185. if nickAccount == "" && skelAccount == "" {
  186. return "", NickReservationNone
  187. } else if nickAccount != "" && (skelAccount == nickAccount || skelAccount == "") {
  188. return nickAccount, finalEnforcementMethod(nickAccount)
  189. } else if skelAccount != "" && nickAccount == "" {
  190. return skelAccount, finalEnforcementMethod(skelAccount)
  191. } else {
  192. // nickAccount != skelAccount and both are nonempty:
  193. // two people have competing claims on (this casefolding of) this nick!
  194. nickMethod := finalEnforcementMethod(nickAccount)
  195. skelMethod := finalEnforcementMethod(skelAccount)
  196. switch {
  197. case nickMethod == NickReservationNone && skelMethod == NickReservationNone:
  198. return nickAccount, NickReservationNone
  199. case skelMethod == NickReservationNone:
  200. return nickAccount, nickMethod
  201. case nickMethod == NickReservationNone:
  202. return skelAccount, skelMethod
  203. default:
  204. // nobody can use this nick
  205. return "!", NickReservationStrict
  206. }
  207. }
  208. }
  209. // Looks up the enforcement method stored in the database for an account
  210. // (typically you want EnforcementStatus instead, which respects the config)
  211. func (am *AccountManager) getStoredEnforcementStatus(account string) string {
  212. am.RLock()
  213. defer am.RUnlock()
  214. return nickReservationToString(am.accountToMethod[account])
  215. }
  216. // Sets a custom enforcement method for an account and stores it in the database.
  217. func (am *AccountManager) SetEnforcementStatus(account string, method NickReservationMethod) (err error) {
  218. config := am.server.Config()
  219. if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
  220. return errFeatureDisabled
  221. }
  222. var serialized string
  223. if method == NickReservationOptional {
  224. serialized = "" // normally this is "default", but we're going to delete the key
  225. } else {
  226. serialized = nickReservationToString(method)
  227. }
  228. key := fmt.Sprintf(keyAccountEnforcement, account)
  229. am.Lock()
  230. defer am.Unlock()
  231. currentMethod := am.accountToMethod[account]
  232. if method != currentMethod {
  233. if method == NickReservationOptional {
  234. delete(am.accountToMethod, account)
  235. } else {
  236. am.accountToMethod[account] = method
  237. }
  238. return am.server.store.Update(func(tx *buntdb.Tx) (err error) {
  239. if serialized != "" {
  240. _, _, err = tx.Set(key, nickReservationToString(method), nil)
  241. } else {
  242. _, err = tx.Delete(key)
  243. }
  244. return
  245. })
  246. }
  247. return nil
  248. }
  249. func (am *AccountManager) AccountToClients(account string) (result []*Client) {
  250. cfaccount, err := CasefoldName(account)
  251. if err != nil {
  252. return
  253. }
  254. am.RLock()
  255. defer am.RUnlock()
  256. return am.accountToClients[cfaccount]
  257. }
  258. func (am *AccountManager) Register(client *Client, account string, callbackNamespace string, callbackValue string, passphrase string, certfp string) error {
  259. casefoldedAccount, err := CasefoldName(account)
  260. skeleton, skerr := Skeleton(account)
  261. if err != nil || skerr != nil || account == "" || account == "*" {
  262. return errAccountCreation
  263. }
  264. if restrictedNicknames[casefoldedAccount] || restrictedNicknames[skeleton] {
  265. return errAccountAlreadyRegistered
  266. }
  267. // can't register a guest nickname
  268. config := am.server.AccountConfig()
  269. renamePrefix := strings.ToLower(config.NickReservation.RenamePrefix)
  270. if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) {
  271. return errAccountAlreadyRegistered
  272. }
  273. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  274. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  275. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  276. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  277. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  278. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  279. certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
  280. credStr, err := am.serializeCredentials(passphrase, certfp)
  281. if err != nil {
  282. return err
  283. }
  284. registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
  285. callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
  286. var setOptions *buntdb.SetOptions
  287. ttl := config.Registration.VerifyTimeout
  288. if ttl != 0 {
  289. setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
  290. }
  291. err = func() error {
  292. am.serialCacheUpdateMutex.Lock()
  293. defer am.serialCacheUpdateMutex.Unlock()
  294. // can't register an account with the same name as a registered nick
  295. if am.NickToAccount(casefoldedAccount) != "" {
  296. return errAccountAlreadyRegistered
  297. }
  298. return am.server.store.Update(func(tx *buntdb.Tx) error {
  299. _, err := am.loadRawAccount(tx, casefoldedAccount)
  300. if err != errAccountDoesNotExist {
  301. return errAccountAlreadyRegistered
  302. }
  303. if certfp != "" {
  304. // make sure certfp doesn't already exist because that'd be silly
  305. _, err := tx.Get(certFPKey)
  306. if err != buntdb.ErrNotFound {
  307. return errCertfpAlreadyExists
  308. }
  309. }
  310. tx.Set(accountKey, "1", setOptions)
  311. tx.Set(accountNameKey, account, setOptions)
  312. tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
  313. tx.Set(credentialsKey, credStr, setOptions)
  314. tx.Set(callbackKey, callbackSpec, setOptions)
  315. if certfp != "" {
  316. tx.Set(certFPKey, casefoldedAccount, setOptions)
  317. }
  318. return nil
  319. })
  320. }()
  321. if err != nil {
  322. return err
  323. }
  324. code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue)
  325. if err != nil {
  326. am.Unregister(casefoldedAccount)
  327. return errCallbackFailed
  328. } else {
  329. return am.server.store.Update(func(tx *buntdb.Tx) error {
  330. _, _, err = tx.Set(verificationCodeKey, code, setOptions)
  331. return err
  332. })
  333. }
  334. }
  335. // validatePassphrase checks whether a passphrase is allowed by our rules
  336. func validatePassphrase(passphrase string) error {
  337. // sanity check the length
  338. if len(passphrase) == 0 || len(passphrase) > 600 {
  339. return errAccountBadPassphrase
  340. }
  341. // for now, just enforce that spaces are not allowed
  342. for _, r := range passphrase {
  343. if unicode.IsSpace(r) {
  344. return errAccountBadPassphrase
  345. }
  346. }
  347. return nil
  348. }
  349. // helper to assemble the serialized JSON for an account's credentials
  350. func (am *AccountManager) serializeCredentials(passphrase string, certfp string) (result string, err error) {
  351. var creds AccountCredentials
  352. creds.Version = 1
  353. // we need at least one of passphrase and certfp:
  354. if passphrase == "" && certfp == "" {
  355. return "", errAccountBadPassphrase
  356. }
  357. // but if we have one, it's fine if the other is missing, it just means no
  358. // credential of that type will be accepted.
  359. creds.Certificate = certfp
  360. if passphrase != "" {
  361. if validatePassphrase(passphrase) != nil {
  362. return "", errAccountBadPassphrase
  363. }
  364. bcryptCost := int(am.server.Config().Accounts.Registration.BcryptCost)
  365. creds.PassphraseHash, err = passwd.GenerateFromPassword([]byte(passphrase), bcryptCost)
  366. if err != nil {
  367. am.server.logger.Error("internal", "could not hash password", err.Error())
  368. return "", errAccountCreation
  369. }
  370. }
  371. credText, err := json.Marshal(creds)
  372. if err != nil {
  373. am.server.logger.Error("internal", "could not marshal credentials", err.Error())
  374. return "", errAccountCreation
  375. }
  376. return string(credText), nil
  377. }
  378. // changes the password for an account
  379. func (am *AccountManager) setPassword(account string, password string) (err error) {
  380. casefoldedAccount, err := CasefoldName(account)
  381. if err != nil {
  382. return err
  383. }
  384. act, err := am.LoadAccount(casefoldedAccount)
  385. if err != nil {
  386. return err
  387. }
  388. credStr, err := am.serializeCredentials(password, act.Credentials.Certificate)
  389. if err != nil {
  390. return err
  391. }
  392. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  393. return am.server.store.Update(func(tx *buntdb.Tx) error {
  394. _, _, err := tx.Set(credentialsKey, credStr, nil)
  395. return err
  396. })
  397. }
  398. func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
  399. if callbackNamespace == "*" || callbackNamespace == "none" || callbackNamespace == "admin" {
  400. return "", nil
  401. } else if callbackNamespace == "mailto" {
  402. return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue)
  403. } else {
  404. return "", errors.New(fmt.Sprintf("Callback not implemented: %s", callbackNamespace))
  405. }
  406. }
  407. func (am *AccountManager) dispatchMailtoCallback(client *Client, casefoldedAccount string, callbackValue string) (code string, err error) {
  408. config := am.server.AccountConfig().Registration.Callbacks.Mailto
  409. code = utils.GenerateSecretToken()
  410. subject := config.VerifyMessageSubject
  411. if subject == "" {
  412. subject = fmt.Sprintf(client.t("Verify your account on %s"), am.server.name)
  413. }
  414. messageStrings := []string{
  415. fmt.Sprintf("From: %s\r\n", config.Sender),
  416. fmt.Sprintf("To: %s\r\n", callbackValue),
  417. fmt.Sprintf("Subject: %s\r\n", subject),
  418. "\r\n", // end headers, begin message body
  419. fmt.Sprintf(client.t("Account: %s"), casefoldedAccount) + "\r\n",
  420. fmt.Sprintf(client.t("Verification code: %s"), code) + "\r\n",
  421. "\r\n",
  422. client.t("To verify your account, issue one of these commands:") + "\r\n",
  423. fmt.Sprintf("/MSG NickServ VERIFY %s %s", casefoldedAccount, code) + "\r\n",
  424. }
  425. var message []byte
  426. for i := 0; i < len(messageStrings); i++ {
  427. message = append(message, []byte(messageStrings[i])...)
  428. }
  429. addr := fmt.Sprintf("%s:%d", config.Server, config.Port)
  430. var auth smtp.Auth
  431. if config.Username != "" && config.Password != "" {
  432. auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
  433. }
  434. // TODO: this will never send the password in plaintext over a nonlocal link,
  435. // but it might send the email in plaintext, regardless of the value of
  436. // config.TLS.InsecureSkipVerify
  437. err = smtp.SendMail(addr, auth, config.Sender, []string{callbackValue}, message)
  438. if err != nil {
  439. am.server.logger.Error("internal", "Failed to dispatch e-mail", err.Error())
  440. }
  441. return
  442. }
  443. func (am *AccountManager) Verify(client *Client, account string, code string) error {
  444. casefoldedAccount, err := CasefoldName(account)
  445. if err != nil || account == "" || account == "*" {
  446. return errAccountVerificationFailed
  447. }
  448. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  449. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  450. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  451. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  452. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  453. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  454. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  455. var raw rawClientAccount
  456. func() {
  457. am.serialCacheUpdateMutex.Lock()
  458. defer am.serialCacheUpdateMutex.Unlock()
  459. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  460. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  461. if err == errAccountDoesNotExist {
  462. return errAccountDoesNotExist
  463. } else if err != nil {
  464. return errAccountVerificationFailed
  465. } else if raw.Verified {
  466. return errAccountAlreadyVerified
  467. }
  468. // actually verify the code
  469. // a stored code of "" means a none callback / no code required
  470. success := false
  471. storedCode, err := tx.Get(verificationCodeKey)
  472. if err == nil {
  473. // this is probably unnecessary
  474. if storedCode == "" || utils.SecretTokensMatch(storedCode, code) {
  475. success = true
  476. }
  477. }
  478. if !success {
  479. return errAccountVerificationInvalidCode
  480. }
  481. // verify the account
  482. tx.Set(verifiedKey, "1", nil)
  483. // don't need the code anymore
  484. tx.Delete(verificationCodeKey)
  485. // re-set all other keys, removing the TTL
  486. tx.Set(accountKey, "1", nil)
  487. tx.Set(accountNameKey, raw.Name, nil)
  488. tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
  489. tx.Set(callbackKey, raw.Callback, nil)
  490. tx.Set(credentialsKey, raw.Credentials, nil)
  491. var creds AccountCredentials
  492. // XXX we shouldn't do (de)serialization inside the txn,
  493. // but this is like 2 usec on my system
  494. json.Unmarshal([]byte(raw.Credentials), &creds)
  495. if creds.Certificate != "" {
  496. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  497. tx.Set(certFPKey, casefoldedAccount, nil)
  498. }
  499. return nil
  500. })
  501. if err == nil {
  502. skeleton, _ := Skeleton(raw.Name)
  503. am.Lock()
  504. am.nickToAccount[casefoldedAccount] = casefoldedAccount
  505. am.skeletonToAccount[skeleton] = casefoldedAccount
  506. am.Unlock()
  507. }
  508. }()
  509. if err != nil {
  510. return err
  511. }
  512. raw.Verified = true
  513. clientAccount, err := am.deserializeRawAccount(raw)
  514. if err != nil {
  515. return err
  516. }
  517. if client != nil {
  518. am.Login(client, clientAccount)
  519. }
  520. return nil
  521. }
  522. func marshalReservedNicks(nicks []string) string {
  523. return strings.Join(nicks, ",")
  524. }
  525. func unmarshalReservedNicks(nicks string) (result []string) {
  526. if nicks == "" {
  527. return
  528. }
  529. return strings.Split(nicks, ",")
  530. }
  531. func (am *AccountManager) SetNickReserved(client *Client, nick string, saUnreserve bool, reserve bool) error {
  532. cfnick, err := CasefoldName(nick)
  533. skeleton, skerr := Skeleton(nick)
  534. // garbage nick, or garbage options, or disabled
  535. nrconfig := am.server.AccountConfig().NickReservation
  536. if err != nil || skerr != nil || cfnick == "" || (reserve && saUnreserve) || !nrconfig.Enabled {
  537. return errAccountNickReservationFailed
  538. }
  539. // the cache is in sync with the DB while we hold serialCacheUpdateMutex
  540. am.serialCacheUpdateMutex.Lock()
  541. defer am.serialCacheUpdateMutex.Unlock()
  542. // find the affected account, which is usually the client's:
  543. account := client.Account()
  544. if saUnreserve {
  545. // unless this is a sadrop:
  546. account = am.NickToAccount(cfnick)
  547. if account == "" {
  548. // nothing to do
  549. return nil
  550. }
  551. }
  552. if account == "" {
  553. return errAccountNotLoggedIn
  554. }
  555. am.Lock()
  556. accountForNick := am.nickToAccount[cfnick]
  557. var accountForSkeleton string
  558. if reserve {
  559. accountForSkeleton = am.skeletonToAccount[skeleton]
  560. }
  561. am.Unlock()
  562. if reserve && (accountForNick != "" || accountForSkeleton != "") {
  563. return errNicknameReserved
  564. } else if !reserve && !saUnreserve && accountForNick != account {
  565. return errNicknameReserved
  566. } else if !reserve && cfnick == account {
  567. return errAccountCantDropPrimaryNick
  568. }
  569. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, account)
  570. unverifiedAccountKey := fmt.Sprintf(keyAccountExists, cfnick)
  571. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  572. if reserve {
  573. // unverified accounts don't show up in NickToAccount yet (which is intentional),
  574. // however you shouldn't be able to reserve a nick out from under them
  575. _, err := tx.Get(unverifiedAccountKey)
  576. if err == nil {
  577. return errNicknameReserved
  578. }
  579. }
  580. rawNicks, err := tx.Get(nicksKey)
  581. if err != nil && err != buntdb.ErrNotFound {
  582. return err
  583. }
  584. nicks := unmarshalReservedNicks(rawNicks)
  585. if reserve {
  586. if len(nicks) >= nrconfig.AdditionalNickLimit {
  587. return errAccountTooManyNicks
  588. }
  589. nicks = append(nicks, nick)
  590. } else {
  591. // compute (original reserved nicks) minus cfnick
  592. var newNicks []string
  593. for _, reservedNick := range nicks {
  594. cfreservednick, _ := CasefoldName(reservedNick)
  595. if cfreservednick != cfnick {
  596. newNicks = append(newNicks, reservedNick)
  597. } else {
  598. // found the original, unfolded version of the nick we're dropping;
  599. // recompute the true skeleton from it
  600. skeleton, _ = Skeleton(reservedNick)
  601. }
  602. }
  603. nicks = newNicks
  604. }
  605. marshaledNicks := marshalReservedNicks(nicks)
  606. _, _, err = tx.Set(nicksKey, string(marshaledNicks), nil)
  607. return err
  608. })
  609. if err == errAccountTooManyNicks || err == errNicknameReserved {
  610. return err
  611. } else if err != nil {
  612. return errAccountNickReservationFailed
  613. }
  614. // success
  615. am.Lock()
  616. defer am.Unlock()
  617. if reserve {
  618. am.nickToAccount[cfnick] = account
  619. am.skeletonToAccount[skeleton] = account
  620. } else {
  621. delete(am.nickToAccount, cfnick)
  622. delete(am.skeletonToAccount, skeleton)
  623. }
  624. return nil
  625. }
  626. func (am *AccountManager) checkPassphrase(accountName, passphrase string) (account ClientAccount, err error) {
  627. account, err = am.LoadAccount(accountName)
  628. if err != nil {
  629. return
  630. }
  631. if !account.Verified {
  632. err = errAccountUnverified
  633. return
  634. }
  635. switch account.Credentials.Version {
  636. case 0:
  637. err = handleLegacyPasswordV0(am.server, accountName, account.Credentials, passphrase)
  638. case 1:
  639. if passwd.CompareHashAndPassword(account.Credentials.PassphraseHash, []byte(passphrase)) != nil {
  640. err = errAccountInvalidCredentials
  641. }
  642. default:
  643. err = errAccountInvalidCredentials
  644. }
  645. return
  646. }
  647. func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
  648. account, err := am.checkPassphrase(accountName, passphrase)
  649. if err != nil {
  650. return err
  651. }
  652. am.Login(client, account)
  653. return nil
  654. }
  655. func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) {
  656. casefoldedAccount, err := CasefoldName(accountName)
  657. if err != nil {
  658. err = errAccountDoesNotExist
  659. return
  660. }
  661. var raw rawClientAccount
  662. am.server.store.View(func(tx *buntdb.Tx) error {
  663. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  664. return nil
  665. })
  666. if err != nil {
  667. return
  668. }
  669. result, err = am.deserializeRawAccount(raw)
  670. return
  671. }
  672. func (am *AccountManager) deserializeRawAccount(raw rawClientAccount) (result ClientAccount, err error) {
  673. result.Name = raw.Name
  674. regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
  675. result.RegisteredAt = time.Unix(regTimeInt, 0)
  676. e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
  677. if e != nil {
  678. am.server.logger.Error("internal", "could not unmarshal credentials", e.Error())
  679. err = errAccountDoesNotExist
  680. return
  681. }
  682. result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
  683. result.Verified = raw.Verified
  684. if raw.VHost != "" {
  685. e := json.Unmarshal([]byte(raw.VHost), &result.VHost)
  686. if e != nil {
  687. am.server.logger.Warning("internal", "could not unmarshal vhost for account", result.Name, e.Error())
  688. // pretend they have no vhost and move on
  689. }
  690. }
  691. return
  692. }
  693. func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
  694. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  695. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  696. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  697. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  698. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  699. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  700. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
  701. vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
  702. _, e := tx.Get(accountKey)
  703. if e == buntdb.ErrNotFound {
  704. err = errAccountDoesNotExist
  705. return
  706. }
  707. result.Name, _ = tx.Get(accountNameKey)
  708. result.RegisteredAt, _ = tx.Get(registeredTimeKey)
  709. result.Credentials, _ = tx.Get(credentialsKey)
  710. result.Callback, _ = tx.Get(callbackKey)
  711. result.AdditionalNicks, _ = tx.Get(nicksKey)
  712. result.VHost, _ = tx.Get(vhostKey)
  713. if _, e = tx.Get(verifiedKey); e == nil {
  714. result.Verified = true
  715. }
  716. return
  717. }
  718. func (am *AccountManager) Unregister(account string) error {
  719. casefoldedAccount, err := CasefoldName(account)
  720. if err != nil {
  721. return errAccountDoesNotExist
  722. }
  723. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  724. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  725. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  726. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  727. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  728. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  729. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  730. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
  731. vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
  732. vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
  733. var clients []*Client
  734. var credText string
  735. var rawNicks string
  736. am.serialCacheUpdateMutex.Lock()
  737. defer am.serialCacheUpdateMutex.Unlock()
  738. var accountName string
  739. am.server.store.Update(func(tx *buntdb.Tx) error {
  740. tx.Delete(accountKey)
  741. accountName, _ = tx.Get(accountNameKey)
  742. tx.Delete(accountNameKey)
  743. tx.Delete(verifiedKey)
  744. tx.Delete(registeredTimeKey)
  745. tx.Delete(callbackKey)
  746. tx.Delete(verificationCodeKey)
  747. rawNicks, _ = tx.Get(nicksKey)
  748. tx.Delete(nicksKey)
  749. credText, err = tx.Get(credentialsKey)
  750. tx.Delete(credentialsKey)
  751. tx.Delete(vhostKey)
  752. _, err := tx.Delete(vhostQueueKey)
  753. am.decrementVHostQueueCount(casefoldedAccount, err)
  754. return nil
  755. })
  756. if err == nil {
  757. var creds AccountCredentials
  758. if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
  759. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  760. am.server.store.Update(func(tx *buntdb.Tx) error {
  761. if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
  762. tx.Delete(certFPKey)
  763. }
  764. return nil
  765. })
  766. }
  767. }
  768. skeleton, _ := Skeleton(accountName)
  769. additionalNicks := unmarshalReservedNicks(rawNicks)
  770. am.Lock()
  771. defer am.Unlock()
  772. clients = am.accountToClients[casefoldedAccount]
  773. delete(am.accountToClients, casefoldedAccount)
  774. delete(am.nickToAccount, casefoldedAccount)
  775. delete(am.skeletonToAccount, skeleton)
  776. for _, nick := range additionalNicks {
  777. delete(am.nickToAccount, nick)
  778. additionalSkel, _ := Skeleton(nick)
  779. delete(am.skeletonToAccount, additionalSkel)
  780. }
  781. for _, client := range clients {
  782. am.logoutOfAccount(client)
  783. }
  784. if err != nil {
  785. return errAccountDoesNotExist
  786. }
  787. return nil
  788. }
  789. func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
  790. if client.certfp == "" {
  791. return errAccountInvalidCredentials
  792. }
  793. var account string
  794. var rawAccount rawClientAccount
  795. certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
  796. err := am.server.store.Update(func(tx *buntdb.Tx) error {
  797. var err error
  798. account, _ = tx.Get(certFPKey)
  799. if account == "" {
  800. return errAccountInvalidCredentials
  801. }
  802. rawAccount, err = am.loadRawAccount(tx, account)
  803. if err != nil || !rawAccount.Verified {
  804. return errAccountUnverified
  805. }
  806. return nil
  807. })
  808. if err != nil {
  809. return err
  810. }
  811. // ok, we found an account corresponding to their certificate
  812. clientAccount, err := am.deserializeRawAccount(rawAccount)
  813. if err != nil {
  814. return err
  815. }
  816. am.Login(client, clientAccount)
  817. return nil
  818. }
  819. // represents someone's status in hostserv
  820. type VHostInfo struct {
  821. ApprovedVHost string
  822. Enabled bool
  823. RequestedVHost string
  824. RejectedVHost string
  825. RejectionReason string
  826. LastRequestTime time.Time
  827. }
  828. // pair type, <VHostInfo, accountName>
  829. type PendingVHostRequest struct {
  830. VHostInfo
  831. Account string
  832. }
  833. // callback type implementing the actual business logic of vhost operations
  834. type vhostMunger func(input VHostInfo) (output VHostInfo, err error)
  835. func (am *AccountManager) VHostSet(account string, vhost string) (result VHostInfo, err error) {
  836. munger := func(input VHostInfo) (output VHostInfo, err error) {
  837. output = input
  838. output.Enabled = true
  839. output.ApprovedVHost = vhost
  840. return
  841. }
  842. return am.performVHostChange(account, munger)
  843. }
  844. func (am *AccountManager) VHostRequest(account string, vhost string) (result VHostInfo, err error) {
  845. munger := func(input VHostInfo) (output VHostInfo, err error) {
  846. output = input
  847. output.RequestedVHost = vhost
  848. output.RejectedVHost = ""
  849. output.RejectionReason = ""
  850. output.LastRequestTime = time.Now().UTC()
  851. return
  852. }
  853. return am.performVHostChange(account, munger)
  854. }
  855. func (am *AccountManager) VHostApprove(account string) (result VHostInfo, err error) {
  856. munger := func(input VHostInfo) (output VHostInfo, err error) {
  857. output = input
  858. output.Enabled = true
  859. output.ApprovedVHost = input.RequestedVHost
  860. output.RequestedVHost = ""
  861. output.RejectionReason = ""
  862. return
  863. }
  864. return am.performVHostChange(account, munger)
  865. }
  866. func (am *AccountManager) VHostReject(account string, reason string) (result VHostInfo, err error) {
  867. munger := func(input VHostInfo) (output VHostInfo, err error) {
  868. output = input
  869. output.RejectedVHost = output.RequestedVHost
  870. output.RequestedVHost = ""
  871. output.RejectionReason = reason
  872. return
  873. }
  874. return am.performVHostChange(account, munger)
  875. }
  876. func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
  877. munger := func(input VHostInfo) (output VHostInfo, err error) {
  878. output = input
  879. output.Enabled = enabled
  880. return
  881. }
  882. return am.performVHostChange(client.Account(), munger)
  883. }
  884. func (am *AccountManager) performVHostChange(account string, munger vhostMunger) (result VHostInfo, err error) {
  885. account, err = CasefoldName(account)
  886. if err != nil || account == "" {
  887. err = errAccountDoesNotExist
  888. return
  889. }
  890. am.vHostUpdateMutex.Lock()
  891. defer am.vHostUpdateMutex.Unlock()
  892. clientAccount, err := am.LoadAccount(account)
  893. if err != nil {
  894. err = errAccountDoesNotExist
  895. return
  896. } else if !clientAccount.Verified {
  897. err = errAccountUnverified
  898. return
  899. }
  900. result, err = munger(clientAccount.VHost)
  901. if err != nil {
  902. return
  903. }
  904. vhtext, err := json.Marshal(result)
  905. if err != nil {
  906. err = errAccountUpdateFailed
  907. return
  908. }
  909. vhstr := string(vhtext)
  910. key := fmt.Sprintf(keyAccountVHost, account)
  911. queueKey := fmt.Sprintf(keyVHostQueueAcctToId, account)
  912. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  913. if _, _, err := tx.Set(key, vhstr, nil); err != nil {
  914. return err
  915. }
  916. // update request queue
  917. if clientAccount.VHost.RequestedVHost == "" && result.RequestedVHost != "" {
  918. id := atomic.AddUint64(&am.vhostRequestID, 1)
  919. if _, _, err = tx.Set(queueKey, strconv.FormatUint(id, 10), nil); err != nil {
  920. return err
  921. }
  922. atomic.AddUint64(&am.vhostRequestPendingCount, 1)
  923. } else if clientAccount.VHost.RequestedVHost != "" && result.RequestedVHost == "" {
  924. _, err = tx.Delete(queueKey)
  925. am.decrementVHostQueueCount(account, err)
  926. }
  927. return nil
  928. })
  929. if err != nil {
  930. err = errAccountUpdateFailed
  931. return
  932. }
  933. am.applyVhostToClients(account, result)
  934. return result, nil
  935. }
  936. // XXX annoying helper method for keeping the queue count in sync with the DB
  937. // `err` is the buntdb error returned from deleting the queue key
  938. func (am *AccountManager) decrementVHostQueueCount(account string, err error) {
  939. if err == nil {
  940. // successfully deleted a queue entry, do a 2's complement decrement:
  941. atomic.AddUint64(&am.vhostRequestPendingCount, ^uint64(0))
  942. } else if err != buntdb.ErrNotFound {
  943. am.server.logger.Error("internal", "buntdb dequeue error", account, err.Error())
  944. }
  945. }
  946. func (am *AccountManager) VHostListRequests(limit int) (requests []PendingVHostRequest, total int) {
  947. am.vHostUpdateMutex.Lock()
  948. defer am.vHostUpdateMutex.Unlock()
  949. total = int(atomic.LoadUint64(&am.vhostRequestPendingCount))
  950. prefix := fmt.Sprintf(keyVHostQueueAcctToId, "")
  951. accounts := make([]string, 0, limit)
  952. err := am.server.store.View(func(tx *buntdb.Tx) error {
  953. return tx.Ascend(vhostRequestIdx, func(key, value string) bool {
  954. accounts = append(accounts, strings.TrimPrefix(key, prefix))
  955. return len(accounts) < limit
  956. })
  957. })
  958. if err != nil {
  959. am.server.logger.Error("internal", "couldn't traverse vhost queue", err.Error())
  960. return
  961. }
  962. for _, account := range accounts {
  963. accountInfo, err := am.LoadAccount(account)
  964. if err == nil {
  965. requests = append(requests, PendingVHostRequest{
  966. Account: account,
  967. VHostInfo: accountInfo.VHost,
  968. })
  969. } else {
  970. am.server.logger.Error("internal", "corrupt account", account, err.Error())
  971. }
  972. }
  973. return
  974. }
  975. func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
  976. // if hostserv is disabled in config, then don't grant vhosts
  977. // that were previously approved while it was enabled
  978. if !am.server.AccountConfig().VHosts.Enabled {
  979. return
  980. }
  981. vhost := ""
  982. if info.Enabled {
  983. vhost = info.ApprovedVHost
  984. }
  985. oldNickmask := client.NickMaskString()
  986. updated := client.SetVHost(vhost)
  987. if updated {
  988. // TODO: doing I/O here is kind of a kludge
  989. go client.sendChghost(oldNickmask, vhost)
  990. }
  991. }
  992. func (am *AccountManager) applyVhostToClients(account string, result VHostInfo) {
  993. am.RLock()
  994. clients := am.accountToClients[account]
  995. am.RUnlock()
  996. for _, client := range clients {
  997. am.applyVHostInfo(client, result)
  998. }
  999. }
  1000. func (am *AccountManager) Login(client *Client, account ClientAccount) {
  1001. changed := client.SetAccountName(account.Name)
  1002. if !changed {
  1003. return
  1004. }
  1005. client.nickTimer.Touch()
  1006. am.applyVHostInfo(client, account.VHost)
  1007. casefoldedAccount := client.Account()
  1008. am.Lock()
  1009. defer am.Unlock()
  1010. am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
  1011. }
  1012. func (am *AccountManager) Logout(client *Client) {
  1013. am.Lock()
  1014. defer am.Unlock()
  1015. casefoldedAccount := client.Account()
  1016. if casefoldedAccount == "" {
  1017. return
  1018. }
  1019. am.logoutOfAccount(client)
  1020. clients := am.accountToClients[casefoldedAccount]
  1021. if len(clients) <= 1 {
  1022. delete(am.accountToClients, casefoldedAccount)
  1023. return
  1024. }
  1025. remainingClients := make([]*Client, len(clients)-1)
  1026. remainingPos := 0
  1027. for currentPos := 0; currentPos < len(clients); currentPos++ {
  1028. if clients[currentPos] != client {
  1029. remainingClients[remainingPos] = clients[currentPos]
  1030. remainingPos++
  1031. }
  1032. }
  1033. am.accountToClients[casefoldedAccount] = remainingClients
  1034. return
  1035. }
  1036. var (
  1037. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  1038. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  1039. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
  1040. "PLAIN": authPlainHandler,
  1041. "EXTERNAL": authExternalHandler,
  1042. }
  1043. )
  1044. // AccountCredentials stores the various methods for verifying accounts.
  1045. type AccountCredentials struct {
  1046. Version uint
  1047. PassphraseSalt []byte // legacy field, not used by v1 and later
  1048. PassphraseHash []byte
  1049. Certificate string // fingerprint
  1050. }
  1051. // ClientAccount represents a user account.
  1052. type ClientAccount struct {
  1053. // Name of the account.
  1054. Name string
  1055. // RegisteredAt represents the time that the account was registered.
  1056. RegisteredAt time.Time
  1057. Credentials AccountCredentials
  1058. Verified bool
  1059. AdditionalNicks []string
  1060. VHost VHostInfo
  1061. }
  1062. // convenience for passing around raw serialized account data
  1063. type rawClientAccount struct {
  1064. Name string
  1065. RegisteredAt string
  1066. Credentials string
  1067. Callback string
  1068. Verified bool
  1069. AdditionalNicks string
  1070. VHost string
  1071. }
  1072. // logoutOfAccount logs the client out of their current account.
  1073. func (am *AccountManager) logoutOfAccount(client *Client) {
  1074. if client.Account() == "" {
  1075. // already logged out
  1076. return
  1077. }
  1078. client.SetAccountName("")
  1079. go client.nickTimer.Touch()
  1080. // dispatch account-notify
  1081. // TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
  1082. go func() {
  1083. for friend := range client.Friends(caps.AccountNotify) {
  1084. friend.Send(nil, client.NickMaskString(), "ACCOUNT", "*")
  1085. }
  1086. }()
  1087. }