|
@@ -29,7 +29,7 @@ const (
|
29
|
29
|
keyAccountRegTime = "account.registered.time %s"
|
30
|
30
|
keyAccountCredentials = "account.credentials %s"
|
31
|
31
|
keyAccountAdditionalNicks = "account.additionalnicks %s"
|
32
|
|
- keyAccountEnforcement = "account.customenforcement %s"
|
|
32
|
+ keyAccountSettings = "account.settings %s"
|
33
|
33
|
keyAccountVHost = "account.vhost %s"
|
34
|
34
|
keyCertToAccount = "account.creds.certfp %s"
|
35
|
35
|
keyAccountChannels = "account.channels %s"
|
|
@@ -55,14 +55,14 @@ type AccountManager struct {
|
55
|
55
|
accountToClients map[string][]*Client
|
56
|
56
|
nickToAccount map[string]string
|
57
|
57
|
skeletonToAccount map[string]string
|
58
|
|
- accountToMethod map[string]NickReservationMethod
|
|
58
|
+ accountToMethod map[string]NickEnforcementMethod
|
59
|
59
|
}
|
60
|
60
|
|
61
|
61
|
func (am *AccountManager) Initialize(server *Server) {
|
62
|
62
|
am.accountToClients = make(map[string][]*Client)
|
63
|
63
|
am.nickToAccount = make(map[string]string)
|
64
|
64
|
am.skeletonToAccount = make(map[string]string)
|
65
|
|
- am.accountToMethod = make(map[string]NickReservationMethod)
|
|
65
|
+ am.accountToMethod = make(map[string]NickEnforcementMethod)
|
66
|
66
|
am.server = server
|
67
|
67
|
|
68
|
68
|
am.buildNickToAccountIndex()
|
|
@@ -76,7 +76,7 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
76
|
76
|
|
77
|
77
|
nickToAccount := make(map[string]string)
|
78
|
78
|
skeletonToAccount := make(map[string]string)
|
79
|
|
- accountToMethod := make(map[string]NickReservationMethod)
|
|
79
|
+ accountToMethod := make(map[string]NickEnforcementMethod)
|
80
|
80
|
existsPrefix := fmt.Sprintf(keyAccountExists, "")
|
81
|
81
|
|
82
|
82
|
am.serialCacheUpdateMutex.Lock()
|
|
@@ -109,12 +109,16 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
109
|
109
|
}
|
110
|
110
|
}
|
111
|
111
|
|
112
|
|
- if methodStr, err := tx.Get(fmt.Sprintf(keyAccountEnforcement, account)); err == nil {
|
113
|
|
- method, err := nickReservationFromString(methodStr)
|
114
|
|
- if err == nil {
|
115
|
|
- accountToMethod[account] = method
|
|
112
|
+ if rawPrefs, err := tx.Get(fmt.Sprintf(keyAccountSettings, account)); err == nil {
|
|
113
|
+ var prefs AccountSettings
|
|
114
|
+ err := json.Unmarshal([]byte(rawPrefs), &prefs)
|
|
115
|
+ if err == nil && prefs.NickEnforcement != NickEnforcementOptional {
|
|
116
|
+ accountToMethod[account] = prefs.NickEnforcement
|
|
117
|
+ } else {
|
|
118
|
+ am.server.logger.Error("internal", "corrupt account creds", account)
|
116
|
119
|
}
|
117
|
120
|
}
|
|
121
|
+
|
118
|
122
|
return true
|
119
|
123
|
})
|
120
|
124
|
return err
|
|
@@ -180,36 +184,44 @@ func (am *AccountManager) NickToAccount(nick string) string {
|
180
|
184
|
return am.nickToAccount[cfnick]
|
181
|
185
|
}
|
182
|
186
|
|
|
187
|
+// given an account, combine stored enforcement method with the config settings
|
|
188
|
+// to compute the actual enforcement method
|
|
189
|
+func configuredEnforcementMethod(config *Config, storedMethod NickEnforcementMethod) (result NickEnforcementMethod) {
|
|
190
|
+ if !config.Accounts.NickReservation.Enabled {
|
|
191
|
+ return NickEnforcementNone
|
|
192
|
+ }
|
|
193
|
+ result = storedMethod
|
|
194
|
+ // if they don't have a custom setting, or customization is disabled, use the default
|
|
195
|
+ if result == NickEnforcementOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
|
|
196
|
+ result = config.Accounts.NickReservation.Method
|
|
197
|
+ }
|
|
198
|
+ if result == NickEnforcementOptional {
|
|
199
|
+ // enforcement was explicitly enabled neither in the config or by the user
|
|
200
|
+ result = NickEnforcementNone
|
|
201
|
+ }
|
|
202
|
+ return
|
|
203
|
+}
|
|
204
|
+
|
183
|
205
|
// Given a nick, looks up the account that owns it and the method (none/timeout/strict)
|
184
|
206
|
// used to enforce ownership.
|
185
|
|
-func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickReservationMethod) {
|
|
207
|
+func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickEnforcementMethod) {
|
186
|
208
|
config := am.server.Config()
|
187
|
209
|
if !config.Accounts.NickReservation.Enabled {
|
188
|
|
- return "", NickReservationNone
|
|
210
|
+ return "", NickEnforcementNone
|
189
|
211
|
}
|
190
|
212
|
|
191
|
213
|
am.RLock()
|
192
|
214
|
defer am.RUnlock()
|
193
|
215
|
|
194
|
|
- // given an account, combine stored enforcement method with the config settings
|
195
|
|
- // to compute the actual enforcement method
|
196
|
|
- finalEnforcementMethod := func(account_ string) (result NickReservationMethod) {
|
197
|
|
- result = am.accountToMethod[account_]
|
198
|
|
- // if they don't have a custom setting, or customization is disabled, use the default
|
199
|
|
- if result == NickReservationOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
|
200
|
|
- result = config.Accounts.NickReservation.Method
|
201
|
|
- }
|
202
|
|
- if result == NickReservationOptional {
|
203
|
|
- // enforcement was explicitly enabled neither in the config or by the user
|
204
|
|
- result = NickReservationNone
|
205
|
|
- }
|
206
|
|
- return
|
|
216
|
+ finalEnforcementMethod := func(account_ string) (result NickEnforcementMethod) {
|
|
217
|
+ storedMethod := am.accountToMethod[account_]
|
|
218
|
+ return configuredEnforcementMethod(config, storedMethod)
|
207
|
219
|
}
|
208
|
220
|
|
209
|
221
|
nickAccount := am.nickToAccount[cfnick]
|
210
|
222
|
skelAccount := am.skeletonToAccount[skeleton]
|
211
|
223
|
if nickAccount == "" && skelAccount == "" {
|
212
|
|
- return "", NickReservationNone
|
|
224
|
+ return "", NickEnforcementNone
|
213
|
225
|
} else if nickAccount != "" && (skelAccount == nickAccount || skelAccount == "") {
|
214
|
226
|
return nickAccount, finalEnforcementMethod(nickAccount)
|
215
|
227
|
} else if skelAccount != "" && nickAccount == "" {
|
|
@@ -220,75 +232,47 @@ func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account st
|
220
|
232
|
nickMethod := finalEnforcementMethod(nickAccount)
|
221
|
233
|
skelMethod := finalEnforcementMethod(skelAccount)
|
222
|
234
|
switch {
|
223
|
|
- case skelMethod == NickReservationNone:
|
|
235
|
+ case skelMethod == NickEnforcementNone:
|
224
|
236
|
return nickAccount, nickMethod
|
225
|
|
- case nickMethod == NickReservationNone:
|
|
237
|
+ case nickMethod == NickEnforcementNone:
|
226
|
238
|
return skelAccount, skelMethod
|
227
|
239
|
default:
|
228
|
240
|
// nobody can use this nick
|
229
|
|
- return "!", NickReservationStrict
|
|
241
|
+ return "!", NickEnforcementStrict
|
230
|
242
|
}
|
231
|
243
|
}
|
232
|
244
|
}
|
233
|
245
|
|
234
|
|
-func (am *AccountManager) BouncerAllowed(account string, session *Session) bool {
|
235
|
|
- // TODO stub
|
236
|
|
- config := am.server.Config()
|
237
|
|
- if !config.Accounts.Bouncer.Enabled {
|
238
|
|
- return false
|
239
|
|
- }
|
240
|
|
- if config.Accounts.Bouncer.AllowedByDefault {
|
241
|
|
- return true
|
242
|
|
- }
|
243
|
|
- return session != nil && session.capabilities.Has(caps.Bouncer)
|
244
|
|
-}
|
245
|
|
-
|
246
|
|
-// Looks up the enforcement method stored in the database for an account
|
247
|
|
-// (typically you want EnforcementStatus instead, which respects the config)
|
248
|
|
-func (am *AccountManager) getStoredEnforcementStatus(account string) string {
|
249
|
|
- am.RLock()
|
250
|
|
- defer am.RUnlock()
|
251
|
|
- return nickReservationToString(am.accountToMethod[account])
|
252
|
|
-}
|
253
|
|
-
|
254
|
246
|
// Sets a custom enforcement method for an account and stores it in the database.
|
255
|
|
-func (am *AccountManager) SetEnforcementStatus(account string, method NickReservationMethod) (err error) {
|
|
247
|
+func (am *AccountManager) SetEnforcementStatus(account string, method NickEnforcementMethod) (finalSettings AccountSettings, err error) {
|
256
|
248
|
config := am.server.Config()
|
257
|
249
|
if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
|
258
|
|
- return errFeatureDisabled
|
|
250
|
+ err = errFeatureDisabled
|
|
251
|
+ return
|
259
|
252
|
}
|
260
|
253
|
|
261
|
|
- var serialized string
|
262
|
|
- if method == NickReservationOptional {
|
263
|
|
- serialized = "" // normally this is "default", but we're going to delete the key
|
264
|
|
- } else {
|
265
|
|
- serialized = nickReservationToString(method)
|
|
254
|
+ setter := func(in AccountSettings) (out AccountSettings, err error) {
|
|
255
|
+ out = in
|
|
256
|
+ out.NickEnforcement = method
|
|
257
|
+ return out, nil
|
266
|
258
|
}
|
267
|
259
|
|
268
|
|
- key := fmt.Sprintf(keyAccountEnforcement, account)
|
|
260
|
+ _, err = am.ModifyAccountSettings(account, setter)
|
|
261
|
+ if err != nil {
|
|
262
|
+ return
|
|
263
|
+ }
|
269
|
264
|
|
|
265
|
+ // this update of the data plane is racey, but it's probably fine
|
270
|
266
|
am.Lock()
|
271
|
267
|
defer am.Unlock()
|
272
|
268
|
|
273
|
|
- currentMethod := am.accountToMethod[account]
|
274
|
|
- if method != currentMethod {
|
275
|
|
- if method == NickReservationOptional {
|
276
|
|
- delete(am.accountToMethod, account)
|
277
|
|
- } else {
|
278
|
|
- am.accountToMethod[account] = method
|
279
|
|
- }
|
280
|
|
-
|
281
|
|
- return am.server.store.Update(func(tx *buntdb.Tx) (err error) {
|
282
|
|
- if serialized != "" {
|
283
|
|
- _, _, err = tx.Set(key, nickReservationToString(method), nil)
|
284
|
|
- } else {
|
285
|
|
- _, err = tx.Delete(key)
|
286
|
|
- }
|
287
|
|
- return
|
288
|
|
- })
|
|
269
|
+ if method == NickEnforcementOptional {
|
|
270
|
+ delete(am.accountToMethod, account)
|
|
271
|
+ } else {
|
|
272
|
+ am.accountToMethod[account] = method
|
289
|
273
|
}
|
290
|
274
|
|
291
|
|
- return nil
|
|
275
|
+ return
|
292
|
276
|
}
|
293
|
277
|
|
294
|
278
|
func (am *AccountManager) AccountToClients(account string) (result []*Client) {
|
|
@@ -813,6 +797,12 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount) (result Cl
|
813
|
797
|
// pretend they have no vhost and move on
|
814
|
798
|
}
|
815
|
799
|
}
|
|
800
|
+ if raw.Settings != "" {
|
|
801
|
+ e := json.Unmarshal([]byte(raw.Settings), &result.Settings)
|
|
802
|
+ if e != nil {
|
|
803
|
+ am.server.logger.Warning("internal", "could not unmarshal settings for account", result.Name, e.Error())
|
|
804
|
+ }
|
|
805
|
+ }
|
816
|
806
|
return
|
817
|
807
|
}
|
818
|
808
|
|
|
@@ -825,6 +815,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
825
|
815
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
826
|
816
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
827
|
817
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
|
818
|
+ settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
828
|
819
|
|
829
|
820
|
_, e := tx.Get(accountKey)
|
830
|
821
|
if e == buntdb.ErrNotFound {
|
|
@@ -838,6 +829,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
838
|
829
|
result.Callback, _ = tx.Get(callbackKey)
|
839
|
830
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
840
|
831
|
result.VHost, _ = tx.Get(vhostKey)
|
|
832
|
+ result.Settings, _ = tx.Get(settingsKey)
|
841
|
833
|
|
842
|
834
|
if _, e = tx.Get(verifiedKey); e == nil {
|
843
|
835
|
result.Verified = true
|
|
@@ -861,7 +853,7 @@ func (am *AccountManager) Unregister(account string) error {
|
861
|
853
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
862
|
854
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
863
|
855
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
864
|
|
- enforcementKey := fmt.Sprintf(keyAccountEnforcement, casefoldedAccount)
|
|
856
|
+ settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
865
|
857
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
866
|
858
|
vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
|
867
|
859
|
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
|
@@ -892,7 +884,7 @@ func (am *AccountManager) Unregister(account string) error {
|
892
|
884
|
tx.Delete(registeredTimeKey)
|
893
|
885
|
tx.Delete(callbackKey)
|
894
|
886
|
tx.Delete(verificationCodeKey)
|
895
|
|
- tx.Delete(enforcementKey)
|
|
887
|
+ tx.Delete(settingsKey)
|
896
|
888
|
rawNicks, _ = tx.Get(nicksKey)
|
897
|
889
|
tx.Delete(nicksKey)
|
898
|
890
|
credText, err = tx.Get(credentialsKey)
|
|
@@ -980,19 +972,13 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
980
|
972
|
}
|
981
|
973
|
|
982
|
974
|
var account string
|
983
|
|
- var rawAccount rawClientAccount
|
984
|
975
|
certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
|
985
|
976
|
|
986
|
|
- err := am.server.store.Update(func(tx *buntdb.Tx) error {
|
987
|
|
- var err error
|
|
977
|
+ err := am.server.store.View(func(tx *buntdb.Tx) error {
|
988
|
978
|
account, _ = tx.Get(certFPKey)
|
989
|
979
|
if account == "" {
|
990
|
980
|
return errAccountInvalidCredentials
|
991
|
981
|
}
|
992
|
|
- rawAccount, err = am.loadRawAccount(tx, account)
|
993
|
|
- if err != nil || !rawAccount.Verified {
|
994
|
|
- return errAccountUnverified
|
995
|
|
- }
|
996
|
982
|
return nil
|
997
|
983
|
})
|
998
|
984
|
|
|
@@ -1001,14 +987,57 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
1001
|
987
|
}
|
1002
|
988
|
|
1003
|
989
|
// ok, we found an account corresponding to their certificate
|
1004
|
|
- clientAccount, err := am.deserializeRawAccount(rawAccount)
|
|
990
|
+ clientAccount, err := am.LoadAccount(account)
|
1005
|
991
|
if err != nil {
|
1006
|
992
|
return err
|
|
993
|
+ } else if !clientAccount.Verified {
|
|
994
|
+ return errAccountUnverified
|
1007
|
995
|
}
|
1008
|
996
|
am.Login(client, clientAccount)
|
1009
|
997
|
return nil
|
1010
|
998
|
}
|
1011
|
999
|
|
|
1000
|
+type settingsMunger func(input AccountSettings) (output AccountSettings, err error)
|
|
1001
|
+
|
|
1002
|
+func (am *AccountManager) ModifyAccountSettings(account string, munger settingsMunger) (newSettings AccountSettings, err error) {
|
|
1003
|
+ casefoldedAccount, err := CasefoldName(account)
|
|
1004
|
+ if err != nil {
|
|
1005
|
+ return newSettings, errAccountDoesNotExist
|
|
1006
|
+ }
|
|
1007
|
+ // TODO implement this in general via a compare-and-swap API
|
|
1008
|
+ accountData, err := am.LoadAccount(casefoldedAccount)
|
|
1009
|
+ if err != nil {
|
|
1010
|
+ return
|
|
1011
|
+ } else if !accountData.Verified {
|
|
1012
|
+ return newSettings, errAccountUnverified
|
|
1013
|
+ }
|
|
1014
|
+ newSettings, err = munger(accountData.Settings)
|
|
1015
|
+ if err != nil {
|
|
1016
|
+ return
|
|
1017
|
+ }
|
|
1018
|
+ text, err := json.Marshal(newSettings)
|
|
1019
|
+ if err != nil {
|
|
1020
|
+ return
|
|
1021
|
+ }
|
|
1022
|
+ key := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
|
1023
|
+ serializedValue := string(text)
|
|
1024
|
+ err = am.server.store.Update(func(tx *buntdb.Tx) (err error) {
|
|
1025
|
+ _, _, err = tx.Set(key, serializedValue, nil)
|
|
1026
|
+ return
|
|
1027
|
+ })
|
|
1028
|
+ if err != nil {
|
|
1029
|
+ err = errAccountUpdateFailed
|
|
1030
|
+ return
|
|
1031
|
+ }
|
|
1032
|
+ // success, push new settings into the client objects
|
|
1033
|
+ am.Lock()
|
|
1034
|
+ defer am.Unlock()
|
|
1035
|
+ for _, client := range am.accountToClients[casefoldedAccount] {
|
|
1036
|
+ client.SetAccountSettings(newSettings)
|
|
1037
|
+ }
|
|
1038
|
+ return
|
|
1039
|
+}
|
|
1040
|
+
|
1012
|
1041
|
// represents someone's status in hostserv
|
1013
|
1042
|
type VHostInfo struct {
|
1014
|
1043
|
ApprovedVHost string
|
|
@@ -1237,6 +1266,9 @@ func (am *AccountManager) Login(client *Client, account ClientAccount) {
|
1237
|
1266
|
am.Lock()
|
1238
|
1267
|
defer am.Unlock()
|
1239
|
1268
|
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
|
1269
|
+ for _, client := range am.accountToClients[casefoldedAccount] {
|
|
1270
|
+ client.SetAccountSettings(account.Settings)
|
|
1271
|
+ }
|
1240
|
1272
|
}
|
1241
|
1273
|
|
1242
|
1274
|
func (am *AccountManager) Logout(client *Client) {
|
|
@@ -1283,6 +1315,21 @@ type AccountCredentials struct {
|
1283
|
1315
|
Certificate string // fingerprint
|
1284
|
1316
|
}
|
1285
|
1317
|
|
|
1318
|
+type BouncerAllowedSetting int
|
|
1319
|
+
|
|
1320
|
+const (
|
|
1321
|
+ BouncerAllowedServerDefault BouncerAllowedSetting = iota
|
|
1322
|
+ BouncerDisallowedByUser
|
|
1323
|
+ BouncerAllowedByUser
|
|
1324
|
+)
|
|
1325
|
+
|
|
1326
|
+type AccountSettings struct {
|
|
1327
|
+ AutoreplayLines *int
|
|
1328
|
+ NickEnforcement NickEnforcementMethod
|
|
1329
|
+ AllowBouncer BouncerAllowedSetting
|
|
1330
|
+ AutoreplayJoins bool
|
|
1331
|
+}
|
|
1332
|
+
|
1286
|
1333
|
// ClientAccount represents a user account.
|
1287
|
1334
|
type ClientAccount struct {
|
1288
|
1335
|
// Name of the account.
|
|
@@ -1293,6 +1340,7 @@ type ClientAccount struct {
|
1293
|
1340
|
Verified bool
|
1294
|
1341
|
AdditionalNicks []string
|
1295
|
1342
|
VHost VHostInfo
|
|
1343
|
+ Settings AccountSettings
|
1296
|
1344
|
}
|
1297
|
1345
|
|
1298
|
1346
|
// convenience for passing around raw serialized account data
|
|
@@ -1304,6 +1352,7 @@ type rawClientAccount struct {
|
1304
|
1352
|
Verified bool
|
1305
|
1353
|
AdditionalNicks string
|
1306
|
1354
|
VHost string
|
|
1355
|
+ Settings string
|
1307
|
1356
|
}
|
1308
|
1357
|
|
1309
|
1358
|
// logoutOfAccount logs the client out of their current account.
|