diff options
Diffstat (limited to 'maple/maple.cc')
| -rw-r--r-- | maple/maple.cc | 230 |
1 files changed, 152 insertions, 78 deletions
diff --git a/maple/maple.cc b/maple/maple.cc index 0bae0e9..6382fa1 100644 --- a/maple/maple.cc +++ b/maple/maple.cc @@ -21,29 +21,78 @@ #include <arpa/inet.h> #include <csignal> #include <filesystem> -#include <fstream> #include <iostream> +#include <map> #include <openssl/err.h> -#include <openssl/ssl.h> #include <sys/socket.h> #include <unistd.h> #include <vector> -static int maple_socket; -static SSL_CTX *ssl_context; - -auto exit_with[[noreturn]](const char *, bool) -> void; +#include "gemini.hh" +#include "maple.hh" +#include "titan.hh" auto main() -> int { sockaddr_in socket_address {}; std::vector<std::string> gemini_files; + bool titan = false; + std::string titan_token; + size_t titan_max_size = 0; + + // Check if the user is want to support Titan + { + char *titan_environment = std::getenv("TITAN"); + + if (titan_environment == nullptr) { + titan = false; + } else { + std::string valid_titan_environment(titan_environment); + + std::transform( + valid_titan_environment.begin(), + valid_titan_environment.end(), + valid_titan_environment.begin(), + [](unsigned char c) -> int { return std::tolower(c); } + ); + + if (valid_titan_environment == "true" || valid_titan_environment == "1") { + char *unvalidated_titan_token = std::getenv("TITAN_TOKEN"); + char *unvalidated_titan_max_size = std::getenv("TITAN_MAX_SIZE"); + + if (unvalidated_titan_token == nullptr) { + titan_token = ""; + } else { + titan_token = std::string(unvalidated_titan_token); + } + + if (unvalidated_titan_max_size == nullptr) { + titan_max_size = 1024; + + std::cout << "no TITAN_MAX_SIZE set, defaulting to 1024" << std::endl; + } else { + try { + titan_max_size = static_cast<size_t>( + std::stoi(unvalidated_titan_max_size) + ); + } catch (...) { + maple::exit_with( + "TITAN_MAX_SIZE could not be interpreted as an integer", + false + ); + } + } + + titan = true; + } + } + } // Try a graceful shutdown when a SIGINT is detected signal(SIGINT, [](int signal_) -> void { std::cout << "shutdown(" << signal_ << ")" << std::endl; - close(maple_socket); - SSL_CTX_free(ssl_context); + close(maple::maple_socket); + SSL_CTX_free(maple::ssl_context); }); // Find and keep track of all Gemini files to serve @@ -61,8 +110,7 @@ auto main() -> int { file_extension.end(), gemini_file_extension.begin(), gemini_file_extension.end(), - [](auto a, auto b) -> bool { - return std::tolower(a) == std::tolower(b); + [](auto a, auto b) -> bool {return std::tolower(a) == std::tolower(b); } )) { gemini_files.push_back(entry.path()); @@ -78,57 +126,58 @@ auto main() -> int { SSL_library_init(); SSL_load_error_strings(); - ssl_context = SSL_CTX_new(TLS_server_method()); - if (!ssl_context) { - exit_with("unable to create ssl context", true); + maple::ssl_context = SSL_CTX_new(TLS_server_method()); + if (!maple::ssl_context) { + maple::exit_with("unable to create ssl context", true); } if (SSL_CTX_use_certificate_file( - ssl_context, + maple::ssl_context, ".maple/public.pem", SSL_FILETYPE_PEM ) <= 0) { - exit_with("unable to use certificate file", true); + maple::exit_with("unable to use certificate file", true); } if (SSL_CTX_use_PrivateKey_file( - ssl_context, + maple::ssl_context, ".maple/private.pem", SSL_FILETYPE_PEM ) <= 0) { - exit_with("unable to use private key file", true); + maple::exit_with("unable to use private key file", true); } socket_address.sin_family = AF_INET; socket_address.sin_port = htons(1965); socket_address.sin_addr.s_addr = htonl(INADDR_ANY); - maple_socket = socket(AF_INET, SOCK_STREAM, 0); - if (maple_socket < 0) { - exit_with("unable to create socket", false); + maple::maple_socket = socket(AF_INET, SOCK_STREAM, 0); + + if (maple::maple_socket < 0) { + maple::exit_with("unable to create socket", false); } // Reuse address. Allows the use of the address instantly after a SIGINT // without having to wait for the socket to die. int reuse_addr = 1; if (setsockopt( - maple_socket, + maple::maple_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(int) ) < 0) { - exit_with("unable to set socket options (SO_LINGER)", false); + maple::exit_with("unable to set socket options (SO_LINGER)", false); } if (bind( - maple_socket, + maple::maple_socket, reinterpret_cast<sockaddr *>(&socket_address), sizeof(socket_address) ) < 0) { - exit_with("unable to bind", false); + maple::exit_with("unable to bind", false); } - if (listen(maple_socket, 1) < 0) { - exit_with("unable to listen", false); + if (listen(maple::maple_socket, 1) < 0) { + maple::exit_with("unable to listen", false); } // Listen and serve connections @@ -137,15 +186,15 @@ auto main() -> int { unsigned int socket_address_length = sizeof(socket_address_); SSL *ssl; int client = accept( - maple_socket, + maple::maple_socket, reinterpret_cast<sockaddr *>(&socket_address_), &socket_address_length ); char request[1024]; - if (client < 0) { exit_with("unable to accept", false); } + if (client < 0) { maple::exit_with("unable to accept", false); } - ssl = SSL_new(ssl_context); + ssl = SSL_new(maple::ssl_context); SSL_set_fd(ssl, client); if (SSL_accept(ssl) <= 0) { @@ -153,65 +202,88 @@ auto main() -> int { } else { std::stringstream response; size_t index_of_junk; + int request_scheme; // Gemini = 1, Titan = 2, Error = 0 + size_t bytes_read; - SSL_read(ssl, request, sizeof(request)); + SSL_read_ex(ssl, request, sizeof(request), &bytes_read); std::string path(request); - path = path.substr(0, path.size() - 2); // Remove "\r\n" - path.erase(0, 9); // Remove "gemini://" - - // Try to remove the host, if you cannot; it must be a trailing slash-less - // hostname, so we will respond with the index. - size_t found_first = path.find_first_of('/'); - if (found_first != std::string::npos) { - path = path.substr( - found_first, - path.size() - 1 - ); // Remove host + if (path.starts_with("gemini://")) { + request_scheme = 1; + } else if (path.starts_with("titan://")) { + request_scheme = 2; } else { - path = "/index.gmi"; + request_scheme = 0; } - // Remove junk, if any - index_of_junk = path.find_first_of('\n'); - if (index_of_junk != std::string::npos) { - path.erase( - path.find_first_of('\n') - 1, - path.size() - 1 - ); - } - - // Check if the route is a file being served - if (std::find( - gemini_files.begin(), - gemini_files.end(), - ".maple/gmi" + path - ) != gemini_files.end()) { - // If the route is a file being served; get the file contents - - std::ifstream file(".maple/gmi" + path); - std::stringstream buffer; - - buffer << file.rdbuf(); + if (request_scheme != 0) { + path = path.substr(0, bytes_read); - response << "20 text/gemini\r\n" << buffer.str(); - } else { - if (path.empty() || path.at(path.length() - 1) == '/') { - std::ifstream file(".maple/gmi" + path + "index.gmi"); - std::stringstream buffer; + // Remove "\r\n" if Gemini + if (request_scheme == 1) { + path = path.substr(0, path.size() - 2); + } - buffer << file.rdbuf(); + if (request_scheme == 1) { + path.erase(0, 9); // Remove "gemini://" + } else { + path.erase(0, 8); // Remove "titan://" + } - response << "20 text/gemini\r\n" << buffer.str(); + // Try to remove the host, if you cannot; it must be a trailing + // slash-less hostname, so we will respond with the index. + size_t found_first = path.find_first_of('/'); + if (found_first != std::string::npos) { + path = path.substr( + found_first, + path.size() - 1 + ); // Remove host } else { - response << "51 The server (Maple) could not find the specified file.\r\n"; + path = "/index.gmi"; + } + +// std::cout << "1: \"" << path << "\"" << std::endl; + + if (request_scheme == 1) { + // Remove junk, if any + index_of_junk = path.find_first_of('\n'); + if (index_of_junk != std::string::npos) { + path.erase( + path.find_first_of('\n') - 1, + path.size() - 1 + ); + } } - } - std::cout << "requested " << path << std::endl; +// std::cout << "2: \"" << path << "\"" << std::endl; + + // Gemini + if (request_scheme == 1) { + maple::gemini::handle_client(gemini_files, path, response); + } else { // Titan + if (!titan) { + response << "20 text/gemini\r\nThe server (Maple) does not have " + "Titan support enabled!"; + } else { + maple::titan::handle_client( + response, + path, + titan_token, + titan_max_size + ); + } + } - SSL_write(ssl, response.str().c_str(), static_cast<int>(response.str().size())); + SSL_write( + ssl, + response.str().c_str(), + static_cast<int>(response.str().size()) + ); + } else { + std::cout << "received a request with an unsupported url scheme" + << std::endl; + } } SSL_shutdown(ssl); @@ -220,8 +292,10 @@ auto main() -> int { } } -auto exit_with[[noreturn]](const char *message, bool ssl) -> void { - perror(message); - if (ssl) { ERR_print_errors_fp(stderr); } - std::exit(EXIT_FAILURE); +namespace maple { + auto exit_with[[noreturn]](const char *message, bool ssl) -> void { + perror(message); + if (ssl) { ERR_print_errors_fp(stderr); } + std::exit(EXIT_FAILURE); + } } |