~rogpeppe/+junk/mgo-tagged-log-messages

« back to all changes in this revision

Viewing changes to auth.go

  • Committer: Roger Peppe
  • Date: 2014-03-14 18:11:33 UTC
  • mfrom: (263.1.8 master)
  • Revision ID: roger.peppe@canonical.com-20140314181133-107ag3xpitk9682u
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
        "sync"
36
36
)
37
37
 
38
 
type authInfo struct {
39
 
        db, user, pass string
40
 
}
41
 
 
42
38
type authCmd struct {
43
 
        Authenticate     int
44
 
        Nonce, User, Key string
 
39
        Authenticate int
 
40
 
 
41
        Nonce string
 
42
        User  string
 
43
        Key   string
 
44
}
 
45
 
 
46
type startSaslCmd struct {
 
47
        StartSASL int `bson:"startSasl"`
45
48
}
46
49
 
47
50
type authResult struct {
63
66
        Logout int
64
67
}
65
68
 
 
69
type saslCmd struct {
 
70
        Start          int    `bson:"saslStart,omitempty"`
 
71
        Continue       int    `bson:"saslContinue,omitempty"`
 
72
        ConversationId int    `bson:"conversationId,omitempty"`
 
73
        Mechanism      string `bson:"mechanism,omitempty"`
 
74
        Payload        []byte
 
75
}
 
76
 
 
77
type saslResult struct {
 
78
        Ok    bool `bson:"ok"`
 
79
        NotOk bool `bson:"code"` // Server <= 2.3.2 returns ok=1 & code>0 on errors (WTF?)
 
80
        Done  bool
 
81
 
 
82
        ConversationId int `bson:"conversationId"`
 
83
        Payload        []byte
 
84
        ErrMsg         string
 
85
}
 
86
 
 
87
type saslStepper interface {
 
88
        Step(serverData []byte) (clientData []byte, done bool, err error)
 
89
        Close()
 
90
}
 
91
 
66
92
func (socket *mongoSocket) getNonce() (nonce string, err error) {
67
93
        socket.Lock()
68
94
        for socket.cachedNonce == "" && socket.dead == nil {
129
155
        }
130
156
}
131
157
 
132
 
func (socket *mongoSocket) Login(db string, user string, pass string) error {
 
158
func (socket *mongoSocket) Login(cred Credential) error {
133
159
        socket.Lock()
134
 
        for _, a := range socket.auth {
135
 
                if a.db == db && a.user == user && a.pass == pass {
136
 
                        socket.debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, db, user)
 
160
        for _, sockCred := range socket.creds {
 
161
                if sockCred == cred {
 
162
                        socket.debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, cred.Source, cred.Username)
137
163
                        socket.Unlock()
138
164
                        return nil
139
165
                }
140
166
        }
141
 
        if auth, found := socket.dropLogout(db, user, pass); found {
142
 
                socket.debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, db, user)
143
 
                socket.auth = append(socket.auth, auth)
 
167
        if socket.dropLogout(cred) {
 
168
                socket.debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, cred.Source, cred.Username)
 
169
                socket.creds = append(socket.creds, cred)
144
170
                socket.Unlock()
145
171
                return nil
146
172
        }
147
173
        socket.Unlock()
148
174
 
149
 
        socket.debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, db, user)
150
 
 
 
175
        socket.debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, cred.Source, cred.Username)
 
176
 
 
177
        var err error
 
178
        switch cred.Mechanism {
 
179
        case "", "MONGO-CR":
 
180
                err = socket.loginClassic(cred)
 
181
        case "MONGO-X509":
 
182
                err = fmt.Errorf("unsupported authentication mechanism: %s", cred.Mechanism)
 
183
        default:
 
184
                // Try SASL for everything else, if it is available.
 
185
                err = socket.loginSASL(cred)
 
186
        }
 
187
 
 
188
        if err != nil {
 
189
                socket.debugf("Socket %p to %s: login error: %s", socket, socket.addr, err)
 
190
        } else {
 
191
                socket.debugf("Socket %p to %s: login successful", socket, socket.addr)
 
192
        }
 
193
        return err
 
194
}
 
195
 
 
196
func (socket *mongoSocket) loginClassic(cred Credential) error {
151
197
        // Note that this only works properly because this function is
152
198
        // synchronous, which means the nonce won't get reset while we're
153
199
        // using it and any other login requests will block waiting for a
159
205
        defer socket.resetNonce()
160
206
 
161
207
        psum := md5.New()
162
 
        psum.Write([]byte(user + ":mongo:" + pass))
 
208
        psum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
163
209
 
164
210
        ksum := md5.New()
165
 
        ksum.Write([]byte(nonce + user))
 
211
        ksum.Write([]byte(nonce + cred.Username))
166
212
        ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil))))
167
213
 
168
214
        key := hex.EncodeToString(ksum.Sum(nil))
169
215
 
170
 
        cmd := authCmd{Authenticate: 1, User: user, Nonce: nonce, Key: key}
171
 
 
 
216
        cmd := authCmd{Authenticate: 1, User: cred.Username, Nonce: nonce, Key: key}
 
217
        res := authResult{}
 
218
        return socket.loginRun(cred.Source, &cmd, &res, func() error {
 
219
                if !res.Ok {
 
220
                        return errors.New(res.ErrMsg)
 
221
                }
 
222
                socket.Lock()
 
223
                socket.dropAuth(cred.Source)
 
224
                socket.creds = append(socket.creds, cred)
 
225
                socket.Unlock()
 
226
                return nil
 
227
        })
 
228
}
 
229
 
 
230
func (socket *mongoSocket) loginSASL(cred Credential) error {
 
231
        sasl, err := saslNew(cred, socket.Server().Addr)
 
232
        if err != nil {
 
233
                return err
 
234
        }
 
235
        defer sasl.Close()
 
236
 
 
237
        // The goal of this logic is to carry a locked socket until the
 
238
        // local SASL step confirms the auth is valid; the socket needs to be
 
239
        // locked so that concurrent action doesn't leave the socket in an
 
240
        // auth state that doesn't reflect the operations that took place.
 
241
        // As a simple case, imagine inverting login=>logout to logout=>login.
 
242
        //
 
243
        // The logic below works because the lock func isn't called concurrently.
 
244
        locked := false
 
245
        lock := func(b bool) {
 
246
                if locked != b {
 
247
                        locked = b
 
248
                        if b {
 
249
                                socket.Lock()
 
250
                        } else {
 
251
                                socket.Unlock()
 
252
                        }
 
253
                }
 
254
        }
 
255
 
 
256
        lock(true)
 
257
        defer lock(false)
 
258
 
 
259
        start := 1
 
260
        cmd := saslCmd{}
 
261
        res := saslResult{}
 
262
        for {
 
263
                payload, done, err := sasl.Step(res.Payload)
 
264
                if err != nil {
 
265
                        return err
 
266
                }
 
267
                if done && res.Done {
 
268
                        socket.dropAuth(cred.Source)
 
269
                        socket.creds = append(socket.creds, cred)
 
270
                        break
 
271
                }
 
272
                lock(false)
 
273
 
 
274
                cmd = saslCmd{
 
275
                        Start:          start,
 
276
                        Continue:       1 - start,
 
277
                        ConversationId: res.ConversationId,
 
278
                        Mechanism:      cred.Mechanism,
 
279
                        Payload:        payload,
 
280
                }
 
281
                start = 0
 
282
                err = socket.loginRun(cred.Source, &cmd, &res, func() error {
 
283
                        // See the comment on lock for why this is necessary.
 
284
                        lock(true)
 
285
                        if !res.Ok || res.NotOk {
 
286
                                return fmt.Errorf("server returned error on SASL authentication step: %s", res.ErrMsg)
 
287
                        }
 
288
                        return nil
 
289
                })
 
290
                if err != nil {
 
291
                        return err
 
292
                }
 
293
                if done && res.Done {
 
294
                        socket.dropAuth(cred.Source)
 
295
                        socket.creds = append(socket.creds, cred)
 
296
                        break
 
297
                }
 
298
        }
 
299
 
 
300
        return nil
 
301
}
 
302
 
 
303
func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error {
172
304
        var mutex sync.Mutex
173
305
        var replyErr error
174
306
        mutex.Lock()
175
307
 
176
308
        op := queryOp{}
177
 
        op.query = &cmd
 
309
        op.query = query
178
310
        op.collection = db + ".$cmd"
179
311
        op.limit = -1
180
312
        op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
185
317
                        return
186
318
                }
187
319
 
188
 
                // Must handle this within the read loop for the socket, so
189
 
                // that concurrent login requests are properly ordered.
190
 
                result := &authResult{}
191
320
                err = bson.Unmarshal(docData, result)
192
321
                if err != nil {
193
322
                        replyErr = err
194
 
                        return
195
 
                }
196
 
                if !result.Ok {
197
 
                        replyErr = errors.New(result.ErrMsg)
198
 
                }
199
 
 
200
 
                socket.Lock()
201
 
                socket.dropAuth(db)
202
 
                socket.auth = append(socket.auth, authInfo{db, user, pass})
203
 
                socket.Unlock()
 
323
                } else {
 
324
                        // Must handle this within the read loop for the socket, so
 
325
                        // that concurrent login requests are properly ordered.
 
326
                        replyErr = f()
 
327
                }
204
328
        }
205
329
 
206
 
        err = socket.Query(&op)
 
330
        err := socket.Query(&op)
207
331
        if err != nil {
208
332
                return err
209
333
        }
218
342
 
219
343
func (socket *mongoSocket) Logout(db string) {
220
344
        socket.Lock()
221
 
        auth, found := socket.dropAuth(db)
 
345
        cred, found := socket.dropAuth(db)
222
346
        if found {
223
347
                socket.debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db)
224
 
                socket.logout = append(socket.logout, auth)
 
348
                socket.logout = append(socket.logout, cred)
225
349
        }
226
350
        socket.Unlock()
227
351
}
228
352
 
229
353
func (socket *mongoSocket) LogoutAll() {
230
354
        socket.Lock()
231
 
        if l := len(socket.auth); l > 0 {
 
355
        if l := len(socket.creds); l > 0 {
232
356
                socket.debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l)
233
 
                socket.logout = append(socket.logout, socket.auth...)
234
 
                socket.auth = socket.auth[0:0]
 
357
                socket.logout = append(socket.logout, socket.creds...)
 
358
                socket.creds = socket.creds[0:0]
235
359
        }
236
360
        socket.Unlock()
237
361
}
243
367
                for i := 0; i != l; i++ {
244
368
                        op := queryOp{}
245
369
                        op.query = &logoutCmd{1}
246
 
                        op.collection = socket.logout[i].db + ".$cmd"
 
370
                        op.collection = socket.logout[i].Source + ".$cmd"
247
371
                        op.limit = -1
248
372
                        ops = append(ops, &op)
249
373
                }
253
377
        return
254
378
}
255
379
 
256
 
func (socket *mongoSocket) dropAuth(db string) (auth authInfo, found bool) {
257
 
        for i, a := range socket.auth {
258
 
                if a.db == db {
259
 
                        copy(socket.auth[i:], socket.auth[i+1:])
260
 
                        socket.auth = socket.auth[:len(socket.auth)-1]
261
 
                        return a, true
 
380
func (socket *mongoSocket) dropAuth(db string) (cred Credential, found bool) {
 
381
        for i, sockCred := range socket.creds {
 
382
                if sockCred.Source == db {
 
383
                        copy(socket.creds[i:], socket.creds[i+1:])
 
384
                        socket.creds = socket.creds[:len(socket.creds)-1]
 
385
                        return sockCred, true
262
386
                }
263
387
        }
264
 
        return auth, false
 
388
        return cred, false
265
389
}
266
390
 
267
 
func (socket *mongoSocket) dropLogout(db, user, pass string) (auth authInfo, found bool) {
268
 
        for i, a := range socket.logout {
269
 
                if a.db == db && a.user == user && a.pass == pass {
 
391
func (socket *mongoSocket) dropLogout(cred Credential) (found bool) {
 
392
        for i, sockCred := range socket.logout {
 
393
                if sockCred == cred {
270
394
                        copy(socket.logout[i:], socket.logout[i+1:])
271
395
                        socket.logout = socket.logout[:len(socket.logout)-1]
272
 
                        return a, true
 
396
                        return true
273
397
                }
274
398
        }
275
 
        return auth, false
 
399
        return false
276
400
}