瀏覽代碼

nickserv: Add NS REGISTER and NS IDENTIFY

tags/v0.11.0-beta
Daniel Oaks 6 年之前
父節點
當前提交
6784cf82bf
共有 1 個檔案被更改,包括 286 行新增1 行删除
  1. 286
    1
      irc/nickserv.go

+ 286
- 1
irc/nickserv.go 查看文件

@@ -4,11 +4,30 @@
4 4
 package irc
5 5
 
6 6
 import (
7
+	"encoding/json"
8
+	"fmt"
9
+	"strconv"
7 10
 	"strings"
11
+	"time"
8 12
 
13
+	"github.com/goshuirc/irc-go/ircfmt"
9 14
 	"github.com/goshuirc/irc-go/ircmsg"
15
+	"github.com/oragono/oragono/irc/passwd"
16
+	"github.com/oragono/oragono/irc/sno"
17
+	"github.com/tidwall/buntdb"
10 18
 )
11 19
 
20
+const nickservHelp = `NickServ lets you register and log into a user account.
21
+
22
+To register an account:
23
+	/NS REGISTER username [password]
24
+Leave out [password] if you're registering using your client certificate fingerprint.
25
+
26
+To login to an account:
27
+	/NS IDENTIFY [username password]
28
+Leave out [username password] to use your client certificate fingerprint. Otherwise,
29
+the given username and password will be used.`
30
+
12 31
 // nsHandler handles the /NS and /NICKSERV commands
13 32
 func nsHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
14 33
 	server.nickservReceivePrivmsg(client, strings.Join(msg.Params, " "))
@@ -19,6 +38,272 @@ func (server *Server) nickservReceiveNotice(client *Client, message string) {
19 38
 	// do nothing
20 39
 }
21 40
 
41
+// extractParam extracts a parameter from the given string, returning the param and the rest of the string.
42
+func extractParam(line string) (string, string) {
43
+	rawParams := strings.SplitN(strings.TrimSpace(line), " ", 2)
44
+	param0 := rawParams[0]
45
+	var param1 string
46
+	if 1 < len(rawParams) {
47
+		param1 = strings.TrimSpace(rawParams[1])
48
+	}
49
+	return param0, param1
50
+}
51
+
22 52
 func (server *Server) nickservReceivePrivmsg(client *Client, message string) {
23
-	client.Notice(client.t("NickServ is not yet implemented, sorry! To register an account, check /HELPOP ACC"))
53
+	command, params := extractParam(message)
54
+	command = strings.ToLower(command)
55
+
56
+	if command == "help" {
57
+		for _, line := range strings.Split(nickservHelp, "\n") {
58
+			client.Notice(line)
59
+		}
60
+	} else if command == "register" {
61
+		// get params
62
+		username, passphrase := extractParam(params)
63
+
64
+		// fail out if we need to
65
+		if username == "" {
66
+			client.Notice(client.t("No username supplied"))
67
+			return
68
+		}
69
+
70
+		certfp := client.certfp
71
+		if passphrase == "" && certfp == "" {
72
+			client.Notice(client.t("You need to either supply a passphrase or be connected via TLS with a client cert"))
73
+			return
74
+		}
75
+
76
+		if !server.accountRegistration.Enabled {
77
+			client.Notice(client.t("Account registration has been disabled"))
78
+			return
79
+		}
80
+
81
+		if client.LoggedIntoAccount() {
82
+			if server.accountRegistration.AllowMultiplePerConnection {
83
+				client.LogoutOfAccount()
84
+			} else {
85
+				client.Notice(client.t("You're already logged into an account"))
86
+				return
87
+			}
88
+		}
89
+
90
+		// get and sanitise account name
91
+		account := strings.TrimSpace(username)
92
+		casefoldedAccount, err := CasefoldName(account)
93
+		// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
94
+		if err != nil || username == "*" {
95
+			client.Notice(client.t("Account name is not valid"))
96
+			return
97
+		}
98
+
99
+		// check whether account exists
100
+		// do it all in one write tx to prevent races
101
+		err = server.store.Update(func(tx *buntdb.Tx) error {
102
+			accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
103
+
104
+			_, err := tx.Get(accountKey)
105
+			if err != buntdb.ErrNotFound {
106
+				//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
107
+				client.Notice(client.t("Account already exists"))
108
+				return errAccountCreation
109
+			}
110
+
111
+			registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
112
+
113
+			tx.Set(accountKey, "1", nil)
114
+			tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil)
115
+			tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil)
116
+			return nil
117
+		})
118
+
119
+		// account could not be created and relevant numerics have been dispatched, abort
120
+		if err != nil {
121
+			if err != errAccountCreation {
122
+				client.Notice(client.t("Account registration failed"))
123
+			}
124
+			return
125
+		}
126
+
127
+		// store details
128
+		err = server.store.Update(func(tx *buntdb.Tx) error {
129
+			// certfp special lookup key
130
+			if passphrase == "" {
131
+				assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp)
132
+
133
+				// make sure certfp doesn't already exist because that'd be silly
134
+				_, err := tx.Get(assembledKeyCertToAccount)
135
+				if err != buntdb.ErrNotFound {
136
+					return errCertfpAlreadyExists
137
+				}
138
+
139
+				tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil)
140
+			}
141
+
142
+			// make creds
143
+			var creds AccountCredentials
144
+
145
+			// always set passphrase salt
146
+			creds.PassphraseSalt, err = passwd.NewSalt()
147
+			if err != nil {
148
+				return fmt.Errorf("Could not create passphrase salt: %s", err.Error())
149
+			}
150
+
151
+			if passphrase == "" {
152
+				creds.Certificate = client.certfp
153
+			} else {
154
+				creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
155
+				if err != nil {
156
+					return fmt.Errorf("Could not hash password: %s", err)
157
+				}
158
+			}
159
+			credText, err := json.Marshal(creds)
160
+			if err != nil {
161
+				return fmt.Errorf("Could not marshal creds: %s", err)
162
+			}
163
+			tx.Set(fmt.Sprintf(keyAccountCredentials, account), string(credText), nil)
164
+
165
+			return nil
166
+		})
167
+
168
+		// details could not be stored and relevant numerics have been dispatched, abort
169
+		if err != nil {
170
+			errMsg := "Could not register"
171
+			if err == errCertfpAlreadyExists {
172
+				errMsg = "An account already exists for your certificate fingerprint"
173
+			}
174
+			client.Notice(errMsg)
175
+			removeFailedAccRegisterData(server.store, casefoldedAccount)
176
+			return
177
+		}
178
+
179
+		err = server.store.Update(func(tx *buntdb.Tx) error {
180
+			tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
181
+
182
+			// load acct info inside store tx
183
+			account := ClientAccount{
184
+				Name:         username,
185
+				RegisteredAt: time.Now(),
186
+				Clients:      []*Client{client},
187
+			}
188
+			//TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables
189
+			server.accounts[casefoldedAccount] = &account
190
+			client.account = &account
191
+
192
+			client.Notice(client.t("Account created"))
193
+			client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name))
194
+			client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
195
+			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))
196
+			return nil
197
+		})
198
+		if err != nil {
199
+			client.Notice(client.t("Account registration failed"))
200
+			removeFailedAccRegisterData(server.store, casefoldedAccount)
201
+			return
202
+		}
203
+
204
+	} else if command == "identify" {
205
+		// fail out if we need to
206
+		if !server.accountAuthenticationEnabled {
207
+			client.Notice(client.t("Login has been disabled"))
208
+			return
209
+		}
210
+
211
+		// try passphrase
212
+		username, passphrase := extractParam(params)
213
+		if username != "" && passphrase != "" {
214
+			// keep it the same as in the ACC CREATE stage
215
+			accountKey, err := CasefoldName(username)
216
+			if err != nil {
217
+				client.Notice(client.t("Could not login with your username/password"))
218
+				return
219
+			}
220
+
221
+			// load and check acct data all in one update to prevent races.
222
+			// as noted elsewhere, change to proper locking for Account type later probably
223
+			var accountName string
224
+			err = server.store.Update(func(tx *buntdb.Tx) error {
225
+				// confirm account is verified
226
+				_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
227
+				if err != nil {
228
+					return errSaslFail
229
+				}
230
+
231
+				creds, err := loadAccountCredentials(tx, accountKey)
232
+				if err != nil {
233
+					return err
234
+				}
235
+
236
+				// ensure creds are valid
237
+				if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(passphrase) < 1 {
238
+					return errSaslFail
239
+				}
240
+				err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, passphrase)
241
+
242
+				// succeeded, load account info if necessary
243
+				account, exists := server.accounts[accountKey]
244
+				if !exists {
245
+					account = loadAccount(server, tx, accountKey)
246
+				}
247
+
248
+				client.LoginToAccount(account)
249
+				accountName = account.Name
250
+
251
+				return err
252
+			})
253
+
254
+			if err == nil {
255
+				client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
256
+				return
257
+			}
258
+		}
259
+
260
+		// try certfp
261
+		certfp := client.certfp
262
+		if certfp != "" {
263
+			var accountName string
264
+			err := server.store.Update(func(tx *buntdb.Tx) error {
265
+				// certfp lookup key
266
+				accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, certfp))
267
+				if err != nil {
268
+					return errSaslFail
269
+				}
270
+
271
+				// confirm account exists
272
+				_, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey))
273
+				if err != nil {
274
+					return errSaslFail
275
+				}
276
+
277
+				// confirm account is verified
278
+				_, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey))
279
+				if err != nil {
280
+					return errSaslFail
281
+				}
282
+
283
+				// confirm the certfp in that account's credentials
284
+				creds, err := loadAccountCredentials(tx, accountKey)
285
+				if err != nil || creds.Certificate != client.certfp {
286
+					return errSaslFail
287
+				}
288
+
289
+				// succeeded, load account info if necessary
290
+				account, exists := server.accounts[accountKey]
291
+				if !exists {
292
+					account = loadAccount(server, tx, accountKey)
293
+				}
294
+
295
+				client.LoginToAccount(account)
296
+				accountName = account.Name
297
+
298
+				return nil
299
+			})
300
+
301
+			if err == nil {
302
+				client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
303
+				return
304
+			}
305
+		}
306
+
307
+		client.Notice(client.t("Could not login with your TLS certificate or supplied username/password"))
308
+	}
24 309
 }

Loading…
取消
儲存