#include <unistd.h>
#include <string.h>
#include "fmt.h"
#include "pathexec.h"
#include "ucspissl.h"
#include "stralloc.h"
#include "str.h"

static char strnum[FMT_ULONG];
static stralloc ctemp = {0};
static stralloc *envsa = 0;
static stralloc btemp = {0};
static stralloc etemp = {0};

#define set_env_id(n,e,v) \
if (!set_env_name_entry((n),(e),(v))) return 0

static int env_val(const char *env,const void *val,int len) {
  const char *v = val;
  if (envsa) {
    if (!stralloc_cats(envsa,env)) return 0;
    if (!stralloc_catb(envsa,"=",1)) return 0;
    if (!stralloc_catb(envsa,v,len)) return 0;
    if (!stralloc_0(envsa)) return 0;
    return 1;
  }
  if (!stralloc_copyb(&etemp,v,len)) return 0;
  if (!stralloc_0(&etemp)) return 0;
  return pathexec_env(env,etemp.s);
}

static int env_str(const char *env,const char *val) {
  if (envsa) {
    return env_val(env,val,str_len(val));
    if (!stralloc_cats(envsa,env)) return 0;
    if (!stralloc_catb(envsa,"=",1)) return 0;
    if (!stralloc_catb(envsa,val,str_len(val) + 1)) return 0;
    return 1;
  }
  return pathexec_env(env,val);
}

static int set_env_name_entry(X509_NAME *xname,const char *env,int nid) {
  X509_NAME_ENTRY *xne;
  int m;
  int n;

  if (!env) return 1;
#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
  for (m = 0; m < sk_X509_NAME_ENTRY_num(xname->entries); m++) {
    xne = sk_X509_NAME_ENTRY_value(xname->entries,m);
    n = OBJ_obj2nid(xne->object);
    if (n == nid)
      if (!env_val(env,xne->value->data,xne->value->length)) return 0;
#else
  for (m = 0; m < X509_NAME_entry_count(xname); m++) {
    xne = X509_NAME_get_entry(xname,m);
    n = OBJ_obj2nid(X509_NAME_ENTRY_get_object(xne));
    if (n == nid)
      if (!env_val(env,X509_NAME_ENTRY_get_data(xne)->data,X509_NAME_ENTRY_get_data(xne)->length)) return 0;
#endif
  }

  return 1;
}

int ssl_session_vars(SSL *ssl) {
  unsigned const char *x;
  SSL_SESSION *session;
  unsigned int n = 0;
  int m;
  const SSL_CIPHER *cipher;
  unsigned char u;
  unsigned char c;

  if (!env_str("SSL_PROTOCOL",SSL_get_version(ssl)))
    return 0;

#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // 0xmnnffppsL
  session = SSL_get_session(ssl);
  x = session->session_id;
  n = session->session_id_length;
#else
  session = SSL_get1_session(ssl);
  x = SSL_SESSION_get_id(session,&n);
#endif

  if (!stralloc_ready(&btemp,2 * n)) return 0;
  btemp.len = 2 * n;
  while (n--) {
    u = x[n];
    c = '0' + (u & 15); 
    if (c > '0' + 9) c += 'a' - '0' - 10;
    btemp.s[2 * n + 1] = c;
    u >>= 4;
    c = '0' + (u & 15); 
    if (c > '0' + 9) c += 'a' - '0' - 10;
    btemp.s[2 * n] = c;
  }
  if (!env_val("SSL_SESSION_ID",btemp.s,btemp.len)) return 0;

  if (!env_str("SSL_CIPHER",SSL_get_cipher_name(ssl))) return 0;
  
  cipher = SSL_get_current_cipher(ssl);
  if (!cipher) return 0;
  n = SSL_CIPHER_get_bits(cipher,&m);
  if (!env_str("SSL_CIPHER_EXPORT",n < 56 ? "true" : "false")) return 0;
  if (!env_val("SSL_CIPHER_USEKEYSIZE",strnum,fmt_ulong(strnum,n))) return 0;
  if (!env_val("SSL_CIPHER_ALGKEYSIZE",strnum,fmt_ulong(strnum,m))) return 0;

  if (!env_str("SSL_VERSION_INTERFACE","ucspi-ssl")) return 0;
  if (!env_str("SSL_VERSION_LIBRARY",OPENSSL_VERSION_TEXT)) return 0;

  return 1;
}

static int ssl_client_bio_vars(X509 *cert,STACK_OF(X509) *chain,BIO *bio) {
  ASN1_STRING *astring;
  int n;
  int m;

  astring = X509_get_notBefore(cert);
  if (!ASN1_UTCTIME_print(bio,astring)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_CLIENT_V_START",btemp.s,btemp.len)) return 0;

  astring = X509_get_notAfter(cert);
  if (!ASN1_UTCTIME_print(bio,astring)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_CLIENT_V_END",btemp.s,btemp.len)) return 0;

  if (!PEM_write_bio_X509(bio,cert)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_CLIENT_CERT",btemp.s,btemp.len)) return 0;

  if (chain) {
    for (m = 0; m < sk_X509_num(chain); m++) {
      if (!stralloc_copys(&ctemp,"SSL_CLIENT_CERT_CHAIN_")) return 0;
      if (!stralloc_catb(&ctemp,strnum,fmt_ulong(strnum,m))) return 0;
      if (!stralloc_0(&ctemp)) return 0;

      if (m < sk_X509_num(chain)) {
        if (!PEM_write_bio_X509(bio,sk_X509_value(chain,m))) return 0;
        n = BIO_pending(bio);
        if (!stralloc_ready(&btemp,n)) return 0;
        btemp.len = n;
        n = BIO_read(bio,btemp.s,n);
        if (n != btemp.len) return 0;
        if (!env_val(ctemp.s,btemp.s,btemp.len)) return 0;
      }
    }
  }

  return 1;
}

static int ssl_client_vars(X509 *cert,STACK_OF(X509) *chain) {
  X509_NAME *xname;
  X509_PUBKEY *pubkey;
  const X509_ALGOR *sigalg;
  const ASN1_OBJECT *calgoid;
  ASN1_OBJECT *algoid;
  BIGNUM *bn;
  BIO *bio;
  char *x = 0;
  int n;

  if (!cert) return 1;

  if (!env_val("SSL_CLIENT_M_VERSION",strnum,fmt_ulong(strnum,X509_get_version(cert) + 1)))
    return 0;

  bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), 0);
  x = BN_bn2dec(bn);
  BN_free(bn);
  if (!env_val("SSL_CLIENT_M_SERIAL",x,strlen(x)))
    return 0;
  OPENSSL_free(x);

  xname = X509_get_subject_name(cert);
  x = X509_NAME_oneline(xname,0,0);
  n = env_str("SSL_CLIENT_S_DN",x);
  free(x);
  if (!n) return 0;

  set_env_id(xname,"SSL_CLIENT_S_DN_C",NID_countryName);
  set_env_id(xname,"SSL_CLIENT_S_DN_ST",NID_stateOrProvinceName);
  set_env_id(xname,"SSL_CLIENT_S_DN_L",NID_localityName);
  set_env_id(xname,"SSL_CLIENT_S_DN_O",NID_organizationName);
  set_env_id(xname,"SSL_CLIENT_S_DN_OU",NID_organizationalUnitName);
  set_env_id(xname,"SSL_CLIENT_S_DN_CN",NID_commonName);
  set_env_id(xname,"SSL_CLIENT_S_DN_T",NID_title);
  set_env_id(xname,"SSL_CLIENT_S_DN_I",NID_initials);
  set_env_id(xname,"SSL_CLIENT_S_DN_G",NID_givenName);
  set_env_id(xname,"SSL_CLIENT_S_DN_S",NID_surname);
  set_env_id(xname,"SSL_CLIENT_S_DN_D",NID_description);
  set_env_id(xname,"SSL_CLIENT_S_DN_UID",NID_x500UniqueIdentifier);
  set_env_id(xname,"SSL_CLIENT_S_DN_Email",NID_pkcs9_emailAddress);

  xname = X509_get_issuer_name(cert);
  x = X509_NAME_oneline(xname,0,0);
  n = env_str("SSL_CLIENT_I_DN",x);
  free(x);
  if (!n) return 0;

  set_env_id(xname,"SSL_CLIENT_I_DN_C",NID_countryName);
  set_env_id(xname,"SSL_CLIENT_I_DN_ST",NID_stateOrProvinceName);
  set_env_id(xname,"SSL_CLIENT_I_DN_L",NID_localityName);
  set_env_id(xname,"SSL_CLIENT_I_DN_O",NID_organizationName);
  set_env_id(xname,"SSL_CLIENT_I_DN_OU",NID_organizationalUnitName);
  set_env_id(xname,"SSL_CLIENT_I_DN_CN",NID_commonName);
  set_env_id(xname,"SSL_CLIENT_I_DN_T",NID_title);
  set_env_id(xname,"SSL_CLIENT_I_DN_I",NID_initials);
  set_env_id(xname,"SSL_CLIENT_I_DN_G",NID_givenName);
  set_env_id(xname,"SSL_CLIENT_I_DN_S",NID_surname);
  set_env_id(xname,"SSL_CLIENT_I_DN_D",NID_description);
  set_env_id(xname,"SSL_CLIENT_I_DN_UID",NID_x500UniqueIdentifier);
  set_env_id(xname,"SSL_CLIENT_I_DN_Email",NID_pkcs9_emailAddress);

/* Signature Algorithm for PubKey */
#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
  n = OBJ_obj2nid(cert->cert_info->signature->algorithm);
#else
  sigalg = X509_get0_tbs_sigalg(cert);
  X509_ALGOR_get0(&calgoid,0,0,sigalg);
  n = OBJ_obj2nid(calgoid);
#endif
  if (!env_str("SSL_CLIENT_A_SIG",(n == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(n)))
    return 0;

/* Algorithm for PubKey */
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // 0xmnnffppsL
  n = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);
#else
  pubkey = X509_get_X509_PUBKEY(cert);
  X509_PUBKEY_get0_param(&algoid,0,0,0,pubkey);
  n = OBJ_obj2nid(algoid);
#endif
  if (!env_str("SSL_CLIENT_A_KEY",(n == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(n)))
    return 0;

  bio = BIO_new(BIO_s_mem());
  if (!bio) return 0;
  n = ssl_client_bio_vars(cert,chain,bio);
  BIO_free(bio);
  if (!n) return 0;

  return 1;
}

static int ssl_server_bio_vars(X509 *cert,STACK_OF(X509) *chain,BIO *bio) {
  ASN1_STRING *astring;
  int n;
  int m;

  astring = X509_get_notBefore(cert);
  if (!ASN1_UTCTIME_print(bio,astring)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_SERVER_V_START",btemp.s,btemp.len)) return 0;

  astring = X509_get_notAfter(cert);
  if (!ASN1_UTCTIME_print(bio,astring)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_SERVER_V_END",btemp.s,btemp.len)) return 0;

  if (!PEM_write_bio_X509(bio,cert)) return 0;
  n = BIO_pending(bio);
  if (!stralloc_ready(&btemp,n)) return 0;
  btemp.len = n;
  n = BIO_read(bio,btemp.s,n);
  if (n != btemp.len) return 0;
  if (!env_val("SSL_SERVER_CERT",btemp.s,btemp.len)) return 0;

  if (chain) {
    for (m = 0; m < sk_X509_num(chain); m++) {
      if (!stralloc_copys(&ctemp,"SSL_SERVER_CERT_CHAIN_")) return 0;
      if (!stralloc_catb(&ctemp,strnum,fmt_ulong(strnum,m))) return 0;
      if (!stralloc_0(&ctemp)) return 0;

      if (m < sk_X509_num(chain)) {
        if (!PEM_write_bio_X509(bio,sk_X509_value(chain,m))) return 0;
        n = BIO_pending(bio);
        if (!stralloc_ready(&btemp,n)) return 0;
        btemp.len = n;
        n = BIO_read(bio,btemp.s,n);
        if (n != btemp.len) return 0;
        if (!env_val(ctemp.s,btemp.s,btemp.len)) return 0;
      }
    }
  }

  return 1;
}

static int ssl_server_vars(X509 *cert,STACK_OF(X509) *chain) {
  X509_NAME *xname;
  X509_PUBKEY *pubkey;
  const X509_ALGOR *sigalg; 
  const ASN1_OBJECT *calgoid;
  ASN1_OBJECT *algoid;
  BIGNUM *bn;
  BIO *bio;
  char *x = 0;
  int n;

  if (!cert) return 1;

  if (!env_val("SSL_SERVER_M_VERSION",strnum,fmt_ulong(strnum,X509_get_version(cert) + 1)))
    return 0;

  bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), 0);
  x = BN_bn2dec(bn);
  BN_free(bn);
  if (!env_val("SSL_SERVER_M_SERIAL",x,strlen(x))) return 0;
  OPENSSL_free(x);

  xname = X509_get_subject_name(cert);
  x = X509_NAME_oneline(xname,0,0);
  n = env_str("SSL_SERVER_S_DN",x);
  free(x);
  if (!n) return 0;

  set_env_id(xname,"SSL_SERVER_S_DN_C",NID_countryName);
  set_env_id(xname,"SSL_SERVER_S_DN_ST",NID_stateOrProvinceName);
  set_env_id(xname,"SSL_SERVER_S_DN_L",NID_localityName);
  set_env_id(xname,"SSL_SERVER_S_DN_O",NID_organizationName);
  set_env_id(xname,"SSL_SERVER_S_DN_OU",NID_organizationalUnitName);
  set_env_id(xname,"SSL_SERVER_S_DN_CN",NID_commonName);
  set_env_id(xname,"SSL_SERVER_S_DN_T",NID_title);
  set_env_id(xname,"SSL_SERVER_S_DN_I",NID_initials);
  set_env_id(xname,"SSL_SERVER_S_DN_G",NID_givenName);
  set_env_id(xname,"SSL_SERVER_S_DN_S",NID_surname);
  set_env_id(xname,"SSL_SERVER_S_DN_D",NID_description);
  set_env_id(xname,"SSL_SERVER_S_DN_UID",NID_x500UniqueIdentifier);
  set_env_id(xname,"SSL_SERVER_S_DN_Email",NID_pkcs9_emailAddress);

  xname = X509_get_issuer_name(cert);
  x = X509_NAME_oneline(xname,0,0);
  n = env_str("SSL_SERVER_I_DN",x);
  free(x);
  if (!n) return 0;

  set_env_id(xname,"SSL_SERVER_I_DN_C",NID_countryName);
  set_env_id(xname,"SSL_SERVER_I_DN_ST",NID_stateOrProvinceName);
  set_env_id(xname,"SSL_SERVER_I_DN_L",NID_localityName);
  set_env_id(xname,"SSL_SERVER_I_DN_O",NID_organizationName);
  set_env_id(xname,"SSL_SERVER_I_DN_OU",NID_organizationalUnitName);
  set_env_id(xname,"SSL_SERVER_I_DN_CN",NID_commonName);
  set_env_id(xname,"SSL_SERVER_I_DN_T",NID_title);
  set_env_id(xname,"SSL_SERVER_I_DN_I",NID_initials);
  set_env_id(xname,"SSL_SERVER_I_DN_G",NID_givenName);
  set_env_id(xname,"SSL_SERVER_I_DN_S",NID_surname);
  set_env_id(xname,"SSL_SERVER_I_DN_D",NID_description);
  set_env_id(xname,"SSL_SERVER_I_DN_UID",NID_x500UniqueIdentifier);
  set_env_id(xname,"SSL_SERVER_I_DN_Email",NID_pkcs9_emailAddress);

/* Signature Algorithm of PubKey */
#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
  n = OBJ_obj2nid(cert->cert_info->signature->algorithm);
#else
  sigalg = X509_get0_tbs_sigalg(cert);
  X509_ALGOR_get0(&calgoid,0,0,sigalg);
  n = OBJ_obj2nid(calgoid);
#endif
  if (!env_str("SSL_SERVER_A_SIG",(n == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(n)))
    return 0;

/* Algorithm of PubKey */
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // 0xmnnffppsL
  n = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);
#else
  pubkey = X509_get_X509_PUBKEY(cert);
  X509_PUBKEY_get0_param(&algoid,0,0,0,pubkey);
  n = OBJ_obj2nid(algoid);
#endif
  if (!env_str("SSL_SERVER_A_KEY",(n == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(n)))
    return 0;

  bio = BIO_new(BIO_s_mem());
  if (!bio) return 0;
  n = ssl_server_bio_vars(cert,chain,bio);
  BIO_free(bio);

  if (!n) return 0;

  return 1;
}

int ssl_client_env(SSL *ssl,stralloc *sa) {
  envsa = sa;
  if (!ssl_session_vars(ssl)) return 0;
  if (!ssl_client_vars(SSL_get_certificate(ssl),0))
    return 0;
#if (OPENSSL_VERSION_NUMBER > 0x30000000L) // 0xmnnffppsL
  if (!ssl_server_vars(0,SSL_get_peer_cert_chain(ssl)))
#else
   if (!ssl_server_vars(SSL_get_peer_certificate(ssl),SSL_get_peer_cert_chain(ssl)))
#endif
    return 0;
  return 1;
}

int ssl_server_env(SSL *ssl,stralloc *sa) {
  envsa = sa;
  if (!ssl_session_vars(ssl)) return 0;
  if (!ssl_server_vars(SSL_get_certificate(ssl),0))
    return 0;
#if (OPENSSL_VERSION_NUMBER > 0x30000000L) // 0xmnnffppsL
  if (!ssl_server_vars(0,SSL_get_peer_cert_chain(ssl)))
#else
  if (!ssl_client_vars(SSL_get_peer_certificate(ssl),SSL_get_peer_cert_chain(ssl)))
#endif
    return 0;
  return 1;
}
