/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * In addition, as two special exceptions:
 *
 * 1) IDEALX S.A.S gives permission to:
 *  * link the code of portions of his program with the OpenSSL library under
 *    certain conditions described in each source file
 *  * distribute linked combinations including the two, with respect to the
 *    OpenSSL license and with the GPL
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception,
 * you may extend this exception to your version of the file(s), but you are
 * not obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version, in all files (this very one along with all
 * source files).

 * 2) IDEALX S.A.S acknowledges that portions of his sourcecode uses (by the
 * way of headers inclusion) some work published by 'RSA Security Inc.'. Those
 * portions are "derived from the RSA Security Inc. PKCS #11Cryptographic
 * Token Interface (Cryptoki)" as described in each individual source file.
 */

#include "P11Session.hh"
#include "string.h"
#include "P11Error.hh"

namespace Cryptonit
{

P11Session::P11Session(P11Error * em, 
			PasswordManager * pm, 
			CK_FUNCTION_LIST_PTR functions,
			CK_SLOT_ID slotid,
			CK_STATE state) :
	err(em),
	passMan(pm), 
	funcs(functions), 
	slotId(slotid), 
	alive(false), 
	logged_in(false)
{
#ifdef DEBUG
 	std::cout << "Creating a session with slot " << slotId 
		<< "..." << std::endl;
#endif 
	// by default, open a R/O session with the slot
	open(state);
}


P11Session::~P11Session()
{
	if(logged()) logout();
	if(live()) close();
#ifdef DEBUG
 	std::cout << "Session to slot " << slotId << " terminated." << std::endl;
#endif 
}


CK_RV P11Session::open(CK_STATE session_type)
{
	CK_RV rv;
	// this flag is mandatory, for compatibility purposes
	CK_FLAGS flags = CKF_SERIAL_SESSION;
	
	switch(session_type)
	{
		case CKS_RW_PUBLIC_SESSION:
		case CKS_RW_USER_FUNCTIONS:
		case CKS_RW_SO_FUNCTIONS:
		{
			// if there is a RO session, close it then open a RW session
			CK_STATE current_state = getState();
			if(live() &&
				(current_state == CKS_RO_PUBLIC_SESSION ||
				current_state == CKS_RO_USER_FUNCTIONS))
			{
				if(current_state == CKS_RO_USER_FUNCTIONS)
				{
					rv = funcs->C_Logout(h_session);
					err->checkRV(rv);
				}
				rv = funcs->C_CloseAllSessions(slotId);
				err->checkRV(rv);
			}
			flags = flags | CKF_RW_SESSION ;
			break;
		}
		case CKS_RO_PUBLIC_SESSION:
		case CKS_RO_USER_FUNCTIONS:
		{
			CK_STATE current_state = getState();
			if(live() &&
				(current_state == CKS_RW_PUBLIC_SESSION ||
				current_state == CKS_RW_USER_FUNCTIONS ||
				current_state == CKS_RW_SO_FUNCTIONS))
			{
				if(current_state == CKS_RW_USER_FUNCTIONS ||
					current_state == CKS_RW_SO_FUNCTIONS)
				{
					rv = funcs->C_Logout(h_session);
					err->checkRV(rv);
				}
				rv = funcs->C_CloseAllSessions(slotId);
				err->checkRV(rv);
			}
			break;
		}
	}
#ifdef DEBUG
 	std::cout << "Opening session with slot " << slotId << "... ";
#endif 
	rv = funcs->C_OpenSession(slotId, flags, NULL_PTR, NULL_PTR, &h_session);
#ifdef DEBUG
 	std::cout << "Session has handle " << h_session << std::endl;
#endif 
	err->checkRV(rv);
	if(rv == CKR_OK)
	{
		alive = true;
		rv = updateInfos();

		if(session_type == CKS_RW_USER_FUNCTIONS ||
			session_type == CKS_RO_USER_FUNCTIONS)
		{
			rv = login(CKU_USER);
		}
		else if(session_type == CKS_RW_SO_FUNCTIONS)
		{
			rv = login(CKU_SO);
		}
	}
	return rv;
}


CK_RV P11Session::close()
{
	CK_RV rv = CKR_OK;
	if(live())
	{
		rv = funcs->C_CloseAllSessions(slotId);
		err->checkRV(rv);
		alive = false;
	}
	return rv;
}


CK_RV P11Session::login(CK_USER_TYPE user_type)
{
	char * password;
	unsigned long pass_length;
	CK_RV rv = CKR_OK;
	
	if(logged())
	{
		if((user_type == CKU_USER && getState() == CKS_RW_SO_FUNCTIONS) ||
			(user_type == CKU_SO && getState() == CKS_RW_USER_FUNCTIONS))
		{
			if((rv = funcs->C_Logout(slotId)))
			{
				err->checkRV(rv);
				rv = updateInfos();
				err->checkRV(rv);
			}
		}
		// TODO: report that the login attempt is useless ?;
		else return CKR_USER_ALREADY_LOGGED_IN;
	}
	else
	{
#ifdef DEBUG
 		std::cout << "Logging in session " << h_session
			<< " with slot " << slotId << "..." << std::endl;
#endif 
		if(!live())
		{
		return CKR_SESSION_HANDLE_INVALID;
		}
		bool ok = false;
		// retrieve the password with the callback function
		if(user_type == CKU_USER)
		{
			ok = passMan->getPassword(std::string("Please enter the user PIN : "), 
					std::string(""),
					&password, 
					0, 
					NULL_PTR);
			//ok = passwordCallback(REQUEST_PIN), password, &pass_length);
		}
		else if (user_type == CKU_SO)
		{
			ok = passMan->getPassword(std::string("Please enter the Security Officer PIN : "), 
					std::string(""),
					&password, 
					0, 
					NULL_PTR);
			//ok = passwordCallback(REQUEST_SOPIN), password, &pass_length);
		}
		if(ok == true)
		{
			pass_length = strlen(password);
			// rudimentary safeguard against overflows
			if(pass_length > LIBP11CRYPTONIT_MAX_PASS_LENGTH)
				pass_length = LIBP11CRYPTONIT_MAX_PASS_LENGTH;
				
			rv = funcs->C_Login(h_session, CKU_USER, (CK_UTF8CHAR*)password, pass_length);
			err->checkRV(rv);
			// TODO: check the flags !
			if(rv == CKR_OK)
			{
				logged_in = true;
				rv = updateInfos();
				err->checkRV(rv);
			}
		}
	}
	return rv;
}


CK_RV P11Session::logout()
{
	CK_RV rv;
#ifdef DEBUG
 	std::cout << "Logging out of session " << h_session
		<< " with slot " << slotId << "..." << std::endl;
#endif 
	rv = funcs->C_Logout(h_session);
	updateInfos();
	return rv;
}


CK_RV P11Session::updateInfos()
{
	return funcs->C_GetSessionInfo(h_session, &infos);
}


/*
bool P11Session::setPasswordCallback(bool (*fct)(PIN_REQUEST_TYPE, char *, unsigned long *))
{
	if(fct)
	{
		passwordCallback = fct;
		return true;
	}
	else return false;
}
*/

} // namespace Cryptonit
