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  }