/** \brief callback notified by \ref upnp_call_t on completion */ bool upnp_call_extipaddr_t::neoip_upnp_call_cb(void *cb_userptr, upnp_call_t &cb_call , const upnp_err_t &cb_upnp_err, const strvar_db_t &strvar_db) throw() { // log to debug KLOG_DBG("enter upnp_err=" << cb_upnp_err << " strvar_db=" << strvar_db); // if the upnp_call_t failed, forward the upnp_err_t if( cb_upnp_err.failed() ) return notify_callback(cb_upnp_err, ip_addr_t()); // if the upnp_call_t replied strvar_db doesnt contain "NewExternalIPAddr", notify an error if( !strvar_db.contain_key("NewExternalIPAddress") ){ upnp_err_t upnp_err(upnp_err_t::ERROR, "unable to find NewExternalIPAddress in upnp_call_t response"); return notify_callback(upnp_err, ip_addr_t()); } // try to convert the content into a ip_addr_t ip_addr_t extipaddr = strvar_db.get_first_value("NewExternalIPAddress"); // if the content can't be parse as an ip_addr_t, notify an error if( !extipaddr.is_fully_qualified() ){ upnp_err_t upnp_err(upnp_err_t::ERROR, "unable to parse NewExternalIPAddress as ip_addr_t"); return notify_callback(upnp_err, ip_addr_t()); } // NOTE: if this point is reached, the upnp_call_extipaddr_t has been successfull, notify the caller return notify_callback(upnp_err_t::OK, extipaddr); }
/** \brief Parse the "peers" fields returned by the http reply from the tracker * * - when it is a 'compact' reply */ bt_tracker_peer_arr_t bt_tracker_reply_t::dvar_to_peer_arr_docompact(const dvar_t &peers_dvar) throw() { bt_tracker_peer_arr_t peer_arr; // log to debug KLOG_DBG("peers_dvar=" << peers_dvar); // sanity check - the peers_dvar MUST be a dvar_type_t::STRING DBG_ASSERT( peers_dvar.type() == dvar_type_t::STRING ); // convert the peers_dvar into a bytearray_t to ease the parsing bytearray_t bytearray = bytearray_t(datum_t(peers_dvar.str().get())); // parse the bytearray_t - it is a suite of ipv4 address / port while( !bytearray.empty() ){ uint32_t ipaddr; uint16_t port; try { bytearray >> ipaddr; bytearray >> port; }catch(serial_except_t &e){ DBGNET_ASSERT( 0 ); return bt_tracker_peer_arr_t(); } // add the bt_tracker_peer_t in the bt_tracker_peer_arr_t ipport_addr_t ipport_addr(ip_addr_t(ipaddr), port); // if the ipport_addr_t is not is_fully_qualified, discard it // - NOTE: this is a workaround a bug seen where http tracker which gives port = 0 // - a similar bug has been seen in utpex from utorrent1.6 if( !ipport_addr.is_fully_qualified() ) continue; // add the new bt_tracker_peer_t in the peer_arr peer_arr += bt_tracker_peer_t( ipport_addr, bt_id_t() ); } // return the result return peer_arr; }
/** \brief Overload the substraction */ ip_addr_t ip_addr_t::operator- (const uint32_t val) const throw() { // sanity check - currently only handle ipv4 DBG_ASSERT( is_v4() ); // return the result return ip_addr_t( get_v4_addr() - val ); }
/** \brief test the comparison operator */ nunit_res_t ip_addr_testclass_t::comparison_op(const nunit_testclass_ftor_t &testclass_ftor) throw() { NUNIT_ASSERT( ip_addr_t() < ip_addr_t("0.0.0.0") ); NUNIT_ASSERT( ip_addr_t("10.0.0.1") < ip_addr_t("10.0.0.2") ); NUNIT_ASSERT( ip_addr_t("12.0.0.1") != ip_addr_t("10.0.0.0") ); NUNIT_ASSERT( ip_addr_t("1.2.3.4") <= ip_addr_t("2.3.4.5")); // return no error return NUNIT_RES_OK; }
/** \brief general test */ nunit_res_t ip_addr_testclass_t::general(const nunit_testclass_ftor_t &testclass_ftor) throw() { NUNIT_ASSERT( ip_addr_t("10.1.2.3").is_private() ); NUNIT_ASSERT( ip_addr_t("192.168.1.2").is_private() ); NUNIT_ASSERT( ip_addr_t("127.1.2.3").is_localhost() ); NUNIT_ASSERT( ip_addr_t("1.2.3.4").is_public() ); NUNIT_ASSERT( ip_addr_t("0.0.0.0").is_any() ); NUNIT_ASSERT( ip_addr_t("255.255.255.255").is_broadcast()); NUNIT_ASSERT( ip_addr_t("1.2.3.4").is_v4() ); NUNIT_ASSERT( ip_addr_t("1.2.3.4").get_version() == 4 ); NUNIT_ASSERT( ip_addr_t("1.2.3.4").get_v4_addr() == 0x01020304); // return no error return NUNIT_RES_OK; }
/** \brief set from a string */ socket_err_t socket_peerid_udp_t::ctor_from_str(const std::string &str) throw() { // parse the input ipaddr = ip_addr_t(str.c_str()); // if an error occur while parsing this, report it now if( ipaddr.is_null() ) return socket_err_t(socket_err_t::BAD_PARAM, "bad socket_peerid_udp_t format in "+ str); // return no error return socket_err_t::OK; }
/** \brief set the ip_addr_t from a string * * @return false if no error occured, true otherwise */ bool ip_addr_t::from_string(const std::string &ip_addr_str) throw() { struct in_addr inaddr; inet_err_t inet_err; // do the inet_aton inet_err = inet_oswarp_t::inet_aton(ip_addr_str.c_str(), &inaddr); if( inet_err.failed() ) return true; // set the object *this = ip_addr_t(ntohl(inaddr.s_addr)); return false; }
/** \brief callback notified when asyncexe_t has an event to report */ bool host2ip_fork_t::neoip_asyncexe_cb(void *cb_userptr, asyncexe_t &cb_asyncexe , const libsess_err_t &libsess_err , const bytearray_t &stdout_barray , const int &exit_status) throw() { // log to debug KLOG_ERR("enter libsess_err="<< libsess_err << " exit_status=" << exit_status << " stdout_barray=" << stdout_barray); // if exit_status is non null, notify the called if( exit_status != 0 ){ std::string reason = "neoip-dnsresolve.sh returned error " + OSTREAMSTR(exit_status); return notify_callback_err(inet_err_t(inet_err_t::ERROR, reason)); } /*************** parse the output_barray ***********************/ std::string recved_str = stdout_barray.to_stdstring(); std::vector<std::string> arr_str = string_t::split(recved_str, "/"); std::vector<ip_addr_t> arr_ipaddr; // parse the arr_str into arr_ipaddr for(size_t i = 0; i < arr_str.size(); i++){ // if this string is empty, leave the loop if( arr_str[i].empty() ) break; // sanity check - the non empty string MUST be convertible in a ip_addr_t DBGNET_ASSERT( !ip_addr_t(arr_str[i]).is_null() ); // else add the ip_addr_t to the arr_ipaddr arr_ipaddr.push_back(ip_addr_t(arr_str[i])); } // notify the caller with a faillure if the arr_ipaddr is empty if( arr_ipaddr.size() == 0 ){ std::string reason = "resolution failed"; return notify_callback_err(inet_err_t(inet_err_t::ERROR, reason)); } // else notify the caller with a success return notify_callback(inet_err_t::OK, arr_ipaddr); }
/** \brief Test function */ nunit_res_t ipcountry_testclass_t::general(const nunit_testclass_ftor_t &testclass_ftor) throw() { inet_err_t inet_err; // log to debug KLOG_DBG("enter"); // start the ipcountry_t ipcountry = nipmem_new ipcountry_t(); inet_err = ipcountry->start(ip_addr_t("62.70.27.118"), this, NULL); NUNIT_ASSERT( inet_err.succeed() ); // copy the functor to report nunit_res_t asynchronously nunit_ftor = testclass_ftor; return NUNIT_RES_DELAYED; }
/** \brief Test the serialization consistency */ nunit_res_t ip_addr_testclass_t::serial_consistency(const nunit_testclass_ftor_t &testclass_ftor) throw() { ip_addr_t ip_addr_toserial = "127.0.0.1"; ip_addr_t ip_addr_unserial; serial_t serial; // test with ip_addr_t equal to a value serial << ip_addr_toserial; serial >> ip_addr_unserial; NUNIT_ASSERT( ip_addr_toserial == ip_addr_unserial ); // test with ip_addr_t equal to NULL ip_addr_toserial = ip_addr_t(); serial << ip_addr_toserial; serial >> ip_addr_unserial; NUNIT_ASSERT( ip_addr_toserial == ip_addr_unserial ); // return no error return NUNIT_RES_OK; }
/** \brief Pick a inner ip address from the available one (may return null if none is available) */ ip_addr_t router_resp_cnx_t::pick_inner_addr(const ip_addr_inval_t &avail_iaddr , const ip_addr_t &prefered_iaddr) throw() { // if no address is available, return a null ip_addr_t if( avail_iaddr.size() == 0 ) return ip_addr_t(); // if the prefered address is not null and is contained in avail_iaddr, pick this one if( !prefered_iaddr.is_null() && avail_iaddr.contain(prefered_iaddr) ) return prefered_iaddr; // TODO find a better algorithm // - this one is too deterministic and will cause negociation retry when 2 tunnel are established // at the same time. // - to pick one at random on the first item? seems better // - do i have the ip_addr_t arithmetic operator to do it ? // return the lowest of the interval return avail_iaddr[0].min_value(); }
/** \brief Start the operation */ inet_err_t ipcountry_t::start(const ip_addr_t &m_ipaddr, ipcountry_cb_t *callback, void *userptr) throw() { // copy the parameter this->m_ipaddr = m_ipaddr; this->callback = callback; this->userptr = userptr; // build the hostname to resolve - ip_addr_t in reverse order appended with .zz.countries.nerd.dk std::string hostname; hostname = ip_addr_t(NEOIP_HTONL(ipaddr().get_v4_addr())).to_string(); hostname += ".zz.countries.nerd.dk"; // log to debug KLOG_DBG("try to resolve hostname=" << hostname); // launch the host2ip_t on the just built hostname inet_err_t inet_err; host2ip = nipmem_new host2ip_t(); inet_err = host2ip->start(hostname, this, NULL); if( inet_err.failed() ) return inet_err; // return no error return inet_err_t::OK; }
/** \brief callback notified by \ref upnp_disc_t on completio\n */ bool upnp_watch_t::neoip_upnp_disc_cb(void *cb_userptr, upnp_disc_t &cb_disc , const upnp_err_t &cb_upnp_err , const upnp_disc_res_t ¬ified_disc_res) throw() { upnp_err_t upnp_err = cb_upnp_err; // log to debug KLOG_DBG("enter upnp_err=" << upnp_err << " disc_res=" << notified_disc_res); // sanity check - if upnp_err.succeed() then disc_res MUST be non-null if( upnp_err.succeed() ) DBG_ASSERT( !notified_disc_res.is_null() ); // sanity check - if upnp_err.failed() then disc_res MUST be null if( upnp_err.failed() ) DBG_ASSERT( notified_disc_res.is_null() ); // sanity check - the disc_timeout MUST NOT be running during upnp_disc_t execution DBG_ASSERT( !disc_timeout.is_running() ); // determine if the upnp_disc_res_t changed or not bool res_changed; if( notified_disc_res == current_disc_res() ) res_changed = false; else res_changed = true; #if 1 // TODO to remove - only to debug assert failing in some case of UPNP_CHANGED upnp_disc_res_t old_disc_res = m_current_disc_res; #endif // copy the new upnp_disc_res_t m_current_disc_res = notified_disc_res; // delete the upnp_disc_t nipmem_zdelete upnp_disc; // if the upnp_disc_res_t changed, notify the caller if( res_changed ){ bool tokeep = notify_callback(upnp_watch_event_t::build_upnpdisc_changed()); if( !tokeep ) return false; } // relaunch the disc_timeout for the next upnp_disc_t disc_timeout.start(disc_delaygen.pre_inc(), this, NULL); #if 0 // TODO not sure about this one // if upnp_unavail() now, reset the m_extipaddr // - TODO should i notify this event ? if( upnp_unavail() ) m_extipaddr = ip_addr_t(); #endif // if the res_changed and now upnp_isavail(), start the extipaddr_timeout and portcleaner_timeout if( res_changed && upnp_isavail() ){ // sanity check: the extipaddr_timeout MUST NOT be running // - TODO this assert fails from time to time... fix this // - i dont even see how it can be right to have this as this 'if' // may be entered at anytime // - instead, would be way better to delete call_extipaddr if needed #if 1 // TODO to remove - only to debug assert failing in some case of UPNP_CHANGED // - this is due to getporttest sometime being 0 on a router in which is it 1 // - so for unknown reason sometime this is diagnosed as // - is it due to a timeout ? and using the default value as 0 ? // - apparently getportendian_test report an error on timeout... so this // cant be it... // - how it is possible to get a 0 while the upnp router is a 1 ? // - what about a race ? // - it uses a constant "upnp revendian testing" tag to identify the addport // SO if 2 getportend_test are done simultaneously, this MAY result in a conflict // - likely rare, but trivial to fix. // - TODO just put a nonce in the tag // - res_changed ISNOT upnp is avail or not, the result may change even // between isavail and isavail // - as shown in changing from getporttest false to getposttest true // - SO DO NOT assume that it is if( !(!extipaddr_timeout.is_running() && !call_extipaddr) ){ KLOG_ERR("new disc_rec=" << m_current_disc_res); KLOG_ERR("old disc_rec=" << old_disc_res); } DBG_ASSERT( !extipaddr_timeout.is_running() ); DBG_ASSERT( !call_extipaddr ); #else DBG_ASSERT( !extipaddr_timeout.is_running() && !call_extipaddr ); #endif // init the extipaddr_timeout extipaddr_delaygen = delaygen_t(profile.extipaddr_delaygen_arg()); extipaddr_timeout.start(extipaddr_delaygen.current(), this, NULL); // init the portcleaner_timeout - IIF profile has portcleaner_enabled if( get_profile().portcleaner_enabled() ){ // - sanity check: upnp_portcleaner_t MUST NOT be running DBG_ASSERT( !upnp_portcleaner ); upnp_portcleaner= nipmem_new upnp_portcleaner_t(); upnp_err = upnp_portcleaner->start(this); DBG_ASSERT( upnp_err.succeed() ); } } // if the res_changed and now upnp_unavail(), stop the extipaddr_timeout and portcleaner_timeout if( res_changed && upnp_unavail() ){ // stop the extipaddr_timeout // - sanity check: the extipaddr_timeout MUST be running DBG_ASSERT( extipaddr_timeout.is_running() || call_extipaddr ); extipaddr_timeout.stop(); nipmem_zdelete call_extipaddr; // stop the portcleaner_timeout - IIF profile has portcleaner_enabled if( get_profile().portcleaner_enabled() ){ // - sanity check: upnp_portcleaner_t MUST be running DBG_ASSERT( upnp_portcleaner ); // delete upnp_portcleaner nipmem_zdelete upnp_portcleaner; } } // return 'dontkeep' as the upnp_disc_t has been deleted return false; }
/** \brief Build a upnp_portdesc_t from a strvar_db_t * * - this is the strvar_db_t replied from a upnp_call_t */ upnp_portdesc_t upnp_portdesc_t::from_strvar_db(const strvar_db_t &strvar_db, bool revendian) throw() { upnp_portdesc_t portdesc; // log to debug KLOG_DBG("enter revendian=" << revendian << " strvar_db=" << strvar_db); /*************** parse the NewExternalPort port ***********************/ if( strvar_db.contain_key("NewExternalPort") ){ // try to convert the content into a port uint16_t port_pview = atoi(strvar_db.get_first_value("NewExternalPort").c_str()); if( revendian ) port_pview = endianswap_t::swap16(port_pview); /*************** parse the NewExternalHost ip_addr_t ***************/ if( !strvar_db.contain_key("NewRemoteHost") ) return upnp_portdesc_t(); // try to convert the content into a port std::string ipaddr_pview_str = strvar_db.get_first_value("NewRemoteHost"); ip_addr_t ipaddr_pview = ipaddr_pview_str.size() ? ipaddr_pview_str : "0.0.0.0"; if( revendian && ipaddr_pview.is_v4() ) ipaddr_pview = ip_addr_t(endianswap_t::swap32(ipaddr_pview.get_v4_addr())); // copy the result into the internal variable portdesc.ipport_pview ( ipport_addr_t(ipaddr_pview, port_pview) ); } /*************** parse the NewProtocol upnp_sockfam_t ***************/ if( strvar_db.contain_key("NewProtocol") ){ std::string str = strvar_db.get_first_value("NewProtocol"); portdesc.sockfam ( upnp_sockfam_t::from_string_nocase(str) ); } /*************** parse the NewInternalPort port ***********************/ if( !strvar_db.contain_key("NewInternalPort") ) return upnp_portdesc_t(); // try to convert the content into a port uint16_t port_lview = atoi(strvar_db.get_first_value("NewInternalPort").c_str()); if( revendian ) port_lview = endianswap_t::swap16(port_lview); /*************** parse the NewInternalClient ip_addr_t ***************/ if( !strvar_db.contain_key("NewInternalClient") ) return upnp_portdesc_t(); // try to convert the content into a ip_addr_t ip_addr_t ipaddr_lview = strvar_db.get_first_value("NewInternalClient"); if( revendian && ipaddr_lview.is_v4() ) ipaddr_lview = ip_addr_t(endianswap_t::swap32(ipaddr_lview.get_v4_addr())); // if the content can't be parse as an ip_addr_t, notify an error if( !ipaddr_lview.is_fully_qualified() ) return upnp_portdesc_t(); // copy the result into the internal variable portdesc.ipport_lview ( ipport_addr_t(ipaddr_lview, port_lview) ); DBG_ASSERT( portdesc.ipport_lview().is_fully_qualified() ); /*************** parse the NewEnabled *******************************/ if( !strvar_db.contain_key("NewEnabled") ) return upnp_portdesc_t(); // try to convert the content into a std::string portdesc.map_enabled ( atoi(strvar_db.get_first_value("NewEnabled").c_str()) ? true : false ); /*************** parse the NewPortMappingDescription ***************/ if( !strvar_db.contain_key("NewPortMappingDescription"))return upnp_portdesc_t(); portdesc.desc_str ( strvar_db.get_first_value("NewPortMappingDescription") ); /*************** parse the portcleaner_tag if needed ***************/ delay_t portcleaner_lease; std::string portcleaner_nonce; std::string real_desc = upnp_portcleaner_t::parse_tag_desc(portdesc.desc_str() , portcleaner_lease, portcleaner_nonce); portdesc.desc_str ( real_desc ); portdesc.portcleaner_lease ( portcleaner_lease ); portdesc.portcleaner_nonce ( portcleaner_nonce ); /*************** parse the NewLeaseDuration ***********************/ if( !strvar_db.contain_key("NewLeaseDuration") ) return upnp_portdesc_t(); std::string lease_delay_str = strvar_db.get_first_value("NewLeaseDuration"); delay_t lease_delay = delay_t::from_sec(atoi(lease_delay_str.c_str())); // handle the special case of 0sec means INFINITE if( lease_delay == delay_t::from_sec(0) ) lease_delay = delay_t::INFINITE; portdesc.lease_delay ( lease_delay ); // sanity check - here portdesc MUST NOT be null DBG_ASSERT( !portdesc.is_null() ); // return the just built upnp_portdesc_t return portdesc; }
signed int main(signed int ac, char** av) { SSL_CTX* ctx(nullptr); SSL_METHOD* meth(nullptr); SSL* ssl(nullptr); signed long sflags(0); signed int sockfd(-1); signed int retval(-1); std::string host(""); std::string port(""); std::string key(""); std::string cert(""); std::string store(""); std::string root(""); ::SSL_library_init(); ::OpenSSL_add_all_algorithms(); ::SSL_load_error_strings(); sflags = ( SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION ); if (7 != ac) { std::cerr << "Usage: " << av[0] << " <host> <port> <certificate> <key> <root certificate> <certificate store>" << std::endl; return EXIT_FAILURE; } host = av[1]; port = av[2]; cert = av[3]; key = av[4]; root = av[5]; store = av[6]; meth = const_cast< SSL_METHOD* >(::TLSv1_2_client_method()); ctx = ::SSL_CTX_new(meth); if (nullptr == meth) { ::ERR_print_errors_fp(stderr); return EXIT_FAILURE; } ::SSL_CTX_set_options(ctx, sflags); if (0 >= ::SSL_CTX_use_certificate_file(ctx, cert.c_str(), SSL_FILETYPE_PEM)) { ::ERR_print_errors_fp(stderr); return EXIT_FAILURE; } if (0 >= ::SSL_CTX_use_PrivateKey_file(ctx, key.c_str(), SSL_FILETYPE_PEM)) { ::ERR_print_errors_fp(stderr); return EXIT_FAILURE; } if (! ::SSL_CTX_check_private_key(ctx)) { std::cerr << "Private key does not match certificate" << std::endl; return EXIT_FAILURE; } if (! ::SSL_CTX_load_verify_locations(ctx, root.c_str(), store.c_str())) { std::cerr << "Load certificate store location failure" << std::endl; return EXIT_FAILURE; } ::SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); //&verify_cb); ::SSL_CTX_set_verify_depth(ctx, 5); std::cout << "Connecting to: " << host << ":" << port << std::endl; sockfd = open_connection(host, port); if (0 > sockfd) return EXIT_FAILURE; ssl = SSL_new(ctx); ::SSL_set_fd(ssl, sockfd); do { retval = ::ERR_get_error(); if (0 != retval) std::cerr << "SSL ERR: " << ::ERR_reason_error_string(retval) << std::endl; } while (0 != retval); retval = ::SSL_connect(ssl); if (1 != retval) { std::cerr << "Error in SSL_connect()" << std::endl; ::ERR_print_errors_fp(stderr); } else { X509* ccert(::SSL_get_peer_certificate(ssl)); char* line(nullptr); char buf[4096] = {0}; signed int len(-1); struct in_addr ia; struct in6_addr i6a; std::vector< ip_addr_t > avec; std::vector< port_t > pvec; //message_t msg(OP_SCAN_STATUS, 0x4141414141414141); if (0 >= ::inet_pton(AF_INET, "127.0.0.0", &ia)) { std::cerr << "inet_pton() error: " << ::strerror(errno) << std::endl; return EXIT_FAILURE; } avec.push_back(ip_addr_t(ia, 24)); if (0 >= ::inet_pton(AF_INET, "192.0.0.0", &ia)) { std::cerr << "inet_pton() error: " << ::strerror(errno) << std::endl; return EXIT_FAILURE; } avec.push_back(ip_addr_t(ia, 8)); if (0 >= ::inet_pton(AF_INET6, "fe80:20c:29ff:feee:4b72::1", &i6a)) { std::cerr << "inet_pton() error: " << ::strerror(errno) << std::endl; return EXIT_FAILURE; } avec.push_back(ip_addr_t(i6a, 120)); if (0 >= ::inet_pton(AF_INET, "10.0.0.1", &ia)) { std::cerr << "inet_pton() error: " << ::strerror(errno) << std::endl; return EXIT_FAILURE; } avec.push_back(ip_addr_t(ia)); if (0 >= ::inet_pton(AF_INET6, "::1", &i6a)) { std::cerr << "inet_pton() error: " << ::strerror(errno) << std::endl; return EXIT_FAILURE; } avec.push_back(ip_addr_t(i6a)); pvec.push_back(port_t(PORT_PROTO_TCP, 80)); pvec.push_back(port_t(PORT_PROTO_TCP, 443)); pvec.push_back(port_t(PORT_PROTO_TCP, 143)); pvec.push_back(port_t(PORT_PROTO_TCP, 22)); pvec.push_back(port_t(PORT_PROTO_TCP, 139)); pvec.push_back(port_t(PORT_PROTO_TCP, 31336)); pvec.push_back(port_t(PORT_PROTO_TCP, 15)); message_t msg(avec, pvec); if (X509_V_OK != ::SSL_get_verify_result(ssl)) std::cout << "Certificate validation failed" << std::endl; else std::cout << "Certificate successfully validated" << std::endl; std::cout << "Connected with " << ::SSL_get_cipher(ssl) << " encryption." << std::endl; if (nullptr == ccert) { std::cerr << "ccert is nullptr" << std::endl; return EXIT_FAILURE; } line = ::X509_NAME_oneline(::X509_get_subject_name(ccert), 0, 0); std::cout << "Subject: " << line << std::endl; ::free(line); line = ::X509_NAME_oneline(::X509_get_issuer_name(ccert), 0, 0); std::cout << "Issuer: " << line << std::endl; ::free(line); std::cout << "Version: " << ::X509_get_version(ccert) << std::endl; ::X509_free(ccert); len = ::SSL_read(ssl, buf, sizeof(buf)); if (0 < len && 4096 > len) { buf[len] = 0; std::cout << "buf: " << buf << std::endl; } std::vector< uint8_t > d(msg.data()); ::SSL_write(ssl, d.data(), d.size()); } ::close(sockfd); ::SSL_CTX_free(ctx); return EXIT_SUCCESS; }