280 lines
7.8 KiB
C++
280 lines
7.8 KiB
C++
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <format>
|
|
#include <filesystem>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <thread>
|
|
|
|
#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;
|
|
};
|