/*! @file

    @id $Id$
*/
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

#undef DATADIR
#include <openssl/opensslv.h>
#ifndef OPENSSL_VERSION_NUMBER
#  error OpenSSL Version Number not Found
#elif OPENSSL_VERSION_NUMBER < 0x00908000L
#  ifdef ALLOW_SSL_OLDER_THAN_0_8
#    warning OpenSSL older than 0.8 detected please upgrade to 1.0
#  else
#    error OpenSSL older than 0.8 detected please upgrade to 1.0
#  endif
#  define OPENSSL_0_7
#  define V0_CONST
#  define CV_STACK
#  define CV_X509
#elif OPENSSL_VERSION_NUMBER < 0x10000000L
#  ifdef ALLOW_SSL_0_8
#    warning OpenSSL 0.8 detected please upgrade to 1.0
#  else
#    error OpenSSL 0.8 detected please upgrade to 1.0
#  endif
#  define OPENSSL_0_8
#  define V0_CONST const
#  define CV_STACK
#  define CV_X509
#else
#  define OPENSSL_1_0
#  define V0_CONST const
#  define CV_STACK (_STACK*)
#  define CV_X509 (STACK_OF(X509)*)
#endif
#ifndef OPENSSL_VERSION_NUMBER
#  error OpenSSL Version Number not Found
#elif OPENSSL_VERSION_NUMBER < 0x10000000L
#  define OPENSSL_0
#  define OPENSSL_V0_CONST
#else
#  define OPENSSL_1
#  define OPENSSL_V0_CONST const
#endif

#include <openssl/crypto.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/evp.h>
#include <openssl/ossl_typ.h>

namespace openssl {

    //==============================================================================
  //! @addtogroup opensslcompat
  //@{
  
  /// Get OpenSSL runtime and buildtime version information
  /** To be displyed in an about box. It also shows mandatory license
      information. */
  inline std::string version() {
    std::string v
      ((std::string(OPENSSL_VERSION_TEXT)==SSLeay_version(SSLEAY_VERSION))
       ? SSLeay_version(SSLEAY_VERSION)
       : std::string(OPENSSL_VERSION_TEXT) + " (Buildtime)"
       + "\n  - Runtime: " + SSLeay_version(SSLEAY_VERSION));
    return v
      + "\n  - OpenSSL API "+SHLIB_VERSION_NUMBER+" compatible"
      + "\n  - " + SSLeay_version(SSLEAY_BUILT_ON)
      + "\n  - " + SSLeay_version(SSLEAY_PLATFORM)
      + "\n\n"
      "This product includes software developed by the OpenSSL Project"
      " for use in the OpenSSL Toolkit (http://www.openssl.org/)"
      "\n\n"
      "This product includes cryptographic software written by Eric Young"
      " (eay@cryptsoft.com)";
  }


  //------------------------------------------------------------------------------
#if OPENSSL_VERSION_NUMBER < 0x10100000L

  /* The following code is copied from:
      https://wiki.openssl.org/index.php/OpenSSL_1.1.0_Changes

      Provide the new functions to old version of OpenSSL. */
#include <string.h>
#include <openssl/engine.h>

inline static void *OPENSSL_zalloc(size_t num) {
    void *ret = OPENSSL_malloc(num);

    if (ret != NULL)
        memset(ret, 0, num);
    return ret;
 }

inline int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
    /* If the fields n and e in r are NULL, the corresponding input
     * parameters MUST be non-NULL for n and e.  d may be
     * left NULL (in case only the public key is used).
     */
    if ((r->n == NULL && n == NULL)
        || (r->e == NULL && e == NULL))
        return 0;

    if (n != NULL) {
        BN_free(r->n);
        r->n = n;
    }
    if (e != NULL) {
        BN_free(r->e);
        r->e = e;
    }
    if (d != NULL) {
        BN_free(r->d);
        r->d = d;
    }

    return 1;
 }

inline int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) {
    /* If the fields p and q in r are NULL, the corresponding input
     * parameters MUST be non-NULL.
     */
    if ((r->p == NULL && p == NULL)
        || (r->q == NULL && q == NULL))
        return 0;

    if (p != NULL) {
        BN_free(r->p);
        r->p = p;
    }
    if (q != NULL) {
        BN_free(r->q);
        r->q = q;
    }

    return 1;
 }

inline int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) {
    /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input
     * parameters MUST be non-NULL.
     */
    if ((r->dmp1 == NULL && dmp1 == NULL)
        || (r->dmq1 == NULL && dmq1 == NULL)
        || (r->iqmp == NULL && iqmp == NULL))
        return 0;

    if (dmp1 != NULL) {
        BN_free(r->dmp1);
        r->dmp1 = dmp1;
    }
    if (dmq1 != NULL) {
        BN_free(r->dmq1);
        r->dmq1 = dmq1;
    }
    if (iqmp != NULL) {
        BN_free(r->iqmp);
        r->iqmp = iqmp;
    }

    return 1;
 }

inline ENGINE* RSA_get0_engine(const RSA *r) {
  return r->engine;
}
  
inline void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) {
    if (n != NULL)
        *n = r->n;
    if (e != NULL)
        *e = r->e;
    if (d != NULL)
        *d = r->d;
 }

inline void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) {
    if (p != NULL)
        *p = r->p;
    if (q != NULL)
        *q = r->q;
 }

inline void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) {
    if (dmp1 != NULL)
        *dmp1 = r->dmp1;
    if (dmq1 != NULL)
        *dmq1 = r->dmq1;
    if (iqmp != NULL)
        *iqmp = r->iqmp;
 }

inline void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) {
    if (p != NULL)
        *p = d->p;
    if (q != NULL)
        *q = d->q;
    if (g != NULL)
        *g = d->g;
 }

inline int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
    /* If the fields p, q and g in d are NULL, the corresponding input
     * parameters MUST be non-NULL.
     */
    if ((d->p == NULL && p == NULL)
        || (d->q == NULL && q == NULL)
        || (d->g == NULL && g == NULL))
        return 0;

    if (p != NULL) {
        BN_free(d->p);
        d->p = p;
    }
    if (q != NULL) {
        BN_free(d->q);
        d->q = q;
    }
    if (g != NULL) {
        BN_free(d->g);
        d->g = g;
    }

    return 1;
 }

inline void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) {
    if (pub_key != NULL)
        *pub_key = d->pub_key;
    if (priv_key != NULL)
        *priv_key = d->priv_key;
 }

inline int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) {
    /* If the field pub_key in d is NULL, the corresponding input
     * parameters MUST be non-NULL.  The priv_key field may
     * be left NULL.
     */
    if (d->pub_key == NULL && pub_key == NULL)
        return 0;

    if (pub_key != NULL) {
        BN_free(d->pub_key);
        d->pub_key = pub_key;
    }
    if (priv_key != NULL) {
        BN_free(d->priv_key);
        d->priv_key = priv_key;
    }

    return 1;
 }

inline void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
    if (pr != NULL)
        *pr = sig->r;
    if (ps != NULL)
        *ps = sig->s;
 }

inline int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
    if (r == NULL || s == NULL)
        return 0;
    BN_clear_free(sig->r);
    BN_clear_free(sig->s);
    sig->r = r;
    sig->s = s;
    return 1;
 }

inline void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps) {
    if (pr != NULL)
        *pr = sig->r;
    if (ps != NULL)
        *ps = sig->s;
 }

inline int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
    if (r == NULL || s == NULL)
        return 0;
    BN_clear_free(sig->r);
    BN_clear_free(sig->s);
    sig->r = r;
    sig->s = s;
    return 1;
 }

inline void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) {
    if (p != NULL)
        *p = dh->p;
    if (q != NULL)
        *q = dh->q;
    if (g != NULL)
        *g = dh->g;
 }

inline int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
    /* If the fields p and g in d are NULL, the corresponding input
     * parameters MUST be non-NULL.  q may remain NULL.
     */
    if ((dh->p == NULL && p == NULL)
        || (dh->g == NULL && g == NULL))
        return 0;

    if (p != NULL) {
        BN_free(dh->p);
        dh->p = p;
    }
    if (q != NULL) {
        BN_free(dh->q);
        dh->q = q;
    }
    if (g != NULL) {
        BN_free(dh->g);
        dh->g = g;
    }

    if (q != NULL) {
        dh->length = BN_num_bits(q);
    }

    return 1;
 }

inline void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) {
    if (pub_key != NULL)
        *pub_key = dh->pub_key;
    if (priv_key != NULL)
        *priv_key = dh->priv_key;
 }

inline int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) {
    /* If the field pub_key in dh is NULL, the corresponding input
     * parameters MUST be non-NULL.  The priv_key field may
     * be left NULL.
     */
    if (dh->pub_key == NULL && pub_key == NULL)
        return 0;

    if (pub_key != NULL) {
        BN_free(dh->pub_key);
        dh->pub_key = pub_key;
    }
    if (priv_key != NULL) {
        BN_free(dh->priv_key);
        dh->priv_key = priv_key;
    }

    return 1;
 }

inline int DH_set_length(DH *dh, long length) {
    dh->length = length;
    return 1;
 }

inline const unsigned char *EVP_CIPHER_CTX_iv(const EVP_CIPHER_CTX *ctx) {
    return ctx->iv;
 }

inline unsigned char *EVP_CIPHER_CTX_iv_noconst(EVP_CIPHER_CTX *ctx) {
    return ctx->iv;
 }

  /** @bug 
./openssl.hxx:416:26: error: invalid conversion from ‘void*’ to ‘EVP_MD_CTX* {aka env_md_ctx_st*}’ [-fpermissive]
     return OPENSSL_zalloc(sizeof(EVP_MD_CTX));
                          ^
      */
 // EVP_MD_CTX *EVP_MD_CTX_new(void)
 // {
 //    return OPENSSL_zalloc(sizeof(EVP_MD_CTX));
 // }

inline void EVP_MD_CTX_free(EVP_MD_CTX *ctx) {
    EVP_MD_CTX_cleanup(ctx);
    OPENSSL_free(ctx);
 }

inline RSA_METHOD *RSA_meth_dup(const RSA_METHOD *meth) {
  RSA_METHOD *ret;
  
  ret = (RSA_METHOD*)OPENSSL_malloc(sizeof(RSA_METHOD));
  
  if (ret != NULL) {
    memcpy(ret, meth, sizeof(*meth));
    ret->name = OPENSSL_strdup(meth->name);
    if (ret->name == NULL) {
      OPENSSL_free(ret);
      return NULL;
    }
  }

  return ret;
}

inline int RSA_meth_set1_name(RSA_METHOD *meth, const char *name) {
    char *tmpname;

    tmpname = OPENSSL_strdup(name);
    if (tmpname == NULL) {
        return 0;
    }

    OPENSSL_free((char *)meth->name);
    meth->name = tmpname;

    return 1;
 }

// inline int RSA_meth_set_priv_enc(RSA_METHOD *meth, int (*priv_enc) (int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)) {
//     meth->rsa_priv_enc = priv_enc;
//     return 1;
//  }

// inline int RSA_meth_set_priv_dec(RSA_METHOD *meth, int (*priv_dec) (int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)) {
//     meth->rsa_priv_dec = priv_dec;
//     return 1;
//  }

// inline int RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa)) {
//     meth->finish = finish;
//     return 1;
//  }

inline void RSA_meth_free(RSA_METHOD *meth) {
    if (meth != NULL) {
        OPENSSL_free((char *)meth->name);
        OPENSSL_free(meth);
    }
 }

inline int RSA_bits(const RSA *r) {
    return (BN_num_bits(r->n));
 }

inline RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey) {
    if (pkey->type != EVP_PKEY_RSA) {
        return NULL;
    }
    return pkey->pkey.rsa;
 }


inline DSA *EVP_PKEY_get0_DSA(EVP_PKEY *pkey) {
    if (pkey->type != EVP_PKEY_DSA) {
        return NULL;
    }
    return pkey->pkey.dsa;
 }


inline DH *EVP_PKEY_get0_DH(EVP_PKEY *pkey) {
    if (pkey->type != EVP_PKEY_DH) {
        return NULL;
    }
    return pkey->pkey.dh;
 }


 // HMAC_CTX *HMAC_CTX_new(void)
 // {
 //    HMAC_CTX *ctx = OPENSSL_malloc(sizeof(*ctx));
 //    if (ctx != NULL) {
 //        if (!HMAC_CTX_reset(ctx)) {
 //            HMAC_CTX_free(ctx);
 //            return NULL;
 //        }
 //    }
 //    return ctx;
 // }

 // void HMAC_CTX_free(HMAC_CTX *ctx)
 // {
 //    if (ctx != NULL) {
 //        hmac_ctx_cleanup(ctx);
 //        EVP_MD_CTX_free(ctx->i_ctx);
 //        EVP_MD_CTX_free(ctx->o_ctx);
 //        EVP_MD_CTX_free(ctx->md_ctx);
 //        OPENSSL_free(ctx);
 //    }
 // }


 /* the following is copied and modified from openssl-1.1.0g/crypto/rsa/rsa_meth.c */

  /*
      * Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
      *
      * Licensed under the OpenSSL license (the "License").  You may not use
      * this file except in compliance with the License.  You can obtain a copy
      * in the file LICENSE in the source distribution or at
      * https://www.openssl.org/source/license.html
      */

// inline RSA_METHOD *RSA_meth_new(const char *name, int flags) {
//     RSA_METHOD *meth = OPENSSL_zalloc(sizeof(*meth));

//     if (meth != NULL) {
//         meth->flags = flags;

//         meth->name = OPENSSL_strdup(name);
//         if (meth->name != NULL)
//             return meth;

//         OPENSSL_free(meth);
//     }

//     RSAerr(RSA_F_RSA_METH_NEW, ERR_R_MALLOC_FAILURE);
//     return NULL;
// }

// inline void RSA_meth_free(RSA_METHOD *meth) {
//     if (meth != NULL) {
//         OPENSSL_free(meth->name);
//         OPENSSL_free(meth);
//     }
// }

// inline RSA_METHOD *RSA_meth_dup(const RSA_METHOD *meth) {
//     RSA_METHOD *ret = OPENSSL_malloc(sizeof(*ret));

//     if (ret != NULL) {
//         memcpy(ret, meth, sizeof(*meth));

//         ret->name = OPENSSL_strdup(meth->name);
//         if (ret->name != NULL)
//             return ret;

//         OPENSSL_free(ret);
//     }

//     RSAerr(RSA_F_RSA_METH_DUP, ERR_R_MALLOC_FAILURE);
//     return NULL;
// }

inline const char *RSA_meth_get0_name(const RSA_METHOD *meth) {
    return meth->name;
}

// inline int RSA_meth_set1_name(RSA_METHOD *meth, const char *name) {
//     char *tmpname = OPENSSL_strdup(name);

//     if (tmpname == NULL) {
//         RSAerr(RSA_F_RSA_METH_SET1_NAME, ERR_R_MALLOC_FAILURE);
//         return 0;
//     }

//     OPENSSL_free(meth->name);
//     meth->name = tmpname;

//     return 1;
// }

inline int RSA_meth_get_flags(RSA_METHOD *meth) {
    return meth->flags;
}

inline int RSA_meth_set_flags(RSA_METHOD *meth, int flags) {
    meth->flags = flags;
    return 1;
}

inline void *RSA_meth_get0_app_data(const RSA_METHOD *meth) {
    return meth->app_data;
}

// inline int RSA_meth_set0_app_data(RSA_METHOD *meth, char *app_data) {
//     meth->app_data = app_data;
//     return 1;
// }

inline int (*RSA_meth_get_pub_enc(const RSA_METHOD *meth))
    (int flen, const unsigned char *from,
     unsigned char *to, RSA *rsa, int padding) {
    return meth->rsa_pub_enc;
}

inline int RSA_meth_set_pub_enc(RSA_METHOD *meth,
                         int (*pub_enc) (int flen, const unsigned char *from,
                                         unsigned char *to, RSA *rsa,
                                         int padding)) {
    meth->rsa_pub_enc = pub_enc;
    return 1;
}

inline int (*RSA_meth_get_pub_dec(const RSA_METHOD *meth))
    (int flen, const unsigned char *from,
     unsigned char *to, RSA *rsa, int padding) {
    return meth->rsa_pub_dec;
}

inline int RSA_meth_set_pub_dec(RSA_METHOD *meth,
                         int (*pub_dec) (int flen, const unsigned char *from,
                                         unsigned char *to, RSA *rsa,
                                         int padding)) {
    meth->rsa_pub_dec = pub_dec;
    return 1;
}

inline int (*RSA_meth_get_priv_enc(const RSA_METHOD *meth))
    (int flen, const unsigned char *from,
     unsigned char *to, RSA *rsa, int padding) {
    return meth->rsa_priv_enc;
}

inline int RSA_meth_set_priv_enc(RSA_METHOD *meth,
                          int (*priv_enc) (int flen, const unsigned char *from,
                                           unsigned char *to, RSA *rsa,
                                           int padding)) {
    meth->rsa_priv_enc = priv_enc;
    return 1;
}

inline int (*RSA_meth_get_priv_dec(const RSA_METHOD *meth))
    (int flen, const unsigned char *from,
     unsigned char *to, RSA *rsa, int padding) {
    return meth->rsa_priv_dec;
}

inline int RSA_meth_set_priv_dec(RSA_METHOD *meth,
                          int (*priv_dec) (int flen, const unsigned char *from,
                                           unsigned char *to, RSA *rsa,
                                           int padding)) {
    meth->rsa_priv_dec = priv_dec;
    return 1;
}

    /* Can be null */
inline int (*RSA_meth_get_mod_exp(const RSA_METHOD *meth))
    (BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) {
    return meth->rsa_mod_exp;
}

inline int RSA_meth_set_mod_exp(RSA_METHOD *meth,
                         int (*mod_exp) (BIGNUM *r0, const BIGNUM *I, RSA *rsa,
                                         BN_CTX *ctx)) {
    meth->rsa_mod_exp = mod_exp;
    return 1;
}

    /* Can be null */
inline int (*RSA_meth_get_bn_mod_exp(const RSA_METHOD *meth))
    (BIGNUM *r, const BIGNUM *a, const BIGNUM *p,
     const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) {
    return meth->bn_mod_exp;
}

inline int RSA_meth_set_bn_mod_exp(RSA_METHOD *meth,
                            int (*bn_mod_exp) (BIGNUM *r,
                                               const BIGNUM *a,
                                               const BIGNUM *p,
                                               const BIGNUM *m,
                                               BN_CTX *ctx,
                                               BN_MONT_CTX *m_ctx)) {
    meth->bn_mod_exp = bn_mod_exp;
    return 1;
}

    /* called at new */
inline int (*RSA_meth_get_init(const RSA_METHOD *meth)) (RSA *rsa) {
    return meth->init;
}

inline int RSA_meth_set_init(RSA_METHOD *meth, int (*init) (RSA *rsa)) {
    meth->init = init;
    return 1;
}

inline     /* called at free */
int (*RSA_meth_get_finish(const RSA_METHOD *meth)) (RSA *rsa) {
    return meth->finish;
}

inline int RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa)) {
    meth->finish = finish;
    return 1;
}

inline int (*RSA_meth_get_sign(const RSA_METHOD *meth))
    (int type,
     const unsigned char *m, unsigned int m_length,
     unsigned char *sigret, unsigned int *siglen,
     const RSA *rsa) {
    return meth->rsa_sign;
}

inline int RSA_meth_set_sign(RSA_METHOD *meth,
                      int (*sign) (int type, const unsigned char *m,
                                   unsigned int m_length,
                                   unsigned char *sigret, unsigned int *siglen,
                                   const RSA *rsa)) {
    meth->rsa_sign = sign;
    return 1;
}

inline int (*RSA_meth_get_verify(const RSA_METHOD *meth))
    (int dtype, const unsigned char *m,
     unsigned int m_length, const unsigned char *sigbuf,
     unsigned int siglen, const RSA *rsa) {
    return meth->rsa_verify;
}

inline int RSA_meth_set_verify(RSA_METHOD *meth,
                        int (*verify) (int dtype, const unsigned char *m,
                                       unsigned int m_length,
                                       const unsigned char *sigbuf,
                                       unsigned int siglen, const RSA *rsa)) {
    meth->rsa_verify = verify;
    return 1;
}

inline int (*RSA_meth_get_keygen(const RSA_METHOD *meth))
    (RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) {
    return meth->rsa_keygen;
}

inline int RSA_meth_set_keygen(RSA_METHOD *meth,
                        int (*keygen) (RSA *rsa, int bits, BIGNUM *e,
                                       BN_GENCB *cb)) {
    meth->rsa_keygen = keygen;
    return 1;
}



  /* the following is copied and modified from OpenSSL 1.1 openssl-1.1.0g stack.h */

#  define OPENSSL_STACK STACK
#  define OPENSSL_sk_num sk_num
#  define OPENSSL_sk_value sk_value
#  define OPENSSL_sk_set sk_set
#  define OPENSSL_sk_new sk_new
#  define OPENSSL_sk_new_null sk_new_null
#  define OPENSSL_sk_free sk_free
#  define OPENSSL_sk_pop_free sk_pop_free
#  define OPENSSL_sk_deep_copy sk_deep_copy
#  define OPENSSL_sk_insert sk_insert
#  define OPENSSL_sk_delete sk_delete
#  define OPENSSL_sk_delete_ptr sk_delete_ptr
#  define OPENSSL_sk_find sk_find
#  define OPENSSL_sk_find_ex sk_find_ex
#  define OPENSSL_sk_push sk_push
#  define OPENSSL_sk_unshift sk_unshift
#  define OPENSSL_sk_shift sk_shift
#  define OPENSSL_sk_pop sk_pop
#  define OPENSSL_sk_zero sk_zero
#  define OPENSSL_sk_set_cmp_func sk_set_cmp_func
#  define OPENSSL_sk_dup sk_dup
#  define OPENSSL_sk_sort sk_sort
#  define OPENSSL_sk_is_sorted sk_is_sorted
  
#endif
  // end of copied code
  //------------------------------------------------------------------------------
  //@}

}
