Add MotD support.
Fixed config up. Better handling of config variables
This commit is contained in:
@@ -3,14 +3,19 @@
|
|||||||
## This is the primary configuration file. Here is where you'll find all the
|
## This is the primary configuration file. Here is where you'll find all the
|
||||||
## necessary options to configure your FTP server.
|
## necessary options to configure your FTP server.
|
||||||
|
|
||||||
## == String Formatting
|
#### Cheat Sheet
|
||||||
|
### String Variables
|
||||||
## These may be used in certain options to be runtime replaced.
|
## These may be used in certain options to be runtime replaced.
|
||||||
|
## %a App Name
|
||||||
## %u Username
|
## %u Username
|
||||||
## %p Password
|
|
||||||
## %v Version
|
## %v Version
|
||||||
## %h Hostname
|
## %h Hostname
|
||||||
|
## %d Date
|
||||||
## == Booleans
|
## %t Time
|
||||||
|
## Example: %a %v: Hi %u. It is %t on %d.
|
||||||
|
## digFTP v1.0.0: Hi user on example.com, your password is h4ckm3.
|
||||||
|
##
|
||||||
|
### Booleans
|
||||||
## true | false
|
## true | false
|
||||||
## ----- | -----
|
## ----- | -----
|
||||||
## 1 | 0
|
## 1 | 0
|
||||||
@@ -18,41 +23,38 @@
|
|||||||
## yes | no
|
## yes | no
|
||||||
## Anything unrecognized will be treated as 'false'.
|
## Anything unrecognized will be treated as 'false'.
|
||||||
|
|
||||||
## CORE OPTIONS
|
|
||||||
## These affect the very core of the server.
|
|
||||||
[core]
|
[core]
|
||||||
## The name of the server it sends to the client.
|
## The name of the server it sends to the client.
|
||||||
## Syntax: server_name=<string>
|
## Syntax: server_name=<string>
|
||||||
server_name=digFTP %v
|
## Default: %a %v
|
||||||
|
server_name=%a %v
|
||||||
|
|
||||||
## MotD command to post the output to clients after log in.
|
## Message of the Day to post to clients upon log in.
|
||||||
## WARNING: This method can be insecure. Use with caution.
|
## Syntax: motd=<string>
|
||||||
## Syntax: motd_command=<command>
|
## motd=file:<path>
|
||||||
#motd_command=cowsay -r Welcome %u.
|
## motd=cmd:<command>
|
||||||
|
## WARNING: Commands could be dangerous. Use with caution.
|
||||||
## MotD file to post to clients after log in. Overrides `motd_command`.
|
#motd=cmd:cowsay -r Welcome %u.
|
||||||
## Syntax: motd_file=<path>
|
#motd=file:ftp.motd
|
||||||
#motd_file=motd
|
#motd=Welcome.
|
||||||
|
|
||||||
## MotD text to post to clients after log in. Overrides `motd_command` and
|
|
||||||
## `motd_file`.
|
|
||||||
## Syntax: motd_text=<string>
|
|
||||||
#motd_text=
|
|
||||||
|
|
||||||
## Path to digFTP plugins
|
## Path to digFTP plugins
|
||||||
## Syntax: plugin_path=<path>
|
## Syntax: plugin_path=<path>
|
||||||
|
## Default: /usr/lib/digftp/plugins
|
||||||
plugin_path=/usr/lib/digftp/plugins
|
plugin_path=/usr/lib/digftp/plugins
|
||||||
|
|
||||||
## Authentication Engine to use for logging in.
|
[engines]
|
||||||
## Syntax: auth_engine=<name>
|
## These specify the interface to use for functions.
|
||||||
|
## Engines are provided through plugins.
|
||||||
|
## Syntax: <interface>=<plugin name>
|
||||||
|
|
||||||
auth_engine=local
|
## Engine used for authenticating users.
|
||||||
|
## Default: pam
|
||||||
## Filer engine to use for the clients file system.
|
auth=pam
|
||||||
## Syntax: file_engine=<name>
|
|
||||||
## Possible values are: local
|
|
||||||
filer_engine=local
|
|
||||||
|
|
||||||
|
## Engine used for handling the filesystem.
|
||||||
|
## Default: local
|
||||||
|
filer=local
|
||||||
|
|
||||||
[net]
|
[net]
|
||||||
## Network address and port to listen on.
|
## Network address and port to listen on.
|
||||||
@@ -60,6 +62,8 @@ filer_engine=local
|
|||||||
listen=127.0.0.1:21
|
listen=127.0.0.1:21
|
||||||
|
|
||||||
## Whether to support SSL. Server must be compiled with WITH_SSL=ON.
|
## Whether to support SSL. Server must be compiled with WITH_SSL=ON.
|
||||||
|
## This is highly recommended as it allows clients to secure their login
|
||||||
|
## credentials. SSL support is provided as explicit (AUTH command).
|
||||||
## Syntax: ssl=<bool>
|
## Syntax: ssl=<bool>
|
||||||
ssl=on
|
ssl=on
|
||||||
|
|
||||||
@@ -67,9 +71,13 @@ ssl=on
|
|||||||
utf8=off
|
utf8=off
|
||||||
|
|
||||||
[ssl]
|
[ssl]
|
||||||
|
## Configuration for FTPS (Explicit SSL)
|
||||||
|
|
||||||
certificate=cert.pem
|
certificate=cert.pem
|
||||||
private_key=key.pem
|
private_key=key.pem
|
||||||
|
|
||||||
|
## Ciphers to use for SSL.
|
||||||
|
## Default: ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH
|
||||||
ciphers=ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH
|
ciphers=ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH
|
||||||
|
|
||||||
ssl_v2=no
|
ssl_v2=no
|
||||||
@@ -81,9 +89,10 @@ tls_v1_3=yes
|
|||||||
compression=yes
|
compression=yes
|
||||||
prefer_server_ciphers=yes
|
prefer_server_ciphers=yes
|
||||||
|
|
||||||
|
#### LOGGING
|
||||||
|
## Filenames for loglevels.
|
||||||
[logging]
|
[logging]
|
||||||
## Log messages to file. Logging has multiple options pertaining to each
|
## Log server output files.
|
||||||
## loglevel. All values are paths.
|
|
||||||
## Syntax: [console|critical|error|warning|info|debug|all]=<path>
|
## Syntax: [console|critical|error|warning|info|debug|all]=<path>
|
||||||
info=digftp.info.log
|
info=digftp.info.log
|
||||||
error=digftp.error.log
|
error=digftp.error.log
|
||||||
@@ -97,3 +106,6 @@ file=passdb
|
|||||||
## Root of logged in user. Can use string formatting.
|
## Root of logged in user. Can use string formatting.
|
||||||
## Syntax: home_path=<path>
|
## Syntax: home_path=<path>
|
||||||
home_path=/home/%u/
|
home_path=/home/%u/
|
||||||
|
|
||||||
|
[pam]
|
||||||
|
chroot=yes
|
||||||
1
conf/ftp.motd
Normal file
1
conf/ftp.motd
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Welcome %u!
|
||||||
18
src/auth.h
18
src/auth.h
@@ -7,6 +7,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
#include "build.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
@@ -40,14 +41,15 @@ public:
|
|||||||
virtual bool authenticate(ClientAuthDetails* details) { return false; }
|
virtual bool authenticate(ClientAuthDetails* details) { return false; }
|
||||||
|
|
||||||
virtual void setUserDirectory(ClientAuthDetails* details) {
|
virtual void setUserDirectory(ClientAuthDetails* details) {
|
||||||
std::string userdir = this->user_directory;
|
std::map<std::string, std::string> vars = {
|
||||||
// Replace escaped var symbol with a dummy character
|
{std::string(APPNAME_VAR), APPNAME},
|
||||||
userdir = replace(userdir, std::string(CNF_PERCENTSYM_VAR), std::string(1, 0xFE));
|
{std::string(VERSION_VAR), VERSION},
|
||||||
userdir = replace(userdir, std::string(CNF_USERNAME_VAR), std::string(details->username));
|
{std::string(USERNAME_VAR), std::string(details->username)},
|
||||||
userdir = replace(userdir, std::string(CNF_PASSWORD_VAR), std::string(details->password));
|
{std::string(DATE_VAR), getCurrentUTCTime()},
|
||||||
userdir = replace(userdir, std::string(CNF_HOSTNAME_VAR), std::string(details->hostname));
|
{std::string(TIME_VAR), getCurrentUTCDate()}
|
||||||
// Replace dummy character and return
|
};
|
||||||
userdir = replace(userdir, std::string(1, 0xFE), "%");
|
|
||||||
|
std::string userdir = conf_format(this->user_directory, vars);
|
||||||
|
|
||||||
// Store the computed path in the details structure
|
// Store the computed path in the details structure
|
||||||
strncpy(details->home_dir, userdir.c_str(), sizeof(details->home_dir) - 1);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
filer = default_filer_factory();
|
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) {
|
void addOption(std::string name, bool toggle) {
|
||||||
@@ -521,41 +521,16 @@ private:
|
|||||||
uint8_t flags = {};
|
uint8_t flags = {};
|
||||||
int state = FTP_STATE_GUEST;
|
int state = FTP_STATE_GUEST;
|
||||||
|
|
||||||
|
// Data
|
||||||
int data_fd;
|
int data_fd;
|
||||||
int data_sock;
|
int data_sock;
|
||||||
struct sockaddr_in data_address;
|
struct sockaddr_in data_address;
|
||||||
|
|
||||||
|
// Rename (RNFR RNTO)
|
||||||
std::string rename_from;
|
std::string rename_from;
|
||||||
bool rename_pending = false;
|
bool rename_pending = false;
|
||||||
|
|
||||||
void authedInit() {
|
// SSL
|
||||||
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* control_ssl;
|
SSL* control_ssl;
|
||||||
SSL* data_ssl;
|
SSL* data_ssl;
|
||||||
SSL_SESSION* cached_session = nullptr;
|
SSL_SESSION* cached_session = nullptr;
|
||||||
@@ -653,4 +628,82 @@ private:
|
|||||||
|
|
||||||
return true;
|
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:
|
public:
|
||||||
virtual ~Filer() {};
|
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 setRoot(std::string _root) = 0;
|
||||||
virtual file_data setCWD(std::string _cwd) = 0;
|
virtual file_data setCWD(std::string _cwd) = 0;
|
||||||
virtual std::filesystem::path getRoot() = 0;
|
virtual std::filesystem::path getRoot() = 0;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#ifndef GLOBALS_H
|
#ifndef GLOBALS_H
|
||||||
#define GLOBALS_H
|
#define GLOBALS_H
|
||||||
|
|
||||||
#define CNF_PERCENTSYM_VAR "%%"
|
#define APPNAME_VAR "a"
|
||||||
#define CNF_VERSION_VAR "%v"
|
#define VERSION_VAR "v"
|
||||||
#define CNF_USERNAME_VAR "%u"
|
#define USERNAME_VAR "u"
|
||||||
#define CNF_PASSWORD_VAR "%p"
|
#define PASSWORD_VAR "p"
|
||||||
#define CNF_HOSTNAME_VAR "%h"
|
#define HOSTNAME_VAR "h"
|
||||||
|
#define DATE_VAR "d"
|
||||||
|
#define TIME_VAR "t"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
22
src/main.cpp
22
src/main.cpp
@@ -183,29 +183,24 @@ void runClient(struct ftpconn* cfd) {
|
|||||||
void initializePlugins() {
|
void initializePlugins() {
|
||||||
std::string plugin_dir = config->getValue("core", "plugin_path", PLUGIN_DIR);
|
std::string plugin_dir = config->getValue("core", "plugin_path", PLUGIN_DIR);
|
||||||
|
|
||||||
// Load auth plugins
|
|
||||||
auto& auth_manager = PluginManager<Auth>::getInstance();
|
auto& auth_manager = PluginManager<Auth>::getInstance();
|
||||||
auth_manager.setLogger(logger);
|
|
||||||
|
|
||||||
// Load filer plugins
|
|
||||||
auto& filer_manager = PluginManager<Filer>::getInstance();
|
auto& filer_manager = PluginManager<Filer>::getInstance();
|
||||||
|
|
||||||
|
auth_manager.setLogger(logger);
|
||||||
filer_manager.setLogger(logger);
|
filer_manager.setLogger(logger);
|
||||||
|
|
||||||
|
// Try loading all plugins into both managers
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(plugin_dir)) {
|
for (const auto& entry : std::filesystem::directory_iterator(plugin_dir)) {
|
||||||
if (entry.path().extension() == ".so") {
|
if (entry.path().extension() == ".so") {
|
||||||
const std::string& filename = entry.path().filename().string();
|
logger->print(LOGLEVEL_DEBUG, "Loading plugin: %s", entry.path().c_str());
|
||||||
if (filename.find(PluginTraits<Auth>::pluginPrefix()) == 0) {
|
|
||||||
auth_manager.loadPlugin(entry.path().string());
|
auth_manager.loadPlugin(entry.path().string());
|
||||||
}
|
|
||||||
else if (filename.find(PluginTraits<Filer>::pluginPrefix()) == 0) {
|
|
||||||
filer_manager.loadPlugin(entry.path().string());
|
filer_manager.loadPlugin(entry.path().string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize auth
|
// Initialize auth
|
||||||
std::string auth_type = config->getValue("core", "auth_engine", "pam");
|
std::string auth_type = config->getValue("engines", "auth", "pam");
|
||||||
auth = auth_manager.createPlugin(auth_type);
|
auth = auth_manager.createPlugin(auth_type, config->get(auth_type)->get());
|
||||||
|
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
logger->print(LOGLEVEL_CRITICAL, "Failed to create auth engine: %s", auth_type.c_str());
|
logger->print(LOGLEVEL_CRITICAL, "Failed to create auth engine: %s", auth_type.c_str());
|
||||||
@@ -213,7 +208,7 @@ void initializePlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize filer
|
// 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);
|
default_filer_factory = filer_manager.getFactory(filer_type);
|
||||||
|
|
||||||
if (!default_filer_factory) {
|
if (!default_filer_factory) {
|
||||||
@@ -231,7 +226,8 @@ int main(int argc , char *argv[]) {
|
|||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
config = new ConfigFile(concatPath(std::string(CONFIG_DIR), "ftp.conf"));
|
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(
|
sscanf(
|
||||||
config->getValue("net", "listen", "127.0.0.1:21").c_str(),
|
config->getValue("net", "listen", "127.0.0.1:21").c_str(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
typedef int (*GetAPIVersionFunc)();
|
typedef int (*GetAPIVersionFunc)();
|
||||||
typedef void (*SetLoggerFunc)(Logger*);
|
typedef void (*SetLoggerFunc)(Logger*);
|
||||||
|
typedef const char* (*GetPluginTypeFunc)();
|
||||||
|
|
||||||
class IPlugin {
|
class IPlugin {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -1,36 +1,42 @@
|
|||||||
#ifndef PLUGIN_MANAGER_H
|
#ifndef PLUGIN_MANAGER_H
|
||||||
#define PLUGIN_MANAGER_H
|
#define PLUGIN_MANAGER_H
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "plugin.h"
|
#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>
|
template<typename T>
|
||||||
class PluginManager {
|
class PluginManager : public IPluginManager {
|
||||||
public:
|
public:
|
||||||
static PluginManager<T>& getInstance() {
|
static PluginManager<T>& getInstance() {
|
||||||
static PluginManager<T> instance;
|
static PluginManager<T> instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLogger(Logger* log) {
|
void setLogger(Logger* log) override {
|
||||||
logger = log;
|
|
||||||
IPlugin::setLogger(log);
|
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);
|
auto it = plugins.find(type);
|
||||||
if (it != plugins.end()) {
|
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());
|
if (logger) logger->print(LOGLEVEL_ERROR, "Plugin type '%s' not found", type.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadPlugin(const std::string& path) {
|
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);
|
void* handle = dlopen(path.c_str(), RTLD_LAZY);
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
if (logger) logger->print(LOGLEVEL_ERROR, "Failed to load plugin %s: %s",
|
if (logger) logger->print(LOGLEVEL_ERROR, "Failed to load plugin %s: %s",
|
||||||
@@ -47,11 +53,20 @@ public:
|
|||||||
auto get_desc = (const char* (*)())dlsym(handle, "getPluginDescription");
|
auto get_desc = (const char* (*)())dlsym(handle, "getPluginDescription");
|
||||||
auto get_ver = (const char* (*)())dlsym(handle, "getPluginVersion");
|
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);
|
dlclose(handle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check API version compatibility
|
||||||
if (!checkAPIVersion(get_api_version(), path)) {
|
if (!checkAPIVersion(get_api_version(), path)) {
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
return false;
|
return false;
|
||||||
@@ -60,18 +75,21 @@ public:
|
|||||||
std::string plugin_name = get_name();
|
std::string plugin_name = get_name();
|
||||||
setLogger(logger);
|
setLogger(logger);
|
||||||
|
|
||||||
|
// Check if already loaded
|
||||||
if (plugins.find(plugin_name) != plugins.end()) {
|
if (plugins.find(plugin_name) != plugins.end()) {
|
||||||
if (logger) logger->print(LOGLEVEL_DEBUG, "Plugin %s is already loaded", plugin_name.c_str());
|
if (logger) logger->print(LOGLEVEL_DEBUG, "Plugin %s is already loaded", plugin_name.c_str());
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and store plugin info
|
||||||
auto plugin_info = createPluginInfo(plugin_name, get_desc(), get_ver(), create, destroy,
|
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;
|
plugins[plugin_name] = plugin_info;
|
||||||
if (logger) logger->print(LOGLEVEL_INFO, "Loaded plugin: %s v%s",
|
if (logger) logger->print(LOGLEVEL_INFO, "Loaded plugin: %s v%s (%s interface)",
|
||||||
plugin_name.c_str(), plugin_info.version.c_str());
|
plugin_name.c_str(), plugin_info.version.c_str(),
|
||||||
|
PluginTraits<T>::interfaceName());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct PluginTraits<Auth> {
|
|||||||
|
|
||||||
static const char* createFuncName() { return "createAuthPlugin"; }
|
static const char* createFuncName() { return "createAuthPlugin"; }
|
||||||
static const char* destroyFuncName() { return "destroyAuthPlugin"; }
|
static const char* destroyFuncName() { return "destroyAuthPlugin"; }
|
||||||
static const char* pluginPrefix() { return "libauth_"; }
|
static const char* interfaceName() { return "Auth"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
@@ -42,7 +42,7 @@ struct PluginTraits<Filer> {
|
|||||||
|
|
||||||
static const char* createFuncName() { return "createFilerPlugin"; }
|
static const char* createFuncName() { return "createFilerPlugin"; }
|
||||||
static const char* destroyFuncName() { return "destroyFilerPlugin"; }
|
static const char* destroyFuncName() { return "destroyFilerPlugin"; }
|
||||||
static const char* pluginPrefix() { return "libfiler_"; }
|
static const char* interfaceName() { return "Filer"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
66
src/util.h
66
src/util.h
@@ -9,6 +9,7 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
std::string replace(std::string subject, const std::string& search, const std::string& replace) {
|
std::string replace(std::string subject, const std::string& search, const std::string& replace) {
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@@ -19,6 +20,55 @@ std::string replace(std::string subject, const std::string& search, const std::s
|
|||||||
return subject;
|
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>
|
template <typename Out>
|
||||||
void split(const std::string &s, char delim, Out result, int limit) {
|
void split(const std::string &s, char delim, Out result, int limit) {
|
||||||
int it = 0;
|
int it = 0;
|
||||||
@@ -133,6 +183,22 @@ std::string concatPath(std::string dir1, std::string dir2) {
|
|||||||
return std::filesystem::weakly_canonical(dir1+"/"+dir2).string();
|
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) {
|
static char* trim(char *str) {
|
||||||
char *end;
|
char *end;
|
||||||
while(isspace(*str))
|
while(isspace(*str))
|
||||||
|
|||||||
Reference in New Issue
Block a user