CLI #25
| @@ -42,11 +42,12 @@ std::vector<char> Authenticator::pbkdf5(std::string password, const std::vector< | ||||
| 	unsigned char hash[32]; | ||||
| 	const EVP_MD *sha256 = EVP_sha256(); | ||||
| 	const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(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<char> result; | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include <variant> | ||||
| #include "database/userdao.h" | ||||
|  | ||||
| #define AUTH_DEFAULT_SALT_SIZE 32 | ||||
| enum AuthenticationError | ||||
| { | ||||
| 	UserNotFound, | ||||
|   | ||||
							
								
								
									
										220
									
								
								cli.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								cli.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | ||||
| #include <map> | ||||
| #include <functional> | ||||
| #include <iomanip> | ||||
|  | ||||
| #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<bool, std::string> CLIHandler::user_add(const std::vector<std::string> &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<char> 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<bool, std::string> CLIHandler::user_change_pw(const std::vector<std::string> &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<bool, std::string> CLIHandler::user_rename(const std::vector<std::string> &args) | ||||
| { | ||||
|  | ||||
| 	return {true, ""}; | ||||
| } | ||||
|  | ||||
| std::pair<bool, std::string> CLIHandler::user_set_perms(const std::vector<std::string> &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<bool, std::string> CLIHandler::user_list(const std::vector<std::string> &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<bool, std::string> CLIHandler::user_show(const std::vector<std::string> &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<bool, std::string> CLIHandler::attach(const std::vector<std::string> &args) | ||||
| { | ||||
| 	/* TODO: consider authentication */ | ||||
| 	pid_t pid = getpid(); | ||||
| 	return {true, "Hi, I am pid: " + std::to_string(pid)}; | ||||
| } | ||||
|  | ||||
| std::pair<bool, std::string> CLIHandler::cli_help(const std::vector<std::string> &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<bool, std::string> CLIHandler::processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd, | ||||
| 														const std::vector<std::string> &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<std::string> 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<bool, std::string> CLIHandler::processCommand(std::string cmd, const std::vector<std::string> &args) | ||||
| { | ||||
| 	return processCommand(this->cmds, cmd, args); | ||||
| } | ||||
|  | ||||
| std::pair<std::string, std::vector<std::string>> CLIHandler::splitCommand(std::string input) | ||||
| { | ||||
| 	input = utils::trim(input); | ||||
| 	std::vector<std::string> splitted = utils::split(input, "\\s+"); | ||||
| 	if(splitted.empty()) | ||||
| 	{ | ||||
| 		return {" ", splitted}; | ||||
| 	} | ||||
| 	std::string cmd = splitted[0]; | ||||
| 	splitted.erase(splitted.begin()); | ||||
| 	return {cmd, splitted}; | ||||
| } | ||||
							
								
								
									
										66
									
								
								cli.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								cli.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #ifndef CLI_H | ||||
| #define CLI_H | ||||
| #include <iostream> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "database/database.h" | ||||
| #include "config.h" | ||||
|  | ||||
| class CLIHandler | ||||
| { | ||||
| 	struct cmd | ||||
| 	{ | ||||
| 		std::string name; | ||||
| 		std::string helptext; | ||||
| 		unsigned int required_args; | ||||
| 		std::vector<cmd> subCommands; | ||||
| 		std::function<std::pair<bool, std::string>(CLIHandler *, const std::vector<std::string> &)> func; | ||||
| 	}; | ||||
|  | ||||
|   private: | ||||
| 	Database *db; | ||||
| 	Config *conf; | ||||
|  | ||||
|   protected: | ||||
| 	std::pair<bool, std::string> attach(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> cli_help(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_add(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_change_pw(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_rename(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_set_perms(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_list(const std::vector<std::string> &args); | ||||
| 	std::pair<bool, std::string> user_show(const std::vector<std::string> &args); | ||||
|  | ||||
| 	std::vector<struct cmd> 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<std::string> &args) -> std::pair<bool, std::string> | ||||
| 		  { | ||||
| 			  exit(EXIT_SUCCESS); | ||||
| 			  return {true, ""}; | ||||
| 		  }}, | ||||
| 		 {"help", "print this help", 0, {}, &CLIHandler::cli_help}, | ||||
| 		 {"attach", "attach to running instance", 0, {}, &CLIHandler::attach}}}; | ||||
|  | ||||
| 	std::pair<bool, std::string> processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd, | ||||
| 												const std::vector<std::string> &args); | ||||
|  | ||||
|   public: | ||||
| 	CLIHandler(Config &config, Database &d); | ||||
| 	std::pair<bool, std::string> processCommand(std::string cmd, const std::vector<std::string> &args); | ||||
| 	static std::pair<std::string, std::vector<std::string>> splitCommand(std::string input); | ||||
| }; | ||||
|  | ||||
| #endif // CLI_H | ||||
							
								
								
									
										137
									
								
								cliconsole.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								cliconsole.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| #include "cliconsole.h" | ||||
|  | ||||
| CLIConsole::CLIConsole(CLIHandler &cliHandler, std::string socketPath) | ||||
| { | ||||
| 	this->handler = &cliHandler; | ||||
| 	this->socketPath = socketPath; | ||||
| } | ||||
|  | ||||
| std::pair<bool, std::string> 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<bool, std::string> 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"; | ||||
| } | ||||
							
								
								
									
										27
									
								
								cliconsole.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								cliconsole.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| #ifndef CLICONSOLE_H | ||||
| #define CLICONSOLE_H | ||||
|  | ||||
| #include <iostream> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/un.h> | ||||
| #include "cli.h" | ||||
|  | ||||
| class CLIConsole | ||||
| { | ||||
|   private: | ||||
| 	struct sockaddr_un server; | ||||
| 	int sock; | ||||
| 	CLIHandler *handler; | ||||
| 	std::string socketPath; | ||||
| 	bool attached = false; | ||||
| 	std::pair<bool, std::string> send(std::string input); | ||||
| 	void attach(); | ||||
|  | ||||
|   public: | ||||
| 	CLIConsole(CLIHandler &cliHandler, std::string socketPath); | ||||
| 	void startInteractive(); | ||||
| }; | ||||
|  | ||||
| #endif // CLICONSOLE_H | ||||
							
								
								
									
										77
									
								
								cliserver.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								cliserver.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #include <sys/socket.h> | ||||
| #include <sys/un.h> | ||||
| #include <unistd.h> | ||||
| #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; | ||||
| } | ||||
							
								
								
									
										16
									
								
								cliserver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								cliserver.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef CLISERVER_H | ||||
| #define CLISERVER_H | ||||
| #include <thread> | ||||
| #include "cli.h" | ||||
|  | ||||
| class CLIServer | ||||
| { | ||||
|   private: | ||||
| 	CLIHandler *handler = nullptr; | ||||
|  | ||||
|   public: | ||||
| 	CLIServer(CLIHandler &handler); | ||||
| 	bool detachServer(std::string socketpath); | ||||
| }; | ||||
|  | ||||
| #endif // CLISERVER_H | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include <string> | ||||
| #include <optional> | ||||
| #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<User> find(std::string username) = 0; | ||||
| 	virtual std::optional<User> find(int id) = 0; | ||||
| 	virtual std::vector<User> list(QueryOption o) = 0; | ||||
| 	virtual void deleteUser(std::string username) = 0; | ||||
| 	virtual void save(const User &u) = 0; | ||||
| 	virtual ~UserDao(){}; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ SOFTWARE. | ||||
| #include <memory> | ||||
| #include <cstring> | ||||
| #include "userdaosqlite.h" | ||||
| #include "sqlitequeryoption.h" | ||||
|  | ||||
| UserDaoSqlite::UserDaoSqlite() | ||||
| { | ||||
| @@ -82,6 +83,39 @@ std::optional<User> UserDaoSqlite::find(int id) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::vector<User> UserDaoSqlite::list(QueryOption o) | ||||
| { | ||||
| 	std::vector<User> 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<char> pw, std::vector<char> 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? | ||||
|   | ||||
| @@ -13,6 +13,7 @@ class UserDaoSqlite : public UserDao, protected SqliteDao | ||||
| 	std::optional<User> find(std::string username); | ||||
| 	std::optional<User> find(int id); | ||||
|  | ||||
| 	std::vector<User> list(QueryOption o); | ||||
| 	void deleteUser(std::string username); | ||||
| 	void save(const User &u); | ||||
| 	using SqliteDao::SqliteDao; | ||||
|   | ||||
| @@ -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<User, AuthenticationError> authresult = authenticator.authenticate(this->userSession->user.login, oldpassword); | ||||
| 			std::variant<User, AuthenticationError> authresult = | ||||
| 				authenticator.authenticate(this->userSession->user.login, oldpassword); | ||||
| 			if(std::holds_alternative<AuthenticationError>(authresult)) | ||||
| 			{ | ||||
| 				return this->errorResponse("Invalid current password", "The old password you entered is invalid"); | ||||
| 			} | ||||
| 			Random r; | ||||
| 			std::vector<char> salt = r.getRandom(23); | ||||
| 			std::vector<char> salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE); | ||||
| 			User user = std::get<User>(authresult); | ||||
| 			user.salt = salt; | ||||
| 			user.password = authenticator.hash(newpassword, user.salt); | ||||
|   | ||||
| @@ -20,6 +20,17 @@ SOFTWARE. | ||||
| */ | ||||
| #include "permissions.h" | ||||
|  | ||||
| static const std::map<std::string, int> 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; | ||||
| } | ||||
|   | ||||
| @@ -14,20 +14,12 @@ | ||||
|  | ||||
| #include <string> | ||||
| #include <map> | ||||
|  | ||||
| class Permissions | ||||
|  | ||||
| { | ||||
|   private: | ||||
| 	int permissions = 0; | ||||
| 	const std::map<std::string, int> 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 | ||||
|   | ||||
							
								
								
									
										61
									
								
								qswiki.cpp
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								qswiki.cpp
									
									
									
									
									
								
							| @@ -25,6 +25,7 @@ SOFTWARE. | ||||
| #include <unistd.h> | ||||
| #include <sys/types.h> | ||||
| #include <filesystem> | ||||
| #include <getopt.h> | ||||
| #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<ICache> createCache(const ConfigVariableResolver &resolver) | ||||
| { | ||||
|  | ||||
| @@ -63,13 +72,48 @@ std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver) | ||||
|  | ||||
| 	return std::make_unique<FsCache>(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(); | ||||
|   | ||||
							
								
								
									
										17
									
								
								utils.cpp
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								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}; | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								utils.h
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								utils.h
									
									
									
									
									
								
							| @@ -93,5 +93,7 @@ template <class T> inline std::string toString(const T &v) | ||||
| 	return std::string(v.begin(), v.end()); | ||||
| } | ||||
|  | ||||
| std::string trim(const std::string &str); | ||||
|  | ||||
| } // namespace utils | ||||
| #endif | ||||
|   | ||||
		مرجع در شماره جدید
	
	Block a user