/** 
 * @file llsechandler_basic.cpp
 * @brief Security API for services such as certificate handling
 * secure local storage, etc.
 *
 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */


#include "llviewerprecompiledheaders.h"
#include "llsecapi.h"
#include "llsechandler_basic.h"

#include "llsdserialize.h"
#include "llxorcipher.h"
#include "llfile.h"
#include "lldir.h"
#include "llviewercontrol.h"
#include "llexception.h"
#include "stringize.h"
#include "llappviewer.h"

#include <openssl/evp.h>
#include <openssl/rand.h>
#include <vector>
#include <iomanip>
#include <ctime>

static const std::string DEFAULT_CREDENTIAL_STORAGE = "credential";

// compat
#define COMPAT_STORE_SALT_SIZE 16

// 256 bits of salt data...
#define STORE_SALT_SIZE 32 
#define BUFFER_READ_SIZE 256


// LLSecAPIBasicHandler Class
// Interface handler class for the various security storage handlers.

// We read the file on construction, and write it on destruction.  This
// means multiple processes cannot modify the datastore.
LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file,
										   const std::string& legacy_password_path)
{
	mProtectedDataFilename = protected_data_file;
	mProtectedDataMap = LLSD::emptyMap();
	mLegacyPasswordPath = legacy_password_path;

}

LLSecAPIBasicHandler::LLSecAPIBasicHandler()
{
}


void LLSecAPIBasicHandler::init()
{
	mProtectedDataMap = LLSD::emptyMap();
	if (mProtectedDataFilename.length() == 0)
	{
		mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
															"bin_conf.dat");
        mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat");
	}
	_readProtectedData(); // initialize mProtectedDataMap
						  // may throw LLProtectedDataException if saved datamap is not decryptable
}
LLSecAPIBasicHandler::~LLSecAPIBasicHandler()
{
	_writeProtectedData();
}

// compat_rc4 reads old rc4 encrypted files
void compat_rc4(llifstream &protected_data_stream, std::string &decrypted_data)
{
	U8 salt[COMPAT_STORE_SALT_SIZE];
	U8 buffer[BUFFER_READ_SIZE];
	U8 decrypted_buffer[BUFFER_READ_SIZE];
	int decrypted_length;
	U8 unique_id[32];
	U32 id_len = LLAppViewer::instance()->getMachineID().getUniqueID(unique_id, sizeof(unique_id));
	LLXORCipher cipher(unique_id, id_len);

	// read in the salt and key
	protected_data_stream.read((char *)salt, COMPAT_STORE_SALT_SIZE);
	if (protected_data_stream.gcount() < COMPAT_STORE_SALT_SIZE)
	{
		throw LLProtectedDataException("Config file too short.");
	}

	cipher.decrypt(salt, COMPAT_STORE_SALT_SIZE);

	EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
	EVP_CipherInit_ex(ctx, EVP_rc4(), NULL, salt, NULL, 0); // 0 is decrypt

	while (protected_data_stream.good()) {
		// read data as a block:
		protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE);

		EVP_CipherUpdate(ctx, decrypted_buffer, &decrypted_length,
			buffer, protected_data_stream.gcount());
		decrypted_data.append((const char *)decrypted_buffer, decrypted_length);
	}

	EVP_CipherFinal(ctx, decrypted_buffer, &decrypted_length);
	decrypted_data.append((const char *)decrypted_buffer, decrypted_length);

	EVP_CIPHER_CTX_free(ctx);
}

void LLSecAPIBasicHandler::_readProtectedData()
{	
	// attempt to load the file into our map
	LLPointer<LLSDParser> parser = new LLSDXMLParser();
	llifstream protected_data_stream(mProtectedDataFilename.c_str(), 
									llifstream::binary);

	if (!protected_data_stream.fail()) {
		U8 salt[STORE_SALT_SIZE];
		U8 buffer[BUFFER_READ_SIZE];
		U8 decrypted_buffer[BUFFER_READ_SIZE];
		int decrypted_length;	
		U8 unique_id[32];
        U32 id_len = LLAppViewer::instance()->getMachineID().getUniqueID(unique_id, sizeof(unique_id));
		LLXORCipher cipher(unique_id, id_len);

		// read in the salt and key
		protected_data_stream.read((char *)salt, STORE_SALT_SIZE);
		if (protected_data_stream.gcount() < STORE_SALT_SIZE)
		{
			LLTHROW(LLProtectedDataException("Config file too short."));
		}

		cipher.decrypt(salt, STORE_SALT_SIZE);		

		// totally lame.  As we're not using the OS level protected data, we need to
		// at least obfuscate the data.  We do this by using a salt stored at the head of the file
		// to encrypt the data, therefore obfuscating it from someone using simple existing tools.
		// We do include the MAC address as part of the obfuscation, which would require an
		// attacker to get the MAC address as well as the protected store, which improves things
		// somewhat.  It would be better to use the password, but as this store
		// will be used to store the SL password when the user decides to have SL remember it, 
		// so we can't use that.  OS-dependent store implementations will use the OS password/storage 
		// mechanisms and are considered to be more secure.
		// We've a strong intent to move to OS dependent protected data stores.
		

		// read in the rest of the file.
		EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
		EVP_CipherInit_ex(ctx, EVP_chacha20(), NULL, salt, NULL, 0); // 0 is decrypt

		std::string decrypted_data;
		while(protected_data_stream.good()) {
			// read data as a block:
			protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE);
			
			EVP_CipherUpdate(ctx, decrypted_buffer, &decrypted_length,
							  buffer, protected_data_stream.gcount());
			decrypted_data.append((const char *)decrypted_buffer, decrypted_length);
		}
		
		EVP_CipherFinal(ctx, decrypted_buffer, &decrypted_length);
		decrypted_data.append((const char *)decrypted_buffer, decrypted_length);

		EVP_CIPHER_CTX_free(ctx);
		std::istringstream parse_stream(decrypted_data);
		if (parser->parse(parse_stream, mProtectedDataMap, 
						  LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
		{
			// clear and reset to try compat
			parser->reset();
			decrypted_data.clear();
			protected_data_stream.clear();
			protected_data_stream.seekg(0, std::ios::beg);
			compat_rc4(protected_data_stream, decrypted_data);

			std::istringstream compat_parse_stream(decrypted_data);
			if (parser->parse(compat_parse_stream, mProtectedDataMap,
				LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
			{
				// everything failed abort
				LLTHROW(LLProtectedDataException("Config file cannot be decrypted."));
			}
		}
	}
}

void LLSecAPIBasicHandler::_writeProtectedData()
{	
	std::ostringstream formatted_data_ostream;
	U8 salt[STORE_SALT_SIZE];
	U8 buffer[BUFFER_READ_SIZE];
	U8 encrypted_buffer[BUFFER_READ_SIZE];

	if(mProtectedDataMap.isUndefined())
	{
		LLFile::remove(mProtectedDataFilename);
		return;
	}

	// create a string with the formatted data.
	LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream);
	std::istringstream formatted_data_istream(formatted_data_ostream.str());
	// generate the seed
	RAND_bytes(salt, STORE_SALT_SIZE);

	// write to a temp file so we don't clobber the initial file if there is
	// an error.
	std::string tmp_filename = mProtectedDataFilename + ".tmp";
	
	llofstream protected_data_stream(tmp_filename.c_str(), 
                                     std::ios_base::binary);
	try
	{
		
		EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
		EVP_CipherInit_ex(ctx, EVP_chacha20(), NULL, salt, NULL, 1); // 1 is encrypt
		U8 unique_id[32];
        U32 id_len = LLAppViewer::instance()->getMachineID().getUniqueID(unique_id, sizeof(unique_id));
		LLXORCipher cipher(unique_id, id_len);
		cipher.encrypt(salt, STORE_SALT_SIZE);
		protected_data_stream.write((const char *)salt, STORE_SALT_SIZE);

		int encrypted_length;
		while (formatted_data_istream.good())
		{
			formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE);
			if(formatted_data_istream.gcount() == 0)
			{
				break;
			}
			EVP_CipherUpdate(ctx, encrypted_buffer, &encrypted_length,
						  buffer, formatted_data_istream.gcount());
			protected_data_stream.write((const char *)encrypted_buffer, encrypted_length);
		}

		EVP_CipherFinal(ctx, encrypted_buffer, &encrypted_length);
		protected_data_stream.write((const char *)encrypted_buffer, encrypted_length);
		
		EVP_CIPHER_CTX_free(ctx);

		protected_data_stream.close();
	}
	catch (...)
	{
		LOG_UNHANDLED_EXCEPTION("LLProtectedDataException(Error writing Protected Data Store)");
		// it's good practice to clean up any secure information on error
		// (even though this file isn't really secure.  Perhaps in the future
		// it may be, however.
		LLFile::remove(tmp_filename);

		// EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData()
		// Decided throwing an exception here was overkill until we figure out why this happens
		//LLTHROW(LLProtectedDataException("Error writing Protected Data Store"));
	}

	try
	{
		// move the temporary file to the specified file location.
		if(((	(LLFile::isfile(mProtectedDataFilename) != 0)
			 && (LLFile::remove(mProtectedDataFilename) != 0)))
		   || (LLFile::rename(tmp_filename, mProtectedDataFilename)))
		{
			LL_WARNS() << "LLProtectedDataException(Could not overwrite protected data store)" << LL_ENDL;
			LLFile::remove(tmp_filename);

			// EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData()
			// Decided throwing an exception here was overkill until we figure out why this happens
			//LLTHROW(LLProtectedDataException("Could not overwrite protected data store"));
		}
	}
	catch (...)
	{
		LOG_UNHANDLED_EXCEPTION(STRINGIZE("renaming '" << tmp_filename << "' to '"
										  << mProtectedDataFilename << "'"));
		// it's good practice to clean up any secure information on error
		// (even though this file isn't really secure.  Perhaps in the future
		// it may be, however).
		LLFile::remove(tmp_filename);

		//crash in LLSecAPIBasicHandler::_writeProtectedData()
		// Decided throwing an exception here was overkill until we figure out why this happens
		//LLTHROW(LLProtectedDataException("Error writing Protected Data Store"));
	}
}

		
// retrieve protected data
LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type,
											const std::string& data_id)
{

	if (mProtectedDataMap.has(data_type) && 
		mProtectedDataMap[data_type].isMap() && 
		mProtectedDataMap[data_type].has(data_id))
	{
		return mProtectedDataMap[data_type][data_id];
	}
																				
	return LLSD();
}

void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type,
											   const std::string& data_id)
{
	if (mProtectedDataMap.has(data_type) &&
		mProtectedDataMap[data_type].isMap() &&
		mProtectedDataMap[data_type].has(data_id))
		{
			mProtectedDataMap[data_type].erase(data_id);
		}
}


//
// persist data in a protected store
//
void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type,
											const std::string& data_id,
											const LLSD& data)
{
	if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) {
		mProtectedDataMap[data_type] = LLSD::emptyMap();
	}
	
	mProtectedDataMap[data_type][data_id] = data; 
}

// persist data in a protected store's map
void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type,
											 const std::string& data_id,
											 const std::string& map_elem,
											 const LLSD& data)
{
    if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) {
        mProtectedDataMap[data_type] = LLSD::emptyMap();
    }

    if (!mProtectedDataMap[data_type].has(data_id) || !mProtectedDataMap[data_type][data_id].isMap()) {
        mProtectedDataMap[data_type][data_id] = LLSD::emptyMap();
    }

    mProtectedDataMap[data_type][data_id][map_elem] = data;
}

// remove data from protected store's map
void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type,
												  const std::string& data_id,
												  const std::string& map_elem)
{
    if (mProtectedDataMap.has(data_type) &&
        mProtectedDataMap[data_type].isMap() &&
        mProtectedDataMap[data_type].has(data_id) &&
        mProtectedDataMap[data_type][data_id].isMap() &&
        mProtectedDataMap[data_type][data_id].has(map_elem))
    {
        mProtectedDataMap[data_type][data_id].erase(map_elem);
    }
}

//
// Create a credential object from an identifier and authenticator.  credentials are
// per grid.
LLPointer<LLCredential> LLSecAPIBasicHandler::createCredential(const std::string& grid,
															   const LLSD& identifier, 
															   const LLSD& authenticator)
{
	LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
	result->setCredentialData(identifier, authenticator);
	return result;
}

// Load a credential from default credential store, given the grid
LLPointer<LLCredential> LLSecAPIBasicHandler::loadCredential(const std::string& grid)
{
	LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid);
	LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
	if(credential.isMap() && 
	   credential.has("identifier"))
	{

		LLSD identifier = credential["identifier"];
		LLSD authenticator;
		if (credential.has("authenticator"))
		{
			authenticator = credential["authenticator"];
		}
		result->setCredentialData(identifier, authenticator);
	}
	else
	{
		// credential was not in protected storage, so pull the credential
		// from the legacy store.
		std::string first_name = gSavedSettings.getString("FirstName");
		std::string last_name = gSavedSettings.getString("LastName");
		
		if (!first_name.empty() && !last_name.empty())
		{
			LLSD identifier = LLSD::emptyMap();
			LLSD authenticator;
			identifier["type"] = "agent";
			identifier["first_name"] = first_name;
			identifier["last_name"] = last_name;
			
			std::string legacy_password = _legacyLoadPassword();
			if (legacy_password.length() > 0)
			{
				authenticator = LLSD::emptyMap();
				authenticator["type"] = "hash";
				authenticator["algorithm"] = "md5";
				authenticator["secret"] = legacy_password;
			}
			result->setCredentialData(identifier, authenticator);
		}		
	}
	return result;
}

// Save the credential to the credential store.  Save the authenticator also if requested.
// That feature is used to implement the 'remember password' functionality.
void LLSecAPIBasicHandler::saveCredential(LLPointer<LLCredential> cred, bool save_authenticator)
{
	LLSD credential = LLSD::emptyMap();
	credential["identifier"] = cred->getIdentifier(); 
	if (save_authenticator) 
	{
		credential["authenticator"] = cred->getAuthenticator();
	}
	LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
	setProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid(), credential);
	//*TODO: If we're saving Agni credentials, should we write the
	// credentials to the legacy password.dat/etc?
	_writeProtectedData();
}

// Remove a credential from the credential store.
void LLSecAPIBasicHandler::deleteCredential(LLPointer<LLCredential> cred)
{
	LLSD undefVal;
	deleteProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid());
	cred->setCredentialData(undefVal, undefVal);
	_writeProtectedData();
}

// has map of credentials declared as specific storage
bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLSD credential = getProtectedData(storage, grid);

    return credential.isMap();
}

// returns true if map is empty or does not exist
bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLSD credential = getProtectedData(storage, grid);

    return !credential.isMap() || credential.size() == 0;
}

// Load map of credentials from specified credential store, given the grid
void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLSD credential = getProtectedData(storage, grid);
    if (credential.isMap())
    {
        LLSD::map_const_iterator crd_it = credential.beginMap();
        for (; crd_it != credential.endMap(); crd_it++)
        {
            LLSD::String name = crd_it->first;
            const LLSD &link_map = crd_it->second;
            LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
            if (link_map.has("identifier"))
            {
                LLSD identifier = link_map["identifier"];
                LLSD authenticator;
                if (link_map.has("authenticator"))
                {
                    authenticator = link_map["authenticator"];
                }
                result->setCredentialData(identifier, authenticator);
            }
            credential_map[name] = result;
        }
    }
}

LLPointer<LLCredential> LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);

    LLSD credential = getProtectedData(storage, grid);
    if (credential.isMap() && credential.has(userkey) && credential[userkey].has("identifier"))
    {
        LLSD identifier = credential[userkey]["identifier"];
        LLSD authenticator;
        if (credential[userkey].has("authenticator"))
        {
            authenticator = credential[userkey]["authenticator"];
        }
        result->setCredentialData(identifier, authenticator);
    }

    return result;
}

// add item to map of credentials from specific storage
void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer<LLCredential> cred, bool save_authenticator)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    std::string user_id = cred->userID();
    LLSD credential = LLSD::emptyMap();
    credential["identifier"] = cred->getIdentifier();
    if (save_authenticator)
    {
        credential["authenticator"] = cred->getAuthenticator();
    }
    LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
    addToProtectedMap(storage, cred->getGrid(), user_id, credential);

    _writeProtectedData();
}

// remove item from map of credentials from specific storage
void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer<LLCredential> cred)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLSD undefVal;
    removeFromProtectedMap(storage, cred->getGrid(), cred->userID());
    cred->setCredentialData(undefVal, undefVal);
    _writeProtectedData();
}

// remove item from map of credentials from specific storage
void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey)
{
    if (storage == DEFAULT_CREDENTIAL_STORAGE)
    {
        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
    }

    LLSD undefVal;
    LLPointer<LLCredential> cred = loadFromCredentialMap(storage, grid, userkey);
    removeFromProtectedMap(storage, grid, userkey);
    cred->setCredentialData(undefVal, undefVal);
    _writeProtectedData();
}

// remove item from map of credentials from specific storage
void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid)
{
    deleteProtectedData(storage, grid);
    _writeProtectedData();
}

// load the legacy hash for agni, and decrypt it given the 
// mac address
std::string LLSecAPIBasicHandler::_legacyLoadPassword()
{
	const S32 HASHED_LENGTH = 32;	
	std::vector<U8> buffer(HASHED_LENGTH);
	llifstream password_file(mLegacyPasswordPath.c_str(), llifstream::binary);
	
	if(password_file.fail())
	{
		return std::string("");
	}
	
	password_file.read((char*)&buffer[0], buffer.size());
	if(password_file.gcount() != (S32)buffer.size())
	{
		return std::string("");
	}
	
	// Decipher with MAC address
	U8 unique_id[32];
    U32 id_len = LLAppViewer::instance()->getMachineID().getUniqueID(unique_id, sizeof(unique_id));
	LLXORCipher cipher(unique_id, id_len);
	cipher.decrypt(&buffer[0], buffer.size());
	
	return std::string((const char*)&buffer[0], buffer.size());
}


// return an identifier for the user
std::string LLSecAPIBasicCredential::userID() const
{
	if (!mIdentifier.isMap())
	{
		return mGrid + "(null)";
	}
	else if ((std::string)mIdentifier["type"] == "agent")
	{
		std::string id = (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"];
		LLStringUtil::toLower(id);
		return id;
	}
	else if ((std::string)mIdentifier["type"] == "account")
	{
		std::string id = (std::string)mIdentifier["account_name"];
		LLStringUtil::toLower(id);
		return id;
	}

	return "unknown";
}

// return a printable user identifier
std::string LLSecAPIBasicCredential::asString() const
{
	if (!mIdentifier.isMap())
	{
		return mGrid + ":(null)";
	}
	else if ((std::string)mIdentifier["type"] == "agent")
	{
		return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"];
	}
	else if ((std::string)mIdentifier["type"] == "account")
	{
		return mGrid + ":" + (std::string)mIdentifier["account_name"];
	}

	return mGrid + ":(unknown type)";
}

