From cfee2bf1d86cc952bbe724fd2c319fe463923dcd Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 25 Sep 2021 19:28:37 +0200 Subject: [PATCH] Begin CLI Parse args using getopt_long() in main(). Begin implementation of a CLI. --- authenticator.cpp | 5 +- authenticator.h | 1 + cli.cpp | 184 +++++++++++++++++++++++++++++++ cli.h | 63 +++++++++++ handlers/handlerusersettings.cpp | 7 +- qswiki.cpp | 50 ++++++++- 6 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 cli.cpp create mode 100644 cli.h 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/cli.cpp b/cli.cpp new file mode 100644 index 0000000..5b8f4a9 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,184 @@ +#include +#include +#include "cli.h" +#include "utils.h" +#include "random.h" +#include "authenticator.h" +#include "config.h" + +CLI::CLI(Config &config, Database &db) +{ + this->db = &db; + this->conf = &config; +} + +bool CLI::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()) + { + std::cout << "Error during hashing - Got empty hash"; + return false; + } + user.password = hashResult; + + try + { + userDao->save(user); + } + catch(std::runtime_error &e) + { + std::cout << "Exception: " << e.what() << std::endl; + return false; + } + return true; +} + +bool CLI::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->sault); + if(user->password.empty()) + { + std::cout << "Error during hashing - Got empty hash"; + return false; + } + + userDao->save(*user); + } + else + { + std::cout << "User not found" << std::endl; + return false; + } +} + +bool CLI::user_rename(const std::vector &args) +{ +} + +bool CLI::user_set_perms(const std::vector &args) +{ +} + +bool CLI::user_list(const std::vector &args) +{ +} + +bool CLI::user_show(const std::vector &args) +{ +} + +bool CLI::cli_help(const std::vector &args) +{ + std::string command; + if(args.size() > 0) + command = args[0]; + for(struct cmd &cmd : cmds) + { + if(command != "" && cmd.name != command) + { + continue; + } + + std::cout << cmd.name << " - " << cmd.helptext << std::endl; + for(struct cmd &subCmd : cmd.subCommands) + { + std::cout << "\t" << subCmd.name << " " << subCmd.helptext << std::endl; + } + std::cout << std::endl; + } + return true; +} + +bool CLI::processCommand(const std::vector &commands, std::string cmd, const std::vector &args) +{ + auto c = std::find_if(commands.begin(), commands.end(), [&cmd](const struct CLI::cmd &a) { return a.name == cmd; }); + if(c == commands.end()) + { + std::cout << "No such command" << 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()); + processCommand(c->subCommands, newcmd, newargs); + } + if(args.size() < c->required_args) + { + std::cout << "not enough parameters passed" << std::endl; + return false; + } + + try + { + return c->func(this, args); + } + catch(std::runtime_error &e) + { + std::cout << "Exception: " << e.what() << std::endl; + } + return false; +} + +bool CLI::processCommand(std::string cmd, const std::vector &args) +{ + return processCommand(this->cmds, cmd, args); +} + +void CLI::startInteractive() +{ + std::cout << "qswiki cli" << std::endl; + + while(true) + { + std::string input; + std::cout << "> "; + std::getline(std::cin, input); + + std::vector splitted = utils::split(input, "\\s+"); + if(splitted.empty()) + { + continue; + } + std::string cmd = splitted[0]; + splitted.erase(splitted.begin()); + if(!processCommand(cmd, splitted)) + { + std::cout << "Command failed" << std::endl; + } + + std::cout << "\n"; + } +} diff --git a/cli.h b/cli.h new file mode 100644 index 0000000..f9f6ded --- /dev/null +++ b/cli.h @@ -0,0 +1,63 @@ +#ifndef CLI_H +#define CLI_H +#include +#include +#include +#include "database/database.h" +#include "config.h" + +class CLI +{ + struct cmd + { + std::string name; + std::string helptext; + unsigned int required_args; + std::vector subCommands; + std::function &)> func; + }; + + private: + Database *db; + Config *conf; + + protected: + bool cli_help(const std::vector &args); + bool user_add(const std::vector &args); + bool user_change_pw(const std::vector &args); + bool user_rename(const std::vector &args); + bool user_set_perms(const std::vector &args); + bool user_list(const std::vector &args); + bool user_show(const std::vector &args); + + std::vector cmds{ + {{"user", + "user operations on the database", + 1, + {{{"add", "[user] [password] - creates a user", 2, {}, &CLI::user_add}, + {"changepw", "[user] [password] - changes the password of user", 2, {}, &CLI::user_change_pw}, + {"rename", "[user] [new name] - renames a user", 2, {}, &CLI::user_rename}, + {"setperms", "[user] [perms] - sets the permissions of the user", 2, {}, &CLI::user_set_perms}, + {"list", "- lists users", 2, {}, &CLI::user_list}, + {"show", "[user] - show detailed information about user", 2, {}, &CLI::user_show}}}, + &CLI::cli_help}, + {"exit", + "exit cli", + 0, + {}, + [](CLI *, const std::vector &args) -> bool + { + exit(EXIT_SUCCESS); + return true; + }}, + {"help", "print this help", 0, {}, &CLI::cli_help}}}; + + bool processCommand(const std::vector &commands, std::string cmd, const std::vector &args); + + public: + CLI(Config &config, Database &d); + void startInteractive(); + bool processCommand(std::string cmd, const std::vector &args); +}; + +#endif // CLI_H 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); diff --git a/qswiki.cpp b/qswiki.cpp index 1f8f53f..b748cea 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,7 @@ SOFTWARE. #include "requestworker.h" #include "cache/fscache.h" #include "sandbox/sandboxfactory.h" +#include "cli.h" void sigterm_handler(int arg) { // TODO: proper shutdown. @@ -56,6 +58,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 +69,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 +123,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."; @@ -114,6 +155,13 @@ int main(int argc, char **argv) auto database = createDatabase(config); + if(cli_mode) + { + CLI cli(config, *database); + cli.startInteractive(); + return 0; + } + // TODO: quite ugly, anon-handling must be rethought auto userdao = database->createUserDao(); std::optional anon = userdao->find(config.handlersConfig.anon_username);