// security_commands.cpp
/*
 *    Copyright (C) 2010 10gen Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// security.cpp links with both dbgrid and db.  this file db only -- at least for now.

// security.cpp

#include "pch.h"
#include "security.h"
#include "../util/md5.hpp"
#include "json.h"
#include "pdfile.h"
#include "db.h"
#include "dbhelpers.h"
#include "commands.h"
#include "jsobj.h"
#include "client.h"

namespace mongo {

    /* authentication

       system.users contains
         { user : <username>, pwd : <pwd_digest>, ... }

       getnonce sends nonce to client

       client then sends { authenticate:1, nonce64:<nonce_str>, user:<username>, key:<key> }

       where <key> is md5(<nonce_str><username><pwd_digest_str>) as a string
    */

    boost::thread_specific_ptr<nonce64> lastNonce;

    class CmdGetNonce : public Command {
    public:
        virtual bool requiresAuth() { return false; }
        virtual bool logTheOp() { return false; }
        virtual bool slaveOk() const {
            return true;
        }
        void help(stringstream& h) const { h << "internal"; }
        virtual LockType locktype() const { return NONE; }
        CmdGetNonce() : Command("getnonce") {}
        bool run(const string&, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
            nonce64 *n = new nonce64(Security::getNonce());
            stringstream ss;
            ss << hex << *n;
            result.append("nonce", ss.str() );
            lastNonce.reset(n);
            return true;
        }
    } cmdGetNonce;

    CmdLogout cmdLogout;

    bool CmdAuthenticate::run(const string& dbname , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
        log() << " authenticate: " << cmdObj << endl;

        string user = cmdObj.getStringField("user");
        string key = cmdObj.getStringField("key");
        string received_nonce = cmdObj.getStringField("nonce");

        if( user.empty() || key.empty() || received_nonce.empty() ) {
            log() << "field missing/wrong type in received authenticate command "
                  << dbname
                  << endl;
            errmsg = "auth fails";
            sleepmillis(10);
            return false;
        }

        stringstream digestBuilder;

        {
            bool reject = false;
            nonce64 *ln = lastNonce.release();
            if ( ln == 0 ) {
                reject = true;
                log(1) << "auth: no lastNonce" << endl;
            }
            else {
                digestBuilder << hex << *ln;
                reject = digestBuilder.str() != received_nonce;
                if ( reject ) log(1) << "auth: different lastNonce" << endl;
            }

            if ( reject ) {
                log() << "auth: bad nonce received or getnonce not called. could be a driver bug or a security attack. db:" << dbname << endl;
                errmsg = "auth fails";
                sleepmillis(30);
                return false;
            }
        }

        BSONObj userObj;
        string pwd;
        if (!getUserObj(dbname, user, userObj, pwd)) {
            errmsg = "auth fails";
            return false;
        }

        md5digest d;
        {
            digestBuilder << user << pwd;
            string done = digestBuilder.str();

            md5_state_t st;
            md5_init(&st);
            md5_append(&st, (const md5_byte_t *) done.c_str(), done.size());
            md5_finish(&st, d);
        }

        string computed = digestToString( d );

        if ( key != computed ) {
            log() << "auth: key mismatch " << user << ", ns:" << dbname << endl;
            errmsg = "auth fails";
            return false;
        }

        authenticate(dbname, user, userObj[ "readOnly" ].isBoolean() && userObj[ "readOnly" ].boolean());

        return true;
    }

    CmdAuthenticate cmdAuthenticate;

} // namespace mongo
