Add MotD support.
Fixed config up. Better handling of config variables
This commit is contained in:
18
src/auth.h
18
src/auth.h
@@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
|
||||
#include "globals.h"
|
||||
#include "build.h"
|
||||
#include "logger.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -40,14 +41,15 @@ public:
|
||||
virtual bool authenticate(ClientAuthDetails* details) { return false; }
|
||||
|
||||
virtual void setUserDirectory(ClientAuthDetails* details) {
|
||||
std::string userdir = this->user_directory;
|
||||
// Replace escaped var symbol with a dummy character
|
||||
userdir = replace(userdir, std::string(CNF_PERCENTSYM_VAR), std::string(1, 0xFE));
|
||||
userdir = replace(userdir, std::string(CNF_USERNAME_VAR), std::string(details->username));
|
||||
userdir = replace(userdir, std::string(CNF_PASSWORD_VAR), std::string(details->password));
|
||||
userdir = replace(userdir, std::string(CNF_HOSTNAME_VAR), std::string(details->hostname));
|
||||
// Replace dummy character and return
|
||||
userdir = replace(userdir, std::string(1, 0xFE), "%");
|
||||
std::map<std::string, std::string> vars = {
|
||||
{std::string(APPNAME_VAR), APPNAME},
|
||||
{std::string(VERSION_VAR), VERSION},
|
||||
{std::string(USERNAME_VAR), std::string(details->username)},
|
||||
{std::string(DATE_VAR), getCurrentUTCTime()},
|
||||
{std::string(TIME_VAR), getCurrentUTCDate()}
|
||||
};
|
||||
|
||||
std::string userdir = conf_format(this->user_directory, vars);
|
||||
|
||||
// Store the computed path in the details structure
|
||||
strncpy(details->home_dir, userdir.c_str(), sizeof(details->home_dir) - 1);
|
||||
|
||||
111
src/client.cpp
111
src/client.cpp
@@ -36,7 +36,7 @@ public:
|
||||
return;
|
||||
}
|
||||
filer = default_filer_factory();
|
||||
submit(230, replace(server_name, CNF_VERSION_VAR, VERSION));
|
||||
submit(230, conf_format(server_name, (std::map<std::string, std::string>){{std::string(APPNAME_VAR),std::string(APPNAME)},{std::string(VERSION_VAR),std::string(VERSION)}}));
|
||||
}
|
||||
|
||||
void addOption(std::string name, bool toggle) {
|
||||
@@ -521,41 +521,16 @@ private:
|
||||
uint8_t flags = {};
|
||||
int state = FTP_STATE_GUEST;
|
||||
|
||||
// Data
|
||||
int data_fd;
|
||||
int data_sock;
|
||||
struct sockaddr_in data_address;
|
||||
|
||||
// Rename (RNFR RNTO)
|
||||
std::string rename_from;
|
||||
bool rename_pending = false;
|
||||
|
||||
void authedInit() {
|
||||
logger->print(LOGLEVEL_INFO, "C(%i) logging in as '%s'", control_sock, auth_data->username);
|
||||
struct file_data fd;
|
||||
if (auth->isChroot()) {
|
||||
if (
|
||||
((struct file_data)filer->setRoot(std::string(this->auth_data->home_dir))).error.code == 0 &&
|
||||
((struct file_data)filer->setCWD("/")).error.code == 0
|
||||
) {
|
||||
state = FTP_STATE_AUTHED;
|
||||
submit(230, "Login OK");
|
||||
logger->print(LOGLEVEL_INFO, "C(%i) Set chrooted root of '%s' to '%s'", control_sock, auth_data->username, filer->getRoot().c_str());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
((struct file_data)filer->setRoot("/")).error.code == 0 &&
|
||||
((struct file_data)filer->setCWD(std::string(this->auth_data->home_dir))).error.code == 0
|
||||
) {
|
||||
state = FTP_STATE_AUTHED;
|
||||
submit(230, "Login OK");
|
||||
logger->print(LOGLEVEL_INFO, "C(%i) Set home of '%s' to '%s'", control_sock, auth_data->username, filer->getCWD().c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
submit(530, "An error occured setting root and/or cwd");
|
||||
return;
|
||||
}
|
||||
|
||||
// SSL
|
||||
SSL* control_ssl;
|
||||
SSL* data_ssl;
|
||||
SSL_SESSION* cached_session = nullptr;
|
||||
@@ -653,4 +628,82 @@ private:
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void authedInit() {
|
||||
logger->print(LOGLEVEL_INFO, "C(%i) logging in as '%s'", control_sock, auth_data->username);
|
||||
struct file_data fd;
|
||||
std::string root;
|
||||
std::string home = "/";
|
||||
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());
|
||||
} else {
|
||||
root = "/";
|
||||
home = std::string(this->auth_data->home_dir);
|
||||
logger->print(LOGLEVEL_INFO, "C(%i) Set home of '%s' to '%s'", control_sock, auth_data->username, home.c_str());
|
||||
}
|
||||
if (
|
||||
((struct file_data)filer->setRoot(root)).error.code == 0 &&
|
||||
((struct file_data)filer->setCWD(home)).error.code == 0
|
||||
) {
|
||||
state = FTP_STATE_AUTHED;
|
||||
submit(230, "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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
submit(530, "An error occured setting root and/or cwd");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string getMotD() {
|
||||
std::string new_motd;
|
||||
|
||||
std::map<std::string, std::string> vars = {
|
||||
{std::string(APPNAME_VAR), APPNAME},
|
||||
{std::string(VERSION_VAR), VERSION},
|
||||
{std::string(USERNAME_VAR), auth_data ? auth_data->username : ""},
|
||||
{std::string(DATE_VAR), getCurrentUTCTime()},
|
||||
{std::string(TIME_VAR), getCurrentUTCDate()}
|
||||
};
|
||||
|
||||
if (motd.rfind("cmd:", 0) == 0) {
|
||||
std::string cmd = conf_format(motd.substr(4), vars);
|
||||
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
logger->print(LOGLEVEL_ERROR, "Failed to execute MOTD command: %s", cmd.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
char buffer[128];
|
||||
std::string result;
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
result += buffer;
|
||||
}
|
||||
|
||||
pclose(pipe);
|
||||
return result;
|
||||
} else if (motd.rfind("file:", 0) == 0) {
|
||||
std::string path = motd.substr(5);
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file) {
|
||||
logger->print(LOGLEVEL_ERROR, "Failed to open MOTD file: %s", path.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return conf_format(buffer.str(), vars);
|
||||
} else {
|
||||
return conf_format(new_motd, vars);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,8 @@ class Filer {
|
||||
public:
|
||||
virtual ~Filer() {};
|
||||
|
||||
virtual bool initialize(const std::map<std::string, std::string>& config) { return true; }
|
||||
|
||||
virtual file_data setRoot(std::string _root) = 0;
|
||||
virtual file_data setCWD(std::string _cwd) = 0;
|
||||
virtual std::filesystem::path getRoot() = 0;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#ifndef GLOBALS_H
|
||||
#define GLOBALS_H
|
||||
|
||||
#define CNF_PERCENTSYM_VAR "%%"
|
||||
#define CNF_VERSION_VAR "%v"
|
||||
#define CNF_USERNAME_VAR "%u"
|
||||
#define CNF_PASSWORD_VAR "%p"
|
||||
#define CNF_HOSTNAME_VAR "%h"
|
||||
#define APPNAME_VAR "a"
|
||||
#define VERSION_VAR "v"
|
||||
#define USERNAME_VAR "u"
|
||||
#define PASSWORD_VAR "p"
|
||||
#define HOSTNAME_VAR "h"
|
||||
#define DATE_VAR "d"
|
||||
#define TIME_VAR "t"
|
||||
|
||||
#endif
|
||||
26
src/main.cpp
26
src/main.cpp
@@ -183,29 +183,24 @@ void runClient(struct ftpconn* cfd) {
|
||||
void initializePlugins() {
|
||||
std::string plugin_dir = config->getValue("core", "plugin_path", PLUGIN_DIR);
|
||||
|
||||
// Load auth plugins
|
||||
auto& auth_manager = PluginManager<Auth>::getInstance();
|
||||
auth_manager.setLogger(logger);
|
||||
|
||||
// Load filer plugins
|
||||
auto& filer_manager = PluginManager<Filer>::getInstance();
|
||||
|
||||
auth_manager.setLogger(logger);
|
||||
filer_manager.setLogger(logger);
|
||||
|
||||
// Try loading all plugins into both managers
|
||||
for (const auto& entry : std::filesystem::directory_iterator(plugin_dir)) {
|
||||
if (entry.path().extension() == ".so") {
|
||||
const std::string& filename = entry.path().filename().string();
|
||||
if (filename.find(PluginTraits<Auth>::pluginPrefix()) == 0) {
|
||||
auth_manager.loadPlugin(entry.path().string());
|
||||
}
|
||||
else if (filename.find(PluginTraits<Filer>::pluginPrefix()) == 0) {
|
||||
filer_manager.loadPlugin(entry.path().string());
|
||||
}
|
||||
logger->print(LOGLEVEL_DEBUG, "Loading plugin: %s", entry.path().c_str());
|
||||
auth_manager.loadPlugin(entry.path().string());
|
||||
filer_manager.loadPlugin(entry.path().string());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize auth
|
||||
std::string auth_type = config->getValue("core", "auth_engine", "pam");
|
||||
auth = auth_manager.createPlugin(auth_type);
|
||||
std::string auth_type = config->getValue("engines", "auth", "pam");
|
||||
auth = auth_manager.createPlugin(auth_type, config->get(auth_type)->get());
|
||||
|
||||
if (!auth) {
|
||||
logger->print(LOGLEVEL_CRITICAL, "Failed to create auth engine: %s", auth_type.c_str());
|
||||
@@ -213,7 +208,7 @@ void initializePlugins() {
|
||||
}
|
||||
|
||||
// Initialize filer
|
||||
std::string filer_type = config->getValue("core", "filer_engine", "local");
|
||||
std::string filer_type = config->getValue("engines", "filer", "local");
|
||||
default_filer_factory = filer_manager.getFactory(filer_type);
|
||||
|
||||
if (!default_filer_factory) {
|
||||
@@ -231,7 +226,8 @@ int main(int argc , char *argv[]) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
config = new ConfigFile(concatPath(std::string(CONFIG_DIR), "ftp.conf"));
|
||||
server_name = config->getValue("core", "server_name", "digFTP %v");
|
||||
server_name = config->getValue("core", "server_name", "%a %v");
|
||||
motd = config->getValue("core", "motd", "cmd:cowsay -r Welcome %u!");
|
||||
|
||||
sscanf(
|
||||
config->getValue("net", "listen", "127.0.0.1:21").c_str(),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
typedef int (*GetAPIVersionFunc)();
|
||||
typedef void (*SetLoggerFunc)(Logger*);
|
||||
typedef const char* (*GetPluginTypeFunc)();
|
||||
|
||||
class IPlugin {
|
||||
public:
|
||||
|
||||
@@ -1,43 +1,49 @@
|
||||
#ifndef PLUGIN_MANAGER_H
|
||||
#define PLUGIN_MANAGER_H
|
||||
|
||||
#include <map>
|
||||
#include <dlfcn.h>
|
||||
#include <filesystem>
|
||||
#include "plugin.h"
|
||||
|
||||
class IPluginManager {
|
||||
public:
|
||||
virtual ~IPluginManager() = default;
|
||||
virtual void setLogger(Logger* log) = 0;
|
||||
virtual bool loadPlugin(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class PluginManager {
|
||||
class PluginManager : public IPluginManager {
|
||||
public:
|
||||
static PluginManager<T>& getInstance() {
|
||||
static PluginManager<T> instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void setLogger(Logger* log) {
|
||||
logger = log;
|
||||
void setLogger(Logger* log) override {
|
||||
IPlugin::setLogger(log);
|
||||
logger = log;
|
||||
}
|
||||
|
||||
T* createPlugin(const std::string& type) {
|
||||
T* createPlugin(const std::string& type, const std::map<std::string, std::string>& config) {
|
||||
auto it = plugins.find(type);
|
||||
if (it != plugins.end()) {
|
||||
return it->second.create();
|
||||
T* plugin = it->second.create();
|
||||
if (plugin) plugin->initialize(config);
|
||||
return plugin;
|
||||
}
|
||||
if (logger) logger->print(LOGLEVEL_ERROR, "Plugin type '%s' not found", type.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool loadPlugin(const std::string& path) {
|
||||
if (logger) logger->print(LOGLEVEL_DEBUG, "Loading plugin: %s", path.c_str());
|
||||
|
||||
void* handle = dlopen(path.c_str(), RTLD_LAZY);
|
||||
if (!handle) {
|
||||
if (logger) logger->print(LOGLEVEL_ERROR, "Failed to load plugin %s: %s",
|
||||
path.c_str(), dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Load common functions
|
||||
auto create = (typename PluginTraits<T>::CreateFunc)dlsym(handle, PluginTraits<T>::createFuncName());
|
||||
auto destroy = (typename PluginTraits<T>::DestroyFunc)dlsym(handle, PluginTraits<T>::destroyFuncName());
|
||||
@@ -46,32 +52,44 @@ public:
|
||||
auto get_name = (const char* (*)())dlsym(handle, "getPluginName");
|
||||
auto get_desc = (const char* (*)())dlsym(handle, "getPluginDescription");
|
||||
auto get_ver = (const char* (*)())dlsym(handle, "getPluginVersion");
|
||||
|
||||
if (!checkRequiredFunctions(create, destroy, get_api_version, setLogger, get_name, get_desc, get_ver)) {
|
||||
|
||||
// If create function isn't found, this plugin doesn't implement our interface
|
||||
if (!create) {
|
||||
dlclose(handle);
|
||||
return true; // Not an error, just not our type
|
||||
}
|
||||
|
||||
// Check all required functions are present
|
||||
if (!checkRequiredFunctions(create, destroy, get_api_version, setLogger,
|
||||
get_name, get_desc, get_ver)) {
|
||||
dlclose(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check API version compatibility
|
||||
if (!checkAPIVersion(get_api_version(), path)) {
|
||||
dlclose(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string plugin_name = get_name();
|
||||
setLogger(logger);
|
||||
|
||||
|
||||
// Check if already loaded
|
||||
if (plugins.find(plugin_name) != plugins.end()) {
|
||||
if (logger) logger->print(LOGLEVEL_DEBUG, "Plugin %s is already loaded", plugin_name.c_str());
|
||||
dlclose(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Create and store plugin info
|
||||
auto plugin_info = createPluginInfo(plugin_name, get_desc(), get_ver(), create, destroy,
|
||||
get_api_version, setLogger, handle);
|
||||
get_api_version, setLogger, handle);
|
||||
|
||||
plugins[plugin_name] = plugin_info;
|
||||
if (logger) logger->print(LOGLEVEL_INFO, "Loaded plugin: %s v%s",
|
||||
plugin_name.c_str(), plugin_info.version.c_str());
|
||||
if (logger) logger->print(LOGLEVEL_INFO, "Loaded plugin: %s v%s (%s interface)",
|
||||
plugin_name.c_str(), plugin_info.version.c_str(),
|
||||
PluginTraits<T>::interfaceName());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct PluginTraits<Auth> {
|
||||
|
||||
static const char* createFuncName() { return "createAuthPlugin"; }
|
||||
static const char* destroyFuncName() { return "destroyAuthPlugin"; }
|
||||
static const char* pluginPrefix() { return "libauth_"; }
|
||||
static const char* interfaceName() { return "Auth"; }
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -42,7 +42,7 @@ struct PluginTraits<Filer> {
|
||||
|
||||
static const char* createFuncName() { return "createFilerPlugin"; }
|
||||
static const char* destroyFuncName() { return "destroyFilerPlugin"; }
|
||||
static const char* pluginPrefix() { return "libfiler_"; }
|
||||
static const char* interfaceName() { return "Filer"; }
|
||||
};
|
||||
|
||||
#endif
|
||||
66
src/util.h
66
src/util.h
@@ -9,6 +9,7 @@
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
|
||||
std::string replace(std::string subject, const std::string& search, const std::string& replace) {
|
||||
size_t pos = 0;
|
||||
@@ -19,6 +20,55 @@ std::string replace(std::string subject, const std::string& search, const std::s
|
||||
return subject;
|
||||
}
|
||||
|
||||
std::string conf_format(const std::string& fmt, const std::map<std::string, std::string>& vars) {
|
||||
std::string result;
|
||||
result.reserve(fmt.length());
|
||||
|
||||
for (size_t i = 0; i < fmt.length(); ++i) {
|
||||
if (fmt[i] != '%') {
|
||||
result += fmt[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle %% -> %
|
||||
if (i + 1 < fmt.length() && fmt[i + 1] == '%') {
|
||||
result += '%';
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for variable name
|
||||
if (i + 1 < fmt.length()) {
|
||||
size_t start = i + 1;
|
||||
size_t end = start;
|
||||
|
||||
// Find the end of the variable name
|
||||
while (end < fmt.length() &&
|
||||
(isalnum(fmt[end]) || fmt[end] == '_')) {
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end > start) {
|
||||
std::string var = fmt.substr(start, end - start);
|
||||
auto it = vars.find(var);
|
||||
if (it != vars.end()) {
|
||||
result += it->second;
|
||||
} else {
|
||||
// Keep the original %var if not found
|
||||
result += fmt.substr(i, end - i);
|
||||
}
|
||||
i = end - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, it's a single % with no variable
|
||||
result += '%';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Out>
|
||||
void split(const std::string &s, char delim, Out result, int limit) {
|
||||
int it = 0;
|
||||
@@ -133,6 +183,22 @@ std::string concatPath(std::string dir1, std::string dir2) {
|
||||
return std::filesystem::weakly_canonical(dir1+"/"+dir2).string();
|
||||
}
|
||||
|
||||
std::string getCurrentUTCTime() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
std::string result(9, '\0');
|
||||
strftime(&result[0], result.size(), "%H:%M:%S", gmtime(&time));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getCurrentUTCDate() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
std::string result(11, '\0');
|
||||
strftime(&result[0], result.size(), "%Y-%m-%d", gmtime(&time));
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* trim(char *str) {
|
||||
char *end;
|
||||
while(isspace(*str))
|
||||
|
||||
Reference in New Issue
Block a user