/*! @file

    @id $Id$

    This product includes software developed by the OpenSSL Project
    for use in the OpenSSL Toolkit (http://www.openssl.org/)
    
    This product includes cryptographic software written by
    Eric Young (eay@cryptsoft.com)
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#ifndef __OPENSSL_ENGINE_HXX__
#define __OPENSSL_ENGINE_HXX__

#include <cryptaux.hxx>

#include <openssl-compatibility.hxx>

#undef DATADIR
#include <openssl/engine.h>
#include <map>
#include <string>

#ifndef OPENSSL_CHECK
#include <stdexcept>
#include <sstream>
#if __GNUC__ >= 2
//! Macro for internal OpenSSL checks.
/*! You can define a different implementation in your compile call */
#define OPENSSL_CHECK(X)                                            \
  if (!(X)) {                                                       \
    ERR_load_ENGINE_strings();                                      \
    std::stringstream ss;                                           \
    for (unsigned long err(0); (err=ERR_get_error());)              \
      ss<<"Error: "<<ERR_error_string(err, 0)<<"; ";                \
    ss<<"Command "<<#X<<" failed in function "<<__PRETTY_FUNCTION__ \
      <<" in file "<<__FILE__<<":"<<__LINE__;                       \
    throw std::runtime_error(ss.str());                             \
  }
#else
#define OPENSSL_CHECK(X)                                             \
  if (!(X)) {                                                        \
    ERR_load_ENGINE_strings();                                       \
    std::stringstream ss;                                            \
    for (unsigned long err(0); (err=ERR_get_error());)               \
      ss<<"Error: "<<ERR_error_string(err, 0)<<"; ";                 \
    ss<<"Command "<<#X<<" failed in file "<<__FILE__<<":"<<__LINE__; \
    throw std::runtime_error(ss.str());                              \
  }
#endif
#endif

//! @addtogroup gopenssl
//@{
//! @defgroup opensslengine OpenSSL Engine C++ Wrapper
//@}

namespace openssl {

  //! @addtogroup opensslengine
  //@{

  //! Abstract Engine Base
  /*! Inherit and overwrite to implement your own Engine.

      Then in the scope where you need it, register your engine:
      <code>
      openssl::RegisterEngine myEngine(new MyEngine);
      </code> */
  class Engine {
      
    public:
      
      Engine(): _e(ENGINE_new()), _cleanup(true) {
        CRYPTOLOG("log");
      }
      virtual ~Engine() {
        CRYPTOLOG("log");
        if (_cleanup) OPENSSL_CHECK(ENGINE_free(_e));
      }
      virtual const char* id() = 0;
      virtual const char* name() = 0;
      virtual int init() {
        CRYPTOLOG("log");
        return 1;
      }
      virtual int finish() {
        CRYPTOLOG("log");
        return 1;
      }
      virtual int ctrl(int, long, void*, void(*)()) {
        CRYPTOLOG("log");
        return 1;
      }
      virtual int cmd() {
        CRYPTOLOG("log");
        return 1;
      }
      virtual EVP_PKEY* pubkey(const char*, UI_METHOD*, void*) {
        CRYPTOLOG("log");
        return 0;
      }
      virtual EVP_PKEY* privkey(const char*, UI_METHOD*, void*) {
        CRYPTOLOG("log");
        return 0;
      }
      virtual std::string rsaEncrypt(const std::string&, int) {
        CRYPTOLOG("log");
        throw std::runtime_error("rsaEncrypt not implemented");
      }
      virtual std::string rsaDecrypt(const std::string&, int) {
        CRYPTOLOG("log");
        throw std::runtime_error("rsaDecrypt not implemented");
      }
      virtual std::string rsaSign(const std::string&, unsigned int) {
        CRYPTOLOG("log");
        throw std::runtime_error("rsaSign not implemented");
      }
      virtual int rsaVerify() {
        CRYPTOLOG("log");
        return 1;
      }
      virtual int rsaFinish() {
        CRYPTOLOG("log");
        return 1;
      }

      class iterator;

    protected:

      friend class EngineMapper;
      friend class iterator;

      Engine(ENGINE* e): _e(e), _cleanup(false) {
        CRYPTOLOG("log");
      }
      
      Engine(const Engine&); // forbidden
      Engine& operator=(const Engine&); // forbidden

      ENGINE* _e;
      bool _cleanup;
      
  };
      
  class Engine::iterator {

    private:
      
      class MyEngine: public Engine {
        public:
          MyEngine(ENGINE*e): Engine(e) {}
          virtual const char* id() {
            return ENGINE_get_id(_e);
          }
          virtual const char* name() {
            return ENGINE_get_name(_e);
          }
      };
      
    public:
      
      iterator(): _e(new MyEngine(ENGINE_get_first())) {
        CRYPTOLOG("log");
      }
      
      ~iterator() {
        CRYPTOLOG("log");
        delete _e;
      }
      
      Engine& operator*() {
        CRYPTOLOG("log");
        return *_e;
      }
      
      Engine* operator->() {
        CRYPTOLOG("log");
        return _e;
      }
      
      iterator& operator++() {
        CRYPTOLOG("log");
        _e->_e = ENGINE_get_next(_e->_e);
        return *this;
      }
      
      operator bool() const {
        CRYPTOLOG("log: "<<_e->_e<<" ---- "<<ENGINE_get_first());
        return _e->_e!=0;
      }
      
    private:
      
      Engine* _e;
      
  };

  class EngineMapper {
      
    public:
      
      static void add(Engine *e) {
        CRYPTOLOG("log");
        _prototypes[e->id()] = e;
        _map[e->_e] = e;
        OPENSSL_CHECK(ENGINE_set_id(e->_e, e->id()));
        OPENSSL_CHECK(ENGINE_set_name(e->_e, e->name()));
        OPENSSL_CHECK(ENGINE_set_destroy_function(e->_e, EngineMapper::destroy));
        OPENSSL_CHECK(ENGINE_set_init_function(e->_e, EngineMapper::init));
        OPENSSL_CHECK(ENGINE_set_finish_function(e->_e, EngineMapper::finish));
        OPENSSL_CHECK(ENGINE_set_ctrl_function(e->_e, EngineMapper::ctrl));
        //OPENSSL_CHECK(ENGINE_set_cmd_defns(e->_e, EngineMapper::cmd));
#ifndef OPENSSL_NO_RSA
        OPENSSL_CHECK(ENGINE_set_RSA(e->_e, rsa()));
#endif
#ifndef OPENSSL_NO_DSA
        // OPENSSL_CHECK(ENGINE_set_DSA(e->_e, DSA_get_default_method()));
#endif
#ifndef OPENSSL_NO_DH
        // OPENSSL_CHECK(ENGINE_set_DH(e->_e, DSA_get_default_method()));
#endif
        // OPENSSL_CHECK(ENGINE_set_RAND(e->_e, rand()));
        OPENSSL_CHECK(ENGINE_set_load_pubkey_function(e->_e, pubkey));
        OPENSSL_CHECK(ENGINE_set_load_privkey_function(e->_e, privkey));
        OPENSSL_CHECK(ENGINE_init(e->_e));
        OPENSSL_CHECK(ENGINE_add(e->_e));
      }

      static void remove(Engine *e) {
        OPENSSL_CHECK(ENGINE_remove(e->_e));
        OPENSSL_CHECK(ENGINE_finish(e->_e));
        _map.erase(e->_e);
        delete e;
      }
  
    private:
      
      static int destroy(ENGINE*) {
        CRYPTOLOG("log");
        return 1;
      }
      static int init(ENGINE* e) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(e));
        return it!=_map.end()?it->second->init():0;
      }
      static int finish(ENGINE* e) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(e));
        return it!=_map.end()?it->second->finish():0;
      }
      static int ctrl(ENGINE* e, int cmd, long i, void* p, void(*f)()) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(e));
        return it!=_map.end()?it->second->ctrl(cmd, i, p, f):0;
      }
      static EVP_PKEY* pubkey(ENGINE* e, const char* c, UI_METHOD* u, void* d) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(e));
        return it!=_map.end()?it->second->pubkey(c, u, d):0;
      }
      static EVP_PKEY* privkey(ENGINE* e, const char* c, UI_METHOD* u, void* d) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(e));
        return it!=_map.end()?it->second->privkey(c, u, d):0;
      }
      static int rsaEncrypt(int flen, const unsigned char *from,
			    unsigned char *to,
			    RSA *rsa, int padding) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(RSA_get0_engine(rsa)));
        if (it==_map.end()) return 0;
        try {
          std::string res(it->second->rsaEncrypt
                          (std::string((const char*)from, flen),
                           padding));
          std::copy(res.begin(), res.end(), to);
          return 1;
        } catch (const std::exception& e) {
          CRYPTOLOG("ERROR: "<<e.what());
          return 0;
        }
      }
      static int rsaDecrypt(int flen, const unsigned char *from,
                            unsigned char *to, RSA *rsa, int padding) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(RSA_get0_engine(rsa)));
        if (it==_map.end()) return 0;
        try {
          std::string res(it->second->rsaDecrypt
                          (std::string((const char*)from, flen),
                           padding));
          std::copy(res.begin(), res.end(), to);
          return 1;
        } catch (const std::exception& e) {
          CRYPTOLOG("ERROR: "<<e.what());
          return 0;
        }
      }
      static int rsaSign(int type, const unsigned char *from,
                         unsigned int flen,
                         unsigned char *to,
                         unsigned int*tlen,
                         const RSA *rsa) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(RSA_get0_engine(rsa)));
        if (it==_map.end()) return 0;
        try {
          std::string res(it->second->rsaSign
                          (std::string((const char*)from, flen),
                           type));
          CRYPTOLOG("to="<<(void*)to<<"; len="<<*tlen);
          CRYPTOLOG("siglen="<<res.size());
          *tlen = (unsigned int)res.size();
          std::copy(res.begin(), res.end(), to);
          return 1;
        } catch (const std::exception& e) {
          CRYPTOLOG("ERROR: "<<e.what());
          return 0;
        }
      }
      static int rsaVerify(int, const unsigned char*,
                           unsigned int,  OPENSSL_V0_CONST unsigned char*,
                           unsigned int, const RSA* rsa) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(RSA_get0_engine(rsa)));
        return it!=_map.end()?it->second->rsaVerify():0;
      }
      static int rsaFinish(RSA *rsa) {
        CRYPTOLOG("log");
        Map::iterator it(_map.find(RSA_get0_engine(rsa)));
        return it!=_map.end()?it->second->rsaFinish():0;
      }
      
    protected:
      
      static RSA_METHOD* rsa() {
        CRYPTOLOG("log");
	static RSA_METHOD* ops(0);
        if (!ops) {
          ops = RSA_meth_dup(RSA_get_default_method());
          RSA_meth_set_pub_enc(ops, rsaEncrypt);
          RSA_meth_set_priv_dec(ops, rsaDecrypt);
          // a.k.a Verify/Sign. actLibrary just allows the proper functions
          // and has this 'backdoor' closed. Breaks 'rsautl', sadly.
          RSA_meth_set_pub_dec(ops, 0);
          RSA_meth_set_priv_enc(ops, 0);
          RSA_meth_set_sign(ops, rsaSign);
          RSA_meth_set_verify(ops, rsaVerify);
          RSA_meth_set_finish(ops, rsaFinish);
        }
	return ops;
      }

    private:

      typedef std::map<ENGINE*, Engine*> Map;
      static Map _map;
      static std::map<std::string, Engine*> _prototypes;
  };

  //! Scoped Engine Registry
  /*! Engine will be deregistered and freed at destruction */
  template <class ENGINE=Engine> class RegisterEngine {
    public:
      RegisterEngine(ENGINE* e = 0): _e(e) {
        if (_e) EngineMapper::add(_e);
      }
      ~RegisterEngine() {
        if (_e) EngineMapper::remove(_e);
      }
      RegisterEngine& operator=(ENGINE* e) {
        if (_e) EngineMapper::remove(_e);
        _e = e;
        if (_e) EngineMapper::add(_e);
        return *this;
      }
      operator ENGINE*() {
        return _e;
      }
       ENGINE* operator->() {
        return _e;
      }
       ENGINE& operator*() {
        return *_e;
      }
    private:
      ENGINE* _e;
  };

  //@}
}
#endif
