//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2019 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    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 Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.


#pragma once

#if OPENSSL_VERSION_NUMBER < 0x10100000L

#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/hmac.h>

// make sure type 94 doesn't collide with anything in bio.h
// Start with the same number as before

static int lastindex = 94;
inline int BIO_get_new_index(void)
{
  int newval = lastindex | BIO_TYPE_SOURCE_SINK;
  lastindex++;
  return newval;
}

inline BIO_METHOD *BIO_meth_new(int type, const char *name)
{
  BIO_METHOD *biom = new BIO_METHOD();

  if ((biom->name = OPENSSL_strdup(name)) == nullptr)
    {
      delete biom;
      BIOerr(BIO_F_BIO_NEW, ERR_R_MALLOC_FAILURE);
      return nullptr;
    }
  biom->type = type;
  return biom;
}

inline void BIO_meth_free(BIO_METHOD *biom)
{
  if (biom != nullptr)
    {
      OPENSSL_free((void *)biom->name);
      delete biom;
    }
}

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

  meth->flags = flags;
  meth->name = name;

  return meth;
}

inline void RSA_meth_free(RSA_METHOD *meth)
{
  delete meth;
}

inline HMAC_CTX *HMAC_CTX_new()
{
  HMAC_CTX *ctx = new HMAC_CTX();
  HMAC_CTX_init(ctx);
  return ctx;
}

inline void HMAC_CTX_free(HMAC_CTX *ctx)
{
  HMAC_CTX_cleanup(ctx);
  delete ctx;
}

inline EVP_MD_CTX *EVP_MD_CTX_new()
{
  return new EVP_MD_CTX();
}

inline void EVP_MD_CTX_free(EVP_MD_CTX *ctx)
{
  delete ctx;
}

inline void BIO_set_shutdown(BIO *a, int shut)
{
  a->shutdown = shut;
}

inline int BIO_get_shutdown(BIO *a)
{
  return a->shutdown;
}

inline void BIO_set_data(BIO *a, void *ptr)
{
  a->ptr = ptr;
}

inline void *BIO_get_data(BIO *a)
{
  return a->ptr;
}

inline void BIO_set_init(BIO *a, int init)
{
  a->init = init;
}

inline int BIO_get_init(BIO *a)
{
  return a->init;
}

inline int BIO_meth_set_write(BIO_METHOD *biom,
			      int (*bwrite)(BIO *, const char *, int))
{
  biom->bwrite = bwrite;
  return 1;
}

inline int BIO_meth_set_read(BIO_METHOD *biom,
			     int (*bread)(BIO *, char *, int))
{
  biom->bread = bread;
  return 1;
}

inline int BIO_meth_set_puts(BIO_METHOD *biom,
			     int (*bputs)(BIO *, const char *))
{
  biom->bputs = bputs;
  return 1;
}

inline int BIO_meth_set_gets(BIO_METHOD *biom,
			     int (*bgets)(BIO *, char *, int))
{
  biom->bgets = bgets;
  return 1;
}

inline int BIO_meth_set_ctrl(BIO_METHOD *biom,
			     long (*ctrl)(BIO *, int, long, void *))
{
  biom->ctrl = ctrl;
  return 1;
}

inline int BIO_meth_set_create(BIO_METHOD *biom, int (*create)(BIO *))
{
  biom->create = create;
  return 1;
}

inline int BIO_meth_set_destroy(BIO_METHOD *biom, int (*destroy)(BIO *))
{
  biom->destroy = destroy;
  return 1;
}

inline RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
{
  return pkey->pkey.rsa;
}

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_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_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_init(RSA_METHOD *meth, int (*init)(RSA *rsa))
{
  meth->init = init;
  return 1;
}

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

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

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

inline DSA *EVP_PKEY_get0_DSA(EVP_PKEY *pkey)
{
  return pkey->pkey.dsa;
}

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

  if (q != nullptr)
    *q = d->q;

  if (g != nullptr)
    *g = d->g;
}

inline void RSA_set_flags(RSA *r, int flags)
{
  r->flags |= flags;
}

inline int RSA_set0_key(RSA *rsa, BIGNUM *n, BIGNUM *e, BIGNUM *d)
{
  if ((rsa->n == nullptr && n == nullptr)
      || (rsa->e == nullptr && e == nullptr))
    return 0;

  if (n != nullptr)
    {
      BN_free(rsa->n);
      rsa->n = n;
    }

  if (e != nullptr)
    {
      BN_free(rsa->e);
      rsa->e = e;
    }

  if (d != nullptr)
    {
      BN_free(rsa->d);
      rsa->d = d;
    }

  return 1;
}

inline void RSA_get0_key(const RSA *rsa, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
{
  if (n != nullptr)
    *n = rsa->n;

  if (e != nullptr)
    *e = rsa->e;

  if (d != nullptr)
    *d = rsa->d;
}

/* Renamed in OpenSSL 1.1 */
#define X509_get0_pubkey X509_get_pubkey
#define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT RSA_F_RSA_EAY_PRIVATE_ENCRYPT

/*
 * EVP_CIPHER_CTX_init and EVP_CIPHER_CTX_cleanup are both replaced by
 * EVP_CIPHER_CTX_reset in OpenSSL 1.1 but replacing them both with
 * reset is wrong for older version. The man page mention cleanup
 * being officially removed and init to be an alias for reset.
 *
 * So we only use reset as alias for init in older versions.
 *
 * EVP_CIPHER_CTX_free already implicitly calls EVP_CIPHER_CTX_cleanup in
 * 1.0.2, so we can avoid using the old API.
 */
#define EVP_CIPHER_CTX_reset	EVP_CIPHER_CTX_init
#endif

#if OPENSSL_VERSION_NUMBER < 0x10101000L
#include <openssl/rsa.h>
#include <openssl/dsa.h>

inline const BIGNUM *RSA_get0_n(const RSA *r)
{
    const BIGNUM *n;
    RSA_get0_key(r, &n, nullptr, nullptr);
    return n;
}

inline const BIGNUM *RSA_get0_e(const RSA *r)
{
    const BIGNUM *e;
    RSA_get0_key(r, nullptr, &e, nullptr);
    return e;
}

inline const BIGNUM *DSA_get0_p(const DSA *d)
{
    const BIGNUM *p;
    DSA_get0_pqg(d, &p, nullptr, nullptr);
    return p;
}
#endif
