303 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			8.0 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();
 | |
| } |