#include #include #include #include #include #include #include #include #include #include #include #include "main.h" #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; std::thread thread; Client(int &_sock) { control_sock = _sock; submit(230, config->getValue("main", "server_name", "digFTP Server")); }; 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 Type: L8"); } 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 submit(431, "No such directory"); } else if (cmd == "CDUP") { int rc = filer->traverse(".."); if (rc == 0) submit(250, "OK"); else submit(431, "No such directory"); } else if (cmd == "MKD") { int rc = filer->createDirectory(argstr); if (rc == 0) submit(257, "\""+filer->relPath(argstr).string()+"\" directory created"); else if (rc == -1) submit(521, "Directory already exists"); else submit(550, "Access Denied"); } else if (cmd == "TYPE") { sscanf(argstr.c_str(), "%c", &(filer->type)); submit(226, "OK"); } else if (cmd == "PASV") { if ((data_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("pasv socket() failed"); return -1; } if (setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (char *)&opt, sizeof(opt)) < 0) { perror("pasv setsockopt() failed"); close(data_fd); return -1; } uint16_t dataport; uint8_t netaddr[4]; uint8_t netport[2]; socklen_t datalen = sizeof(data_address); data_address.sin_family = AF_INET; data_address.sin_addr.s_addr = INADDR_ANY; data_address.sin_port = 0; if (bind(data_fd, (struct sockaddr *)&data_address, sizeof(data_address)) < 0) { perror("pasv bind() failed"); close(data_fd); return -1; } if (listen(data_fd, 3) < 0) { perror("pasv listen() failed"); close(data_fd); return -1; } if (getsockname(data_fd, (struct sockaddr *)&data_address, &datalen) == 0) { sscanf(config->getValue("net", "listen_address", "127.0.0.1").c_str(), "%d.%d.%d.%d", &netaddr[0], &netaddr[1], &netaddr[2], &netaddr[3]); dataport = ntohs(data_address.sin_port); memcpy(&netport[0], &dataport, 2); printf("[i] D(%i) PASV initialized: %u.%u.%u.%u:%u\n", data_fd, netaddr[0], netaddr[1], netaddr[2], netaddr[3], dataport); } else { perror("pasv getpeername() failed"); close(data_fd); return -1; } char* pasvok; asprintf( &pasvok, "Entering Passive Mode (%u,%u,%u,%u,%u,%u).", netaddr[0], netaddr[1], netaddr[2], netaddr[3], netport[1], netport[0] ); submit(227, std::string(pasvok)); free(pasvok); if ((data_sock = accept(data_fd, NULL, NULL)) >= 0) { printf("[i] D(%i) PASV accepted: %i\n", data_fd, data_sock); state = 2; } else { perror("accept() failed"); submit(425, "Unknown Error"); data_close(); } } 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) PORT initialized: %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) { submit(425, "Unknown Error"); data_close(); } 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(250, "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); free(fd.data); submit(226, "OK"); } else { if (fd.ecode == -3) submit(550, "I refuse to transmit in ASCII mode!"); else submit(550, "Access Denied"); } } 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"); } data_close(); } 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 (auth->check(name, argstr)) { printf("[d] (%i) logged in as '%s'\n", control_sock, name.c_str()); // We can now set the root safely (I hope). filer->setRoot(auth->getUserDirectory(name)); state = 1; submit(230, "Login OK"); } else { name = ""; submit(530, "Invalid Credentials."); } } else if (cmd == "AUTH") { submit(502, "Command not implemented"); } else { submit(332, "Not Logged In!"); return -1; } } 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() { if (data_sock <= 0) return 0; printf("[d] D(%i) Closing...\n", data_sock); close(data_sock); data_sock = -1; close(data_fd); data_fd = -1; state = 1; return 0; } private: std::string name; Filer* filer = new Filer(); const int opt = 1; int data_fd; int data_sock; struct sockaddr_in data_address; };