Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. "strconv"
  9. "time"
  10. "github.com/goshuirc/irc-go/ircfmt"
  11. "github.com/oragono/oragono/irc/caps"
  12. "github.com/oragono/oragono/irc/sno"
  13. "github.com/tidwall/buntdb"
  14. )
  15. const (
  16. keyAccountExists = "account.exists %s"
  17. keyAccountVerified = "account.verified %s"
  18. keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
  19. keyAccountRegTime = "account.registered.time %s"
  20. keyAccountCredentials = "account.credentials %s"
  21. keyCertToAccount = "account.creds.certfp %s"
  22. )
  23. var (
  24. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  25. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  26. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte) bool{
  27. "PLAIN": authPlainHandler,
  28. "EXTERNAL": authExternalHandler,
  29. }
  30. // NoAccount is a placeholder which means that the user is not logged into an account.
  31. NoAccount = ClientAccount{
  32. Name: "*", // * is used until actual account name is set
  33. }
  34. // generic sasl fail error
  35. errSaslFail = errors.New("SASL failed")
  36. )
  37. // ClientAccount represents a user account.
  38. type ClientAccount struct {
  39. // Name of the account.
  40. Name string
  41. // RegisteredAt represents the time that the account was registered.
  42. RegisteredAt time.Time
  43. // Clients that are currently logged into this account (useful for notifications).
  44. Clients []*Client
  45. }
  46. // loadAccountCredentials loads an account's credentials from the store.
  47. func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
  48. credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
  49. if err != nil {
  50. return nil, err
  51. }
  52. var creds AccountCredentials
  53. err = json.Unmarshal([]byte(credText), &creds)
  54. if err != nil {
  55. return nil, err
  56. }
  57. return &creds, nil
  58. }
  59. // loadAccount loads an account from the store, note that the account must actually exist.
  60. func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
  61. name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
  62. regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
  63. regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
  64. accountInfo := ClientAccount{
  65. Name: name,
  66. RegisteredAt: time.Unix(regTimeInt, 0),
  67. Clients: []*Client{},
  68. }
  69. server.accounts[accountKey] = &accountInfo
  70. return &accountInfo
  71. }
  72. // LoginToAccount logs the client into the given account.
  73. func (client *Client) LoginToAccount(account *ClientAccount) {
  74. if client.account == account {
  75. // already logged into this acct, no changing necessary
  76. return
  77. } else if client.LoggedIntoAccount() {
  78. // logout of existing acct
  79. var newClientAccounts []*Client
  80. for _, c := range account.Clients {
  81. if c != client {
  82. newClientAccounts = append(newClientAccounts, c)
  83. }
  84. }
  85. account.Clients = newClientAccounts
  86. }
  87. account.Clients = append(account.Clients, client)
  88. client.account = account
  89. 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, account.Name))
  90. //TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
  91. }
  92. // LogoutOfAccount logs the client out of their current account.
  93. func (client *Client) LogoutOfAccount() {
  94. account := client.account
  95. if account == nil {
  96. // already logged out
  97. return
  98. }
  99. // logout of existing acct
  100. var newClientAccounts []*Client
  101. for _, c := range account.Clients {
  102. if c != client {
  103. newClientAccounts = append(newClientAccounts, c)
  104. }
  105. }
  106. account.Clients = newClientAccounts
  107. client.account = nil
  108. // dispatch account-notify
  109. for friend := range client.Friends(caps.AccountNotify) {
  110. friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
  111. }
  112. }
  113. // successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
  114. func (client *Client) successfulSaslAuth() {
  115. client.Send(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
  116. client.Send(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
  117. // dispatch account-notify
  118. for friend := range client.Friends(caps.AccountNotify) {
  119. friend.Send(nil, client.nickMaskString, "ACCOUNT", client.account.Name)
  120. }
  121. }