From 32393932e902f8b2a5b43aa0067f688d2ad18b72 Mon Sep 17 00:00:00 2001 From: Vladislav Nepogodin Date: Sat, 29 Jun 2024 03:38:21 +0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20move=20user=20creation=20into=20?= =?UTF-8?q?gucc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gucc/CMakeLists.txt | 1 + gucc/include/gucc/io_utils.hpp | 1 + gucc/include/gucc/user.hpp | 25 +++++++ gucc/meson.build | 1 + gucc/src/io_utils.cpp | 14 +++- gucc/src/user.cpp | 129 +++++++++++++++++++++++++++++++++ src/utils.cpp | 37 ++++------ 7 files changed, 184 insertions(+), 24 deletions(-) create mode 100644 gucc/include/gucc/user.hpp create mode 100644 gucc/src/user.cpp diff --git a/gucc/CMakeLists.txt b/gucc/CMakeLists.txt index 943e1e1..6f75ff9 100644 --- a/gucc/CMakeLists.txt +++ b/gucc/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(${PROJECT_NAME} SHARED src/luks.cpp include/gucc/luks.hpp src/zfs.cpp include/gucc/zfs.hpp src/btrfs.cpp include/gucc/btrfs.hpp + src/user.cpp include/gucc/user.hpp #src/chwd_profiles.cpp src/chwd_profiles.hpp #src/disk.cpp src/disk.hpp ) diff --git a/gucc/include/gucc/io_utils.hpp b/gucc/include/gucc/io_utils.hpp index 35a6348..fc59891 100644 --- a/gucc/include/gucc/io_utils.hpp +++ b/gucc/include/gucc/io_utils.hpp @@ -11,6 +11,7 @@ auto safe_getenv(const char* env_name) noexcept -> std::string_view; void exec(const std::vector& vec) noexcept; auto exec(std::string_view command, bool interactive = false) noexcept -> std::string; void arch_chroot(std::string_view command, std::string_view mountpoint, bool interactive = false) noexcept; +auto arch_chroot_checked(std::string_view command, std::string_view mountpoint) noexcept -> bool; } // namespace gucc::utils diff --git a/gucc/include/gucc/user.hpp b/gucc/include/gucc/user.hpp new file mode 100644 index 0000000..b2c08a8 --- /dev/null +++ b/gucc/include/gucc/user.hpp @@ -0,0 +1,25 @@ +#ifndef USER_HPP +#define USER_HPP + +#include // for string +#include // for string_view +#include // for vector + +namespace gucc::user { + +struct UserInfo final { + std::string_view username; + std::string_view password; + std::string_view shell; + std::string_view sudoers_group; +}; + +// Create group on the system +auto create_group(std::string_view group, std::string_view mountpoint) noexcept -> bool; + +// Create user on the system +auto create_new_user(const user::UserInfo& user_info, const std::vector& default_groups, std::string_view mountpoint) noexcept -> bool; + +} // namespace gucc::user + +#endif // USER_HPP diff --git a/gucc/meson.build b/gucc/meson.build index 5e16044..bd64dbb 100644 --- a/gucc/meson.build +++ b/gucc/meson.build @@ -9,6 +9,7 @@ gucc_lib = library('gucc', 'src/luks.cpp', 'src/zfs.cpp', 'src/btrfs.cpp', + 'src/user.cpp', ], include_directories : [include_directories('include')], dependencies: deps diff --git a/gucc/src/io_utils.cpp b/gucc/src/io_utils.cpp index f4f50b8..2e5c2d2 100644 --- a/gucc/src/io_utils.cpp +++ b/gucc/src/io_utils.cpp @@ -94,10 +94,22 @@ void arch_chroot(std::string_view command, std::string_view mountpoint, bool int const auto& cmd_formatted = fmt::format(FMT_COMPILE("arch-chroot {} {} 2>>/tmp/cachyos-install.log 2>&1"), mountpoint, command); #ifdef NDEVENV - gucc::utils::exec(cmd_formatted, interactive); + utils::exec(cmd_formatted, interactive); #else spdlog::info("Running with arch-chroot(interactive='{}'): '{}'", interactive, cmd_formatted); #endif } +auto arch_chroot_checked(std::string_view command, std::string_view mountpoint) noexcept -> bool { + // TODO(vnepogodin): refactor to move output into variable and print into log + const auto& cmd_formatted = fmt::format(FMT_COMPILE("arch-chroot {} {} 2>>/tmp/cachyos-install.log 1>/dev/null"), mountpoint, command); + +#ifdef NDEVENV + return utils::exec(cmd_formatted, true) == "0"; +#else + spdlog::info("Running with checked arch-chroot: '{}'", cmd_formatted); + return true; +#endif +} + } // namespace gucc::utils diff --git a/gucc/src/user.cpp b/gucc/src/user.cpp new file mode 100644 index 0000000..adce040 --- /dev/null +++ b/gucc/src/user.cpp @@ -0,0 +1,129 @@ +#include "gucc/user.hpp" +#include "gucc/io_utils.hpp" +#include "gucc/string_utils.hpp" + +#include // for find +#include +#include // for ofstream + +#include +#include + +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +namespace fs = std::filesystem; + +using namespace std::string_view_literals; + +namespace gucc::user { + +auto create_group(std::string_view group, std::string_view mountpoint) noexcept -> bool { + // TODO(vnepogodin): + // 1. add parameter to check if the group was already created + // 2. add parameter if the group should be --system group + const auto& cmd = fmt::format(FMT_COMPILE("groupadd {}"), group); + return utils::arch_chroot_checked(cmd, mountpoint); +} + +auto create_new_user(const user::UserInfo& user_info, const std::vector& default_groups, std::string_view mountpoint) noexcept -> bool { + if (!user_info.sudoers_group.empty() && !ranges::contains(default_groups, user_info.sudoers_group)) { + spdlog::error("Failed to create user {}! User default groups doesn't contain sudoers group({})", user_info.username, user_info.sudoers_group); + return false; + } + + // Create needed groups + for (const auto& default_group : default_groups) { + if (!user::create_group(default_group, mountpoint)) { + spdlog::error("Failed to create group {}", default_group); + return false; + } + } + + // Create the user + spdlog::info("Creating user {}", user_info.username); + const auto& usercmd = [](auto&& username, auto&& user_shell) -> std::string { + using namespace std::string_view_literals; + static constexpr auto USER_BASE_CMD = "useradd -m -U"sv; + if (!user_shell.empty()) { + return fmt::format(FMT_COMPILE("{} -s {} {}"), USER_BASE_CMD, user_shell, username); + } + return fmt::format(FMT_COMPILE("{} {}"), USER_BASE_CMD, username); + }(user_info.username, user_info.shell); + + if (!utils::arch_chroot_checked(usercmd, mountpoint)) { + spdlog::error("Failed to create user with {}", usercmd); + return false; + } + + // Set user groups + spdlog::info("Setting groups for user {}", user_info.username); + const auto& groups_set_cmd = fmt::format(FMT_COMPILE("usermod -aG {} {}"), utils::join(default_groups, ","), user_info.username); + if (!utils::arch_chroot_checked(groups_set_cmd, mountpoint)) { + spdlog::error("Failed to set user groups with {}", groups_set_cmd); + return false; + } + + // Setup user permissions + spdlog::info("Setting user permissions for {}", user_info.username); + const auto& user_group = fmt::format(FMT_COMPILE("{0}:{0}"), user_info.username); + const auto& user_homedir = fmt::format(FMT_COMPILE("/home/{}"), user_info.username); + const auto& setup_cmd = fmt::format(FMT_COMPILE("chown -R {} {}"), user_group, user_homedir); + if (!utils::arch_chroot_checked(setup_cmd, mountpoint)) { + spdlog::error("Failed to setup user permissions on {} as {}", user_homedir, user_group); + return false; + } + + // TODO(vnepogodin): should encrypt user password properly here + const auto& encrypted_passwd = utils::exec(fmt::format(FMT_COMPILE("openssl passwd {}"), user_info.password)); + const auto& password_set_cmd = fmt::format(FMT_COMPILE("usermod -p '{}' {}"), encrypted_passwd, user_info.username); + if (!utils::arch_chroot_checked(password_set_cmd, mountpoint)) { + spdlog::error("Failed to set password for user {}", user_info.username); + return false; + } + + // Setup sudoers + if (user_info.sudoers_group.empty()) { + spdlog::info("skipping sudoers group is empty"); + return true; + } + + const auto& sudoers_filepath = fmt::format(FMT_COMPILE("{}/etc/sudoers.d/10-installer"), mountpoint); + { + const auto& sudoers_line = fmt::format(FMT_COMPILE("%{} ALL=(ALL) ALL\n"), user_info.sudoers_group); + std::ofstream sudoers_file{sudoers_filepath, std::ios::out | std::ios::trunc}; + if (!sudoers_file.is_open()) { + spdlog::error("Failed to open sudoers for writing {}", sudoers_filepath); + return false; + } + sudoers_file << sudoers_line; + } + + std::error_code err{}; + fs::permissions(sudoers_filepath, + fs::perms::owner_read | fs::perms::group_read, // 0440 + fs::perm_options::replace, err); + if (err) { + spdlog::error("Failed to set permissions for sudoers file: {}", err.message()); + return false; + } + return true; +} + +} // namespace gucc::user diff --git a/src/utils.cpp b/src/utils.cpp index ff7fcbe..28f6e3c 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -13,6 +13,7 @@ #include "gucc/luks.hpp" #include "gucc/pacmanconf_repo.hpp" #include "gucc/string_utils.hpp" +#include "gucc/user.hpp" #include // for transform #include // for array @@ -470,29 +471,18 @@ void create_new_user(const std::string_view& user, const std::string_view& passw } } - // Create the user, set password, then remove temporary password file - utils::arch_chroot("groupadd sudo", false); - utils::arch_chroot(fmt::format(FMT_COMPILE("groupadd {}"), user), false); - utils::arch_chroot(fmt::format(FMT_COMPILE("useradd {0} -m -g {0} -G sudo,storage,power,network,video,audio,lp,sys,input -s {1}"), user, shell), false); - spdlog::info("add user to groups"); - - // check if user has been created - const auto& user_check = gucc::utils::exec(fmt::format(FMT_COMPILE("arch-chroot {} getent passwd {}"), mountpoint, user)); - if (user_check.empty()) { - spdlog::error("User has not been created!"); - } - std::error_code err{}; - gucc::utils::exec(fmt::format(FMT_COMPILE("echo -e \"{0}\\n{0}\" > /tmp/.passwd"), password)); - const auto& ret_status = gucc::utils::exec(fmt::format(FMT_COMPILE("arch-chroot {} passwd {} < /tmp/.passwd &>/dev/null"), mountpoint, user), true); - spdlog::info("create user pwd: {}", ret_status); - fs::remove("/tmp/.passwd", err); - - // Set up basic configuration files and permissions for user - // arch_chroot "cp /etc/skel/.bashrc /home/${USER}" - utils::arch_chroot(fmt::format(FMT_COMPILE("chown -R {0}:{0} /home/{0}"), user), false); - const auto& sudoers_file = fmt::format(FMT_COMPILE("{}/etc/sudoers"), mountpoint); - if (fs::exists(sudoers_file)) { - gucc::utils::exec(fmt::format(FMT_COMPILE("sed -i '/NOPASSWD/!s/# %sudo/%sudo/g' {}"), sudoers_file)); + // Create user with sudoers and default groups + using namespace std::string_literals; + using namespace std::string_view_literals; + const gucc::user::UserInfo user_info{ + .username = user, + .password = password, + .shell = shell, + .sudoers_group = "sudo"sv, + }; + const std::vector default_user_groups{"sudo"s, "storage"s, "power"s, "network"s, "video"s, "audio"s, "lp"s, "sys"s, "input"s}; + if (!gucc::user::create_new_user(user_info, default_user_groups, mountpoint)) { + spdlog::error("Failed to create user"); } #else spdlog::debug("user := {}, password := {}", user, password); @@ -1768,6 +1758,7 @@ void enable_autologin([[maybe_unused]] const std::string_view& dm, [[maybe_unuse gucc::utils::exec(fmt::format(FMT_COMPILE("sed -i 's/^#autologin-user=/autologin-user={}/' /mnt/etc/lightdm/lightdm.conf"), user)); gucc::utils::exec("sed -i 's/^#autologin-user-timeout=0/autologin-user-timeout=0/' /mnt/etc/lightdm/lightdm.conf"); + // TODO(vnepogodin): refactor with gucc utils::arch_chroot("groupadd -r autologin", false); utils::arch_chroot(fmt::format(FMT_COMPILE("gpasswd -a {} autologin"), user), false); } else if (dm == "sddm") {