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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. "fmt"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/goshuirc/irc-go/ircfmt"
  12. "github.com/oragono/oragono/irc/caps"
  13. "github.com/oragono/oragono/irc/passwd"
  14. "github.com/oragono/oragono/irc/sno"
  15. "github.com/tidwall/buntdb"
  16. )
  17. const (
  18. keyAccountExists = "account.exists %s"
  19. keyAccountVerified = "account.verified %s"
  20. keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
  21. keyAccountRegTime = "account.registered.time %s"
  22. keyAccountCredentials = "account.credentials %s"
  23. keyCertToAccount = "account.creds.certfp %s"
  24. )
  25. // everything about accounts is persistent; therefore, the database is the authoritative
  26. // source of truth for all account information. anything on the heap is just a cache
  27. type AccountManager struct {
  28. sync.RWMutex // tier 2
  29. serialCacheUpdateMutex sync.Mutex // tier 3
  30. server *Server
  31. // track clients logged in to accounts
  32. accountToClients map[string][]*Client
  33. nickToAccount map[string]string
  34. }
  35. func NewAccountManager(server *Server) *AccountManager {
  36. am := AccountManager{
  37. accountToClients: make(map[string][]*Client),
  38. nickToAccount: make(map[string]string),
  39. server: server,
  40. }
  41. am.buildNickToAccountIndex()
  42. return &am
  43. }
  44. func (am *AccountManager) buildNickToAccountIndex() {
  45. if am.server.AccountConfig().NickReservation == NickReservationDisabled {
  46. return
  47. }
  48. result := make(map[string]string)
  49. existsPrefix := fmt.Sprintf(keyAccountExists, "")
  50. am.serialCacheUpdateMutex.Lock()
  51. defer am.serialCacheUpdateMutex.Unlock()
  52. err := am.server.store.View(func(tx *buntdb.Tx) error {
  53. err := tx.AscendGreaterOrEqual("", existsPrefix, func(key, value string) bool {
  54. if !strings.HasPrefix(key, existsPrefix) {
  55. return false
  56. }
  57. accountName := strings.TrimPrefix(key, existsPrefix)
  58. if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
  59. result[accountName] = accountName
  60. }
  61. return true
  62. })
  63. return err
  64. })
  65. if err != nil {
  66. am.server.logger.Error("internal", fmt.Sprintf("couldn't read reserved nicks: %v", err))
  67. } else {
  68. am.Lock()
  69. am.nickToAccount = result
  70. am.Unlock()
  71. }
  72. return
  73. }
  74. func (am *AccountManager) NickToAccount(cfnick string) string {
  75. am.RLock()
  76. defer am.RUnlock()
  77. return am.nickToAccount[cfnick]
  78. }
  79. func (am *AccountManager) Register(client *Client, account string, callbackNamespace string, callbackValue string, passphrase string, certfp string) error {
  80. casefoldedAccount, err := CasefoldName(account)
  81. if err != nil || account == "" || account == "*" {
  82. return errAccountCreation
  83. }
  84. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  85. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  86. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  87. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  88. certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
  89. var creds AccountCredentials
  90. // always set passphrase salt
  91. creds.PassphraseSalt, err = passwd.NewSalt()
  92. if err != nil {
  93. return errAccountCreation
  94. }
  95. // it's fine if this is empty, that just means no certificate is authorized
  96. creds.Certificate = certfp
  97. if passphrase != "" {
  98. creds.PassphraseHash, err = am.server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
  99. if err != nil {
  100. am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
  101. return errAccountCreation
  102. }
  103. }
  104. credText, err := json.Marshal(creds)
  105. if err != nil {
  106. am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
  107. return errAccountCreation
  108. }
  109. credStr := string(credText)
  110. registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
  111. var setOptions *buntdb.SetOptions
  112. ttl := am.server.AccountConfig().Registration.VerifyTimeout
  113. if ttl != 0 {
  114. setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
  115. }
  116. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  117. _, err := am.loadRawAccount(tx, casefoldedAccount)
  118. if err != errAccountDoesNotExist {
  119. return errAccountAlreadyRegistered
  120. }
  121. if certfp != "" {
  122. // make sure certfp doesn't already exist because that'd be silly
  123. _, err := tx.Get(certFPKey)
  124. if err != buntdb.ErrNotFound {
  125. return errCertfpAlreadyExists
  126. }
  127. }
  128. tx.Set(accountKey, "1", setOptions)
  129. tx.Set(accountNameKey, account, setOptions)
  130. tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
  131. tx.Set(credentialsKey, credStr, setOptions)
  132. if certfp != "" {
  133. tx.Set(certFPKey, casefoldedAccount, setOptions)
  134. }
  135. return nil
  136. })
  137. if err != nil {
  138. return err
  139. }
  140. return nil
  141. }
  142. func (am *AccountManager) Verify(client *Client, account string, code string) error {
  143. casefoldedAccount, err := CasefoldName(account)
  144. if err != nil || account == "" || account == "*" {
  145. return errAccountVerificationFailed
  146. }
  147. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  148. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  149. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  150. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  151. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  152. var raw rawClientAccount
  153. func() {
  154. am.serialCacheUpdateMutex.Lock()
  155. defer am.serialCacheUpdateMutex.Unlock()
  156. am.server.store.Update(func(tx *buntdb.Tx) error {
  157. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  158. if err == errAccountDoesNotExist {
  159. return errAccountDoesNotExist
  160. } else if err != nil {
  161. return errAccountVerificationFailed
  162. } else if raw.Verified {
  163. return errAccountAlreadyVerified
  164. }
  165. // TODO add code verification here
  166. // return errAccountVerificationFailed if it fails
  167. // verify the account
  168. tx.Set(verifiedKey, "1", nil)
  169. // re-set all other keys, removing the TTL
  170. tx.Set(accountKey, "1", nil)
  171. tx.Set(accountNameKey, raw.Name, nil)
  172. tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
  173. tx.Set(credentialsKey, raw.Credentials, nil)
  174. var creds AccountCredentials
  175. // XXX we shouldn't do (de)serialization inside the txn,
  176. // but this is like 2 usec on my system
  177. json.Unmarshal([]byte(raw.Credentials), &creds)
  178. if creds.Certificate != "" {
  179. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  180. tx.Set(certFPKey, casefoldedAccount, nil)
  181. }
  182. return nil
  183. })
  184. if err == nil {
  185. am.Lock()
  186. am.nickToAccount[casefoldedAccount] = casefoldedAccount
  187. am.Unlock()
  188. }
  189. }()
  190. if err != nil {
  191. return err
  192. }
  193. am.Login(client, raw.Name)
  194. return nil
  195. }
  196. func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
  197. casefoldedAccount, err := CasefoldName(accountName)
  198. if err != nil {
  199. return errAccountDoesNotExist
  200. }
  201. account, err := am.LoadAccount(casefoldedAccount)
  202. if err != nil {
  203. return err
  204. }
  205. if !account.Verified {
  206. return errAccountUnverified
  207. }
  208. err = am.server.passwords.CompareHashAndPassword(
  209. account.Credentials.PassphraseHash, account.Credentials.PassphraseSalt, passphrase)
  210. if err != nil {
  211. return errAccountInvalidCredentials
  212. }
  213. am.Login(client, account.Name)
  214. return nil
  215. }
  216. func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
  217. var raw rawClientAccount
  218. am.server.store.View(func(tx *buntdb.Tx) error {
  219. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  220. if err == buntdb.ErrNotFound {
  221. err = errAccountDoesNotExist
  222. }
  223. return nil
  224. })
  225. if err != nil {
  226. return
  227. }
  228. result.Name = raw.Name
  229. regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
  230. result.RegisteredAt = time.Unix(regTimeInt, 0)
  231. e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
  232. if e != nil {
  233. am.server.logger.Error("internal", fmt.Sprintf("could not unmarshal credentials: %v", e))
  234. err = errAccountDoesNotExist
  235. return
  236. }
  237. result.Verified = raw.Verified
  238. return
  239. }
  240. func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
  241. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  242. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  243. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  244. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  245. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  246. _, e := tx.Get(accountKey)
  247. if e == buntdb.ErrNotFound {
  248. err = errAccountDoesNotExist
  249. return
  250. }
  251. if result.Name, err = tx.Get(accountNameKey); err != nil {
  252. return
  253. }
  254. if result.RegisteredAt, err = tx.Get(registeredTimeKey); err != nil {
  255. return
  256. }
  257. if result.Credentials, err = tx.Get(credentialsKey); err != nil {
  258. return
  259. }
  260. if _, e = tx.Get(verifiedKey); e == nil {
  261. result.Verified = true
  262. }
  263. return
  264. }
  265. func (am *AccountManager) Unregister(account string) error {
  266. casefoldedAccount, err := CasefoldName(account)
  267. if err != nil {
  268. return errAccountDoesNotExist
  269. }
  270. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  271. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  272. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  273. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  274. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  275. var clients []*Client
  276. func() {
  277. var credText string
  278. am.serialCacheUpdateMutex.Lock()
  279. defer am.serialCacheUpdateMutex.Unlock()
  280. am.server.store.Update(func(tx *buntdb.Tx) error {
  281. tx.Delete(accountKey)
  282. tx.Delete(accountNameKey)
  283. tx.Delete(verifiedKey)
  284. tx.Delete(registeredTimeKey)
  285. credText, err = tx.Get(credentialsKey)
  286. tx.Delete(credentialsKey)
  287. return nil
  288. })
  289. if err == nil {
  290. var creds AccountCredentials
  291. if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
  292. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  293. am.server.store.Update(func(tx *buntdb.Tx) error {
  294. if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
  295. tx.Delete(certFPKey)
  296. }
  297. return nil
  298. })
  299. }
  300. }
  301. am.Lock()
  302. defer am.Unlock()
  303. clients = am.accountToClients[casefoldedAccount]
  304. delete(am.accountToClients, casefoldedAccount)
  305. // TODO when registration of multiple nicks is fully implemented,
  306. // save the nicks that were deleted from the store and delete them here:
  307. delete(am.nickToAccount, casefoldedAccount)
  308. }()
  309. for _, client := range clients {
  310. client.LogoutOfAccount()
  311. }
  312. return nil
  313. }
  314. func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
  315. if client.certfp == "" {
  316. return errAccountInvalidCredentials
  317. }
  318. var account string
  319. var rawAccount rawClientAccount
  320. certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
  321. err := am.server.store.Update(func(tx *buntdb.Tx) error {
  322. var err error
  323. account, _ = tx.Get(certFPKey)
  324. if account == "" {
  325. return errAccountInvalidCredentials
  326. }
  327. rawAccount, err = am.loadRawAccount(tx, account)
  328. if err != nil || !rawAccount.Verified {
  329. return errAccountUnverified
  330. }
  331. return nil
  332. })
  333. if err != nil {
  334. return err
  335. }
  336. // ok, we found an account corresponding to their certificate
  337. am.Login(client, rawAccount.Name)
  338. return nil
  339. }
  340. func (am *AccountManager) Login(client *Client, account string) {
  341. client.LoginToAccount(account)
  342. casefoldedAccount, _ := CasefoldName(account)
  343. am.Lock()
  344. defer am.Unlock()
  345. am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
  346. }
  347. func (am *AccountManager) Logout(client *Client) {
  348. casefoldedAccount := client.Account()
  349. if casefoldedAccount == "" || casefoldedAccount == "*" {
  350. return
  351. }
  352. client.LogoutOfAccount()
  353. am.Lock()
  354. defer am.Unlock()
  355. if client.LoggedIntoAccount() {
  356. return
  357. }
  358. clients := am.accountToClients[casefoldedAccount]
  359. if len(clients) <= 1 {
  360. delete(am.accountToClients, casefoldedAccount)
  361. return
  362. }
  363. remainingClients := make([]*Client, len(clients)-1)
  364. remainingPos := 0
  365. for currentPos := 0; currentPos < len(clients); currentPos++ {
  366. if clients[currentPos] != client {
  367. remainingClients[remainingPos] = clients[currentPos]
  368. remainingPos++
  369. }
  370. }
  371. am.accountToClients[casefoldedAccount] = remainingClients
  372. return
  373. }
  374. var (
  375. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  376. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  377. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
  378. "PLAIN": authPlainHandler,
  379. "EXTERNAL": authExternalHandler,
  380. }
  381. )
  382. // AccountCredentials stores the various methods for verifying accounts.
  383. type AccountCredentials struct {
  384. PassphraseSalt []byte
  385. PassphraseHash []byte
  386. Certificate string // fingerprint
  387. }
  388. // ClientAccount represents a user account.
  389. type ClientAccount struct {
  390. // Name of the account.
  391. Name string
  392. // RegisteredAt represents the time that the account was registered.
  393. RegisteredAt time.Time
  394. Credentials AccountCredentials
  395. Verified bool
  396. }
  397. // convenience for passing around raw serialized account data
  398. type rawClientAccount struct {
  399. Name string
  400. RegisteredAt string
  401. Credentials string
  402. Verified bool
  403. }
  404. // LoginToAccount logs the client into the given account.
  405. func (client *Client) LoginToAccount(account string) {
  406. casefoldedAccount, err := CasefoldName(account)
  407. if err != nil {
  408. return
  409. }
  410. if client.Account() == casefoldedAccount {
  411. // already logged into this acct, no changing necessary
  412. return
  413. }
  414. client.SetAccountName(casefoldedAccount)
  415. client.nickTimer.Touch()
  416. client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]"), client.nickMaskString, casefoldedAccount))
  417. //TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
  418. }
  419. // LogoutOfAccount logs the client out of their current account.
  420. func (client *Client) LogoutOfAccount() {
  421. if client.Account() == "" {
  422. // already logged out
  423. return
  424. }
  425. client.SetAccountName("")
  426. client.nickTimer.Touch()
  427. // dispatch account-notify
  428. for friend := range client.Friends(caps.AccountNotify) {
  429. friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
  430. }
  431. }
  432. // successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
  433. func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
  434. rb.Add(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.AccountName(), fmt.Sprintf("You are now logged in as %s", client.AccountName()))
  435. rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
  436. // dispatch account-notify
  437. for friend := range client.Friends(caps.AccountNotify) {
  438. friend.Send(nil, client.nickMaskString, "ACCOUNT", client.AccountName())
  439. }
  440. }