Browse Source

refactor account registration, add nick enforcement

tags/v0.11.0-beta
Shivaram Lingamneni 6 years ago
parent
commit
ad73d68807
18 changed files with 863 additions and 600 deletions
  1. 1
    1
      DEVELOPING.md
  2. 0
    58
      irc/accountreg.go
  3. 450
    63
      irc/accounts.go
  4. 7
    5
      irc/channel.go
  5. 2
    2
      irc/chanserv.go
  6. 16
    14
      irc/client.go
  7. 9
    0
      irc/client_lookup_set.go
  8. 11
    7
      irc/commands.go
  9. 52
    7
      irc/config.go
  10. 19
    12
      irc/errors.go
  11. 27
    1
      irc/getters.go
  12. 88
    192
      irc/handlers.go
  13. 77
    0
      irc/idletimer.go
  14. 6
    0
      irc/nickname.go
  15. 21
    170
      irc/nickserv.go
  16. 3
    3
      irc/responsebuffer.go
  17. 65
    65
      irc/server.go
  18. 9
    0
      oragono.yaml

+ 1
- 1
DEVELOPING.md View File

98
 
98
 
99
 There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
99
 There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
100
 
100
 
101
-We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. We haven't completely decided where this lock fits into the overall lock model. For now, it's probably better to err on the side of caution: if possible, don't acquire new locks inside the `buntdb` transaction, and be careful about what locks are held around the transaction as well.
101
+We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. This mutex is considered tier 1. However, it's shared globally across all consumers, so if possible you should avoid acquiring it while holding ordinary application-level mutexes.
102
 
102
 
103
 ## Command handlers and ResponseBuffer
103
 ## Command handlers and ResponseBuffer
104
 
104
 

+ 0
- 58
irc/accountreg.go View File

1
-// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2
-// released under the MIT license
3
-
4
-package irc
5
-
6
-import (
7
-	"fmt"
8
-
9
-	"github.com/tidwall/buntdb"
10
-)
11
-
12
-// AccountRegistration manages the registration of accounts.
13
-type AccountRegistration struct {
14
-	Enabled                    bool
15
-	EnabledCallbacks           []string
16
-	EnabledCredentialTypes     []string
17
-	AllowMultiplePerConnection bool
18
-}
19
-
20
-// AccountCredentials stores the various methods for verifying accounts.
21
-type AccountCredentials struct {
22
-	PassphraseSalt []byte
23
-	PassphraseHash []byte
24
-	Certificate    string // fingerprint
25
-}
26
-
27
-// NewAccountRegistration returns a new AccountRegistration, configured correctly.
28
-func NewAccountRegistration(config AccountRegistrationConfig) (accountReg AccountRegistration) {
29
-	if config.Enabled {
30
-		accountReg.Enabled = true
31
-		accountReg.AllowMultiplePerConnection = config.AllowMultiplePerConnection
32
-		for _, name := range config.EnabledCallbacks {
33
-			// we store "none" as "*" internally
34
-			if name == "none" {
35
-				name = "*"
36
-			}
37
-			accountReg.EnabledCallbacks = append(accountReg.EnabledCallbacks, name)
38
-		}
39
-		// no need to make this configurable, right now at least
40
-		accountReg.EnabledCredentialTypes = []string{
41
-			"passphrase",
42
-			"certfp",
43
-		}
44
-	}
45
-	return accountReg
46
-}
47
-
48
-// removeFailedAccRegisterData removes the data created by ACC REGISTER if the account creation fails early.
49
-func removeFailedAccRegisterData(store *buntdb.DB, account string) {
50
-	// error is ignored here, we can't do much about it anyways
51
-	store.Update(func(tx *buntdb.Tx) error {
52
-		tx.Delete(fmt.Sprintf(keyAccountExists, account))
53
-		tx.Delete(fmt.Sprintf(keyAccountRegTime, account))
54
-		tx.Delete(fmt.Sprintf(keyAccountCredentials, account))
55
-
56
-		return nil
57
-	})
58
-}

+ 450
- 63
irc/accounts.go View File

7
 	"encoding/json"
7
 	"encoding/json"
8
 	"fmt"
8
 	"fmt"
9
 	"strconv"
9
 	"strconv"
10
+	"strings"
11
+	"sync"
10
 	"time"
12
 	"time"
11
 
13
 
12
 	"github.com/goshuirc/irc-go/ircfmt"
14
 	"github.com/goshuirc/irc-go/ircfmt"
13
 	"github.com/oragono/oragono/irc/caps"
15
 	"github.com/oragono/oragono/irc/caps"
16
+	"github.com/oragono/oragono/irc/passwd"
14
 	"github.com/oragono/oragono/irc/sno"
17
 	"github.com/oragono/oragono/irc/sno"
15
 	"github.com/tidwall/buntdb"
18
 	"github.com/tidwall/buntdb"
16
 )
19
 )
24
 	keyCertToAccount      = "account.creds.certfp %s"
27
 	keyCertToAccount      = "account.creds.certfp %s"
25
 )
28
 )
26
 
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
+
36
+	server *Server
37
+	// track clients logged in to accounts
38
+	accountToClients map[string][]*Client
39
+	nickToAccount    map[string]string
40
+}
41
+
42
+func NewAccountManager(server *Server) *AccountManager {
43
+	am := AccountManager{
44
+		accountToClients: make(map[string][]*Client),
45
+		nickToAccount:    make(map[string]string),
46
+		server:           server,
47
+	}
48
+
49
+	am.buildNickToAccountIndex()
50
+	return &am
51
+}
52
+
53
+func (am *AccountManager) buildNickToAccountIndex() {
54
+	if am.server.AccountConfig().NickReservation == NickReservationDisabled {
55
+		return
56
+	}
57
+
58
+	result := make(map[string]string)
59
+	existsPrefix := fmt.Sprintf(keyAccountExists, "")
60
+
61
+	am.serialCacheUpdateMutex.Lock()
62
+	defer am.serialCacheUpdateMutex.Unlock()
63
+
64
+	err := am.server.store.View(func(tx *buntdb.Tx) error {
65
+		err := tx.AscendGreaterOrEqual("", existsPrefix, func(key, value string) bool {
66
+			if !strings.HasPrefix(key, existsPrefix) {
67
+				return false
68
+			}
69
+			accountName := strings.TrimPrefix(key, existsPrefix)
70
+			if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
71
+				result[accountName] = accountName
72
+			}
73
+			return true
74
+		})
75
+		return err
76
+	})
77
+
78
+	if err != nil {
79
+		am.server.logger.Error("internal", fmt.Sprintf("couldn't read reserved nicks: %v", err))
80
+	} else {
81
+		am.Lock()
82
+		am.nickToAccount = result
83
+		am.Unlock()
84
+	}
85
+
86
+	return
87
+}
88
+
89
+func (am *AccountManager) NickToAccount(cfnick string) string {
90
+	am.RLock()
91
+	defer am.RUnlock()
92
+	return am.nickToAccount[cfnick]
93
+}
94
+
95
+func (am *AccountManager) Register(client *Client, account string, callbackNamespace string, callbackValue string, passphrase string, certfp string) error {
96
+	casefoldedAccount, err := CasefoldName(account)
97
+	if err != nil || account == "" || account == "*" {
98
+		return errAccountCreation
99
+	}
100
+
101
+	accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
102
+	accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
103
+	registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
104
+	credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
105
+	certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
106
+
107
+	var creds AccountCredentials
108
+	// always set passphrase salt
109
+	creds.PassphraseSalt, err = passwd.NewSalt()
110
+	if err != nil {
111
+		return errAccountCreation
112
+	}
113
+	// it's fine if this is empty, that just means no certificate is authorized
114
+	creds.Certificate = certfp
115
+	if passphrase != "" {
116
+		creds.PassphraseHash, err = am.server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
117
+		if err != nil {
118
+			am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
119
+			return errAccountCreation
120
+		}
121
+	}
122
+
123
+	credText, err := json.Marshal(creds)
124
+	if err != nil {
125
+		am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
126
+		return errAccountCreation
127
+	}
128
+	credStr := string(credText)
129
+
130
+	registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
131
+
132
+	var setOptions *buntdb.SetOptions
133
+	ttl := am.server.AccountConfig().Registration.VerifyTimeout
134
+	if ttl != 0 {
135
+		setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
136
+	}
137
+
138
+	err = am.server.store.Update(func(tx *buntdb.Tx) error {
139
+		_, err := am.loadRawAccount(tx, casefoldedAccount)
140
+		if err != errAccountDoesNotExist {
141
+			return errAccountAlreadyRegistered
142
+		}
143
+
144
+		if certfp != "" {
145
+			// make sure certfp doesn't already exist because that'd be silly
146
+			_, err := tx.Get(certFPKey)
147
+			if err != buntdb.ErrNotFound {
148
+				return errCertfpAlreadyExists
149
+			}
150
+		}
151
+
152
+		tx.Set(accountKey, "1", setOptions)
153
+		tx.Set(accountNameKey, account, setOptions)
154
+		tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
155
+		tx.Set(credentialsKey, credStr, setOptions)
156
+		if certfp != "" {
157
+			tx.Set(certFPKey, casefoldedAccount, setOptions)
158
+		}
159
+		return nil
160
+	})
161
+
162
+	if err != nil {
163
+		return err
164
+	}
165
+
166
+	return nil
167
+}
168
+
169
+func (am *AccountManager) Verify(client *Client, account string, code string) error {
170
+	casefoldedAccount, err := CasefoldName(account)
171
+	if err != nil || account == "" || account == "*" {
172
+		return errAccountVerificationFailed
173
+	}
174
+
175
+	verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
176
+	accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
177
+	accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
178
+	registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
179
+	credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
180
+
181
+	var raw rawClientAccount
182
+
183
+	func() {
184
+		am.serialCacheUpdateMutex.Lock()
185
+		defer am.serialCacheUpdateMutex.Unlock()
186
+
187
+		am.server.store.Update(func(tx *buntdb.Tx) error {
188
+			raw, err = am.loadRawAccount(tx, casefoldedAccount)
189
+			if err == errAccountDoesNotExist {
190
+				return errAccountDoesNotExist
191
+			} else if err != nil {
192
+				return errAccountVerificationFailed
193
+			} else if raw.Verified {
194
+				return errAccountAlreadyVerified
195
+			}
196
+
197
+			// TODO add code verification here
198
+			// return errAccountVerificationFailed if it fails
199
+
200
+			// verify the account
201
+			tx.Set(verifiedKey, "1", nil)
202
+			// re-set all other keys, removing the TTL
203
+			tx.Set(accountKey, "1", nil)
204
+			tx.Set(accountNameKey, raw.Name, nil)
205
+			tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
206
+			tx.Set(credentialsKey, raw.Credentials, nil)
207
+
208
+			var creds AccountCredentials
209
+			// XXX we shouldn't do (de)serialization inside the txn,
210
+			// but this is like 2 usec on my system
211
+			json.Unmarshal([]byte(raw.Credentials), &creds)
212
+			if creds.Certificate != "" {
213
+				certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
214
+				tx.Set(certFPKey, casefoldedAccount, nil)
215
+			}
216
+
217
+			return nil
218
+		})
219
+
220
+		if err == nil {
221
+			am.Lock()
222
+			am.nickToAccount[casefoldedAccount] = casefoldedAccount
223
+			am.Unlock()
224
+		}
225
+	}()
226
+
227
+	if err != nil {
228
+		return err
229
+	}
230
+
231
+	am.Login(client, raw.Name)
232
+	return nil
233
+}
234
+
235
+func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
236
+	casefoldedAccount, err := CasefoldName(accountName)
237
+	if err != nil {
238
+		return errAccountDoesNotExist
239
+	}
240
+
241
+	account, err := am.LoadAccount(casefoldedAccount)
242
+	if err != nil {
243
+		return err
244
+	}
245
+
246
+	if !account.Verified {
247
+		return errAccountUnverified
248
+	}
249
+
250
+	err = am.server.passwords.CompareHashAndPassword(
251
+		account.Credentials.PassphraseHash, account.Credentials.PassphraseSalt, passphrase)
252
+	if err != nil {
253
+		return errAccountInvalidCredentials
254
+	}
255
+
256
+	am.Login(client, account.Name)
257
+	return nil
258
+}
259
+
260
+func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
261
+	var raw rawClientAccount
262
+	am.server.store.View(func(tx *buntdb.Tx) error {
263
+		raw, err = am.loadRawAccount(tx, casefoldedAccount)
264
+		if err == buntdb.ErrNotFound {
265
+			err = errAccountDoesNotExist
266
+		}
267
+		return nil
268
+	})
269
+	if err != nil {
270
+		return
271
+	}
272
+
273
+	result.Name = raw.Name
274
+	regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
275
+	result.RegisteredAt = time.Unix(regTimeInt, 0)
276
+	e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
277
+	if e != nil {
278
+		am.server.logger.Error("internal", fmt.Sprintf("could not unmarshal credentials: %v", e))
279
+		err = errAccountDoesNotExist
280
+		return
281
+	}
282
+	result.Verified = raw.Verified
283
+	return
284
+}
285
+
286
+func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
287
+	accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
288
+	accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
289
+	registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
290
+	credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
291
+	verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
292
+
293
+	_, e := tx.Get(accountKey)
294
+	if e == buntdb.ErrNotFound {
295
+		err = errAccountDoesNotExist
296
+		return
297
+	}
298
+
299
+	if result.Name, err = tx.Get(accountNameKey); err != nil {
300
+		return
301
+	}
302
+	if result.RegisteredAt, err = tx.Get(registeredTimeKey); err != nil {
303
+		return
304
+	}
305
+	if result.Credentials, err = tx.Get(credentialsKey); err != nil {
306
+		return
307
+	}
308
+	if _, e = tx.Get(verifiedKey); e == nil {
309
+		result.Verified = true
310
+	}
311
+
312
+	return
313
+}
314
+
315
+func (am *AccountManager) Unregister(account string) error {
316
+	casefoldedAccount, err := CasefoldName(account)
317
+	if err != nil {
318
+		return errAccountDoesNotExist
319
+	}
320
+
321
+	accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
322
+	accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
323
+	registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
324
+	credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
325
+	verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
326
+
327
+	var clients []*Client
328
+
329
+	func() {
330
+		var credText string
331
+
332
+		am.serialCacheUpdateMutex.Lock()
333
+		defer am.serialCacheUpdateMutex.Unlock()
334
+
335
+		am.server.store.Update(func(tx *buntdb.Tx) error {
336
+			tx.Delete(accountKey)
337
+			tx.Delete(accountNameKey)
338
+			tx.Delete(verifiedKey)
339
+			tx.Delete(registeredTimeKey)
340
+			credText, err = tx.Get(credentialsKey)
341
+			tx.Delete(credentialsKey)
342
+			return nil
343
+		})
344
+
345
+		if err == nil {
346
+			var creds AccountCredentials
347
+			if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
348
+				certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
349
+				am.server.store.Update(func(tx *buntdb.Tx) error {
350
+					if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
351
+						tx.Delete(certFPKey)
352
+					}
353
+					return nil
354
+				})
355
+			}
356
+		}
357
+
358
+		am.Lock()
359
+		defer am.Unlock()
360
+		clients = am.accountToClients[casefoldedAccount]
361
+		delete(am.accountToClients, casefoldedAccount)
362
+		// TODO when registration of multiple nicks is fully implemented,
363
+		// save the nicks that were deleted from the store and delete them here:
364
+		delete(am.nickToAccount, casefoldedAccount)
365
+	}()
366
+
367
+	for _, client := range clients {
368
+		client.LogoutOfAccount()
369
+	}
370
+
371
+	return nil
372
+}
373
+
374
+func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
375
+	if client.certfp == "" {
376
+		return errAccountInvalidCredentials
377
+	}
378
+
379
+	var account string
380
+	var rawAccount rawClientAccount
381
+	certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
382
+
383
+	err := am.server.store.Update(func(tx *buntdb.Tx) error {
384
+		var err error
385
+		account, _ = tx.Get(certFPKey)
386
+		if account == "" {
387
+			return errAccountInvalidCredentials
388
+		}
389
+		rawAccount, err = am.loadRawAccount(tx, account)
390
+		if err != nil || !rawAccount.Verified {
391
+			return errAccountUnverified
392
+		}
393
+		return nil
394
+	})
395
+
396
+	if err != nil {
397
+		return err
398
+	}
399
+
400
+	// ok, we found an account corresponding to their certificate
401
+
402
+	am.Login(client, rawAccount.Name)
403
+	return nil
404
+}
405
+
406
+func (am *AccountManager) Login(client *Client, account string) {
407
+	client.LoginToAccount(account)
408
+
409
+	casefoldedAccount, _ := CasefoldName(account)
410
+	am.Lock()
411
+	defer am.Unlock()
412
+	am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
413
+}
414
+
415
+func (am *AccountManager) Logout(client *Client) {
416
+	casefoldedAccount := client.Account()
417
+	if casefoldedAccount == "" || casefoldedAccount == "*" {
418
+		return
419
+	}
420
+
421
+	client.LogoutOfAccount()
422
+
423
+	am.Lock()
424
+	defer am.Unlock()
425
+
426
+	if client.LoggedIntoAccount() {
427
+		return
428
+	}
429
+
430
+	clients := am.accountToClients[casefoldedAccount]
431
+	if len(clients) <= 1 {
432
+		delete(am.accountToClients, casefoldedAccount)
433
+		return
434
+	}
435
+	remainingClients := make([]*Client, len(clients)-1)
436
+	remainingPos := 0
437
+	for currentPos := 0; currentPos < len(clients); currentPos++ {
438
+		if clients[currentPos] != client {
439
+			remainingClients[remainingPos] = clients[currentPos]
440
+			remainingPos++
441
+		}
442
+	}
443
+	am.accountToClients[casefoldedAccount] = remainingClients
444
+	return
445
+}
446
+
27
 var (
447
 var (
28
 	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
448
 	// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
29
 	// This can be moved to some other data structure/place if we need to load/unload mechs later.
449
 	// This can be moved to some other data structure/place if we need to load/unload mechs later.
31
 		"PLAIN":    authPlainHandler,
451
 		"PLAIN":    authPlainHandler,
32
 		"EXTERNAL": authExternalHandler,
452
 		"EXTERNAL": authExternalHandler,
33
 	}
453
 	}
34
-
35
-	// NoAccount is a placeholder which means that the user is not logged into an account.
36
-	NoAccount = ClientAccount{
37
-		Name: "*", // * is used until actual account name is set
38
-	}
39
 )
454
 )
40
 
455
 
456
+// AccountCredentials stores the various methods for verifying accounts.
457
+type AccountCredentials struct {
458
+	PassphraseSalt []byte
459
+	PassphraseHash []byte
460
+	Certificate    string // fingerprint
461
+}
462
+
41
 // ClientAccount represents a user account.
463
 // ClientAccount represents a user account.
42
 type ClientAccount struct {
464
 type ClientAccount struct {
43
 	// Name of the account.
465
 	// Name of the account.
44
 	Name string
466
 	Name string
45
 	// RegisteredAt represents the time that the account was registered.
467
 	// RegisteredAt represents the time that the account was registered.
46
 	RegisteredAt time.Time
468
 	RegisteredAt time.Time
47
-	// Clients that are currently logged into this account (useful for notifications).
48
-	Clients []*Client
469
+	Credentials  AccountCredentials
470
+	Verified     bool
49
 }
471
 }
50
 
472
 
51
-// loadAccountCredentials loads an account's credentials from the store.
52
-func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
53
-	credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
54
-	if err != nil {
55
-		return nil, err
56
-	}
57
-
58
-	var creds AccountCredentials
59
-	err = json.Unmarshal([]byte(credText), &creds)
60
-	if err != nil {
61
-		return nil, err
62
-	}
63
-
64
-	return &creds, nil
473
+// convenience for passing around raw serialized account data
474
+type rawClientAccount struct {
475
+	Name         string
476
+	RegisteredAt string
477
+	Credentials  string
478
+	Verified     bool
65
 }
479
 }
66
 
480
 
67
-// loadAccount loads an account from the store, note that the account must actually exist.
68
-func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
69
-	name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
70
-	regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
71
-	regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
72
-	accountInfo := ClientAccount{
73
-		Name:         name,
74
-		RegisteredAt: time.Unix(regTimeInt, 0),
75
-		Clients:      []*Client{},
481
+// LoginToAccount logs the client into the given account.
482
+func (client *Client) LoginToAccount(account string) {
483
+	casefoldedAccount, err := CasefoldName(account)
484
+	if err != nil {
485
+		return
76
 	}
486
 	}
77
-	server.accounts[accountKey] = &accountInfo
78
-
79
-	return &accountInfo
80
-}
81
 
487
 
82
-// LoginToAccount logs the client into the given account.
83
-func (client *Client) LoginToAccount(account *ClientAccount) {
84
-	if client.account == account {
488
+	if client.Account() == casefoldedAccount {
85
 		// already logged into this acct, no changing necessary
489
 		// already logged into this acct, no changing necessary
86
 		return
490
 		return
87
-	} else if client.LoggedIntoAccount() {
88
-		// logout of existing acct
89
-		var newClientAccounts []*Client
90
-		for _, c := range account.Clients {
91
-			if c != client {
92
-				newClientAccounts = append(newClientAccounts, c)
93
-			}
94
-		}
95
-		account.Clients = newClientAccounts
96
 	}
491
 	}
97
 
492
 
98
-	account.Clients = append(account.Clients, client)
99
-	client.account = account
100
-	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))
493
+	client.SetAccountName(casefoldedAccount)
494
+	client.nickTimer.Touch()
495
+
496
+	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, casefoldedAccount))
101
 
497
 
102
 	//TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
498
 	//TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
103
 }
499
 }
104
 
500
 
105
 // LogoutOfAccount logs the client out of their current account.
501
 // LogoutOfAccount logs the client out of their current account.
106
 func (client *Client) LogoutOfAccount() {
502
 func (client *Client) LogoutOfAccount() {
107
-	account := client.account
108
-	if account == nil {
503
+	if client.Account() == "" {
109
 		// already logged out
504
 		// already logged out
110
 		return
505
 		return
111
 	}
506
 	}
112
 
507
 
113
-	// logout of existing acct
114
-	var newClientAccounts []*Client
115
-	for _, c := range account.Clients {
116
-		if c != client {
117
-			newClientAccounts = append(newClientAccounts, c)
118
-		}
119
-	}
120
-	account.Clients = newClientAccounts
121
-
122
-	client.account = nil
508
+	client.SetAccountName("")
509
+	client.nickTimer.Touch()
123
 
510
 
124
 	// dispatch account-notify
511
 	// dispatch account-notify
125
 	for friend := range client.Friends(caps.AccountNotify) {
512
 	for friend := range client.Friends(caps.AccountNotify) {
129
 
516
 
130
 // successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
517
 // successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
131
 func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
518
 func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
132
-	rb.Add(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))
519
+	rb.Add(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.AccountName(), fmt.Sprintf("You are now logged in as %s", client.AccountName()))
133
 	rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
520
 	rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
134
 
521
 
135
 	// dispatch account-notify
522
 	// dispatch account-notify
136
 	for friend := range client.Friends(caps.AccountNotify) {
523
 	for friend := range client.Friends(caps.AccountNotify) {
137
-		friend.Send(nil, client.nickMaskString, "ACCOUNT", client.account.Name)
524
+		friend.Send(nil, client.nickMaskString, "ACCOUNT", client.AccountName())
138
 	}
525
 	}
139
 }
526
 }

+ 7
- 5
irc/channel.go View File

383
 	for _, member := range channel.Members() {
383
 	for _, member := range channel.Members() {
384
 		if member == client {
384
 		if member == client {
385
 			if member.capabilities.Has(caps.ExtendedJoin) {
385
 			if member.capabilities.Has(caps.ExtendedJoin) {
386
-				rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
386
+				rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
387
 			} else {
387
 			} else {
388
 				rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
388
 				rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
389
 			}
389
 			}
390
 		} else {
390
 		} else {
391
 			if member.capabilities.Has(caps.ExtendedJoin) {
391
 			if member.capabilities.Has(caps.ExtendedJoin) {
392
-				member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
392
+				member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
393
 			} else {
393
 			} else {
394
 				member.Send(nil, client.nickMaskString, "JOIN", channel.name)
394
 				member.Send(nil, client.nickMaskString, "JOIN", channel.name)
395
 			}
395
 			}
407
 	// give channel mode if necessary
407
 	// give channel mode if necessary
408
 	newChannel := firstJoin && !channel.IsRegistered()
408
 	newChannel := firstJoin && !channel.IsRegistered()
409
 	var givenMode *modes.Mode
409
 	var givenMode *modes.Mode
410
-	if client.AccountName() == channel.registeredFounder {
410
+	account := client.Account()
411
+	cffounder, _ := CasefoldName(channel.registeredFounder)
412
+	if account != "" && account == cffounder {
411
 		givenMode = &modes.ChannelFounder
413
 		givenMode = &modes.ChannelFounder
412
 	} else if newChannel {
414
 	} else if newChannel {
413
 		givenMode = &modes.ChannelOperator
415
 		givenMode = &modes.ChannelOperator
419
 	}
421
 	}
420
 
422
 
421
 	if client.capabilities.Has(caps.ExtendedJoin) {
423
 	if client.capabilities.Has(caps.ExtendedJoin) {
422
-		rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
424
+		rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
423
 	} else {
425
 	} else {
424
 		rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
426
 		rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
425
 	}
427
 	}
526
 	if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
528
 	if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
527
 		return false
529
 		return false
528
 	}
530
 	}
529
-	if channel.flags[modes.RegisteredOnly] && client.account == &NoAccount {
531
+	if channel.flags[modes.RegisteredOnly] && client.Account() == "" {
530
 		return false
532
 		return false
531
 	}
533
 	}
532
 	return true
534
 	return true

+ 2
- 2
irc/chanserv.go View File

70
 		return
70
 		return
71
 	}
71
 	}
72
 
72
 
73
-	if client.account == &NoAccount {
73
+	if client.Account() == "" {
74
 		rb.ChanServNotice(client.t("You must be logged in to register a channel"))
74
 		rb.ChanServNotice(client.t("You must be logged in to register a channel"))
75
 		return
75
 		return
76
 	}
76
 	}
77
 
77
 
78
 	// this provides the synchronization that allows exactly one registration of the channel:
78
 	// this provides the synchronization that allows exactly one registration of the channel:
79
-	err = channelInfo.SetRegistered(client.AccountName())
79
+	err = channelInfo.SetRegistered(client.Account())
80
 	if err != nil {
80
 	if err != nil {
81
 		rb.ChanServNotice(err.Error())
81
 		rb.ChanServNotice(err.Error())
82
 		return
82
 		return

+ 16
- 14
irc/client.go View File

36
 
36
 
37
 // Client is an IRC client.
37
 // Client is an IRC client.
38
 type Client struct {
38
 type Client struct {
39
-	account            *ClientAccount
39
+	account            string
40
+	accountName        string
40
 	atime              time.Time
41
 	atime              time.Time
41
 	authorized         bool
42
 	authorized         bool
42
 	awayMessage        string
43
 	awayMessage        string
62
 	nickCasefolded     string
63
 	nickCasefolded     string
63
 	nickMaskCasefolded string
64
 	nickMaskCasefolded string
64
 	nickMaskString     string // cache for nickmask string since it's used with lots of replies
65
 	nickMaskString     string // cache for nickmask string since it's used with lots of replies
66
+	nickTimer          *NickTimer
65
 	operName           string
67
 	operName           string
66
 	proxiedIP          net.IP // actual remote IP if using the PROXY protocol
68
 	proxiedIP          net.IP // actual remote IP if using the PROXY protocol
67
 	quitMessage        string
69
 	quitMessage        string
96
 		flags:          make(map[modes.Mode]bool),
98
 		flags:          make(map[modes.Mode]bool),
97
 		server:         server,
99
 		server:         server,
98
 		socket:         &socket,
100
 		socket:         &socket,
99
-		account:        &NoAccount,
100
 		nick:           "*", // * is used until actual nick is given
101
 		nick:           "*", // * is used until actual nick is given
101
 		nickCasefolded: "*",
102
 		nickCasefolded: "*",
102
 		nickMaskString: "*", // * is used until actual nick is given
103
 		nickMaskString: "*", // * is used until actual nick is given
217
 	client.idletimer = NewIdleTimer(client)
218
 	client.idletimer = NewIdleTimer(client)
218
 	client.idletimer.Start()
219
 	client.idletimer.Start()
219
 
220
 
221
+	client.nickTimer = NewNickTimer(client)
222
+
220
 	// Set the hostname for this client
223
 	// Set the hostname for this client
221
 	// (may be overridden by a later PROXY command from stunnel)
224
 	// (may be overridden by a later PROXY command from stunnel)
222
 	client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr())
225
 	client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr())
299
 	client.TryResume()
302
 	client.TryResume()
300
 
303
 
301
 	// finish registration
304
 	// finish registration
302
-	client.Touch()
303
 	client.updateNickMask("")
305
 	client.updateNickMask("")
304
 	client.server.monitorManager.AlertAbout(client, true)
306
 	client.server.monitorManager.AlertAbout(client, true)
305
 }
307
 }
338
 		return
340
 		return
339
 	}
341
 	}
340
 
342
 
341
-	oldAccountName := oldClient.AccountName()
342
-	newAccountName := client.AccountName()
343
+	oldAccountName := oldClient.Account()
344
+	newAccountName := client.Account()
343
 
345
 
344
 	if oldAccountName == "" || newAccountName == "" || oldAccountName != newAccountName {
346
 	if oldAccountName == "" || newAccountName == "" || oldAccountName != newAccountName {
345
 		client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must be logged into the same account"))
347
 		client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must be logged into the same account"))
406
 			}
408
 			}
407
 
409
 
408
 			if member.capabilities.Has(caps.ExtendedJoin) {
410
 			if member.capabilities.Has(caps.ExtendedJoin) {
409
-				member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
411
+				member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
410
 			} else {
412
 			} else {
411
 				member.Send(nil, client.nickMaskString, "JOIN", channel.name)
413
 				member.Send(nil, client.nickMaskString, "JOIN", channel.name)
412
 			}
414
 			}
589
 
591
 
590
 // LoggedIntoAccount returns true if this client is logged into an account.
592
 // LoggedIntoAccount returns true if this client is logged into an account.
591
 func (client *Client) LoggedIntoAccount() bool {
593
 func (client *Client) LoggedIntoAccount() bool {
592
-	return client.account != nil && client.account != &NoAccount
594
+	return client.Account() != ""
593
 }
595
 }
594
 
596
 
595
 // RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
597
 // RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
687
 		client.idletimer.Stop()
689
 		client.idletimer.Stop()
688
 	}
690
 	}
689
 
691
 
692
+	client.server.accounts.Logout(client)
693
+
690
 	client.socket.Close()
694
 	client.socket.Close()
691
 
695
 
692
 	// send quit messages to friends
696
 	// send quit messages to friends
723
 // Adds account-tag to the line as well.
727
 // Adds account-tag to the line as well.
724
 func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) error {
728
 func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) error {
725
 	// attach account-tag
729
 	// attach account-tag
726
-	if client.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
730
+	if client.capabilities.Has(caps.AccountTag) && client.LoggedIntoAccount() {
727
 		if tags == nil {
731
 		if tags == nil {
728
-			tags = ircmsg.MakeTags("account", from.account.Name)
732
+			tags = ircmsg.MakeTags("account", from.AccountName())
729
 		} else {
733
 		} else {
730
-			(*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
734
+			(*tags)["account"] = ircmsg.MakeTagValue(from.AccountName())
731
 		}
735
 		}
732
 	}
736
 	}
733
 	// attach message-id
737
 	// attach message-id
772
 	maxlenTags, maxlenRest := client.maxlens()
776
 	maxlenTags, maxlenRest := client.maxlens()
773
 	line, err := message.LineMaxLen(maxlenTags, maxlenRest)
777
 	line, err := message.LineMaxLen(maxlenTags, maxlenRest)
774
 	if err != nil {
778
 	if err != nil {
775
-		// try not to fail quietly - especially useful when running tests, as a note to dig deeper
776
-		// log.Println("Error assembling message:")
777
-		// spew.Dump(message)
778
-		// debug.PrintStack()
779
+		logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
780
+		client.server.logger.Error("internal", logline)
779
 
781
 
780
 		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
782
 		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
781
 		line, _ := message.Line()
783
 		line, _ := message.Line()

+ 9
- 0
irc/client_lookup_set.go View File

98
 		return err
98
 		return err
99
 	}
99
 	}
100
 
100
 
101
+	var reservedAccount string
102
+	reservation := client.server.AccountConfig().NickReservation
103
+	if reservation != NickReservationDisabled {
104
+		reservedAccount = client.server.accounts.NickToAccount(newcfnick)
105
+	}
106
+
101
 	clients.Lock()
107
 	clients.Lock()
102
 	defer clients.Unlock()
108
 	defer clients.Unlock()
103
 
109
 
107
 	if currentNewEntry != nil && currentNewEntry != client {
113
 	if currentNewEntry != nil && currentNewEntry != client {
108
 		return errNicknameInUse
114
 		return errNicknameInUse
109
 	}
115
 	}
116
+	if reservation == NickReservationStrict && reservedAccount != client.Account() {
117
+		return errNicknameReserved
118
+	}
110
 	clients.byNick[newcfnick] = client
119
 	clients.byNick[newcfnick] = client
111
 	client.updateNickMask(newNick)
120
 	client.updateNickMask(newNick)
112
 	return nil
121
 	return nil

+ 11
- 7
irc/commands.go View File

39
 		client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
39
 		client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
40
 		return false
40
 		return false
41
 	}
41
 	}
42
-	if !cmd.leaveClientActive {
43
-		client.Active()
44
-	}
45
-	if !cmd.leaveClientIdle {
46
-		client.Touch()
47
-	}
42
+
48
 	rb := NewResponseBuffer(client)
43
 	rb := NewResponseBuffer(client)
49
 	rb.Label = GetLabel(msg)
44
 	rb.Label = GetLabel(msg)
50
 
45
 
57
 		server.tryRegister(client)
52
 		server.tryRegister(client)
58
 	}
53
 	}
59
 
54
 
55
+	if !cmd.leaveClientIdle {
56
+		client.Touch()
57
+	}
58
+
59
+	if !cmd.leaveClientActive {
60
+		client.Active()
61
+	}
62
+
60
 	return exiting
63
 	return exiting
61
 }
64
 }
62
 
65
 
67
 	Commands = map[string]Command{
70
 	Commands = map[string]Command{
68
 		"ACC": {
71
 		"ACC": {
69
 			handler:   accHandler,
72
 			handler:   accHandler,
70
-			minParams: 3,
73
+			minParams: 2,
71
 		},
74
 		},
72
 		"AMBIANCE": {
75
 		"AMBIANCE": {
73
 			handler:   sceneHandler,
76
 			handler:   sceneHandler,
98
 		"DEBUG": {
101
 		"DEBUG": {
99
 			handler:   debugHandler,
102
 			handler:   debugHandler,
100
 			minParams: 1,
103
 			minParams: 1,
104
+			oper:      true,
101
 		},
105
 		},
102
 		"DLINE": {
106
 		"DLINE": {
103
 			handler:   dlineHandler,
107
 			handler:   dlineHandler,

+ 52
- 7
irc/config.go View File

8
 import (
8
 import (
9
 	"crypto/tls"
9
 	"crypto/tls"
10
 	"encoding/json"
10
 	"encoding/json"
11
+	"errors"
11
 	"fmt"
12
 	"fmt"
12
 	"io/ioutil"
13
 	"io/ioutil"
13
 	"log"
14
 	"log"
57
 	return bytes
58
 	return bytes
58
 }
59
 }
59
 
60
 
61
+type NickReservation int
62
+
63
+const (
64
+	NickReservationDisabled NickReservation = iota
65
+	NickReservationWithTimeout
66
+	NickReservationStrict
67
+)
68
+
69
+func (nr *NickReservation) UnmarshalYAML(unmarshal func(interface{}) error) error {
70
+	var orig, raw string
71
+	var err error
72
+	if err = unmarshal(&orig); err != nil {
73
+		return err
74
+	}
75
+	if raw, err = Casefold(orig); err != nil {
76
+		return err
77
+	}
78
+	if raw == "disabled" || raw == "false" || raw == "" {
79
+		*nr = NickReservationDisabled
80
+	} else if raw == "timeout" {
81
+		*nr = NickReservationWithTimeout
82
+	} else if raw == "strict" {
83
+		*nr = NickReservationStrict
84
+	} else {
85
+		return errors.New(fmt.Sprintf("invalid nick-reservation value: %s", orig))
86
+	}
87
+	return nil
88
+}
89
+
90
+type AccountConfig struct {
91
+	Registration           AccountRegistrationConfig
92
+	AuthenticationEnabled  bool            `yaml:"authentication-enabled"`
93
+	NickReservation        NickReservation `yaml:"nick-reservation"`
94
+	NickReservationTimeout time.Duration   `yaml:"nick-reservation-timeout"`
95
+}
96
+
60
 // AccountRegistrationConfig controls account registration.
97
 // AccountRegistrationConfig controls account registration.
61
 type AccountRegistrationConfig struct {
98
 type AccountRegistrationConfig struct {
62
-	Enabled          bool
63
-	EnabledCallbacks []string `yaml:"enabled-callbacks"`
64
-	Callbacks        struct {
99
+	Enabled                bool
100
+	EnabledCallbacks       []string      `yaml:"enabled-callbacks"`
101
+	EnabledCredentialTypes []string      `yaml:"-"`
102
+	VerifyTimeout          time.Duration `yaml:"verify-timeout"`
103
+	Callbacks              struct {
65
 		Mailto struct {
104
 		Mailto struct {
66
 			Server string
105
 			Server string
67
 			Port   int
106
 			Port   int
180
 		Path string
219
 		Path string
181
 	}
220
 	}
182
 
221
 
183
-	Accounts struct {
184
-		Registration          AccountRegistrationConfig
185
-		AuthenticationEnabled bool `yaml:"authentication-enabled"`
186
-	}
222
+	Accounts AccountConfig
187
 
223
 
188
 	Channels struct {
224
 	Channels struct {
189
 		DefaultModes *string `yaml:"default-modes"`
225
 		DefaultModes *string `yaml:"default-modes"`
469
 	}
505
 	}
470
 	config.Logging = newLogConfigs
506
 	config.Logging = newLogConfigs
471
 
507
 
508
+	// hardcode this for now
509
+	config.Accounts.Registration.EnabledCredentialTypes = []string{"passphrase", "certfp"}
510
+	for i, name := range config.Accounts.Registration.EnabledCallbacks {
511
+		if name == "none" {
512
+			// we store "none" as "*" internally
513
+			config.Accounts.Registration.EnabledCallbacks[i] = "*"
514
+		}
515
+	}
516
+
472
 	config.Server.MaxSendQBytes, err = bytefmt.ToBytes(config.Server.MaxSendQString)
517
 	config.Server.MaxSendQBytes, err = bytefmt.ToBytes(config.Server.MaxSendQString)
473
 	if err != nil {
518
 	if err != nil {
474
 		return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
519
 		return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())

+ 19
- 12
irc/errors.go View File

9
 
9
 
10
 // Runtime Errors
10
 // Runtime Errors
11
 var (
11
 var (
12
-	errAccountCreation          = errors.New("Account could not be created")
13
-	errCertfpAlreadyExists      = errors.New("An account already exists with your certificate")
14
-	errChannelAlreadyRegistered = errors.New("Channel is already registered")
15
-	errChannelNameInUse         = errors.New("Channel name in use")
16
-	errInvalidChannelName       = errors.New("Invalid channel name")
17
-	errMonitorLimitExceeded     = errors.New("Monitor limit exceeded")
18
-	errNickMissing              = errors.New("nick missing")
19
-	errNicknameInUse            = errors.New("nickname in use")
20
-	errNoExistingBan            = errors.New("Ban does not exist")
21
-	errNoSuchChannel            = errors.New("No such channel")
22
-	errRenamePrivsNeeded        = errors.New("Only chanops can rename channels")
23
-	errSaslFail                 = errors.New("SASL failed")
12
+	errAccountAlreadyRegistered  = errors.New("Account already exists")
13
+	errAccountCreation           = errors.New("Account could not be created")
14
+	errAccountDoesNotExist       = errors.New("Account does not exist")
15
+	errAccountVerificationFailed = errors.New("Account verification failed")
16
+	errAccountUnverified         = errors.New("Account is not yet verified")
17
+	errAccountAlreadyVerified    = errors.New("Account is already verified")
18
+	errAccountInvalidCredentials = errors.New("Invalid account credentials")
19
+	errCertfpAlreadyExists       = errors.New("An account already exists with your certificate")
20
+	errChannelAlreadyRegistered  = errors.New("Channel is already registered")
21
+	errChannelNameInUse          = errors.New("Channel name in use")
22
+	errInvalidChannelName        = errors.New("Invalid channel name")
23
+	errMonitorLimitExceeded      = errors.New("Monitor limit exceeded")
24
+	errNickMissing               = errors.New("nick missing")
25
+	errNicknameInUse             = errors.New("nickname in use")
26
+	errNicknameReserved          = errors.New("nickname is reserved")
27
+	errNoExistingBan             = errors.New("Ban does not exist")
28
+	errNoSuchChannel             = errors.New("No such channel")
29
+	errRenamePrivsNeeded         = errors.New("Only chanops can rename channels")
30
+	errSaslFail                  = errors.New("SASL failed")
24
 )
31
 )
25
 
32
 
26
 // Socket Errors
33
 // Socket Errors

+ 27
- 1
irc/getters.go View File

56
 	return server.channelRegistrationEnabled
56
 	return server.channelRegistrationEnabled
57
 }
57
 }
58
 
58
 
59
+func (server *Server) AccountConfig() *AccountConfig {
60
+	server.configurableStateMutex.RLock()
61
+	defer server.configurableStateMutex.RUnlock()
62
+	return server.accountConfig
63
+}
64
+
59
 func (client *Client) Nick() string {
65
 func (client *Client) Nick() string {
60
 	client.stateMutex.RLock()
66
 	client.stateMutex.RLock()
61
 	defer client.stateMutex.RUnlock()
67
 	defer client.stateMutex.RUnlock()
104
 	return client.isDestroyed
110
 	return client.isDestroyed
105
 }
111
 }
106
 
112
 
113
+func (client *Client) Account() string {
114
+	client.stateMutex.RLock()
115
+	defer client.stateMutex.RUnlock()
116
+	return client.account
117
+}
118
+
107
 func (client *Client) AccountName() string {
119
 func (client *Client) AccountName() string {
108
 	client.stateMutex.RLock()
120
 	client.stateMutex.RLock()
109
 	defer client.stateMutex.RUnlock()
121
 	defer client.stateMutex.RUnlock()
110
-	return client.account.Name
122
+	if client.accountName == "" {
123
+		return "*"
124
+	}
125
+	return client.accountName
126
+}
127
+
128
+func (client *Client) SetAccountName(account string) {
129
+	var casefoldedAccount string
130
+	if account != "" {
131
+		casefoldedAccount, _ = CasefoldName(account)
132
+	}
133
+	client.stateMutex.Lock()
134
+	defer client.stateMutex.Unlock()
135
+	client.account = casefoldedAccount
136
+	client.accountName = account
111
 }
137
 }
112
 
138
 
113
 func (client *Client) HasMode(mode modes.Mode) bool {
139
 func (client *Client) HasMode(mode modes.Mode) bool {

+ 88
- 192
irc/handlers.go View File

11
 	"encoding/base64"
11
 	"encoding/base64"
12
 	"encoding/json"
12
 	"encoding/json"
13
 	"fmt"
13
 	"fmt"
14
-	"log"
15
 	"net"
14
 	"net"
16
 	"os"
15
 	"os"
17
 	"runtime"
16
 	"runtime"
42
 		return accRegisterHandler(server, client, msg, rb)
41
 		return accRegisterHandler(server, client, msg, rb)
43
 	} else if subcommand == "verify" {
42
 	} else if subcommand == "verify" {
44
 		rb.Notice(client.t("VERIFY is not yet implemented"))
43
 		rb.Notice(client.t("VERIFY is not yet implemented"))
44
+	} else if subcommand == "unregister" {
45
+		return accUnregisterHandler(server, client, msg, rb)
45
 	} else {
46
 	} else {
46
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", msg.Params[0], client.t("Unknown subcommand"))
47
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", msg.Params[0], client.t("Unknown subcommand"))
47
 	}
48
 	}
49
 	return false
50
 	return false
50
 }
51
 }
51
 
52
 
53
+// ACC UNREGISTER <accountname>
54
+func accUnregisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
55
+	// get and sanitise account name
56
+	account := strings.TrimSpace(msg.Params[1])
57
+	casefoldedAccount, err := CasefoldName(account)
58
+	// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
59
+	if err != nil || msg.Params[1] == "*" {
60
+		rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, account, client.t("Account name is not valid"))
61
+		return false
62
+	}
63
+
64
+	if !(account == client.Account() || client.HasRoleCapabs("unregister")) {
65
+		rb.Add(nil, server.name, ERR_NOPRIVS, client.Nick(), account, client.t("Insufficient oper privs"))
66
+		return false
67
+	}
68
+
69
+	err = server.accounts.Unregister(account)
70
+	// TODO better responses all around here
71
+	if err != nil {
72
+		errorMsg := fmt.Sprintf("Unknown error while unregistering account %s", casefoldedAccount)
73
+		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, errorMsg)
74
+		return false
75
+	}
76
+	rb.Notice(fmt.Sprintf("Successfully unregistered account %s", casefoldedAccount))
77
+	return false
78
+}
79
+
52
 // ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
80
 // ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
53
 func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
81
 func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
54
 	// make sure reg is enabled
82
 	// make sure reg is enabled
55
-	if !server.accountRegistration.Enabled {
83
+	if !server.AccountConfig().Registration.Enabled {
56
 		rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("Account registration is disabled"))
84
 		rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("Account registration is disabled"))
57
 		return false
85
 		return false
58
 	}
86
 	}
59
 
87
 
60
 	// clients can't reg new accounts if they're already logged in
88
 	// clients can't reg new accounts if they're already logged in
61
 	if client.LoggedIntoAccount() {
89
 	if client.LoggedIntoAccount() {
62
-		if server.accountRegistration.AllowMultiplePerConnection {
63
-			client.LogoutOfAccount()
90
+		if server.AccountConfig().Registration.AllowMultiplePerConnection {
91
+			server.accounts.Logout(client)
64
 		} else {
92
 		} else {
65
 			rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("You're already logged into an account"))
93
 			rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("You're already logged into an account"))
66
 			return false
94
 			return false
76
 		return false
104
 		return false
77
 	}
105
 	}
78
 
106
 
79
-	// check whether account exists
80
-	// do it all in one write tx to prevent races
81
-	err = server.store.Update(func(tx *buntdb.Tx) error {
82
-		accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
83
-
84
-		_, err := tx.Get(accountKey)
85
-		if err != buntdb.ErrNotFound {
86
-			//TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be
87
-			rb.Add(nil, server.name, ERR_ACCOUNT_ALREADY_EXISTS, client.nick, account, client.t("Account already exists"))
88
-			return errAccountCreation
89
-		}
90
-
91
-		registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
92
-
93
-		tx.Set(accountKey, "1", nil)
94
-		tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil)
95
-		tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil)
96
-		return nil
97
-	})
98
-
99
-	// account could not be created and relevant numerics have been dispatched, abort
100
-	if err != nil {
101
-		if err != errAccountCreation {
102
-			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", client.t("Could not register"))
103
-			log.Println("Could not save registration initial data:", err.Error())
104
-		}
107
+	if len(msg.Params) < 4 {
108
+		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
105
 		return false
109
 		return false
106
 	}
110
 	}
107
 
111
 
108
-	// account didn't already exist, continue with account creation and dispatching verification (if required)
109
 	callback := strings.ToLower(msg.Params[2])
112
 	callback := strings.ToLower(msg.Params[2])
110
 	var callbackNamespace, callbackValue string
113
 	var callbackNamespace, callbackValue string
111
 
114
 
115
 		callbackValues := strings.SplitN(callback, ":", 2)
118
 		callbackValues := strings.SplitN(callback, ":", 2)
116
 		callbackNamespace, callbackValue = callbackValues[0], callbackValues[1]
119
 		callbackNamespace, callbackValue = callbackValues[0], callbackValues[1]
117
 	} else {
120
 	} else {
118
-		callbackNamespace = server.accountRegistration.EnabledCallbacks[0]
121
+		callbackNamespace = server.AccountConfig().Registration.EnabledCallbacks[0]
119
 		callbackValue = callback
122
 		callbackValue = callback
120
 	}
123
 	}
121
 
124
 
122
 	// ensure the callback namespace is valid
125
 	// ensure the callback namespace is valid
123
 	// need to search callback list, maybe look at using a map later?
126
 	// need to search callback list, maybe look at using a map later?
124
 	var callbackValid bool
127
 	var callbackValid bool
125
-	for _, name := range server.accountRegistration.EnabledCallbacks {
128
+	for _, name := range server.AccountConfig().Registration.EnabledCallbacks {
126
 		if callbackNamespace == name {
129
 		if callbackNamespace == name {
127
 			callbackValid = true
130
 			callbackValid = true
128
 		}
131
 		}
130
 
133
 
131
 	if !callbackValid {
134
 	if !callbackValid {
132
 		rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, client.t("Callback namespace is not supported"))
135
 		rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, client.t("Callback namespace is not supported"))
133
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
134
 		return false
136
 		return false
135
 	}
137
 	}
136
 
138
 
140
 	if len(msg.Params) > 4 {
142
 	if len(msg.Params) > 4 {
141
 		credentialType = strings.ToLower(msg.Params[3])
143
 		credentialType = strings.ToLower(msg.Params[3])
142
 		credentialValue = msg.Params[4]
144
 		credentialValue = msg.Params[4]
143
-	} else if len(msg.Params) == 4 {
145
+	} else {
146
+		// exactly 4 params
144
 		credentialType = "passphrase" // default from the spec
147
 		credentialType = "passphrase" // default from the spec
145
 		credentialValue = msg.Params[3]
148
 		credentialValue = msg.Params[3]
146
-	} else {
147
-		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
148
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
149
-		return false
150
 	}
149
 	}
151
 
150
 
152
 	// ensure the credential type is valid
151
 	// ensure the credential type is valid
153
 	var credentialValid bool
152
 	var credentialValid bool
154
-	for _, name := range server.accountRegistration.EnabledCredentialTypes {
153
+	for _, name := range server.AccountConfig().Registration.EnabledCredentialTypes {
155
 		if credentialType == name {
154
 		if credentialType == name {
156
 			credentialValid = true
155
 			credentialValid = true
157
 		}
156
 		}
158
 	}
157
 	}
159
 	if credentialType == "certfp" && client.certfp == "" {
158
 	if credentialType == "certfp" && client.certfp == "" {
160
 		rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate"))
159
 		rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate"))
161
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
162
 		return false
160
 		return false
163
 	}
161
 	}
164
 
162
 
165
 	if !credentialValid {
163
 	if !credentialValid {
166
 		rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("Credential type is not supported"))
164
 		rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("Credential type is not supported"))
167
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
168
 		return false
165
 		return false
169
 	}
166
 	}
170
 
167
 
171
-	// store details
172
-	err = server.store.Update(func(tx *buntdb.Tx) error {
173
-		// certfp special lookup key
174
-		if credentialType == "certfp" {
175
-			assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp)
176
-
177
-			// make sure certfp doesn't already exist because that'd be silly
178
-			_, err := tx.Get(assembledKeyCertToAccount)
179
-			if err != buntdb.ErrNotFound {
180
-				return errCertfpAlreadyExists
181
-			}
182
-
183
-			tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil)
184
-		}
185
-
186
-		// make creds
187
-		var creds AccountCredentials
188
-
189
-		// always set passphrase salt
190
-		creds.PassphraseSalt, err = passwd.NewSalt()
191
-		if err != nil {
192
-			return fmt.Errorf("Could not create passphrase salt: %s", err.Error())
193
-		}
194
-
195
-		if credentialType == "certfp" {
196
-			creds.Certificate = client.certfp
197
-		} else if credentialType == "passphrase" {
198
-			creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, credentialValue)
199
-			if err != nil {
200
-				return fmt.Errorf("Could not hash password: %s", err)
201
-			}
202
-		}
203
-		credText, err := json.Marshal(creds)
204
-		if err != nil {
205
-			return fmt.Errorf("Could not marshal creds: %s", err)
206
-		}
207
-		tx.Set(fmt.Sprintf(keyAccountCredentials, account), string(credText), nil)
208
-
209
-		return nil
210
-	})
211
-
212
-	// details could not be stored and relevant numerics have been dispatched, abort
168
+	var passphrase, certfp string
169
+	if credentialType == "certfp" {
170
+		certfp = client.certfp
171
+	} else if credentialType == "passphrase" {
172
+		passphrase = credentialValue
173
+	}
174
+	err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp)
213
 	if err != nil {
175
 	if err != nil {
214
-		errMsg := "Could not register"
176
+		msg := "Unknown"
177
+		code := ERR_UNKNOWNERROR
215
 		if err == errCertfpAlreadyExists {
178
 		if err == errCertfpAlreadyExists {
216
-			errMsg = "An account already exists for your certificate fingerprint"
179
+			msg = "An account already exists for your certificate fingerprint"
180
+		} else if err == errAccountAlreadyRegistered {
181
+			msg = "Account already exists"
182
+			code = ERR_ACCOUNT_ALREADY_EXISTS
217
 		}
183
 		}
218
-		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", errMsg)
219
-		log.Println("Could not save registration creds:", err.Error())
220
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
184
+		if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
185
+			msg = err.Error()
186
+		}
187
+		rb.Add(nil, server.name, code, client.nick, "ACC", "REGISTER", client.t(msg))
221
 		return false
188
 		return false
222
 	}
189
 	}
223
 
190
 
224
 	// automatically complete registration
191
 	// automatically complete registration
225
 	if callbackNamespace == "*" {
192
 	if callbackNamespace == "*" {
226
-		err = server.store.Update(func(tx *buntdb.Tx) error {
227
-			tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
228
-
229
-			// load acct info inside store tx
230
-			account := ClientAccount{
231
-				Name:         strings.TrimSpace(msg.Params[1]),
232
-				RegisteredAt: time.Now(),
233
-				Clients:      []*Client{client},
234
-			}
235
-			//TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables
236
-			server.accounts[casefoldedAccount] = &account
237
-			client.account = &account
238
-
239
-			rb.Add(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, account.Name, client.t("Account created"))
240
-			rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name))
241
-			rb.Add(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
242
-			server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), account.Name, client.nickMaskString))
243
-			return nil
244
-		})
193
+		err := server.accounts.Verify(client, casefoldedAccount, "")
245
 		if err != nil {
194
 		if err != nil {
246
-			rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", client.t("Could not register"))
247
-			log.Println("Could not save verification confirmation (*):", err.Error())
248
-			removeFailedAccRegisterData(server.store, casefoldedAccount)
249
 			return false
195
 			return false
250
 		}
196
 		}
251
-
252
-		return false
197
+		client.Send(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, casefoldedAccount, client.t("Account created"))
198
+		client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, casefoldedAccount, fmt.Sprintf(client.t("You are now logged in as %s"), casefoldedAccount))
199
+		client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
200
+		server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), casefoldedAccount, client.nickMaskString))
253
 	}
201
 	}
254
 
202
 
255
 	// dispatch callback
203
 	// dispatch callback
261
 // AUTHENTICATE [<mechanism>|<data>|*]
209
 // AUTHENTICATE [<mechanism>|<data>|*]
262
 func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
210
 func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
263
 	// sasl abort
211
 	// sasl abort
264
-	if !server.accountAuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
212
+	if !server.AccountConfig().AuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
265
 		rb.Add(nil, server.name, ERR_SASLABORTED, client.nick, client.t("SASL authentication aborted"))
213
 		rb.Add(nil, server.name, ERR_SASLABORTED, client.nick, client.t("SASL authentication aborted"))
266
 		client.saslInProgress = false
214
 		client.saslInProgress = false
267
 		client.saslMechanism = ""
215
 		client.saslMechanism = ""
374
 		return false
322
 		return false
375
 	}
323
 	}
376
 
324
 
377
-	// load and check acct data all in one update to prevent races.
378
-	// as noted elsewhere, change to proper locking for Account type later probably
379
-	err = server.store.Update(func(tx *buntdb.Tx) error {
380
-		// confirm account is verified
381
-		_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
382
-		if err != nil {
383
-			return errSaslFail
384
-		}
385
-
386
-		creds, err := loadAccountCredentials(tx, accountKey)
387
-		if err != nil {
388
-			return err
389
-		}
390
-
391
-		// ensure creds are valid
392
-		password := string(splitValue[2])
393
-		if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(password) < 1 {
394
-			return errSaslFail
395
-		}
396
-		err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)
397
-
398
-		// succeeded, load account info if necessary
399
-		account, exists := server.accounts[accountKey]
400
-		if !exists {
401
-			account = loadAccount(server, tx, accountKey)
402
-		}
403
-
404
-		client.LoginToAccount(account)
405
-
406
-		return err
407
-	})
408
-
325
+	password := string(splitValue[2])
326
+	err = server.accounts.AuthenticateByPassphrase(client, accountKey, password)
409
 	if err != nil {
327
 	if err != nil {
410
-		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
328
+		msg := authErrorToMessage(server, err)
329
+		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
411
 		return false
330
 		return false
412
 	}
331
 	}
413
 
332
 
415
 	return false
334
 	return false
416
 }
335
 }
417
 
336
 
337
+func authErrorToMessage(server *Server, err error) (msg string) {
338
+	if err == errAccountDoesNotExist || err == errAccountUnverified || err == errAccountInvalidCredentials {
339
+		msg = err.Error()
340
+	} else {
341
+		server.logger.Error("internal", fmt.Sprintf("sasl authentication failure: %v", err))
342
+		msg = "Unknown"
343
+	}
344
+	return
345
+}
346
+
418
 // AUTHENTICATE EXTERNAL
347
 // AUTHENTICATE EXTERNAL
419
 func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
348
 func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
420
 	if client.certfp == "" {
349
 	if client.certfp == "" {
422
 		return false
351
 		return false
423
 	}
352
 	}
424
 
353
 
425
-	err := server.store.Update(func(tx *buntdb.Tx) error {
426
-		// certfp lookup key
427
-		accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, client.certfp))
428
-		if err != nil {
429
-			return errSaslFail
430
-		}
431
-
432
-		// confirm account exists
433
-		_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
434
-		if err != nil {
435
-			return errSaslFail
436
-		}
437
-
438
-		// confirm account is verified
439
-		_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
440
-		if err != nil {
441
-			return errSaslFail
442
-		}
443
-
444
-		// confirm the certfp in that account's credentials
445
-		creds, err := loadAccountCredentials(tx, accountKey)
446
-		if err != nil || creds.Certificate != client.certfp {
447
-			return errSaslFail
448
-		}
449
-
450
-		// succeeded, load account info if necessary
451
-		account, exists := server.accounts[accountKey]
452
-		if !exists {
453
-			account = loadAccount(server, tx, accountKey)
454
-		}
455
-
456
-		client.LoginToAccount(account)
457
-
458
-		return nil
459
-	})
460
-
354
+	err := server.accounts.AuthenticateByCertFP(client)
461
 	if err != nil {
355
 	if err != nil {
462
-		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
356
+		msg := authErrorToMessage(server, err)
357
+		rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
463
 		return false
358
 		return false
464
 	}
359
 	}
465
 
360
 
582
 
477
 
583
 // DEBUG <subcmd>
478
 // DEBUG <subcmd>
584
 func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
479
 func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
585
-	if !client.flags[modes.Operator] {
480
+	param, err := Casefold(msg.Params[0])
481
+	if err != nil {
482
+		return false
483
+	}
484
+
485
+	if !client.HasMode(modes.Operator) {
586
 		return false
486
 		return false
587
 	}
487
 	}
588
 
488
 
589
-	switch msg.Params[0] {
489
+	switch param {
590
 	case "GCSTATS":
490
 	case "GCSTATS":
591
 		stats := debug.GCStats{
491
 		stats := debug.GCStats{
592
 			Pause:          make([]time.Duration, 10),
492
 			Pause:          make([]time.Duration, 10),
2107
 	}
2007
 	}
2108
 
2008
 
2109
 	founder := channel.Founder()
2009
 	founder := channel.Founder()
2110
-	if founder != "" && founder != client.AccountName() {
2010
+	if founder != "" && founder != client.Account() {
2111
 		//TODO(dan): Change this to ERR_CANNOTRENAME
2011
 		//TODO(dan): Change this to ERR_CANNOTRENAME
2112
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, client.t("Only channel founders can change registered channels"))
2012
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, client.t("Only channel founders can change registered channels"))
2113
 		return false
2013
 		return false
2130
 		} else {
2030
 		} else {
2131
 			mcl.Send(nil, mcl.nickMaskString, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
2031
 			mcl.Send(nil, mcl.nickMaskString, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
2132
 			if mcl.capabilities.Has(caps.ExtendedJoin) {
2032
 			if mcl.capabilities.Has(caps.ExtendedJoin) {
2133
-				accountName := "*"
2134
-				if mcl.account != nil {
2135
-					accountName = mcl.account.Name
2136
-				}
2137
-				mcl.Send(nil, mcl.nickMaskString, "JOIN", newName, accountName, mcl.realname)
2033
+				mcl.Send(nil, mcl.nickMaskString, "JOIN", newName, mcl.AccountName(), mcl.realname)
2138
 			} else {
2034
 			} else {
2139
 				mcl.Send(nil, mcl.nickMaskString, "JOIN", newName)
2035
 				mcl.Send(nil, mcl.nickMaskString, "JOIN", newName)
2140
 			}
2036
 			}

+ 77
- 0
irc/idletimer.go View File

165
 		return ""
165
 		return ""
166
 	}
166
 	}
167
 }
167
 }
168
+
169
+// NickTimer manages timing out of clients who are squatting reserved nicks
170
+type NickTimer struct {
171
+	sync.Mutex // tier 1
172
+
173
+	// immutable after construction
174
+	timeout time.Duration
175
+	client  *Client
176
+
177
+	// mutable
178
+	nick           string
179
+	accountForNick string
180
+	account        string
181
+	timer          *time.Timer
182
+}
183
+
184
+// NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled)
185
+func NewNickTimer(client *Client) *NickTimer {
186
+	config := client.server.AccountConfig()
187
+	if config.NickReservation != NickReservationWithTimeout {
188
+		return nil
189
+	}
190
+	nt := NickTimer{
191
+		client:  client,
192
+		timeout: config.NickReservationTimeout,
193
+	}
194
+	return &nt
195
+}
196
+
197
+// Touch records a nick change and updates the timer as necessary
198
+func (nt *NickTimer) Touch() {
199
+	if nt == nil {
200
+		return
201
+	}
202
+
203
+	nick := nt.client.NickCasefolded()
204
+	account := nt.client.Account()
205
+	accountForNick := nt.client.server.accounts.NickToAccount(nick)
206
+
207
+	var shouldWarn bool
208
+
209
+	func() {
210
+		nt.Lock()
211
+		defer nt.Unlock()
212
+		// the timer will not reset as long as the squatter is targeting the same account
213
+		accountChanged := accountForNick != nt.accountForNick
214
+		// change state
215
+		nt.nick = nick
216
+		nt.account = account
217
+		nt.accountForNick = accountForNick
218
+		delinquent := accountForNick != "" && accountForNick != account
219
+
220
+		if nt.timer != nil && (!delinquent || accountChanged) {
221
+			nt.timer.Stop()
222
+			nt.timer = nil
223
+		}
224
+		if delinquent && accountChanged {
225
+			nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
226
+			shouldWarn = true
227
+		}
228
+	}()
229
+
230
+	if shouldWarn {
231
+		nt.sendWarning()
232
+	}
233
+}
234
+
235
+func (nt *NickTimer) sendWarning() {
236
+	baseNotice := "Nickname is reserved; you must change it or authenticate to NickServ within %v"
237
+	nt.client.Notice(fmt.Sprintf(nt.client.t(baseNotice), nt.timeout))
238
+}
239
+
240
+func (nt *NickTimer) processTimeout() {
241
+	baseMsg := "Nick is reserved and authentication timeout expired: %v"
242
+	nt.client.Quit(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout))
243
+	nt.client.destroy(false)
244
+}

+ 6
- 0
irc/nickname.go View File

17
 		"=scene=":  true, // used for rp commands
17
 		"=scene=":  true, // used for rp commands
18
 		"chanserv": true,
18
 		"chanserv": true,
19
 		"nickserv": true,
19
 		"nickserv": true,
20
+		"hostserv": true,
20
 	}
21
 	}
21
 )
22
 )
22
 
23
 
45
 	if err == errNicknameInUse {
46
 	if err == errNicknameInUse {
46
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use"))
47
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use"))
47
 		return false
48
 		return false
49
+	} else if err == errNicknameReserved {
50
+		client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is reserved by a different account"))
51
+		return false
48
 	} else if err != nil {
52
 	} else if err != nil {
49
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
53
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
50
 		return false
54
 		return false
51
 	}
55
 	}
52
 
56
 
57
+	client.nickTimer.Touch()
58
+
53
 	client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
59
 	client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
54
 	if hadNick {
60
 	if hadNick {
55
 		target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname))
61
 		target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname))

+ 21
- 170
irc/nickserv.go View File

4
 package irc
4
 package irc
5
 
5
 
6
 import (
6
 import (
7
-	"encoding/json"
8
 	"fmt"
7
 	"fmt"
9
-	"strconv"
10
 	"strings"
8
 	"strings"
11
-	"time"
12
 
9
 
13
 	"github.com/goshuirc/irc-go/ircfmt"
10
 	"github.com/goshuirc/irc-go/ircfmt"
14
-	"github.com/oragono/oragono/irc/passwd"
15
 	"github.com/oragono/oragono/irc/sno"
11
 	"github.com/oragono/oragono/irc/sno"
16
-	"github.com/tidwall/buntdb"
17
 )
12
 )
18
 
13
 
19
 const nickservHelp = `NickServ lets you register and log into a user account.
14
 const nickservHelp = `NickServ lets you register and log into a user account.
80
 		return
75
 		return
81
 	}
76
 	}
82
 
77
 
83
-	if !server.accountRegistration.Enabled {
78
+	if !server.AccountConfig().Registration.Enabled {
84
 		rb.Notice(client.t("Account registration has been disabled"))
79
 		rb.Notice(client.t("Account registration has been disabled"))
85
 		return
80
 		return
86
 	}
81
 	}
87
 
82
 
88
 	if client.LoggedIntoAccount() {
83
 	if client.LoggedIntoAccount() {
89
-		if server.accountRegistration.AllowMultiplePerConnection {
90
-			client.LogoutOfAccount()
84
+		if server.AccountConfig().Registration.AllowMultiplePerConnection {
85
+			server.accounts.Logout(client)
91
 		} else {
86
 		} else {
92
 			rb.Notice(client.t("You're already logged into an account"))
87
 			rb.Notice(client.t("You're already logged into an account"))
93
 			return
88
 			return
103
 		return
98
 		return
104
 	}
99
 	}
105
 
100
 
106
-	// check whether account exists
107
-	// do it all in one write tx to prevent races
108
-	err = server.store.Update(func(tx *buntdb.Tx) error {
109
-		accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
110
-
111
-		_, err := tx.Get(accountKey)
112
-		if err != buntdb.ErrNotFound {
113
-			//TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be
114
-			rb.Notice(client.t("Account already exists"))
115
-			return errAccountCreation
116
-		}
117
-
118
-		registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
119
-
120
-		tx.Set(accountKey, "1", nil)
121
-		tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil)
122
-		tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil)
123
-		return nil
124
-	})
125
-
126
 	// account could not be created and relevant numerics have been dispatched, abort
101
 	// account could not be created and relevant numerics have been dispatched, abort
127
 	if err != nil {
102
 	if err != nil {
128
 		if err != errAccountCreation {
103
 		if err != errAccountCreation {
131
 		return
106
 		return
132
 	}
107
 	}
133
 
108
 
134
-	// store details
135
-	err = server.store.Update(func(tx *buntdb.Tx) error {
136
-		// certfp special lookup key
137
-		if passphrase == "" {
138
-			assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp)
139
-
140
-			// make sure certfp doesn't already exist because that'd be silly
141
-			_, err := tx.Get(assembledKeyCertToAccount)
142
-			if err != buntdb.ErrNotFound {
143
-				return errCertfpAlreadyExists
144
-			}
145
-
146
-			tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil)
147
-		}
148
-
149
-		// make creds
150
-		var creds AccountCredentials
151
-
152
-		// always set passphrase salt
153
-		creds.PassphraseSalt, err = passwd.NewSalt()
154
-		if err != nil {
155
-			return fmt.Errorf("Could not create passphrase salt: %s", err.Error())
156
-		}
157
-
158
-		if passphrase == "" {
159
-			creds.Certificate = client.certfp
160
-		} else {
161
-			creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
162
-			if err != nil {
163
-				return fmt.Errorf("Could not hash password: %s", err)
164
-			}
165
-		}
166
-		credText, err := json.Marshal(creds)
167
-		if err != nil {
168
-			return fmt.Errorf("Could not marshal creds: %s", err)
169
-		}
170
-		tx.Set(fmt.Sprintf(keyAccountCredentials, account), string(credText), nil)
171
-
172
-		return nil
173
-	})
109
+	err = server.accounts.Register(client, account, "", "", passphrase, client.certfp)
110
+	if err == nil {
111
+		err = server.accounts.Verify(client, casefoldedAccount, "")
112
+	}
174
 
113
 
175
 	// details could not be stored and relevant numerics have been dispatched, abort
114
 	// details could not be stored and relevant numerics have been dispatched, abort
176
 	if err != nil {
115
 	if err != nil {
177
 		errMsg := "Could not register"
116
 		errMsg := "Could not register"
178
 		if err == errCertfpAlreadyExists {
117
 		if err == errCertfpAlreadyExists {
179
 			errMsg = "An account already exists for your certificate fingerprint"
118
 			errMsg = "An account already exists for your certificate fingerprint"
119
+		} else if err == errAccountAlreadyRegistered {
120
+			errMsg = "Account already exists"
180
 		}
121
 		}
181
-		rb.Notice(errMsg)
182
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
122
+		rb.Notice(client.t(errMsg))
183
 		return
123
 		return
184
 	}
124
 	}
185
 
125
 
186
-	err = server.store.Update(func(tx *buntdb.Tx) error {
187
-		tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
188
-
189
-		// load acct info inside store tx
190
-		account := ClientAccount{
191
-			Name:         username,
192
-			RegisteredAt: time.Now(),
193
-			Clients:      []*Client{client},
194
-		}
195
-		//TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables
196
-		server.accounts[casefoldedAccount] = &account
197
-		client.account = &account
198
-
199
-		rb.Notice(client.t("Account created"))
200
-		rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name))
201
-		rb.Add(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
202
-		server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), account.Name, client.nickMaskString))
203
-		return nil
204
-	})
205
-	if err != nil {
206
-		rb.Notice(client.t("Account registration failed"))
207
-		removeFailedAccRegisterData(server.store, casefoldedAccount)
208
-		return
209
-	}
126
+	rb.Notice(client.t("Account created"))
127
+	rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, casefoldedAccount, fmt.Sprintf(client.t("You are now logged in as %s"), casefoldedAccount))
128
+	rb.Add(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
129
+	server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), casefoldedAccount, client.nickMaskString))
210
 }
130
 }
211
 
131
 
212
 func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
132
 func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
213
 	// fail out if we need to
133
 	// fail out if we need to
214
-	if !server.accountAuthenticationEnabled {
134
+	if !server.AccountConfig().AuthenticationEnabled {
215
 		rb.Notice(client.t("Login has been disabled"))
135
 		rb.Notice(client.t("Login has been disabled"))
216
 		return
136
 		return
217
 	}
137
 	}
219
 	// try passphrase
139
 	// try passphrase
220
 	if username != "" && passphrase != "" {
140
 	if username != "" && passphrase != "" {
221
 		// keep it the same as in the ACC CREATE stage
141
 		// keep it the same as in the ACC CREATE stage
222
-		accountKey, err := CasefoldName(username)
142
+		accountName, err := CasefoldName(username)
223
 		if err != nil {
143
 		if err != nil {
224
 			rb.Notice(client.t("Could not login with your username/password"))
144
 			rb.Notice(client.t("Could not login with your username/password"))
225
 			return
145
 			return
226
 		}
146
 		}
227
 
147
 
228
-		// load and check acct data all in one update to prevent races.
229
-		// as noted elsewhere, change to proper locking for Account type later probably
230
-		var accountName string
231
-		err = server.store.Update(func(tx *buntdb.Tx) error {
232
-			// confirm account is verified
233
-			_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
234
-			if err != nil {
235
-				return errSaslFail
236
-			}
237
-
238
-			creds, err := loadAccountCredentials(tx, accountKey)
239
-			if err != nil {
240
-				return err
241
-			}
242
-
243
-			// ensure creds are valid
244
-			if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(passphrase) < 1 {
245
-				return errSaslFail
246
-			}
247
-			err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, passphrase)
248
-
249
-			// succeeded, load account info if necessary
250
-			account, exists := server.accounts[accountKey]
251
-			if !exists {
252
-				account = loadAccount(server, tx, accountKey)
253
-			}
254
-
255
-			client.LoginToAccount(account)
256
-			accountName = account.Name
257
-
258
-			return err
259
-		})
260
-
148
+		err = server.accounts.AuthenticateByPassphrase(client, accountName, passphrase)
261
 		if err == nil {
149
 		if err == nil {
262
 			rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
150
 			rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
263
 			return
151
 			return
265
 	}
153
 	}
266
 
154
 
267
 	// try certfp
155
 	// try certfp
268
-	certfp := client.certfp
269
-	if certfp != "" {
270
-		var accountName string
271
-		err := server.store.Update(func(tx *buntdb.Tx) error {
272
-			// certfp lookup key
273
-			accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, certfp))
274
-			if err != nil {
275
-				return errSaslFail
276
-			}
277
-
278
-			// confirm account exists
279
-			_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
280
-			if err != nil {
281
-				return errSaslFail
282
-			}
283
-
284
-			// confirm account is verified
285
-			_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
286
-			if err != nil {
287
-				return errSaslFail
288
-			}
289
-
290
-			// confirm the certfp in that account's credentials
291
-			creds, err := loadAccountCredentials(tx, accountKey)
292
-			if err != nil || creds.Certificate != client.certfp {
293
-				return errSaslFail
294
-			}
295
-
296
-			// succeeded, load account info if necessary
297
-			account, exists := server.accounts[accountKey]
298
-			if !exists {
299
-				account = loadAccount(server, tx, accountKey)
300
-			}
301
-
302
-			client.LoginToAccount(account)
303
-			accountName = account.Name
304
-
305
-			return nil
306
-		})
307
-
156
+	if client.certfp != "" {
157
+		err := server.accounts.AuthenticateByCertFP(client)
308
 		if err == nil {
158
 		if err == nil {
309
-			rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
159
+			rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), client.AccountName()))
160
+			// TODO more notices?
310
 			return
161
 			return
311
 		}
162
 		}
312
 	}
163
 	}

+ 3
- 3
irc/responsebuffer.go View File

43
 // AddFromClient adds a new message from a specific client to our queue.
43
 // AddFromClient adds a new message from a specific client to our queue.
44
 func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) {
44
 func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) {
45
 	// attach account-tag
45
 	// attach account-tag
46
-	if rb.target.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
46
+	if rb.target.capabilities.Has(caps.AccountTag) && from.LoggedIntoAccount() {
47
 		if tags == nil {
47
 		if tags == nil {
48
-			tags = ircmsg.MakeTags("account", from.account.Name)
48
+			tags = ircmsg.MakeTags("account", from.AccountName())
49
 		} else {
49
 		} else {
50
-			(*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
50
+			(*tags)["account"] = ircmsg.MakeTagValue(from.AccountName())
51
 		}
51
 		}
52
 	}
52
 	}
53
 	// attach message-id
53
 	// attach message-id

+ 65
- 65
irc/server.go View File

87
 
87
 
88
 // Server is the main Oragono server.
88
 // Server is the main Oragono server.
89
 type Server struct {
89
 type Server struct {
90
-	accountAuthenticationEnabled bool
91
-	accountRegistration          *AccountRegistration
92
-	accounts                     map[string]*ClientAccount
93
-	batches                      *BatchManager
94
-	channelRegistrationEnabled   bool
95
-	channels                     *ChannelManager
96
-	channelRegistry              *ChannelRegistry
97
-	checkIdent                   bool
98
-	clients                      *ClientManager
99
-	configFilename               string
100
-	configurableStateMutex       sync.RWMutex // tier 1; generic protection for server state modified by rehash()
101
-	connectionLimiter            *connection_limits.Limiter
102
-	connectionThrottler          *connection_limits.Throttler
103
-	ctime                        time.Time
104
-	defaultChannelModes          modes.Modes
105
-	dlines                       *DLineManager
106
-	loggingRawIO                 bool
107
-	isupport                     *isupport.List
108
-	klines                       *KLineManager
109
-	languages                    *languages.Manager
110
-	limits                       Limits
111
-	listeners                    map[string]*ListenerWrapper
112
-	logger                       *logger.Manager
113
-	MaxSendQBytes                uint64
114
-	monitorManager               *MonitorManager
115
-	motdLines                    []string
116
-	name                         string
117
-	nameCasefolded               string
118
-	networkName                  string
119
-	operators                    map[string]Oper
120
-	operclasses                  map[string]OperClass
121
-	password                     []byte
122
-	passwords                    *passwd.SaltedManager
123
-	recoverFromErrors            bool
124
-	rehashMutex                  sync.Mutex // tier 3
125
-	rehashSignal                 chan os.Signal
126
-	proxyAllowedFrom             []string
127
-	signals                      chan os.Signal
128
-	snomasks                     *SnoManager
129
-	store                        *buntdb.DB
130
-	stsEnabled                   bool
131
-	webirc                       []webircConfig
132
-	whoWas                       *WhoWasList
90
+	accountConfig              *AccountConfig
91
+	accounts                   *AccountManager
92
+	batches                    *BatchManager
93
+	channelRegistrationEnabled bool
94
+	channels                   *ChannelManager
95
+	channelRegistry            *ChannelRegistry
96
+	checkIdent                 bool
97
+	clients                    *ClientManager
98
+	configFilename             string
99
+	configurableStateMutex     sync.RWMutex // tier 1; generic protection for server state modified by rehash()
100
+	connectionLimiter          *connection_limits.Limiter
101
+	connectionThrottler        *connection_limits.Throttler
102
+	ctime                      time.Time
103
+	defaultChannelModes        modes.Modes
104
+	dlines                     *DLineManager
105
+	loggingRawIO               bool
106
+	isupport                   *isupport.List
107
+	klines                     *KLineManager
108
+	languages                  *languages.Manager
109
+	limits                     Limits
110
+	listeners                  map[string]*ListenerWrapper
111
+	logger                     *logger.Manager
112
+	MaxSendQBytes              uint64
113
+	monitorManager             *MonitorManager
114
+	motdLines                  []string
115
+	name                       string
116
+	nameCasefolded             string
117
+	networkName                string
118
+	operators                  map[string]Oper
119
+	operclasses                map[string]OperClass
120
+	password                   []byte
121
+	passwords                  *passwd.SaltedManager
122
+	recoverFromErrors          bool
123
+	rehashMutex                sync.Mutex // tier 4
124
+	rehashSignal               chan os.Signal
125
+	proxyAllowedFrom           []string
126
+	signals                    chan os.Signal
127
+	snomasks                   *SnoManager
128
+	store                      *buntdb.DB
129
+	stsEnabled                 bool
130
+	webirc                     []webircConfig
131
+	whoWas                     *WhoWasList
133
 }
132
 }
134
 
133
 
135
 var (
134
 var (
150
 func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
149
 func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
151
 	// initialize data structures
150
 	// initialize data structures
152
 	server := &Server{
151
 	server := &Server{
153
-		accounts:            make(map[string]*ClientAccount),
154
 		batches:             NewBatchManager(),
152
 		batches:             NewBatchManager(),
155
 		channels:            NewChannelManager(),
153
 		channels:            NewChannelManager(),
156
 		clients:             NewClientManager(),
154
 		clients:             NewClientManager(),
214
 	isupport.Add("UTF8MAPPING", casemappingName)
212
 	isupport.Add("UTF8MAPPING", casemappingName)
215
 
213
 
216
 	// account registration
214
 	// account registration
217
-	if server.accountRegistration.Enabled {
215
+	if server.accountConfig.Registration.Enabled {
218
 		// 'none' isn't shown in the REGCALLBACKS vars
216
 		// 'none' isn't shown in the REGCALLBACKS vars
219
 		var enabledCallbacks []string
217
 		var enabledCallbacks []string
220
-		for _, name := range server.accountRegistration.EnabledCallbacks {
218
+		for _, name := range server.accountConfig.Registration.EnabledCallbacks {
221
 			if name != "*" {
219
 			if name != "*" {
222
 				enabledCallbacks = append(enabledCallbacks, name)
220
 				enabledCallbacks = append(enabledCallbacks, name)
223
 			}
221
 			}
348
 	// make listener
346
 	// make listener
349
 	var listener net.Listener
347
 	var listener net.Listener
350
 	var err error
348
 	var err error
351
-	optionalUnixPrefix := "unix:"
352
-	optionalPrefixLen := len(optionalUnixPrefix)
353
-	if len(addr) >= optionalPrefixLen && strings.ToLower(addr[0:optionalPrefixLen]) == optionalUnixPrefix {
354
-		addr = addr[optionalPrefixLen:]
355
-		if len(addr) == 0 || addr[0] != '/' {
356
-			log.Fatal("Bad unix socket address", addr)
357
-		}
358
-	}
359
-	if len(addr) > 0 && addr[0] == '/' {
349
+	addr = strings.TrimPrefix(addr, "unix:")
350
+	if strings.HasPrefix(addr, "/") {
360
 		// https://stackoverflow.com/a/34881585
351
 		// https://stackoverflow.com/a/34881585
361
 		os.Remove(addr)
352
 		os.Remove(addr)
362
 		listener, err = net.Listen("unix", addr)
353
 		listener, err = net.Listen("unix", addr)
478
 			}
469
 			}
479
 
470
 
480
 			if c.capabilities.Has(caps.ExtendedJoin) {
471
 			if c.capabilities.Has(caps.ExtendedJoin) {
481
-				c.Send(nil, c.nickMaskString, "JOIN", channel.name, c.account.Name, c.realname)
472
+				c.Send(nil, c.nickMaskString, "JOIN", channel.name, c.AccountName(), c.realname)
482
 			} else {
473
 			} else {
483
 				c.Send(nil, c.nickMaskString, "JOIN", channel.name)
474
 				c.Send(nil, c.nickMaskString, "JOIN", channel.name)
484
 			}
475
 			}
630
 	if target.flags[modes.TLS] {
621
 	if target.flags[modes.TLS] {
631
 		rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
622
 		rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
632
 	}
623
 	}
633
-	accountName := target.AccountName()
634
-	if accountName != "" {
635
-		rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, accountName, client.t("is logged in as"))
624
+	if target.LoggedIntoAccount() {
625
+		rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, client.AccountName(), client.t("is logged in as"))
636
 	}
626
 	}
637
 	if target.flags[modes.Bot] {
627
 	if target.flags[modes.Bot] {
638
 		rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
628
 		rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
803
 	server.languages = lm
793
 	server.languages = lm
804
 
794
 
805
 	// SASL
795
 	// SASL
806
-	if config.Accounts.AuthenticationEnabled && !server.accountAuthenticationEnabled {
796
+	oldAccountConfig := server.AccountConfig()
797
+	authPreviouslyEnabled := oldAccountConfig != nil && !oldAccountConfig.AuthenticationEnabled
798
+	if config.Accounts.AuthenticationEnabled && !authPreviouslyEnabled {
807
 		// enabling SASL
799
 		// enabling SASL
808
 		SupportedCapabilities.Enable(caps.SASL)
800
 		SupportedCapabilities.Enable(caps.SASL)
809
 		CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
801
 		CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
810
 		addedCaps.Add(caps.SASL)
802
 		addedCaps.Add(caps.SASL)
811
-	}
812
-	if !config.Accounts.AuthenticationEnabled && server.accountAuthenticationEnabled {
803
+	} else if !config.Accounts.AuthenticationEnabled && authPreviouslyEnabled {
813
 		// disabling SASL
804
 		// disabling SASL
814
 		SupportedCapabilities.Disable(caps.SASL)
805
 		SupportedCapabilities.Disable(caps.SASL)
815
 		removedCaps.Add(caps.SASL)
806
 		removedCaps.Add(caps.SASL)
816
 	}
807
 	}
817
-	server.accountAuthenticationEnabled = config.Accounts.AuthenticationEnabled
808
+
809
+	server.configurableStateMutex.Lock()
810
+	server.accountConfig = &config.Accounts
811
+	server.configurableStateMutex.Unlock()
812
+
813
+	nickReservationPreviouslyDisabled := oldAccountConfig != nil && oldAccountConfig.NickReservation == NickReservationDisabled
814
+	nickReservationNowEnabled := config.Accounts.NickReservation != NickReservationDisabled
815
+	if nickReservationPreviouslyDisabled && nickReservationNowEnabled {
816
+		server.accounts.buildNickToAccountIndex()
817
+	}
818
 
818
 
819
 	// STS
819
 	// STS
820
 	stsValue := config.Server.STS.Value()
820
 	stsValue := config.Server.STS.Value()
902
 	server.checkIdent = config.Server.CheckIdent
902
 	server.checkIdent = config.Server.CheckIdent
903
 
903
 
904
 	// registration
904
 	// registration
905
-	accountReg := NewAccountRegistration(config.Accounts.Registration)
906
-	server.accountRegistration = &accountReg
907
 	server.channelRegistrationEnabled = config.Channels.Registration.Enabled
905
 	server.channelRegistrationEnabled = config.Channels.Registration.Enabled
908
 
906
 
909
 	server.defaultChannelModes = ParseDefaultChannelModes(config)
907
 	server.defaultChannelModes = ParseDefaultChannelModes(config)
1043
 
1041
 
1044
 	server.channelRegistry = NewChannelRegistry(server)
1042
 	server.channelRegistry = NewChannelRegistry(server)
1045
 
1043
 
1044
+	server.accounts = NewAccountManager(server)
1045
+
1046
 	return nil
1046
 	return nil
1047
 }
1047
 }
1048
 
1048
 

+ 9
- 0
oragono.yaml View File

159
     # is account authentication enabled?
159
     # is account authentication enabled?
160
     authentication-enabled: true
160
     authentication-enabled: true
161
 
161
 
162
+    # will the server enforce that only the account holder can use the account name as a nick?
163
+    # options:
164
+    # `disabled`: no enforcement
165
+    # `timeout` (auth to nickserv within some period of time or you're disconnected)
166
+    # `strict`: must authenticate up front with SASL
167
+    nick-reservation: disabled
168
+    nick-reservation-timeout: 30s
169
+
162
 # channel options
170
 # channel options
163
 channels:
171
 channels:
164
     # modes that are set when new channels are created
172
     # modes that are set when new channels are created
210
         capabilities:
218
         capabilities:
211
             - "oper:rehash"
219
             - "oper:rehash"
212
             - "oper:die"
220
             - "oper:die"
221
+            - "unregister"
213
             - "samode"
222
             - "samode"
214
 
223
 
215
 # ircd operators
224
 # ircd operators

Loading…
Cancel
Save