|
@@ -29,6 +29,7 @@ const (
|
29
|
29
|
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
|
30
|
30
|
keyAccountRegTime = "account.registered.time %s"
|
31
|
31
|
keyAccountCredentials = "account.credentials %s"
|
|
32
|
+ keyAccountAdditionalNicks = "account.additionalnicks %s"
|
32
|
33
|
keyCertToAccount = "account.creds.certfp %s"
|
33
|
34
|
)
|
34
|
35
|
|
|
@@ -75,6 +76,12 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
75
|
76
|
if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
|
76
|
77
|
result[accountName] = accountName
|
77
|
78
|
}
|
|
79
|
+ if rawNicks, err := tx.Get(fmt.Sprintf(keyAccountAdditionalNicks, accountName)); err == nil {
|
|
80
|
+ additionalNicks := unmarshalReservedNicks(rawNicks)
|
|
81
|
+ for _, nick := range additionalNicks {
|
|
82
|
+ result[nick] = accountName
|
|
83
|
+ }
|
|
84
|
+ }
|
78
|
85
|
return true
|
79
|
86
|
})
|
80
|
87
|
return err
|
|
@@ -91,7 +98,12 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
91
|
98
|
return
|
92
|
99
|
}
|
93
|
100
|
|
94
|
|
-func (am *AccountManager) NickToAccount(cfnick string) string {
|
|
101
|
+func (am *AccountManager) NickToAccount(nick string) string {
|
|
102
|
+ cfnick, err := CasefoldName(nick)
|
|
103
|
+ if err != nil {
|
|
104
|
+ return ""
|
|
105
|
+ }
|
|
106
|
+
|
95
|
107
|
am.RLock()
|
96
|
108
|
defer am.RUnlock()
|
97
|
109
|
return am.nickToAccount[cfnick]
|
|
@@ -325,13 +337,92 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
325
|
337
|
return nil
|
326
|
338
|
}
|
327
|
339
|
|
328
|
|
-func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
|
329
|
|
- casefoldedAccount, err := CasefoldName(accountName)
|
|
340
|
+func marshalReservedNicks(nicks []string) string {
|
|
341
|
+ return strings.Join(nicks, ",")
|
|
342
|
+}
|
|
343
|
+
|
|
344
|
+func unmarshalReservedNicks(nicks string) (result []string) {
|
|
345
|
+ if nicks == "" {
|
|
346
|
+ return
|
|
347
|
+ }
|
|
348
|
+ return strings.Split(nicks, ",")
|
|
349
|
+}
|
|
350
|
+
|
|
351
|
+func (am *AccountManager) SetNickReserved(client *Client, nick string, reserve bool) error {
|
|
352
|
+ cfnick, err := CasefoldName(nick)
|
330
|
353
|
if err != nil {
|
331
|
|
- return errAccountDoesNotExist
|
|
354
|
+ return errAccountNickReservationFailed
|
|
355
|
+ }
|
|
356
|
+
|
|
357
|
+ // sanity check so we don't persist bad data
|
|
358
|
+ account := client.Account()
|
|
359
|
+ if account == "" || cfnick == "" || !am.server.AccountConfig().NickReservation.Enabled {
|
|
360
|
+ return errAccountNickReservationFailed
|
332
|
361
|
}
|
333
|
362
|
|
334
|
|
- account, err := am.LoadAccount(casefoldedAccount)
|
|
363
|
+ limit := am.server.AccountConfig().NickReservation.AdditionalNickLimit
|
|
364
|
+
|
|
365
|
+ am.serialCacheUpdateMutex.Lock()
|
|
366
|
+ defer am.serialCacheUpdateMutex.Unlock()
|
|
367
|
+
|
|
368
|
+ // the cache is in sync with the DB while we hold serialCacheUpdateMutex
|
|
369
|
+ accountForNick := am.NickToAccount(cfnick)
|
|
370
|
+ if reserve && accountForNick != "" {
|
|
371
|
+ return errNicknameReserved
|
|
372
|
+ } else if !reserve && accountForNick != account {
|
|
373
|
+ return errAccountNickReservationFailed
|
|
374
|
+ } else if !reserve && cfnick == account {
|
|
375
|
+ return errAccountCantDropPrimaryNick
|
|
376
|
+ }
|
|
377
|
+
|
|
378
|
+ nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, account)
|
|
379
|
+ err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
380
|
+ rawNicks, err := tx.Get(nicksKey)
|
|
381
|
+ if err != nil && err != buntdb.ErrNotFound {
|
|
382
|
+ return err
|
|
383
|
+ }
|
|
384
|
+
|
|
385
|
+ nicks := unmarshalReservedNicks(rawNicks)
|
|
386
|
+
|
|
387
|
+ if reserve {
|
|
388
|
+ if len(nicks) >= limit {
|
|
389
|
+ return errAccountTooManyNicks
|
|
390
|
+ }
|
|
391
|
+ nicks = append(nicks, cfnick)
|
|
392
|
+ } else {
|
|
393
|
+ var newNicks []string
|
|
394
|
+ for _, reservedNick := range nicks {
|
|
395
|
+ if reservedNick != cfnick {
|
|
396
|
+ newNicks = append(newNicks, reservedNick)
|
|
397
|
+ }
|
|
398
|
+ }
|
|
399
|
+ nicks = newNicks
|
|
400
|
+ }
|
|
401
|
+
|
|
402
|
+ marshaledNicks := marshalReservedNicks(nicks)
|
|
403
|
+ _, _, err = tx.Set(nicksKey, string(marshaledNicks), nil)
|
|
404
|
+ return err
|
|
405
|
+ })
|
|
406
|
+
|
|
407
|
+ if err == errAccountTooManyNicks {
|
|
408
|
+ return err
|
|
409
|
+ } else if err != nil {
|
|
410
|
+ return errAccountNickReservationFailed
|
|
411
|
+ }
|
|
412
|
+
|
|
413
|
+ // success
|
|
414
|
+ am.Lock()
|
|
415
|
+ defer am.Unlock()
|
|
416
|
+ if reserve {
|
|
417
|
+ am.nickToAccount[cfnick] = account
|
|
418
|
+ } else {
|
|
419
|
+ delete(am.nickToAccount, cfnick)
|
|
420
|
+ }
|
|
421
|
+ return nil
|
|
422
|
+}
|
|
423
|
+
|
|
424
|
+func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
|
|
425
|
+ account, err := am.LoadAccount(accountName)
|
335
|
426
|
if err != nil {
|
336
|
427
|
return err
|
337
|
428
|
}
|
|
@@ -350,7 +441,13 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s
|
350
|
441
|
return nil
|
351
|
442
|
}
|
352
|
443
|
|
353
|
|
-func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
|
|
444
|
+func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) {
|
|
445
|
+ casefoldedAccount, err := CasefoldName(accountName)
|
|
446
|
+ if err != nil {
|
|
447
|
+ err = errAccountDoesNotExist
|
|
448
|
+ return
|
|
449
|
+ }
|
|
450
|
+
|
354
|
451
|
var raw rawClientAccount
|
355
|
452
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
356
|
453
|
raw, err = am.loadRawAccount(tx, casefoldedAccount)
|
|
@@ -369,6 +466,7 @@ func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAc
|
369
|
466
|
err = errAccountDoesNotExist
|
370
|
467
|
return
|
371
|
468
|
}
|
|
469
|
+ result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
|
372
|
470
|
result.Verified = raw.Verified
|
373
|
471
|
return
|
374
|
472
|
}
|
|
@@ -380,6 +478,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
380
|
478
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
381
|
479
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
382
|
480
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
|
481
|
+ nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
383
|
482
|
|
384
|
483
|
_, e := tx.Get(accountKey)
|
385
|
484
|
if e == buntdb.ErrNotFound {
|
|
@@ -391,6 +490,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
391
|
490
|
result.RegisteredAt, _ = tx.Get(registeredTimeKey)
|
392
|
491
|
result.Credentials, _ = tx.Get(credentialsKey)
|
393
|
492
|
result.Callback, _ = tx.Get(callbackKey)
|
|
493
|
+ result.AdditionalNicks, _ = tx.Get(nicksKey)
|
394
|
494
|
|
395
|
495
|
if _, e = tx.Get(verifiedKey); e == nil {
|
396
|
496
|
result.Verified = true
|
|
@@ -412,51 +512,56 @@ func (am *AccountManager) Unregister(account string) error {
|
412
|
512
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
413
|
513
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
414
|
514
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
|
515
|
+ nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
415
|
516
|
|
416
|
517
|
var clients []*Client
|
417
|
518
|
|
418
|
|
- func() {
|
419
|
|
- var credText string
|
|
519
|
+ var credText string
|
|
520
|
+ var rawNicks string
|
420
|
521
|
|
421
|
|
- am.serialCacheUpdateMutex.Lock()
|
422
|
|
- defer am.serialCacheUpdateMutex.Unlock()
|
|
522
|
+ am.serialCacheUpdateMutex.Lock()
|
|
523
|
+ defer am.serialCacheUpdateMutex.Unlock()
|
423
|
524
|
|
424
|
|
- am.server.store.Update(func(tx *buntdb.Tx) error {
|
425
|
|
- tx.Delete(accountKey)
|
426
|
|
- tx.Delete(accountNameKey)
|
427
|
|
- tx.Delete(verifiedKey)
|
428
|
|
- tx.Delete(registeredTimeKey)
|
429
|
|
- tx.Delete(callbackKey)
|
430
|
|
- tx.Delete(verificationCodeKey)
|
431
|
|
- credText, err = tx.Get(credentialsKey)
|
432
|
|
- tx.Delete(credentialsKey)
|
433
|
|
- return nil
|
434
|
|
- })
|
|
525
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
526
|
+ tx.Delete(accountKey)
|
|
527
|
+ tx.Delete(accountNameKey)
|
|
528
|
+ tx.Delete(verifiedKey)
|
|
529
|
+ tx.Delete(registeredTimeKey)
|
|
530
|
+ tx.Delete(callbackKey)
|
|
531
|
+ tx.Delete(verificationCodeKey)
|
|
532
|
+ rawNicks, _ = tx.Get(nicksKey)
|
|
533
|
+ tx.Delete(nicksKey)
|
|
534
|
+ credText, err = tx.Get(credentialsKey)
|
|
535
|
+ tx.Delete(credentialsKey)
|
|
536
|
+ return nil
|
|
537
|
+ })
|
435
|
538
|
|
436
|
|
- if err == nil {
|
437
|
|
- var creds AccountCredentials
|
438
|
|
- if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
|
439
|
|
- certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
440
|
|
- am.server.store.Update(func(tx *buntdb.Tx) error {
|
441
|
|
- if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
|
442
|
|
- tx.Delete(certFPKey)
|
443
|
|
- }
|
444
|
|
- return nil
|
445
|
|
- })
|
446
|
|
- }
|
|
539
|
+ if err == nil {
|
|
540
|
+ var creds AccountCredentials
|
|
541
|
+ if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
|
|
542
|
+ certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
|
543
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
544
|
+ if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
|
|
545
|
+ tx.Delete(certFPKey)
|
|
546
|
+ }
|
|
547
|
+ return nil
|
|
548
|
+ })
|
447
|
549
|
}
|
|
550
|
+ }
|
448
|
551
|
|
449
|
|
- am.Lock()
|
450
|
|
- defer am.Unlock()
|
451
|
|
- clients = am.accountToClients[casefoldedAccount]
|
452
|
|
- delete(am.accountToClients, casefoldedAccount)
|
453
|
|
- // TODO when registration of multiple nicks is fully implemented,
|
454
|
|
- // save the nicks that were deleted from the store and delete them here:
|
455
|
|
- delete(am.nickToAccount, casefoldedAccount)
|
456
|
|
- }()
|
|
552
|
+ additionalNicks := unmarshalReservedNicks(rawNicks)
|
457
|
553
|
|
|
554
|
+ am.Lock()
|
|
555
|
+ defer am.Unlock()
|
|
556
|
+
|
|
557
|
+ clients = am.accountToClients[casefoldedAccount]
|
|
558
|
+ delete(am.accountToClients, casefoldedAccount)
|
|
559
|
+ delete(am.nickToAccount, casefoldedAccount)
|
|
560
|
+ for _, nick := range additionalNicks {
|
|
561
|
+ delete(am.nickToAccount, nick)
|
|
562
|
+ }
|
458
|
563
|
for _, client := range clients {
|
459
|
|
- client.LogoutOfAccount()
|
|
564
|
+ am.logoutOfAccount(client)
|
460
|
565
|
}
|
461
|
566
|
|
462
|
567
|
if err != nil {
|
|
@@ -498,29 +603,25 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
498
|
603
|
}
|
499
|
604
|
|
500
|
605
|
func (am *AccountManager) Login(client *Client, account string) {
|
501
|
|
- client.LoginToAccount(account)
|
502
|
|
-
|
503
|
|
- casefoldedAccount, _ := CasefoldName(account)
|
504
|
606
|
am.Lock()
|
505
|
607
|
defer am.Unlock()
|
|
608
|
+
|
|
609
|
+ am.loginToAccount(client, account)
|
|
610
|
+ casefoldedAccount := client.Account()
|
506
|
611
|
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
507
|
612
|
}
|
508
|
613
|
|
509
|
614
|
func (am *AccountManager) Logout(client *Client) {
|
510
|
|
- casefoldedAccount := client.Account()
|
511
|
|
- if casefoldedAccount == "" || casefoldedAccount == "*" {
|
512
|
|
- return
|
513
|
|
- }
|
514
|
|
-
|
515
|
|
- client.LogoutOfAccount()
|
516
|
|
-
|
517
|
615
|
am.Lock()
|
518
|
616
|
defer am.Unlock()
|
519
|
617
|
|
520
|
|
- if client.LoggedIntoAccount() {
|
|
618
|
+ casefoldedAccount := client.Account()
|
|
619
|
+ if casefoldedAccount == "" {
|
521
|
620
|
return
|
522
|
621
|
}
|
523
|
622
|
|
|
623
|
+ am.logoutOfAccount(client)
|
|
624
|
+
|
524
|
625
|
clients := am.accountToClients[casefoldedAccount]
|
525
|
626
|
if len(clients) <= 1 {
|
526
|
627
|
delete(am.accountToClients, casefoldedAccount)
|
|
@@ -559,42 +660,45 @@ type ClientAccount struct {
|
559
|
660
|
// Name of the account.
|
560
|
661
|
Name string
|
561
|
662
|
// RegisteredAt represents the time that the account was registered.
|
562
|
|
- RegisteredAt time.Time
|
563
|
|
- Credentials AccountCredentials
|
564
|
|
- Verified bool
|
|
663
|
+ RegisteredAt time.Time
|
|
664
|
+ Credentials AccountCredentials
|
|
665
|
+ Verified bool
|
|
666
|
+ AdditionalNicks []string
|
565
|
667
|
}
|
566
|
668
|
|
567
|
669
|
// convenience for passing around raw serialized account data
|
568
|
670
|
type rawClientAccount struct {
|
569
|
|
- Name string
|
570
|
|
- RegisteredAt string
|
571
|
|
- Credentials string
|
572
|
|
- Callback string
|
573
|
|
- Verified bool
|
|
671
|
+ Name string
|
|
672
|
+ RegisteredAt string
|
|
673
|
+ Credentials string
|
|
674
|
+ Callback string
|
|
675
|
+ Verified bool
|
|
676
|
+ AdditionalNicks string
|
574
|
677
|
}
|
575
|
678
|
|
576
|
|
-// LoginToAccount logs the client into the given account.
|
577
|
|
-func (client *Client) LoginToAccount(account string) {
|
|
679
|
+// loginToAccount logs the client into the given account.
|
|
680
|
+func (am *AccountManager) loginToAccount(client *Client, account string) {
|
578
|
681
|
changed := client.SetAccountName(account)
|
579
|
682
|
if changed {
|
580
|
|
- client.nickTimer.Touch()
|
|
683
|
+ go client.nickTimer.Touch()
|
581
|
684
|
}
|
582
|
685
|
}
|
583
|
686
|
|
584
|
|
-// LogoutOfAccount logs the client out of their current account.
|
585
|
|
-func (client *Client) LogoutOfAccount() {
|
|
687
|
+// logoutOfAccount logs the client out of their current account.
|
|
688
|
+func (am *AccountManager) logoutOfAccount(client *Client) {
|
586
|
689
|
if client.Account() == "" {
|
587
|
690
|
// already logged out
|
588
|
691
|
return
|
589
|
692
|
}
|
590
|
693
|
|
591
|
694
|
client.SetAccountName("")
|
592
|
|
- client.nickTimer.Touch()
|
|
695
|
+ go client.nickTimer.Touch()
|
593
|
696
|
|
594
|
697
|
// dispatch account-notify
|
595
|
698
|
// TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
|
596
|
|
- for friend := range client.Friends(caps.AccountNotify) {
|
597
|
|
- friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
|
598
|
|
- }
|
|
699
|
+ go func() {
|
|
700
|
+ for friend := range client.Friends(caps.AccountNotify) {
|
|
701
|
+ friend.Send(nil, client.NickMaskString(), "ACCOUNT", "*")
|
|
702
|
+ }
|
|
703
|
+ }()
|
599
|
704
|
}
|
600
|
|
-
|