From 164b2c19ee719dd75f499481912485f9d8f3f05c Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 26 Sep 2021 15:05:15 +0200 Subject: [PATCH 1/5] userDao: Implement list() --- database/userdao.h | 2 ++ database/userdaosqlite.cpp | 34 ++++++++++++++++++++++++++++++++++ database/userdaosqlite.h | 1 + 3 files changed, 37 insertions(+) diff --git a/database/userdao.h b/database/userdao.h index 20cd271..5500d5a 100644 --- a/database/userdao.h +++ b/database/userdao.h @@ -3,6 +3,7 @@ #include #include #include "../user.h" +#include "queryoption.h" class UserDao { public: @@ -10,6 +11,7 @@ class UserDao virtual bool exists(std::string username) = 0; virtual std::optional find(std::string username) = 0; virtual std::optional find(int id) = 0; + virtual std::vector list(QueryOption o) = 0; virtual void deleteUser(std::string username) = 0; virtual void save(const User &u) = 0; virtual ~UserDao(){}; diff --git a/database/userdaosqlite.cpp b/database/userdaosqlite.cpp index 2ac7719..3691de9 100644 --- a/database/userdaosqlite.cpp +++ b/database/userdaosqlite.cpp @@ -23,6 +23,7 @@ SOFTWARE. #include #include #include "userdaosqlite.h" +#include "sqlitequeryoption.h" UserDaoSqlite::UserDaoSqlite() { @@ -82,6 +83,39 @@ std::optional UserDaoSqlite::find(int id) } } +std::vector UserDaoSqlite::list(QueryOption o) +{ + std::vector result; + + try + { + + std::string queryOption = SqliteQueryOption(o).setOrderByColumn("username").setPrependWhere(true).build(); + std::string query = "SELECT username, password, salt, permissions, enabled FROM user " + queryOption; + + *db << query >> + [&](std::string username, std::vector pw, std::vector salt, int permisisons, bool enabled) + { + User tmp; + tmp.login = username; + tmp.password = pw; + tmp.salt = salt; + tmp.permissions = Permissions{permisisons}; + tmp.enabled = enabled; + result.push_back(tmp); + }; + } + catch(const sqlite::errors::no_rows &e) + { + return result; + } + catch(sqlite::sqlite_exception &e) + { + throwFrom(e); + } + return result; +} + void UserDaoSqlite::deleteUser(std::string username) { // What to do with the contributions of the user? diff --git a/database/userdaosqlite.h b/database/userdaosqlite.h index bcbc2df..f913feb 100644 --- a/database/userdaosqlite.h +++ b/database/userdaosqlite.h @@ -13,6 +13,7 @@ class UserDaoSqlite : public UserDao, protected SqliteDao std::optional find(std::string username); std::optional find(int id); + std::vector list(QueryOption o); void deleteUser(std::string username); void save(const User &u); using SqliteDao::SqliteDao; -- 2.45.2 From 5037a17fbac9dc897cdb7bbdd0fcba1f32e5945f Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 3 Oct 2021 16:47:35 +0200 Subject: [PATCH 2/5] utils: introduce trim() --- utils.cpp | 17 +++++++++++++++++ utils.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/utils.cpp b/utils.cpp index 7783e6d..d936f7a 100644 --- a/utils.cpp +++ b/utils.cpp @@ -181,3 +181,20 @@ std::string utils::toISODate(time_t t) } return std::string{result}; } + +std::string utils::trim(const std::string &str) +{ + std::string_view chars = " \t\n\r"; + std::string_view view = str; + auto n = view.find_first_not_of(chars); + if(n != std::string_view::npos) + { + view.remove_prefix(n); + } + n = view.find_last_not_of(chars); + if(n != std::string_view::npos) + { + view.remove_suffix(view.size() - n - 1); + } + return std::string{view}; +} diff --git a/utils.h b/utils.h index d19be1b..5173a4e 100644 --- a/utils.h +++ b/utils.h @@ -93,5 +93,7 @@ template inline std::string toString(const T &v) return std::string(v.begin(), v.end()); } +std::string trim(const std::string &str); + } // namespace utils #endif -- 2.45.2 From 8b044d712bb3aa7893b670a343dbddfa2e75489d Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 3 Oct 2021 17:01:03 +0200 Subject: [PATCH 3/5] Authenticator: Introduce AUTH_DEFAULT_SALT_SIZE --- authenticator.cpp | 5 +++-- authenticator.h | 1 + handlers/handlerusersettings.cpp | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/authenticator.cpp b/authenticator.cpp index 9a01f71..ee355fa 100644 --- a/authenticator.cpp +++ b/authenticator.cpp @@ -42,11 +42,12 @@ std::vector Authenticator::pbkdf5(std::string password, const std::vector< unsigned char hash[32]; const EVP_MD *sha256 = EVP_sha256(); const unsigned char *rawsalt = reinterpret_cast(salt.data()); - int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash); + int ret = + PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash); if(ret != 1) { Logger::error() << "Authenticator: pbkdf5: Failed to create hash"; - return { }; + return {}; } std::vector result; diff --git a/authenticator.h b/authenticator.h index 8620964..07322c1 100644 --- a/authenticator.h +++ b/authenticator.h @@ -3,6 +3,7 @@ #include #include "database/userdao.h" +#define AUTH_DEFAULT_SALT_SIZE 32 enum AuthenticationError { UserNotFound, diff --git a/handlers/handlerusersettings.cpp b/handlers/handlerusersettings.cpp index e8d63ec..6957c23 100644 --- a/handlers/handlerusersettings.cpp +++ b/handlers/handlerusersettings.cpp @@ -15,19 +15,20 @@ Response HandlerUserSettings::handleRequest(const Request &r) if(newpassword != newpasswordconfirm) { - //TODO: is not nice, users has to hit the back button... + // TODO: is not nice, users has to hit the back button... return this->errorResponse("Passwords don't match", "The entered new passwords don't match"); } auto userDao = this->database->createUserDao(); Authenticator authenticator(*userDao); - std::variant authresult = authenticator.authenticate(this->userSession->user.login, oldpassword); + std::variant authresult = + authenticator.authenticate(this->userSession->user.login, oldpassword); if(std::holds_alternative(authresult)) { return this->errorResponse("Invalid current password", "The old password you entered is invalid"); } Random r; - std::vector salt = r.getRandom(23); + std::vector salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE); User user = std::get(authresult); user.salt = salt; user.password = authenticator.hash(newpassword, user.salt); -- 2.45.2 From 1082f8ac5a185b9f8f0dd12e18f4159d0131cb42 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 3 Oct 2021 17:01:44 +0200 Subject: [PATCH 4/5] Permissions: Add toString() Get a (reasonable) string representation of the permissions contained in a Permissions object. --- permissions.cpp | 28 ++++++++++++++++++++++++++++ permissions.h | 19 +++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/permissions.cpp b/permissions.cpp index 712437f..7a3644f 100644 --- a/permissions.cpp +++ b/permissions.cpp @@ -20,6 +20,17 @@ SOFTWARE. */ #include "permissions.h" +static const std::map permmap = {{"can_read", PERM_CAN_READ}, + {"can_edit", PERM_CAN_EDIT}, + {"can_page_history", PERM_CAN_PAGE_HISTORY}, + {"can_global_history", PERM_CAN_GLOBAL_HISTORY}, + {"can_delete", PERM_CAN_DELETE}, + {"can_see_page_list", PERM_CAN_SEE_PAGE_LIST}, + {"can_create", PERM_CAN_CREATE}, + {"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST}, + {"can_see_links_here", PERM_CAN_SEE_LINKS_HERE}, + {"can_search", PERM_CAN_SEARCH}}; + Permissions::Permissions(int permissions) { this->permissions = permissions; @@ -36,3 +47,20 @@ Permissions::Permissions(const std::string &str) } } } + +std::string Permissions::toString(int perms) +{ + std::string result; + for(auto pair : permmap) + { + if(pair.second & perms) + { + result += pair.first + ","; + } + } + if(result.size() > 0) + { + result.pop_back(); + } + return result; +} diff --git a/permissions.h b/permissions.h index 9139e58..d66a937 100644 --- a/permissions.h +++ b/permissions.h @@ -14,20 +14,12 @@ #include #include + class Permissions + { private: int permissions = 0; - const std::map permmap = {{"can_read", PERM_CAN_READ}, - {"can_edit", PERM_CAN_EDIT}, - {"can_page_history", PERM_CAN_PAGE_HISTORY}, - {"can_global_history", PERM_CAN_GLOBAL_HISTORY}, - {"can_delete", PERM_CAN_DELETE}, - {"can_see_page_list", PERM_CAN_SEE_PAGE_LIST}, - {"can_create", PERM_CAN_CREATE}, - {"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST}, - {"can_see_links_here", PERM_CAN_SEE_LINKS_HERE}, - {"can_search", PERM_CAN_SEARCH}}; public: Permissions() @@ -102,6 +94,13 @@ class Permissions { return this->permissions & PERM_CAN_SEE_PAGE_LIST; } + + std::string toString() const + { + return Permissions::toString(this->permissions); + } + + static std::string toString(int perms); }; #endif // PERMISSIONS_H -- 2.45.2 From 3d0fce590ba48ee680628549fb53b8b3b21b2763 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 3 Oct 2021 17:03:22 +0200 Subject: [PATCH 5/5] Introduce CLI main: Parse args using getopt_long() in main(). Begin implementation of a CLI. It can be launched using ./qswiki config --cli. Allow connecting to another instance using "attach" command. This uses Unix domain sockets, and in the future can be used to drop caches, reload template, etc. Closes: #21 --- cli.cpp | 220 +++++++++++++++++++++++++++++++++++++++++++++++++ cli.h | 66 +++++++++++++++ cliconsole.cpp | 137 ++++++++++++++++++++++++++++++ cliconsole.h | 27 ++++++ cliserver.cpp | 77 +++++++++++++++++ cliserver.h | 16 ++++ qswiki.cpp | 61 +++++++++++++- 7 files changed, 603 insertions(+), 1 deletion(-) create mode 100644 cli.cpp create mode 100644 cli.h create mode 100644 cliconsole.cpp create mode 100644 cliconsole.h create mode 100644 cliserver.cpp create mode 100644 cliserver.h diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 0000000..cc8ce68 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,220 @@ +#include +#include +#include + +#include "cli.h" +#include "utils.h" +#include "random.h" +#include "authenticator.h" +#include "config.h" +#include "logger.h" + +CLIHandler::CLIHandler(Config &config, Database &db) +{ + this->db = &db; + this->conf = &config; +} + +std::pair CLIHandler::user_add(const std::vector &args) +{ + std::string username = args.at(0); + std::string password = args.at(1); + + auto userDao = db->createUserDao(); + + Permissions perms = this->conf->handlersConfig.anon_permissions; + int p = perms.getPermissions(); + p |= PERM_CAN_CREATE | PERM_CAN_SEARCH | PERM_CAN_EDIT; + Permissions newPermissions = Permissions{p}; + + Random r; + User user; + user.enabled = true; + user.login = username; + user.salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE); + user.permissions = newPermissions; + + Authenticator auth{*userDao}; + std::vector hashResult = auth.hash(password, user.salt); + if(hashResult.empty()) + { + return {false, "Error during hashing - Got empty hash"}; + } + user.password = hashResult; + + try + { + userDao->save(user); + } + catch(std::runtime_error &e) + { + return {false, "Exception: " + std::string(e.what())}; + } + return {true, ""}; +} + +std::pair CLIHandler::user_change_pw(const std::vector &args) +{ + std::string username = args.at(0); + std::string password = args.at(1); + + auto userDao = db->createUserDao(); + + auto user = userDao->find(username); + if(user) + { + Random r; + Authenticator auth{*userDao}; + user->salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE); + user->password = auth.hash(password, user->salt); + if(user->password.empty()) + { + return {false, "Error during hashing - Got empty hash"}; + } + + userDao->save(*user); + } + else + { + return {false, "User not found"}; + } +} + +std::pair CLIHandler::user_rename(const std::vector &args) +{ + + return {true, ""}; +} + +std::pair CLIHandler::user_set_perms(const std::vector &args) +{ + auto userDao = this->db->createUserDao(); + std::string username = args.at(0); + std::string permission_string = args.at(1); + + Permissions perms{permission_string}; + + auto user = userDao->find(username); + if(user) + { + user->permissions = perms; + userDao->save(*user); + user_show({username}); + return {true, ""}; + } + + return {false, "User not found"}; +} + +std::pair CLIHandler::user_list(const std::vector &args) +{ + auto userDao = this->db->createUserDao(); + QueryOption o; + auto result = userDao->list(o); + std::stringstream stream; + for(User &u : result) + { + stream << u.login << "\t" << std::string(u.enabled ? "enabled" : "disabled") << "\t" << u.permissions.toString() + << std::endl; + } + return {true, stream.str()}; +} + +std::pair CLIHandler::user_show(const std::vector &args) +{ + std::string username = args.at(0); + auto userDao = this->db->createUserDao(); + auto user = userDao->find(username); + std::stringstream stream; + if(user) + { + stream << "Username: " << user->login << std::endl; + + stream << "Enabled: " << std::string(user->enabled ? "yes" : "no") << std::endl; + stream << "Permissions (general): " << user->permissions.toString() << std::endl; + return {true, stream.str()}; + } + return {false, "User not found"}; +} + +std::pair CLIHandler::attach(const std::vector &args) +{ + /* TODO: consider authentication */ + pid_t pid = getpid(); + return {true, "Hi, I am pid: " + std::to_string(pid)}; +} + +std::pair CLIHandler::cli_help(const std::vector &args) +{ + std::string command; + if(args.size() > 0) + command = args[0]; + std::stringstream stream; + for(struct cmd &cmd : cmds) + { + if(command != "" && cmd.name != command) + { + continue; + } + + stream << cmd.name << " - " << cmd.helptext << std::endl; + for(struct cmd &subCmd : cmd.subCommands) + { + stream << "\t" << subCmd.name << " " << subCmd.helptext << std::endl; + } + stream << std::endl; + } + return {true, stream.str()}; +} + +std::pair CLIHandler::processCommand(const std::vector &commands, std::string cmd, + const std::vector &args) +{ + auto c = std::find_if(commands.begin(), commands.end(), + [&cmd](const struct CLIHandler::cmd &a) { return a.name == cmd; }); + if(c == commands.end()) + { + std::cout << "No such command: " << cmd << std::endl; + return cli_help({}); + } + + if(!c->subCommands.empty() && args.size() >= c->required_args) + { + std::string newcmd = args[0]; + std::vector newargs = args; + newargs.erase(newargs.begin()); + return processCommand(c->subCommands, newcmd, newargs); + } + if(args.size() < c->required_args) + { + return {false, "not enough parameters passed"}; + } + + try + { + return c->func(this, args); + } + catch(std::runtime_error &e) + { + return {false, "Exception: " + std::string(e.what())}; + } + return {false, ""}; +} + +std::pair CLIHandler::processCommand(std::string cmd, const std::vector &args) +{ + return processCommand(this->cmds, cmd, args); +} + +std::pair> CLIHandler::splitCommand(std::string input) +{ + input = utils::trim(input); + std::vector splitted = utils::split(input, "\\s+"); + if(splitted.empty()) + { + return {" ", splitted}; + } + std::string cmd = splitted[0]; + splitted.erase(splitted.begin()); + return {cmd, splitted}; +} diff --git a/cli.h b/cli.h new file mode 100644 index 0000000..ab26530 --- /dev/null +++ b/cli.h @@ -0,0 +1,66 @@ +#ifndef CLI_H +#define CLI_H +#include +#include +#include +#include "database/database.h" +#include "config.h" + +class CLIHandler +{ + struct cmd + { + std::string name; + std::string helptext; + unsigned int required_args; + std::vector subCommands; + std::function(CLIHandler *, const std::vector &)> func; + }; + + private: + Database *db; + Config *conf; + + protected: + std::pair attach(const std::vector &args); + std::pair cli_help(const std::vector &args); + std::pair user_add(const std::vector &args); + std::pair user_change_pw(const std::vector &args); + std::pair user_rename(const std::vector &args); + std::pair user_set_perms(const std::vector &args); + std::pair user_list(const std::vector &args); + std::pair user_show(const std::vector &args); + + std::vector cmds{ + {{"user", + "user operations on the database", + 1, + {{{"add", "[user] [password] - creates a user", 2, {}, &CLIHandler::user_add}, + {"changepw", "[user] [password] - changes the password of user", 2, {}, &CLIHandler::user_change_pw}, + {"rename", "[user] [new name] - renames a user", 2, {}, &CLIHandler::user_rename}, + {"setperms", "[user] [perms] - sets the permissions of the user", 2, {}, &CLIHandler::user_set_perms}, + {"list", "- lists users", 0, {}, &CLIHandler::user_list}, + {"show", "[user] - show detailed information about user", 1, {}, &CLIHandler::user_show}}}, + &CLIHandler::cli_help}, + {"exit", + "exit cli", + 0, + {}, + [](CLIHandler *, const std::vector &args) -> std::pair + { + exit(EXIT_SUCCESS); + return {true, ""}; + }}, + {"help", "print this help", 0, {}, &CLIHandler::cli_help}, + {"attach", "attach to running instance", 0, {}, &CLIHandler::attach}}}; + + std::pair processCommand(const std::vector &commands, std::string cmd, + const std::vector &args); + + public: + CLIHandler(Config &config, Database &d); + std::pair processCommand(std::string cmd, const std::vector &args); + static std::pair> splitCommand(std::string input); +}; + +#endif // CLI_H diff --git a/cliconsole.cpp b/cliconsole.cpp new file mode 100644 index 0000000..28e1d10 --- /dev/null +++ b/cliconsole.cpp @@ -0,0 +1,137 @@ +#include "cliconsole.h" + +CLIConsole::CLIConsole(CLIHandler &cliHandler, std::string socketPath) +{ + this->handler = &cliHandler; + this->socketPath = socketPath; +} + +std::pair CLIConsole::send(std::string input) +{ + ssize_t ret = + sendto(this->sock, input.c_str(), input.size(), 0, (const sockaddr *)&this->server, sizeof(this->server)); + if((size_t)ret != input.size()) + { + return {false, "sendto failed: " + std::to_string(ret) + " " + std::string(strerror(errno))}; + } + char buffer[1024] = {0}; + ret = recvfrom(this->sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL); + if(ret == -1) + { + return {false, "recvfrom failed: " + std::string(strerror(errno))}; + } + + bool success = false; + std::string_view view = buffer; + if(view[0] == '1') + { + success = true; + } + view.remove_prefix(1); + std::string msg = std::string{view}; + + return {success, msg}; +} + +void CLIConsole::attach() +{ + if(attached) + { + std::cout << "Already attached" << std::endl; + return; + } + if(socketPath.size() > sizeof(this->server.sun_path) - 1) + { + std::cout << "Socket path too long" << std::endl; + return; + } + memset(&this->server, 0, sizeof(this->server)); + this->server.sun_family = AF_UNIX; + memcpy(&this->server.sun_path, socketPath.c_str(), socketPath.size()); + this->server.sun_path[socketPath.size()] = 0; + + int s = socket(AF_UNIX, SOCK_DGRAM, 0); + if(s == -1) + { + std::cout << "Failed to create socket" << strerror(errno) << std::endl; + return; + } + this->sock = s; + + struct sockaddr_un client; + client.sun_family = AF_UNIX; + client.sun_path[0] = '\0'; + + int ret = bind(this->sock, (struct sockaddr *)&client, sizeof(client)); + if(ret != 0) + { + std::cout << "bind() failed: " << strerror(errno) << std::endl; + return; + } + auto result = this->send("attach"); + if(result.first) + { + std::cout << "Attached successfully: " << result.second << std::endl; + this->attached = true; + } + else + { + std::cout << "Attached unsuccessfully: " << result.second << std::endl; + } +} + +void CLIConsole::startInteractive() +{ + std::cout << "qswiki CLI" << std::endl; + std::cout << "not attached - use 'attach' to connect to running instance" << std::endl; + + while(true) + { + std::string input; + std::cout << "> "; + std::getline(std::cin, input); + + if(std::cin.bad() || std::cin.eof()) + { + std::cout << "Exiting" << std::endl; + return; + } + if(input.empty()) + { + continue; + } + + auto pair = CLIHandler::splitCommand(input); + if(pair.first == "exit") + { + std::cout << "Exiting CLI"; + exit(EXIT_SUCCESS); + } + if(pair.first == "attach") + { + attach(); + continue; + } + + std::pair result; + if(!attached) + { + result = handler->processCommand(pair.first, pair.second); + } + else + { + result = this->send(input); + } + + if(!result.second.empty()) + { + std::cout << result.second << std::endl; + } + if(!result.first) + { + std::cout << "Command failed" << std::endl; + } + } + + std::cout << "\n"; +} diff --git a/cliconsole.h b/cliconsole.h new file mode 100644 index 0000000..1545020 --- /dev/null +++ b/cliconsole.h @@ -0,0 +1,27 @@ +#ifndef CLICONSOLE_H +#define CLICONSOLE_H + +#include +#include +#include +#include +#include +#include "cli.h" + +class CLIConsole +{ + private: + struct sockaddr_un server; + int sock; + CLIHandler *handler; + std::string socketPath; + bool attached = false; + std::pair send(std::string input); + void attach(); + + public: + CLIConsole(CLIHandler &cliHandler, std::string socketPath); + void startInteractive(); +}; + +#endif // CLICONSOLE_H diff --git a/cliserver.cpp b/cliserver.cpp new file mode 100644 index 0000000..2ba76d8 --- /dev/null +++ b/cliserver.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include "cliserver.h" +#include "logger.h" +CLIServer::CLIServer(CLIHandler &handler) +{ + this->handler = &handler; +} + +bool CLIServer::detachServer(std::string socketpath) +{ + struct sockaddr_un name; + const int max_socket_length = sizeof(name.sun_path) - 1; + if(socketpath.size() > max_socket_length) + { + perror("socket path too long"); + return false; + } + int s = socket(AF_UNIX, SOCK_DGRAM, 0); + if(s == -1) + { + perror("socket"); + return false; + } + + memset(&name, 0, sizeof(name)); + name.sun_family = AF_UNIX; + memcpy(&name.sun_path, socketpath.c_str(), socketpath.size()); + + unlink(socketpath.c_str()); + int ret = bind(s, (const struct sockaddr *)&name, sizeof(name)); + if(ret == -1) + { + perror("bind"); + exit(EXIT_FAILURE); + } + + auto worker = [=] + { + while(true) + { + char buffer[1024] = {0}; + struct sockaddr_un peer; + socklen_t peerlen = sizeof(peer); + + int ret = recvfrom(s, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peerlen); + if(ret == -1) + { + Logger::error() << "Error during recvfrom in CLI server: " << strerror(errno); + return false; + } + + std::string input{buffer}; + + auto pair = CLIHandler::splitCommand(input); + + auto result = handler->processCommand(pair.first, pair.second); + char resultCode = '0'; + if(result.first) + { + resultCode = '1'; + } + std::string resultString; + resultString += resultCode; + resultString += result.second; + ret = sendto(s, resultString.c_str(), resultString.size(), 0, (struct sockaddr *)&peer, peerlen); + if(ret == -1) + { + Logger::error() << "Error during sendto in CLI server: " << strerror(errno); + } + } + }; + std::thread t1{worker}; + t1.detach(); + return true; +} diff --git a/cliserver.h b/cliserver.h new file mode 100644 index 0000000..9745da8 --- /dev/null +++ b/cliserver.h @@ -0,0 +1,16 @@ +#ifndef CLISERVER_H +#define CLISERVER_H +#include +#include "cli.h" + +class CLIServer +{ + private: + CLIHandler *handler = nullptr; + + public: + CLIServer(CLIHandler &handler); + bool detachServer(std::string socketpath); +}; + +#endif // CLISERVER_H diff --git a/qswiki.cpp b/qswiki.cpp index 1f8f53f..9d5738e 100644 --- a/qswiki.cpp +++ b/qswiki.cpp @@ -25,6 +25,7 @@ SOFTWARE. #include #include #include +#include #include "gateway/gatewayinterface.h" #include "gateway/gatewayfactory.h" #include "handlers/handlerfactory.h" @@ -37,6 +38,10 @@ SOFTWARE. #include "requestworker.h" #include "cache/fscache.h" #include "sandbox/sandboxfactory.h" +#include "cli.h" +#include "cliconsole.h" +#include "cliserver.h" + void sigterm_handler(int arg) { // TODO: proper shutdown. @@ -56,6 +61,10 @@ void setup_signal_handlers() } } +#define OPT_PRINT_VERSION 23 + +static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version", no_argument, 0, OPT_PRINT_VERSION}}; + std::unique_ptr createCache(const ConfigVariableResolver &resolver) { @@ -63,13 +72,48 @@ std::unique_ptr createCache(const ConfigVariableResolver &resolver) return std::make_unique(path); } + +std::string get_version_string() +{ + return "master"; +} + int main(int argc, char **argv) { + + char *configfilepath = NULL; + int option; + int option_index; + bool cli_mode = false; + if(geteuid() == 0) { std::cerr << "Do not run this as root!" << std::endl; return 1; } + + while((option = getopt_long(argc, argv, "cv", long_options, &option_index)) != -1) + { + switch(option) + { + case 'c': + cli_mode = true; + break; + case OPT_PRINT_VERSION: + std::cout << get_version_string() << std::endl; + exit(EXIT_SUCCESS); + break; + } + } + + if(optind == argc) + { + std::cerr << "Missing config path" << std::endl; + return 1; + } + + configfilepath = argv[optind++]; + auto sandbox = createSandbox(); // TODO: do we want to keep it mandatory or configurable? if(!sandbox->supported()) @@ -82,7 +126,7 @@ int main(int argc, char **argv) std::cerr << "no path to config file provided" << std::endl; return 1; } - std::string configpath = std::filesystem::absolute(argv[1]).string(); + std::string configpath = std::filesystem::absolute(configfilepath).string(); if(!sandbox->enableForInit()) { Logger::error() << "Sandboxing for init mode could not be activated."; @@ -113,6 +157,21 @@ int main(int argc, char **argv) Logger::setStream(&logstream); auto database = createDatabase(config); + std::string socketPath = config.configVarResolver.getConfig("socketpath"); + CLIHandler cliHandler(config, *database); + + if(cli_mode) + { + CLIConsole console{cliHandler, socketPath}; + console.startInteractive(); + return 0; + } + CLIServer cliServer{cliHandler}; + if(!cliServer.detachServer(socketPath)) + { + Logger::error() << "Error: Failed to detach unix socket server"; + return 1; + } // TODO: quite ugly, anon-handling must be rethought auto userdao = database->createUserDao(); -- 2.45.2