/*
 * 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 <iostream>

#include <stdio.h> //access
#include <unistd.h> //access
#include <sys/stat.h> //stat
#include <sys/types.h> // *dir
#include <dirent.h> // *dir

#include "User.hh"
#include "ConfigDatabase.hh"
#include "Utils.hh"
#include "pkcs8.hh"
#include "pkcs10.hh"
#include "pkcs12.hh"

namespace Cryptonit {

  User::User() {
    login = "";
    info = NULL;
    info = new Entry();
    addressBook = NULL;
    authorities = NULL;
    homeDir = "";
    configFile = "";
    addressBookFile = "";
    caFile = "";
    p12File = "";
    certificatesDir = "";
    caCertificatesDir = "";
    p12Dir = "";
    crlDir = "";
  }

  User::User( const std::string userLogin )
  {
    info = NULL;
    addressBook = NULL;
    authorities = NULL;
	
    if( userLogin != "" ) {
#ifdef DEBUG
       std::cout << "User login = " << userLogin << std::endl;
#endif 
      login = std::string(userLogin);
      homeDir = getCryptonitHome() + userLogin;
      configFile = appendDir( homeDir, PREFERENCES_FILENAME );
      certificatesDir = appendDir( homeDir, CERTIFICATES_PATH );
      caCertificatesDir = appendDir( homeDir, CA_PATH );
      p12Dir = appendDir( homeDir, P12_PATH );
      addressBookFile = appendDir( homeDir, ADDRESSBOOK_FILENAME );
      caFile = appendDir( homeDir, CA_FILENAME );
      p12File = appendDir( homeDir, PKCS12_FILENAME );
      crlDir = appendDir( homeDir , CRL_PATH );

      ConfigDatabase* ds = new ConfigDatabase( configFile );

      std::string params[] = {"", ""};
      if( ! ds->read( params ) ) {
#ifdef DEBUG
 	std::cerr << "Cannot load configuration file '"
		  << configFile << "'." <<std::endl;
#endif 
	return;
      }

      Entry* entry;
      entry = ds->getEntry( userLogin );
      info = entry;

      delete ds;
    }
  }


  User::User( const User &u ) 
  {
    login = u.login;
    homeDir = u.homeDir;
    info = u.info;
    certificatesDir = u.certificatesDir;
    addressBook = u.addressBook;
    authorities = u.authorities;
    configFile = u.configFile;
    addressBookFile = u.addressBookFile;
    caFile = u.caFile;
    p12File = u.p12File;
    certificatesDir = u.certificatesDir;
    caCertificatesDir = u.caCertificatesDir;
    p12Dir = u.p12Dir;
    crlDir = u.crlDir;
  }

  User::~User()
  {
    if(info != NULL)
      delete info;
    if(addressBook != NULL) {
      addressBook->save();
      delete addressBook;
    }
    if(authorities != NULL) {
      authorities->save();
      delete authorities;
    }
  }

#ifdef WIN32
#define MKDIR(x) mkdir(x)
#else 
#define MKDIR(x) mkdir(x, 0700)
#endif

  bool User::create( const std::string login ) 
  {
    /* Check if the user already exists */
    std::vector<std::string> validUsers;
    validUsers = getValidUsers();
	
    if( find(validUsers.begin(), validUsers.end(), login) != validUsers.end() ) {
      /* User already exists */
      return false;
    }

    /* Let's create it */
    std::string home;
    home = appendDir( getCryptonitHome(), login );
    if( MKDIR(home.c_str()) != 0 ) {
      /* Cannot create the user dir */
      return false;
    }

    /** Create users specific dirs **/
    if( MKDIR(appendDir(home, CERTIFICATES_PATH).c_str()) != 0 )
      return false;
    if( MKDIR(appendDir(home, CA_PATH).c_str()) != 0 )
      return false;
    if( MKDIR(appendDir(home, CRL_PATH).c_str()) != 0 )
      return false;
    if( MKDIR(appendDir(home, P12_PATH).c_str()) != 0 )
      return false;

    /** Create empty files **/
    FILE* fp;

    /* Empty AddressBook */
    fp = fopen( appendDir(home, ADDRESSBOOK_FILENAME).c_str(), "wb" );
    /* "wb" mode is used for Win32 */
    if( fp == NULL ) {
      return false;
    }
    if( fprintf(fp, "count=0\n") != 8 ) {
      return false;
    }
    fclose(fp);

    /* Empty AuthoritiesDB */
    fp = fopen( appendDir(home, CA_FILENAME).c_str(), "wb" );
    if( fp == NULL ) {
      return false;
    }
    if( fprintf(fp, "count=0\n") != 8 ) {
      return false;
    }
    fclose(fp);

    /* All is ok */
    return true;
  }


  void User::remove()
  {
    DIR* dir;
    struct dirent *entry;
	
    std::string path = appendDir( getCryptonitHome(), login );
	
    dir = opendir( path.c_str() );
    if( dir == NULL ) {
#ifdef DEBUG
       std::cerr << "Cannot open dir '" << path << "'."<< std::endl;
#endif 
      return;
    }
	
    // We only have two level, so make it simple...
    entry = readdir(dir);
    while( entry ) {
      // Skip . and .. entries
      if( strcmp(entry->d_name, ".") != 0
	  && strcmp(entry->d_name, "..") != 0 ) { 

	struct stat s;
	std::string buffer = appendDir( path, entry->d_name );

	if( stat( buffer.c_str(), &s) == 0 ) {
	  if( S_ISDIR(s.st_mode) ) {
	    // delete all files in this dir
	    DIR* subdir;
	    struct dirent *subentry;
	    std::string subpath = appendDir( path, entry->d_name );
			
	    if( (subdir = opendir(subpath.c_str())) != NULL ) {
	      subentry = readdir( subdir );
	      while( subentry ) {
		// Skip . and .. entries
		if( strcmp(subentry->d_name, ".") != 0
		    && strcmp(subentry->d_name, "..") != 0 ) { 
		  // Remove file from subdir
		  std::remove( appendDir(subpath, subentry->d_name).c_str() );
		}
		subentry = readdir( subdir );
	      }
	      closedir( subdir );
	    }
	    // Remove subdir
	    rmdir( subpath.c_str() );
	  }			
	  else {
	    // Remove file from user dir
	    std::remove( appendDir(path,entry->d_name).c_str() );
	  }
	}
      }
      entry = readdir(dir);
    }
    closedir( dir );
    // Remove user dir
    rmdir( path.c_str() );
  }




  bool User::saveToFile( const std::string fileName )
  {
    std::string filename = fileName;
	
    if( filename == "" && configFile == "" )
      return false;

    if( filename == "" && configFile != "" )
      filename = configFile;

    if( filename != "" ) {

      ConfigDatabase* ds = new ConfigDatabase( filename );

      if( ! ds->append( login, info ) ) {
#ifdef DEBUG
 	std::cerr << "Cannot add user's info into DirectoryService." << std::endl;
#endif 
	return false;
      }

      std::string p[] = {filename, ""};
      if( ! ds->commit(p) ) {
#ifdef DEBUG
 	std::cerr << "Cannot write user information in file '"<<filename<<"'."<<std::endl;
#endif 
	return false;
      }
      delete ds;
      return true;
    }
    else return false;
  }


  std::string User::getLogin()
  {
    return login;
  }


  void User::setLogin( const std::string userLogin )
  {
    if( userLogin != "" )
      login = userLogin;
  }

  void User::setLogin( const char* userLogin )
  {
    std::string l(userLogin);
    
    if( l != "" )
      login = l;
  }
  
	
	
  std::string User::getHome()
  {
    return homeDir;
  }


  void User::setHome( const std::string home )
  {
    if( home != "" )
      homeDir = home;
  }


  Entry* User::getUserInfos()
  {
    return info;
  }



  void User::setUserInfo( Entry* userInfo )
  {
    if( info != NULL )
      delete info;
    info = userInfo;
  }


  std::string User::getInfo( const std::string infoName )
  {
    if( info != NULL && infoName != "" ) {
      return std::string(info->getAttributeFirstValue( infoName ));
    }
    return std::string("");
  }


  std::vector<std::string> User::getInfos( const std::string infoName )
  {
    if( info != NULL && infoName != "" ) {
      return info->getAttributeValues( infoName );
    }
    else return std::vector<std::string> (0);
  }


  bool User::setInfo( const std::string infoName, const std::string infoValue )
  {
    if( infoName != "") {
      if(infoValue != "") {
       return info->append( infoName, new Attribute( infoValue ) );
     }
     else {
       return info->erase(infoName);
     }
    }
    else return false;
  }


  bool User::setInfos( const std::string infoName, const std::vector<std::string>& infoValues )
  {
    if( infoName != "" && infoValues.size() > 0 ){
      return info->append( infoName, new Attribute( infoValues ) );
    } else if( infoName != "" && infoValues.size() == 0 ) {
      return info->erase( infoName );
    } else {
      return false;
    }
  }


  void User::setCertificatesDir(std::string dir){
    if(dir != "")	    
      certificatesDir = dir;
  }

  std::string User::getCertificatesDir(){
    return certificatesDir;
  }

  void User::setCACertificatesDir(std::string dir){
    if(dir != "")	    
      caCertificatesDir = dir;
  }

  std::string User::getCACertificatesDir(){
    return caCertificatesDir;
  }

  void User::setP12Dir(std::string dir){
    if(dir != "")	    
      p12Dir = dir;
  }

  std::string User::getP12Dir(){
    return p12Dir;
  }

  void User::setCRLDir(std::string dir){
    if(dir !=""){
      crlDir = dir;
    }
  }

  std::string User::getCRLDir(){
    return crlDir;
  }



  bool User::isUserValid( std::string login )
  {
    std::string home;
    home = appendDir( getCryptonitHome(), login );

    /** Check files **/
    /* Check PREFERENCES_FILENAME */
    if( access( appendDir(home, PREFERENCES_FILENAME).c_str(), R_OK | W_OK ) == -1 )
      return false;
    /* Check ADDRESSBOOK_FILENAME */
    if( access( appendDir(home, ADDRESSBOOK_FILENAME).c_str(), R_OK | W_OK ) == -1 )
      return false;
    /* Check CA_FILENAME */
    if( access( appendDir(home, CA_FILENAME).c_str(), R_OK | W_OK ) == -1 )
      return false;
    /* Check PKCS12_FILENAME */
    if( access( appendDir(home, PKCS12_FILENAME).c_str(), R_OK | W_OK ) == -1 )
      return false;

    /** Check dirs **/
    struct stat s;
    /* Check CERTIFICATES_PATH */
    if( stat(appendDir(home, CERTIFICATES_PATH).c_str(), &s) == 0 ) {
      if( ! S_ISDIR(s.st_mode) )
	return false;
    } else {
      return false;
    }
	
    /* Check CA_PATH */
    if( stat(appendDir(home, CA_PATH).c_str(), &s) == 0 ) {
      if( ! S_ISDIR(s.st_mode) )
	return false;
    } else {
      return false;
    }

    /* Check CRL_PATH */
    if( stat(appendDir(home, CRL_PATH).c_str(), &s) == 0 ) {
      if( ! S_ISDIR(s.st_mode) )
	return false;
    } else {
      return false;
    }

    /* Check P12_PATH */
    if( stat(appendDir(home, P12_PATH).c_str(), &s) == 0 ) {
      if( ! S_ISDIR(s.st_mode) )
	return false;
    } else {
      return false;
    }

    /* All are ok */
    return true;
  }


  std::vector<std::string> User::getValidUsers()
  {
    DIR* dir;
    struct dirent *entry;
    std::vector<std::string> validUsers;
	
    dir = opendir( getCryptonitHome().c_str() );
    if( dir == NULL ) {
      return std::vector<std::string>(0);
    }

    entry = readdir(dir);
    while (entry
	   && (0 == strcmp (entry->d_name, ".") ||
	       0 == strcmp (entry->d_name, ".."))) {

      if( isUserValid( entry->d_name ) ) {
	validUsers.push_back(std::string(entry->d_name));
      }
      entry = readdir(dir);
    }
	
    closedir( dir );
    return validUsers;
  }


    
  int User::addIdentityFromP12( const std::string identityName,
				const std::string filename, const std::string password )
  {
    if( identityName == "" ) {
      return -3;
    }

#ifdef DEBUG
     std::cerr << "Adding new identity from PKCS12'" << identityName << "' from '"
	      << filename << "'." << std::endl;
#endif 

    pkcs12 p12;

    if( p12.load( filename.c_str(), password.c_str() ) != SUCCESS )
      return -1;

    // load the certificate from the PKCS12
    Certificate cert;
    cert = Certificate(p12.getCertificate());
    std::string certHash = cert.getHash();
	
    std::vector<std::string> pkcs12infos;
    pkcs12infos = getInfos( "PKCS12" );
    std::vector<std::string>::iterator stringIt;

    std::string pkcs12name;
    pkcs12name = setExt(certHash, "p12");


    // Check if the PKCS#12 already exists
    for ( stringIt = pkcs12infos.begin() ; stringIt != pkcs12infos.end() ; stringIt++ ){
      if( pkcs12name == *stringIt ) {
	return -2;
      }
    }


    // Check if the identity name already exists
    std::vector<std::string> identitiesInfos;
    identitiesInfos = getInfos( "Identities" );

    for( stringIt = identitiesInfos.begin(); stringIt != identitiesInfos.end(); stringIt++) {
      if( identityName == stringIt->c_str() ) {
	return -3;
      }
    }

    // Save the extracted certificate to the user certificates dir
    std::string certFilename;
    certFilename = setExt(certHash, "der");
    certFilename = appendDir( getCertificatesDir(), certFilename );

#ifdef DEBUG
     std::cerr << "Writing certificate to: " << certFilename;
#endif 
    if( cert.save(certFilename.c_str()) != 0 ) {
#ifdef DEBUG
       std::cerr << " Failed !" << std::endl;
#endif 
      return -4;
    }
#ifdef DEBUG
     std::cerr << " done." << std::endl;
#endif 

    // Add it to the user's preferences
    std::vector<std::string> infos;
    infos = getInfos( "X509_Certificate" );
    infos.push_back( setExt(certHash, "der") );
    setInfos( "X509_Certificate", infos );



    // Insert the identity informations into the user addressbook
    Contact *contact = new Contact(cert.getSubjectName().getValues(DN_DISPLAY_SHORT | DN_DISPLAY_VALUE, ','));
    contact->setInfo("userCertificate", setExt(certHash, "der").c_str());
    /* Record some info into the addresse book 
     * TODO: We need to find a way to list all available informations from the PKCS#12
     */
    if( cert.getSubjectName().getValue("CN") != "" )
      contact->setInfo("cn", cert.getSubjectName().getValue("CN"));
    if( cert.getSubjectName().getValue("SN") != "" )
      contact->setInfo("sn", cert.getSubjectName().getValue("SN"));
    if( cert.getSubjectName().getValue("C") != "" )
      contact->setInfo("co", cert.getSubjectName().getValue("C"));
    if( cert.getSubjectName().getValue("O") != "" )
      contact->setInfo("o", cert.getSubjectName().getValue("O"));
    if( cert.getEmailAddress() != "" )
      contact->setInfo("mail", cert.getEmailAddress());

    /* Loading local address book */
    std::string localAddressBook = "config://";
    localAddressBook += getAddressBookFile();
	
    addressBook = new AddressBook( localAddressBook );

    if( addressBook == NULL || ! addressBook->addContact( contact ) ) 
      return -5;




    /* Load authorities database */
    std::string authoritiesDB = "config://";
    authoritiesDB += getCAFile();
	
    authorities = new AuthoritiesDB( authoritiesDB );

    if( authorities == NULL )
      return -6;


    // Extract all CA from the PKCS#12 and add them to the user dir
    std::vector<Certificate> CA;
    std::vector<Certificate>::iterator it;
    CA = p12.getCA();

    for( it = CA.begin(); it != CA.end(); it++ ) {
      std::string caHash = it->getHash();
	    
      std::string caFilename;
      caFilename = setExt(caHash, "der");
      caFilename = appendDir( getCACertificatesDir(), caFilename );

#ifdef DEBUG
       std::cerr << "Writing CA certificate to: " << caFilename;
#endif 

      if( it->save(caFilename.c_str()) != 0 ) {
#ifdef DEBUG
 	std::cerr << " Failed !" << std::endl;
#endif 
	return -6;
      } else {
	authorities->addAuthority( it->getSubjectName().getValues(DN_DISPLAY_LONG | DN_DISPLAY_VALUE, ','),
				   setExt(caHash, "der") );
#ifdef DEBUG
 	std::cerr << " done." << std::endl;
#endif 
      }

    }



    // Copy PKCS#12 file into the user dir
    std::string p12Filename;
    p12Filename = setExt(certHash, "p12");
    p12Filename = appendDir(getP12Dir(), p12Filename);
	
#ifdef DEBUG
     std::cerr << "Writing PKCS#12 to: " << p12Filename;
#endif 

    if( copyFile(filename, p12Filename) != 0 ) {
#ifdef DEBUG
       std::cerr << " Failed !" << std::endl;
#endif 
      return -7;
    } else {
#ifdef DEBUG
       std::cerr << " done." << std::endl;
#endif 
    }


    // Add PKCS#12 into the user's preferences file
    std::vector<std::string> pkcs12s;
    pkcs12s = getInfos( "PKCS12" );
    pkcs12s.push_back( setExt(certHash, "p12") );
    setInfos("PKCS12", pkcs12s );
	
    // Same thing for the identity name
    std::vector<std::string> identities;
    identities = getInfos( "Identities" );
    identities.push_back( identityName );
    setInfos("Identities", identities);
	

    return 0;
  }



  int User::addIdentityFromP10( const std::string identityName,
				const std::string requestFilename, const std::string certFilename,
				const std::string privateKeyFilename, const std::string privateKeyPass )
  {
#ifdef DEBUG
     std::cerr << "Adding new identity from PKCS10 '" << identityName << "' from request '"
	      << requestFilename << "' using certificate '" << certFilename 
	      << "' and private key '" << privateKeyFilename << "'." << std::endl;
#endif 
	
    // Open the private key
    pkcs8 p8;
    if( p8.load(privateKeyFilename.c_str(), pem_format, privateKeyPass.c_str()) != true ) {
      return -1;
    }

    // Open originating request
    pkcs10 p10;
    if( p10.load( requestFilename.c_str() ) != 0 ) {
      return -2;
    }

    // Check if the private key p8 was used for generating the p10 request
    if( p10.matchPrivateKey( p8 ) == false ) {
      return -3;
    }

    // Open the certificate
    Certificate cert;
    if( cert.load( certFilename.c_str() ) != 0 ) {
      return -4;
    }

    // Create a PKCS 12
    char* pass = strdup(privateKeyPass.c_str());
    pkcs12 p12;
    if( p12.create( p8, cert, pass ) != 0 ) {
      free(pass);
      return -5;
    }

    // Generate a temporary filename for the PKCS#12
    int fd;
    std::string p12TmpFilename = getP12Dir();
    p12TmpFilename = appendDir(p12TmpFilename, "XXXXXX");

    if( (fd = makeTempFile(p12TmpFilename)) == -1 ) {
      free(pass);
      return -6;
    }
    close(fd);


    // Write the PKCS12 in the P12/ user's dir
    if( p12.save( p12TmpFilename.c_str() ) <= 0 ) {
      free(pass);
      return -7;
    }

    int ret = addIdentityFromP12(identityName, p12TmpFilename, privateKeyPass.c_str());
    if( ret < 0 )
      ret -= 50;

    free(pass);
    std::remove( p12TmpFilename.c_str() );

    return ret;
  }

}


