1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-11-26 11:07:17 +01:00
Files
SqMod/vendor/DPP/src/dpp/sslclient.cpp
Sandu Liviu Catalin 4f70f89b78 Basic Discord library layout.
Foundation for the discord library bindings. To be gradually exposed to the script.
2021-09-10 20:13:42 +03:00

408 lines
11 KiB
C++

/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#include <errno.h>
#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <io.h>
#pragma comment(lib,"ws2_32")
#else
#include <resolv.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#endif
#ifdef OPENSSL_SYS_WIN32
#undef X509_NAME
#undef X509_EXTENSIONS
#undef X509_CERT_PAIR
#undef PKCS7_ISSUER_AND_SERIAL
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <exception>
#include <string>
#include <iostream>
#include <fmt/format.h>
#include <dpp/sslclient.h>
#include <dpp/discord.h>
#include <dpp/dispatcher.h>
namespace dpp {
class opensslcontext {
public:
/** OpenSSL session */
SSL* ssl;
/** OpenSSL context */
SSL_CTX* ctx;
};
/* You'd think that we would get better performance with a bigger buffer, but SSL frames are 16k each.
* SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as
* it'd go unused.
*/
#define BUFSIZZ 1024 * 16
const int ERROR_STATUS = -1;
ssl_client::ssl_client(const std::string &_hostname, const std::string &_port) : last_tick(time(NULL)), hostname(_hostname), port(_port), bytes_in(0), bytes_out(0)
{
#ifndef WIN32
signal(SIGALRM, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
signal(SIGXFSZ, SIG_IGN);
#endif
ssl = new opensslcontext();
Connect();
}
/* SSL Client constructor throws std::runtime_error if it can't connect to the host */
void ssl_client::Connect()
{
/* Initial connection is done in blocking mode. There is a timeout on it. */
nonblocking = false;
const SSL_METHOD *method = TLS_client_method(); /* Create new client-method instance */
/* Create SSL context */
ssl->ctx = SSL_CTX_new(method);
if (ssl->ctx == nullptr)
throw dpp::exception("Failed to create SSL client context!");
/* Create SSL session */
ssl->ssl = SSL_new(ssl->ctx);
if (ssl->ssl == nullptr)
throw dpp::exception("SSL_new failed!");
/* Resolve hostname to IP */
struct hostent *host;
if ((host = gethostbyname(hostname.c_str())) == nullptr)
throw dpp::exception(fmt::format("Couldn't resolve hostname '{}'", hostname));
struct addrinfo hints = {0}, *addrs;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int status = getaddrinfo(hostname.c_str(), port.c_str(), &hints, &addrs);
if (status != 0)
throw dpp::exception(fmt::format("getaddrinfo (host={}, port={}): ", hostname, port, gai_strerror(status)));
/* Attempt each address in turn, if there are multiple IP addresses on the hostname */
int err;
for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next) {
sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
if (sfd == ERROR_STATUS) {
err = errno;
continue;
} else if (connect(sfd, addr->ai_addr, addr->ai_addrlen) == 0) {
break;
}
err = errno;
shutdown(sfd, 2);
#ifdef _WIN32
if (sfd >= 0 && sfd < FD_SETSIZE) {
closesocket(sfd);
}
#else
::close(sfd);
#endif
sfd = ERROR_STATUS;
}
freeaddrinfo(addrs);
/* Check if none of the IPs yielded a valid connection */
if (sfd == ERROR_STATUS)
throw dpp::exception(strerror(err));
/* We're good to go - hand the fd over to openssl */
SSL_set_fd(ssl->ssl, sfd);
status = SSL_connect(ssl->ssl);
if (status != 1) {
throw dpp::exception("SSL_connect error");
}
this->cipher = SSL_get_cipher(ssl->ssl);
}
void ssl_client::write(const std::string &data)
{
/* If we are in nonblocking mode, append to the buffer,
* otherwise just use SSL_write directly. The only time we
* use SSL_write directly is during connection before the
* ReadLoop is called, which allows for guaranteed simple
* lock-step delivery e.g. for HTTP header negotiation
*/
if (nonblocking) {
obuffer += data;
} else {
SSL_write(ssl->ssl, data.data(), data.length());
}
}
void ssl_client::one_second_timer()
{
}
std::string ssl_client::get_cipher() {
return cipher;
}
void ssl_client::log(dpp::loglevel severity, const std::string &msg) const
{
}
void ssl_client::read_loop()
{
/* The read loop is non-blocking using select(). This method
* cannot read while it is waiting for write, or write while it is
* waiting for read. This is a limitation of the openssl libraries,
* as SSL is sent and received in low level ~16k frames which must
* be synchronised and ordered correctly. Attempting to send while
* we need another frame or receive while we are due to send a frame
* would cause the protocol to break.
*/
int width;
int r = 0;
size_t ClientToServerLength = 0, ClientToServerOffset = 0;
bool read_blocked_on_write = false, write_blocked_on_read = false,read_blocked = false;
fd_set readfds, writefds, efds;
char ClientToServerBuffer[BUFSIZZ], ServerToClientBuffer[BUFSIZZ];
/* Make the socket nonblocking */
#ifdef _WIN32
u_long mode = 1;
int result = ioctlsocket(sfd, FIONBIO, &mode);
if (result != NO_ERROR)
throw dpp::exception("Can't switch socket to non-blocking mode!");
#else
int ofcmode;
ofcmode = fcntl(sfd, F_GETFL, 0);
ofcmode |= O_NDELAY;
if (fcntl(sfd, F_SETFL, ofcmode)) {
throw dpp::exception("Can't switch socket to non-blocking mode!");
}
#endif
nonblocking = true;
width = sfd + 1;
try {
/* Loop until there is a socket error */
while(true) {
if (last_tick != time(NULL)) {
this->one_second_timer();
last_tick = time(NULL);
}
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&efds);
FD_SET(sfd,&readfds);
FD_SET(sfd,&efds);
if (custom_readable_fd && custom_readable_fd() >= 0) {
int cfd = custom_readable_fd();
FD_SET(cfd, &readfds);
FD_SET(cfd, &efds);
}
if (custom_writeable_fd && custom_writeable_fd() >= 0) {
int cfd = custom_writeable_fd();
FD_SET(cfd, &writefds);
}
/* If we're waiting for a read on the socket don't try to write to the server */
if (ClientToServerLength || obuffer.length() || read_blocked_on_write) {
FD_SET(sfd,&writefds);
}
timeval ts;
ts.tv_sec = 0;
ts.tv_usec = 50000;
r = select(FD_SETSIZE, &readfds, &writefds, &efds, &ts);
if (r == 0)
continue;
if (custom_writeable_fd && FD_ISSET(custom_writeable_fd(), &writefds)) {
custom_writeable_ready();
}
if (custom_readable_fd && FD_ISSET(custom_readable_fd(), &readfds)) {
custom_readable_ready();
}
if (custom_readable_fd && FD_ISSET(custom_readable_fd(), &efds)) {
}
if (FD_ISSET(sfd, &efds)) {
this->log(dpp::ll_error, fmt::format("Error on SSL connection: {}", strerror(errno)));
return;
}
/* Now check if there's data to read */
if((FD_ISSET(sfd,&readfds) && !write_blocked_on_read) || (read_blocked_on_write && FD_ISSET(sfd,&writefds))) {
do {
read_blocked_on_write = false;
read_blocked = false;
r = SSL_read(ssl->ssl,ServerToClientBuffer,BUFSIZZ);
int e = SSL_get_error(ssl->ssl,r);
switch (e) {
case SSL_ERROR_NONE:
/* Data received, add it to the buffer */
buffer.append(ServerToClientBuffer, r);
this->handle_buffer(buffer);
bytes_in += r;
break;
case SSL_ERROR_ZERO_RETURN:
/* End of data */
SSL_shutdown(ssl->ssl);
return;
break;
case SSL_ERROR_WANT_READ:
read_blocked = true;
break;
/* We get a WANT_WRITE if we're trying to rehandshake and we block on a write during that rehandshake.
* We need to wait on the socket to be writeable but reinitiate the read when it is
*/
case SSL_ERROR_WANT_WRITE:
read_blocked_on_write = true;
break;
default:
return;
break;
}
/* We need a check for read_blocked here because SSL_pending() doesn't work properly during the
* handshake. This check prevents a busy-wait loop around SSL_read()
*/
} while (SSL_pending(ssl->ssl) && !read_blocked);
}
/* Check for input on the sendq */
if (obuffer.length() && ClientToServerLength == 0) {
memcpy(&ClientToServerBuffer, obuffer.data(), obuffer.length() > BUFSIZZ ? BUFSIZZ : obuffer.length());
ClientToServerLength = obuffer.length() > BUFSIZZ ? BUFSIZZ : obuffer.length();
obuffer = obuffer.substr(ClientToServerLength, obuffer.length());
ClientToServerOffset = 0;
}
/* If the socket is writeable... */
if ((FD_ISSET(sfd,&writefds) && ClientToServerLength) || (write_blocked_on_read && FD_ISSET(sfd,&readfds))) {
write_blocked_on_read = false;
/* Try to write */
r = SSL_write(ssl->ssl, ClientToServerBuffer + ClientToServerOffset, ClientToServerLength);
switch(SSL_get_error(ssl->ssl,r)) {
/* We wrote something */
case SSL_ERROR_NONE:
ClientToServerLength -= r;
ClientToServerOffset += r;
bytes_out += r;
break;
/* We would have blocked */
case SSL_ERROR_WANT_WRITE:
break;
/* We get a WANT_READ if we're trying to rehandshake and we block onwrite during the current connection.
* We need to wait on the socket to be readable but reinitiate our write when it is
*/
case SSL_ERROR_WANT_READ:
write_blocked_on_read = true;
break;
/* Some other error */
default:
return;
break;
}
}
}
}
catch (const std::exception &e) {
log(ll_warning, fmt::format("Read loop ended: {}", e.what()));
}
}
uint64_t ssl_client::get_bytes_out()
{
return bytes_out;
}
uint64_t ssl_client::get_bytes_in()
{
return bytes_in;
}
bool ssl_client::handle_buffer(std::string &buffer)
{
return true;
}
void ssl_client::close()
{
if (ssl->ssl) {
SSL_free(ssl->ssl);
ssl->ssl = nullptr;
}
shutdown(sfd, 2);
#ifdef _WIN32
if (sfd >= 0 && sfd < FD_SETSIZE) {
closesocket(sfd);
}
#else
::close(sfd);
#endif
if (ssl->ctx) {
SSL_CTX_free(ssl->ctx);
ssl->ctx = nullptr;
}
sfd = -1;
obuffer.clear();
buffer.clear();
}
ssl_client::~ssl_client()
{
this->close();
delete ssl;
}
};