├── server ├── data │ └── user.db.example ├── security │ ├── server.crt.example │ ├── server.key.example │ └── openssl.cnf ├── include │ ├── ssl_utils.hpp │ ├── file_utils.hpp │ ├── db.hpp │ ├── crypto.hpp │ ├── threadpool.hpp │ └── handlers.hpp ├── CMakeLists.txt └── src │ ├── threadpool.cpp │ ├── file_utils.cpp │ ├── ssl_utils.cpp │ ├── db.cpp │ ├── crypto.cpp │ ├── main.cpp │ └── handlers.cpp ├── client ├── security │ └── server.crt.example ├── include │ ├── ssl_utils.hpp │ ├── bio_utils.hpp │ ├── progressbar.hpp │ └── handlers.hpp ├── CMakeLists.txt └── src │ ├── progressbar.cpp │ ├── ssl_utils.cpp │ ├── bio_utils.cpp │ ├── main.cpp │ └── handlers.cpp ├── .gitignore ├── README.md └── test └── integration ├── diff2ConcurrentUsers └── test.py └── sameConcurrentUsers └── test.py /server/data/user.db.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/security/server.crt.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/security/server.crt.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/security/server.key.example: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | store/ 2 | .vscode/ 3 | build/ 4 | 5 | server.key 6 | server.csr 7 | server.crt 8 | 9 | *.db 10 | *.log 11 | benchmark.md -------------------------------------------------------------------------------- /client/include/ssl_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SSL_UTILS_HPP 2 | #define SSL_UTILS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace ssl { 10 | 11 | SSL_CTX *create_SSLctx(); 12 | 13 | } // namespace ssl 14 | 15 | #endif -------------------------------------------------------------------------------- /server/include/ssl_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SSL_UTILS_HPP 2 | #define SSL_UTILS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | 10 | namespace ssl { 11 | 12 | SSL_CTX *create_SSLctx(const char *cert_file, const char *key_file); 13 | 14 | } // namespace ssl 15 | #endif -------------------------------------------------------------------------------- /client/include/bio_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BIO_UTILS_HPP 2 | #define BIO_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | // #include 8 | 9 | namespace bio { 10 | BIO *create_socket_bio(const char *hostname, const char *port, int family); 11 | } 12 | 13 | #endif -------------------------------------------------------------------------------- /server/security/openssl.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = req_distinguished_name 3 | x509_extensions = v3_req 4 | prompt = no 5 | 6 | [req_distinguished_name] 7 | C = IN 8 | ST = Odisha 9 | L = Bhubaneswar 10 | O = KIIT 11 | CN = 127.0.0.1 12 | 13 | [v3_req] 14 | subjectAltName = @alt_names 15 | 16 | [alt_names] 17 | IP.1 = 127.0.0.1 18 | -------------------------------------------------------------------------------- /server/include/file_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace utils { 12 | bool isRoot(); 13 | std::string ls(std::string directory); 14 | bool ensureDirectory(const std::string& path); 15 | off_t getFilesize(int filefd); 16 | } // namespace utils 17 | 18 | #endif -------------------------------------------------------------------------------- /server/include/db.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DB_HPP 2 | #define DB_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | class Database { 10 | private: 11 | sqlite3* db; 12 | 13 | public: 14 | explicit Database(const std::string& dbPath); 15 | ~Database(); 16 | bool initTable(); 17 | bool executeQuery(const std::string& query); 18 | bool registerUser(const std::string& username, const std::string& plainPassword); 19 | bool userExists(const std::string& username); 20 | std::string getHashedPassword(const std::string& username); 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(FTP-client VERSION 1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 9 | 10 | include_directories(include) 11 | 12 | set(SOURCES 13 | src/main.cpp 14 | src/handlers.cpp 15 | src/progressbar.cpp 16 | src/ssl_utils.cpp 17 | src/bio_utils.cpp 18 | ) 19 | 20 | add_executable(client ${SOURCES}) 21 | 22 | # Link libraries 23 | find_package(ZLIB REQUIRED) 24 | find_package(OpenSSL REQUIRED) 25 | 26 | target_link_libraries(client 27 | PRIVATE 28 | ZLIB::ZLIB 29 | OpenSSL::SSL 30 | OpenSSL::Crypto 31 | ) -------------------------------------------------------------------------------- /client/include/progressbar.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PROGRESSBAR_HPP 2 | #define PROGRESSBAR_HPP 3 | 4 | #include 5 | #include 6 | 7 | class ProgressBar { 8 | private: 9 | int totalSteps; 10 | int barWidth; 11 | char barChar; 12 | char emptyChar; 13 | int currentStep; 14 | 15 | public: 16 | ProgressBar(int totalSteps, int barWidth = 50, char barChar = '#', char emptyChar = ' ') : totalSteps(totalSteps), barWidth(barWidth), barChar(barChar), emptyChar(emptyChar), currentStep(currentStep) { 17 | if (totalSteps <= 0) { 18 | throw std::invalid_argument("total currentSteps must be > 0"); 19 | } 20 | } 21 | void print(); 22 | void increment(); 23 | void update(int currentStep); 24 | }; 25 | 26 | #endif -------------------------------------------------------------------------------- /server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(FTP-server VERSION 1.0 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 9 | 10 | include_directories(include) 11 | 12 | set(SOURCES 13 | src/main.cpp 14 | src/handlers.cpp 15 | src/file_utils.cpp 16 | src/threadpool.cpp 17 | src/ssl_utils.cpp 18 | src/db.cpp 19 | src/crypto.cpp 20 | ) 21 | 22 | add_executable(server ${SOURCES}) 23 | 24 | # Link libraries 25 | find_package(ZLIB REQUIRED) 26 | find_package(OpenSSL REQUIRED) 27 | find_package(SQLite3 REQUIRED) 28 | 29 | target_link_libraries(server 30 | PRIVATE 31 | ZLIB::ZLIB 32 | OpenSSL::SSL 33 | OpenSSL::Crypto 34 | SQLite::SQLite3 35 | ) 36 | -------------------------------------------------------------------------------- /client/src/progressbar.cpp: -------------------------------------------------------------------------------- 1 | #include "progressbar.hpp" 2 | 3 | // #include 4 | 5 | void ProgressBar::print() { 6 | int progress = static_cast((static_cast(currentStep) / totalSteps) * barWidth); 7 | int percentage = static_cast((static_cast(currentStep) / totalSteps) * 100); 8 | 9 | std::string bar(progress, barChar); 10 | std::string empty(barWidth - progress, emptyChar); 11 | 12 | std::cout << "\r[" << bar << empty << "] " << percentage << "%" << std::flush; 13 | } 14 | 15 | void ProgressBar::increment() { 16 | if (currentStep < totalSteps) { 17 | ++currentStep; 18 | print(); 19 | } 20 | } 21 | void ProgressBar::update(int currentStep) { 22 | if (currentStep < 0 || currentStep > totalSteps) { 23 | throw std::out_of_range("currentSteps must be >= 0 and <= totalSteps"); 24 | } 25 | this->currentStep = currentStep; 26 | print(); 27 | } -------------------------------------------------------------------------------- /server/include/crypto.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CRYPTO_HPP 2 | #define CRYPTO_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class Crypto { 12 | private: 13 | const int SALT_LENGTH = 32; 14 | const int HASH_LENGTH = 32; 15 | const int ITERATIONS = 100000; 16 | 17 | std::vector generateSalt(); 18 | std::vector pbkdf2(const std::string& password, const std::vector& salt); 19 | std::string toHex(const std::vector& data); 20 | std::vector toBinary(const std::string& data); 21 | std::string encodeResult(const std::vector& salt, const std::vector& hash); 22 | std::pair decodeResult(const std::string& storedHash); // 23 | 24 | public: 25 | std::string hashPassword(const std::string& password); 26 | bool verifyPasswords(const std::string& storedHash, const std::string& plainPassword); 27 | }; 28 | 29 | #endif -------------------------------------------------------------------------------- /server/src/threadpool.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/threadpool.hpp" 2 | 3 | Threadpool::Threadpool(int numThreads) : numThreads(numThreads), stop(false) { 4 | if (numThreads <= 0) { 5 | throw std::invalid_argument("No. of threads must be >= 0"); 6 | } 7 | for (int i = 0; i < numThreads; i++) { 8 | threads.emplace_back([this]() { 9 | std::function task; 10 | while (1) { 11 | std::unique_lock lock(mtx); 12 | cv.wait(lock, [this]() { return !tasks.empty() || stop; }); 13 | 14 | if (stop && tasks.empty()) { 15 | return; 16 | } 17 | task = std::move(tasks.front()); 18 | tasks.pop(); 19 | 20 | lock.unlock(); 21 | task(); 22 | } 23 | }); 24 | } 25 | std::cout << "Threadpool created\n"; 26 | } 27 | 28 | Threadpool::~Threadpool() { 29 | std::unique_lock lock(mtx); 30 | stop = true; 31 | lock.unlock(); 32 | cv.notify_all(); 33 | 34 | for (auto& t : threads) { 35 | t.join(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/include/handlers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HANDLERS_HPP 2 | #define HANDLERS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | // #include 8 | #include 9 | // #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | // #include 16 | #include 17 | 18 | #include 19 | // #include 20 | #include 21 | #include 22 | #include 23 | 24 | // Contains at least one lowercase letter, one uppercase letter, one digit, and is at least 8 characters long 25 | const std::regex passwordRegex("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>])[a-zA-Z\\d!@#$%^&*(),.?\":{}|<>]{8,}$"); 26 | 27 | // Contains only alphanumeric characters, underscores, and hyphens 28 | const std::regex usernameRegex("^[a-zA-Z0-9._-]+$"); 29 | 30 | namespace handlers { 31 | void serverHandler(SSL *ssl); 32 | void registerUser(SSL *ssl); 33 | bool checkLoggedIn(SSL *ssl); 34 | void loginUser(SSL *ssl); 35 | void listFiles(SSL *ssl); 36 | void uploadFile(SSL *ssl); 37 | void downloadFile(SSL *ssl); 38 | void renameFile(SSL *ssl); 39 | void deleteFile(SSL *ssl); 40 | 41 | } // namespace handlers 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /client/src/ssl_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/ssl_utils.hpp" 2 | 3 | namespace ssl { 4 | 5 | SSL_CTX *create_SSLctx() { 6 | SSL_CTX *ctx = NULL; 7 | ctx = SSL_CTX_new(TLS_client_method()); 8 | if (!ctx) { 9 | std::cerr << "Unable to create SSL_CTX\n"; 10 | SSL_CTX_free(ctx); 11 | exit(EXIT_FAILURE); 12 | } 13 | 14 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); 15 | // SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); 16 | 17 | // if (!SSL_CTX_set_default_verify_paths(ctx)) { 18 | // std::cerr << "Failed to set the default trusted certificate store\n"; 19 | // SSL_CTX_free(ctx); 20 | // exit(EXIT_FAILURE); 21 | // } 22 | 23 | //if (!SSL_CTX_load_verify_locations(ctx, "/home/sid/projects/chatapp/client/security/server.crt", NULL)) { 24 | if (!SSL_CTX_load_verify_locations(ctx, "../security/server.crt", NULL)) { 25 | std::cerr << "Failed to load server certificate as a trusted certificate\n"; 26 | SSL_CTX_free(ctx); 27 | exit(EXIT_FAILURE); 28 | } 29 | 30 | if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { 31 | std::cerr << "Failed to set the minimum TLS protocol version\n"; 32 | SSL_CTX_free(ctx); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | return ctx; 37 | } 38 | 39 | } // namespace ssl 40 | -------------------------------------------------------------------------------- /server/include/threadpool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_HPP 2 | #define THREADPOOL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class Threadpool { 14 | private: 15 | int numThreads; 16 | std::vector threads; 17 | std::queue> tasks; 18 | std::mutex mtx; 19 | std::condition_variable cv; 20 | bool stop; 21 | 22 | public: 23 | explicit Threadpool(int numThreads); 24 | ~Threadpool(); 25 | 26 | template 27 | auto enqueueTask(F&& func, Args&&... args) -> std::future { 28 | using returnType = decltype(func(args...)); 29 | using taskType = std::packaged_task; 30 | auto boundTask = std::bind(std::forward(func), std::forward(args)...); 31 | auto task = std::make_shared(std::move(boundTask)); 32 | 33 | std::future result = task->get_future(); 34 | 35 | std::unique_lock lock(mtx); 36 | tasks.emplace([task]() -> void { (*task)(); }); 37 | 38 | lock.unlock(); 39 | cv.notify_one(); 40 | return result; 41 | } 42 | }; 43 | 44 | #endif -------------------------------------------------------------------------------- /client/src/bio_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/bio_utils.hpp" 2 | 3 | namespace bio { 4 | BIO *create_socket_bio(const char *hostname, const char *port, int family) { 5 | BIO_ADDRINFO *res = NULL; 6 | 7 | // Lookup IP address info for the server 8 | if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_STREAM, 0, &res)) return NULL; 9 | 10 | int sock = -1; 11 | const BIO_ADDRINFO *ai = NULL; 12 | 13 | for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { 14 | sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_STREAM, 0, 0); 15 | if (sock == -1) continue; 16 | 17 | /* Connect the socket to the server's address */ 18 | if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), BIO_SOCK_NODELAY)) { 19 | ERR_print_errors_fp(stderr); 20 | BIO_closesocket(sock); 21 | sock = -1; 22 | continue; 23 | } 24 | 25 | /* We have a connected socket so break out of the loop */ 26 | break; 27 | } 28 | 29 | BIO_ADDRINFO_free(res); 30 | 31 | if (sock == -1) return NULL; 32 | 33 | /* Create a BIO to wrap the socket */ 34 | BIO *bio = BIO_new(BIO_s_socket()); 35 | if (bio == NULL) { 36 | ERR_print_errors_fp(stderr); 37 | BIO_closesocket(sock); 38 | return NULL; 39 | } 40 | 41 | // BIO_CLOSE ensures the socket will be automatically closed when the BIO is freed 42 | BIO_set_fd(bio, sock, BIO_CLOSE); 43 | 44 | return bio; 45 | } 46 | 47 | } // namespace bio -------------------------------------------------------------------------------- /server/src/file_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/file_utils.hpp" 2 | 3 | namespace utils { 4 | 5 | bool isRoot() { 6 | if (getuid() != 0) { 7 | return false; 8 | } else { 9 | return true; 10 | } 11 | } 12 | 13 | std::string ls(std::string directory) { 14 | DIR* d = opendir(directory.c_str()); 15 | struct dirent* dir; 16 | std::string files; 17 | 18 | if (d) { 19 | while ((dir = readdir(d)) != NULL) { 20 | if (strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0) { 21 | std::string filename = dir->d_name; 22 | if ((filename.size() > 3) && (filename.substr(filename.size() - 3) == ".gz")) { 23 | filename = filename.substr(0, filename.size() - 3); 24 | } 25 | files += filename; 26 | files += "\n"; 27 | } 28 | } 29 | closedir(d); 30 | } 31 | 32 | return files; 33 | } 34 | 35 | bool ensureDirectory(const std::string& path) { 36 | struct stat st; 37 | memset(&st, 0, sizeof(st)); 38 | 39 | if (stat(path.c_str(), &st) == -1) { 40 | if (mkdir(path.c_str(), 0700) == -1) { 41 | std::cerr << "Failed to create directory " << path << ": " << strerror(errno) << "\n"; 42 | return false; 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | off_t getFilesize(int filefd) { 49 | off_t filesize = lseek(filefd, 0, SEEK_END); 50 | lseek(filefd, 0, SEEK_SET); 51 | return filesize; 52 | } 53 | 54 | 55 | } // namespace utils -------------------------------------------------------------------------------- /server/include/handlers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HANDLERS_HPP 2 | #define HANDLERS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | // #include 14 | // #include 15 | // #include 16 | #include 17 | 18 | // #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | // #include 24 | 25 | // Contains at least one lowercase letter, one uppercase letter, one digit, and is at least 8 characters long 26 | const std::regex passwordRegex("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>])[a-zA-Z\\d!@#$%^&*(),.?\":{}|<>]{8,}$"); 27 | 28 | // Contains only alphanumeric characters, underscores, and hyphens 29 | const std::regex usernameRegex("^[a-zA-Z0-9._-]+$"); 30 | 31 | class clientHandler { 32 | private: 33 | SSL* ssl; 34 | bool isLoggedin; 35 | std::string username; 36 | std::string folderdir; 37 | std::string client_ip; 38 | int client_port; 39 | static std::unordered_map folderLocks; 40 | 41 | void clientInfo(); 42 | void sendLoggedInStatus(); 43 | 44 | public: 45 | clientHandler(SSL* ssl, const std::string client_ip, int client_port) : ssl(ssl), client_ip(client_ip), client_port(client_port), isLoggedin(false){}; 46 | void handler(); 47 | void registerUser(); 48 | void loginUser(); 49 | void listFiles(); 50 | void uploadFile(); 51 | void downloadFile(); 52 | void renameFile(); 53 | void deleteFile(); 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /server/src/ssl_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/ssl_utils.hpp" 2 | 3 | static const char *CACHE_ID = "OpenSSL Demo Server"; 4 | 5 | namespace ssl { 6 | 7 | SSL_CTX *create_SSLctx(const char *cert_file, const char *key_file) { 8 | SSL_CTX *ctx = NULL; 9 | ctx = SSL_CTX_new(TLS_server_method()); 10 | if (!ctx) { 11 | std::cerr << "Unable to create SSL_CTX\n"; 12 | ERR_print_errors_fp(stderr); 13 | SSL_CTX_free(ctx); 14 | exit(EXIT_FAILURE); 15 | } 16 | 17 | if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { 18 | std::cerr << "Failed to set the minimum TLS protocol version\n"; 19 | ERR_print_errors_fp(stderr); 20 | SSL_CTX_free(ctx); 21 | exit(EXIT_FAILURE); 22 | } 23 | 24 | // if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { 25 | // std::cerr << "Failed to load the server certificate chain file\n"; 26 | // SSL_CTX_free(ctx); 27 | // exit(EXIT_FAILURE); 28 | // } 29 | if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) <= 0) { 30 | std::cerr << "Failed to load certificate file\n"; 31 | ERR_print_errors_fp(stderr); 32 | SSL_CTX_free(ctx); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) { 37 | std::cerr << "Error loading server private key file\n"; 38 | ERR_print_errors_fp(stderr); 39 | SSL_CTX_free(ctx); 40 | exit(EXIT_FAILURE); 41 | } 42 | 43 | SSL_CTX_set_session_id_context(ctx, reinterpret_cast(CACHE_ID), sizeof(CACHE_ID)); 44 | SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); // enables server-side session caching 45 | 46 | SSL_CTX_sess_set_cache_size(ctx, 1024); // how many client TLS sessions to cache 47 | 48 | SSL_CTX_set_timeout(ctx, 3600); // sessions older than this are considered a cache miss even if still in the cache 49 | 50 | SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); // client cert auth not needed 51 | 52 | return ctx; 53 | } 54 | 55 | } // namespace ssl 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Preview 2 | https://github.com/user-attachments/assets/9bb2322e-b213-4fac-a544-2d6d4a10ce91 3 | 4 | --- 5 | 6 | ## Dependencies 7 | Ensure the following libraries are installed: 8 | 9 | ### OpenSSL (For Secure FTP using SSL/TLS protocol and PBKDF2 hashing) 10 | ```bash 11 | sudo apt-get install openssl libssl-dev 12 | ``` 13 | 14 | ### Zlib (For Gzip compression/decompression) 15 | ```bash 16 | sudo apt install zlib1g zlib1g-dev 17 | ``` 18 | 19 | ### SQLite3 (For storing user information) 20 | ```bash 21 | sudo apt install sqlite3 libsqlite3-dev 22 | ``` 23 | 24 | ### build-essential (Required build tools) 25 | ```bash 26 | sudo apt install build-essential 27 | ``` 28 | ### Cmake (For building the project) 29 | ```bash 30 | sudo apt install cmake 31 | ``` 32 | 33 | --- 34 | 35 | ## Generating SSL Certificate and Key for the Server 36 | 37 | 1. Navigate to the `/security` directory inside the server folder: 38 | ```bash 39 | cd server/security 40 | ``` 41 | 42 | 2. Generate a self-signed SSL certificate and private key: 43 | ```bash 44 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ 45 | -keyout server.key -out server.crt -config openssl.cnf 46 | ``` 47 | 48 | 3. Copy the generated certificate (`server.crt`) to the client’s `/security` directory: 49 | ```bash 50 | cp server.crt ../../client/security 51 | ``` 52 | 53 | --- 54 | 55 | ## Build Instructions 56 | 57 | ### Build the Server 58 | 1. Navigate to the server directory: 59 | ```bash 60 | cd server 61 | ``` 62 | 63 | 2. Create a build directory and compile: 64 | ```bash 65 | mkdir -p build 66 | cd build 67 | cmake .. 68 | cmake --build . 69 | ``` 70 | 71 | 3. Run the server: 72 | ```bash 73 | sudo ./bin/server 74 | ``` 75 | **Example:** 76 | ```bash 77 | sudo ./bin/server 8000 78 | ``` 79 | 80 | ### Build the Client 81 | 1. Navigate to the client directory: 82 | ```bash 83 | cd client 84 | ``` 85 | 86 | 2. Create a build directory and compile: 87 | ```bash 88 | mkdir -p build 89 | cd build 90 | cmake .. 91 | cmake --build . 92 | ``` 93 | 94 | 3. Run the client: 95 | ```bash 96 | ./bin/client 97 | ``` 98 | **Example:** 99 | ```bash 100 | ./bin/client 127.0.0.1 8000 101 | ``` 102 | 103 | --- 104 | 105 | ## Debugging with Valgrind 106 | 107 | Valgrind can be used to check memory leaks and runtime errors. 108 | 109 | ### Debugging the Server 110 | ```bash 111 | sudo valgrind --leak-check=full --track-origins=yes ./bin/server 112 | ``` 113 | 114 | ### Debugging the Client 115 | ```bash 116 | valgrind --leak-check=full --track-origins=yes ./bin/client 117 | ``` 118 | 119 | --- 120 | 121 | ## Example Usage 122 | ### Start the Server 123 | ```bash 124 | sudo ./bin/server 8000 125 | ``` 126 | 127 | ### Start the Client 128 | ```bash 129 | ./bin/client 127.0.0.1 8000 130 | -------------------------------------------------------------------------------- /test/integration/diff2ConcurrentUsers/test.py: -------------------------------------------------------------------------------- 1 | # Correct 2 | 3 | # 2 different clients form differnet ip/port operating concurrently 4 | 5 | import threading 6 | import subprocess 7 | import time 8 | 9 | def run_client_operations(port, operations, log_file): 10 | with open(log_file, "w") as log: 11 | process = subprocess.Popen(["../../../client/build/bin/client", "127.0.0.1", str(port)], 12 | stdin=subprocess.PIPE, 13 | stdout=log, 14 | stderr=log, 15 | text=True) 16 | try: 17 | for operation in operations: 18 | process.stdin.write(f"{operation}\n") 19 | process.stdin.flush() 20 | # time.sleep(1) # Delay to simulate user input 21 | process.stdin.write("8\n") # Exit 22 | process.stdin.flush() 23 | process.communicate() # Wait for the process to finish 24 | except Exception as e: 25 | log.write(f"Error in client on port {port}: {e}\n") 26 | finally: 27 | process.terminate() 28 | 29 | client1_operations = [ 30 | "2", # Login 31 | "sidsg", # Username 32 | "Sidsg1234", # Password 33 | "3", # List files 34 | "4", # Upload file 35 | "/home/sid/Pictures/spike.png", # File to upload 36 | "3", # List files 37 | "6", # Rename file 38 | "spike.png", # Old filename 39 | "spike2.png", # New filename 40 | "7", # Delete file 41 | "spike.png", # File to delete 42 | "3", # List files 43 | "5", # Download file 44 | "spike.png", # File to download 45 | "./", # Destination path 46 | "5", # Download file 47 | "spike2.png", # File to download 48 | "./", # Destination path 49 | "7", # Delete file 50 | "spike2.png" # File to delete 51 | ] 52 | 53 | client2_operations = [ 54 | "2", # Login 55 | "mkbhd", # Username 56 | "Mkbhd1234", # Password 57 | "3", # List files 58 | "4", # Upload file 59 | "/home/sid/Pictures/spike.png", # File to upload 60 | "3", # List files 61 | "6", # Rename file 62 | "spike.png", # Old filename 63 | "spike3.png", # New filename 64 | "7", # Delete file 65 | "spike.png", # File to delete 66 | "3", # List files 67 | "5", # Download file 68 | "spike.png", # File to download 69 | "./", # Destination path 70 | "5", # Download file 71 | "spike3.png", # File to download 72 | "./", # Destination path 73 | "7", # Delete file 74 | "spike3.png" # File to delete 75 | ] 76 | 77 | thread1 = threading.Thread(target=run_client_operations, args=(7000, client1_operations, "client1.log")) 78 | thread2 = threading.Thread(target=run_client_operations, args=(7000, client2_operations, "client2.log")) 79 | 80 | thread1.start() 81 | thread2.start() 82 | 83 | thread1.join() 84 | thread2.join() 85 | 86 | print("Test completed.") 87 | -------------------------------------------------------------------------------- /server/src/db.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/db.hpp" 2 | 3 | Database::Database(const std::string& dbPath) { 4 | if (sqlite3_open(dbPath.c_str(), &db) != SQLITE_OK) { 5 | std::cerr << "Failed to open SQLite database: " << sqlite3_errmsg(db) << std::endl; 6 | db = nullptr; 7 | } else { 8 | std::cout << "Database initialized\n"; 9 | } 10 | } 11 | 12 | Database::~Database() { 13 | if (db) { 14 | sqlite3_close(db); 15 | } 16 | } 17 | 18 | bool Database::initTable() { 19 | std::string createTableQuery = 20 | "CREATE TABLE IF NOT EXISTS users (" 21 | "id INTEGER PRIMARY KEY AUTOINCREMENT, " 22 | "username TEXT NOT NULL UNIQUE, " 23 | "hashedPassword TEXT NOT NULL);"; 24 | 25 | return executeQuery(createTableQuery); 26 | } 27 | 28 | bool Database::executeQuery(const std::string& query) { 29 | char* errMsg = nullptr; 30 | if (sqlite3_exec(db, query.c_str(), nullptr, nullptr, &errMsg) != SQLITE_OK) { 31 | std::cerr << "SQLite query error: " << errMsg << std::endl; 32 | sqlite3_free(errMsg); 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | bool Database::registerUser(const std::string& username, const std::string& password) { 39 | sqlite3_stmt* stmt; 40 | std::string query = "INSERT INTO users (username, hashedPassword) VALUES (?, ?);"; 41 | 42 | if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { 43 | std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; 44 | return false; 45 | } 46 | 47 | sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC); 48 | sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC); 49 | 50 | if (sqlite3_step(stmt) != SQLITE_DONE) { 51 | std::cerr << "Failed to execute statement: " << sqlite3_errmsg(db) << std::endl; 52 | sqlite3_finalize(stmt); 53 | return false; 54 | } 55 | 56 | sqlite3_finalize(stmt); 57 | return true; 58 | } 59 | 60 | bool Database::userExists(const std::string& username) { 61 | sqlite3_stmt* stmt; 62 | std::string query = "SELECT 1 FROM users WHERE username = ? LIMIT 1;"; 63 | 64 | if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { 65 | std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; 66 | return false; 67 | } 68 | 69 | sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC); 70 | 71 | bool exists = sqlite3_step(stmt) == SQLITE_ROW; 72 | 73 | sqlite3_finalize(stmt); 74 | return exists; 75 | } 76 | 77 | std::string Database::getHashedPassword(const std::string& username) { 78 | sqlite3_stmt* stmt; 79 | std::string query = "SELECT hashedPassword FROM users WHERE username = ? LIMIT 1;"; 80 | std::string hashedPassword; 81 | 82 | if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { 83 | std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; 84 | return ""; 85 | } 86 | 87 | sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC); 88 | 89 | if (sqlite3_step(stmt) == SQLITE_ROW) { 90 | const char* password = reinterpret_cast(sqlite3_column_text(stmt, 0)); 91 | if (password) { 92 | hashedPassword = password; 93 | } 94 | } 95 | 96 | sqlite3_finalize(stmt); 97 | return hashedPassword; 98 | } 99 | -------------------------------------------------------------------------------- /server/src/crypto.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/crypto.hpp" 2 | 3 | std::vector Crypto::generateSalt() { 4 | std::vector salt(SALT_LENGTH); 5 | if (!RAND_bytes(salt.data(), SALT_LENGTH)) { 6 | throw std::runtime_error("Failed to generate random salt"); 7 | } 8 | return salt; 9 | } 10 | 11 | std::vector Crypto::pbkdf2(const std::string& password, const std::vector& salt) { 12 | std::vector hash(HASH_LENGTH); 13 | if (!PKCS5_PBKDF2_HMAC(password.c_str(), password.length(), salt.data(), salt.size(), ITERATIONS, EVP_sha256(), HASH_LENGTH, hash.data())) { 14 | throw std::runtime_error("Failed to hash password using PBKDF2"); 15 | } 16 | return hash; 17 | } 18 | 19 | std::string Crypto::toHex(const std::vector& data) { 20 | std::ostringstream oss; 21 | for (unsigned char byte : data) { 22 | oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); 23 | } 24 | return oss.str(); 25 | } 26 | 27 | std::vector Crypto::toBinary(const std::string& data) { 28 | std::vector result; 29 | for (size_t i = 0; i < data.length(); i += 2) { 30 | std::string byteString = data.substr(i, 2); 31 | unsigned char byte = static_cast(std::stoi(byteString, nullptr, 16)); 32 | result.push_back(byte); 33 | } 34 | return result; 35 | } 36 | 37 | std::string Crypto::encodeResult(const std::vector& salt, const std::vector& hash) { 38 | std::ostringstream oss; 39 | 40 | oss << "$pbkdf2$" << ITERATIONS << "$"; 41 | 42 | oss << toHex(salt) << "$"; 43 | 44 | oss << toHex(hash); 45 | 46 | return oss.str(); 47 | } 48 | 49 | std::pair Crypto::decodeResult(const std::string& storedHash) { 50 | // storedHash: $pbkdf2$100000$$ 51 | size_t firstDollar = storedHash.find('$'); 52 | size_t secondDollar = storedHash.find('$', firstDollar + 1); 53 | size_t thirdDollar = storedHash.find('$', secondDollar + 1); 54 | size_t fourthDollar = storedHash.find('$', thirdDollar + 1); 55 | 56 | if (firstDollar == std::string::npos || secondDollar == std::string::npos || thirdDollar == std::string::npos || fourthDollar == std::string::npos) { 57 | throw std::invalid_argument("Invalid stored hash format"); 58 | } 59 | 60 | std::string saltHex = storedHash.substr(thirdDollar + 1, fourthDollar - thirdDollar - 1); 61 | std::string hashHex = storedHash.substr(fourthDollar + 1); 62 | 63 | return {saltHex, hashHex}; 64 | } 65 | std::string Crypto::hashPassword(const std::string& password) { 66 | std::vector salt = generateSalt(); 67 | std::vector hash = pbkdf2(password, salt); 68 | 69 | return encodeResult(salt, hash); 70 | } 71 | 72 | bool Crypto::verifyPasswords(const std::string& storedHash, const std::string& providedPassword) { 73 | auto [storedSaltHex, storedHashHex] = decodeResult(storedHash); 74 | 75 | std::vector saltBinary = toBinary(storedSaltHex); 76 | std::vector derivedHashBinary = pbkdf2(providedPassword, saltBinary); 77 | 78 | std::string derivedHashHex = toHex(derivedHashBinary); 79 | 80 | // std::cout << "StoredSaltHex: " << storedSaltHex << "\n"; 81 | // std::cout << "StoredHashHex: " << storedHashHex << "\n"; 82 | // std::cout << "DerivedHashHex: " << derivedHashHex << "\n"; 83 | 84 | if (storedHashHex == derivedHashHex) { 85 | // std::cout << "correct\n"; 86 | return true; 87 | } else { 88 | // std::cout << "wrong\n"; 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/integration/sameConcurrentUsers/test.py: -------------------------------------------------------------------------------- 1 | # synchromnization bug present 2 | 3 | """ 4 | concurrent users with same username login from different ports/ip 5 | 6 | client1: login, username, password 7 | client2: login, username, password 8 | client1: upload the file /home/sid/Pictures/spike.png first then 9 | client2: list files 10 | client1: list files 11 | client1: rename req 12 | client2: delete req 13 | client1: provide filename to rename 14 | client2: provide filename to delete and deletes the file 15 | client1: provide new filename to which it has to be renamed 16 | 17 | finally client2 delete operation is success and client 1 rename operation is failure 18 | """ 19 | 20 | 21 | import threading 22 | import subprocess 23 | import time 24 | 25 | def run_client_operations(client_id, port, operations, log_file, condition, step): 26 | with open(log_file, 'w') as log: 27 | process = subprocess.Popen( 28 | ["../../../client/build/bin/client", "127.0.0.1", str(port)], 29 | stdin=subprocess.PIPE, 30 | stdout=log, 31 | stderr=log, 32 | text=True 33 | ) 34 | try: 35 | for operation in operations: 36 | if isinstance(operation, tuple): 37 | op, required_step, next_step = operation 38 | with condition: 39 | while step[0] != required_step: 40 | print(f"[{client_id}] Waiting for step {required_step}") 41 | success = condition.wait(timeout=10) 42 | if not success: 43 | print(f"[{client_id}] Timeout waiting for step {required_step}") 44 | return 45 | print(f"[{client_id}] Executing operation in step {required_step}") 46 | if op is not None: 47 | process.stdin.write(f"{op}\n") 48 | process.stdin.flush() 49 | time.sleep(1) 50 | step[0] = next_step 51 | condition.notify_all() 52 | else: 53 | process.stdin.write(f"{operation}\n") 54 | process.stdin.flush() 55 | time.sleep(1) 56 | process.stdin.write("8\n") # Exit 57 | process.stdin.flush() 58 | process.wait() # Wait for proper client shutdown 59 | except Exception as e: 60 | print(f"[{client_id}] Error: {e}") 61 | finally: 62 | process.terminate() 63 | 64 | # Shared condition and step tracker 65 | condition = threading.Condition() 66 | step = [1] # Start at step 1 67 | 68 | # Define operations with synchronization points 69 | client1_operations = [ 70 | "2", # Login 71 | "sidsg", # Username 72 | "Sidsg1234", # Password 73 | ("4", 1, 2), # Upload file (step 1 -> step 2) 74 | "/home/sid/Pictures/spike.png", # File to upload 75 | ("3", 3, 4), # List files (step 3 -> step 4) 76 | ("6", 5, 6), # Rename file (step 5 -> step 6) 77 | "spike.png", # Old filename 78 | "spike_new.png", # New filename 79 | ] 80 | 81 | client2_operations = [ 82 | "2", # Login 83 | "sidsg", # Username 84 | "Sidsg1234", # Password 85 | ("3", 2, 3), # List files (step 2 -> step 3) 86 | ("7", 4, 5), # Delete file (step 4 -> step 5) 87 | "spike.png", # File to delete 88 | ] 89 | 90 | # Start threads 91 | thread1 = threading.Thread(target=run_client_operations, args=("Client1", 7000, client1_operations, "client1.log", condition, step)) 92 | thread2 = threading.Thread(target=run_client_operations, args=("Client2", 7000, client2_operations, "client2.log", condition, step)) 93 | 94 | thread1.start() 95 | thread2.start() 96 | 97 | thread1.join() 98 | thread2.join() 99 | 100 | print("Test completed. Logs are saved in 'client1.log' and 'client2.log'.") 101 | -------------------------------------------------------------------------------- /client/src/main.cpp: -------------------------------------------------------------------------------- 1 | // #include 2 | // #include 3 | // #include 4 | // #include 5 | 6 | // #include 7 | // #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "../include/bio_utils.hpp" 16 | #include "../include/handlers.hpp" 17 | #include "../include/ssl_utils.hpp" 18 | 19 | #define BUFFER_SIZE 4096 20 | #define CHUNK_SIZE 16384 21 | 22 | // int PORT; 23 | char *PORT; 24 | char *hostname; 25 | 26 | int main(int argc, char *argv[]) { 27 | if (argc != 3) { 28 | std::cerr << "Usage: " << argv[0] << " " 29 | << "\n"; 30 | return 1; 31 | } 32 | 33 | hostname = argv[1]; 34 | // PORT = atoi(argv[2]); 35 | PORT = argv[2]; 36 | 37 | // int sockfd = socket(AF_INET, SOCK_STREAM, 0); // server fd 38 | // if (sockfd == -1) { 39 | // std::cerr << "socket creation failed: " << std::strerror(errno) << "\n"; 40 | // return 1; 41 | // } 42 | // std::cout << "Socket created\n"; 43 | 44 | // struct sockaddr_in serverAddr; 45 | 46 | // serverAddr.sin_family = AF_INET; 47 | // serverAddr.sin_port = htons(PORT); 48 | // serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP); 49 | 50 | // if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) { 51 | // std::cerr << "failed connect(): " << std::strerror(errno) << "\n"; 52 | // } 53 | // std::cout << "server connected\n"; 54 | 55 | // handlers::serverHandler(sockfd); 56 | 57 | // close(sockfd); 58 | 59 | SSL_CTX *ctx = ssl::create_SSLctx(); 60 | 61 | SSL *ssl = SSL_new(ctx); 62 | if (!ssl) { 63 | std::cerr << "Failed to create the SSL object\n"; 64 | ERR_print_errors_fp(stderr); 65 | SSL_CTX_free(ctx); 66 | exit(EXIT_FAILURE); 67 | } 68 | 69 | BIO *bio = bio::create_socket_bio(hostname, PORT, AF_INET); 70 | if (bio == NULL) { 71 | std::cerr << "Failed to crete the BIO\n"; 72 | ERR_print_errors_fp(stderr); 73 | SSL_CTX_free(ctx); 74 | exit(EXIT_FAILURE); 75 | } 76 | 77 | SSL_set_bio(ssl, bio, bio); 78 | 79 | // Tell the server during the handshake which hostname we are attempting to connect to in case the server supports multiple hosts. 80 | if (!SSL_set_tlsext_host_name(ssl, hostname)) { 81 | std::cerr << "Failed to set the SNI hostname\n"; 82 | ERR_print_errors_fp(stderr); 83 | SSL_CTX_free(ctx); 84 | exit(EXIT_FAILURE); 85 | } 86 | 87 | // Ensure we check during certificate verification that the server has supplied a certificate for the hostname that we were expecting. 88 | if (!SSL_set1_host(ssl, hostname)) { 89 | std::cerr << "Failed to set the certificate verification hostname\n"; 90 | ERR_print_errors_fp(stderr); 91 | SSL_CTX_free(ctx); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | int nodelay_flag = 1; 96 | int client_fd; 97 | client_fd = SSL_get_fd(ssl); 98 | if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, sizeof(nodelay_flag)) == -1) { 99 | std::cerr << "Failed to enable TCP_NODELAY: " << std::strerror(errno) << "\n"; 100 | } 101 | std::cout<<"Disabled Nagles' algorithm\n"; 102 | 103 | /* Do the handshake with the server */ 104 | if (SSL_connect(ssl) < 1) { 105 | std::cerr << "Failed to connect to the server\n"; 106 | /* 107 | * If the failure is due to a verification error we can get more 108 | * information about it from SSL_get_verify_result(). 109 | */ 110 | if (SSL_get_verify_result(ssl) != X509_V_OK) printf("Verify error: %s\n", X509_verify_cert_error_string(SSL_get_verify_result(ssl))); 111 | ERR_print_errors_fp(stderr); 112 | SSL_CTX_free(ctx); 113 | exit(EXIT_FAILURE); 114 | } 115 | 116 | std::cout << "Connected to server:\n"; 117 | 118 | // handlers::serverHandler(sockfd); 119 | 120 | handlers::serverHandler(ssl); 121 | 122 | SSL_shutdown(ssl); 123 | SSL_free(ssl); 124 | SSL_CTX_free(ctx); 125 | 126 | return 0; 127 | } -------------------------------------------------------------------------------- /server/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | // #include 10 | #include 11 | 12 | // #include 13 | // #include 14 | #include 15 | 16 | #include "../include/db.hpp" 17 | #include "../include/file_utils.hpp" 18 | #include "../include/handlers.hpp" 19 | #include "../include/ssl_utils.hpp" 20 | #include "../include/threadpool.hpp" 21 | 22 | #define BACKLOG 20 23 | #define BUFFER_SIZE 4096 24 | #define CHUNK_SIZE 16384 25 | 26 | // int PORT; 27 | const char* PORT; 28 | 29 | int main(int argc, char* argv[]) { 30 | signal(SIGPIPE, SIG_IGN); 31 | 32 | if (!utils::isRoot()) { 33 | std::cerr << "This program must be run as root/sudo user\n"; 34 | return 1; 35 | } 36 | 37 | SSL_CTX* ctx = ssl::create_SSLctx("../security/server.crt", "../security/server.key"); 38 | 39 | if (argc != 2) { 40 | std::cerr << "Usage: " << argv[0] << " " 41 | << "\n"; 42 | return 1; 43 | } 44 | 45 | // PORT = atoi(argv[1]); 46 | PORT = argv[1]; 47 | 48 | if (!utils::ensureDirectory("../../store")) { 49 | std::cerr << "Failed to create store directory\n"; 50 | return 1; 51 | } 52 | 53 | Database db("../data/user.db"); 54 | if (!db.initTable()) { 55 | std::cerr << "Failed to initialize database table\n"; 56 | return 1; 57 | } 58 | 59 | std::cout << "User table initialized\n"; 60 | 61 | /* 62 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); // server fd 63 | if (sockfd == -1) { 64 | std::cerr << "socket creation failed: " << std::strerror(errno) << "\n"; 65 | return 1; 66 | } 67 | std::cout << "Socket created\n"; 68 | 69 | struct sockaddr_in serverAddr; 70 | 71 | serverAddr.sin_family = AF_INET; 72 | serverAddr.sin_port = htons(PORT); 73 | serverAddr.sin_addr.s_addr = INADDR_ANY; 74 | */ 75 | 76 | BIO* acceptor_bio = BIO_new_accept(PORT); // BIO_new() + BIO_set_accept_name() / socket() 77 | if (!acceptor_bio) { 78 | std::cerr << "Error creating acceptor BIO\n"; 79 | ERR_print_errors_fp(stderr); 80 | SSL_CTX_free(ctx); 81 | exit(EXIT_FAILURE); 82 | } 83 | 84 | // int yes = 1; 85 | 86 | // if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { // to avoid "port already in use" error 87 | // std::cerr << "setsockopt() failed: " << std::strerror(errno) << "\n"; 88 | // close(sockfd); 89 | // return 1; 90 | // } 91 | 92 | // if (bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) { 93 | // std::cerr << "failed binding to port: " << PORT << " " << std::strerror(errno) << "\n"; 94 | // close(sockfd); 95 | // return 1; 96 | // } 97 | 98 | // if (listen(sockfd, BACKLOG) == -1) { 99 | // std::cerr << "failed listening: " << std::strerror(errno) << "\n"; 100 | // close(sockfd); 101 | // return 1; 102 | // } 103 | 104 | BIO_set_bind_mode(acceptor_bio, BIO_BIND_REUSEADDR); // setsockopt() + bind() + listen() 105 | if (BIO_do_accept(acceptor_bio) <= 0) { 106 | std::cerr << "Error setting up acceptor socket"; 107 | ERR_print_errors_fp(stderr); 108 | SSL_CTX_free(ctx); 109 | exit(EXIT_FAILURE); 110 | } 111 | 112 | int server_fd; 113 | BIO_get_fd(acceptor_bio, &server_fd); 114 | int nodelay_flag = 1; 115 | 116 | if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay_flag, sizeof(nodelay_flag)) == -1) { 117 | std::cerr << "Failed to set TCP_NODELAY: " << std::strerror(errno) << "\n"; 118 | } 119 | 120 | std::cout << "Disabled Nagle's algorithm\n"; 121 | 122 | std::cout << "Server listening on port: " << PORT << "\n"; 123 | 124 | // struct sockaddr_in clientAddr; 125 | // socklen_t clientAddrSize = sizeof(clientAddr); 126 | 127 | Threadpool pool(16); 128 | 129 | while (1) { 130 | BIO* client_bio = NULL; 131 | SSL* ssl = NULL; 132 | // int clientfd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddrSize); // client fd 133 | 134 | // if (clientfd == -1) { 135 | // std::cerr << "failed accept(): " << std::strerror(errno) << "\n"; 136 | // continue; 137 | // } 138 | // std::cout << "client connected: Address=" << inet_ntoa(clientAddr.sin_addr) << " Port=" << ntohs(clientAddr.sin_port) << "\n"; 139 | // pool.enqueueTask(handlers::clientHandler, clientfd); 140 | 141 | if (BIO_do_accept(acceptor_bio) <= 0) { // Wait for the next client to connect 142 | std::cerr << "Error accepting client connection\n"; 143 | ERR_print_errors_fp(stderr); 144 | continue; 145 | } 146 | 147 | client_bio = BIO_pop(acceptor_bio); // Pop the client connection from the BIO chain 148 | if (!client_bio) { 149 | std::cerr << "Error popping client BIO\n"; 150 | ERR_print_errors_fp(stderr); 151 | continue; 152 | } 153 | // get client address 154 | struct sockaddr_storage addr; 155 | socklen_t addr_len = sizeof(addr); 156 | int client_fd; 157 | 158 | BIO_get_fd(client_bio, &client_fd); 159 | getpeername(client_fd, (struct sockaddr*)&addr, &addr_len); 160 | 161 | char client_ip[INET6_ADDRSTRLEN]; 162 | int client_port; 163 | 164 | if (addr.ss_family == AF_INET) { 165 | struct sockaddr_in* s = (struct sockaddr_in*)&addr; 166 | inet_ntop(AF_INET, &s->sin_addr, client_ip, sizeof(client_ip)); 167 | client_port = ntohs(s->sin_port); 168 | } else { 169 | struct sockaddr_in6* s = (struct sockaddr_in6*)&addr; 170 | inet_ntop(AF_INET6, &s->sin6_addr, client_ip, sizeof(client_ip)); 171 | client_port = ntohs(s->sin6_port); 172 | } 173 | 174 | printf("New Client connected from IP: %s, Port: %d\n", client_ip, client_port); 175 | 176 | if ((ssl = SSL_new(ctx)) == NULL) { // Associate a new SSL handle with the new connection 177 | std::cerr << "Error creating SSL handle for new connection\n"; 178 | ERR_print_errors_fp(stderr); 179 | BIO_free(client_bio); 180 | continue; 181 | } 182 | SSL_set_bio(ssl, client_bio, client_bio); 183 | 184 | /* Attempt an SSL handshake with the client */ 185 | if (SSL_accept(ssl) <= 0) { 186 | std::cerr << "Error performing SSL handshake with client\n"; 187 | ERR_print_errors_fp(stderr); 188 | SSL_free(ssl); 189 | continue; 190 | } 191 | 192 | printf("SSL handshake success with client from IP: %s, Port: %d\n", client_ip, client_port); 193 | 194 | pool.enqueueTask([ssl, client_ip, client_port]() { 195 | try { 196 | clientHandler handler(ssl, client_ip, client_port); 197 | handler.handler(); 198 | } catch (const std::exception& e) { 199 | std::cerr << "Exception in client handler: " << e.what() << "\n"; 200 | SSL_shutdown(ssl); 201 | SSL_free(ssl); 202 | } catch (...) { 203 | std::cerr << "Unknown error occurred in client handler.\n"; 204 | SSL_shutdown(ssl); 205 | SSL_free(ssl); 206 | } 207 | }); 208 | // Handle client in new thread 209 | // std::thread(handle_client, ssl).detach(); 210 | } 211 | 212 | BIO_free_all(acceptor_bio); 213 | SSL_CTX_free(ctx); 214 | 215 | return 0; 216 | } -------------------------------------------------------------------------------- /client/src/handlers.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/handlers.hpp" 2 | 3 | #include "../include/progressbar.hpp" 4 | 5 | #define BUFFER_SIZE 4096 6 | #define CHUNK_SIZE 16384 7 | 8 | namespace handlers { 9 | 10 | void serverHandler(SSL *ssl) { 11 | int option = 0; 12 | 13 | while (true) { 14 | std::cout << "Choose:\n" 15 | << " 1. Register\n" 16 | << " 2. Login\n" 17 | << " 3. List files\n" 18 | << " 4. Upload file\n" 19 | << " 5. Download file\n" 20 | << " 6. Rename file\n" 21 | << " 7. Delete file\n" 22 | << " 8. Exit\n" 23 | << "Enter your choice: "; 24 | 25 | int option; 26 | if (!(std::cin >> option)) { 27 | std::cin.clear(); 28 | std::cin.ignore(std::numeric_limits::max(), '\n'); 29 | std::cerr << "Invalid option. Please enter a number between 1 and 8.\n"; 30 | continue; 31 | } 32 | 33 | std::cout << option << "\n"; 34 | 35 | switch (option) { 36 | case 1: // register 37 | registerUser(ssl); 38 | break; 39 | 40 | case 2: // login 41 | loginUser(ssl); 42 | break; 43 | 44 | case 3: // list files 45 | listFiles(ssl); 46 | break; 47 | 48 | case 4: // upload file 49 | uploadFile(ssl); 50 | break; 51 | 52 | case 5: // download file 53 | downloadFile(ssl); 54 | break; 55 | 56 | case 6: // rename file 57 | renameFile(ssl); 58 | break; 59 | 60 | case 7: // delete file 61 | deleteFile(ssl); 62 | break; 63 | 64 | case 8: // exit 65 | std::cout << "Exiting...\n"; 66 | return; 67 | 68 | default: 69 | std::cerr << "Invalid option. Please enter a number between 1 and 8.\n"; 70 | break; 71 | } 72 | 73 | std::cout << "\n"; 74 | } 75 | } 76 | 77 | void registerUser(SSL *ssl) { 78 | std::cout << "Registering user\n"; 79 | // send register req 80 | const std::string registerRequest = "register"; 81 | if (SSL_write(ssl, registerRequest.c_str(), registerRequest.size()) <= 0) { 82 | std::cerr << "Failed to send register request: " << std::strerror(errno) << "\n"; 83 | ERR_print_errors_fp(stderr); 84 | return; 85 | } 86 | std::string username, password; 87 | 88 | std::cout << "Enter username: "; 89 | std::cin >> username; 90 | 91 | // Contains at least one lowercase letter, one uppercase letter, one digit, and is at least 8 characters long 92 | std::cout << "\nPassword pattern: \n1. at least one lowercase letter, \n2. at least one uppercase letter, \n3. at least one digit, \n4. at least 8 characters long\n\n"; 93 | std::cout << "Enter password: "; 94 | std::cin >> password; 95 | 96 | if (!std::regex_match(username, usernameRegex)) { 97 | std::cerr << "Invalid username received: " << username << "\n"; 98 | return; 99 | } 100 | if (!std::regex_match(password, passwordRegex)) { 101 | std::cerr << "Password does not meet the required criteria.\n"; 102 | return; 103 | } 104 | 105 | // Send username 106 | if (SSL_write(ssl, username.c_str(), username.size()) <= 0) { 107 | std::cerr << "Failed to send username: " << std::strerror(errno) << "\n"; 108 | return; 109 | } 110 | 111 | // Send password 112 | if (SSL_write(ssl, password.c_str(), password.size()) <= 0) { 113 | std::cerr << "Failed to send password: " << std::strerror(errno) << "\n"; 114 | return; 115 | } 116 | 117 | // Wait to receive ACK 118 | std::string responseBuffer(1024, '\0'); 119 | int bytesRecv = SSL_read(ssl, responseBuffer.data(), responseBuffer.size() - 1); 120 | if (bytesRecv <= 0) { 121 | if (bytesRecv == 0) { 122 | std::cerr << "Server closed the connection while waiting for ACK\n"; 123 | } else { 124 | std::cerr << "Failed to receive ACK: " << std::strerror(errno) << "\n"; 125 | } 126 | return; 127 | } 128 | 129 | responseBuffer.resize(bytesRecv); 130 | std::string response(responseBuffer.data(), bytesRecv); 131 | 132 | if (response == "OK") { 133 | std::cout << "User registered successfully.\n"; 134 | } else if (response == "USER_EXISTS") { 135 | std::cerr << "Error: User already registered.\n"; 136 | } else if (response == "INVALID_USERNAME_REGEX") { 137 | std::cerr << "Error: Invalid username format.\n"; 138 | } else if (response == "INVALID_PASSWORD_REGEX") { 139 | std::cerr << "Error: Password does not meet the required criteria.\n"; 140 | } else { 141 | std::cerr << "Error: Unknown response from server: " << response << "\n"; 142 | } 143 | } 144 | 145 | void loginUser(SSL *ssl) { 146 | std::cout << "Log in\n"; 147 | // send login req 148 | const std::string uploadRequest = "login"; 149 | if (SSL_write(ssl, uploadRequest.c_str(), uploadRequest.size()) <= 0) { 150 | std::cerr << "Failed to send login request: " << std::strerror(errno) << "\n"; 151 | ERR_print_errors_fp(stderr); 152 | return; 153 | } 154 | 155 | std::string username, password; 156 | std::cout << "Enter username: "; 157 | std::cin >> username; 158 | std::cout << "Enter password: "; 159 | std::cin >> password; 160 | 161 | // Send username 162 | if (SSL_write(ssl, username.c_str(), username.size()) == -1) { 163 | std::cerr << "Failed to send username: " << std::strerror(errno) << "\n"; 164 | return; 165 | } 166 | 167 | // Send password 168 | if (SSL_write(ssl, password.c_str(), password.size()) == -1) { 169 | std::cerr << "Failed to send password: " << std::strerror(errno) << "\n"; 170 | return; 171 | } 172 | 173 | std::string responseBuffer(1024, '\0'); 174 | int bytesRecv = SSL_read(ssl, responseBuffer.data(), responseBuffer.size() - 1); 175 | if (bytesRecv <= 0) { 176 | if (bytesRecv == 0) { 177 | std::cerr << "Server closed the connection while waiting for ACK\n"; 178 | } else { 179 | std::cerr << "Failed to receive ACK: " << std::strerror(errno) << "\n"; 180 | } 181 | return; 182 | } 183 | 184 | responseBuffer[bytesRecv] = '\0'; 185 | std::string response(responseBuffer.data(), bytesRecv); 186 | 187 | if (response == "LOGIN_SUCCESS") { 188 | std::cout << "User logged in successfully.\n"; 189 | } else if (response == "USER_NOT_FOUND") { 190 | std::cerr << "Error: User not found. Please register first.\n"; 191 | } else if (response == "WRONG_PASSWORD") { 192 | std::cerr << "Error: Incorrect password. Please try again.\n"; 193 | } else { 194 | std::cerr << "Error: Unknown response from server: " << response << "\n"; 195 | } 196 | } 197 | 198 | bool checkLoggedIn(SSL *ssl) { 199 | std::string ackBuffer(1024, '\0'); 200 | int bytesRecv = SSL_read(ssl, ackBuffer.data(), ackBuffer.size() - 1); 201 | if (bytesRecv <= 0) { 202 | if (bytesRecv == 0) { 203 | std::cerr << "Server closed the connection while waiting for ACK\n"; 204 | } else { 205 | std::cerr << "Failed to receive ACK: " << std::strerror(errno) << "\n"; 206 | } 207 | return false; 208 | } 209 | std::string response(ackBuffer.data(), bytesRecv); 210 | if (response != "LOGIN_SUCCESS") { 211 | std::cerr << "Error: User not logged in. Please login first.\n"; 212 | return false; 213 | } 214 | return true; 215 | } 216 | 217 | // function only performed if client is logged-in 218 | 219 | void listFiles(SSL *ssl) { 220 | std::cout << "List files\n"; 221 | const std::string command = "ls"; 222 | if (SSL_write(ssl, command.c_str(), command.size()) == -1) { 223 | std::cerr << "Failed to send command: " << std::strerror(errno) << "\n"; 224 | ERR_print_errors_fp(stderr); 225 | return; 226 | } 227 | 228 | if (!checkLoggedIn(ssl)) { 229 | return; 230 | } 231 | std::vector recvBuffer(BUFFER_SIZE, 0); 232 | 233 | int bytesRecv = SSL_read(ssl, recvBuffer.data(), recvBuffer.size() - 1); 234 | if (bytesRecv <= 0) { 235 | if (bytesRecv == 0) { 236 | std::cerr << "Connection closed by server\n"; 237 | } else { 238 | std::cerr << "Failed to receive data: " << std::strerror(errno) << "\n"; 239 | ERR_print_errors_fp(stderr); 240 | } 241 | return; 242 | } 243 | 244 | recvBuffer[bytesRecv] = '\0'; 245 | std::cout << "Directory files:\n" << recvBuffer.data() << "\n"; 246 | } 247 | 248 | void uploadFile(SSL *ssl) { 249 | std::cout << "Upload file\n"; 250 | 251 | std::string pathname; 252 | std::cout << "Enter file pathname: \n"; 253 | std::cin >> pathname; 254 | 255 | int filefd = open(pathname.c_str(), O_RDONLY); 256 | if (filefd == -1) { 257 | std::cerr << "Failed to open file: " << std::strerror(errno) << "\n"; 258 | const std::string errorMsg = "error"; 259 | if (SSL_write(ssl, errorMsg.c_str(), errorMsg.size()) <= 0) { 260 | std::cerr << "Failed to send error response to server: " << std::strerror(errno) << "\n"; 261 | ERR_print_errors_fp(stderr); 262 | } 263 | return; 264 | } 265 | 266 | // send upload req 267 | const std::string uploadRequest = "upload"; 268 | if (SSL_write(ssl, uploadRequest.c_str(), uploadRequest.size()) <= 0) { 269 | std::cerr << "Failed to send upload request: " << std::strerror(errno) << "\n"; 270 | ERR_print_errors_fp(stderr); 271 | close(filefd); 272 | return; 273 | } 274 | 275 | if (!checkLoggedIn(ssl)) { 276 | return; 277 | } 278 | 279 | std::string filename = pathname.substr(pathname.find_last_of("/") + 1); 280 | 281 | off_t filesize = lseek(filefd, 0, SEEK_END); 282 | if (filesize == -1) { 283 | std::cerr << "Failed to get file size: " << std::strerror(errno) << "\n"; 284 | close(filefd); 285 | return; 286 | } 287 | lseek(filefd, 0, SEEK_SET); 288 | 289 | // send metadata (filename:filesize) 290 | std::string metadata = filename + ":" + std::to_string(filesize); 291 | if (SSL_write(ssl, metadata.c_str(), metadata.size()) <= 0) { 292 | std::cerr << "Failed to send metadata: " << std::strerror(errno) << "\n"; 293 | ERR_print_errors_fp(stderr); 294 | close(filefd); 295 | return; 296 | } 297 | 298 | // wait to recv ACK 299 | std::string ackBuffer(3, '\0'); 300 | int bytesRecv = SSL_read(ssl, &ackBuffer[0], ackBuffer.size() - 1); 301 | if (bytesRecv <= 0) { 302 | if (bytesRecv == 0) { 303 | std::cerr << "Server closed the connection while waiting for ACK\n"; 304 | } else { 305 | std::cerr << "Failed to receive ACK: " << std::strerror(errno) << "\n"; 306 | ERR_print_errors_fp(stderr); 307 | } 308 | close(filefd); 309 | return; 310 | } 311 | 312 | ackBuffer.resize(bytesRecv); 313 | if (ackBuffer != "OK") { 314 | std::cerr << "Received NACK from server\n"; 315 | close(filefd); 316 | return; 317 | } 318 | 319 | bool isKernelTLS = (BIO_get_ktls_send(SSL_get_rbio(ssl)) > 0); 320 | if (isKernelTLS) { 321 | std::cerr << "Kernel TLS enabled, using SSL_sendfile for upload \n"; 322 | } else { 323 | std::cerr << "Kernel TLS not enabled, falling back to read + SSL_write for file upload\n"; 324 | } 325 | 326 | // disable TCP_NODELAY 327 | int nodelay_flag = 0; 328 | int client_fd; 329 | client_fd = SSL_get_fd(ssl); 330 | if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, sizeof(nodelay_flag)) == -1) { 331 | std::cerr << "Failed to disable TCP_NODELAY (enable nagles's algo) : " << std::strerror(errno) << "\n"; 332 | } 333 | std::cout << "Disabled TCP_NODELAY (Enabled Nagle's algo)\n"; 334 | 335 | // inti progress bar 336 | const int totalSteps = 100; 337 | ProgressBar progressBar(totalSteps); 338 | 339 | // sendfile in chunks 340 | off_t totalBytesSent = 0; 341 | size_t bytesRemaining = filesize; 342 | const size_t chunkSize = 65536; 343 | 344 | // start timer 345 | auto startTime = std::chrono::high_resolution_clock::now(); 346 | 347 | // std::cout << "bytesRemainning at start: " << bytesRemaining << std::endl; 348 | while (bytesRemaining > 0) { 349 | size_t toSend = std::min(static_cast(bytesRemaining), chunkSize); 350 | ssize_t bytesSent = 0; 351 | 352 | if (isKernelTLS) { 353 | bytesSent = SSL_sendfile(ssl, filefd, totalBytesSent, toSend, 0); 354 | } else { 355 | std::vector buffer(toSend); 356 | buffer.clear(); 357 | ssize_t readBytes = pread(filefd, buffer.data(), toSend, totalBytesSent); 358 | if (readBytes > 0) { 359 | bytesSent = SSL_write(ssl, buffer.data(), readBytes); 360 | } 361 | 362 | totalBytesSent += bytesSent; 363 | } 364 | if (bytesSent <= 0) { 365 | std::cerr << "Failed to send file chunk: " << std::strerror(errno) << "\n"; 366 | ERR_print_errors_fp(stderr); 367 | close(filefd); 368 | return; 369 | } 370 | 371 | bytesRemaining -= bytesSent; 372 | 373 | // std::cout << "bytesRemainning now: " << bytesRemaining << std::endl; 374 | // update progress bar 375 | int progress = static_cast((static_cast(totalBytesSent) / filesize) * totalSteps); 376 | progressBar.update(std::min(progress, totalSteps)); 377 | } 378 | 379 | // std::cout << "bytesRemainning at end: " << bytesRemaining << std::endl; 380 | close(filefd); 381 | 382 | const std::string completionSignal = "UPLOAD_COMPLETE"; 383 | if (SSL_write(ssl, completionSignal.c_str(), completionSignal.size()) <= 0) { 384 | std::cerr << "Failed to send completion signal: " << std::strerror(errno) << "\n"; 385 | ERR_print_errors_fp(stderr); 386 | } 387 | 388 | auto endTime = std::chrono::high_resolution_clock::now(); 389 | std::chrono::duration duration = endTime - startTime; 390 | 391 | std::cout << "\nFile sent and compressed successfully in " << duration.count() << " seconds\n"; 392 | 393 | // enable TCP_NODELAY 394 | nodelay_flag = 1; 395 | if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, sizeof(nodelay_flag)) == -1) { 396 | std::cerr << "Failed to elable TCP_NODELAY (disable nagles's algo) : " << std::strerror(errno) << "\n"; 397 | } 398 | std::cout << "Enabled TCP_NODELAY (Disabled Nagle's algo)\n"; 399 | } 400 | 401 | void downloadFile(SSL *ssl) { 402 | std::cout << "Download file\n"; 403 | 404 | std::string buffer = "download"; 405 | // send download req 406 | int bytesSent = SSL_write(ssl, buffer.c_str(), buffer.length()); 407 | 408 | if (bytesSent <= 0) { 409 | std::cerr << "Failed sending download request: " << std::strerror(errno) << "\n"; 410 | ERR_print_errors_fp(stderr); 411 | return; 412 | } 413 | buffer.clear(); 414 | 415 | if (!checkLoggedIn(ssl)) { 416 | return; 417 | } 418 | std::string filename; 419 | std::cout << "Enter file filename to download: \n"; 420 | std::cin >> filename; 421 | 422 | std::string downloadPath; 423 | std::cout << "Enter pathname to download file: \n"; 424 | std::cin >> downloadPath; 425 | std::string fullPath = downloadPath; 426 | 427 | if (downloadPath.back() != '/') { 428 | fullPath += "/"; 429 | } 430 | fullPath += filename; 431 | 432 | bytesSent = SSL_write(ssl, filename.c_str(), filename.length()); // send filename 433 | if (bytesSent <= 0) { 434 | std::cerr << "Failed to send filename: " << std::strerror(errno) << "\n"; 435 | ERR_print_errors_fp(stderr); 436 | return; 437 | } 438 | 439 | std::vector recvBuffer(BUFFER_SIZE); 440 | int bytesRecv = SSL_read(ssl, recvBuffer.data(), recvBuffer.size()); 441 | 442 | if (bytesRecv <= 0) { 443 | std::cerr << "Failed receiving compressed file size: " << std::strerror(errno) << "\n"; 444 | ERR_print_errors_fp(stderr); 445 | return; 446 | } 447 | 448 | std::string serverResponse(recvBuffer.begin(), recvBuffer.begin() + bytesRecv); 449 | if (serverResponse.find("ERROR:") != std::string::npos) { 450 | std::cerr << "Server Error: " << serverResponse << "\n"; 451 | return; 452 | } 453 | 454 | off_t filesize = std::stoll(serverResponse); 455 | std::fill(recvBuffer.begin(), recvBuffer.end(), 0); 456 | 457 | bytesSent = SSL_write(ssl, "OK", 2); 458 | if (bytesSent <= 0) { 459 | std::cerr << "Failed sending ACK: " << std::strerror(errno) << "\n"; 460 | ERR_print_errors_fp(stderr); 461 | return; 462 | } 463 | 464 | int downloadFd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); 465 | if (downloadFd == -1) { 466 | std::cerr << "Failed to create file: " << fullPath << " - " << std::strerror(errno) << "\n"; 467 | return; 468 | } 469 | 470 | int totalSteps = 100; 471 | ProgressBar progressBar(totalSteps); 472 | off_t totalBytesRecv = 0; 473 | off_t bytesRemaining = filesize; 474 | 475 | // init zlib 476 | z_stream zstream{}; 477 | zstream.zalloc = Z_NULL; 478 | zstream.zfree = Z_NULL; 479 | zstream.opaque = Z_NULL; 480 | zstream.avail_in = 0; 481 | zstream.next_in = Z_NULL; 482 | 483 | if (inflateInit2(&zstream, 16 + MAX_WBITS) != Z_OK) { 484 | std::cerr << "Failed to initialize zlib for decompression\n"; 485 | close(downloadFd); 486 | return; 487 | } 488 | 489 | // start timer 490 | auto startTime = std::chrono::high_resolution_clock::now(); 491 | try { 492 | std::vector decompressionBuffer(CHUNK_SIZE); 493 | while (bytesRemaining > 0) { 494 | bytesRecv = SSL_read(ssl, recvBuffer.data(), recvBuffer.size()); // recv compressed file data 495 | 496 | if (bytesRecv <= 0) { 497 | ERR_print_errors_fp(stderr); 498 | throw std::runtime_error("Failed receiving compressed file: " + std::string(std::strerror(errno))); 499 | } 500 | 501 | totalBytesRecv += bytesRecv; 502 | bytesRemaining -= bytesRecv; 503 | 504 | zstream.avail_in = bytesRecv; 505 | zstream.next_in = reinterpret_cast(recvBuffer.data()); 506 | 507 | // decompress and write to file 508 | do { 509 | zstream.avail_out = decompressionBuffer.size(); 510 | zstream.next_out = decompressionBuffer.data(); 511 | 512 | int ret = inflate(&zstream, Z_NO_FLUSH); 513 | if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { 514 | throw std::runtime_error("Decompression error: " + std::to_string(ret)); 515 | } 516 | 517 | size_t decompressedBytes = decompressionBuffer.size() - zstream.avail_out; 518 | if (write(downloadFd, decompressionBuffer.data(), decompressedBytes) == -1) { 519 | throw std::runtime_error("Failed writing to file: " + std::string(std::strerror(errno))); 520 | } 521 | } while (zstream.avail_in > 0 || zstream.avail_out == 0); 522 | 523 | // update progress bar 524 | int progress = static_cast((static_cast(totalBytesRecv) / filesize) * totalSteps); 525 | progressBar.update(std::min(progress, totalSteps)); 526 | } 527 | 528 | std::cout << "\nReceived file: " << filename << "\n"; 529 | } catch (const std::exception &e) { 530 | std::cerr << e.what() << "\n"; 531 | } catch (...) { 532 | std::cerr << "An unknown error occurred.\n"; 533 | } 534 | 535 | inflateEnd(&zstream); 536 | 537 | auto endTime = std::chrono::high_resolution_clock::now(); 538 | std::chrono::duration duration = endTime - startTime; 539 | 540 | std::cout << "File successfully recieved and uncompressed in: " << duration.count() << " seconds\n"; 541 | close(downloadFd); 542 | } 543 | 544 | void renameFile(SSL *ssl) { 545 | std::cout << "Rename file\n"; 546 | 547 | // send rename req 548 | const std::string buffer = "rename"; 549 | if (SSL_write(ssl, buffer.c_str(), buffer.length()) <= 0) { 550 | std::cerr << "Failed sending rename request: " << std::strerror(errno) << "\n"; 551 | ERR_print_errors_fp(stderr); 552 | return; 553 | } 554 | 555 | if (!checkLoggedIn(ssl)) { 556 | return; 557 | } 558 | std::string filename; 559 | std::cout << "Enter the filename to rename: "; 560 | std::cin >> filename; 561 | 562 | std::string newname; 563 | std::cout << "NOTE: Mention the file extension while giving the new name.\n"; 564 | std::cout << "Enter new filename: "; 565 | std::cin >> newname; 566 | 567 | const std::string metadata = filename + ":" + newname; 568 | 569 | // send metadata 570 | if (SSL_write(ssl, metadata.c_str(), metadata.length()) <= 0) { 571 | std::cerr << "Failed to send metadata (filename and newname): " << std::strerror(errno) << "\n"; 572 | ERR_print_errors_fp(stderr); 573 | return; 574 | } 575 | 576 | std::vector recvBuffer(BUFFER_SIZE); 577 | 578 | // recv response 579 | int bytesRecv = SSL_read(ssl, recvBuffer.data(), recvBuffer.size() - 1); 580 | if (bytesRecv <= 0) { 581 | if (bytesRecv == 0) { 582 | std::cerr << "Connection closed by server.\n"; 583 | } else { 584 | std::cerr << "Failed receiving server response: " << std::strerror(errno) << "\n"; 585 | } 586 | ERR_print_errors_fp(stderr); 587 | return; 588 | } 589 | 590 | recvBuffer[bytesRecv] = '\0'; 591 | 592 | std::string serverResponse(recvBuffer.begin(), recvBuffer.begin() + bytesRecv); 593 | if (serverResponse.find("ERROR:") != std::string::npos) { 594 | std::cerr << "Server Error: " << serverResponse << "\n"; 595 | return; 596 | } 597 | 598 | std::cout << "File \"" << filename << "\" successfully renamed to \"" << newname << "\".\n"; 599 | } 600 | 601 | void deleteFile(SSL *ssl) { 602 | std::cout << "Delete file\n"; 603 | 604 | // send delete req 605 | const std::string buffer = "delete"; 606 | if (SSL_write(ssl, buffer.c_str(), buffer.length()) <= 0) { 607 | std::cerr << "Failed to send delete request: " << std::strerror(errno) << "\n"; 608 | return; 609 | } 610 | 611 | if (!checkLoggedIn(ssl)) { 612 | return; 613 | } 614 | std::string filename; 615 | std::cout << "Enter the filename to delete: "; 616 | std::cin >> filename; 617 | 618 | // send filename 619 | if (SSL_write(ssl, filename.c_str(), filename.length()) <= 0) { 620 | std::cerr << "Failed to send filename: " << std::strerror(errno) << "\n"; 621 | ERR_print_errors_fp(stderr); 622 | return; 623 | } 624 | 625 | std::vector recvBuffer(BUFFER_SIZE); 626 | 627 | // recv response 628 | int bytesRecv = SSL_read(ssl, recvBuffer.data(), recvBuffer.size() - 1); 629 | if (bytesRecv <= 0) { 630 | if (bytesRecv == 0) { 631 | std::cerr << "Connection closed by the server.\n"; 632 | } else { 633 | std::cerr << "Failed to receive server response: " << std::strerror(errno) << "\n"; 634 | } 635 | ERR_print_errors_fp(stderr); 636 | return; 637 | } 638 | 639 | recvBuffer[bytesRecv] = '\0'; 640 | 641 | std::string serverResponse(recvBuffer.begin(), recvBuffer.begin() + bytesRecv); 642 | if (serverResponse.find("ERROR:") != std::string::npos) { 643 | std::cerr << "Server Error: " << serverResponse << "\n"; 644 | return; 645 | } 646 | 647 | std::cout << "File \"" << filename << "\" successfully deleted.\n"; 648 | } 649 | 650 | } // namespace handlers 651 | -------------------------------------------------------------------------------- /server/src/handlers.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/handlers.hpp" 2 | 3 | // #include 4 | // #include 5 | 6 | #include "../include/crypto.hpp" 7 | #include "../include/db.hpp" 8 | #include "../include/file_utils.hpp" 9 | 10 | #define BUFFER_SIZE 4096 11 | #define CHUNK_SIZE 16384 12 | std::string folderdir = "\0"; 13 | std::unordered_map clientHandler::folderLocks; 14 | 15 | void clientHandler::clientInfo() { 16 | std::string prefix = username + ":" + client_ip + ":" + std::to_string(client_port) + " - "; 17 | std::cout << prefix; 18 | } 19 | 20 | void clientHandler::handler() { 21 | std::vector buffer(BUFFER_SIZE, 0); 22 | 23 | while (1) { 24 | std::fill(buffer.begin(), buffer.end(), 0); 25 | 26 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size() - 1); // recv option 27 | if (bytesRecv <= 0) { 28 | clientInfo(); 29 | std::cerr << "Client disconnected or error occurred: " << std::strerror(errno) << "\n"; 30 | ERR_print_errors_fp(stderr); 31 | break; 32 | } 33 | 34 | buffer[bytesRecv] = '\0'; 35 | std::string option(buffer.data()); 36 | if (option.empty()) { 37 | clientInfo(); 38 | std::cerr << "Empty or invalid option received\n"; 39 | continue; 40 | } 41 | 42 | clientInfo(); 43 | std::cout << "Option selected: " << option << "\n"; 44 | 45 | try { 46 | if (option == "register") { 47 | registerUser(); 48 | } else if (option == "login") { 49 | loginUser(); 50 | } else if (option == "ls") { 51 | listFiles(); 52 | } else if (option == "upload") { 53 | uploadFile(); 54 | } else if (option == "download") { 55 | downloadFile(); 56 | } else if (option == "rename") { 57 | renameFile(); 58 | } else if (option == "delete") { 59 | deleteFile(); 60 | } else { 61 | std::cerr << "Unknown option selected by client\n"; 62 | const std::string errorMsg = "ERROR: Unknown option\n"; 63 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 64 | } 65 | } catch (const std::exception& e) { 66 | std::cerr << "Exception during handling client option: " << e.what() << "\n"; 67 | } catch (...) { 68 | std::cerr << "Unknown error occurred during handling client option\n"; 69 | } 70 | 71 | std::cout << "\n"; 72 | } 73 | } 74 | 75 | void clientHandler::registerUser() { 76 | std::string username, password; 77 | 78 | // recv username 79 | std::vector buffer(BUFFER_SIZE, 0); 80 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size() - 1); 81 | if (bytesRecv <= 0) { 82 | clientInfo(); 83 | std::cerr << "Failed to receive username: " << std::strerror(errno) << "\n"; 84 | ERR_print_errors_fp(stderr); 85 | return; 86 | } 87 | 88 | buffer[bytesRecv] = '\0'; 89 | username = buffer.data(); 90 | 91 | // recv password 92 | bytesRecv = SSL_read(ssl, buffer.data(), buffer.size() - 1); 93 | if (bytesRecv <= 0) { 94 | clientInfo(); 95 | std::cerr << "Failed to receive password: " << std::strerror(errno) << "\n"; 96 | ERR_print_errors_fp(stderr); 97 | return; 98 | } 99 | 100 | buffer[bytesRecv] = '\0'; 101 | password = buffer.data(); 102 | 103 | if (!std::regex_match(username, usernameRegex)) { 104 | clientInfo(); 105 | std::cerr << "Invalid username received: " << username << "\n"; 106 | const std::string errorMsg = "INVALID_USERNAME_REGEX"; 107 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 108 | return; 109 | } 110 | 111 | if (!std::regex_match(password, passwordRegex)) { 112 | clientInfo(); 113 | std::cerr << "Invalid password received: " << password << "\n"; 114 | const std::string errorMsg = "INVALID_PASSWORD_REGEX"; 115 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 116 | return; 117 | } 118 | 119 | Crypto crypto; 120 | 121 | std::string hashedPassword = crypto.hashPassword(password); 122 | 123 | Database db("../data/user.db"); 124 | 125 | if (db.userExists(username)) { 126 | const std::string errorMsg = "USER_EXISTS"; 127 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 128 | return; 129 | } 130 | 131 | if (!db.registerUser(username, hashedPassword)) { 132 | clientInfo(); 133 | std::cerr << "Failed to register user\n"; 134 | return; 135 | } 136 | 137 | folderdir = "../../store/" + username; 138 | 139 | if (!utils::ensureDirectory(folderdir)) { 140 | clientInfo(); 141 | std::cerr << "Failed to create store directory\n"; 142 | return; 143 | } 144 | 145 | // send ACK 146 | const std::string ack = "OK"; 147 | if (SSL_write(ssl, ack.c_str(), ack.size()) <= 0) { 148 | clientInfo(); 149 | std::cerr << "Failed to send ACK: " << std::strerror(errno) << "\n"; 150 | ERR_print_errors_fp(stderr); 151 | return; 152 | } 153 | 154 | clientInfo(); 155 | std::cout << "User registered successfully: " << username << "\n"; 156 | } 157 | 158 | void clientHandler::loginUser() { 159 | std::string username, password; 160 | 161 | // recv username 162 | std::vector buffer(BUFFER_SIZE, 0); 163 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size() - 1); 164 | if (bytesRecv <= 0) { 165 | clientInfo(); 166 | std::cerr << "Failed to receive username: " << std::strerror(errno) << "\n"; 167 | ERR_print_errors_fp(stderr); 168 | return; 169 | } 170 | 171 | buffer[bytesRecv] = '\0'; 172 | username = buffer.data(); 173 | 174 | // recv password 175 | bytesRecv = SSL_read(ssl, buffer.data(), buffer.size() - 1); 176 | if (bytesRecv <= 0) { 177 | clientInfo(); 178 | std::cerr << "Failed to receive password: " << std::strerror(errno) << "\n"; 179 | ERR_print_errors_fp(stderr); 180 | return; 181 | } 182 | buffer[bytesRecv] = '\0'; 183 | password = buffer.data(); 184 | 185 | Database db("../data/user.db"); 186 | if (!db.userExists(username)) { 187 | const std::string errorMsg = "USER_NOT_FOUND"; 188 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 189 | return; 190 | } 191 | std::string storedHash = db.getHashedPassword(username); 192 | 193 | if (storedHash.empty()) { 194 | const std::string errorMsg = "USER_NOT_FOUND"; 195 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 196 | return; 197 | } 198 | 199 | Crypto crypto; 200 | 201 | if (!crypto.verifyPasswords(storedHash, password)) { 202 | const std::string errorMsg = "WRONG_PASSWORD"; 203 | std::cout << errorMsg << "\n"; 204 | SSL_write(ssl, errorMsg.c_str(), errorMsg.size()); 205 | return; 206 | } 207 | 208 | folderdir = "../../store/" + username; 209 | 210 | if (!utils::ensureDirectory(folderdir)) { 211 | clientInfo(); 212 | std::cerr << "Failed to create store directory\n"; 213 | return; 214 | } 215 | 216 | isLoggedin = true; 217 | this->username = username; 218 | 219 | // Send ack 220 | const std::string ack = "LOGIN_SUCCESS"; 221 | SSL_write(ssl, ack.c_str(), ack.size()); 222 | 223 | clientInfo(); 224 | std::cout << "User logged in successfully: " << username << "\n"; 225 | } 226 | 227 | void clientHandler::sendLoggedInStatus() { 228 | if (isLoggedin) { 229 | const std::string ack = "LOGIN_SUCCESS"; 230 | if (SSL_write(ssl, ack.c_str(), ack.size()) <= 0) { 231 | clientInfo(); 232 | std::cerr << "Failed to send ACK: " << std::strerror(errno) << "\n"; 233 | ERR_print_errors_fp(stderr); 234 | return; 235 | } 236 | } else { 237 | const std::string ack = "LOGIN_FAIL"; 238 | if (SSL_write(ssl, ack.c_str(), ack.size()) <= 0) { 239 | clientInfo(); 240 | std::cerr << "Failed to send ACK: " << std::strerror(errno) << "\n"; 241 | ERR_print_errors_fp(stderr); 242 | return; 243 | } 244 | } 245 | } 246 | 247 | // function only performed if client is logged-in 248 | 249 | void clientHandler::listFiles() { 250 | sendLoggedInStatus(); 251 | if (!isLoggedin) { 252 | clientInfo(); 253 | std::cout << "User not logged in\n"; 254 | return; 255 | } 256 | clientInfo(); 257 | std::cout << "user logged in\n"; 258 | 259 | std::string files = utils::ls(folderdir); 260 | 261 | if (files.empty()) { 262 | files = "Empty directory"; 263 | } 264 | 265 | std::vector buffer(files.begin(), files.end()); 266 | 267 | buffer.push_back('\0'); 268 | 269 | size_t totalSize = buffer.size(); 270 | size_t bytesSent = 0; 271 | clientInfo(); 272 | std::cout << "Sending directory files to client...\n"; 273 | 274 | // send files list in chunk 275 | while (bytesSent < totalSize) { 276 | size_t chunkSize = std::min(BUFFER_SIZE, static_cast(totalSize - bytesSent)); 277 | ssize_t result = SSL_write(ssl, buffer.data() + bytesSent, chunkSize); 278 | 279 | if (result == -1) { 280 | clientInfo(); 281 | std::cerr << "Failed to send directory files: " << std::strerror(errno) << "\n"; 282 | ERR_print_errors_fp(stderr); 283 | SSL_shutdown(ssl); 284 | SSL_free(ssl); 285 | return; 286 | } 287 | 288 | bytesSent += result; 289 | } 290 | 291 | clientInfo(); 292 | std::cout << "Successfully sent directory files to client.\n"; 293 | } 294 | 295 | void clientHandler::uploadFile() { 296 | sendLoggedInStatus(); 297 | if (!isLoggedin) { 298 | clientInfo(); 299 | std::cout << "User not logged in\n"; 300 | return; 301 | } 302 | clientInfo(); 303 | std::lock_guard lock(folderLocks[username]); 304 | 305 | std::vector buffer(BUFFER_SIZE, 0); 306 | 307 | // recv file metadata (filename:filesize) 308 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size()); 309 | if (bytesRecv <= 0) { 310 | clientInfo(); 311 | std::cerr << "Client disconnected or error receiving metadata: " << std::strerror(errno) << "\n"; 312 | return; 313 | } 314 | 315 | std::string metadata(buffer.data(), bytesRecv); 316 | 317 | // Validate and parse metadata 318 | size_t partition = metadata.find(":"); 319 | if (partition == std::string::npos) { 320 | clientInfo(); 321 | std::cerr << "Invalid metadata format received from client\n"; 322 | return; 323 | } 324 | 325 | std::string filename = metadata.substr(0, partition); 326 | std::string filesizeStr = metadata.substr(partition + 1); 327 | 328 | std::cout << "filesizeStr: " << filesizeStr << std::endl; 329 | 330 | // validate filename 331 | std::regex validFilenameRegex("^[a-zA-Z0-9._-]+$"); 332 | if (!std::regex_match(filename, validFilenameRegex)) { 333 | clientInfo(); 334 | std::cerr << "Invalid filename received: " << filename << "\n"; 335 | return; 336 | } 337 | 338 | // validate filesize 339 | off_t filesize = 0; 340 | try { 341 | filesize = std::stoull(filesizeStr); 342 | if (filesize <= 0) { 343 | throw std::invalid_argument("Filesize must be positive"); 344 | } 345 | } catch (const std::exception& e) { 346 | clientInfo(); 347 | std::cerr << "Invalid filesize received: " << filesizeStr << " (" << e.what() << ")\n"; 348 | return; 349 | } 350 | 351 | clientInfo(); 352 | std::cout << "Filename: " << filename << "\n"; 353 | clientInfo(); 354 | std::cout << "Filesize: " << filesize << " bytes\n"; 355 | 356 | // send ACK 357 | if (SSL_write(ssl, "OK", 2) <= 0) { 358 | clientInfo(); 359 | std::cerr << "Failed sending ACK: " << std::strerror(errno) << "\n"; 360 | ERR_print_errors_fp(stderr); 361 | return; 362 | } 363 | 364 | // prepare write to compressed file 365 | std::string compressedFilepath = folderdir + "/" + filename + ".gz"; 366 | gzFile compressedfile = gzopen(compressedFilepath.c_str(), "wb"); 367 | if (!compressedfile) { 368 | clientInfo(); 369 | std::cerr << "Failed to create compressed file: " << std::strerror(errno) << "\n"; 370 | return; 371 | } 372 | 373 | // recv file content in chunks 374 | size_t remaining = filesize; 375 | 376 | // std::cout << "remainning at start: " << remaining << std::endl; 377 | while (remaining > 0) { 378 | buffer.assign(BUFFER_SIZE, 0); 379 | 380 | size_t chunkSize = std::min(remaining, static_cast(BUFFER_SIZE)); 381 | bytesRecv = SSL_read(ssl, buffer.data(), chunkSize); 382 | if (bytesRecv <= 0) { 383 | clientInfo(); 384 | std::cerr << "Client disconnected during file transfer: " << std::strerror(errno) << "\n"; 385 | ERR_print_errors_fp(stderr); 386 | gzclose(compressedfile); 387 | return; 388 | } 389 | 390 | // write to compressed file 391 | int bytesWritten = gzwrite(compressedfile, buffer.data(), bytesRecv); 392 | if (bytesWritten != bytesRecv) { 393 | clientInfo(); 394 | std::cerr << "Error writing to compressed file\n"; 395 | gzclose(compressedfile); 396 | return; 397 | } 398 | 399 | remaining -= bytesRecv; 400 | 401 | // std::cout << "remainning now: " << remaining << std::endl; 402 | } 403 | 404 | // std::cout << "remainning at end: " << remaining << std::endl; 405 | 406 | gzclose(compressedfile); 407 | 408 | // Wait for end-of-transfer marker 409 | buffer.assign(BUFFER_SIZE, 0); 410 | bytesRecv = SSL_read(ssl, buffer.data(), buffer.size()); 411 | if (bytesRecv <= 0 || std::string(buffer.data(), bytesRecv) != "UPLOAD_COMPLETE") { 412 | clientInfo(); 413 | std::cerr << "Failed to receive end-of-transfer marker from client\n"; 414 | return; 415 | } 416 | 417 | clientInfo(); 418 | std::cout << "File received and saved to " << compressedFilepath << "\n"; 419 | } 420 | 421 | void clientHandler::downloadFile() { 422 | sendLoggedInStatus(); 423 | if (!isLoggedin) { 424 | clientInfo(); 425 | std::cout << "User not logged in\n"; 426 | return; 427 | } 428 | clientInfo(); 429 | std::cout << "user logged in\n"; 430 | std::vector buffer(BUFFER_SIZE, 0); 431 | 432 | // Receive filename 433 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size()); 434 | if (bytesRecv <= 0) { 435 | clientInfo(); 436 | std::cerr << "Client disconnected or error receiving filename: " << std::strerror(errno) << "\n"; 437 | ERR_print_errors_fp(stderr); 438 | return; 439 | } 440 | 441 | std::string filename(buffer.data(), bytesRecv); 442 | filename += ".gz"; 443 | std::string filepath = folderdir + "/" + filename; 444 | 445 | int filefd = open(filepath.c_str(), O_RDONLY); 446 | if (filefd == -1) { 447 | clientInfo(); 448 | std::cerr << "File not found: " << filename << "\n"; 449 | std::string errorMsg = "ERROR: File Not Found"; 450 | if (SSL_write(ssl, errorMsg.c_str(), errorMsg.size()) <= 0) { 451 | clientInfo(); 452 | std::cerr << "Failed to send error message to client: " << std::strerror(errno) << "\n"; 453 | ERR_print_errors_fp(stderr); 454 | } 455 | return; 456 | } 457 | 458 | off_t filesize = utils::getFilesize(filefd); 459 | if (filesize == -1) { 460 | clientInfo(); 461 | std::cerr << "Failed to get file size: " << std::strerror(errno) << "\n"; 462 | close(filefd); 463 | return; 464 | } 465 | 466 | // Send filesize 467 | std::string fileSizeMsg = std::to_string(filesize); 468 | if (SSL_write(ssl, fileSizeMsg.c_str(), fileSizeMsg.size()) <= 0) { 469 | clientInfo(); 470 | std::cerr << "Failed to send compressed file size: " << std::strerror(errno) << "\n"; 471 | ERR_print_errors_fp(stderr); 472 | close(filefd); 473 | return; 474 | } 475 | 476 | // Receive ACK 477 | buffer.assign(BUFFER_SIZE, 0); 478 | bytesRecv = SSL_read(ssl, buffer.data(), 2); // Expecting "OK" 479 | if (bytesRecv <= 0 || std::string(buffer.data(), bytesRecv) != "OK") { 480 | clientInfo(); 481 | std::cerr << "Client failed to ACK file size: " << std::strerror(errno) << "\n"; 482 | ERR_print_errors_fp(stderr); 483 | close(filefd); 484 | return; 485 | } 486 | 487 | // Check if Kernel TLS is enabled 488 | bool isKernelTLS = (BIO_get_ktls_send(SSL_get_rbio(ssl)) > 0); 489 | if (isKernelTLS) { 490 | clientInfo(); 491 | std::cerr << "Kernel TLS enabled, using SSL_sendfile for download\n"; 492 | } else { 493 | clientInfo(); 494 | std::cerr << "Kernel TLS not enabled, falling back to read + SSL_write\n"; 495 | } 496 | 497 | 498 | // disable TCP_NODELAY 499 | int nodelay_flag = 0; 500 | int server_fd; 501 | server_fd = SSL_get_fd(ssl); 502 | if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, sizeof(nodelay_flag)) == -1) { 503 | std::cerr << "Failed to disable TCP_NODELAY: " << std::strerror(errno) << "\n"; 504 | } 505 | std::cout << "Disabled TCP_NODELAY\n"; 506 | 507 | // // enable TCP_CORK 508 | // int cork_flag = 1; 509 | // int server_fd; 510 | // server_fd = SSL_get_fd(ssl); 511 | // if (setsockopt(server_fd, IPPROTO_TCP, TCP_CORK, &cork_flag, sizeof(cork_flag)) == -1) { 512 | // std::cerr << "Failed to enable TCP_CORK: " << std::strerror(errno) << "\n"; 513 | // } 514 | // std::cout << "Enabled TCP_CORK\n"; 515 | 516 | // Send file in chunks 517 | off_t totalBytesSent = 0; 518 | off_t bytesRemaining = filesize; 519 | const size_t chunkSize = 65536; 520 | 521 | while (bytesRemaining > 0) { 522 | size_t toSend = std::min(static_cast(bytesRemaining), chunkSize); 523 | ssize_t bytesSent = 0; 524 | 525 | if (isKernelTLS) { // Use SSL_sendfile for kTLS-enabled systems 526 | bytesSent = SSL_sendfile(ssl, filefd, totalBytesSent, toSend, 0); 527 | } else { // Fallback: Use regular SSL_write for non-kTLS systems 528 | std::vector buffer(toSend); 529 | ssize_t readBytes = pread(filefd, buffer.data(), toSend, totalBytesSent); 530 | if (readBytes > 0) { 531 | bytesSent = SSL_write(ssl, buffer.data(), readBytes); 532 | } 533 | totalBytesSent += bytesSent; 534 | } 535 | 536 | if (bytesSent <= 0) { 537 | clientInfo(); 538 | std::cerr << "Failed to send file: " << std::strerror(errno) << "\n"; 539 | ERR_print_errors_fp(stderr); 540 | close(filefd); 541 | return; 542 | } 543 | 544 | bytesRemaining -= bytesSent; 545 | } 546 | 547 | clientInfo(); 548 | std::cout << "Compressed file sent to client successfully.\n"; 549 | 550 | close(filefd); 551 | 552 | nodelay_flag = 1; 553 | if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay_flag, sizeof(nodelay_flag)) == -1) { 554 | std::cerr << "Failed to enable TCP_NODELAY: " << std::strerror(errno) << "\n"; 555 | } 556 | std::cout << "Enabled TCP_NODELAY\n"; 557 | // // disable TCP_CORK 558 | // cork_flag = 0; 559 | // if (setsockopt(server_fd, IPPROTO_TCP, TCP_CORK, &cork_flag, sizeof(cork_flag)) == -1) { 560 | // std::cerr << "Failed to disable TCP_CORK: " << std::strerror(errno) << "\n"; 561 | // } 562 | // std::cout << "Disabled TCP_CORK\n"; 563 | } 564 | 565 | void clientHandler::renameFile() { 566 | sendLoggedInStatus(); 567 | if (!isLoggedin) { 568 | clientInfo(); 569 | std::cout << "User not logged in\n"; 570 | return; 571 | } 572 | clientInfo(); 573 | std::lock_guard lock(folderLocks[username]); 574 | std::vector buffer(BUFFER_SIZE, 0); 575 | 576 | // Receive file metadata 577 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size()); 578 | if (bytesRecv <= 0) { 579 | if (bytesRecv == 0) { 580 | clientInfo(); 581 | std::cerr << "Client disconnected while receiving metadata.\n"; 582 | } else { 583 | clientInfo(); 584 | std::cerr << "Error receiving metadata: " << std::strerror(errno) << "\n"; 585 | ERR_print_errors_fp(stderr); 586 | } 587 | return; 588 | } 589 | 590 | std::string metadata(buffer.data(), bytesRecv); 591 | 592 | std::string::size_type partition = metadata.find(":"); 593 | if (partition == std::string::npos) { 594 | clientInfo(); 595 | std::cerr << "Invalid metadata received: " << metadata << "\n"; 596 | std::string errorMsg = "ERROR: Invalid metadata format"; 597 | if (SSL_write(ssl, errorMsg.c_str(), errorMsg.size()) <= 0) { 598 | clientInfo(); 599 | std::cerr << "Failed to send error message to client: " << std::strerror(errno) << "\n"; 600 | ERR_print_errors_fp(stderr); 601 | } 602 | return; 603 | } 604 | 605 | std::string filename = metadata.substr(0, partition); 606 | std::string newname = metadata.substr(partition + 1); 607 | 608 | std::string filepath = folderdir + "/" + filename + ".gz"; 609 | std::string newpath = folderdir + "/" + newname + ".gz"; 610 | 611 | clientInfo(); 612 | std::cout << "Old path: " << filepath << "\n"; 613 | clientInfo(); 614 | std::cout << "New path: " << newpath << "\n"; 615 | 616 | // Rename file 617 | if (rename(filepath.c_str(), newpath.c_str()) != 0) { 618 | clientInfo(); 619 | std::cerr << "Failed to rename file: " << std::strerror(errno) << "\n"; 620 | std::string errorMsg = "ERROR: Failed to rename file"; 621 | if (SSL_write(ssl, errorMsg.c_str(), errorMsg.size()) <= 0) { 622 | clientInfo(); 623 | std::cerr << "Failed to send error message to client: " << std::strerror(errno) << "\n"; 624 | ERR_print_errors_fp(stderr); 625 | } 626 | return; 627 | } 628 | 629 | // Send success response 630 | std::string successMsg = "SUCCESS"; 631 | if (SSL_write(ssl, successMsg.c_str(), successMsg.size()) <= 0) { 632 | clientInfo(); 633 | std::cerr << "Failed to send success message: " << std::strerror(errno) << "\n"; 634 | ERR_print_errors_fp(stderr); 635 | return; 636 | } 637 | 638 | clientInfo(); 639 | std::cout << "File renamed successfully.\n"; 640 | } 641 | 642 | void clientHandler::deleteFile() { 643 | sendLoggedInStatus(); 644 | if (!isLoggedin) { 645 | clientInfo(); 646 | std::cout << "User not logged in\n"; 647 | return; 648 | } 649 | clientInfo(); 650 | std::lock_guard lock(folderLocks[username]); 651 | // Receive filename 652 | std::vector buffer(BUFFER_SIZE, 0); 653 | int bytesRecv = SSL_read(ssl, buffer.data(), buffer.size()); 654 | if (bytesRecv <= 0) { 655 | if (bytesRecv == 0) { 656 | clientInfo(); 657 | std::cerr << "Client disconnected while receiving filename.\n"; 658 | } else { 659 | clientInfo(); 660 | std::cerr << "Error receiving filename: " << std::strerror(errno) << "\n"; 661 | ERR_print_errors_fp(stderr); 662 | } 663 | return; 664 | } 665 | 666 | std::string filename(buffer.data(), bytesRecv); 667 | 668 | std::string filepath = folderdir + "/" + filename + ".gz"; 669 | 670 | // Delete file 671 | if (remove(filepath.c_str()) != 0) { 672 | clientInfo(); 673 | std::cerr << "Failed to delete file: " << std::strerror(errno) << "\n"; 674 | std::string errorMsg = "ERROR: Failed to delete file"; 675 | if (SSL_write(ssl, errorMsg.c_str(), errorMsg.size()) <= 0) { 676 | clientInfo(); 677 | std::cerr << "Failed to send error message to client: " << std::strerror(errno) << "\n"; 678 | ERR_print_errors_fp(stderr); 679 | } 680 | return; 681 | } 682 | 683 | // Send success response 684 | std::string successMsg = "SUCCESS"; 685 | if (SSL_write(ssl, successMsg.c_str(), successMsg.size()) <= 0) { 686 | clientInfo(); 687 | std::cerr << "Failed to send success message: " << std::strerror(errno) << "\n"; 688 | ERR_print_errors_fp(stderr); 689 | return; 690 | } 691 | 692 | clientInfo(); 693 | std::cout << "File successfully deleted: " << filepath << "\n"; 694 | } 695 | --------------------------------------------------------------------------------