/* * libkmipclient - KMIP client library * * Copyright IBM Corp. 2021 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include "lib/zt_common.h" #include "kmip.h" #include "utils.h" #define HTTP_HDR_CONTENT_TYPE "Content-Type:" #define CURL_ERROR_CHECK(rc, text, debug, label) \ do { \ if ((rc) != CURLE_OK) { \ kmip_debug((debug), "%s: %s", (text), \ curl_easy_strerror((rc))); \ goto label; \ } \ } while (0) struct curl_sslctx_cb_data { const struct kmip_connection *conn; bool debug; }; struct curl_write_cb_data { const struct kmip_connection *conn; bool error; bool debug; union { struct { json_tokener *tok; json_object *resp_obj; } json; struct { xmlParserCtxtPtr ctx; } xml; struct { BIO *resp_mem_bio; } ttlv; }; }; struct curl_header_cb_data { const struct kmip_connection *conn; bool error; bool debug; }; /** * Initializes a new HTTPS connection to a KMIP server. * * @param connn The KMIP connection * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_connection_https_init(struct kmip_connection *conn, bool debug) { const char *content_type, *accept, *server, *tok; const struct curl_tlssessioninfo *info = NULL; bool port_found = false; struct stat sb; int rc; if (conn == NULL) return -EINVAL; if (strncmp(conn->config.server, "https://", 8) != 0) { kmip_debug(debug, "Server must start with 'https://'"); return -EINVAL; } /* Find port (if any) and beginning of uri */ server = conn->config.server + 8; if (*server == '[') { /* IPv6 address enclosed in square brackets */ tok = strchr(server, ']'); if (tok == NULL) { kmip_debug(debug, "malformed IPv6 address"); return -EINVAL; } tok++; port_found = (*tok == ':'); } else { /* hostname or IPv4 address */ tok = strchr(server, ':'); port_found = (tok != NULL); } conn->https.curl = curl_easy_init(); if (conn->https.curl == NULL) { kmip_debug(debug, "curl_easy_init failed"); return -EIO; } /* * The CURLOPT_SSL_CTX_FUNCTION callback only works with the OpenSSL * curl backend. Check that OpenSSL is the current curl backend. */ rc = curl_easy_getinfo(conn->https.curl, CURLINFO_TLS_SSL_PTR, &info); CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_TLS_SSL_PTR", debug, out); if (info->backend != CURLSSLBACKEND_OPENSSL) { kmip_debug(debug, "libcurl is not using the OpenSSL backend"); rc = -EIO; goto out; } rc = curl_easy_setopt(conn->https.curl, CURLOPT_VERBOSE, debug ? 1 : 0); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_VERBOSE", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_URL, conn->config.server); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", debug, out); if (!port_found) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_PORT, KMIP_DEFAULT_HTTPS_PORT_NUM); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", debug, out); } rc = curl_easy_setopt(conn->https.curl, CURLOPT_SSL_VERIFYPEER, conn->config.tls_verify_peer ? 1L : 0L); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYPEER", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_SSL_VERIFYHOST, conn->config.tls_verify_host ? 2L : 0L); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYHOST", debug, out); if (conn->config.tls_ca != NULL) { if (stat(conn->config.tls_ca, &sb) != 0) { rc = -errno; kmip_debug(debug, "stat failed on '%s': %s", conn->config.tls_ca, strerror(-rc)); goto out; } if (S_ISDIR(sb.st_mode)) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_CAPATH, conn->config.tls_ca); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAPATH", debug, out); } else { rc = curl_easy_setopt(conn->https.curl, CURLOPT_CAINFO, conn->config.tls_ca); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAINFO", debug, out); } } if (conn->config.tls_issuer_cert != NULL) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_ISSUERCERT, conn->config.tls_issuer_cert); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ISSUERCERT", debug, out); } if (conn->config.tls_pinned_pubkey != NULL) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_PINNEDPUBLICKEY, conn->config.tls_pinned_pubkey); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_PINNEDPUBLICKEY", debug, out); } rc = curl_easy_setopt(conn->https.curl, CURLOPT_FOLLOWLOCATION, 0L); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION", debug, out); if (conn->config.tls_cipher_list != NULL) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_SSL_CIPHER_LIST, conn->config.tls_cipher_list); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_CIPHER_LIST", debug, out); } if (conn->config.tls13_cipher_list != NULL) { rc = curl_easy_setopt(conn->https.curl, CURLOPT_TLS13_CIPHERS, conn->config.tls13_cipher_list); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_TLS13_CIPHERS", debug, out); } switch (conn->config.encoding) { case KMIP_ENCODING_TTLV: content_type = "Content-Type: application/octet-stream"; accept = "Accept: application/octet-stream"; break; case KMIP_ENCODING_JSON: content_type = "Content-Type: application/json;charset=UTF-8"; accept = "Accept: application/json"; break; case KMIP_ENCODING_XML: content_type = "Content-Type: text/xml;charset=UTF-8"; accept = "Accept: text/xml"; break; default: kmip_debug(debug, "invalid encoding: %d", conn->config.encoding); rc = -EINVAL; goto out; } conn->https.headers = curl_slist_append(conn->https.headers, content_type); if (conn->https.headers == NULL) { kmip_debug(debug, "curl_slist_append failed"); rc = -ENOMEM; goto out; } conn->https.headers = curl_slist_append(conn->https.headers, accept); if (conn->https.headers == NULL) { kmip_debug(debug, "curl_slist_append failed"); rc = -ENOMEM; goto out; } conn->https.headers = curl_slist_append(conn->https.headers, "Accept-Charset: UTF-8"); if (conn->https.headers == NULL) { kmip_debug(debug, "curl_slist_append failed"); rc = -ENOMEM; goto out; } /* Disable "Expect: 100-continue" */ conn->https.headers = curl_slist_append(conn->https.headers, "Expect:"); if (conn->https.headers == NULL) { kmip_debug(debug, "curl_slist_append failed"); rc = -ENOMEM; goto out; } /* As per KMIP HTTPS profile: Cache-Control: no-cache */ conn->https.headers = curl_slist_append(conn->https.headers, "Cache-Control: no-cache"); if (conn->https.headers == NULL) { kmip_debug(debug, "curl_slist_append failed"); rc = -ENOMEM; goto out; } rc = curl_easy_setopt(conn->https.curl, CURLOPT_HTTPHEADER, conn->https.headers); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HTTPHEADER", debug, out); rc = 0; out: if (rc != 0) kmip_connection_https_term(conn); return rc; } /** * This callback called before the SSL handshake is performed. * It sets the client certificate and private key into the context. * It also adds a pinned server certificate to the SSL certificate store, so * that it is treated as trusted, although it might be self-signed. */ static CURLcode mkip_connection_https_sslctx_cb(CURL *UNUSED(curl), void *sslctx, void *parm) { struct curl_sslctx_cb_data *sslctx_cb = parm; SSL_CTX *ssl_ctx = (SSL_CTX *)sslctx; const struct kmip_connection *conn; X509_STORE *store; X509 *cert = NULL; FILE *fp; int rc; if (ssl_ctx == NULL || sslctx_cb == NULL || sslctx_cb->conn == NULL) return CURLE_ABORTED_BY_CALLBACK; conn = sslctx_cb->conn; if (SSL_CTX_use_certificate_file(sslctx, conn->config.tls_client_cert, SSL_FILETYPE_PEM) != 1) { kmip_debug(sslctx_cb->debug, "Failed to load the client " "certificate '%s'", conn->config.tls_client_cert); if (sslctx_cb->debug) ERR_print_errors_fp(stderr); return CURLE_ABORTED_BY_CALLBACK; } if (SSL_CTX_use_PrivateKey(ssl_ctx, conn->config.tls_client_key) != 1) { kmip_debug(sslctx_cb->debug, "Failed to set the client key"); if (sslctx_cb->debug) ERR_print_errors_fp(stderr); return CURLE_ABORTED_BY_CALLBACK; } if (conn->config.tls_server_cert == NULL) return CURLE_OK; store = SSL_CTX_get_cert_store(ssl_ctx); if (store == NULL) { kmip_debug(sslctx_cb->debug, "Failed to get SSL Store"); if (sslctx_cb->debug) ERR_print_errors_fp(stderr); return CURLE_ABORTED_BY_CALLBACK; } fp = fopen(conn->config.tls_server_cert, "r"); if (fp == NULL) { rc = -errno; kmip_debug(sslctx_cb->debug, "Failed to read server cert '%s': %s", conn->config.tls_server_cert, strerror(-rc)); return CURLE_ABORTED_BY_CALLBACK; } cert = PEM_read_X509(fp, NULL, NULL, NULL); fclose(fp); if (cert == NULL) { kmip_debug(sslctx_cb->debug, "Failed to read the server " "certificate from file '%s'", conn->config.tls_server_cert); if (sslctx_cb->debug) ERR_print_errors_fp(stderr); return CURLE_ABORTED_BY_CALLBACK; } if (sslctx_cb->debug) { kmip_debug(sslctx_cb->debug, "Pinned server certificate:"); X509_print_ex_fp(stderr, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); } rc = X509_STORE_add_cert(store, cert); if (rc != 1) { kmip_debug(sslctx_cb->debug, "Failed to add server " "certificate to SSL Store"); if (sslctx_cb->debug) ERR_print_errors_fp(stderr); X509_free(cert); return CURLE_ABORTED_BY_CALLBACK; } X509_free(cert); return CURLE_OK; } /** * Callback called during curl_easy_perform() to handle received headers. * Check for the expected response content type. */ static size_t mkip_connection_https_header_cb(void *contents, size_t size, size_t nmemb, void *userp) { struct curl_header_cb_data *cb = (struct curl_header_cb_data *)userp; size_t num = size * nmemb; const char *content_type; char *hdr = contents; size_t ofs; char *val; if (num < strlen(HTTP_HDR_CONTENT_TYPE)) goto out; if (strncasecmp(hdr, HTTP_HDR_CONTENT_TYPE, strlen(HTTP_HDR_CONTENT_TYPE)) != 0) goto out; ofs = strlen(HTTP_HDR_CONTENT_TYPE); val = hdr + ofs; while (*val == ' ' && ofs < num) { ofs++; val++; } if (ofs >= num) goto out; switch (cb->conn->config.encoding) { case KMIP_ENCODING_TTLV: content_type = "application/octet-stream"; break; case KMIP_ENCODING_JSON: content_type = "application/json"; break; case KMIP_ENCODING_XML: content_type = "text/xml"; break; default: return 0; } if (num - ofs >= strlen(content_type) && strncasecmp(val, content_type, strlen(content_type)) == 0) goto out; cb->error = true; kmip_debug(cb->debug, "Unexpected response Content-Type: %.*s", (int)(num - ofs), val); return 0; out: return num; } /** * Callback called during curl_easy_perform() to handle received data. * Parse the (potentially partial) KMIP data. */ static size_t mkip_connection_https_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { struct curl_write_cb_data *cb = (struct curl_write_cb_data *)userp; enum json_tokener_error jerr; size_t num = size * nmemb; int rc; switch (cb->conn->config.encoding) { case KMIP_ENCODING_TTLV: kmip_debug(cb->debug, "Response Data (TTLV): %lu bytes", num); if (cb->debug) kmip_print_dump(__func__, (unsigned char *)contents, num, 2); if (BIO_write(cb->ttlv.resp_mem_bio, contents, num) != (int)num) { cb->error = true; kmip_debug(cb->debug, "BIO_write failed"); return 0; } break; case KMIP_ENCODING_JSON: kmip_debug(cb->debug, "Response Data (JSON):"); kmip_debug(cb->debug, " ->%*s<-", (int)num, (char *)contents); if (cb->json.resp_obj != NULL) { kmip_debug(cb->debug, "JSON data already complete, but " "additional data received"); cb->error = true; return 0; } cb->json.resp_obj = json_tokener_parse_ex(cb->json.tok, (const char *)contents, num); if (cb->json.resp_obj == NULL) { jerr = json_tokener_get_error(cb->json.tok); if (jerr == json_tokener_continue) goto out; cb->error = true; kmip_debug(cb->debug, "json_tokener_parse_ex failed: %s", json_tokener_error_desc(jerr)); return 0; } break; case KMIP_ENCODING_XML: kmip_debug(cb->debug, "Response Data (XML):"); kmip_debug(cb->debug, " ->%*s<-", (int)num, (char *)contents); rc = xmlParseChunk(cb->xml.ctx, (const char *)contents, num, 0); if (rc != XML_ERR_OK) { cb->error = true; kmip_debug(cb->debug, "xmlParseChunk failed: %d", rc); return 0; } break; } out: return num; } /** * Perform a request over the KMIP connection * * @param conn the KMIP connection * @param request the request to send * @param response On return: the received response. Must be freed by * the caller. * * @param debug if true, debug messages are printed * * @returns 0 in case of success, or a negative errno value */ int kmip_connection_https_perform(struct kmip_connection *conn, struct kmip_node *request, struct kmip_node **response, bool debug) { struct curl_sslctx_cb_data sslctx_cb = { 0 }; struct curl_header_cb_data header_cb = { 0 }; struct curl_write_cb_data write_cb = { 0 }; char error_str[CURL_ERROR_SIZE] = { 0 }; json_object *req_json_obj = NULL; xmlNode *req_xml_obj = NULL; xmlDoc *req_xml_doc = NULL; BIO *req_mem_bio = NULL; char *req_buff = NULL; int req_buff_size = 0; long status_code; size_t size; int rc; if (conn == NULL || request == NULL || response == NULL) return -EINVAL; *response = NULL; rc = curl_easy_setopt(conn->https.curl, CURLOPT_ERRORBUFFER, error_str); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ERRORBUFFER", debug, out); /* Setup SSL Context callback */ sslctx_cb.conn = conn; sslctx_cb.debug = debug; rc = curl_easy_setopt(conn->https.curl, CURLOPT_SSL_CTX_FUNCTION, mkip_connection_https_sslctx_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt " "CURLOPT_SSL_CTX_FUNCTION", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_SSL_CTX_DATA, &sslctx_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_CTX_DATA", debug, out); /* Setup write callback to handle received data */ write_cb.conn = conn; write_cb.debug = debug; switch (conn->config.encoding) { case KMIP_ENCODING_TTLV: write_cb.ttlv.resp_mem_bio = BIO_new(BIO_s_mem()); if (write_cb.ttlv.resp_mem_bio == NULL) { kmip_debug(debug, "BIO_new failed"); rc = -ENOMEM; goto out; } break; case KMIP_ENCODING_JSON: write_cb.json.tok = json_tokener_new(); if (write_cb.json.tok == NULL) { kmip_debug(debug, "json_tokener_new failed"); rc = -EIO; goto out; } break; case KMIP_ENCODING_XML: write_cb.xml.ctx = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL); if (write_cb.xml.ctx == NULL) { kmip_debug(debug, "xmlCreatePushParserCtxt failed"); rc = -EIO; goto out; } break; } rc = curl_easy_setopt(conn->https.curl, CURLOPT_WRITEFUNCTION, mkip_connection_https_write_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEFUNCTION", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_WRITEDATA, (void *)&write_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEDATA", debug, out); /* Setup header callback to check content type */ header_cb.conn = conn; header_cb.debug = debug; rc = curl_easy_setopt(conn->https.curl, CURLOPT_HEADERFUNCTION, mkip_connection_https_header_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERFUNCTION", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_HEADERDATA, (void *)&header_cb); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERDATA", debug, out); /* Setup POST request and post data */ rc = curl_easy_setopt(conn->https.curl, CURLOPT_CUSTOMREQUEST, "POST"); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CUSTOMREQUEST", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_POST, 1L); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POST", debug, out); switch (conn->config.encoding) { case KMIP_ENCODING_TTLV: req_mem_bio = BIO_new(BIO_s_mem()); if (req_mem_bio == NULL) { kmip_debug(debug, "BIO_new failed"); rc = -ENOMEM; goto out; } rc = kmip_encode_ttlv(request, req_mem_bio, &size, debug); if (rc != 0) { kmip_debug(debug, "kmip_encode_ttlv failed"); goto out; } req_buff_size = BIO_get_mem_data(req_mem_bio, &req_buff); kmip_debug(debug, "Request Data (TTLV): %d bytes", req_buff_size); if (debug) kmip_print_dump(__func__, (unsigned char *)req_buff, req_buff_size, 2); break; case KMIP_ENCODING_JSON: rc = kmip_encode_json(request, &req_json_obj, debug); if (rc != 0) { kmip_debug(debug, "kmip_encode_json failed"); goto out; } /* * The memory returned by json_object_to_json_string_ext * is freed when the JSON object is freed. */ req_buff = (char *)json_object_to_json_string_ext(req_json_obj, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); if (req_buff == NULL) { kmip_debug(debug, "json_object_to_json_string_ext failed"); rc = -EIO; goto out; } req_buff_size = strlen(req_buff); kmip_debug(debug, "Request Data (JSON):"); kmip_debug(debug, " ->%*s<-", req_buff_size, req_buff); break; case KMIP_ENCODING_XML: req_xml_doc = xmlNewDoc((xmlChar *)"1.0"); if (req_xml_doc == NULL) { kmip_debug(debug, "xmlNewDoc failed"); rc = -EIO; goto out; } rc = kmip_encode_xml(request, &req_xml_obj, debug); if (rc != 0) { kmip_debug(debug, "kmip_encode_xml failed"); goto out; } xmlDocSetRootElement(req_xml_doc, req_xml_obj); req_xml_obj = NULL; xmlDocDumpFormatMemoryEnc(req_xml_doc, (xmlChar **)&req_buff, &req_buff_size, "UTF-8", 0); if (req_buff == NULL || req_buff_size == 0) { kmip_debug(debug, "xmlDocDumpFormatMemoryEnc failed"); rc = -EIO; goto out; } kmip_debug(debug, "Request Data (XML):"); kmip_debug(debug, " ->%*s<-", req_buff_size, req_buff); break; } rc = curl_easy_setopt(conn->https.curl, CURLOPT_POSTFIELDSIZE, req_buff_size); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POSTFIELDSIZE", debug, out); rc = curl_easy_setopt(conn->https.curl, CURLOPT_POSTFIELDS, req_buff); CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POSTFIELDS", debug, out); /* Perform the request */ rc = curl_easy_perform(conn->https.curl); if (rc != CURLE_OK) { kmip_debug(debug, "curl_easy_perform for '%s' failed: %s", conn->config.server, curl_easy_strerror(rc)); kmip_debug(debug, "Error: %s", error_str); if (header_cb.error) { kmip_debug(debug, "Unexpected Content-Type"); rc = -EBADMSG; } if (write_cb.error) { kmip_debug(debug, "JSON/XML parsing failed"); rc = -EBADMSG; } rc = -EIO; goto out; } /* Check response */ rc = curl_easy_getinfo(conn->https.curl, CURLINFO_RESPONSE_CODE, &status_code); CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_RESPONSE_CODE", debug, out); kmip_debug(debug, "HTTP status code: %d", status_code); if (status_code != 200) { rc = -EBADMSG; goto out; } /* Process received data */ switch (conn->config.encoding) { case KMIP_ENCODING_TTLV: rc = kmip_decode_ttlv(write_cb.ttlv.resp_mem_bio, NULL, response, KMIP_DECODE_MAX_NESTING_LEVEL, debug); if (rc != 0) { kmip_debug(debug, "kmip_decode_ttlv failed"); goto out; } break; case KMIP_ENCODING_JSON: if (write_cb.json.resp_obj == NULL) { kmip_debug(debug, "JSON content not wellformed"); rc = -EBADMSG; goto out; } rc = kmip_decode_json(write_cb.json.resp_obj, NULL, response, KMIP_DECODE_MAX_NESTING_LEVEL, debug); if (rc != 0) { kmip_debug(debug, "kmip_decode_json failed"); goto out; } break; case KMIP_ENCODING_XML: rc = xmlParseChunk(write_cb.xml.ctx, "", 0, 1); if (rc != XML_ERR_OK || !write_cb.xml.ctx->wellFormed || write_cb.xml.ctx->myDoc == NULL) { kmip_debug(debug, "XML content not wellformed"); rc = -EBADMSG; goto out; } rc = kmip_decode_xml(xmlDocGetRootElement( write_cb.xml.ctx->myDoc), NULL, response, KMIP_DECODE_MAX_NESTING_LEVEL, debug); if (rc != 0) { kmip_debug(debug, "kmip_decode_xml failed"); goto out; } break; } rc = 0; out: /* Cleanup */ switch (conn->config.encoding) { case KMIP_ENCODING_TTLV: if (req_mem_bio != NULL) BIO_free(req_mem_bio); if (write_cb.ttlv.resp_mem_bio != NULL) BIO_free(write_cb.ttlv.resp_mem_bio); break; case KMIP_ENCODING_JSON: if (write_cb.json.tok != NULL) json_tokener_free(write_cb.json.tok); if (write_cb.json.resp_obj != NULL) json_object_put(write_cb.json.resp_obj); if (req_json_obj != NULL) json_object_put(req_json_obj); break; case KMIP_ENCODING_XML: if (write_cb.xml.ctx != NULL) { xmlFreeDoc(write_cb.xml.ctx->myDoc); xmlFreeParserCtxt(write_cb.xml.ctx); } if (req_xml_doc != NULL) xmlFreeDoc(req_xml_doc); if (req_xml_obj != NULL) xmlFreeNode(req_xml_obj); if (req_buff != NULL) xmlFree(req_buff); break; } if (rc != 0 && *response != NULL) { kmip_node_free(*response); *response = NULL; } curl_easy_setopt(conn->https.curl, CURLOPT_SSL_CTX_FUNCTION, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_SSL_CTX_DATA, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_WRITEDATA, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_HEADERFUNCTION, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_HEADERDATA, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_ERRORBUFFER, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(conn->https.curl, CURLOPT_POSTFIELDSIZE, -1); return rc; } /** * Terminates a HTTPS KMIP connection. * * @param conn the KMIP connection to free */ void kmip_connection_https_term(struct kmip_connection *conn) { if (conn == NULL) return; if (conn->https.curl != NULL) curl_easy_cleanup(conn->https.curl); conn->https.curl = NULL; if (conn->https.headers != NULL) curl_slist_free_all(conn->https.headers); conn->https.headers = NULL; }