TeaSpeakLibrary/test/WSSTest.cpp

303 lines
7.2 KiB
C++

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <cstring>
#include <iostream>
#include <deque>
#include <ThreadPool/Mutex.h>
#include <ThreadPool/Thread.h>
#include <src/log/LogUtils.h>
#include <src/ssl/SSLSocket.h>
#include <event.h>
#include <src/ws/WebSocket.h>
using namespace std;
int create_socket(int port)
{
int s;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
int optval = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)) < 0)
printf("Cannot set SO_REUSEADDR option on listen socket (%s)\n", strerror(errno));
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(int)) < 0)
printf("Cannot set SO_REUSEADDR option on listen socket (%s)\n", strerror(errno));
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to bind");
exit(EXIT_FAILURE);
}
if (listen(s, 1) < 0) {
perror("Unable to listen");
exit(EXIT_FAILURE);
}
return s;
}
void setNonBlock(int fd) {
}
void init_openssl()
{
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl()
{
EVP_cleanup();
}
SSL_CTX *create_context()
{
const SSL_METHOD *method;
SSL_CTX *ctx;
method = SSLv23_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
//#define CERTIFICATE_FILE "cert.pem"
//#define KEY_FILE "key.pem"
#define CERTIFICATE_FILE "/home/wolverindev/TeamSpeak/server/environment/default_certificate.pem"
#define KEY_FILE "/home/wolverindev/TeamSpeak/server/environment/default_privatekey.pem"
std::pair<EVP_PKEY*, X509*> createCerts(pem_password_cb* password) {
/*
auto bio = BIO_new_file("cert.pem", "r");
if(!bio) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
auto cert = PEM_read_bio_X509(bio, nullptr, password, nullptr);
if(!cert) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
BIO_free(bio);
bio = BIO_new_file("key.pem", "r");
if(!bio) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
auto key = PEM_read_bio_PrivateKey(bio, nullptr, password, nullptr);
if(!key) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
BIO_free(bio);
*/
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
BN_set_word(e.get(), RSA_F4);
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return {nullptr, nullptr};
EVP_PKEY_assign_RSA(key.get(), rsa);
auto cert = X509_new();
X509_set_pubkey(cert, key.get());
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
X509_NAME* name = X509_get_subject_name(cert);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *) "DE", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *) "TeaSpeak", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *) "Web Server", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "emailAddress", MBSTRING_ASC, (unsigned char *)"contact@teaspeak.de", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"web.teaspeak.de", -1, -1, 0);
X509_set_issuer_name(cert, name);
X509_set_subject_name(cert, name);
X509_sign(cert, key.get(), EVP_sha512());
return {key.release(), cert};
};
/*
* Generate key:
openssl req -x509 -out cert.pem -newkey rsa:2048 -keyout key.pem -days 365 -passout pass:markus -config <(
cat <<-EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
C=DE
O=TeaSpeak
OU=Web Server
emailAddress=contact@teaspeak.de
CN = web.teaspeak.de
EOF
)
*/
void configure_context(SSL_CTX *ctx)
{
SSL_CTX_set_ecdh_auto(ctx, 1);
auto certs = createCerts([](char* buffer, int length, int rwflag, void* data) -> int {
std::string password = "markus";
memcpy(buffer, password.data(), password.length());
return password.length();
});
PEM_write_X509(stdout, certs.second);
if (SSL_CTX_use_PrivateKey(ctx, certs.first) <= 0 ) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_certificate(ctx, certs.second) <= 0 ) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
}
struct Client {
int fd;
ssl::SSLSocket ssl;
ws::WebSocket ws;
std::deque<std::string> writeQueue;
event* read;
event* write;
};
void handleRead(int fd, short, void* ptrClient) {
auto client = (Client*) ptrClient;
ssize_t bufferLength = 1024;
char buffer[bufferLength];
bufferLength = recv(client->fd, buffer, bufferLength, MSG_DONTWAIT);
if(bufferLength < 0){
cout << "Invalid read: " << bufferLength << " / " << errno << endl;
event_del(client->read);
event_del(client->write);
return;
} else if(bufferLength == 0) return;
cout << "Read " << bufferLength << " bytes" << endl;
client->ssl.proceedMessage(string(buffer, bufferLength));
}
void handleWrite(int fd, short, void* ptrClient) {
auto client = (Client*) ptrClient;
while(!client->writeQueue.empty()) {
auto message = std::move(client->writeQueue.front());
client->writeQueue.pop_front();
send(client->fd, message.data(), message.length(), 0);
cout << "Send " << message.length() << " bytes" << endl;
}
}
int main(int argc, char **argv)
{
int sock;
SSL_CTX *ctx;
init_openssl();
ctx = create_context();
configure_context(ctx);
sock = create_socket(4433);
auto evLoop = event_base_new();
threads::Thread([evLoop](){
while(true) {
event_base_dispatch(evLoop);
threads::self::sleep_for(std::chrono::milliseconds(10));
};
cerr << "event_base_dispatch() exited!" << endl;
}).detach();
/* Handle connections */
cout << "Waiting!" << endl;
while(1) {
struct sockaddr_in addr{};
uint len = sizeof(addr);
int fd = accept(sock, (struct sockaddr*)&addr, &len);
if (fd < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}
auto client = new Client{};
client->fd = fd;
client->read = event_new(evLoop, fd, EV_READ | EV_PERSIST, handleRead, client);
client->write = event_new(evLoop, fd, EV_WRITE, handleWrite, client);
client->ssl.callback_error = [](ssl::SSLSocketError, const std::string& error) {
cout << error << endl;
};
client->ssl.callback_write = [client](const std::string& buffer) {
client->writeQueue.push_back(buffer);
event_add(client->write, nullptr);
};
client->ssl.callback_read = [client](const std::string& buffer) {
cout << buffer;
client->ws.proceedMessage(buffer);
};
auto ptr = std::shared_ptr<SSL_CTX>(ctx, [](SSL_CTX*){});
client->ssl.initialize(ptr);
client->ws.on_message = [](const std::string& message) {
cout << "[WS] Message: " << endl << message << endl;
};
client->ws.write_message = [client](const std::string& data) {
cout << "[WS] Send: " << endl << data << endl;
client->ssl.sendMessage(data);
};
client->ws.initialize();
event_add(client->read, nullptr);
}
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
}