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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "crypto/rand"
  6. "crypto/subtle"
  7. "encoding/hex"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "net/smtp"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. "github.com/oragono/oragono/irc/caps"
  17. "github.com/oragono/oragono/irc/passwd"
  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. keyCertToAccount = "account.creds.certfp %s"
  29. )
  30. // everything about accounts is persistent; therefore, the database is the authoritative
  31. // source of truth for all account information. anything on the heap is just a cache
  32. type AccountManager struct {
  33. sync.RWMutex // tier 2
  34. serialCacheUpdateMutex sync.Mutex // tier 3
  35. server *Server
  36. // track clients logged in to accounts
  37. accountToClients map[string][]*Client
  38. nickToAccount map[string]string
  39. }
  40. func NewAccountManager(server *Server) *AccountManager {
  41. am := AccountManager{
  42. accountToClients: make(map[string][]*Client),
  43. nickToAccount: make(map[string]string),
  44. server: server,
  45. }
  46. am.buildNickToAccountIndex()
  47. return &am
  48. }
  49. func (am *AccountManager) buildNickToAccountIndex() {
  50. if !am.server.AccountConfig().NickReservation.Enabled {
  51. return
  52. }
  53. result := make(map[string]string)
  54. existsPrefix := fmt.Sprintf(keyAccountExists, "")
  55. am.serialCacheUpdateMutex.Lock()
  56. defer am.serialCacheUpdateMutex.Unlock()
  57. err := am.server.store.View(func(tx *buntdb.Tx) error {
  58. err := tx.AscendGreaterOrEqual("", existsPrefix, func(key, value string) bool {
  59. if !strings.HasPrefix(key, existsPrefix) {
  60. return false
  61. }
  62. accountName := strings.TrimPrefix(key, existsPrefix)
  63. if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
  64. result[accountName] = accountName
  65. }
  66. return true
  67. })
  68. return err
  69. })
  70. if err != nil {
  71. am.server.logger.Error("internal", fmt.Sprintf("couldn't read reserved nicks: %v", err))
  72. } else {
  73. am.Lock()
  74. am.nickToAccount = result
  75. am.Unlock()
  76. }
  77. return
  78. }
  79. func (am *AccountManager) NickToAccount(cfnick string) string {
  80. am.RLock()
  81. defer am.RUnlock()
  82. return am.nickToAccount[cfnick]
  83. }
  84. func (am *AccountManager) Register(client *Client, account string, callbackNamespace string, callbackValue string, passphrase string, certfp string) error {
  85. casefoldedAccount, err := CasefoldName(account)
  86. if err != nil || account == "" || account == "*" {
  87. return errAccountCreation
  88. }
  89. // can't register a guest nickname
  90. renamePrefix := strings.ToLower(am.server.AccountConfig().NickReservation.RenamePrefix)
  91. if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) {
  92. return errAccountAlreadyRegistered
  93. }
  94. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  95. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  96. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  97. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  98. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  99. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  100. certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
  101. var creds AccountCredentials
  102. // always set passphrase salt
  103. creds.PassphraseSalt, err = passwd.NewSalt()
  104. if err != nil {
  105. return errAccountCreation
  106. }
  107. // it's fine if this is empty, that just means no certificate is authorized
  108. creds.Certificate = certfp
  109. if passphrase != "" {
  110. creds.PassphraseHash, err = am.server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
  111. if err != nil {
  112. am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
  113. return errAccountCreation
  114. }
  115. }
  116. credText, err := json.Marshal(creds)
  117. if err != nil {
  118. am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
  119. return errAccountCreation
  120. }
  121. credStr := string(credText)
  122. registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
  123. callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
  124. var setOptions *buntdb.SetOptions
  125. ttl := am.server.AccountConfig().Registration.VerifyTimeout
  126. if ttl != 0 {
  127. setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
  128. }
  129. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  130. _, err := am.loadRawAccount(tx, casefoldedAccount)
  131. if err != errAccountDoesNotExist {
  132. return errAccountAlreadyRegistered
  133. }
  134. if certfp != "" {
  135. // make sure certfp doesn't already exist because that'd be silly
  136. _, err := tx.Get(certFPKey)
  137. if err != buntdb.ErrNotFound {
  138. return errCertfpAlreadyExists
  139. }
  140. }
  141. tx.Set(accountKey, "1", setOptions)
  142. tx.Set(accountNameKey, account, setOptions)
  143. tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
  144. tx.Set(credentialsKey, credStr, setOptions)
  145. tx.Set(callbackKey, callbackSpec, setOptions)
  146. if certfp != "" {
  147. tx.Set(certFPKey, casefoldedAccount, setOptions)
  148. }
  149. return nil
  150. })
  151. if err != nil {
  152. return err
  153. }
  154. code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue)
  155. if err != nil {
  156. am.Unregister(casefoldedAccount)
  157. return errCallbackFailed
  158. } else {
  159. return am.server.store.Update(func(tx *buntdb.Tx) error {
  160. _, _, err = tx.Set(verificationCodeKey, code, setOptions)
  161. return err
  162. })
  163. }
  164. }
  165. func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
  166. if callbackNamespace == "*" || callbackNamespace == "none" {
  167. return "", nil
  168. } else if callbackNamespace == "mailto" {
  169. return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue)
  170. } else {
  171. return "", errors.New(fmt.Sprintf("Callback not implemented: %s", callbackNamespace))
  172. }
  173. }
  174. func (am *AccountManager) dispatchMailtoCallback(client *Client, casefoldedAccount string, callbackValue string) (code string, err error) {
  175. config := am.server.AccountConfig().Registration.Callbacks.Mailto
  176. buf := make([]byte, 16)
  177. rand.Read(buf)
  178. code = hex.EncodeToString(buf)
  179. subject := config.VerifyMessageSubject
  180. if subject == "" {
  181. subject = fmt.Sprintf(client.t("Verify your account on %s"), am.server.name)
  182. }
  183. messageStrings := []string{
  184. fmt.Sprintf("From: %s\r\n", config.Sender),
  185. fmt.Sprintf("To: %s\r\n", callbackValue),
  186. fmt.Sprintf("Subject: %s\r\n", subject),
  187. "\r\n", // end headers, begin message body
  188. fmt.Sprintf(client.t("Account: %s"), casefoldedAccount) + "\r\n",
  189. fmt.Sprintf(client.t("Verification code: %s"), code) + "\r\n",
  190. "\r\n",
  191. client.t("To verify your account, issue one of these commands:") + "\r\n",
  192. fmt.Sprintf("/ACC VERIFY %s %s", casefoldedAccount, code) + "\r\n",
  193. fmt.Sprintf("/MSG NickServ VERIFY %s %s", casefoldedAccount, code) + "\r\n",
  194. }
  195. var message []byte
  196. for i := 0; i < len(messageStrings); i++ {
  197. message = append(message, []byte(messageStrings[i])...)
  198. }
  199. addr := fmt.Sprintf("%s:%d", config.Server, config.Port)
  200. var auth smtp.Auth
  201. if config.Username != "" && config.Password != "" {
  202. auth = smtp.PlainAuth("", config.Username, config.Password, config.Server)
  203. }
  204. // TODO: this will never send the password in plaintext over a nonlocal link,
  205. // but it might send the email in plaintext, regardless of the value of
  206. // config.TLS.InsecureSkipVerify
  207. err = smtp.SendMail(addr, auth, config.Sender, []string{callbackValue}, message)
  208. if err != nil {
  209. am.server.logger.Error("internal", fmt.Sprintf("Failed to dispatch e-mail: %v", err))
  210. }
  211. return
  212. }
  213. func (am *AccountManager) Verify(client *Client, account string, code string) error {
  214. casefoldedAccount, err := CasefoldName(account)
  215. if err != nil || account == "" || account == "*" {
  216. return errAccountVerificationFailed
  217. }
  218. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  219. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  220. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  221. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  222. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  223. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  224. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  225. var raw rawClientAccount
  226. func() {
  227. am.serialCacheUpdateMutex.Lock()
  228. defer am.serialCacheUpdateMutex.Unlock()
  229. err = am.server.store.Update(func(tx *buntdb.Tx) error {
  230. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  231. if err == errAccountDoesNotExist {
  232. return errAccountDoesNotExist
  233. } else if err != nil {
  234. return errAccountVerificationFailed
  235. } else if raw.Verified {
  236. return errAccountAlreadyVerified
  237. }
  238. // actually verify the code
  239. // a stored code of "" means a none callback / no code required
  240. success := false
  241. storedCode, err := tx.Get(verificationCodeKey)
  242. if err == nil {
  243. // this is probably unnecessary
  244. if storedCode == "" || subtle.ConstantTimeCompare([]byte(code), []byte(storedCode)) == 1 {
  245. success = true
  246. }
  247. }
  248. if !success {
  249. return errAccountVerificationInvalidCode
  250. }
  251. // verify the account
  252. tx.Set(verifiedKey, "1", nil)
  253. // don't need the code anymore
  254. tx.Delete(verificationCodeKey)
  255. // re-set all other keys, removing the TTL
  256. tx.Set(accountKey, "1", nil)
  257. tx.Set(accountNameKey, raw.Name, nil)
  258. tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
  259. tx.Set(callbackKey, raw.Callback, nil)
  260. tx.Set(credentialsKey, raw.Credentials, nil)
  261. var creds AccountCredentials
  262. // XXX we shouldn't do (de)serialization inside the txn,
  263. // but this is like 2 usec on my system
  264. json.Unmarshal([]byte(raw.Credentials), &creds)
  265. if creds.Certificate != "" {
  266. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  267. tx.Set(certFPKey, casefoldedAccount, nil)
  268. }
  269. return nil
  270. })
  271. if err == nil {
  272. am.Lock()
  273. am.nickToAccount[casefoldedAccount] = casefoldedAccount
  274. am.Unlock()
  275. }
  276. }()
  277. if err != nil {
  278. return err
  279. }
  280. am.Login(client, raw.Name)
  281. return nil
  282. }
  283. func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
  284. casefoldedAccount, err := CasefoldName(accountName)
  285. if err != nil {
  286. return errAccountDoesNotExist
  287. }
  288. account, err := am.LoadAccount(casefoldedAccount)
  289. if err != nil {
  290. return err
  291. }
  292. if !account.Verified {
  293. return errAccountUnverified
  294. }
  295. err = am.server.passwords.CompareHashAndPassword(
  296. account.Credentials.PassphraseHash, account.Credentials.PassphraseSalt, passphrase)
  297. if err != nil {
  298. return errAccountInvalidCredentials
  299. }
  300. am.Login(client, account.Name)
  301. return nil
  302. }
  303. func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
  304. var raw rawClientAccount
  305. am.server.store.View(func(tx *buntdb.Tx) error {
  306. raw, err = am.loadRawAccount(tx, casefoldedAccount)
  307. if err == buntdb.ErrNotFound {
  308. err = errAccountDoesNotExist
  309. }
  310. return nil
  311. })
  312. if err != nil {
  313. return
  314. }
  315. result.Name = raw.Name
  316. regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
  317. result.RegisteredAt = time.Unix(regTimeInt, 0)
  318. e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
  319. if e != nil {
  320. am.server.logger.Error("internal", fmt.Sprintf("could not unmarshal credentials: %v", e))
  321. err = errAccountDoesNotExist
  322. return
  323. }
  324. result.Verified = raw.Verified
  325. return
  326. }
  327. func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
  328. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  329. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  330. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  331. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  332. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  333. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  334. _, e := tx.Get(accountKey)
  335. if e == buntdb.ErrNotFound {
  336. err = errAccountDoesNotExist
  337. return
  338. }
  339. result.Name, _ = tx.Get(accountNameKey)
  340. result.RegisteredAt, _ = tx.Get(registeredTimeKey)
  341. result.Credentials, _ = tx.Get(credentialsKey)
  342. result.Callback, _ = tx.Get(callbackKey)
  343. if _, e = tx.Get(verifiedKey); e == nil {
  344. result.Verified = true
  345. }
  346. return
  347. }
  348. func (am *AccountManager) Unregister(account string) error {
  349. casefoldedAccount, err := CasefoldName(account)
  350. if err != nil {
  351. return errAccountDoesNotExist
  352. }
  353. accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
  354. accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
  355. registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
  356. credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
  357. callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
  358. verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
  359. verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
  360. var clients []*Client
  361. func() {
  362. var credText string
  363. am.serialCacheUpdateMutex.Lock()
  364. defer am.serialCacheUpdateMutex.Unlock()
  365. am.server.store.Update(func(tx *buntdb.Tx) error {
  366. tx.Delete(accountKey)
  367. tx.Delete(accountNameKey)
  368. tx.Delete(verifiedKey)
  369. tx.Delete(registeredTimeKey)
  370. tx.Delete(callbackKey)
  371. tx.Delete(verificationCodeKey)
  372. credText, err = tx.Get(credentialsKey)
  373. tx.Delete(credentialsKey)
  374. return nil
  375. })
  376. if err == nil {
  377. var creds AccountCredentials
  378. if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
  379. certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
  380. am.server.store.Update(func(tx *buntdb.Tx) error {
  381. if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
  382. tx.Delete(certFPKey)
  383. }
  384. return nil
  385. })
  386. }
  387. }
  388. am.Lock()
  389. defer am.Unlock()
  390. clients = am.accountToClients[casefoldedAccount]
  391. delete(am.accountToClients, casefoldedAccount)
  392. // TODO when registration of multiple nicks is fully implemented,
  393. // save the nicks that were deleted from the store and delete them here:
  394. delete(am.nickToAccount, casefoldedAccount)
  395. }()
  396. for _, client := range clients {
  397. client.LogoutOfAccount()
  398. }
  399. if err != nil {
  400. return errAccountDoesNotExist
  401. }
  402. return nil
  403. }
  404. func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
  405. if client.certfp == "" {
  406. return errAccountInvalidCredentials
  407. }
  408. var account string
  409. var rawAccount rawClientAccount
  410. certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
  411. err := am.server.store.Update(func(tx *buntdb.Tx) error {
  412. var err error
  413. account, _ = tx.Get(certFPKey)
  414. if account == "" {
  415. return errAccountInvalidCredentials
  416. }
  417. rawAccount, err = am.loadRawAccount(tx, account)
  418. if err != nil || !rawAccount.Verified {
  419. return errAccountUnverified
  420. }
  421. return nil
  422. })
  423. if err != nil {
  424. return err
  425. }
  426. // ok, we found an account corresponding to their certificate
  427. am.Login(client, rawAccount.Name)
  428. return nil
  429. }
  430. func (am *AccountManager) Login(client *Client, account string) {
  431. client.LoginToAccount(account)
  432. casefoldedAccount, _ := CasefoldName(account)
  433. am.Lock()
  434. defer am.Unlock()
  435. am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
  436. }
  437. func (am *AccountManager) Logout(client *Client) {
  438. casefoldedAccount := client.Account()
  439. if casefoldedAccount == "" || casefoldedAccount == "*" {
  440. return
  441. }
  442. client.LogoutOfAccount()
  443. am.Lock()
  444. defer am.Unlock()
  445. if client.LoggedIntoAccount() {
  446. return
  447. }
  448. clients := am.accountToClients[casefoldedAccount]
  449. if len(clients) <= 1 {
  450. delete(am.accountToClients, casefoldedAccount)
  451. return
  452. }
  453. remainingClients := make([]*Client, len(clients)-1)
  454. remainingPos := 0
  455. for currentPos := 0; currentPos < len(clients); currentPos++ {
  456. if clients[currentPos] != client {
  457. remainingClients[remainingPos] = clients[currentPos]
  458. remainingPos++
  459. }
  460. }
  461. am.accountToClients[casefoldedAccount] = remainingClients
  462. return
  463. }
  464. var (
  465. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  466. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  467. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
  468. "PLAIN": authPlainHandler,
  469. "EXTERNAL": authExternalHandler,
  470. }
  471. )
  472. // AccountCredentials stores the various methods for verifying accounts.
  473. type AccountCredentials struct {
  474. PassphraseSalt []byte
  475. PassphraseHash []byte
  476. Certificate string // fingerprint
  477. }
  478. // ClientAccount represents a user account.
  479. type ClientAccount struct {
  480. // Name of the account.
  481. Name string
  482. // RegisteredAt represents the time that the account was registered.
  483. RegisteredAt time.Time
  484. Credentials AccountCredentials
  485. Verified bool
  486. }
  487. // convenience for passing around raw serialized account data
  488. type rawClientAccount struct {
  489. Name string
  490. RegisteredAt string
  491. Credentials string
  492. Callback string
  493. Verified bool
  494. }
  495. // LoginToAccount logs the client into the given account.
  496. func (client *Client) LoginToAccount(account string) {
  497. changed := client.SetAccountName(account)
  498. if changed {
  499. client.nickTimer.Touch()
  500. }
  501. }
  502. // LogoutOfAccount logs the client out of their current account.
  503. func (client *Client) LogoutOfAccount() {
  504. if client.Account() == "" {
  505. // already logged out
  506. return
  507. }
  508. client.SetAccountName("")
  509. client.nickTimer.Touch()
  510. // dispatch account-notify
  511. // TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
  512. for friend := range client.Friends(caps.AccountNotify) {
  513. friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
  514. }
  515. }