From e106fa6f6b0e5f775ab9124a7e57ffdc7a56158b Mon Sep 17 00:00:00 2001 From: Wirlaburla Date: Sun, 10 Dec 2023 22:02:43 -0600 Subject: [PATCH] Created Repo --- CMakeLists.txt | 10 ++ conf/worlio-ftp.conf | 14 +++ src/CMakeLists.txt | 4 + src/client.cpp | 221 ++++++++++++++++++++++++++++++++++++++++ src/filer.cpp | 233 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 235 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp.bak | 184 +++++++++++++++++++++++++++++++++ src/server.h | 13 +++ src/tunnel.cpp.bak | 216 +++++++++++++++++++++++++++++++++++++++ src/util.h | 52 ++++++++++ 10 files changed, 1182 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 conf/worlio-ftp.conf create mode 100644 src/CMakeLists.txt create mode 100644 src/client.cpp create mode 100644 src/filer.cpp create mode 100644 src/main.cpp create mode 100644 src/main.cpp.bak create mode 100644 src/server.h create mode 100644 src/tunnel.cpp.bak create mode 100644 src/util.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..864dde8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.26) +PROJECT(worlioftp) + +SET(CMAKE_CXX_STANDARD 20) + +#SET(CMAKE_VERBOSE_MAKEFILE ON) + +#ADD_DEFINITIONS(-Wall -O2) + +ADD_SUBDIRECTORY(src) diff --git a/conf/worlio-ftp.conf b/conf/worlio-ftp.conf new file mode 100644 index 0000000..397a0cf --- /dev/null +++ b/conf/worlio-ftp.conf @@ -0,0 +1,14 @@ +[general] +name=Worlio FTP +contact=admin@localhost +location=US +motd=Welcome! +version=0.0 +path=/data/ftp-test + +[network] +address=0.0.0.0 +port=21 +data-port=20 +active=true +passive=true \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..91cb6c5 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( + worlioftp + main.cpp +) diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..922da93 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "filer.cpp" +#include "util.h" + +namespace fs = std::filesystem; + +class Client { +public: + int control_sock; + /* == State + * -1 - Delete + * 0 - Visitor / Unauthenticated + * 1 - Authenticated + * 2 - Waiting for data + */ + int state = 0; + + Client(int &_sock) { + control_sock = _sock; + submit(230, "Init"); + }; + + int receive(std::string cmd, std::string argstr) { + if (cmd == "QUIT") { + submit(250, "Goodbye!"); + return -1; + } else if (state > 0) { + if (cmd == "SYST") { + submit(215, "UNIX"); + } else if (cmd == "PWD") { + submit(257, "'"+filer->cwd.string()+"'"); + } else if (cmd == "CWD") { + int rc = filer->traverse(argstr); + if (rc == 0) { + submit(250, "OK"); + } else if (rc == -1) { + submit(500, "Not found!"); + } else { + submit(500, std::to_string(rc)); + } + } else if (cmd == "CDUP") { + int rc = filer->traverse(".."); + if (rc == 0) { + submit(250, "OK"); + } else if (rc == -1) { + submit(500, "Not found!"); + } else { + submit(500, std::to_string(rc)); + } + } else if (cmd == "MKD") { + int rc = filer->createDirectory(argstr); + if (rc == 0) { + submit(250, "OK"); + } else { + submit(500, std::to_string(rc)); + } + } else if (cmd == "TYPE") { + sscanf(argstr.c_str(), "%c", &(filer->type)); + submit(226, "OK"); + } else if (cmd == "PASV") { + submit(502, "Command not implemented"); + } else if (cmd == "PORT") { + data_sock = socket(AF_INET, SOCK_STREAM, 0); + + unsigned char act_port[2]; + int act_ip[4], port_dec; + char ip_decimal[40]; + sscanf(argstr.c_str(), "%d,%d,%d,%d,%d,%d", &act_ip[0], &act_ip[1], &act_ip[2], &act_ip[3], (int*)&act_port[0], (int*)&act_port[1]); + sprintf(ip_decimal, "%d.%d.%d.%d", act_ip[0], act_ip[1], act_ip[2], act_ip[3]); + port_dec = act_port[0]*256+act_port[1]; + printf("[d] D(%i) Initialized data transport: %s:%d\n", control_sock, ip_decimal, port_dec); + + data_address.sin_family = AF_INET; + data_address.sin_addr.s_addr = inet_addr(ip_decimal); + data_address.sin_port = htons(port_dec); + + if (connect(data_sock, (struct sockaddr *)&data_address, sizeof(data_address)) != 0) { + close(data_sock); + data_sock = 0; + submit(425, "Unknown Error"); + } else { + state = 2; + submit(200, "OK"); + } + } else if (cmd == "SIZE") { + int ret = filer->fileSize(argstr); + if (ret >= 0) { + submit(213, std::to_string(ret)); + } else { + submit(550, "Access Denied "+std::to_string(ret)); + } + } else if (cmd == "FEAT") { + submit(211, ":\r\n211 END"); + } else if (cmd == "NOOP") { + submit(226, "OK"); + } else if (cmd == "DELE" || cmd == "RMD") { + int ret = filer->deleteFile(argstr); + if (ret == 0) { + submit(226, "OK"); + } else { + submit(550, "Access Denied "+std::to_string(ret)); + } + } else if (state == 2) { + if (data_sock <= 0) return -1; + if (cmd == "LIST" || cmd == "NLST") { + submit(150, "Transferring"); + + std::string output = filer->list(); + char out[output.size()] = {0}; + strncpy(out, output.c_str(), output.size()); + data_submit(out, output.size()); + + submit(226, "OK"); + } else if (cmd == "RETR") { + struct file_data fd = filer->readFile(argstr); + if (fd.ecode == 0) { + submit(150, "Transferring"); + data_submit(fd.data, fd.size); + } else { + if (fd.ecode == -3) submit(550, "I refuse to transmit in ASCII mode!"); + else submit(550, "Access Denied"); + } + // Segfault on freeing ASCII data, memory leak on binary. + if (filer->type != 'A') free(fd.data); + submit(226, "OK"); + } else if (cmd == "STOR") { + unsigned char inbuf[BUFFERSIZE] = {0}; + submit(150, "Transferring"); + int psize; + // Write nothing to file to make sure it exists. + // Also so I don't have to write a more complex writer. + int ret = filer->writeFile(argstr, inbuf, 0); + while ((psize = recv(data_sock, inbuf, sizeof(inbuf), 0)) > 0) { + ret = filer->appendFile(argstr, inbuf, psize); + if (ret < 0) { + submit(550, "Access Denied "+std::to_string(ret)); + data_close(); + return -1; + } + for (int i = 0; i < BUFFERSIZE; i++) inbuf[i] = 0; + } + submit(226, "OK"); + } + if (data_close() != 0) { + printf("[!] Data Sock returned %i instead of 0! Not closed!!!\n", data_sock); + } + } else { + submit(502, "Command not implemented"); + } + } else { + state = 0; // If we reach here, force state to zero just in case. + if (cmd == "USER") { + name = argstr; + submit(331, "Password required"); + } else if (cmd == "PASS") { + if (argstr == "123") { + printf("[d] (%i) logged in as '%s'\n", control_sock, name.c_str()); + // We can now set the root safely (I hope). + filer->setRoot("/data/ftp-test/"+name); + state = 1; + submit(230, "Login successful"); + } else { + submit(530, "Authentication refused."); + return -1; + } + } else if (cmd == "AUTH") { + submit(502, "Command not implemented"); + } else { + submit(332, "Login Required"); + } + } + return 0; + } + + int submit(int code, std::string msg) { + std::string out = std::to_string(code)+" "+msg+"\r\n"; + int bytes = send(control_sock, out.c_str(), out.size(), 0); + if (bytes < 0) { + printf("[d] C(%i) !< %i %s\n", control_sock, code, msg.c_str()); + return 1; + } + printf("[d] C(%i) << %i %s\n", control_sock, code, msg.c_str()); + return 0; + } + + int data_submit(char* out, int size) { + int bytes = send(data_sock, out, size, 0); + if (bytes < 0) { + printf("[d] D(%i) !< %i\n", data_sock, size); + return 1; + } + printf("[d] D(%i) << %i\n", data_sock, size); + return 0; + } + + int data_close() { + printf("[d] D(%i) Closing...\n", data_sock); + close(data_sock); + data_sock = 0; + state = 1; + return data_sock; // Should be 0 if successful. + } + +private: + std::string name; + Filer* filer = new Filer(); + + int data_sock; + struct sockaddr_in data_address; +}; \ No newline at end of file diff --git a/src/filer.cpp b/src/filer.cpp new file mode 100644 index 0000000..01cf5e4 --- /dev/null +++ b/src/filer.cpp @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include +#include + +/* === THE FILER === + * This class and its structs handle file operations for + * specified users. Its goal is an easy, consistent, and + * modular class to simply call on from inside Client. + * + * A Filer object has its defined root that it should never + * break out of. If it does, best hope we aren't running as + * root. + */ + +/* == STATUS CODES == + * + * -3 - Uhhhhhh + * -2 - Invalid Permissions + * -1 - File Not Found + * 0 - Generic Success + */ + +struct file_data { + char* data; + int size; + int ecode; +}; + +namespace fs = std::filesystem; + +class Filer { +public: + fs::path cwd; + char type = 'A'; + + Filer() {}; + + int traverse(std::string dir) { + fs::path ndir = fs::weakly_canonical(cwd / dir); + fs::path fdir = fullPath(dir); + printf("[i] Traversing: %s\n", ndir.c_str()); + if (fdir.string().rfind(root.string(), 0) != 0) return -2; + else if (!fs::exists(fdir) || !fs::is_directory(fdir)) return -1; + else { + cwd = ndir; + return 0; + } + } + + int setRoot(std::string _root) { + fs::path froot = fs::weakly_canonical(_root); + printf("[i] Setting root: %s\n", froot.c_str()); + if (!fs::exists(froot)) fs::create_directory(froot); + root = fs::absolute(froot); + cwd = "/"; + return 0; + } + + int createDirectory(std::string dir) { + fs::path ndir = fs::weakly_canonical(cwd / dir); + fs::path fdir = fullPath(dir); + if (fdir.string().rfind(root.string(), 0) != 0) return -2; + else if (fs::exists(fdir)) return -1; + else { + fs::create_directory(fdir); + return 0; + } + } + + fs::path fullPath() { + return fs::weakly_canonical(root.string()+"/"+cwd.string()); + } + + fs::path fullPath(std::string in) { + return fs::weakly_canonical(root.string()+"/"+(cwd / in).string()); + } + + int fileSize(std::string name) { + fs::path nfile = fs::weakly_canonical(cwd / name); + fs::path ffile = fullPath(name); + printf("[i] Retreiving filesize: %s\n", nfile.c_str()); + if (ffile.string().rfind(root.string(), 0) != 0) return -2; + else if (!fs::exists(ffile)) return -1; + else if (type == 'A') return -3; + else { + std::ifstream infile(ffile, std::ios::in|std::ios::binary|std::ios::ate); + if (infile.is_open()) { + return infile.tellg(); + } else return -2; + } + return 0; + } + + int deleteFile(std::string name) { + fs::path nfile = fs::weakly_canonical(cwd / name); + fs::path ffile = fullPath(name); + printf("[i] Deleting file: %s\n", nfile.c_str()); + if (ffile.string().rfind(root.string(), 0) != 0) return -2; + else if (!fs::exists(ffile)) return -1; + else { + if (fs::is_directory(ffile) && !fs::is_empty(ffile)) return -5; + else return fs::remove(ffile)?0:-4; + } + } + + file_data readFile(std::string name) { + struct file_data fd; + + fs::path nfile = fs::weakly_canonical(cwd / name); + fs::path ffile = fullPath(name); + printf("[i] Retreiving file: %s\n", nfile.c_str()); + if (ffile.string().rfind(root.string(), 0) != 0) fd.ecode = -2; + else if (!fs::exists(ffile)) fd.ecode = -1; + else if (type == 'A') { + std::ifstream infile(ffile, std::ios::in|std::ios::ate); + if (infile.is_open()) { + std::string filedata; + for (std::string line; getline(infile, line);) { + filedata+=(line+"\r\n"); + } + fd.size = filedata.size(); + strncpy(fd.data, filedata.c_str(), fd.size); + infile.close(); + fd.ecode = 0; + } else fd.ecode = -2; + } else { + std::ifstream infile(ffile, std::ios::in|std::ios::binary|std::ios::ate); + if (infile.is_open()) { + fd.size = infile.tellg(); + fd.data = new char[fd.size]; + infile.seekg(0, std::ios::beg); + infile.read(fd.data, fd.size); + infile.close(); + fd.ecode = 0; + } else fd.ecode = -2; + } + return fd; + } + + // Yes, there are two separate functions for essentially the same thing + // but with a different flag. Yes, I could've easily combined the two. + // No, I don't wanna. + int writeFile(std::string name, unsigned char* data, int size) { + fs::path nfile = fs::weakly_canonical(cwd / name); + fs::path ffile = fullPath(name); + printf("[i] Storing file: %s\n", nfile.c_str()); + if (ffile.string().rfind(root.string(), 0) != 0) return -2; + else { + std::ofstream outfile(ffile, std::ios::out|std::ios::binary); + outfile.write((char *)data, size); + outfile.close(); + return size; + + } + return 0; + } + + int appendFile(std::string name, unsigned char* data, int size) { + fs::path nfile = fs::weakly_canonical(cwd / name); + fs::path ffile = fullPath(name); + if (ffile.string().rfind(root.string(), 0) != 0) return -2; + else { + std::ofstream outfile(ffile, std::ios::out|std::ios::binary|std::ios::app); + outfile.write((char *)data, size); + outfile.close(); + return size; + + } + return 0; + } + + // Gets a list of files and folders within current working directory. + // Outputs in the format of 'ls -lA'. + std::string list() { + std::ostringstream listStream; + // Not checking for pwd existence. If it doesn't exist and we + // got this far, we fucked up anyway. + for(fs::directory_entry const& p : fs::directory_iterator(fullPath())) { + char *line; + + // Stole part of this from here: + // https://github.com/Siim/ftp/blob/master/handles.c#L154 + // It's amazing how simple shit is missing from std::filesystem + // Thanks boost! + struct stat fstat; + struct tm *time; + time_t rawtime; + char timebuff[80]; + + if (stat(p.path().c_str(), &fstat) == -1) { + return ""; + } + + /* Convert time_t to tm struct */ + rawtime = fstat.st_mtime; + time = localtime(&rawtime); + strftime(timebuff, 80, "%b %d %H:%M", time); + + // God should've smitten me before I wrote such attrocities. + fs::perms fperms = fs::status(p).permissions(); + asprintf( + &line, + "%c%c%c%c%c%c%c%c%c%c %u %4u %4u %12u %s %s\r\n", + p.is_directory()?'d':'-', + (fperms & fs::perms::owner_read) != fs::perms::none?'r':'-', + (fperms & fs::perms::owner_write) != fs::perms::none?'w':'-', + (fperms & fs::perms::owner_exec) != fs::perms::none?'x':'-', + (fperms & fs::perms::group_read) != fs::perms::none?'r':'-', + (fperms & fs::perms::group_write) != fs::perms::none?'w':'-', + (fperms & fs::perms::group_exec) != fs::perms::none?'x':'-', + (fperms & fs::perms::others_read) != fs::perms::none?'r':'-', + (fperms & fs::perms::others_write) != fs::perms::none?'w':'-', + (fperms & fs::perms::others_exec) != fs::perms::none?'x':'-', + fs::hard_link_count(p), + fstat.st_uid, + fstat.st_gid, + fstat.st_size, + timebuff, + p.path().filename().c_str() + ); + + listStream << std::string(line); + free(line); + } + return listStream.str(); + } + +private: + fs::path root; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..341eb8d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "client.cpp" +#include "util.h" + +using namespace std::chrono_literals; + +// I stole the majority* of this multi-client sock code from IBM +// of all places. Had to still modify it for my own uses but +// laziness wins again! + +struct pollfd fds[MAXCLIENTS]; +struct clientfd { + Client* client; + std::thread thread; + bool close = false; +} fdc[MAXCLIENTS]; + +bool run = true, compress_array = false; + +void runClient(struct clientfd* cfd) { + char inbuf[BUFFERSIZE]; + printf("[d] C(%i) Initialized\n", cfd->client->control_sock); + // Loop as long as it is a valid file descriptor. + while (fcntl(cfd->client->control_sock, F_GETFD) != -1) { + printf("[d] C(%i) Attempting read...\n", cfd->client->control_sock); + int rc = recv(cfd->client->control_sock, inbuf, sizeof(inbuf), 0); + if (rc < 0) { + if (errno != EWOULDBLOCK) { + perror("recv() failed"); + break; + } + continue; + } + + if (rc == 0 || cfd->client == nullptr) { + printf("[d] C(%i) closed\n", cfd->client->control_sock); + break; + } + + std::string lin(inbuf); + int len = lin.find("\r\n", 0); + int cmdend = lin.find(" ", 0); + if (cmdend >= len || cmdend == std::string::npos) cmdend = len; + std::string cmd = toUpper(lin.substr(0, cmdend)); + std::string args = ""; + if (len > cmdend) args = lin.substr(cmdend+1, len-cmdend-1); + + printf("[d] C(%i) >> '%s' '%s'\n", cfd->client->control_sock, cmd.c_str(), args.c_str()); + + if (cfd->client->receive(cmd, args) < 0) break; + inbuf[0] = '\0'; + } + printf("[d] C(%i) Marking for deletion...\n", cfd->client->control_sock); + cfd->close = true; + cfd->thread.detach(); +} + +int main(int argc , char *argv[]) { + int opt = 1, + master_socket = -1, + newsock = -1, + nfds = 1, + current_size = 0; + + char inbuf[BUFFERSIZE]; + struct sockaddr_in ctrl_address; + + if ((master_socket = socket(AF_INET , SOCK_STREAM , 0)) < 0) { + perror("socket() failed"); + exit(-1); + } + + if (setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { + perror("setsockopt() failed"); + close(master_socket); + exit(-1); + } + + if (ioctl(master_socket, FIONBIO, (char *)&opt) < 0) { + perror("ioctl() failed"); + close(master_socket); + exit(-1); + } + + ctrl_address.sin_family = AF_INET; + ctrl_address.sin_addr.s_addr = INADDR_ANY; + ctrl_address.sin_port = htons(CONTROL_PORT); + + if (bind(master_socket, (struct sockaddr *)&ctrl_address, sizeof(ctrl_address))<0 < 0) { + perror("bind() failed"); + close(master_socket); + exit(-1); + } + + if (listen(master_socket, 3) < 0) { + perror("listen() failed"); + close(master_socket); + exit(-1); + } + + memset(fds, 0 , sizeof(fds)); + memset(fdc, 0 , sizeof(fdc)); + + fds[0].fd = master_socket; + fds[0].events = POLLIN; + + printf("[i] Server started.\n"); + while (run) { + int pc = poll(fds, nfds, -1); + + if (pc < 0) { + perror("poll() failed"); + break; + } + + if (pc == 0) { + printf("poll() timed out\n"); + break; + } + + current_size = nfds; + for (int i = 0; i < current_size; i++) { + inbuf[0] = '\0'; + + if(fds[i].revents == 0) + continue; + + if(fds[i].revents != POLLIN) { + printf("[!] C(%i) Error! revents = %d\n", fds[i].fd, fds[i].revents); + goto conn_close; + } + + if (fds[i].fd == master_socket) { + do { + newsock = accept(master_socket, NULL, NULL); + if (newsock < 0) { + if (errno != EWOULDBLOCK) { + perror("accept() failed"); + run = false; + } + break; + } + + printf("[d] C(%i) Accepted client%i\n", newsock); + // If we assign thead if it is still attached, + // we suffer greatly, and by we I mean me. + if (fdc[nfds].thread.joinable()) { + printf("[!] C(%i) Thread still joinable! Detaching...\n", newsock); + // Pray the thread will end itself. + fdc[nfds].thread.detach(); + } + fds[nfds].fd = newsock; + fds[nfds].events = POLLIN; + fdc[nfds].client = new Client(newsock); + fdc[nfds].thread = std::thread(runClient, &fdc[nfds]); + nfds++; + } while (newsock != -1); + } else { + /* + int rc = recv(fds[i].fd, inbuf, sizeof(inbuf), 0); + if (rc < 0) { + if (errno != EWOULDBLOCK) { + perror("recv() failed"); + fdc[i].close = true; + } + continue; + } + + if (rc == 0 || fdc[i].client == nullptr) { + printf("[d] (%i) closed\n", fds[i].fd); + fdc[i].close = true; + } + + std::string lin(inbuf); + int len = lin.find("\r\n", 0); + int cmdend = lin.find(" ", 0); + if (cmdend >= len || cmdend == std::string::npos) cmdend = len; + std::string cmd = toUpper(lin.substr(0, cmdend)); + std::string args = ""; + if (len > cmdend) args = lin.substr(cmdend+1, len-cmdend-1); + + printf("[d] (%i) >> '%s' '%s'\n", fds[i].fd, cmd.c_str(), args.c_str()); + + if (fdc[i].client->receive(cmd, args) < 0) fdc[i].close = true; + inbuf[0] = '\0'; + */ + + if (fdc[i].close) { + conn_close: + printf("[d] C(%i) Deleting client...\n", fds[i].fd); + close(fds[i].fd); + fds[i].fd = -1; + if (fdc[i].thread.joinable()) fdc[i].thread.detach(); + fdc[i].client = nullptr; + fdc[i].close = false; + compress_array = true; + } + } + } + + if (compress_array) { + compress_array = false; + printf("[d] Compressing...\n"); + for (int i = 0; i < nfds; i++) { + if (fds[i].fd == -1) { + for(int j = i; j < nfds; j++) { + printf("[d] Compressing: id %i to fd %i\n", j, fds[j+1].fd); + fds[j].fd = fds[j+1].fd; + } + i--; + nfds--; + } + } + printf("[d] Compressing complete!\n"); + } + } + return 0; +} \ No newline at end of file diff --git a/src/main.cpp.bak b/src/main.cpp.bak new file mode 100644 index 0000000..12310bc --- /dev/null +++ b/src/main.cpp.bak @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "server.h" +#include "client.cpp" +#include "util.h" + +using namespace std::chrono_literals; + +// I stole the majority* of this multi-client sock code from IBM +// of all places. Had to still modify it for my own uses but +// laziness wins again! + +struct pollfd fds[MAXCLIENTS]; +Client* fclients[MAXCLIENTS]; +std::future fthreads[MAXCLIENTS]; + +bool run = true, compress_array = false; + +bool runClient(int sock, Client* client) { + char inbuf[BUFFERSIZE]; + int rc; + while (rc = recv(sock, inbuf, sizeof(inbuf), 0)) { + if (rc < 0) { + if (errno != EWOULDBLOCK) { + perror("recv() failed"); + break; + } + continue; + } + + if (rc == 0 || client == nullptr) { + printf("[d] (%i) closed\n", sock); + break; + } + + std::string lin(inbuf); + int len = lin.find("\r\n", 0); + int cmdend = lin.find(" ", 0); + if (cmdend >= len || cmdend == std::string::npos) cmdend = len; + std::string cmd = toUpper(lin.substr(0, cmdend)); + std::string args = ""; + if (len > cmdend) args = lin.substr(cmdend+1, len-cmdend-1); + + printf("[d] (%i) >> '%s' '%s'\n", client->control_sock, cmd.c_str(), args.c_str()); + + if (client->receive(cmd, args) < 0) break; + inbuf[0] = '\0'; + } + return true; +} + +int main(int argc , char *argv[]) { + int opt = 1, + master_socket = -1, + newsock = -1, + nfds = 1, + current_size = 0; + + char inbuf[BUFFERSIZE]; + struct sockaddr_in ctrl_address; + + if ((master_socket = socket(AF_INET , SOCK_STREAM , 0)) < 0) { + perror("socket() failed"); + exit(-1); + } + + if (setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { + perror("setsockopt() failed"); + close(master_socket); + exit(-1); + } + + if (ioctl(master_socket, FIONBIO, (char *)&opt) < 0) { + perror("ioctl() failed"); + close(master_socket); + exit(-1); + } + + ctrl_address.sin_family = AF_INET; + ctrl_address.sin_addr.s_addr = INADDR_ANY; + ctrl_address.sin_port = htons(CONTROL_PORT); + + if (bind(master_socket, (struct sockaddr *)&ctrl_address, sizeof(ctrl_address))<0 < 0) { + perror("bind() failed"); + close(master_socket); + exit(-1); + } + + if (listen(master_socket, 3) < 0) { + perror("listen() failed"); + close(master_socket); + exit(-1); + } + + memset(fds, 0 , sizeof(fds)); + + fds[0].fd = master_socket; + fds[0].events = POLLIN; + + while (run) { + int pc = poll(fds, nfds, -1); + + if (pc < 0) { + perror("poll() failed"); + break; + } + + if (pc == 0) { + printf("poll() timed out. End program.\n"); + break; + } + + current_size = nfds; + for (int i = 0; i < current_size; i++) { + inbuf[0] = '\0'; + /* + if(fds[i].revents == 0) + continue; + + if(fds[i].revents != POLLIN) { + printf("[!] Error! revents = %d\n", fds[i].revents); + run = false; + break; + }*/ + + if (fds[i].fd == master_socket) { + do { + newsock = accept(master_socket, NULL, NULL); + if (newsock < 0) { + if (errno != EWOULDBLOCK) { + perror("accept() failed"); + run = false; + } + break; + } + + printf("[d] (%i) initialized\n", newsock); + fds[nfds].fd = newsock; + fds[nfds].events = POLLIN; + Client* newClient = new Client(newsock); + fclients[nfds] = newClient; + fthreads[nfds] = std::async(runClient, newsock, newClient); + nfds++; + } while (newsock != -1); + } else { + if (fthreads[i].wait_for(0s) == std::future_status::ready) { + close(fds[i].fd); + fds[i].fd = -1; + compress_array = true; + fclients[i] = nullptr; + } + } + } + + if (compress_array) { + compress_array = false; + for (int i = 0; i < nfds; i++) { + if (fds[i].fd == -1) { + for(int j = i; j < nfds; j++) { + fds[j].fd = fds[j+1].fd; + } + i--; + nfds--; + } + } + } + } + return 0; +} \ No newline at end of file diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..a64dafe --- /dev/null +++ b/src/server.h @@ -0,0 +1,13 @@ +#ifndef SERVER_H +#define SERVER_H + +// BUNCH OF BULLSHIT +#define APPNAME "tunnelFTP (Worlio Bridge)" +#define APPVER "0.0" +#define CONTROL_PORT 21 +#define DATA_PORT 20 +#define BUFFERSIZE 1024*512 +#define MAXCLIENTS 256 +#define BASEDIR "/data/ftp-test" + +#endif \ No newline at end of file diff --git a/src/tunnel.cpp.bak b/src/tunnel.cpp.bak new file mode 100644 index 0000000..561f120 --- /dev/null +++ b/src/tunnel.cpp.bak @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tunnel.h" + +int main(int argc , char *argv[]) { + int opt = 1; + int master_socket, + addrlen, + new_socket, + control_socket[MAXCLIENTS], + data_socket[MAXCLIENTS], + activity, + cs; + char* client_pwd[MAXCLIENTS]; + char* client_user[MAXCLIENTS]; + int max_sd; + struct sockaddr_in ctrl_address; + struct sockaddr_in data_address; + + char send_buffer[BUFFERSIZE], + receive_buffer[BUFFERSIZE]; + + fd_set readfds; + + for (int i = 0; i < MAXCLIENTS; i++) { + control_socket[i] = 0; + } + + if((master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0) { + perror("socket failed"); + exit(EXIT_FAILURE); + } + + if(setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { + perror("setsockopt"); + exit(EXIT_FAILURE); + } + + ctrl_address.sin_family = AF_INET; + ctrl_address.sin_addr.s_addr = INADDR_ANY; + ctrl_address.sin_port = htons(CONTROL_PORT); + + if (bind(master_socket, (struct sockaddr *)&ctrl_address, sizeof(ctrl_address))<0) { + perror("bind failed"); + exit(EXIT_FAILURE); + } + printf("Listener on port %d\n", CONTROL_PORT); + + if (listen(master_socket, 3) < 0) { + perror("listen"); + exit(EXIT_FAILURE); + } + + //accept the incoming connection + addrlen = sizeof(ctrl_address); + puts("Waiting for connections ..."); + + while(1) { + FD_ZERO(&readfds); + FD_SET(master_socket, &readfds); + max_sd = master_socket; + + send_buffer[0] = '\0'; + receive_buffer[0] = '\0'; + + //add child sockets to set + for (int i = 0; i < MAXCLIENTS; i++) { + //socket descriptor + cs = control_socket[i]; + + //if valid socket descriptor then add to read list + if(cs > 0) + FD_SET(cs, &readfds); + + //highest file descriptor number, need it for the select function + if(cs > max_sd) + max_sd = cs; + } + + //wait for an activity on one of the sockets , timeout is NULL , + //so wait indefinitely + activity = select(max_sd + 1, &readfds, NULL, NULL, NULL); + + if ((activity < 0) && (errno!=EINTR)) { + printf("select error"); + } + + if (FD_ISSET(master_socket, &readfds)) { + if ((new_socket = accept(master_socket, (struct sockaddr *)&ctrl_address, (socklen_t*)&ctrl_address))<0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + //inform user of socket number - used in send and receive commands + printf("[DEBUG] New connection: socket fd %d, ip %s, port %d\n", new_socket, inet_ntoa(ctrl_address.sin_addr), ntohs(ctrl_address.sin_port)); + + //send new connection greeting message + sprintf(send_buffer, "220 %s %s\r\n", APPNAME, APPVER); + if(send(new_socket, send_buffer, strlen(send_buffer), 0) < 0) + perror("send"); + + //add new socket to array of sockets + for (int i = 0; i < MAXCLIENTS; i++) { + //if position is empty + if(control_socket[i] == 0) { + control_socket[i] = new_socket; + client_pwd[i] = ""; + printf("Adding client %d to list\n", i); + break; + } + } + printf("CLIENTS ["); + for (int i = 0; i < MAXCLIENTS; i++) { + if (control_socket[i] == 0) + printf(" "); + else + printf("#"); + } + printf("]\r\n"); + } + + for (int i = 0; i < MAXCLIENTS; i++) { + cs = control_socket[i]; + int bytes; + send_buffer[0] = '\0'; + + if (FD_ISSET(cs, &readfds)) { + for (int n = 0; n < BUFFERSIZE; n++) { + bytes = recv(cs, &receive_buffer[n], 1, 0); + if (bytes <= 0) break; + if (receive_buffer[n] == '\n' || receive_buffer[n] == '\r') { + receive_buffer[n] = '\0'; + break; + } + } + if (bytes <= 0 || strlen(receive_buffer) <= 0) continue; + printf("[DEBUG] Received from client %i: '%s'\r\n", cs, receive_buffer); + if (strncmp(receive_buffer,"USER",4)==0) { + char name[32]; + strncpy(name, &receive_buffer[5], strlen(receive_buffer)-5); + name[strlen(receive_buffer)-5] = '\0'; + client_user[i] = name; + sprintf(send_buffer, "331 Password required\r\n"); + } else if (strncmp(receive_buffer,"PASS",4)==0 || strncmp(receive_buffer,"pass",4)==0) { + sprintf(send_buffer, "230 Login successful\r\n"); + } else if (strncmp(receive_buffer,"SYST",4)==0) { + sprintf(send_buffer, "215 LINUX\r\n"); + } else if (strncmp(receive_buffer,"PWD",3)==0) { + sprintf(send_buffer, "257 '%s'\r\n", client_pwd[i]); + } else if (strncmp(receive_buffer,"CWD",3)==0 || strncmp(receive_buffer,"cwd",3)==0) { + char directory[BUFFERSIZE]; + strncpy(directory, &receive_buffer[4], strlen(receive_buffer)-4); + directory[strlen(receive_buffer)-4] = '\0'; + client_pwd[i] = (char*)directory; + sprintf(send_buffer, "%s\r\n", client_pwd[i]); + } else if (strncmp(receive_buffer,"TYPE",4)==0) { + sprintf(send_buffer, "200 OK\r\n"); + } else if (strncmp(receive_buffer,"PASV",4)==0) { + sprintf(send_buffer, "502\r\n"); + } else if (strncmp(receive_buffer,"PORT",4)==0) { + int ds = data_socket[i]; + ds = socket(AF_INET, SOCK_STREAM, 0); + //local variables + unsigned char act_port[2]; + int act_ip[4], port_dec; + char ip_decimal[40]; + sscanf(receive_buffer, "PORT %d,%d,%d,%d,%d,%d",&act_ip[0],&act_ip[1],&act_ip[2],&act_ip[3],(int*)&act_port[0],(int*)&act_port[1]); + sprintf(ip_decimal, "%d.%d.%d.%d", act_ip[0], act_ip[1], act_ip[2],act_ip[3]); + printf("[DEBUG] Active IP of client %i is %s\n", cs, ip_decimal); + port_dec = act_port[0]*256+act_port[1]; + printf("[DEBUG] Active port of client %i is %d\n", cs, port_dec); + + data_address.sin_family = AF_INET; + data_address.sin_addr.s_addr = inet_addr(ip_decimal); + data_address.sin_port = htons(port_dec); + + if (connect(ds, (struct sockaddr *)&data_address, (int) sizeof(struct sockaddr)) != 0) { + sprintf(send_buffer, "425 Unknown Error\r\n"); + close(ds); + } else { + sprintf(send_buffer, "200 OK\r\n"); + } + } else if (strncmp(receive_buffer,"QUIT",4)==0 || strncmp(receive_buffer,"quit",4)==0) { + sprintf(send_buffer, "221 Goodbye\r\n"); + send(cs, send_buffer, strlen(send_buffer), 0); + getpeername(cs, (struct sockaddr*)&ctrl_address, (socklen_t*)&addrlen); + printf("[DEBUG] Client disconnected: ip %s, port %d\n", inet_ntoa(ctrl_address.sin_addr), ntohs(ctrl_address.sin_port)); + close(cs); + control_socket[i] = 0; + continue; + } else { + sprintf(send_buffer, "500 Unrecognized command\r\n"); + } + if (strlen(send_buffer)>0) { + bytes = send(cs, send_buffer, strlen(send_buffer), 0); + if (bytes < 0) continue; + char debugSend[BUFFERSIZE]; + strncpy(debugSend, send_buffer, strlen(send_buffer)-2); + debugSend[strlen(send_buffer)-2] = '\0'; + printf("[DEBUG] Sent to client %i: '%s'\r\n", cs, debugSend); + } + } + } + } + + return 0; +} \ No newline at end of file diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..cfa04b6 --- /dev/null +++ b/src/util.h @@ -0,0 +1,52 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include +#include +#include +#include + +template +void split(const std::string &s, char delim, Out result, int limit) { + int it = 0; + std::istringstream iss(s); + std::string item; + while (std::getline(iss, item, delim) && it <= limit) { + *result++ = item; + if (limit > 0) it++; + } +} + +std::vector split(const std::string &s, char delim, int limit) { + std::vector elems; + split(s, delim, std::back_inserter(elems), limit); + return elems; +} + +static std::string toLower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){ return std::tolower(c); }); + return str; +} + +static std::string toUpper(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){ return std::toupper(c); }); + return str; +} + +static char* trim(char *str) { + char *end; + while(isspace(*str)) + str++; + if(*str == 0) + return str; + end = str + strnlen(str, 128) - 1; + while(end > str && isspace(*end)) + end--; + *(end+1) = '\0'; + return str; +} + +#endif \ No newline at end of file