auth.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. package mgo
  27. import (
  28. "crypto/md5"
  29. "encoding/hex"
  30. "errors"
  31. "fmt"
  32. "labix.org/v2/mgo/bson"
  33. "sync"
  34. )
  35. type authCmd struct {
  36. Authenticate int
  37. Nonce string
  38. User string
  39. Key string
  40. }
  41. type startSaslCmd struct {
  42. StartSASL int `bson:"startSasl"`
  43. }
  44. type authResult struct {
  45. ErrMsg string
  46. Ok bool
  47. }
  48. type getNonceCmd struct {
  49. GetNonce int
  50. }
  51. type getNonceResult struct {
  52. Nonce string
  53. Err string "$err"
  54. Code int
  55. }
  56. type logoutCmd struct {
  57. Logout int
  58. }
  59. type saslCmd struct {
  60. Start int `bson:"saslStart,omitempty"`
  61. Continue int `bson:"saslContinue,omitempty"`
  62. ConversationId int `bson:"conversationId,omitempty"`
  63. Mechanism string `bson:"mechanism,omitempty"`
  64. Payload []byte
  65. }
  66. type saslResult struct {
  67. Ok bool `bson:"ok"`
  68. NotOk bool `bson:"code"` // Server <= 2.3.2 returns ok=1 & code>0 on errors (WTF?)
  69. Done bool
  70. ConversationId int `bson:"conversationId"`
  71. Payload []byte
  72. ErrMsg string
  73. }
  74. type saslStepper interface {
  75. Step(serverData []byte) (clientData []byte, done bool, err error)
  76. Close()
  77. }
  78. func (socket *mongoSocket) getNonce() (nonce string, err error) {
  79. socket.Lock()
  80. for socket.cachedNonce == "" && socket.dead == nil {
  81. debugf("Socket %p to %s: waiting for nonce", socket, socket.addr)
  82. socket.gotNonce.Wait()
  83. }
  84. if socket.cachedNonce == "mongos" {
  85. socket.Unlock()
  86. return "", errors.New("Can't authenticate with mongos; see http://j.mp/mongos-auth")
  87. }
  88. debugf("Socket %p to %s: got nonce", socket, socket.addr)
  89. nonce, err = socket.cachedNonce, socket.dead
  90. socket.cachedNonce = ""
  91. socket.Unlock()
  92. if err != nil {
  93. nonce = ""
  94. }
  95. return
  96. }
  97. func (socket *mongoSocket) resetNonce() {
  98. debugf("Socket %p to %s: requesting a new nonce", socket, socket.addr)
  99. op := &queryOp{}
  100. op.query = &getNonceCmd{GetNonce: 1}
  101. op.collection = "admin.$cmd"
  102. op.limit = -1
  103. op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
  104. if err != nil {
  105. socket.kill(errors.New("getNonce: "+err.Error()), true)
  106. return
  107. }
  108. result := &getNonceResult{}
  109. err = bson.Unmarshal(docData, &result)
  110. if err != nil {
  111. socket.kill(errors.New("Failed to unmarshal nonce: "+err.Error()), true)
  112. return
  113. }
  114. debugf("Socket %p to %s: nonce unmarshalled: %#v", socket, socket.addr, result)
  115. if result.Code == 13390 {
  116. // mongos doesn't yet support auth (see http://j.mp/mongos-auth)
  117. result.Nonce = "mongos"
  118. } else if result.Nonce == "" {
  119. var msg string
  120. if result.Err != "" {
  121. msg = fmt.Sprintf("Got an empty nonce: %s (%d)", result.Err, result.Code)
  122. } else {
  123. msg = "Got an empty nonce"
  124. }
  125. socket.kill(errors.New(msg), true)
  126. return
  127. }
  128. socket.Lock()
  129. if socket.cachedNonce != "" {
  130. socket.Unlock()
  131. panic("resetNonce: nonce already cached")
  132. }
  133. socket.cachedNonce = result.Nonce
  134. socket.gotNonce.Signal()
  135. socket.Unlock()
  136. }
  137. err := socket.Query(op)
  138. if err != nil {
  139. socket.kill(errors.New("resetNonce: "+err.Error()), true)
  140. }
  141. }
  142. func (socket *mongoSocket) Login(cred Credential) error {
  143. socket.Lock()
  144. for _, sockCred := range socket.creds {
  145. if sockCred == cred {
  146. debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, cred.Source, cred.Username)
  147. socket.Unlock()
  148. return nil
  149. }
  150. }
  151. if socket.dropLogout(cred) {
  152. debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, cred.Source, cred.Username)
  153. socket.creds = append(socket.creds, cred)
  154. socket.Unlock()
  155. return nil
  156. }
  157. socket.Unlock()
  158. debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, cred.Source, cred.Username)
  159. var err error
  160. switch cred.Mechanism {
  161. case "", "MONGO-CR":
  162. err = socket.loginClassic(cred)
  163. case "MONGO-X509":
  164. err = fmt.Errorf("unsupported authentication mechanism: %s", cred.Mechanism)
  165. default:
  166. // Try SASL for everything else, if it is available.
  167. err = socket.loginSASL(cred)
  168. }
  169. if err != nil {
  170. debugf("Socket %p to %s: login error: %s", socket, socket.addr, err)
  171. } else {
  172. debugf("Socket %p to %s: login successful", socket, socket.addr)
  173. }
  174. return err
  175. }
  176. func (socket *mongoSocket) loginClassic(cred Credential) error {
  177. // Note that this only works properly because this function is
  178. // synchronous, which means the nonce won't get reset while we're
  179. // using it and any other login requests will block waiting for a
  180. // new nonce provided in the defer call below.
  181. nonce, err := socket.getNonce()
  182. if err != nil {
  183. return err
  184. }
  185. defer socket.resetNonce()
  186. psum := md5.New()
  187. psum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
  188. ksum := md5.New()
  189. ksum.Write([]byte(nonce + cred.Username))
  190. ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil))))
  191. key := hex.EncodeToString(ksum.Sum(nil))
  192. cmd := authCmd{Authenticate: 1, User: cred.Username, Nonce: nonce, Key: key}
  193. res := authResult{}
  194. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  195. if !res.Ok {
  196. return errors.New(res.ErrMsg)
  197. }
  198. socket.Lock()
  199. socket.dropAuth(cred.Source)
  200. socket.creds = append(socket.creds, cred)
  201. socket.Unlock()
  202. return nil
  203. })
  204. }
  205. func (socket *mongoSocket) loginSASL(cred Credential) error {
  206. sasl, err := saslNew(cred, socket.Server().Addr)
  207. if err != nil {
  208. return err
  209. }
  210. defer sasl.Close()
  211. // The goal of this logic is to carry a locked socket until the
  212. // local SASL step confirms the auth is valid; the socket needs to be
  213. // locked so that concurrent action doesn't leave the socket in an
  214. // auth state that doesn't reflect the operations that took place.
  215. // As a simple case, imagine inverting login=>logout to logout=>login.
  216. //
  217. // The logic below works because the lock func isn't called concurrently.
  218. locked := false
  219. lock := func(b bool) {
  220. if locked != b {
  221. locked = b
  222. if b {
  223. socket.Lock()
  224. } else {
  225. socket.Unlock()
  226. }
  227. }
  228. }
  229. lock(true)
  230. defer lock(false)
  231. start := 1
  232. cmd := saslCmd{}
  233. res := saslResult{}
  234. for {
  235. payload, done, err := sasl.Step(res.Payload)
  236. if err != nil {
  237. return err
  238. }
  239. if done && res.Done {
  240. socket.dropAuth(cred.Source)
  241. socket.creds = append(socket.creds, cred)
  242. break
  243. }
  244. lock(false)
  245. cmd = saslCmd{
  246. Start: start,
  247. Continue: 1 - start,
  248. ConversationId: res.ConversationId,
  249. Mechanism: cred.Mechanism,
  250. Payload: payload,
  251. }
  252. start = 0
  253. err = socket.loginRun(cred.Source, &cmd, &res, func() error {
  254. // See the comment on lock for why this is necessary.
  255. lock(true)
  256. if !res.Ok || res.NotOk {
  257. return fmt.Errorf("server returned error on SASL authentication step: %s", res.ErrMsg)
  258. }
  259. return nil
  260. })
  261. if err != nil {
  262. return err
  263. }
  264. if done && res.Done {
  265. socket.dropAuth(cred.Source)
  266. socket.creds = append(socket.creds, cred)
  267. break
  268. }
  269. }
  270. return nil
  271. }
  272. func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error {
  273. var mutex sync.Mutex
  274. var replyErr error
  275. mutex.Lock()
  276. op := queryOp{}
  277. op.query = query
  278. op.collection = db + ".$cmd"
  279. op.limit = -1
  280. op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
  281. defer mutex.Unlock()
  282. if err != nil {
  283. replyErr = err
  284. return
  285. }
  286. err = bson.Unmarshal(docData, result)
  287. if err != nil {
  288. replyErr = err
  289. } else {
  290. // Must handle this within the read loop for the socket, so
  291. // that concurrent login requests are properly ordered.
  292. replyErr = f()
  293. }
  294. }
  295. err := socket.Query(&op)
  296. if err != nil {
  297. return err
  298. }
  299. mutex.Lock() // Wait.
  300. return replyErr
  301. }
  302. func (socket *mongoSocket) Logout(db string) {
  303. socket.Lock()
  304. cred, found := socket.dropAuth(db)
  305. if found {
  306. debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db)
  307. socket.logout = append(socket.logout, cred)
  308. }
  309. socket.Unlock()
  310. }
  311. func (socket *mongoSocket) LogoutAll() {
  312. socket.Lock()
  313. if l := len(socket.creds); l > 0 {
  314. debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l)
  315. socket.logout = append(socket.logout, socket.creds...)
  316. socket.creds = socket.creds[0:0]
  317. }
  318. socket.Unlock()
  319. }
  320. func (socket *mongoSocket) flushLogout() (ops []interface{}) {
  321. socket.Lock()
  322. if l := len(socket.logout); l > 0 {
  323. debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l)
  324. for i := 0; i != l; i++ {
  325. op := queryOp{}
  326. op.query = &logoutCmd{1}
  327. op.collection = socket.logout[i].Source + ".$cmd"
  328. op.limit = -1
  329. ops = append(ops, &op)
  330. }
  331. socket.logout = socket.logout[0:0]
  332. }
  333. socket.Unlock()
  334. return
  335. }
  336. func (socket *mongoSocket) dropAuth(db string) (cred Credential, found bool) {
  337. for i, sockCred := range socket.creds {
  338. if sockCred.Source == db {
  339. copy(socket.creds[i:], socket.creds[i+1:])
  340. socket.creds = socket.creds[:len(socket.creds)-1]
  341. return sockCred, true
  342. }
  343. }
  344. return cred, false
  345. }
  346. func (socket *mongoSocket) dropLogout(cred Credential) (found bool) {
  347. for i, sockCred := range socket.logout {
  348. if sockCred == cred {
  349. copy(socket.logout[i:], socket.logout[i+1:])
  350. socket.logout = socket.logout[:len(socket.logout)-1]
  351. return true
  352. }
  353. }
  354. return false
  355. }