|
@@ -40,8 +40,9 @@ const (
|
40
|
40
|
keyAccountChannels = "account.channels %s" // channels registered to the account
|
41
|
41
|
keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined
|
42
|
42
|
keyAccountLastSeen = "account.lastseen %s"
|
43
|
|
- keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
44
|
|
- keyAccountRealname = "account.realname %s" // client realname stored as string
|
|
43
|
+ keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
|
44
|
+ keyAccountRealname = "account.realname %s" // client realname stored as string
|
|
45
|
+ keyAccountSuspended = "account.suspended %s" // client realname stored as string
|
45
|
46
|
|
46
|
47
|
maxCertfpsPerAccount = 5
|
47
|
48
|
)
|
|
@@ -117,7 +118,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
|
117
|
118
|
|
118
|
119
|
for _, accountName := range accounts {
|
119
|
120
|
account, err := am.LoadAccount(accountName)
|
120
|
|
- if err == nil && account.Verified &&
|
|
121
|
+ if err == nil && (account.Verified && account.Suspended == nil) &&
|
121
|
122
|
persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
|
122
|
123
|
am.server.AddAlwaysOnClient(
|
123
|
124
|
account,
|
|
@@ -1035,6 +1036,9 @@ func (am *AccountManager) checkPassphrase(accountName, passphrase string) (accou
|
1035
|
1036
|
if !account.Verified {
|
1036
|
1037
|
err = errAccountUnverified
|
1037
|
1038
|
return
|
|
1039
|
+ } else if account.Suspended != nil {
|
|
1040
|
+ err = errAccountSuspended
|
|
1041
|
+ return
|
1038
|
1042
|
}
|
1039
|
1043
|
|
1040
|
1044
|
switch account.Credentials.Version {
|
|
@@ -1230,6 +1234,15 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount, cfName str
|
1230
|
1234
|
am.server.logger.Warning("internal", "could not unmarshal settings for account", result.Name, e.Error())
|
1231
|
1235
|
}
|
1232
|
1236
|
}
|
|
1237
|
+ if raw.Suspended != "" {
|
|
1238
|
+ sus := new(AccountSuspension)
|
|
1239
|
+ e := json.Unmarshal([]byte(raw.Suspended), sus)
|
|
1240
|
+ if e != nil {
|
|
1241
|
+ am.server.logger.Error("internal", "corrupt suspension data", result.Name, e.Error())
|
|
1242
|
+ } else {
|
|
1243
|
+ result.Suspended = sus
|
|
1244
|
+ }
|
|
1245
|
+ }
|
1233
|
1246
|
return
|
1234
|
1247
|
}
|
1235
|
1248
|
|
|
@@ -1243,6 +1256,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
1243
|
1256
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
1244
|
1257
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
1245
|
1258
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
|
1259
|
+ suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
1246
|
1260
|
|
1247
|
1261
|
_, e := tx.Get(accountKey)
|
1248
|
1262
|
if e == buntdb.ErrNotFound {
|
|
@@ -1257,6 +1271,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
1257
|
1271
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
1258
|
1272
|
result.VHost, _ = tx.Get(vhostKey)
|
1259
|
1273
|
result.Settings, _ = tx.Get(settingsKey)
|
|
1274
|
+ result.Suspended, _ = tx.Get(suspendedKey)
|
1260
|
1275
|
|
1261
|
1276
|
if _, e = tx.Get(verifiedKey); e == nil {
|
1262
|
1277
|
result.Verified = true
|
|
@@ -1265,20 +1280,44 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
1265
|
1280
|
return
|
1266
|
1281
|
}
|
1267
|
1282
|
|
1268
|
|
-func (am *AccountManager) Suspend(accountName string) (err error) {
|
|
1283
|
+type AccountSuspension struct {
|
|
1284
|
+ AccountName string `json:"AccountName,omitempty"`
|
|
1285
|
+ TimeCreated time.Time
|
|
1286
|
+ Duration time.Duration
|
|
1287
|
+ OperName string
|
|
1288
|
+ Reason string
|
|
1289
|
+}
|
|
1290
|
+
|
|
1291
|
+func (am *AccountManager) Suspend(accountName string, duration time.Duration, operName, reason string) (err error) {
|
1269
|
1292
|
account, err := CasefoldName(accountName)
|
1270
|
1293
|
if err != nil {
|
1271
|
1294
|
return errAccountDoesNotExist
|
1272
|
1295
|
}
|
1273
|
1296
|
|
|
1297
|
+ suspension := AccountSuspension{
|
|
1298
|
+ TimeCreated: time.Now().UTC(),
|
|
1299
|
+ Duration: duration,
|
|
1300
|
+ OperName: operName,
|
|
1301
|
+ Reason: reason,
|
|
1302
|
+ }
|
|
1303
|
+ suspensionStr, err := json.Marshal(suspension)
|
|
1304
|
+ if err != nil {
|
|
1305
|
+ am.server.logger.Error("internal", "suspension json unserializable", err.Error())
|
|
1306
|
+ return errAccountDoesNotExist
|
|
1307
|
+ }
|
|
1308
|
+
|
1274
|
1309
|
existsKey := fmt.Sprintf(keyAccountExists, account)
|
1275
|
|
- verifiedKey := fmt.Sprintf(keyAccountVerified, account)
|
|
1310
|
+ suspensionKey := fmt.Sprintf(keyAccountSuspended, account)
|
|
1311
|
+ var setOptions *buntdb.SetOptions
|
|
1312
|
+ if duration != time.Duration(0) {
|
|
1313
|
+ setOptions = &buntdb.SetOptions{Expires: true, TTL: duration}
|
|
1314
|
+ }
|
1276
|
1315
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
1277
|
1316
|
_, err := tx.Get(existsKey)
|
1278
|
1317
|
if err != nil {
|
1279
|
1318
|
return errAccountDoesNotExist
|
1280
|
1319
|
}
|
1281
|
|
- _, err = tx.Delete(verifiedKey)
|
|
1320
|
+ _, _, err = tx.Set(suspensionKey, string(suspensionStr), setOptions)
|
1282
|
1321
|
return err
|
1283
|
1322
|
})
|
1284
|
1323
|
|
|
@@ -1293,7 +1332,13 @@ func (am *AccountManager) Suspend(accountName string) (err error) {
|
1293
|
1332
|
delete(am.accountToClients, account)
|
1294
|
1333
|
am.Unlock()
|
1295
|
1334
|
|
1296
|
|
- am.killClients(clients)
|
|
1335
|
+ // kill clients, sending them the reason
|
|
1336
|
+ suspension.AccountName = accountName
|
|
1337
|
+ for _, client := range clients {
|
|
1338
|
+ client.Logout()
|
|
1339
|
+ client.Quit(suspensionToString(client, suspension), nil)
|
|
1340
|
+ client.destroy(nil)
|
|
1341
|
+ }
|
1297
|
1342
|
return nil
|
1298
|
1343
|
}
|
1299
|
1344
|
|
|
@@ -1312,20 +1357,53 @@ func (am *AccountManager) Unsuspend(account string) (err error) {
|
1312
|
1357
|
}
|
1313
|
1358
|
|
1314
|
1359
|
existsKey := fmt.Sprintf(keyAccountExists, cfaccount)
|
1315
|
|
- verifiedKey := fmt.Sprintf(keyAccountVerified, cfaccount)
|
|
1360
|
+ suspensionKey := fmt.Sprintf(keyAccountSuspended, account)
|
1316
|
1361
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
1317
|
1362
|
_, err := tx.Get(existsKey)
|
1318
|
1363
|
if err != nil {
|
1319
|
1364
|
return errAccountDoesNotExist
|
1320
|
1365
|
}
|
1321
|
|
- tx.Set(verifiedKey, "1", nil)
|
|
1366
|
+ _, err = tx.Delete(suspensionKey)
|
|
1367
|
+ if err != nil {
|
|
1368
|
+ return errNoop
|
|
1369
|
+ }
|
1322
|
1370
|
return nil
|
1323
|
1371
|
})
|
1324
|
1372
|
|
1325
|
|
- if err != nil {
|
1326
|
|
- return errAccountDoesNotExist
|
|
1373
|
+ return err
|
|
1374
|
+}
|
|
1375
|
+
|
|
1376
|
+func (am *AccountManager) ListSuspended() (result []AccountSuspension) {
|
|
1377
|
+ var names []string
|
|
1378
|
+ var raw []string
|
|
1379
|
+
|
|
1380
|
+ prefix := fmt.Sprintf(keyAccountSuspended, "")
|
|
1381
|
+ am.server.store.View(func(tx *buntdb.Tx) error {
|
|
1382
|
+ err := tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
|
1383
|
+ if !strings.HasPrefix(key, prefix) {
|
|
1384
|
+ return false
|
|
1385
|
+ }
|
|
1386
|
+ raw = append(raw, value)
|
|
1387
|
+ cfname := strings.TrimPrefix(key, prefix)
|
|
1388
|
+ name, _ := tx.Get(fmt.Sprintf(keyAccountName, cfname))
|
|
1389
|
+ names = append(names, name)
|
|
1390
|
+ return true
|
|
1391
|
+ })
|
|
1392
|
+ return err
|
|
1393
|
+ })
|
|
1394
|
+
|
|
1395
|
+ result = make([]AccountSuspension, 0, len(raw))
|
|
1396
|
+ for i := 0; i < len(raw); i++ {
|
|
1397
|
+ var sus AccountSuspension
|
|
1398
|
+ err := json.Unmarshal([]byte(raw[i]), &sus)
|
|
1399
|
+ if err != nil {
|
|
1400
|
+ am.server.logger.Error("internal", "corrupt data for suspension", names[i], err.Error())
|
|
1401
|
+ continue
|
|
1402
|
+ }
|
|
1403
|
+ sus.AccountName = names[i]
|
|
1404
|
+ result = append(result, sus)
|
1327
|
1405
|
}
|
1328
|
|
- return nil
|
|
1406
|
+ return
|
1329
|
1407
|
}
|
1330
|
1408
|
|
1331
|
1409
|
func (am *AccountManager) Unregister(account string, erase bool) error {
|
|
@@ -1351,6 +1429,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1351
|
1429
|
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
|
1352
|
1430
|
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
|
1353
|
1431
|
realnameKey := fmt.Sprintf(keyAccountRealname, casefoldedAccount)
|
|
1432
|
+ suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
1354
|
1433
|
|
1355
|
1434
|
var clients []*Client
|
1356
|
1435
|
defer func() {
|
|
@@ -1410,6 +1489,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1410
|
1489
|
tx.Delete(lastSeenKey)
|
1411
|
1490
|
tx.Delete(modesKey)
|
1412
|
1491
|
tx.Delete(realnameKey)
|
|
1492
|
+ tx.Delete(suspendedKey)
|
1413
|
1493
|
|
1414
|
1494
|
return nil
|
1415
|
1495
|
})
|
|
@@ -1491,6 +1571,9 @@ func (am *AccountManager) AuthenticateByCertificate(client *Client, certfp strin
|
1491
|
1571
|
} else if !clientAccount.Verified {
|
1492
|
1572
|
err = errAccountUnverified
|
1493
|
1573
|
return
|
|
1574
|
+ } else if clientAccount.Suspended != nil {
|
|
1575
|
+ err = errAccountSuspended
|
|
1576
|
+ return
|
1494
|
1577
|
}
|
1495
|
1578
|
// TODO(#1109) clean this check up?
|
1496
|
1579
|
if client.registered {
|
|
@@ -1882,6 +1965,7 @@ type ClientAccount struct {
|
1882
|
1965
|
RegisteredAt time.Time
|
1883
|
1966
|
Credentials AccountCredentials
|
1884
|
1967
|
Verified bool
|
|
1968
|
+ Suspended *AccountSuspension
|
1885
|
1969
|
AdditionalNicks []string
|
1886
|
1970
|
VHost VHostInfo
|
1887
|
1971
|
Settings AccountSettings
|
|
@@ -1897,4 +1981,5 @@ type rawClientAccount struct {
|
1897
|
1981
|
AdditionalNicks string
|
1898
|
1982
|
VHost string
|
1899
|
1983
|
Settings string
|
|
1984
|
+ Suspended string
|
1900
|
1985
|
}
|