github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/third_party/labix.org/v2/mgo/auth.go (about) 1 // mgo - MongoDB driver for Go 2 // 3 // Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net> 4 // 5 // All rights reserved. 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions are met: 9 // 10 // 1. Redistributions of source code must retain the above copyright notice, this 11 // list of conditions and the following disclaimer. 12 // 2. Redistributions in binary form must reproduce the above copyright notice, 13 // this list of conditions and the following disclaimer in the documentation 14 // and/or other materials provided with the distribution. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27 package mgo 28 29 import ( 30 "camlistore.org/third_party/labix.org/v2/mgo/bson" 31 "crypto/md5" 32 "encoding/hex" 33 "errors" 34 "fmt" 35 "sync" 36 ) 37 38 type authInfo struct { 39 db, user, pass string 40 } 41 42 type authCmd struct { 43 Authenticate int 44 Nonce, User, Key string 45 } 46 47 type authResult struct { 48 ErrMsg string 49 Ok bool 50 } 51 52 type getNonceCmd struct { 53 GetNonce int 54 } 55 56 type getNonceResult struct { 57 Nonce string 58 Err string "$err" 59 Code int 60 } 61 62 type logoutCmd struct { 63 Logout int 64 } 65 66 func (socket *mongoSocket) getNonce() (nonce string, err error) { 67 socket.Lock() 68 for socket.cachedNonce == "" && socket.dead == nil { 69 debugf("Socket %p to %s: waiting for nonce", socket, socket.addr) 70 socket.gotNonce.Wait() 71 } 72 if socket.cachedNonce == "mongos" { 73 socket.Unlock() 74 return "", errors.New("Can't authenticate with mongos; see http://j.mp/mongos-auth") 75 } 76 debugf("Socket %p to %s: got nonce", socket, socket.addr) 77 nonce, err = socket.cachedNonce, socket.dead 78 socket.cachedNonce = "" 79 socket.Unlock() 80 if err != nil { 81 nonce = "" 82 } 83 return 84 } 85 86 func (socket *mongoSocket) resetNonce() { 87 debugf("Socket %p to %s: requesting a new nonce", socket, socket.addr) 88 op := &queryOp{} 89 op.query = &getNonceCmd{GetNonce: 1} 90 op.collection = "admin.$cmd" 91 op.limit = -1 92 op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) { 93 if err != nil { 94 socket.kill(errors.New("getNonce: "+err.Error()), true) 95 return 96 } 97 result := &getNonceResult{} 98 err = bson.Unmarshal(docData, &result) 99 if err != nil { 100 socket.kill(errors.New("Failed to unmarshal nonce: "+err.Error()), true) 101 return 102 } 103 debugf("Socket %p to %s: nonce unmarshalled: %#v", socket, socket.addr, result) 104 if result.Code == 13390 { 105 // mongos doesn't yet support auth (see http://j.mp/mongos-auth) 106 result.Nonce = "mongos" 107 } else if result.Nonce == "" { 108 var msg string 109 if result.Err != "" { 110 msg = fmt.Sprintf("Got an empty nonce: %s (%d)", result.Err, result.Code) 111 } else { 112 msg = "Got an empty nonce" 113 } 114 socket.kill(errors.New(msg), true) 115 return 116 } 117 socket.Lock() 118 if socket.cachedNonce != "" { 119 socket.Unlock() 120 panic("resetNonce: nonce already cached") 121 } 122 socket.cachedNonce = result.Nonce 123 socket.gotNonce.Signal() 124 socket.Unlock() 125 } 126 err := socket.Query(op) 127 if err != nil { 128 socket.kill(errors.New("resetNonce: "+err.Error()), true) 129 } 130 } 131 132 func (socket *mongoSocket) Login(db string, user string, pass string) error { 133 socket.Lock() 134 for _, a := range socket.auth { 135 if a.db == db && a.user == user && a.pass == pass { 136 debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, db, user) 137 socket.Unlock() 138 return nil 139 } 140 } 141 if auth, found := socket.dropLogout(db, user, pass); found { 142 debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, db, user) 143 socket.auth = append(socket.auth, auth) 144 socket.Unlock() 145 return nil 146 } 147 socket.Unlock() 148 149 debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, db, user) 150 151 // Note that this only works properly because this function is 152 // synchronous, which means the nonce won't get reset while we're 153 // using it and any other login requests will block waiting for a 154 // new nonce provided in the defer call below. 155 nonce, err := socket.getNonce() 156 if err != nil { 157 return err 158 } 159 defer socket.resetNonce() 160 161 psum := md5.New() 162 psum.Write([]byte(user + ":mongo:" + pass)) 163 164 ksum := md5.New() 165 ksum.Write([]byte(nonce + user)) 166 ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil)))) 167 168 key := hex.EncodeToString(ksum.Sum(nil)) 169 170 cmd := authCmd{Authenticate: 1, User: user, Nonce: nonce, Key: key} 171 172 var mutex sync.Mutex 173 var replyErr error 174 mutex.Lock() 175 176 op := queryOp{} 177 op.query = &cmd 178 op.collection = db + ".$cmd" 179 op.limit = -1 180 op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) { 181 defer mutex.Unlock() 182 183 if err != nil { 184 replyErr = err 185 return 186 } 187 188 // Must handle this within the read loop for the socket, so 189 // that concurrent login requests are properly ordered. 190 result := &authResult{} 191 err = bson.Unmarshal(docData, result) 192 if err != nil { 193 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() 204 } 205 206 err = socket.Query(&op) 207 if err != nil { 208 return err 209 } 210 mutex.Lock() // Wait. 211 if replyErr != nil { 212 debugf("Socket %p to %s: login error: %s", socket, socket.addr, replyErr) 213 } else { 214 debugf("Socket %p to %s: login successful", socket, socket.addr) 215 } 216 return replyErr 217 } 218 219 func (socket *mongoSocket) Logout(db string) { 220 socket.Lock() 221 auth, found := socket.dropAuth(db) 222 if found { 223 debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db) 224 socket.logout = append(socket.logout, auth) 225 } 226 socket.Unlock() 227 } 228 229 func (socket *mongoSocket) LogoutAll() { 230 socket.Lock() 231 if l := len(socket.auth); l > 0 { 232 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] 235 } 236 socket.Unlock() 237 } 238 239 func (socket *mongoSocket) flushLogout() (ops []interface{}) { 240 socket.Lock() 241 if l := len(socket.logout); l > 0 { 242 debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l) 243 for i := 0; i != l; i++ { 244 op := queryOp{} 245 op.query = &logoutCmd{1} 246 op.collection = socket.logout[i].db + ".$cmd" 247 op.limit = -1 248 ops = append(ops, &op) 249 } 250 socket.logout = socket.logout[0:0] 251 } 252 socket.Unlock() 253 return 254 } 255 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 262 } 263 } 264 return auth, false 265 } 266 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 { 270 copy(socket.logout[i:], socket.logout[i+1:]) 271 socket.logout = socket.logout[:len(socket.logout)-1] 272 return a, true 273 } 274 } 275 return auth, false 276 }