From f59ea622cc7e8bb1009be5771242fe58324479eb Mon Sep 17 00:00:00 2001 From: Wirlaburla Date: Wed, 1 Jan 2025 18:18:00 -0600 Subject: [PATCH] use std::format in logger, re-organize main.cpp, add SIGINT handling. --- src/client.cpp | 53 ++- src/logger.h | 34 +- src/main.cpp | 477 +++++++++++++----------- src/plugin.h | 5 +- src/plugin_manager.h | 24 +- src/plugins/auth_pam/auth_pam.cpp | 10 +- src/plugins/filer_local/filer_local.cpp | 13 +- 7 files changed, 343 insertions(+), 273 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index f3a9d83..2617e5a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -56,7 +56,7 @@ public: return 0; } // Fatal error - logger->print(LOGLEVEL_ERROR, "C(%i) SSL handshake failed with error: %d", control_sock, err); + logger->print(LOGLEVEL_ERROR, "client {} SSL handshake failed with error: {}", control_sock, err); ERR_print_errors_fp(stderr); SSL_free(control_ssl); control_ssl = nullptr; @@ -66,7 +66,7 @@ public: } // Handshake completed successfully ssl_handshake_complete = true; - logger->print(LOGLEVEL_DEBUG, "C(%i) SSL handshake completed", control_sock); + logger->print(LOGLEVEL_DEBUG, "client {} SSL handshake completed", control_sock); return 0; } @@ -226,7 +226,7 @@ public: if (getsockname(data_fd, (struct sockaddr *)&data_address, &datalen) == 0) { dataport = ntohs(data_address.sin_port); memcpy(&netport[0], &dataport, 2); - logger->print(LOGLEVEL_DEBUG, "D(%i) PASV initialized: %u.%u.%u.%u:%u", data_fd, server_address[0], server_address[1], server_address[2], server_address[3], dataport); + logger->print(LOGLEVEL_DEBUG, "client {} initialized PASV on dataline {}: {}.{}.{}.{}:{}", control_sock, data_fd, server_address[0], server_address[1], server_address[2], server_address[3], dataport); } else { perror("pasv getpeername() failed"); close(data_fd); @@ -248,7 +248,7 @@ public: submit(227, std::string(pasvok)); free(pasvok); if ((data_sock = accept(data_fd, NULL, NULL)) >= 0) { - logger->print(LOGLEVEL_INFO, "D(%i) PASV accepted: %i", data_fd, data_sock); + logger->print(LOGLEVEL_INFO, "dataline {} PASV accepted on socket {}", data_fd, data_sock); if (is_secure && protect_data) { if (!setupDataSSL()) { @@ -260,7 +260,7 @@ public: state = FTP_STATE_ONDATA; } else { - logger->print(LOGLEVEL_ERROR, "D(%i) PASV accept failed: %s", data_fd, strerror(errno)); + logger->print(LOGLEVEL_ERROR, "dataline {} PASV accept failed: {}", data_fd, strerror(errno)); submit(425, "Can't open data connection"); data_close(); } @@ -408,7 +408,7 @@ public: // The socket is not ready, main loop will handle it return 0; } - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_write failed with error: %d", control_sock, err); + logger->print(LOGLEVEL_ERROR, "client {} SSL_write failed with error: {}", control_sock, err); return 1; } } else { @@ -416,10 +416,10 @@ public: } if (result >= 0) { - logger->print(LOGLEVEL_DEBUG, "C(%i) << %s", control_sock, msg.c_str()); + logger->print(LOGLEVEL_DEBUG, "sent client {}: {}", control_sock, msg); return 0; } - logger->print(LOGLEVEL_ERROR, "C(%i) !< %s", control_sock, msg.c_str()); + logger->print(LOGLEVEL_ERROR, "send to client {} failed: {}", control_sock, msg); return 1; } @@ -430,7 +430,7 @@ public: // Send through SSL if secure connection is established int written = SSL_write(control_ssl, response.c_str(), response.length()); if (written <= 0) { - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_write failed", control_sock); + logger->print(LOGLEVEL_ERROR, "client {} SSL_write failed", control_sock); return; } } else { @@ -438,7 +438,7 @@ public: send(control_sock, response.c_str(), response.length(), 0); } - logger->print(LOGLEVEL_DEBUG, "C(%i) << %d %s", control_sock, code, msg.c_str()); + logger->print(LOGLEVEL_DEBUG, "sent client {}: {} {}", control_sock, code, msg); } void submit(int code, const std::vector& msgs) { @@ -461,14 +461,14 @@ public: if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ) { continue; // Need to retry } - logger->print(LOGLEVEL_DEBUG, "D(%i) SSL write error: %d", data_sock, ssl_err); + logger->print(LOGLEVEL_DEBUG, "dataline {} SSL write error: {}", data_sock, ssl_err); return 1; } } else { bytes = send(data_sock, out + total_sent, size - total_sent, 0); if (bytes < 0) { if (errno == EINTR) continue; - logger->print(LOGLEVEL_DEBUG, "D(%i) !< Error: %s", data_sock, strerror(errno)); + logger->print(LOGLEVEL_DEBUG, "send to dataline {} error: {}", data_sock, strerror(errno)); return 1; } } @@ -479,7 +479,7 @@ public: int data_close() { if (data_sock <= 0) return 0; - logger->print(LOGLEVEL_DEBUG, "D(%i) Closing...", data_sock); + logger->print(LOGLEVEL_DEBUG, "dataline {} closing...", data_sock); if (data_ssl) { SSL_shutdown(data_ssl); @@ -546,19 +546,19 @@ private: bool setupControlSSL() { control_ssl = SSL_new(SSLManager::getInstance().getContext()); if (!control_ssl) { - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_new failed", control_sock); + logger->print(LOGLEVEL_ERROR, "client {} SSL_new failed", control_sock); return false; } if (!SSL_set_fd(control_ssl, control_sock)) { - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_set_fd failed", control_sock); + logger->print(LOGLEVEL_ERROR, "client {} SSL_set_fd failed", control_sock); SSL_free(control_ssl); control_ssl = nullptr; return false; } ssl_handshake_complete = false; - logger->print(LOGLEVEL_DEBUG, "C(%i) SSL setup complete, handshake pending", control_sock); + logger->print(LOGLEVEL_DEBUG, "waiting for client{} SSL handshake", control_sock); // Cache the session after successful handshake if (cached_session) { @@ -575,7 +575,7 @@ private: data_ssl = SSL_new(SSLManager::getInstance().getContext()); if (!data_ssl) { - logger->print(LOGLEVEL_ERROR, "C(%i) Data SSL_new failed", control_sock); + logger->print(LOGLEVEL_ERROR, "dataline {} SSL_new failed", data_sock); return false; } @@ -586,7 +586,7 @@ private: SSL_set_accept_state(data_ssl); if (!SSL_set_fd(data_ssl, data_sock)) { - logger->print(LOGLEVEL_ERROR, "C(%i) Data SSL_set_fd failed", control_sock); + logger->print(LOGLEVEL_ERROR, "dataline {} SSL_set_fd failed", data_sock); SSL_free(data_ssl); data_ssl = nullptr; return false; @@ -606,7 +606,7 @@ private: pfd.events = (ssl_err == SSL_ERROR_WANT_READ) ? POLLIN : POLLOUT; if (poll(&pfd, 1, 1000) <= 0) { - logger->print(LOGLEVEL_ERROR, "C(%i) Data SSL handshake timeout", control_sock); + logger->print(LOGLEVEL_ERROR, "dataline {} SSL handshake timeout", data_sock); SSL_free(data_ssl); data_ssl = nullptr; return false; @@ -614,7 +614,7 @@ private: continue; } - logger->print(LOGLEVEL_ERROR, "C(%i) Data SSL handshake failed: %s", + logger->print(LOGLEVEL_ERROR, "dataline {} SSL handshake failed: {}", control_sock, ERR_error_string(ERR_get_error(), nullptr)); SSL_free(data_ssl); data_ssl = nullptr; @@ -626,32 +626,30 @@ private: // Log whether session was reused if (SSL_session_reused(data_ssl)) { - logger->print(LOGLEVEL_DEBUG, "C(%i) Data SSL session resumed", control_sock); + logger->print(LOGLEVEL_DEBUG, "dataline {} SSL session resumed", data_sock); } else { - logger->print(LOGLEVEL_DEBUG, "C(%i) Data SSL new session", control_sock); + logger->print(LOGLEVEL_DEBUG, "dataline {} SSL new session", data_sock); } return true; } void authedInit() { - logger->print(LOGLEVEL_INFO, "C(%i) logging in as '%s'", control_sock, auth_data->username); struct file_data fd; std::string root; std::string home = "/"; std::vector auth_response; if (auth->isChroot()) { root = std::string(this->auth_data->home_dir); - logger->print(LOGLEVEL_INFO, "C(%i) Set chrooted root of '%s' to '%s'", control_sock, auth_data->username, root.c_str()); } else { root = "/"; home = std::string(this->auth_data->home_dir); - logger->print(LOGLEVEL_INFO, "C(%i) Set home of '%s' to '%s'", control_sock, auth_data->username, home.c_str()); } if ( ((struct file_data)filer->setRoot(root)).error.code == 0 && ((struct file_data)filer->setCWD(home)).error.code == 0 ) { + logger->print(LOGLEVEL_DEBUG, "authenticated {} on client {} with root '{}' and home '{}'", auth_data->username, control_sock, root, home); state = FTP_STATE_AUTHED; auth_response.push_back("Login OK"); std::string user_motd = getMotD(); @@ -662,6 +660,7 @@ private: auth_response.push_back(line); } } + logger->print(LOGLEVEL_INFO, "user '{}' logged in on client {}", auth_data->username, control_sock); submit(230, auth_response); return; } @@ -685,7 +684,7 @@ private: FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { - logger->print(LOGLEVEL_ERROR, "Failed to execute MOTD command: %s", cmd.c_str()); + logger->print(LOGLEVEL_ERROR, "motd command failed: {}", cmd); return ""; } @@ -702,7 +701,7 @@ private: std::ifstream file(path); if (!file) { - logger->print(LOGLEVEL_ERROR, "Failed to open MOTD file: %s", path.c_str()); + logger->print(LOGLEVEL_ERROR, "failed to open motd file: {}", path); return ""; } diff --git a/src/logger.h b/src/logger.h index dc8bb45..4605e1f 100644 --- a/src/logger.h +++ b/src/logger.h @@ -8,7 +8,7 @@ #include #include -#define LOGLEVEL_CONSOLE 5 +#define LOGLEVEL_MIN 10 #define LOGLEVEL_CRITICAL 4 #define LOGLEVEL_ERROR 3 #define LOGLEVEL_WARNING 2 @@ -28,16 +28,31 @@ public: logfiles[level] = std::ofstream(path, std::ios::binary|std::ios::app); } - template - void print(int level, const char* message, Args... args) { + void setConsoleLevel(int level) { + this->console_level = level; + } + + void print(int level, std::string message) { std::lock_guard lock(log_mutex); - char* prepared; - asprintf(&prepared, "[%s] (%c) %s\n", getTime(), logTypeChar(level), message); - char* formatted; - asprintf(&formatted, prepared, args...); - fprintf(stdout, formatted); + + std::string output = std::format("[{}] ({}) {}\n", getTime(), logTypeChar(level), message); + + if (level >= console_level) std::cout << output; if (logMutex.try_lock() && logfiles[level].is_open()) { - logfiles[level].write(formatted, strlen(formatted)); + logfiles[level].write(output.c_str(), output.length()); + logfiles[level].flush(); + logMutex.unlock(); + } + } + + template + void print(int level, std::format_string message, Args... args) { + std::lock_guard lock(log_mutex); + std::string formatted = std::vformat(message.get(), std::make_format_args(args...)); + std::string output = std::format("[{}] ({}) {}\n", getTime(), logTypeChar(level), formatted); + if (level >= console_level) std::cout << output; + if (logMutex.try_lock() && logfiles[level].is_open()) { + logfiles[level].write(output.c_str(), output.length()); logfiles[level].flush(); logMutex.unlock(); } @@ -50,6 +65,7 @@ public: } private: std::map logfiles; + int console_level = LOGLEVEL_MIN; static const char* getTime() { time_t rawtime; diff --git a/src/main.cpp b/src/main.cpp index e57401c..009c0c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -33,198 +34,23 @@ struct ftpconn { bool close = false; } fdc[MAX_CLIENTS]; -void runClient(struct ftpconn* cfd) { - if (!cfd) { - logger->print(LOGLEVEL_ERROR, "Invalid connection handle"); - return; - } - - std::unique_lock lock(client_mutex); - if (!cfd->client) { - logger->print(LOGLEVEL_ERROR, "Invalid client handle"); - return; - } - - int client_sock = cfd->client->control_sock; - Client* client = cfd->client; - lock.unlock(); - - char inbuf[BUFFERSIZE]; - logger->print(LOGLEVEL_DEBUG, "C(%i) Client initialized", client_sock); - - while (true) { - memset(inbuf, 0, BUFFERSIZE); - - if (fcntl(client_sock, F_GETFD) < 0) { - logger->print(LOGLEVEL_DEBUG, "C(%i) Socket closed", client_sock); - break; - } - - struct timeval tv; - tv.tv_sec = 60; - tv.tv_usec = 0; - - fd_set readfds, writefds; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_SET(client_sock, &readfds); - - // Add socket to writefds if SSL wants to write - if (client->isSecure() && client->getSSL()) { - FD_SET(client_sock, &writefds); - } - - int select_result = select(client_sock + 1, &readfds, &writefds, NULL, &tv); - if (select_result < 0) { - if (errno == EINTR) continue; - logger->print(LOGLEVEL_ERROR, "C(%i) Select failed: %s", client_sock, strerror(errno)); - break; - } - - if (select_result == 0) { - logger->print(LOGLEVEL_DEBUG, "C(%i) Connection timeout", client_sock); - break; - } - - int rc; - if (client->isSecure() && client->getSSL()) { - if (!client->isHandshakeComplete()) { - // Continue SSL handshake - ERR_clear_error(); // Clear any previous errors - int ret = SSL_accept(client->getSSL()); - if (ret <= 0) { - int ssl_err = SSL_get_error(client->getSSL(), ret); - if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { - continue; // Need more data for handshake - } - unsigned long err = ERR_get_error(); - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - logger->print(LOGLEVEL_ERROR, "C(%i) SSL handshake failed with error: %d (%s)", - client_sock, ssl_err, err_buf); - break; - } - client->setHandshakeComplete(true); - logger->print(LOGLEVEL_DEBUG, "C(%i) SSL handshake completed", client_sock); - continue; - } else { - // Normal SSL read after handshake - ERR_clear_error(); // Clear any previous errors - rc = SSL_read(client->getSSL(), inbuf, sizeof(inbuf) - 1); - if (rc <= 0) { - int ssl_err = SSL_get_error(client->getSSL(), rc); - if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { - continue; - } - if (ssl_err == SSL_ERROR_SYSCALL) { - unsigned long err = ERR_get_error(); - if (err == 0 && rc == 0) { - logger->print(LOGLEVEL_DEBUG, "C(%i) SSL connection closed", client_sock); - } else { - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_read syscall error: %s", - client_sock, err_buf); - } - } else { - unsigned long err = ERR_get_error(); - char err_buf[256]; - ERR_error_string_n(err, err_buf, sizeof(err_buf)); - logger->print(LOGLEVEL_ERROR, "C(%i) SSL_read failed with error: %d (%s)", - client_sock, ssl_err, err_buf); - } - break; - } - } - } else { - rc = recv(client_sock, inbuf, sizeof(inbuf) - 1, 0); - if (rc <= 0) { - if (rc == 0) { - logger->print(LOGLEVEL_DEBUG, "C(%i) Client disconnected", client_sock); - } else { - logger->print(LOGLEVEL_ERROR, "C(%i) Recv failed: %s", client_sock, strerror(errno)); - } - break; - } - } - - inbuf[rc] = '\0'; - if (rc >= 2 && inbuf[rc-2] == '\r' && inbuf[rc-1] == '\n') { - rc -= 2; - inbuf[rc] = '\0'; - } - - std::string input(inbuf, rc); - logger->print(LOGLEVEL_DEBUG, "C(%i) >> %s", client_sock, input.c_str()); - - std::string::size_type space_pos = input.find(" "); - std::string cmd = space_pos != std::string::npos ? - toUpper(input.substr(0, space_pos)) : toUpper(input); - std::string args = space_pos != std::string::npos ? - input.substr(space_pos + 1) : ""; - - lock.lock(); - if (!cfd->client) { - lock.unlock(); - break; - } - - int revc = client->receive(cmd, args); - lock.unlock(); - - if (revc != 0) break; - } - - // Mark for cleanup - logger->print(LOGLEVEL_DEBUG, "C(%i) Client thread ending", client_sock); - cfd->close = true; -} - -void initializePlugins() { - std::string plugin_dir = config->getValue("core", "plugin_path", PLUGIN_DIR); - - auto& auth_manager = PluginManager::getInstance(); - auto& filer_manager = PluginManager::getInstance(); - - auth_manager.setLogger(logger); - filer_manager.setLogger(logger); - - // Try loading all plugins into both managers - for (const auto& entry : std::filesystem::directory_iterator(plugin_dir)) { - if (entry.path().extension() == ".so") { - logger->print(LOGLEVEL_DEBUG, "Loading plugin: %s", entry.path().c_str()); - auth_manager.loadPlugin(entry.path().string()); - filer_manager.loadPlugin(entry.path().string()); - } - } - - // Initialize auth - std::string auth_type = config->getValue("engines", "auth", "pam"); - auth = auth_manager.createPlugin(auth_type, config->get(auth_type)->get()); - - if (!auth) { - logger->print(LOGLEVEL_CRITICAL, "Failed to create auth engine: %s", auth_type.c_str()); - exit(1); - } - - // Initialize filer - std::string filer_type = config->getValue("engines", "filer", "local"); - default_filer_name = filer_type; - default_filer_factory = filer_manager.getFactory(filer_type); - - if (!default_filer_factory) { - logger->print(LOGLEVEL_CRITICAL, "Failed to get filer factory for type: %s", filer_type.c_str()); - exit(1); - } -} +void runClient(struct ftpconn* cfd); +void initializePlugins(); +void shutdown(int signum); int main(int argc , char *argv[]) { - printf("%s %s Copyright (C) 2024 Worlio LLC\n", APPNAME, VERSION); - printf("This program comes with ABSOLUTELY NO WARRANTY.\n"); - printf("This is free software, and you are welcome to redistribute it under certain conditions.\n\n"); + std::cout << + std::format( + "{} {} Maintained by Worlio LLC 2024-2025\n" + "This program comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under certain conditions.\n\n", + APPNAME, + VERSION + ); // SIGNALS signal(SIGPIPE, SIG_IGN); + std::signal(SIGINT, shutdown); config = new ConfigFile(concatPath(std::string(CONFIG_DIR), "ftp.conf")); server_name = config->getValue("core", "server_name", "%a %v"); @@ -249,17 +75,37 @@ int main(int argc , char *argv[]) { logger->openFileOnLevel(LOGLEVEL_ERROR, config->getValue("logging", "error", mainLogFile).c_str()); logger->openFileOnLevel(LOGLEVEL_CRITICAL, config->getValue("logging", "critical", mainLogFile).c_str()); + std::string console_loglevel_string = config->getValue("logging", "console", "all"); + if (console_loglevel_string == "all") + logger->setConsoleLevel(LOGLEVEL_MAX); + else if (console_loglevel_string == "debug") + logger->setConsoleLevel(LOGLEVEL_DEBUG); + else if (console_loglevel_string == "info") + logger->setConsoleLevel(LOGLEVEL_INFO); + else if (console_loglevel_string == "warning") + logger->setConsoleLevel(LOGLEVEL_WARNING); + else if (console_loglevel_string == "error") + logger->setConsoleLevel(LOGLEVEL_ERROR); + else if (console_loglevel_string == "critical") + logger->setConsoleLevel(LOGLEVEL_CRITICAL); + else if (console_loglevel_string == "none") + logger->setConsoleLevel(LOGLEVEL_MIN); + else { + logger->setConsoleLevel(LOGLEVEL_MIN); + logger->print(LOGLEVEL_ERROR, "Could not determine console log type."); + } + bool ssl_enable = config->getBool("net", "ssl", true); bool ssl_flags = SSL_OP_NO_TICKET; if (ssl_enable) { std::string cert_file = config->getValue("ssl", "certificate", "cert.pem"); if (cert_file[0] != '/') cert_file = concatPath(std::string(CONFIG_DIR), cert_file); - logger->print(LOGLEVEL_INFO, "Using certificate file: %s", cert_file.c_str()); + logger->print(LOGLEVEL_INFO, "Using certificate file: {}", cert_file); std::string key_file = config->getValue("ssl", "private_key", "key.pem"); if (key_file[0] != '/') key_file = concatPath(std::string(CONFIG_DIR), key_file); - logger->print(LOGLEVEL_INFO, "Using private key file: %s", key_file.c_str()); + logger->print(LOGLEVEL_INFO, "Using private key file: {}", key_file); if (!SSLManager::getInstance().initialize(cert_file, key_file)) { logger->print(LOGLEVEL_CRITICAL, "Failed to initialize SSL"); return 1; @@ -338,11 +184,11 @@ int main(int argc , char *argv[]) { if ((src = bind(master_socket, (struct sockaddr *)&ctrl_address, sizeof(ctrl_address))) < 0) { logger->print( LOGLEVEL_CRITICAL, - "Bind to %i.%i.%i.%i:%i failed", - &server_address[0], - &server_address[1], - &server_address[2], - &server_address[3], + "Bind to {}.{}.{}.{}:{} failed", + static_cast(server_address[0]), + static_cast(server_address[1]), + static_cast(server_address[2]), + static_cast(server_address[3]), server_port ); close(master_socket); @@ -381,7 +227,7 @@ int main(int argc , char *argv[]) { // Handle poll errors properly without skipping cleanup if (fds[i].revents != POLLIN) { if (fds[i].fd != master_socket) { - logger->print(LOGLEVEL_ERROR, "Poll error on fd %d", fds[i].fd); + logger->print(LOGLEVEL_ERROR, "net: poll error on fd {}", fds[i].fd); fdc[i].close = true; } } @@ -392,7 +238,7 @@ int main(int argc , char *argv[]) { newsock = accept(master_socket, NULL, NULL); if (newsock < 0) { if (errno != EWOULDBLOCK) { - logger->print(LOGLEVEL_ERROR, "accept() failed: %s", strerror(errno)); + logger->print(LOGLEVEL_ERROR, "net: accept() failed: {}", strerror(errno)); runServer = false; } break; @@ -417,7 +263,7 @@ int main(int argc , char *argv[]) { int flags = fcntl(newsock, F_GETFL, 0); fcntl(newsock, F_SETFL, flags | O_NONBLOCK); - logger->print(LOGLEVEL_DEBUG, "C(%i) Accepted client in slot %d", newsock, slot); + logger->print(LOGLEVEL_DEBUG, "client {} accepted in slot {}", newsock, slot); fds[slot].fd = newsock; fds[slot].events = POLLIN; @@ -439,7 +285,7 @@ int main(int argc , char *argv[]) { // Handle cleanup for any connections marked for closing if (fds[i].fd != master_socket && (fdc[i].close || fds[i].revents != POLLIN)) { int fd = fds[i].fd; - logger->print(LOGLEVEL_DEBUG, "C(%i) Cleaning up client in slot %d", fd, i); + logger->print(LOGLEVEL_DEBUG, "cleaning up client {} from slot {}", fd, i); // Close socket if (fd > 0) { @@ -464,8 +310,6 @@ int main(int argc , char *argv[]) { fds[i].revents = 0; memset(&fdc[i], 0, sizeof(struct ftpconn)); - logger->print(LOGLEVEL_DEBUG, "C(%i) Cleanup completed", fd); - // Recalculate nfds if needed if (i == nfds - 1) { for (int j = nfds - 1; j >= 0; j--) { @@ -478,21 +322,234 @@ int main(int argc , char *argv[]) { } } } + + logger->print(LOGLEVEL_INFO, "Server closing..."); + close(master_socket); + for (int i = 0; i < current_size; i++) { + if (fds[i].fd != master_socket) { + int fd = fds[i].fd; + + logger->print(LOGLEVEL_DEBUG, "disconnecting client {} from slot {}", fd, i); - // Cleanup - for (int i = 0; i < nfds; i++) { - if (fds[i].fd >= 0) { - close(fds[i].fd); - if (fdc[i].thread && fdc[i].thread->joinable()) { - fdc[i].thread->join(); + if (fd > 0) { + shutdown(fd, SHUT_RDWR); + close(fd); } - delete fdc[i].thread; + + if (fdc[i].thread) { + if (fdc[i].thread->joinable()) { + fdc[i].thread->join(); + } + delete fdc[i].thread; + } + // Clean up client delete fdc[i].client; + + // Reset slot + fds[i].fd = -1; + fds[i].events = 0; + fds[i].revents = 0; + memset(&fdc[i], 0, sizeof(struct ftpconn)); + + if (i == nfds - 1) { + for (int j = nfds - 1; j >= 0; j--) { + if (fds[j].fd != -1) { + nfds = j + 1; + break; + } + } + } } } - - close(master_socket); - logger->print(LOGLEVEL_INFO, "Server closing..."); logger->close(); return 0; } + +void initializePlugins() { + std::string plugin_dir = config->getValue("core", "plugin_path", PLUGIN_DIR); + + auto& auth_manager = PluginManager::getInstance(); + auto& filer_manager = PluginManager::getInstance(); + + auth_manager.setLogger(logger); + filer_manager.setLogger(logger); + + // Try loading all plugins into both managers + for (const auto& entry : std::filesystem::directory_iterator(plugin_dir)) { + if (entry.path().extension() == ".so") { + logger->print(LOGLEVEL_DEBUG, "Loading plugin: {}", entry.path().string()); + auth_manager.loadPlugin(entry.path().string()); + filer_manager.loadPlugin(entry.path().string()); + } + } + + // Initialize auth + std::string auth_type = config->getValue("engines", "auth", "pam"); + auth = auth_manager.createPlugin(auth_type, config->get(auth_type)->get()); + + if (!auth) { + logger->print(LOGLEVEL_CRITICAL, "Failed to create auth engine: {}", auth_type); + exit(1); + } + + // Initialize filer + std::string filer_type = config->getValue("engines", "filer", "local"); + default_filer_name = filer_type; + default_filer_factory = filer_manager.getFactory(filer_type); + + if (!default_filer_factory) { + logger->print(LOGLEVEL_CRITICAL, "Failed to get filer factory for type {}", filer_type); + exit(1); + } +} + +void runClient(struct ftpconn* cfd) { + if (!cfd) { + logger->print(LOGLEVEL_ERROR, "Invalid connection handle"); + return; + } + + std::unique_lock lock(client_mutex); + if (!cfd->client) { + logger->print(LOGLEVEL_ERROR, "Invalid client handle"); + return; + } + + int client_sock = cfd->client->control_sock; + Client* client = cfd->client; + lock.unlock(); + + char inbuf[BUFFERSIZE]; + logger->print(LOGLEVEL_DEBUG, "client {} initialized", client_sock); + + while (!cfd->close) { + memset(inbuf, 0, BUFFERSIZE); + + if (fcntl(client_sock, F_GETFD) < 0) { + logger->print(LOGLEVEL_DEBUG, "closed client {} socket", client_sock); + break; + } + + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; + + fd_set readfds, writefds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(client_sock, &readfds); + + // Add socket to writefds if SSL wants to write + if (client->isSecure() && client->getSSL()) { + FD_SET(client_sock, &writefds); + } + + int select_result = select(client_sock + 1, &readfds, &writefds, NULL, &tv); + if (select_result < 0) { + if (errno == EINTR) continue; + logger->print(LOGLEVEL_ERROR, "client {} experienced select fail: %s", client_sock, strerror(errno)); + break; + } + + if (select_result == 0) { + logger->print(LOGLEVEL_INFO, "client {} timeout", client_sock); + break; + } + + int rc; + if (client->isSecure() && client->getSSL()) { + if (!client->isHandshakeComplete()) { + // Continue SSL handshake + ERR_clear_error(); // Clear any previous errors + int ret = SSL_accept(client->getSSL()); + if (ret <= 0) { + int ssl_err = SSL_get_error(client->getSSL(), ret); + if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { + continue; // Need more data for handshake + } + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + logger->print(LOGLEVEL_ERROR, "client {} SSL handshake failed with error: {} ({})", + client_sock, ssl_err, err_buf); + break; + } + client->setHandshakeComplete(true); + logger->print(LOGLEVEL_DEBUG, "client {} SSL handshake completed", client_sock); + continue; + } else { + // Normal SSL read after handshake + ERR_clear_error(); // Clear any previous errors + rc = SSL_read(client->getSSL(), inbuf, sizeof(inbuf) - 1); + if (rc <= 0) { + int ssl_err = SSL_get_error(client->getSSL(), rc); + if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { + continue; + } + if (ssl_err == SSL_ERROR_SYSCALL) { + unsigned long err = ERR_get_error(); + if (err == 0 && rc == 0) { + logger->print(LOGLEVEL_DEBUG, "client {} SSL connection closed", client_sock); + } else { + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + logger->print(LOGLEVEL_ERROR, "client {} experienced SSL_read syscall error: {}", + client_sock, err_buf); + } + } else { + unsigned long err = ERR_get_error(); + char err_buf[256]; + ERR_error_string_n(err, err_buf, sizeof(err_buf)); + logger->print(LOGLEVEL_ERROR, "client {} experienced SSL_read error: {} ({})", + client_sock, ssl_err, err_buf); + } + break; + } + } + } else { + rc = recv(client_sock, inbuf, sizeof(inbuf) - 1, 0); + if (rc <= 0) { + if (rc == 0) { + logger->print(LOGLEVEL_DEBUG, "client {} disconnected", client_sock); + } else { + logger->print(LOGLEVEL_ERROR, "recieve from client {} failed: {}", client_sock, strerror(errno)); + } + break; + } + } + + inbuf[rc] = '\0'; + if (rc >= 2 && inbuf[rc-2] == '\r' && inbuf[rc-1] == '\n') { + rc -= 2; + inbuf[rc] = '\0'; + } + + std::string input(inbuf, rc); + logger->print(LOGLEVEL_DEBUG, "recieved from client {}: {}", client_sock, input); + + std::string::size_type space_pos = input.find(" "); + std::string cmd = space_pos != std::string::npos ? + toUpper(input.substr(0, space_pos)) : toUpper(input); + std::string args = space_pos != std::string::npos ? + input.substr(space_pos + 1) : ""; + + lock.lock(); + if (!cfd->client) { + lock.unlock(); + break; + } + + int revc = client->receive(cmd, args); + lock.unlock(); + + if (revc != 0) break; + } + + // Mark for cleanup + logger->print(LOGLEVEL_DEBUG, "client {} thread ending", client_sock); + cfd->close = true; +} + +void shutdown(int signum) { + runServer = false; +} \ No newline at end of file diff --git a/src/plugin.h b/src/plugin.h index 28edb01..940d2c5 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -2,6 +2,7 @@ #define PLUGIN_H #include +#include #include "logger.h" typedef int (*GetAPIVersionFunc)(); @@ -11,7 +12,7 @@ typedef const char* (*GetPluginTypeFunc)(); class IPlugin { public: virtual ~IPlugin() = default; - static void setLogger(Logger* log) { logger = log; } + static void setLogger(Logger* _logger) { logger = _logger; } protected: static Logger* logger; }; @@ -44,7 +45,7 @@ struct PluginTraits { const char* getPluginDescription() { return description; } \ const char* getPluginVersion() { return version; } \ int getAPIVersion() { return PluginTraits::API_VERSION; } \ - void setLogger(Logger* log) { IPlugin::setLogger(log); } \ + void setLogger(Logger* _logger) { IPlugin::setLogger(_logger); } \ } #endif \ No newline at end of file diff --git a/src/plugin_manager.h b/src/plugin_manager.h index 0915aaf..f3fe517 100644 --- a/src/plugin_manager.h +++ b/src/plugin_manager.h @@ -32,15 +32,15 @@ public: if (plugin) plugin->initialize(config); return plugin; } - if (logger) logger->print(LOGLEVEL_ERROR, "Plugin type '%s' not found", type.c_str()); + if (logger) logger->print(LOGLEVEL_ERROR, "plugin type '{}' not found", type); return nullptr; } bool loadPlugin(const std::string& path) { void* handle = dlopen(path.c_str(), RTLD_LAZY); if (!handle) { - if (logger) logger->print(LOGLEVEL_ERROR, "Failed to load plugin %s: %s", - path.c_str(), dlerror()); + if (logger) logger->print(LOGLEVEL_ERROR, "failed to load {}: {}", + path, dlerror()); return false; } @@ -77,7 +77,7 @@ public: // Check if already loaded if (plugins.find(plugin_name) != plugins.end()) { - if (logger) logger->print(LOGLEVEL_DEBUG, "Plugin %s is already loaded", plugin_name.c_str()); + if (logger) logger->print(LOGLEVEL_DEBUG, "Plugin {} is already loaded", plugin_name); dlclose(handle); return true; } @@ -87,9 +87,8 @@ public: get_api_version, setLogger, handle); plugins[plugin_name] = plugin_info; - if (logger) logger->print(LOGLEVEL_INFO, "Loaded plugin: %s v%s (%s interface)", - plugin_name.c_str(), plugin_info.version.c_str(), - PluginTraits::interfaceName()); + if (logger) logger->print(LOGLEVEL_INFO, "Loaded {} plugin: {} v{}", + PluginTraits::interfaceName(), plugin_name, plugin_info.version); return true; } @@ -98,14 +97,13 @@ public: if (it != plugins.end()) { return it->second.create; } - if (logger) logger->print(LOGLEVEL_ERROR, "Plugin type '%s' not found", type.c_str()); + if (logger) logger->print(LOGLEVEL_ERROR, "plugin type '{}' not found", type); return nullptr; } void unloadPlugins() { - if (logger) logger->print(LOGLEVEL_DEBUG, "Unloading all plugins"); for (auto& pair : plugins) { - if (logger) logger->print(LOGLEVEL_DEBUG, "Unloading plugin: %s", pair.first.c_str()); + if (logger) logger->print(LOGLEVEL_DEBUG, "unloading plugin: {}", pair.first); dlclose(pair.second.handle); } plugins.clear(); @@ -139,7 +137,7 @@ private: !get_name ? "getPluginName" : !get_desc ? "getPluginDescription" : "getPluginVersion"; - logger->print(LOGLEVEL_ERROR, "Missing required function: %s", missing); + logger->print(LOGLEVEL_ERROR, "missing required function: {}", missing); } return false; } @@ -150,8 +148,8 @@ private: if (version != PluginTraits::API_VERSION) { if (logger) { logger->print(LOGLEVEL_ERROR, - "Incompatible plugin API version in %s (got %d, expected %d)", - path.c_str(), version, PluginTraits::API_VERSION); + "incompatible plugin API version in {} (got {}, expected {})", + path, version, PluginTraits::API_VERSION); } return false; } diff --git a/src/plugins/auth_pam/auth_pam.cpp b/src/plugins/auth_pam/auth_pam.cpp index f24d9db..b6e5067 100644 --- a/src/plugins/auth_pam/auth_pam.cpp +++ b/src/plugins/auth_pam/auth_pam.cpp @@ -63,7 +63,7 @@ public: virtual bool authenticate(ClientAuthDetails* auth_data) override { if (!auth_data || !auth_data->username[0] || !auth_data->password[0]) { - logger->print(LOGLEVEL_ERROR, "auth_pam: Cannot use empty auth data"); + logger->print(LOGLEVEL_ERROR, "cannot use empty auth data"); return false; } @@ -76,7 +76,7 @@ public: // Start PAM session int retval = pam_start(service_name.c_str(), auth_data->username, &conv, &pamh); if (retval != PAM_SUCCESS) { - logger->print(LOGLEVEL_ERROR, "auth_pam: Failed to start PAM: %s", + logger->print(LOGLEVEL_ERROR, "failed to start PAM: {}", pamh ? pam_strerror(pamh, retval) : "Unknown error"); if (pamh) pam_end(pamh, retval); return false; @@ -85,7 +85,7 @@ public: // Authenticate user retval = pam_authenticate(pamh, 0); if (retval != PAM_SUCCESS) { - logger->print(LOGLEVEL_ERROR, "auth_pam: Authentication failed: %s", + logger->print(LOGLEVEL_ERROR, "authentication failed: {}", pam_strerror(pamh, retval)); pam_end(pamh, retval); return false; @@ -94,7 +94,7 @@ public: // Check account validity retval = pam_acct_mgmt(pamh, 0); if (retval != PAM_SUCCESS) { - logger->print(LOGLEVEL_ERROR, "auth_pam: Account validation failed: %s", + logger->print(LOGLEVEL_ERROR, "account validation failed: {}", pam_strerror(pamh, retval)); pam_end(pamh, retval); return false; @@ -106,7 +106,7 @@ public: // Get user info struct passwd* pw = getpwnam(auth_data->username); if (!pw) { - logger->print(LOGLEVEL_ERROR, "auth_pam: Failed to get user info for %s", + logger->print(LOGLEVEL_ERROR, "failed to get user info for {}", auth_data->username); return false; } diff --git a/src/plugins/filer_local/filer_local.cpp b/src/plugins/filer_local/filer_local.cpp index c4554b5..ab18bf4 100644 --- a/src/plugins/filer_local/filer_local.cpp +++ b/src/plugins/filer_local/filer_local.cpp @@ -43,10 +43,9 @@ public: cwd = "/"; } catch (const fs::filesystem_error& ex) { - logger->print(LOGLEVEL_ERROR, "setRoot error: %s", ex.what()); + logger->print(LOGLEVEL_ERROR, "setRoot error: {}", ex.what()); fd.error = {FilerStatusCodes::Exception, ex.what()}; } - return fd; } @@ -68,7 +67,7 @@ public: cwd = new_cwd; } catch (const fs::filesystem_error& ex) { - logger->print(LOGLEVEL_ERROR, "setCWD error: %s", ex.what()); + logger->print(LOGLEVEL_ERROR, "setCWD error: {}", ex.what()); fd.error = {FilerStatusCodes::Exception, ex.what()}; } @@ -105,16 +104,16 @@ public: *target = fs::path(*target).lexically_normal(); if (fs::is_symlink(*target) && resolve_symlink) { fs::path resolved_sym = fs::weakly_canonical(*target); - logger->print(LOGLEVEL_DEBUG, "Resolved symlink path: %s", resolved_sym.c_str()); + logger->print(LOGLEVEL_DEBUG, "resolved symlink path: {}", resolved_sym.string()); return resolve_ext_symlinks ? true : resolved_sym.string().starts_with(root.string()); } else if (!(*target).string().starts_with(root.string())) return false; - logger->print(LOGLEVEL_DEBUG, "Resolved path: %s", target->c_str()); + logger->print(LOGLEVEL_DEBUG, "resolved path: {}", target->string()); return true; } catch (const std::exception& e) { - logger->print(LOGLEVEL_ERROR, "Path resolution error: %s", e.what()); + logger->print(LOGLEVEL_ERROR, "path resolution error: {}", e.what()); return false; } } @@ -151,7 +150,7 @@ public: fd.path = strdup(requested_path.c_str()); fd.relpath = strdup(fs::relative(requested_path, root).c_str()); } catch (const fs::filesystem_error& ex) { - logger->print(LOGLEVEL_ERROR, "Path fallback: %s", ex.what()); + logger->print(LOGLEVEL_ERROR, "traversal error: {}", ex.what()); fd.error = file_error{FilerStatusCodes::Exception, ex.what()}; } return fd;