#include "sslclient.h" #include #include #include #include #include #include #include #include // https://stackoverflow.com/questions/25896916/parse-http-headers-in-c // https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST // https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET std::pair HttpClient::doRequest(const std::string &method, const std::string &url, const std::map &headers, const std::string &request_body) { // split url to parts parseURL(url); if (proto != "https") throw std::runtime_error("Unsupported protocol"); // create headers const std::string headers_string = createRequestHeaders(headers); // create request std::string request = method + " " + full_url + " HTTP/1.0\r\nHost: " + server + headers_string + "\r\n\r\n"; if (method == "POST") request.append(request_body); // read response int bytes_read = sslRequest(server, request); if (bytes_read <= 0) { std::cerr << "no data read" << std::endl; return std::make_pair(403, ""); } // get headers std::string::size_type end_of_headers = ssl_read_packet.find("\r\n\r\n"); if (end_of_headers == std::string::npos) throw std::runtime_error("invalid reply, end of headers not found"); const std::string response = ssl_read_packet.substr(0, end_of_headers); auto status_pos = response.find("\r\n"); const std::string response_headers = response.substr(status_pos + 2, response.length() - 2 - status_pos); const std::string status_string = response.substr(0, status_pos); // parse status code const int resp_status = responseStatusCode(status_string); // parse headers responseHeaders(response_headers); // get content length if given int content_len = 0; auto cl_it = headers_map.find("Content-Length"); if (cl_it != headers_map.end()) { content_len = std::stoi(cl_it->second); } // and fetch the rest if not read completely while (content_len > 0 && content_len > ssl_read_packet.length() - 4 - end_of_headers) { /* auto read_bytes = */ sslRecvPacket(); } // get body const std::string body = ssl_read_packet.substr(end_of_headers + 4, ssl_read_packet.length() - 4 - end_of_headers); return std::make_pair(resp_status, body); } void HttpClient::responseHeaders(const std::string &headers) { std::istringstream resp(headers); std::string header; while (std::getline(resp, header) && header != "\r") { auto index = header.find(": ", 0); if (index != std::string::npos) headers_map.insert(std::make_pair(header.substr(0, index), header.substr(index + 1))); } } int HttpClient::responseStatusCode(const std::string &status_str) { auto resp_status = 200; // default is OK std::regex status_rgx{R"(^HTTP/\d\.\d (\d{3}) .+$)"}; std::smatch status_matches; if (std::regex_search(status_str, status_matches, status_rgx)) { if (status_matches.size() > 1) resp_status = std::stoi(status_matches[1].str()); } return resp_status; } std::string HttpClient::createRequestHeaders(const std::map &headers) { std::string headers_string; for (const auto & header : headers) headers_string.append("\r\n" + header.first + ": " + header.second); return headers_string; } void HttpClient::parseURL(const std::string &url) { std::regex rgx{R"(^(?:((?:https?|s?ftp))://)([^:/\s]+)(?::(\d*))?(?:/([^\s?#]+)?([?][^?#]*)?(#.*)?)?)"}; std::smatch matches; if (std::regex_search(url, matches, rgx)) { for (size_t i = 0; i < matches.size(); ++i) { switch (i) { case 0: full_url = matches[i].str(); break; case 1: proto = matches[i].str(); break; case 2: server = matches[i].str(); break; case 3: port = matches[i].str(); break; case 4: uri = matches[i].str(); break; case 5: params = matches[i].str(); break; case 6: href = matches[i].str(); break; default: std::cerr << "Unexpected part of url: " << url << std::endl; break; } } } else { std::cerr << "Cannot parse url: " << url << std::endl; } } std::string HttpClient::inetAddress(const std::string &hostname) { hostent *record = gethostbyname(hostname.c_str()); if (record == nullptr) { throw std::runtime_error(hostname + " network unavailable."); } auto *address = (in_addr *) record->h_addr; std::string ip_address = inet_ntoa(*address); return ip_address; } int HttpClient::sslRecvPacket() { int buf_len = 16384; unsigned char buf[buf_len + 1]; int len = 0; int read_bytes = 0; do { memset(buf, 0, sizeof(buf)); len = SSL_read(ssl, buf, buf_len); if (len > 0) { read_bytes += len; buf[len] = 0; // actually redundant std::vector data(buf, buf + len); std::string chunk(data.begin(), data.end()); ssl_read_packet.append(chunk); } } while (len > 0); if (len < 0) { int err = SSL_get_error(ssl, len); if (err == SSL_ERROR_WANT_READ) return 0; if (err == SSL_ERROR_WANT_WRITE) return 0; if (err == SSL_ERROR_ZERO_RETURN || err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) return -1; } return read_bytes; } int HttpClient::sslSendPacket(const std::string &buf) { int len = SSL_write(ssl, buf.c_str(), (int)strlen(buf.c_str())); if (len < 0) { int err = SSL_get_error(ssl, len); switch (err) { case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: return 0; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SYSCALL: case SSL_ERROR_SSL: default: return -1; } } return len; } int HttpClient::sslRequest(const std::string &server_name, const std::string &request) { // create socket int s; s = socket(AF_INET, SOCK_STREAM, 0); if (!s) { std::cerr << "HttpClient::sslRequest, error creating socket" << std::endl; return -1; } // socket address std::string server_ip = inetAddress(server_name); struct sockaddr_in sa{}; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(server_ip.c_str()); sa.sin_port = htons(443); socklen_t socklen = sizeof(sa); // connect to server if (connect(s, (struct sockaddr *) &sa, socklen)) { std::cerr << "HttpClient::sslRequest, error connecting to server" << std::endl; return -1; } SSL_library_init(); SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); const SSL_METHOD *meth = TLS_client_method(); SSL_CTX *ctx = SSL_CTX_new(meth); ssl = SSL_new(ctx); if (!ssl) { std::cerr << "HttpClient::sslRequest, error creating SSL" << std::endl; logSSL(); return -1; } sock = SSL_get_fd(ssl); SSL_set_fd(ssl, s); SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void *) server.c_str()); int err = SSL_connect(ssl); if (err <= 0) { std::cerr << "HttpClient::sslRequest, error creating SSL connection. " << err << std::endl; logSSL(); fflush(stdout); return -1; } // log cipher // std::cerr << "HttpClient::sslRequest, SSL connection using" << SSL_get_cipher (ssl) << std::endl; // showCerts(ssl); // send request // std::cerr << "HttpClient::sslRequest, SSL sending request: " << request << std::endl; int written_bytes = sslSendPacket(request); if (written_bytes != request.length()) { std::cerr << "HttpClient::sslRequest, error sending request" << std::endl; return -1; } // read response and return its length ssl_read_packet.clear(); return sslRecvPacket(); } void HttpClient::logSSL() { unsigned long err; while ((err = ERR_get_error())) { char *str = ERR_error_string(err, nullptr); if (!str) return; std::cerr << str << std::endl; } } void HttpClient::showCerts(SSL* ssl) { X509 *cert; char *line; cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */ if ( cert != NULL ) { std::cerr << "HttpClient::showCerts, Server certificates: " << std::endl; line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); std::cerr << "HttpClient::showCerts, Subject: " << line << std::endl; free(line); /* free the malloc'ed string */ line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); std::cerr << "HttpClient::showCerts, Issuer: " << line << std::endl; free(line); /* free the malloc'ed string */ X509_free(cert); /* free the malloc'ed certificate copy */ } else { std::cerr << "HttpClient::showCerts, No certificates." << std::endl; } }