您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

accounts.go 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268
  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. config := am.server.AccountConfig()
  268. // if nick reservation is enabled, you can only register your current nickname
  269. // as an account; this prevents "land-grab" situations where someone else
  270. // registers your nick out from under you and then NS GHOSTs you
  271. // n.b. client is nil during a SAREGISTER:
  272. if config.NickReservation.Enabled && client != nil && client.Nick() != account {
  273. return errAccountMustHoldNick
  274. }
  275. // can't register a guest nickname
  276. renamePrefix := strings.ToLower(config.NickReservation.RenamePrefix)
  277. if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) {
  278. return errAccountAlreadyRegistered
  279. }
  280. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  281. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  282. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  283. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  284. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  285. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  286. certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
  287. credStr, err := am.serializeCredentials(passphrase, certfp)
  288. if err != nil {
  289. return err
  290. }
  291. registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
  292. callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
  293. var setOptions *buntdb.SetOptions
  294. ttl := config.Registration.VerifyTimeout
  295. if ttl != 0 {
  296. setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
  297. }
  298. err = func() error {
  299. am.serialCacheUpdateMutex.Lock()
  300. defer am.serialCacheUpdateMutex.Unlock()
  301. // can't register an account with the same name as a registered nick
  302. if am.NickToAccount(casefoldedAccount) != "" {
  303. return errAccountAlreadyRegistered
  304. }
  305. return am.server.store.Update(func(tx *buntdb.Tx) error {
  306. _, err := am.loadRawAccount(tx, casefoldedAccount)
  307. if err != errAccountDoesNotExist {
  308. return errAccountAlreadyRegistered
  309. }
  310. if certfp != "" {
  311. // make sure certfp doesn't already exist because that'd be silly
  312. _, err := tx.Get(certFPKey)
  313. if err != buntdb.ErrNotFound {
  314. return errCertfpAlreadyExists
  315. }
  316. }
  317. tx.Set(accountKey, "1", setOptions)
  318. tx.Set(accountNameKey, account, setOptions)
  319. tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
  320. tx.Set(credentialsKey, credStr, setOptions)
  321. tx.Set(callbackKey, callbackSpec, setOptions)
  322. if certfp != "" {
  323. tx.Set(certFPKey, casefoldedAccount, setOptions)
  324. }
  325. return nil
  326. })
  327. }()
  328. if err != nil {
  329. return err
  330. }
  331. code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue)
  332. if err != nil {
  333. am.Unregister(casefoldedAccount)
  334. return errCallbackFailed
  335. } else {
  336. return am.server.store.Update(func(tx *buntdb.Tx) error {
  337. _, _, err = tx.Set(verificationCodeKey, code, setOptions)
  338. return err
  339. })
  340. }
  341. }
  342. // validatePassphrase checks whether a passphrase is allowed by our rules
  343. func validatePassphrase(passphrase string) error {
  344. // sanity check the length
  345. if len(passphrase) == 0 || len(passphrase) > 600 {
  346. return errAccountBadPassphrase
  347. }
  348. // for now, just enforce that spaces are not allowed
  349. for _, r := range passphrase {
  350. if unicode.IsSpace(r) {
  351. return errAccountBadPassphrase
  352. }
  353. }
  354. return nil
  355. }
  356. // helper to assemble the serialized JSON for an account's credentials
  357. func (am *AccountManager) serializeCredentials(passphrase string, certfp string) (result string, err error) {
  358. var creds AccountCredentials
  359. creds.Version = 1
  360. // we need at least one of passphrase and certfp:
  361. if passphrase == "" && certfp == "" {
  362. return "", errAccountBadPassphrase
  363. }
  364. // but if we have one, it's fine if the other is missing, it just means no
  365. // credential of that type will be accepted.
  366. creds.Certificate = certfp
  367. if passphrase != "" {
  368. if validatePassphrase(passphrase) != nil {
  369. return "", errAccountBadPassphrase
  370. }
  371. bcryptCost := int(am.server.Config().Accounts.Registration.BcryptCost)
  372. creds.PassphraseHash, err = passwd.GenerateFromPassword([]byte(passphrase), bcryptCost)
  373. if err != nil {
  374. am.server.logger.Error("internal", "could not hash password", err.Error())
  375. return "", errAccountCreation
  376. }
  377. }
  378. credText, err := json.Marshal(creds)
  379. if err != nil {
  380. am.server.logger.Error("internal", "could not marshal credentials", err.Error())
  381. return "", errAccountCreation
  382. }
  383. return string(credText), nil
  384. }
  385. // changes the password for an account
  386. func (am *AccountManager) setPassword(account string, password string) (err error) {
  387. casefoldedAccount, err := CasefoldName(account)
  388. if err != nil {
  389. return err
  390. }
  391. act, err := am.LoadAccount(casefoldedAccount)
  392. if err != nil {
  393. return err
  394. }
  395. credStr, err := am.serializeCredentials(password, act.Credentials.Certificate)
  396. if err != nil {
  397. return err
  398. }
  399. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  400. return am.server.store.Update(func(tx *buntdb.Tx) error {
  401. _, _, err := tx.Set(credentialsKey, credStr, nil)
  402. return err
  403. })
  404. }
  405. func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
  406. if callbackNamespace == "*" || callbackNamespace == "none" || callbackNamespace == "admin" {
  407. return "", nil
  408. } else if callbackNamespace == "mailto" {
  409. return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue)
  410. } else {
  411. return "", errors.New(fmt.Sprintf("Callback not implemented: %s", callbackNamespace))
  412. }
  413. }
  414. func (am *AccountManager) dispatchMailtoCallback(client *Client, casefoldedAccount string, callbackValue string) (code string, err error) {
  415. config := am.server.AccountConfig().Registration.Callbacks.Mailto
  416. code = utils.GenerateSecretToken()
  417. subject := config.VerifyMessageSubject
  418. if subject == "" {
  419. subject = fmt.Sprintf(client.t("Verify your account on %s"), am.server.name)
  420. }
  421. messageStrings := []string{
  422. fmt.Sprintf("From: %s\r\n", config.Sender),
  423. fmt.Sprintf("To: %s\r\n", callbackValue),
  424. fmt.Sprintf("Subject: %s\r\n", subject),
  425. "\r\n", // end headers, begin message body
  426. fmt.Sprintf(client.t("Account: %s"), casefoldedAccount) + "\r\n",
  427. fmt.Sprintf(client.t("Verification code: %s"), code) + "\r\n",
  428. "\r\n",
  429. client.t("To verify your account, issue one of these commands:") + "\r\n",
  430. fmt.Sprintf("/MSG NickServ VERIFY %s %s", casefoldedAccount, code) + "\r\n",
  431. }
  432. var message []byte
  433. for i := 0; i < len(messageStrings); i++ {
  434. message = append(message, []byte(messageStrings[i])...)
  435. }
  436. addr := fmt.Sprintf("%s:%d", config.Server, config.Port)
  437. var auth smtp.Auth
  438. if config.Username != "" && config.Password != "" {
  439. auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
  440. }
  441. // TODO: this will never send the password in plaintext over a nonlocal link,
  442. // but it might send the email in plaintext, regardless of the value of
  443. // config.TLS.InsecureSkipVerify
  444. err = smtp.SendMail(addr, auth, config.Sender, []string{callbackValue}, message)
  445. if err != nil {
  446. am.server.logger.Error("internal", "Failed to dispatch e-mail", err.Error())
  447. }
  448. return
  449. }
  450. func (am *AccountManager) Verify(client *Client, account string, code string) error {
  451. casefoldedAccount, err := CasefoldName(account)
  452. if err != nil || account == "" || account == "*" {
  453. return errAccountVerificationFailed
  454. }
  455. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  456. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  457. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  458. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  459. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  460. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  461. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  462. var raw rawClientAccount
  463. func() {
  464. am.serialCacheUpdateMutex.Lock()
  465. defer am.serialCacheUpdateMutex.Unlock()
  466. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  467. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  468. if err == errAccountDoesNotExist {
  469. return errAccountDoesNotExist
  470. } else if err != nil {
  471. return errAccountVerificationFailed
  472. } else if raw.Verified {
  473. return errAccountAlreadyVerified
  474. }
  475. // actually verify the code
  476. // a stored code of "" means a none callback / no code required
  477. success := false
  478. storedCode, err := tx.Get(verificationCodeKey)
  479. if err == nil {
  480. // this is probably unnecessary
  481. if storedCode == "" || utils.SecretTokensMatch(storedCode, code) {
  482. success = true
  483. }
  484. }
  485. if !success {
  486. return errAccountVerificationInvalidCode
  487. }
  488. // verify the account
  489. tx.Set(verifiedKey, "1", nil)
  490. // don't need the code anymore
  491. tx.Delete(verificationCodeKey)
  492. // re-set all other keys, removing the TTL
  493. tx.Set(accountKey, "1", nil)
  494. tx.Set(accountNameKey, raw.Name, nil)
  495. tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
  496. tx.Set(callbackKey, raw.Callback, nil)
  497. tx.Set(credentialsKey, raw.Credentials, nil)
  498. var creds AccountCredentials
  499. // XXX we shouldn't do (de)serialization inside the txn,
  500. // but this is like 2 usec on my system
  501. json.Unmarshal([]byte(raw.Credentials), &creds)
  502. if creds.Certificate != "" {
  503. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  504. tx.Set(certFPKey, casefoldedAccount, nil)
  505. }
  506. return nil
  507. })
  508. if err == nil {
  509. skeleton, _ := Skeleton(raw.Name)
  510. am.Lock()
  511. am.nickToAccount[casefoldedAccount] = casefoldedAccount
  512. am.skeletonToAccount[skeleton] = casefoldedAccount
  513. am.Unlock()
  514. }
  515. }()
  516. if err != nil {
  517. return err
  518. }
  519. nick := "[server admin]"
  520. if client != nil {
  521. nick = client.Nick()
  522. }
  523. am.server.logger.Info("accounts", "client", nick, "registered account", casefoldedAccount)
  524. raw.Verified = true
  525. clientAccount, err := am.deserializeRawAccount(raw)
  526. if err != nil {
  527. return err
  528. }
  529. if client != nil {
  530. am.Login(client, clientAccount)
  531. }
  532. return nil
  533. }
  534. func marshalReservedNicks(nicks []string) string {
  535. return strings.Join(nicks, ",")
  536. }
  537. func unmarshalReservedNicks(nicks string) (result []string) {
  538. if nicks == "" {
  539. return
  540. }
  541. return strings.Split(nicks, ",")
  542. }
  543. func (am *AccountManager) SetNickReserved(client *Client, nick string, saUnreserve bool, reserve bool) error {
  544. cfnick, err := CasefoldName(nick)
  545. skeleton, skerr := Skeleton(nick)
  546. // garbage nick, or garbage options, or disabled
  547. nrconfig := am.server.AccountConfig().NickReservation
  548. if err != nil || skerr != nil || cfnick == "" || (reserve && saUnreserve) || !nrconfig.Enabled {
  549. return errAccountNickReservationFailed
  550. }
  551. // the cache is in sync with the DB while we hold serialCacheUpdateMutex
  552. am.serialCacheUpdateMutex.Lock()
  553. defer am.serialCacheUpdateMutex.Unlock()
  554. // find the affected account, which is usually the client's:
  555. account := client.Account()
  556. if saUnreserve {
  557. // unless this is a sadrop:
  558. account = am.NickToAccount(cfnick)
  559. if account == "" {
  560. // nothing to do
  561. return nil
  562. }
  563. }
  564. if account == "" {
  565. return errAccountNotLoggedIn
  566. }
  567. am.Lock()
  568. accountForNick := am.nickToAccount[cfnick]
  569. var accountForSkeleton string
  570. if reserve {
  571. accountForSkeleton = am.skeletonToAccount[skeleton]
  572. }
  573. am.Unlock()
  574. if reserve && (accountForNick != "" || accountForSkeleton != "") {
  575. return errNicknameReserved
  576. } else if !reserve && !saUnreserve && accountForNick != account {
  577. return errNicknameReserved
  578. } else if !reserve && cfnick == account {
  579. return errAccountCantDropPrimaryNick
  580. }
  581. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, account)
  582. unverifiedAccountKey := fmt.Sprintf(keyAccountExists, cfnick)
  583. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  584. if reserve {
  585. // unverified accounts don't show up in NickToAccount yet (which is intentional),
  586. // however you shouldn't be able to reserve a nick out from under them
  587. _, err := tx.Get(unverifiedAccountKey)
  588. if err == nil {
  589. return errNicknameReserved
  590. }
  591. }
  592. rawNicks, err := tx.Get(nicksKey)
  593. if err != nil && err != buntdb.ErrNotFound {
  594. return err
  595. }
  596. nicks := unmarshalReservedNicks(rawNicks)
  597. if reserve {
  598. if len(nicks) >= nrconfig.AdditionalNickLimit {
  599. return errAccountTooManyNicks
  600. }
  601. nicks = append(nicks, nick)
  602. } else {
  603. // compute (original reserved nicks) minus cfnick
  604. var newNicks []string
  605. for _, reservedNick := range nicks {
  606. cfreservednick, _ := CasefoldName(reservedNick)
  607. if cfreservednick != cfnick {
  608. newNicks = append(newNicks, reservedNick)
  609. } else {
  610. // found the original, unfolded version of the nick we're dropping;
  611. // recompute the true skeleton from it
  612. skeleton, _ = Skeleton(reservedNick)
  613. }
  614. }
  615. nicks = newNicks
  616. }
  617. marshaledNicks := marshalReservedNicks(nicks)
  618. _, _, err = tx.Set(nicksKey, string(marshaledNicks), nil)
  619. return err
  620. })
  621. if err == errAccountTooManyNicks || err == errNicknameReserved {
  622. return err
  623. } else if err != nil {
  624. return errAccountNickReservationFailed
  625. }
  626. // success
  627. am.Lock()
  628. defer am.Unlock()
  629. if reserve {
  630. am.nickToAccount[cfnick] = account
  631. am.skeletonToAccount[skeleton] = account
  632. } else {
  633. delete(am.nickToAccount, cfnick)
  634. delete(am.skeletonToAccount, skeleton)
  635. }
  636. return nil
  637. }
  638. func (am *AccountManager) checkPassphrase(accountName, passphrase string) (account ClientAccount, err error) {
  639. account, err = am.LoadAccount(accountName)
  640. if err != nil {
  641. return
  642. }
  643. if !account.Verified {
  644. err = errAccountUnverified
  645. return
  646. }
  647. switch account.Credentials.Version {
  648. case 0:
  649. err = handleLegacyPasswordV0(am.server, accountName, account.Credentials, passphrase)
  650. case 1:
  651. if passwd.CompareHashAndPassword(account.Credentials.PassphraseHash, []byte(passphrase)) != nil {
  652. err = errAccountInvalidCredentials
  653. }
  654. default:
  655. err = errAccountInvalidCredentials
  656. }
  657. return
  658. }
  659. func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
  660. account, err := am.checkPassphrase(accountName, passphrase)
  661. if err != nil {
  662. return err
  663. }
  664. am.Login(client, account)
  665. return nil
  666. }
  667. func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) {
  668. casefoldedAccount, err := CasefoldName(accountName)
  669. if err != nil {
  670. err = errAccountDoesNotExist
  671. return
  672. }
  673. var raw rawClientAccount
  674. am.server.store.View(func(tx *buntdb.Tx) error {
  675. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  676. return nil
  677. })
  678. if err != nil {
  679. return
  680. }
  681. result, err = am.deserializeRawAccount(raw)
  682. return
  683. }
  684. func (am *AccountManager) deserializeRawAccount(raw rawClientAccount) (result ClientAccount, err error) {
  685. result.Name = raw.Name
  686. regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
  687. result.RegisteredAt = time.Unix(regTimeInt, 0)
  688. e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
  689. if e != nil {
  690. am.server.logger.Error("internal", "could not unmarshal credentials", e.Error())
  691. err = errAccountDoesNotExist
  692. return
  693. }
  694. result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
  695. result.Verified = raw.Verified
  696. if raw.VHost != "" {
  697. e := json.Unmarshal([]byte(raw.VHost), &result.VHost)
  698. if e != nil {
  699. am.server.logger.Warning("internal", "could not unmarshal vhost for account", result.Name, e.Error())
  700. // pretend they have no vhost and move on
  701. }
  702. }
  703. return
  704. }
  705. func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
  706. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  707. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  708. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  709. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  710. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  711. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  712. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
  713. vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
  714. _, e := tx.Get(accountKey)
  715. if e == buntdb.ErrNotFound {
  716. err = errAccountDoesNotExist
  717. return
  718. }
  719. result.Name, _ = tx.Get(accountNameKey)
  720. result.RegisteredAt, _ = tx.Get(registeredTimeKey)
  721. result.Credentials, _ = tx.Get(credentialsKey)
  722. result.Callback, _ = tx.Get(callbackKey)
  723. result.AdditionalNicks, _ = tx.Get(nicksKey)
  724. result.VHost, _ = tx.Get(vhostKey)
  725. if _, e = tx.Get(verifiedKey); e == nil {
  726. result.Verified = true
  727. }
  728. return
  729. }
  730. func (am *AccountManager) Unregister(account string) error {
  731. casefoldedAccount, err := CasefoldName(account)
  732. if err != nil {
  733. return errAccountDoesNotExist
  734. }
  735. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  736. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  737. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  738. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  739. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  740. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  741. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  742. nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
  743. vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
  744. vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
  745. var clients []*Client
  746. var credText string
  747. var rawNicks string
  748. am.serialCacheUpdateMutex.Lock()
  749. defer am.serialCacheUpdateMutex.Unlock()
  750. var accountName string
  751. am.server.store.Update(func(tx *buntdb.Tx) error {
  752. tx.Delete(accountKey)
  753. accountName, _ = tx.Get(accountNameKey)
  754. tx.Delete(accountNameKey)
  755. tx.Delete(verifiedKey)
  756. tx.Delete(registeredTimeKey)
  757. tx.Delete(callbackKey)
  758. tx.Delete(verificationCodeKey)
  759. rawNicks, _ = tx.Get(nicksKey)
  760. tx.Delete(nicksKey)
  761. credText, err = tx.Get(credentialsKey)
  762. tx.Delete(credentialsKey)
  763. tx.Delete(vhostKey)
  764. _, err := tx.Delete(vhostQueueKey)
  765. am.decrementVHostQueueCount(casefoldedAccount, err)
  766. return nil
  767. })
  768. if err == nil {
  769. var creds AccountCredentials
  770. if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
  771. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  772. am.server.store.Update(func(tx *buntdb.Tx) error {
  773. if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
  774. tx.Delete(certFPKey)
  775. }
  776. return nil
  777. })
  778. }
  779. }
  780. skeleton, _ := Skeleton(accountName)
  781. additionalNicks := unmarshalReservedNicks(rawNicks)
  782. am.Lock()
  783. defer am.Unlock()
  784. clients = am.accountToClients[casefoldedAccount]
  785. delete(am.accountToClients, casefoldedAccount)
  786. delete(am.nickToAccount, casefoldedAccount)
  787. delete(am.skeletonToAccount, skeleton)
  788. for _, nick := range additionalNicks {
  789. delete(am.nickToAccount, nick)
  790. additionalSkel, _ := Skeleton(nick)
  791. delete(am.skeletonToAccount, additionalSkel)
  792. }
  793. for _, client := range clients {
  794. am.logoutOfAccount(client)
  795. }
  796. if err != nil {
  797. return errAccountDoesNotExist
  798. }
  799. return nil
  800. }
  801. func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
  802. if client.certfp == "" {
  803. return errAccountInvalidCredentials
  804. }
  805. var account string
  806. var rawAccount rawClientAccount
  807. certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
  808. err := am.server.store.Update(func(tx *buntdb.Tx) error {
  809. var err error
  810. account, _ = tx.Get(certFPKey)
  811. if account == "" {
  812. return errAccountInvalidCredentials
  813. }
  814. rawAccount, err = am.loadRawAccount(tx, account)
  815. if err != nil || !rawAccount.Verified {
  816. return errAccountUnverified
  817. }
  818. return nil
  819. })
  820. if err != nil {
  821. return err
  822. }
  823. // ok, we found an account corresponding to their certificate
  824. clientAccount, err := am.deserializeRawAccount(rawAccount)
  825. if err != nil {
  826. return err
  827. }
  828. am.Login(client, clientAccount)
  829. return nil
  830. }
  831. // represents someone's status in hostserv
  832. type VHostInfo struct {
  833. ApprovedVHost string
  834. Enabled bool
  835. RequestedVHost string
  836. RejectedVHost string
  837. RejectionReason string
  838. LastRequestTime time.Time
  839. }
  840. // pair type, <VHostInfo, accountName>
  841. type PendingVHostRequest struct {
  842. VHostInfo
  843. Account string
  844. }
  845. // callback type implementing the actual business logic of vhost operations
  846. type vhostMunger func(input VHostInfo) (output VHostInfo, err error)
  847. func (am *AccountManager) VHostSet(account string, vhost string) (result VHostInfo, err error) {
  848. munger := func(input VHostInfo) (output VHostInfo, err error) {
  849. output = input
  850. output.Enabled = true
  851. output.ApprovedVHost = vhost
  852. return
  853. }
  854. return am.performVHostChange(account, munger)
  855. }
  856. func (am *AccountManager) VHostRequest(account string, vhost string) (result VHostInfo, err error) {
  857. munger := func(input VHostInfo) (output VHostInfo, err error) {
  858. output = input
  859. output.RequestedVHost = vhost
  860. output.RejectedVHost = ""
  861. output.RejectionReason = ""
  862. output.LastRequestTime = time.Now().UTC()
  863. return
  864. }
  865. return am.performVHostChange(account, munger)
  866. }
  867. func (am *AccountManager) VHostApprove(account string) (result VHostInfo, err error) {
  868. munger := func(input VHostInfo) (output VHostInfo, err error) {
  869. output = input
  870. output.Enabled = true
  871. output.ApprovedVHost = input.RequestedVHost
  872. output.RequestedVHost = ""
  873. output.RejectionReason = ""
  874. return
  875. }
  876. return am.performVHostChange(account, munger)
  877. }
  878. func (am *AccountManager) VHostReject(account string, reason string) (result VHostInfo, err error) {
  879. munger := func(input VHostInfo) (output VHostInfo, err error) {
  880. output = input
  881. output.RejectedVHost = output.RequestedVHost
  882. output.RequestedVHost = ""
  883. output.RejectionReason = reason
  884. return
  885. }
  886. return am.performVHostChange(account, munger)
  887. }
  888. func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
  889. munger := func(input VHostInfo) (output VHostInfo, err error) {
  890. output = input
  891. output.Enabled = enabled
  892. return
  893. }
  894. return am.performVHostChange(client.Account(), munger)
  895. }
  896. func (am *AccountManager) performVHostChange(account string, munger vhostMunger) (result VHostInfo, err error) {
  897. account, err = CasefoldName(account)
  898. if err != nil || account == "" {
  899. err = errAccountDoesNotExist
  900. return
  901. }
  902. am.vHostUpdateMutex.Lock()
  903. defer am.vHostUpdateMutex.Unlock()
  904. clientAccount, err := am.LoadAccount(account)
  905. if err != nil {
  906. err = errAccountDoesNotExist
  907. return
  908. } else if !clientAccount.Verified {
  909. err = errAccountUnverified
  910. return
  911. }
  912. result, err = munger(clientAccount.VHost)
  913. if err != nil {
  914. return
  915. }
  916. vhtext, err := json.Marshal(result)
  917. if err != nil {
  918. err = errAccountUpdateFailed
  919. return
  920. }
  921. vhstr := string(vhtext)
  922. key := fmt.Sprintf(keyAccountVHost, account)
  923. queueKey := fmt.Sprintf(keyVHostQueueAcctToId, account)
  924. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  925. if _, _, err := tx.Set(key, vhstr, nil); err != nil {
  926. return err
  927. }
  928. // update request queue
  929. if clientAccount.VHost.RequestedVHost == "" && result.RequestedVHost != "" {
  930. id := atomic.AddUint64(&am.vhostRequestID, 1)
  931. if _, _, err = tx.Set(queueKey, strconv.FormatUint(id, 10), nil); err != nil {
  932. return err
  933. }
  934. atomic.AddUint64(&am.vhostRequestPendingCount, 1)
  935. } else if clientAccount.VHost.RequestedVHost != "" && result.RequestedVHost == "" {
  936. _, err = tx.Delete(queueKey)
  937. am.decrementVHostQueueCount(account, err)
  938. }
  939. return nil
  940. })
  941. if err != nil {
  942. err = errAccountUpdateFailed
  943. return
  944. }
  945. am.applyVhostToClients(account, result)
  946. return result, nil
  947. }
  948. // XXX annoying helper method for keeping the queue count in sync with the DB
  949. // `err` is the buntdb error returned from deleting the queue key
  950. func (am *AccountManager) decrementVHostQueueCount(account string, err error) {
  951. if err == nil {
  952. // successfully deleted a queue entry, do a 2's complement decrement:
  953. atomic.AddUint64(&am.vhostRequestPendingCount, ^uint64(0))
  954. } else if err != buntdb.ErrNotFound {
  955. am.server.logger.Error("internal", "buntdb dequeue error", account, err.Error())
  956. }
  957. }
  958. func (am *AccountManager) VHostListRequests(limit int) (requests []PendingVHostRequest, total int) {
  959. am.vHostUpdateMutex.Lock()
  960. defer am.vHostUpdateMutex.Unlock()
  961. total = int(atomic.LoadUint64(&am.vhostRequestPendingCount))
  962. prefix := fmt.Sprintf(keyVHostQueueAcctToId, "")
  963. accounts := make([]string, 0, limit)
  964. err := am.server.store.View(func(tx *buntdb.Tx) error {
  965. return tx.Ascend(vhostRequestIdx, func(key, value string) bool {
  966. accounts = append(accounts, strings.TrimPrefix(key, prefix))
  967. return len(accounts) < limit
  968. })
  969. })
  970. if err != nil {
  971. am.server.logger.Error("internal", "couldn't traverse vhost queue", err.Error())
  972. return
  973. }
  974. for _, account := range accounts {
  975. accountInfo, err := am.LoadAccount(account)
  976. if err == nil {
  977. requests = append(requests, PendingVHostRequest{
  978. Account: account,
  979. VHostInfo: accountInfo.VHost,
  980. })
  981. } else {
  982. am.server.logger.Error("internal", "corrupt account", account, err.Error())
  983. }
  984. }
  985. return
  986. }
  987. func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
  988. // if hostserv is disabled in config, then don't grant vhosts
  989. // that were previously approved while it was enabled
  990. if !am.server.AccountConfig().VHosts.Enabled {
  991. return
  992. }
  993. vhost := ""
  994. if info.Enabled {
  995. vhost = info.ApprovedVHost
  996. }
  997. oldNickmask := client.NickMaskString()
  998. updated := client.SetVHost(vhost)
  999. if updated {
  1000. // TODO: doing I/O here is kind of a kludge
  1001. go client.sendChghost(oldNickmask, vhost)
  1002. }
  1003. }
  1004. func (am *AccountManager) applyVhostToClients(account string, result VHostInfo) {
  1005. am.RLock()
  1006. clients := am.accountToClients[account]
  1007. am.RUnlock()
  1008. for _, client := range clients {
  1009. am.applyVHostInfo(client, result)
  1010. }
  1011. }
  1012. func (am *AccountManager) Login(client *Client, account ClientAccount) {
  1013. changed := client.SetAccountName(account.Name)
  1014. if !changed {
  1015. return
  1016. }
  1017. client.nickTimer.Touch()
  1018. am.applyVHostInfo(client, account.VHost)
  1019. casefoldedAccount := client.Account()
  1020. am.Lock()
  1021. defer am.Unlock()
  1022. am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
  1023. }
  1024. func (am *AccountManager) Logout(client *Client) {
  1025. am.Lock()
  1026. defer am.Unlock()
  1027. casefoldedAccount := client.Account()
  1028. if casefoldedAccount == "" {
  1029. return
  1030. }
  1031. am.logoutOfAccount(client)
  1032. clients := am.accountToClients[casefoldedAccount]
  1033. if len(clients) <= 1 {
  1034. delete(am.accountToClients, casefoldedAccount)
  1035. return
  1036. }
  1037. remainingClients := make([]*Client, len(clients)-1)
  1038. remainingPos := 0
  1039. for currentPos := 0; currentPos < len(clients); currentPos++ {
  1040. if clients[currentPos] != client {
  1041. remainingClients[remainingPos] = clients[currentPos]
  1042. remainingPos++
  1043. }
  1044. }
  1045. am.accountToClients[casefoldedAccount] = remainingClients
  1046. return
  1047. }
  1048. var (
  1049. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  1050. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  1051. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
  1052. "PLAIN": authPlainHandler,
  1053. "EXTERNAL": authExternalHandler,
  1054. }
  1055. )
  1056. // AccountCredentials stores the various methods for verifying accounts.
  1057. type AccountCredentials struct {
  1058. Version uint
  1059. PassphraseSalt []byte // legacy field, not used by v1 and later
  1060. PassphraseHash []byte
  1061. Certificate string // fingerprint
  1062. }
  1063. // ClientAccount represents a user account.
  1064. type ClientAccount struct {
  1065. // Name of the account.
  1066. Name string
  1067. // RegisteredAt represents the time that the account was registered.
  1068. RegisteredAt time.Time
  1069. Credentials AccountCredentials
  1070. Verified bool
  1071. AdditionalNicks []string
  1072. VHost VHostInfo
  1073. }
  1074. // convenience for passing around raw serialized account data
  1075. type rawClientAccount struct {
  1076. Name string
  1077. RegisteredAt string
  1078. Credentials string
  1079. Callback string
  1080. Verified bool
  1081. AdditionalNicks string
  1082. VHost string
  1083. }
  1084. // logoutOfAccount logs the client out of their current account.
  1085. func (am *AccountManager) logoutOfAccount(client *Client) {
  1086. if client.Account() == "" {
  1087. // already logged out
  1088. return
  1089. }
  1090. client.SetAccountName("")
  1091. go client.nickTimer.Touch()
  1092. // dispatch account-notify
  1093. // TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
  1094. go func() {
  1095. for friend := range client.Friends(caps.AccountNotify) {
  1096. friend.Send(nil, client.NickMaskString(), "ACCOUNT", "*")
  1097. }
  1098. }()
  1099. }