#include "test.h"

#ifdef USE_SSLEAY

#include <sys/time.h>
#include <sys/types.h>

#include <openssl/opensslv.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <openssl/crypto.h>
#include <openssl/lhash.h>
#include <openssl/objects.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pkcs12.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>

int portnum; /* the HTTPS port number we use */

typedef struct sslctxparm_st {
  CURL* curl;
  int accesstype;
  unsigned char * accessinfoURL;
} sslctxparm;


static unsigned char *i2s_ASN1_IA5STRING( ASN1_IA5STRING *ia5)
{
  unsigned char *tmp;
  if(!ia5 || !ia5->length)
    return NULL;
  tmp = OPENSSL_malloc(ia5->length + 1);
  memcpy(tmp, ia5->data, ia5->length);
  tmp[ia5->length] = 0;
  return tmp;
}

/* A conveniance routine to get an access URI. */

static unsigned char *my_get_ext(X509 * cert, const int type,
                                 int extensiontype)
{
  int i;
  STACK_OF(ACCESS_DESCRIPTION) * accessinfo ;
  accessinfo =  X509_get_ext_d2i(cert, extensiontype, NULL, NULL) ;

  if (!sk_ACCESS_DESCRIPTION_num(accessinfo))
    return NULL;

  for (i = 0; i < sk_ACCESS_DESCRIPTION_num(accessinfo); i++) {
    ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(accessinfo, i);
    if (OBJ_obj2nid(ad->method) == type) {
      if (ad->location->type == GEN_URI) {
        return i2s_ASN1_IA5STRING(ad->location->d.ia5);
      }
      return NULL;
    }
  }
  return NULL;
}

void * globalparm = NULL;

char newurl[512];

static int ssl_app_verify_callback(X509_STORE_CTX *ctx, void *arg)
{
  sslctxparm * p = (sslctxparm *) arg;
  int ok, err;

  fprintf(stderr,"ssl_app_verify_callback sslctxparm=%p ctx=%p\n",
          (void *)p, (void*)ctx);

#if OPENSSL_VERSION_NUMBER<0x00907000L
/* not necessary in openssl 0.9.7 or later */

  fprintf(stderr,"This version %s of openssl does not support a parm (%p)"
          ", getting a global static %p \n",
          OPENSSL_VERSION_TEXT, (void *)p, (void *)globalparm);

  p = globalparm;
#endif

/* The following error should not occur. We test this to avoid segfault. */
  if (!p || !ctx) {
    fprintf(stderr,"Internal error in ssl_app_verify_callback "
            "sslctxparm=%p ctx=%p\n",(void *)p,(void*)ctx);
    return 0;
  }

  ok= X509_verify_cert(ctx);
  err=X509_STORE_CTX_get_error(ctx);

/* The following seems to be a problem in 0.9.7/8 openssl versions */

#if 1
  if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
      err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
    fprintf(stderr,"X509_verify_cert: repairing self signed\n") ;
    X509_STORE_CTX_set_error(ctx,X509_V_OK);
    ok = 1;
  }
#endif

  if (ok && ctx->cert) {
    unsigned char * accessinfoURL ;

    accessinfoURL = my_get_ext(ctx->cert,p->accesstype ,NID_info_access);
    if (accessinfoURL) {

      if (strcmp((char *)p->accessinfoURL, (char *)accessinfoURL)) {
        fprintf(stderr, "Setting URL <%s>, was <%s>\n",
                (char *)accessinfoURL, (char *)p->accessinfoURL);
        OPENSSL_free(p->accessinfoURL);
        p->accessinfoURL = accessinfoURL;

        /* We need to be able to deal with a custom port number, but the
           URL in the cert uses a static one. We thus need to create a new
           URL that uses the currently requested port number which may not
           be the one this URL uses! */
        sprintf(newurl, "https://127.0.0.1:%d/509", portnum);
        fprintf(stderr, "But *really* Setting URL <%s>\n", newurl);

        curl_easy_setopt(p->curl, CURLOPT_URL, newurl);
      }
      else
        OPENSSL_free(accessinfoURL);
    }
  }
  return(ok);
}


static CURLcode sslctxfun(CURL * curl, void * sslctx, void * parm)
{
  sslctxparm * p = (sslctxparm *) parm;

  SSL_CTX * ctx = (SSL_CTX *) sslctx ;
  fprintf(stderr,"sslctxfun start curl=%p ctx=%p parm=%p\n",
          (void *)curl,(void *)ctx,(void *)p);

  SSL_CTX_set_quiet_shutdown(ctx,1);
  SSL_CTX_set_cipher_list(ctx,"RC4-MD5");
  SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);

/* one might assume that the cert validaton would not fail when setting this,
   but it still does, see the error handling in the call back */

  SSL_CTX_set_verify_depth(ctx,0);
  SSL_CTX_set_verify(ctx,SSL_VERIFY_NONE,NULL);

#if OPENSSL_VERSION_NUMBER<0x00907000L
/* in newer openssl versions we can set a parameter for the call back. */
  fprintf(stderr,"This version %s of openssl does not support a parm,"
          " setting global one\n", OPENSSL_VERSION_TEXT);
  /* this is only done to support 0.9.6 version */
  globalparm = parm;

/* in 0.9.6 the parm is not taken */
#endif
  SSL_CTX_set_cert_verify_callback(ctx, ssl_app_verify_callback, parm);
  fprintf(stderr,"sslctxfun end\n");

  return CURLE_OK ;
}

int test(char *URL)
{
  CURLM* multi;
  sslctxparm p;

  int i = 0;
  CURLMsg *msg;

  if(arg2) {
    portnum = atoi(arg2);
  }

  curl_global_init(CURL_GLOBAL_ALL);

  p.curl = curl_easy_init();

  p.accessinfoURL = (unsigned char *) strdup(URL);
  p.accesstype = OBJ_obj2nid(OBJ_txt2obj("AD_DVCS",0)) ;

  curl_easy_setopt(p.curl, CURLOPT_URL, p.accessinfoURL);

  curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun)  ;
  curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_DATA, &p);

  curl_easy_setopt(p.curl, CURLOPT_SSL_VERIFYPEER, FALSE);
  curl_easy_setopt(p.curl, CURLOPT_SSL_VERIFYHOST, 1);

  fprintf(stderr, "Going to perform %s\n", (char *)p.accessinfoURL);

  {
    CURLMcode res;
    int running;
    char done=FALSE;

    multi = curl_multi_init();

    res = curl_multi_add_handle(multi, p.curl);

    while(!done) {
      fd_set rd, wr, exc;
      int max_fd;
      struct timeval interval;

      interval.tv_sec = 1;
      interval.tv_usec = 0;

      while (res == CURLM_CALL_MULTI_PERFORM) {
        res = curl_multi_perform(multi, &running);
        fprintf(stderr, "running=%d res=%d\n",running,res);
        if (running <= 0) {
          done = TRUE;
          break;
        }
      }
      if(done)
        break;

      if (res != CURLM_OK) {
        fprintf(stderr, "not okay???\n");
        i = 80;
        break;
      }

      FD_ZERO(&rd);
      FD_ZERO(&wr);
      FD_ZERO(&exc);
      max_fd = 0;

      if (curl_multi_fdset(multi, &rd, &wr, &exc, &max_fd) != CURLM_OK) {
        fprintf(stderr, "unexpected failured of fdset.\n");
        i = 89;
        break;
      }

      if (select(max_fd+1, &rd, &wr, &exc, &interval) == -1) {
        fprintf(stderr, "bad select??\n");
        i =95;
        break;
      }

      res = CURLM_CALL_MULTI_PERFORM;
    }
    msg = curl_multi_info_read(multi, &running);
    /* this should now contain a result code from the easy handle, get it */
    if(msg)
      i = msg->data.result;
  }

  fprintf(stderr, "all done\n");

  curl_multi_remove_handle(multi, p.curl);
  curl_easy_cleanup(p.curl);
  curl_multi_cleanup(multi);

  curl_global_cleanup();
  free(p.accessinfoURL);

  return i;
}
#else /* USE_SSLEAY */
int test(char *URL)
{
  (void)URL;
  return CURLE_FAILED_INIT;
}
#endif /* USE_SSLEAY */
