/*
 * 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 "P11Manager.hh"

#ifndef LIBP11CRYPTONIT_MAX_PATH_LENGTH
#define LIBP11CRYPTONIT_MAX_PATH_LENGTH 1024
#endif

namespace Cryptonit
{

P11Manager::P11Manager(P11Error * em, 
				PasswordManager * pm,
				const char * lib_path) :
		err(em), passMan(pm), funcs(NULL_PTR), loaded(false)
{
	unsigned int path_length = strlen(lib_path);
	// safeguard against too long paths
	if(path_length > LIBP11CRYPTONIT_MAX_PATH_LENGTH)
		path_length = LIBP11CRYPTONIT_MAX_PATH_LENGTH;
	libPath = new std::string(lib_path, path_length);
	
	if(em != NULL && pm !=NULL)
	{
		start();
	}
}

P11Manager::~P11Manager()
{
	stop();
	delete libPath;
}

int P11Manager::restart()
{
	CK_RV rv;
#ifdef DEBUG
 	std::cout << "Restarting the Cryptoki library..." << std::endl;
#endif 
	rv = stop();
	if(rv == CKR_OK) rv = start();
	return rv;
}

CK_RV P11Manager::start()
{
	CK_RV rv;
	/* slots.clear(); */
	clearSlots();

	// initialize cryptoki
#ifdef DEBUG
 	std::cout << "Initializing the Cryptoki library..." << std::endl;
#endif 
	funcs = pkcs11_get_function_list(libPath->c_str());
	rv = pkcs11_initialize(funcs);
	if(rv == CKR_OK)
	{
		loaded = true;
#ifdef DEBUG
 		std::cout << " done" << std::endl;
#endif 
	}
	else err->checkRV(rv);
	// list slots and init sessions
	rv = listSlots(true); // list only slots which have a token
	return rv;
}

CK_RV P11Manager::stop()
{
	CK_RV rv;
	// delete slots;
	std::vector<P11Slot *>::iterator i;
	for( i = slots.begin() ; i != slots.end() ; i++ ) delete (*i);
	if(slots.empty() == false) clearSlots();
	// close cryptoki
	rv = funcs->C_Finalize(NULL_PTR);
	if(rv == CKR_OK) loaded = false;
	else err->checkRV(rv);
	return rv;
}

int P11Manager::updateSlotList()
{
	CK_RV rv;
	rv = listSlots(true);
	if(rv == CKR_OK) return 0;
	else
	{
		err->checkRV(rv);
		return 0;
	}
}

std::vector<Certificate> P11Manager::listCertificates()
{
	// create a vector
	std::vector<Certificate> ret(0);
	// refresh slots list ! otherwise slots and sessions stay active
	CK_RV rv = listSlots(true);
	
	if(rv == CKR_OK)
	{
		// for each slot, add certificates to the vector
		std::vector<P11Slot *>::iterator i;
		for(i = slots.begin() ; i != slots.end() ; i++)
		{
			std::vector<Certificate> slotCerts(0);
			slotCerts = (*i)->listCertificates();
			std::vector<Certificate>::iterator j;
			for(j = slotCerts.begin(); j != slotCerts.end() ; j++)
			{
				ret.push_back(*j);
			}
			//slotCerts.clear();
		}
	}
	else err->checkRV(rv);
	// return the vector
	return ret;
}

CK_RV P11Manager::listSlots(bool withToken)
{
	CK_RV rv = CKR_OK;
	P11Slot * tmpSlot;
	
	/* TODO : improve this (rescan "intelligently") ? */
	
	if(slots.empty() == false) clearSlots();
	// list slots and fill the slot vector
	if(funcs) 
	{
		CK_ULONG nslots, islot;
		CK_SLOT_ID *pslots = NULL;
		if((rv = funcs->C_GetSlotList(((withToken)?TRUE:FALSE), NULL_PTR, &nslots)) == CKR_OK) 
		{
			pslots = new CK_SLOT_ID[nslots];
#ifdef DEBUG
 			std::cout << "Listing available slots";
#endif 
#ifdef DEBUG
 			if(withToken) std::cout << " with token present";
#endif 
#ifdef DEBUG
 			std::cout << "... " << nslots << " slots found." << std::endl;
#endif 
			if(pslots && 
				((rv = funcs->C_GetSlotList(((withToken)?TRUE:FALSE), pslots, &nslots)) == CKR_OK)) 
			{
				for (islot = 0; islot < nslots; islot++) 
				{
					tmpSlot = new P11Slot(funcs, pslots[islot], err, passMan);
					slots.push_back(tmpSlot);
				}
			}
			else err->checkRV(rv);
		}
		else err->checkRV(rv);
	}
	return rv;
}

GetKeyReturn P11Manager::getKey(Certificate & c, Key **key)
{
	GetKeyReturn ret = gkrCANCELED;
	CK_RV rv = listSlots(true);
	if(rv == CKR_OK)
	{
		std::vector<P11Slot*>::iterator i;
		for(i = slots.begin() ; i != slots.end() ; i++)
		{
			ret = (*i)->getKey(c, key);
			if(ret == gkrSUCCESS) return ret;
		}
	}
	else err->checkRV(rv);
	return ret;
}

int P11Manager::signWithPrivKey(EVP_PKEY * key, int flen, const unsigned char *from,
	unsigned char *to, int padding)
{
	return RSA_private_encrypt(flen, from, to, key->pkey.rsa, padding);
}

int P11Manager::decryptWithPrivKey(EVP_PKEY * key, int flen, const unsigned char *from,
	unsigned char *to, int padding)
{
	return RSA_private_decrypt(flen, from, to, key->pkey.rsa, padding);
}

bool P11Manager::getInfos( int &cryptokiVersionMajor,
		int &cryptokiVersionMinor,
		std::string &manufacturerID,
		std::string &libraryDescription,
		int &libraryVersionMajor,
		int &libraryVersionMinor )
{
	if( funcs ) 
	{
		CK_RV rv;
		CK_INFO info;

		if( (rv = funcs->C_GetInfo(&info)) == CKR_OK ) 
		{
			char buffer[33];
			unsigned int i;
	
			cryptokiVersionMajor = info.cryptokiVersion.major;
			cryptokiVersionMinor = info.cryptokiVersion.minor;
	
			libraryVersionMajor = info.libraryVersion.major;
			libraryVersionMinor = info.libraryVersion.minor;
	
			// Get ManufacturerID
			// The returned string is by definition 32 characters
			// long, padded with spaces, the string is *NOT* null
			// terminated.
			// The spaces are wipped out.
			memset( buffer, 0, 33 );
			memcpy( buffer, info.manufacturerID, 32);
			buffer[32] = '\0';
			i = 31;
			while( buffer[i] == ' ' ) 
			{
				i--;
			}
			buffer[i+1] = '\0';
			manufacturerID = std::string(buffer);
	
			// Get LibraryDescription
			memset( buffer, 0, 33 );
			memcpy( buffer, info.libraryDescription, 32);
			buffer[32] = '\0';
			i = 31;
			while( buffer[i] == ' ' ) 
			{
				i--;
			}
			buffer[i+1] = '\0';
			libraryDescription = std::string(buffer);
	
#ifdef DEBUG
 			std::cout << "cryptokiVersion: " << cryptokiVersionMajor
				<< "." << cryptokiVersionMinor << std::endl;
 			std::cout << "manufacturerID: '" << manufacturerID
				<< "'"<< std::endl;
 			std::cout << "libraryDescription: '" << libraryDescription
				<< "'" << std::endl;
 			std::cout << "libraryVersion: " << libraryVersionMajor
				<< "." << libraryVersionMinor << std::endl;
#endif 
	
			return true;
		}
		else 
		{
			err->checkRV(rv);
			return false;
		}
	}
	return false;
}

void P11Manager::clearSlots()
{
  std::vector<P11Slot *>::iterator i;
  for(i = slots.begin() ; i != slots.end() ; i++)
	{
	  P11Slot *s = *i;
	  if(s) {
		delete (s);
	  }
	}
  slots.clear();
}

} // namespace Cryptonit
