diff --git a/src/client.cpp b/src/client.cpp index 4b46169..a56d42b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -39,10 +39,6 @@ public: submit(230, conf_format(server_name, (std::map){{std::string(APPNAME_VAR),std::string(APPNAME)},{std::string(VERSION_VAR),std::string(VERSION)}})); } - void addOption(std::string name, bool toggle) { - this->options[name] = toggle; - } - int receive(std::string cmd, std::string argstr) { if (control_sock <= 0) return 1; @@ -69,97 +65,127 @@ public: return 0; } - if (cmd == "AUTH") { + if (cmd == "USER") { + if (state == FTP_STATE_GUEST) { + if (argstr.length() >= sizeof(auth_data->username)) + throw std::runtime_error("Username too long"); + std::strncpy(auth_data->username, argstr.c_str(), sizeof(auth_data->username) - 1); + auth_data->username[sizeof(auth_data->username) - 1] = '\0'; + if (auth->isPasswordRequired()) + submit(331, "Password required"); + else authedInit(); + } else submit(503, "Already logged in"); + } else if (cmd == "PASS") { + if (state == FTP_STATE_GUEST) { + if (strlen(auth_data->username) > 0) { + std::strncpy(auth_data->password, argstr.c_str(), sizeof(auth_data->password) - 1); + auth_data->password[sizeof(auth_data->password) - 1] = '\0'; + if (auth->authenticate(auth_data)) + authedInit(); + else { + auth_data->username[0] = {}; + submit(530, "Invalid Credentials."); + } + } else submit(503, "Bad sequence of commands"); + } else submit(503, "Already logged in"); + } else if (cmd == "REIN") { + if (state == FTP_STATE_ONDATA) data_close(); + auth_data = new ClientAuthDetails{}; + filer = default_filer_factory(); + // Reset features + for (const auto &x : features) + features[x.first] = "OFF"; + state = FTP_STATE_GUEST; + submit(220, "Service ready for user"); + } else if (cmd == "AUTH") { if (argstr == "TLS" || argstr == "SSL") { if (setupControlSSL()) { submit(234, "AUTH TLS successful"); is_secure = true; return 0; - } else { - submit(431, "AUTH TLS failed"); - } - } else { - submit(504, "AUTH type not supported"); - } - return 0; + } else submit(431, "AUTH TLS failed"); + } else submit(504, "AUTH type not supported"); } else if (cmd == "PBSZ") { - if (!is_secure) { - submit(503, "PBSZ not allowed on insecure control connection"); - } else { - submit(200, "PBSZ=0"); - } + if (!is_secure) submit(503, "PBSZ not allowed on insecure control connection"); + else submit(200, "PBSZ=0"); return 0; } else if (cmd == "PROT") { - if (!is_secure) { - submit(503, "PROT not allowed on insecure control connection"); - } else if (argstr == "P") { + if (!is_secure) submit(503, "PROT not allowed on insecure control connection"); + else if (argstr == "P") { protect_data = true; submit(200, "Protection level set to Private"); } else if (argstr == "C") { protect_data = false; submit(200, "Protection level set to Clear"); - } else { - submit(504, "PROT level not supported"); - } - return 0; - } else if (cmd == "QUIT") { - state = FTP_STATE_CLOSE; - submit(250, "Goodbye!"); - shutdown(control_sock, SHUT_RDWR); // Add immediate shutdown - return 1; // Signal thread to terminate - } else if (state >= FTP_STATE_AUTHED) { - if (cmd == "SYST") { - submit(215, "UNIX Type: L8"); - } else if (cmd == "TYPE") { + } else submit(504, "PROT level not supported"); + } else if (cmd == "FEAT") { + submit("211-Extensions supported:"); + for (const auto &s : global_features) + submit(" "+toUpper(s)); + submit(211, "END"); + } else if (cmd == "OPTS") { + std::vector args = parseArgs(argstr); + if (args.size() > 0) { + std::string name = toUpper(args[0]); + if (args.size() > 1) { + if (features.find(name) == features.end()) + features[name] = toUpper(args[1]); + else submit(451, "Unknown feature "+name); + } else { + if (features.find(name) == features.end()) + submit(200, name+" is set to "+features[name]); + else submit(501, "Unknown feature "+name); + } + } else submit(550, "Malformed Request"); + } else if (cmd == "NOOP") { + submit(226, "OK"); + } else if (cmd == "SYST") { + submit(215, "UNIX Type: L8"); + } else if (cmd == "TYPE") { + if (state >= FTP_STATE_AUTHED) { char type = 0; sscanf(argstr.c_str(), "%c", &(type)); filer->setTransferMode(type); submit(226, "OK"); - } else if (cmd == "OPTS") { - std::vector args = parseArgs(argstr); - if (args.size() > 0) { - std::string name = toUpper(args[0]); - int rc = false; - - if (args.size() > 1) rc = toggleOption(name, toUpper(args[1])=="ON"); - else rc = toggleOption(name, true); - - if (rc == 0) submit(200, "OK"); - else if (rc == 1) submit(451, "Option not enabled or not recognized"); - else submit(550, "Unknown error"); - } else submit(550, "Malformed Request"); - } else if (cmd == "PWD") { + } else submit(530, "Not logged in"); + } else if (cmd == "PWD") { + if (state >= FTP_STATE_AUTHED) { submit(257, "'"+filer->getCWD().string()+"'"); - } else if (cmd == "CWD") { + } else submit(530, "Not logged in"); + } else if (cmd == "CWD") { + if (state >= FTP_STATE_AUTHED) { struct file_data fd = filer->traverse(argstr); if (fd.error.code == 0) submit(250, "OK"); else submit(431, std::string(fd.error.msg)); - } else if (cmd == "CDUP") { + } else submit(530, "Not logged in"); + } else if (cmd == "CDUP") { + if (state >= FTP_STATE_AUTHED) { struct file_data fd = filer->traverse(".."); if (fd.error.code == 0) submit(250, "OK"); else submit(431, std::string(fd.error.msg)); - } else if (cmd == "MKD") { + } else submit(530, "Not logged in"); + } else if (cmd == "MKD") { + if (state >= FTP_STATE_AUTHED) { struct file_data fd = filer->createDirectory(argstr); if (fd.error.code == 0) submit(257, "\""+std::string(fd.path)+"\" directory created"); else if (fd.error.code == FilerStatusCodes::FileExists) submit(521, std::string(fd.error.msg)); else submit(550, std::string(fd.error.msg)); - } else if (cmd == "SIZE") { + } else submit(530, "Not logged in"); + } else if (cmd == "SIZE") { + if (state >= FTP_STATE_AUTHED) { struct file_data fd = filer->fileSize(argstr); if (fd.error.code == 0) submit(213, std::to_string(fd.size)); else submit(550, std::string(fd.error.msg)); - } else if (cmd == "FEAT") { - submit("211-Extensions supported:"); - for (const auto &opt : options) { - submit(" "+toUpper(opt.first)); - } - submit(211, "END"); - } else if (cmd == "NOOP") { - submit(226, "OK"); - } else if (cmd == "DELE" || cmd == "RMD") { + } else submit(530, "Not logged in"); + } else if (cmd == "DELE" || cmd == "RMD") { + if (state >= FTP_STATE_AUTHED) { struct file_data fd = filer->deleteFile(argstr); if (fd.error.code == 0) submit(250, "OK"); else submit(550, std::string(fd.error.msg)); - } else if (cmd == "PASV") { + } else submit(530, "Not logged in"); + } else if (cmd == "PASV") { + if (state >= FTP_STATE_AUTHED) { + if (state == FTP_STATE_ONDATA) data_close(); if ((data_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("pasv socket() failed"); return 1; @@ -233,9 +259,11 @@ public: submit(425, "Can't open data connection"); data_close(); } - } else if (cmd == "RNFR") { + } else submit(530, "Not logged in"); + } else if (cmd == "RNFR") { + if (state >= FTP_STATE_AUTHED) { if (argstr.empty()) { - submit(501, "Syntax error in parameters or arguments."); + submit(501, "Syntax error in parameters or arguments"); return 0; } @@ -250,15 +278,17 @@ public: // Store the source path and mark rename as pending rename_from = argstr; rename_pending = true; - submit(350, "Ready for RNTO."); - } else if (cmd == "RNTO") { + submit(350, "Ready for RNTO"); + } else submit(530, "Not logged in"); + } else if (cmd == "RNTO") { + if (state >= FTP_STATE_AUTHED) { if (!rename_pending) { - submit(503, "RNFR required first."); + submit(503, "RNFR required first"); return 0; } if (argstr.empty()) { - submit(501, "Syntax error in parameters or arguments."); + submit(501, "Syntax error in parameters or arguments"); rename_pending = false; return 0; } @@ -267,136 +297,97 @@ public: struct file_data fd = filer->renameFile(rename_from, argstr); rename_pending = false; // Reset rename state - if (fd.error.code == 0) { - submit(250, "Rename successful."); - } else { - submit(550, std::string(fd.error.msg)); - } - } else if (state == FTP_STATE_ONDATA) { - if (data_sock <= 0) return 1; - if (cmd == "LIST" || cmd == "NLST") { + if (fd.error.code == 0) submit(250, "Rename successful"); + else submit(550, std::string(fd.error.msg)); + } else submit(530, "Not logged in"); + } else if (cmd == "LIST" || cmd == "NLST") { + if (state == FTP_STATE_ONDATA && data_sock > 0) { + submit(150, "Transferring"); + + std::string dirname = ""; + if (argstr.find_first_of('/') != std::string::npos) + dirname = argstr.substr(argstr.find_first_of('/')); + struct file_data fd = filer->list(dirname); + data_submit(fd.bin, fd.size); + + submit(226, "OK"); + data_close(); + } else submit(425, "No data connection"); + } else if (cmd == "RETR") { + if (state == FTP_STATE_ONDATA && data_sock > 0) { + struct file_data fd = filer->readFile(argstr); + if (fd.error.code == 0 && fd.stream && fd.stream->is_open()) { submit(150, "Transferring"); - std::string dirname = ""; - if (argstr.find_first_of('/') != std::string::npos) - dirname = argstr.substr(argstr.find_first_of('/')); - struct file_data fd = filer->list(dirname); - data_submit(fd.bin, fd.size); + char buffer[8192]; + bool transfer_ok = true; - submit(226, "OK"); - } else if (cmd == "RETR") { - struct file_data fd = filer->readFile(argstr); - if (fd.error.code == 0 && fd.stream && fd.stream->is_open()) { - submit(150, "Transferring"); + while (!fd.stream->eof()) { + fd.stream->read(buffer, sizeof(buffer)); + size_t bytes_read = fd.stream->gcount(); - char buffer[8192]; - bool transfer_ok = true; - - while (!fd.stream->eof()) { - fd.stream->read(buffer, sizeof(buffer)); - size_t bytes_read = fd.stream->gcount(); - - if (bytes_read > 0) { - if (data_submit(buffer, bytes_read) != 0) { - submit(426, "Transfer failed"); - transfer_ok = false; - break; - } - } - - if (fd.stream->fail() && !fd.stream->eof()) { - submit(426, "Read error"); + if (bytes_read > 0) { + if (data_submit(buffer, bytes_read) != 0) { + submit(426, "Transfer failed"); transfer_ok = false; break; } } - if (transfer_ok) { - submit(226, "OK"); - } - } else { - submit(550, std::string(fd.error.msg)); - } - data_close(); - } else if (cmd == "STOR") { - unsigned char inbuf[BUFFERSIZE] = {0}; - submit(150, "Transferring"); - int psize; - struct file_data fd = filer->writeFile(argstr, inbuf, 0); - - while (true) { - if (is_secure && protect_data && data_ssl) { - psize = SSL_read(data_ssl, inbuf, sizeof(inbuf)); - if (psize <= 0) { - int err = SSL_get_error(data_ssl, psize); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { - continue; - } - break; - } - } else { - psize = recv(data_sock, inbuf, sizeof(inbuf), 0); - if (psize <= 0) break; - } - - fd = filer->writeFile(argstr, inbuf, psize, true); - if (fd.error.code != 0) { - submit(550, "Access Denied: "+std::string(fd.error.msg)); - data_close(); + if (fd.stream->fail() && !fd.stream->eof()) { + submit(426, "Read error"); + transfer_ok = false; break; } - memset(inbuf, 0, BUFFERSIZE); } - submit(226, "OK"); - } else { - submit(502, "Command not implemented"); - } + if (transfer_ok) submit(226, "OK"); + } else submit(550, std::string(fd.error.msg)); data_close(); - } else { - submit(502, "Command not implemented"); - } - } else { - state = FTP_STATE_GUEST; // If we reach here, force state to zero just in case. - if (cmd == "USER") { - if (argstr.length() >= sizeof(auth_data->username)) { - throw std::runtime_error("Username too long"); + } else submit(425, "No data connection"); + } else if (cmd == "STOR") { + if (state == FTP_STATE_ONDATA && data_sock > 0) { + unsigned char inbuf[BUFFERSIZE] = {0}; + submit(150, "Transferring"); + int psize; + struct file_data fd = filer->writeFile(argstr, inbuf, 0); + + while (true) { + if (is_secure && protect_data && data_ssl) { + psize = SSL_read(data_ssl, inbuf, sizeof(inbuf)); + if (psize <= 0) { + int err = SSL_get_error(data_ssl, psize); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + continue; + } + break; + } + } else { + psize = recv(data_sock, inbuf, sizeof(inbuf), 0); + if (psize <= 0) break; + } + + fd = filer->writeFile(argstr, inbuf, psize, true); + if (fd.error.code != 0) { + submit(550, "Access Denied: "+std::string(fd.error.msg)); + data_close(); + break; + } + memset(inbuf, 0, BUFFERSIZE); } - std::strncpy(auth_data->username, argstr.c_str(), sizeof(auth_data->username) - 1); - auth_data->username[sizeof(auth_data->username) - 1] = '\0'; - if (auth->isPasswordRequired()) - submit(331, "Password required"); - else authedInit(); - } else if (cmd == "PASS") { - std::strncpy(auth_data->password, argstr.c_str(), sizeof(auth_data->password) - 1); - auth_data->password[sizeof(auth_data->password) - 1] = '\0'; - if (auth->authenticate(auth_data)) authedInit(); - else { - auth_data->username[0] = {}; - submit(530, "Invalid Credentials."); - } - } else { - submit(332, "Not Logged In!"); - return 1; - } - } + submit(226, "OK"); + data_close(); + } else submit(425, "No data connection"); + } else if (cmd == "QUIT") { + state = FTP_STATE_CLOSE; + submit(250, "Goodbye!"); + shutdown(control_sock, SHUT_RDWR); // Add immediate shutdown + return 1; // Signal thread to terminate + } else submit(502, "Command not implemented"); return 0; } - int toggleOption(std::string name, bool toggle) { - auto feat_it = options.find(name); - if (feat_it != options.end()) { - feat_it->second = toggle; - return 0; - } - return 1; - } - - bool getOption(std::string name) { - auto feat_it = options.find(name); - if (feat_it != options.end()) { - return feat_it->second; - } - return false; + void addFeature(std::string name, std::string value) { + this->features[name] = value.empty()?"OFF":value; } int submit(std::string msg) { @@ -445,6 +436,15 @@ public: logger->print(LOGLEVEL_DEBUG, "C(%i) << %d %s", control_sock, code, msg.c_str()); } + void submit(int code, const std::vector& msgs) { + for(int i = 0; i < msgs.size(); i++){ + if (i+1 == msgs.size()) + submit(std::to_string(code) + " " + msgs[i]); + else + submit(std::to_string(code) + "-" + msgs[i]); + } + } + int data_submit(char* out, size_t size) { size_t total_sent = 0; while (total_sent < size) { @@ -514,7 +514,7 @@ public: } private: - std::map options; + std::map features; ClientAuthDetails* auth_data = new ClientAuthDetails{}; Filer* filer = {}; const int opt = 1; @@ -634,6 +634,7 @@ private: struct file_data fd; std::string root; std::string home = "/"; + std::vector auth_response; if (auth->isChroot()) { root = std::string(this->auth_data->home_dir); logger->print(LOGLEVEL_INFO, "C(%i) Set chrooted root of '%s' to '%s'", control_sock, auth_data->username, root.c_str()); @@ -647,15 +648,16 @@ private: ((struct file_data)filer->setCWD(home)).error.code == 0 ) { state = FTP_STATE_AUTHED; - submit(230, "Login OK"); + auth_response.push_back("Login OK"); std::string user_motd = getMotD(); if (user_motd.size() > 0) { std::stringstream motd_stream(user_motd); std::string line; while(std::getline(motd_stream, line)) { - submit(230, line); + auth_response.push_back(line); } } + submit(230, auth_response); return; } submit(530, "An error occured setting root and/or cwd"); diff --git a/src/main.cpp b/src/main.cpp index a08cced..a96a6d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -248,7 +248,9 @@ int main(int argc , char *argv[]) { logger->openFileOnLevel(LOGLEVEL_ERROR, config->getValue("logging", "error", mainLogFile).c_str()); logger->openFileOnLevel(LOGLEVEL_CRITICAL, config->getValue("logging", "critical", mainLogFile).c_str()); - if (config->getBool("net", "ssl", false)) { + bool ssl_enable = config->getBool("net", "ssl", true); + bool ssl_flags = SSL_OP_NO_TICKET; + if (ssl_enable) { std::string cert_file = config->getValue("ssl", "certificate", "cert.pem"); if (cert_file[0] != '/') cert_file = concatPath(std::string(CONFIG_DIR), cert_file); @@ -264,7 +266,6 @@ int main(int argc , char *argv[]) { // set configured ciphers SSLManager::getInstance().setCiphers(config->getValue("ssl", "ciphers", "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH").c_str()); // handle configured flags - bool ssl_flags = SSL_OP_NO_TICKET; if (!config->getBool("ssl", "ssl_v2", false)) ssl_flags+=SSL_OP_NO_SSLv2; if (!config->getBool("ssl", "ssl_v3", false)) @@ -278,6 +279,15 @@ int main(int argc , char *argv[]) { if (!config->getBool("ssl", "tls_v1_3", true)) ssl_flags+=SSL_OP_NO_TLSv1_3; + if (!(ssl_flags & SSL_OP_NO_SSLv2) || !(ssl_flags & SSL_OP_NO_SSLv3)) + global_features.push_back("AUTH SSL"); + if (!(ssl_flags & SSL_OP_NO_TLSv1) || !(ssl_flags & SSL_OP_NO_TLSv1_1) || + !(ssl_flags & SSL_OP_NO_TLSv1_2) || !(ssl_flags & SSL_OP_NO_TLSv1_3)) + global_features.push_back("AUTH TLS"); + + global_features.push_back("PBSZ"); + global_features.push_back("PROT"); + if ((ssl_flags & SSL_OP_NO_SSLv2) && (ssl_flags & SSL_OP_NO_SSLv3) && (ssl_flags & SSL_OP_NO_TLSv1) && (ssl_flags & SSL_OP_NO_TLSv1_1) && (ssl_flags & SSL_OP_NO_TLSv1_2) && (ssl_flags & SSL_OP_NO_TLSv1_3)) @@ -413,8 +423,8 @@ int main(int argc , char *argv[]) { fds[slot].revents = 0; fdc[slot].client = new Client(newsock); - fdc[slot].client->addOption("SIZE", true); - fdc[slot].client->addOption("UTF8", config->getBool("features", "utf8", true)); + for (const auto &o : client_options) + fdc[slot].client->addFeature(o, ""); fdc[slot].thread = new std::thread(runClient, &fdc[slot]); fdc[slot].close = false; diff --git a/src/main.h b/src/main.h index aa2ae9f..f7d00f3 100644 --- a/src/main.h +++ b/src/main.h @@ -25,5 +25,13 @@ Logger* logger; static typename PluginTraits::CreateFunc default_filer_factory = nullptr; bool runServer; bool runCompression; +std::vector global_features = { + "SIZE", + "UTF8", + "PASV" +}; +std::vector client_options = { + "UTF8" +}; #endif \ No newline at end of file