/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Poco::Util::Application; using Poco::Util::HelpFormatter; using Poco::Util::Option; using Poco::Util::OptionSet; using Poco::Util::XMLConfiguration; #define MIN_PWD_SALT_LENGTH 20 #define MIN_PWD_ITERATIONS 1000 #define MIN_PWD_HASH_LENGTH 20 class LoolConfig final: public XMLConfiguration { public: LoolConfig() {} }; struct AdminConfig { unsigned pwdSaltLength = 128; unsigned pwdIterations = 10000; unsigned pwdHashLength = 128; }; // Config tool to change loolwsd configuration (loolwsd.xml) class Config: public Application { // Display help information on the console void displayHelp(); LoolConfig _loolConfig; AdminConfig _adminConfig; public: static std::string ConfigFile; static std::string SupportKeyString; static bool SupportKeyStringProvided; protected: void defineOptions(OptionSet&) override; void handleOption(const std::string&, const std::string&) override; int main(const std::vector&) override; }; std::string Config::ConfigFile = #if ENABLE_DEBUG DEBUG_ABSSRCDIR #else LOOLWSD_CONFIGDIR #endif "/loolwsd.xml"; std::string Config::SupportKeyString; bool Config::SupportKeyStringProvided = false; void Config::displayHelp() { HelpFormatter helpFormatter(options()); helpFormatter.setCommand(commandName()); helpFormatter.setUsage("COMMAND [OPTIONS]"); helpFormatter.setHeader("loolconfig - Configuration tool for LibreOffice Online.\n" "\n" "Some options make sense only with a specific command.\n\n" "Options:"); helpFormatter.format(std::cout); // Command list std::cout << std::endl << "Commands: " << std::endl << " set-admin-password" << std::endl #if ENABLE_SUPPORT_KEY << " set-support-key" << std::endl #endif << " set " << std::endl << " update-system-template" << std::endl << std::endl; } void Config::defineOptions(OptionSet& optionSet) { Application::defineOptions(optionSet); optionSet.addOption(Option("help", "h", "Show this usage information.") .required(false) .repeatable(false)); optionSet.addOption(Option("config-file", "", "Specify configuration file path manually.") .required(false) .repeatable(false) .argument("path")); optionSet.addOption(Option("pwd-salt-length", "", "Length of the salt to use to hash password [set-admin-password].") .required(false) .repeatable(false). argument("number")); optionSet.addOption(Option("pwd-iterations", "", "Number of iterations to do in PKDBF2 password hashing [set-admin-password].") .required(false) .repeatable(false) .argument("number")); optionSet.addOption(Option("pwd-hash-length", "", "Length of password hash to generate [set-admin-password].") .required(false) .repeatable(false) .argument("number")); #if ENABLE_SUPPORT_KEY optionSet.addOption(Option("support-key", "", "Specify the support key [set-support-key].") .required(false) .repeatable(false) .argument("key")); #endif } void Config::handleOption(const std::string& optionName, const std::string& optionValue) { Application::handleOption(optionName, optionValue); if (optionName == "help") { displayHelp(); std::exit(Application::EXIT_OK); } else if (optionName == "config-file") { ConfigFile = optionValue; } else if (optionName == "pwd-salt-length") { unsigned len = std::stoi(optionValue); if (len < MIN_PWD_SALT_LENGTH) { len = MIN_PWD_SALT_LENGTH; std::cout << "Password salt length adjusted to minimum " << len << std::endl; } _adminConfig.pwdSaltLength = len; } else if (optionName == "pwd-iterations") { unsigned len = std::stoi(optionValue); if (len < MIN_PWD_ITERATIONS) { len = MIN_PWD_ITERATIONS; std::cout << "Password iteration adjusted to minimum " << len << std::endl; } _adminConfig.pwdIterations = len; } else if (optionName == "pwd-hash-length") { unsigned len = std::stoi(optionValue); if (len < MIN_PWD_HASH_LENGTH) { len = MIN_PWD_HASH_LENGTH; std::cout << "Password hash length adjusted to minimum " << len << std::endl; } _adminConfig.pwdHashLength = len; } else if (optionName == "support-key") { SupportKeyString = optionValue; SupportKeyStringProvided = true; } } int Config::main(const std::vector& args) { if (args.empty()) { std::cerr << "Nothing to do." << std::endl; displayHelp(); return Application::EXIT_NOINPUT; } bool changed = false; _loolConfig.load(ConfigFile); if (args[0] == "set-admin-password") { #if HAVE_PKCS5_PBKDF2_HMAC unsigned char pwdhash[_adminConfig.pwdHashLength]; unsigned char salt[_adminConfig.pwdSaltLength]; RAND_bytes(salt, _adminConfig.pwdSaltLength); std::stringstream stream; // Ask for user password termios oldTermios; tcgetattr(STDIN_FILENO, &oldTermios); termios newTermios = oldTermios; // Disable user input mirroring on console for password input newTermios.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &newTermios); std::string adminPwd; std::cout << "Enter admin password: "; std::getline(std::cin, adminPwd); std::string reAdminPwd; std::cout << std::endl << "Confirm admin password: "; std::getline(std::cin, reAdminPwd); std::cout << std::endl; // Set the termios to old state tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios); if (adminPwd != reAdminPwd) { std::cout << "Password mismatch." << std::endl; return Application::EXIT_DATAERR; } // Do the magic ! PKCS5_PBKDF2_HMAC(adminPwd.c_str(), -1, salt, _adminConfig.pwdSaltLength, _adminConfig.pwdIterations, EVP_sha512(), _adminConfig.pwdHashLength, pwdhash); // Make salt randomness readable for (unsigned j = 0; j < _adminConfig.pwdSaltLength; ++j) stream << std::hex << std::setw(2) << std::setfill('0') << static_cast(salt[j]); const std::string saltHash = stream.str(); // Clear our used hex stream to make space for password hash stream.str(""); stream.clear(); // Make the hashed password readable for (unsigned j = 0; j < _adminConfig.pwdHashLength; ++j) stream << std::hex << std::setw(2) << std::setfill('0') << static_cast(pwdhash[j]); const std::string passwordHash = stream.str(); std::stringstream pwdConfigValue("pbkdf2.sha512.", std::ios_base::in | std::ios_base::out | std::ios_base::ate); pwdConfigValue << std::to_string(_adminConfig.pwdIterations) << "."; pwdConfigValue << saltHash << "." << passwordHash; _loolConfig.setString("admin_console.secure_password[@desc]", "Salt and password hash combination generated using PBKDF2 with SHA512 digest."); _loolConfig.setString("admin_console.secure_password", pwdConfigValue.str()); changed = true; #else std::cerr << "This application was compiled with old OpenSSL. Operation not supported. You can use plain text password in /etc/loolwsd/loolwsd.xml." << std::endl; return Application::EXIT_UNAVAILABLE; #endif } #if ENABLE_SUPPORT_KEY else if (args[0] == "set-support-key") { std::string supportKeyString; if (SupportKeyStringProvided) supportKeyString = SupportKeyString; else { std::cout << "Enter support key: "; std::getline(std::cin, supportKeyString); } if (!supportKeyString.empty()) { SupportKey key(supportKeyString); if (!key.verify()) std::cerr << "Invalid key\n"; else { int validDays = key.validDaysRemaining(); if (validDays <= 0) std::cerr << "Valid but expired key\n"; else { std::cerr << "Valid for " << validDays << " days - setting to config\n"; _loolConfig.setString("support_key", supportKeyString); } } } else { std::cerr << "Removing empty support key\n"; _loolConfig.remove("support_key"); } changed = true; } #endif else if (args[0] == "set") { if (args.size() == 3) { // args[1] = key // args[2] = value if (_loolConfig.has(args[1])) { const std::string val = _loolConfig.getString(args[1]); std::cout << "Previous value found in config file: \"" << val << "\"" << std::endl; std::cout << "Changing value to: \"" << args[2] << "\"" << std::endl; _loolConfig.setString(args[1], args[2]); changed = true; } else std::cerr << "No property, \"" << args[1] << "\"," << " found in config file." << std::endl; } else std::cerr << "set expects a key and value as arguments" << std::endl << "Eg: " << std::endl << " set logging.level trace" << std::endl; } else if (args[0] == "update-system-template") { const char command[] = "su lool --shell=/bin/sh -c 'loolwsd-systemplate-setup /opt/lool/systemplate " LO_PATH " >/dev/null 2>&1'"; std::cout << "Running the following command:" << std::endl << command << std::endl; system(command); } else { std::cerr << "No such command, \"" << args[0] << "\"" << std::endl; displayHelp(); } if (changed) { std::cout << "Saving configuration to : " << ConfigFile << " ..." << std::endl; _loolConfig.save(ConfigFile); std::cout << "Saved" << std::endl; } // This tool only handles options, nothing to do here return Application::EXIT_OK; } POCO_APP_MAIN(Config); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */