363 lines
7.5 KiB
C++
363 lines
7.5 KiB
C++
/*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Additional permission under GNU GPL version 3 section 7
|
|
*
|
|
* If you modify this Program, or any covered work, by linking or combining
|
|
* it with OpenSSL (or a modified version of that library), containing parts
|
|
* covered by the terms of OpenSSL License and SSLeay License, the licensors
|
|
* of this Program grant you additional permission to convey the resulting work.
|
|
*
|
|
*/
|
|
|
|
#include "socket.h"
|
|
#include "jpsock.h"
|
|
#include "jconf.h"
|
|
#include "console.h"
|
|
#include "executor.h"
|
|
|
|
#ifndef CONF_NO_TLS
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/opensslconf.h>
|
|
|
|
#ifndef OPENSSL_THREADS
|
|
#error OpenSSL was compiled without thread support
|
|
#endif
|
|
#endif
|
|
|
|
plain_socket::plain_socket(jpsock* err_callback) : pCallback(err_callback)
|
|
{
|
|
hSocket = INVALID_SOCKET;
|
|
pSockAddr = nullptr;
|
|
}
|
|
|
|
bool plain_socket::set_hostname(const char* sAddr)
|
|
{
|
|
char sAddrMb[256];
|
|
char *sTmp, *sPort;
|
|
|
|
size_t ln = strlen(sAddr);
|
|
if (ln >= sizeof(sAddrMb))
|
|
return pCallback->set_socket_error("CONNECT error: Pool address overflow.");
|
|
|
|
memcpy(sAddrMb, sAddr, ln);
|
|
sAddrMb[ln] = '\0';
|
|
|
|
if ((sTmp = strstr(sAddrMb, "//")) != nullptr)
|
|
memmove(sAddrMb, sTmp, strlen(sTmp) + 1);
|
|
|
|
if ((sPort = strchr(sAddrMb, ':')) == nullptr)
|
|
return pCallback->set_socket_error("CONNECT error: Pool port number not specified, please use format <hostname>:<port>.");
|
|
|
|
sPort[0] = '\0';
|
|
sPort++;
|
|
|
|
addrinfo hints = { 0 };
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
pAddrRoot = nullptr;
|
|
int err;
|
|
if ((err = getaddrinfo(sAddrMb, sPort, &hints, &pAddrRoot)) != 0)
|
|
return pCallback->set_socket_error_strerr("CONNECT error: GetAddrInfo: ", err);
|
|
|
|
addrinfo *ptr = pAddrRoot;
|
|
addrinfo *ipv4 = nullptr, *ipv6 = nullptr;
|
|
|
|
while (ptr != nullptr)
|
|
{
|
|
if (ptr->ai_family == AF_INET)
|
|
ipv4 = ptr;
|
|
if (ptr->ai_family == AF_INET6)
|
|
ipv6 = ptr;
|
|
ptr = ptr->ai_next;
|
|
}
|
|
|
|
if (ipv4 == nullptr && ipv6 == nullptr)
|
|
{
|
|
freeaddrinfo(pAddrRoot);
|
|
pAddrRoot = nullptr;
|
|
return pCallback->set_socket_error("CONNECT error: I found some DNS records but no IPv4 or IPv6 addresses.");
|
|
}
|
|
else if (ipv4 != nullptr && ipv6 == nullptr)
|
|
pSockAddr = ipv4;
|
|
else if (ipv4 == nullptr && ipv6 != nullptr)
|
|
pSockAddr = ipv6;
|
|
else if (ipv4 != nullptr && ipv6 != nullptr)
|
|
{
|
|
if(jconf::inst()->PreferIpv4())
|
|
pSockAddr = ipv4;
|
|
else
|
|
pSockAddr = ipv6;
|
|
}
|
|
|
|
hSocket = socket(pSockAddr->ai_family, pSockAddr->ai_socktype, pSockAddr->ai_protocol);
|
|
|
|
if (hSocket == INVALID_SOCKET)
|
|
{
|
|
freeaddrinfo(pAddrRoot);
|
|
pAddrRoot = nullptr;
|
|
return pCallback->set_socket_error_strerr("CONNECT error: Socket creation failed ");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool plain_socket::connect()
|
|
{
|
|
int ret = ::connect(hSocket, pSockAddr->ai_addr, (int)pSockAddr->ai_addrlen);
|
|
|
|
freeaddrinfo(pAddrRoot);
|
|
pAddrRoot = nullptr;
|
|
|
|
if (ret != 0)
|
|
return pCallback->set_socket_error_strerr("CONNECT error: ");
|
|
else
|
|
return true;
|
|
}
|
|
|
|
int plain_socket::recv(char* buf, unsigned int len)
|
|
{
|
|
int ret = ::recv(hSocket, buf, len, 0);
|
|
|
|
if(ret == 0)
|
|
pCallback->set_socket_error("RECEIVE error: socket closed");
|
|
if(ret == SOCKET_ERROR || ret < 0)
|
|
pCallback->set_socket_error_strerr("RECEIVE error: ");
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool plain_socket::send(const char* buf)
|
|
{
|
|
int pos = 0, slen = strlen(buf);
|
|
while (pos != slen)
|
|
{
|
|
int ret = ::send(hSocket, buf + pos, slen - pos, 0);
|
|
if (ret == SOCKET_ERROR)
|
|
{
|
|
pCallback->set_socket_error_strerr("SEND error: ");
|
|
return false;
|
|
}
|
|
else
|
|
pos += ret;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void plain_socket::close(bool free)
|
|
{
|
|
if(hSocket != INVALID_SOCKET)
|
|
{
|
|
sock_close(hSocket);
|
|
hSocket = INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
#ifndef CONF_NO_TLS
|
|
tls_socket::tls_socket(jpsock* err_callback) : pCallback(err_callback)
|
|
{
|
|
}
|
|
|
|
void tls_socket::print_error()
|
|
{
|
|
BIO* err_bio = BIO_new(BIO_s_mem());
|
|
ERR_print_errors(err_bio);
|
|
|
|
char *buf = nullptr;
|
|
size_t len = BIO_get_mem_data(err_bio, &buf);
|
|
|
|
pCallback->set_socket_error(buf, len);
|
|
|
|
BIO_free(err_bio);
|
|
}
|
|
|
|
void tls_socket::init_ctx()
|
|
{
|
|
const SSL_METHOD* method = SSLv23_method();
|
|
|
|
if(method == nullptr)
|
|
return;
|
|
|
|
ctx = SSL_CTX_new(method);
|
|
if(ctx == nullptr)
|
|
return;
|
|
|
|
if(jconf::inst()->TlsSecureAlgos())
|
|
{
|
|
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_COMPRESSION);
|
|
}
|
|
}
|
|
|
|
bool tls_socket::set_hostname(const char* sAddr)
|
|
{
|
|
if(ctx == nullptr)
|
|
{
|
|
init_ctx();
|
|
if(ctx == nullptr)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if((bio = BIO_new_ssl_connect(ctx)) == nullptr)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
if(BIO_set_conn_hostname(bio, sAddr) != 1)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
BIO_get_ssl(bio, &ssl);
|
|
if(ssl == nullptr)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
if(jconf::inst()->TlsSecureAlgos())
|
|
{
|
|
if(SSL_set_cipher_list(ssl, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4:!SHA1") != 1)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool tls_socket::connect()
|
|
{
|
|
if(BIO_do_connect(bio) != 1)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
if(BIO_do_handshake(bio) != 1)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
/* Step 1: verify a server certificate was presented during the negotiation */
|
|
X509* cert = SSL_get_peer_certificate(ssl);
|
|
if(cert == nullptr)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
const EVP_MD* digest;
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
unsigned int dlen;
|
|
|
|
digest = EVP_get_digestbyname("sha256");
|
|
if(digest == nullptr)
|
|
{
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
if(X509_digest(cert, digest, md, &dlen) != 1)
|
|
{
|
|
X509_free(cert);
|
|
print_error();
|
|
return false;
|
|
}
|
|
|
|
if(pCallback->pool_id != executor::dev_pool_id)
|
|
{
|
|
//Base64 encode digest
|
|
BIO *bmem, *b64;
|
|
b64 = BIO_new(BIO_f_base64());
|
|
bmem = BIO_new(BIO_s_mem());
|
|
|
|
BIO_puts(bmem, "SHA256:");
|
|
b64 = BIO_push(b64, bmem);
|
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
|
BIO_write(b64, md, dlen);
|
|
BIO_flush(b64);
|
|
|
|
const char* conf_md = jconf::inst()->GetTlsFingerprint();
|
|
char *b64_md = nullptr;
|
|
size_t b64_len = BIO_get_mem_data(bmem, &b64_md);
|
|
|
|
if(strlen(conf_md) == 0)
|
|
{
|
|
printer::inst()->print_msg(L1, "Server fingerprint: %.*s", (int)b64_len, b64_md);
|
|
}
|
|
else if(strncmp(b64_md, conf_md, b64_len) != 0)
|
|
{
|
|
printer::inst()->print_msg(L0, "FINGERPRINT FAILED CHECK: %.*s was given, %s was configured",
|
|
(int)b64_len, b64_md, conf_md);
|
|
|
|
pCallback->set_socket_error("FINGERPRINT FAILED CHECK");
|
|
BIO_free_all(b64);
|
|
X509_free(cert);
|
|
return false;
|
|
}
|
|
|
|
BIO_free_all(b64);
|
|
}
|
|
|
|
X509_free(cert);
|
|
return true;
|
|
}
|
|
|
|
int tls_socket::recv(char* buf, unsigned int len)
|
|
{
|
|
int ret = BIO_read(bio, buf, len);
|
|
|
|
if(ret == 0)
|
|
pCallback->set_socket_error("RECEIVE error: socket closed");
|
|
if(ret < 0)
|
|
print_error();
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool tls_socket::send(const char* buf)
|
|
{
|
|
return BIO_puts(bio, buf) > 0;
|
|
}
|
|
|
|
void tls_socket::close(bool free)
|
|
{
|
|
if(bio == nullptr || ssl == nullptr)
|
|
return;
|
|
|
|
if(!free)
|
|
{
|
|
sock_close(BIO_get_fd(bio, nullptr));
|
|
}
|
|
else
|
|
{
|
|
BIO_free_all(bio);
|
|
ssl = nullptr;
|
|
bio = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|