auth.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 "PLAIN":
  164. err = socket.loginPlain(cred)
  165. case "MONGO-X509":
  166. err = fmt.Errorf("unsupported authentication mechanism: %s", cred.Mechanism)
  167. default:
  168. // Try SASL for everything else, if it is available.
  169. err = socket.loginSASL(cred)
  170. }
  171. if err != nil {
  172. debugf("Socket %p to %s: login error: %s", socket, socket.addr, err)
  173. } else {
  174. debugf("Socket %p to %s: login successful", socket, socket.addr)
  175. }
  176. return err
  177. }
  178. func (socket *mongoSocket) loginClassic(cred Credential) error {
  179. // Note that this only works properly because this function is
  180. // synchronous, which means the nonce won't get reset while we're
  181. // using it and any other login requests will block waiting for a
  182. // new nonce provided in the defer call below.
  183. nonce, err := socket.getNonce()
  184. if err != nil {
  185. return err
  186. }
  187. defer socket.resetNonce()
  188. psum := md5.New()
  189. psum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
  190. ksum := md5.New()
  191. ksum.Write([]byte(nonce + cred.Username))
  192. ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil))))
  193. key := hex.EncodeToString(ksum.Sum(nil))
  194. cmd := authCmd{Authenticate: 1, User: cred.Username, Nonce: nonce, Key: key}
  195. res := authResult{}
  196. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  197. if !res.Ok {
  198. return errors.New(res.ErrMsg)
  199. }
  200. socket.Lock()
  201. socket.dropAuth(cred.Source)
  202. socket.creds = append(socket.creds, cred)
  203. socket.Unlock()
  204. return nil
  205. })
  206. }
  207. func (socket *mongoSocket) loginPlain(cred Credential) error {
  208. cmd := saslCmd{Start: 1, Mechanism: "PLAIN", Payload: []byte("\x00" + cred.Username + "\x00" + cred.Password)}
  209. res := authResult{}
  210. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  211. if !res.Ok {
  212. return errors.New(res.ErrMsg)
  213. }
  214. socket.Lock()
  215. socket.dropAuth(cred.Source)
  216. socket.creds = append(socket.creds, cred)
  217. socket.Unlock()
  218. return nil
  219. })
  220. }
  221. func (socket *mongoSocket) loginSASL(cred Credential) error {
  222. sasl, err := saslNew(cred, socket.Server().Addr)
  223. if err != nil {
  224. return err
  225. }
  226. defer sasl.Close()
  227. // The goal of this logic is to carry a locked socket until the
  228. // local SASL step confirms the auth is valid; the socket needs to be
  229. // locked so that concurrent action doesn't leave the socket in an
  230. // auth state that doesn't reflect the operations that took place.
  231. // As a simple case, imagine inverting login=>logout to logout=>login.
  232. //
  233. // The logic below works because the lock func isn't called concurrently.
  234. locked := false
  235. lock := func(b bool) {
  236. if locked != b {
  237. locked = b
  238. if b {
  239. socket.Lock()
  240. } else {
  241. socket.Unlock()
  242. }
  243. }
  244. }
  245. lock(true)
  246. defer lock(false)
  247. start := 1
  248. cmd := saslCmd{}
  249. res := saslResult{}
  250. for {
  251. payload, done, err := sasl.Step(res.Payload)
  252. if err != nil {
  253. return err
  254. }
  255. if done && res.Done {
  256. socket.dropAuth(cred.Source)
  257. socket.creds = append(socket.creds, cred)
  258. break
  259. }
  260. lock(false)
  261. cmd = saslCmd{
  262. Start: start,
  263. Continue: 1 - start,
  264. ConversationId: res.ConversationId,
  265. Mechanism: cred.Mechanism,
  266. Payload: payload,
  267. }
  268. start = 0
  269. err = socket.loginRun(cred.Source, &cmd, &res, func() error {
  270. // See the comment on lock for why this is necessary.
  271. lock(true)
  272. if !res.Ok || res.NotOk {
  273. return fmt.Errorf("server returned error on SASL authentication step: %s", res.ErrMsg)
  274. }
  275. return nil
  276. })
  277. if err != nil {
  278. return err
  279. }
  280. if done && res.Done {
  281. socket.dropAuth(cred.Source)
  282. socket.creds = append(socket.creds, cred)
  283. break
  284. }
  285. }
  286. return nil
  287. }
  288. func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error {
  289. var mutex sync.Mutex
  290. var replyErr error
  291. mutex.Lock()
  292. op := queryOp{}
  293. op.query = query
  294. op.collection = db + ".$cmd"
  295. op.limit = -1
  296. op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
  297. defer mutex.Unlock()
  298. if err != nil {
  299. replyErr = err
  300. return
  301. }
  302. err = bson.Unmarshal(docData, result)
  303. if err != nil {
  304. replyErr = err
  305. } else {
  306. // Must handle this within the read loop for the socket, so
  307. // that concurrent login requests are properly ordered.
  308. replyErr = f()
  309. }
  310. }
  311. err := socket.Query(&op)
  312. if err != nil {
  313. return err
  314. }
  315. mutex.Lock() // Wait.
  316. return replyErr
  317. }
  318. func (socket *mongoSocket) Logout(db string) {
  319. socket.Lock()
  320. cred, found := socket.dropAuth(db)
  321. if found {
  322. debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db)
  323. socket.logout = append(socket.logout, cred)
  324. }
  325. socket.Unlock()
  326. }
  327. func (socket *mongoSocket) LogoutAll() {
  328. socket.Lock()
  329. if l := len(socket.creds); l > 0 {
  330. debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l)
  331. socket.logout = append(socket.logout, socket.creds...)
  332. socket.creds = socket.creds[0:0]
  333. }
  334. socket.Unlock()
  335. }
  336. func (socket *mongoSocket) flushLogout() (ops []interface{}) {
  337. socket.Lock()
  338. if l := len(socket.logout); l > 0 {
  339. debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l)
  340. for i := 0; i != l; i++ {
  341. op := queryOp{}
  342. op.query = &logoutCmd{1}
  343. op.collection = socket.logout[i].Source + ".$cmd"
  344. op.limit = -1
  345. ops = append(ops, &op)
  346. }
  347. socket.logout = socket.logout[0:0]
  348. }
  349. socket.Unlock()
  350. return
  351. }
  352. func (socket *mongoSocket) dropAuth(db string) (cred Credential, found bool) {
  353. for i, sockCred := range socket.creds {
  354. if sockCred.Source == db {
  355. copy(socket.creds[i:], socket.creds[i+1:])
  356. socket.creds = socket.creds[:len(socket.creds)-1]
  357. return sockCred, true
  358. }
  359. }
  360. return cred, false
  361. }
  362. func (socket *mongoSocket) dropLogout(cred Credential) (found bool) {
  363. for i, sockCred := range socket.logout {
  364. if sockCred == cred {
  365. copy(socket.logout[i:], socket.logout[i+1:])
  366. socket.logout = socket.logout[:len(socket.logout)-1]
  367. return true
  368. }
  369. }
  370. return false
  371. }