#define _POSIX_C_SOURCE 200809L
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>

static size_t sink(char *ptr, size_t size, size_t nmemb, void *userdata) {
  (void)ptr;
  (void)userdata;
  return size * nmemb;
}

static void must(CURLcode rc, const char *what) {
  if(rc != CURLE_OK) {
    fprintf(stderr, "SETOPT_FAILURE what=%s code=%d text=%s\n", what,
            (int)rc, curl_easy_strerror(rc));
    exit(90);
  }
}

static CURLcode perform_one(CURLM *multi, CURL *easy, const char *label) {
  int running = 0;
  CURLMcode mr;
  CURLcode result = CURLE_FAILED_INIT;
  int msgs = 0;
  must(curl_easy_setopt(easy, CURLOPT_PRIVATE, label), "PRIVATE");
  mr = curl_multi_add_handle(multi, easy);
  if(mr != CURLM_OK) exit(91);
  do {
    mr = curl_multi_perform(multi, &running);
    if(mr != CURLM_OK) exit(92);
    if(running) {
      int nfds = 0;
      mr = curl_multi_poll(multi, NULL, 0, 1000, &nfds);
      if(mr != CURLM_OK) exit(93);
    }
  } while(running);
  {
    CURLMsg *msg;
    while((msg = curl_multi_info_read(multi, &msgs)) != NULL) {
      if(msg->msg == CURLMSG_DONE && msg->easy_handle == easy)
        result = msg->data.result;
    }
  }
  curl_multi_remove_handle(multi, easy);
  {
    long num_connects = -1;
    long response = 0;
    curl_easy_getinfo(easy, CURLINFO_NUM_CONNECTS, &num_connects);
    curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &response);
    printf("RESULT label=%s code=%d text=%s new_connects=%ld http=%ld\n",
           label, (int)result, curl_easy_strerror(result), num_connects,
           response);
    fflush(stdout);
  }
  return result;
}

static CURL *make_easy(const char *url, const char *default_ca,
                       int explicitly_set_ca) {
  CURL *easy = curl_easy_init();
  if(!easy) exit(94);
  must(curl_easy_setopt(easy, CURLOPT_URL, url), "URL");
  /* Both handles ultimately have the same CAfile string. The first obtains it
   * from CURL_CA_BUNDLE at build time, so CURL_CA_NATIVE remains active. The
   * second explicitly sets that same path, marking CA material as custom and
   * disabling implicit native CA. That custom/default distinction was absent
   * from the vulnerable connection reuse key. */
  if(explicitly_set_ca)
    must(curl_easy_setopt(easy, CURLOPT_CAINFO, default_ca), "CAINFO");
  must(curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, 0L), "SSL_OPTIONS");
  must(curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1),
       "HTTP_VERSION");
  must(curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, sink), "WRITEFUNCTION");
  must(curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L), "VERBOSE");
  must(curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, 3000L),
       "CONNECTTIMEOUT");
  must(curl_easy_setopt(easy, CURLOPT_TIMEOUT_MS, 5000L), "TIMEOUT");
  must(curl_easy_setopt(easy, CURLOPT_PROXY, ""), "PROXY");
  return easy;
}

int main(int argc, char **argv) {
  CURLM *multi;
  CURL *native_default, *custom_same_path, *fresh_custom;
  CURLcode rn, rs, rf;
  const curl_version_info_data *vi;
  if(argc != 3) {
    fprintf(stderr, "usage: %s URL DEFAULT_EMPTY_CA_FILE\n", argv[0]);
    return 64;
  }
  if(curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) return 65;
  vi = curl_version_info(CURLVERSION_NOW);
  printf("LIBCURL version=%s ssl=%s features=0x%lx\n", vi->version,
         vi->ssl_version ? vi->ssl_version : "none",
         (unsigned long)vi->features);

  multi = curl_multi_init();
  if(!multi) return 66;
  native_default = make_easy(argv[1], argv[2], 0);
  custom_same_path = make_easy(argv[1], argv[2], 1);
  rn = perform_one(multi, native_default, "native-default-prime");
  curl_easy_cleanup(native_default);
  rs = perform_one(multi, custom_same_path, "custom-same-path-shared-cache");
  curl_easy_cleanup(custom_same_path);
  curl_multi_cleanup(multi);

  multi = curl_multi_init();
  if(!multi) return 67;
  fresh_custom = make_easy(argv[1], argv[2], 1);
  rf = perform_one(multi, fresh_custom, "custom-same-path-fresh-control");
  curl_easy_cleanup(fresh_custom);
  curl_multi_cleanup(multi);
  curl_global_cleanup();

  if(rn == CURLE_OK && rs == CURLE_OK && rf == CURLE_PEER_FAILED_VERIFICATION) {
    puts("ORACLE=VULNERABLE_WRONG_TRUST_REUSE");
    return 0;
  }
  if(rn == CURLE_OK && rs == CURLE_PEER_FAILED_VERIFICATION &&
     rf == CURLE_PEER_FAILED_VERIFICATION) {
    puts("ORACLE=FIXED_POLICY_ISOLATION");
    return 1;
  }
  printf("ORACLE=UNEXPECTED native=%d shared=%d fresh=%d\n",
         (int)rn, (int)rs, (int)rf);
  return 2;
}
