Serenity Operating System
at master 141 lines 4.7 kB view raw
1/* 2 * Copyright (c) 2021, Brandon Pruitt <brapru@pm.me> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibCore/Account.h> 8#include <LibCore/ArgsParser.h> 9#include <LibCore/DeprecatedFile.h> 10#include <LibCore/System.h> 11#include <LibMain/Main.h> 12#include <pwd.h> 13#include <stdio.h> 14#include <string.h> 15#include <unistd.h> 16 17ErrorOr<int> serenity_main(Main::Arguments arguments) 18{ 19 if (geteuid() != 0) { 20 warnln("Not running as root :^("); 21 return 1; 22 } 23 24 TRY(Core::System::setegid(0)); 25 26 TRY(Core::System::pledge("stdio wpath rpath cpath fattr tty")); 27 TRY(Core::System::unveil("/etc", "rwc")); 28 29 int uid = 0; 30 int gid = 0; 31 bool lock = false; 32 bool unlock = false; 33 StringView new_home_directory; 34 bool move_home = false; 35 StringView shell; 36 StringView gecos; 37 StringView username; 38 39 auto args_parser = Core::ArgsParser(); 40 args_parser.set_general_help("Modify a user account"); 41 args_parser.add_option(uid, "The new numerical value of the user's ID", "uid", 'u', "uid"); 42 args_parser.add_option(gid, "The group number of the user's new initial login group", "gid", 'g', "gid"); 43 args_parser.add_option(lock, "Lock password", "lock", 'L'); 44 args_parser.add_option(unlock, "Unlock password", "unlock", 'U'); 45 args_parser.add_option(new_home_directory, "The user's new login directory", "home", 'd', "new-home"); 46 args_parser.add_option(move_home, "Move the content of the user's home directory to the new location", "move", 'm'); 47 args_parser.add_option(shell, "The name of the user's new login shell", "shell", 's', "path-to-shell"); 48 args_parser.add_option(gecos, "Change the GECOS field of the user", "gecos", 'n', "general-info"); 49 args_parser.add_positional_argument(username, "Username of the account to modify", "username"); 50 51 args_parser.parse(arguments); 52 53 auto account_or_error = Core::Account::from_name(username); 54 55 if (account_or_error.is_error()) { 56 warnln("Core::Account::from_name: {}", account_or_error.error()); 57 return 1; 58 } 59 60 // target_account is the account we are modifying. 61 auto& target_account = account_or_error.value(); 62 63 if (move_home) { 64 TRY(Core::System::unveil(target_account.home_directory(), "c"sv)); 65 TRY(Core::System::unveil(new_home_directory, "wc"sv)); 66 } 67 68 unveil(nullptr, nullptr); 69 70 if (uid) { 71 if (uid < 0) { 72 warnln("invalid uid {}", uid); 73 return 1; 74 } 75 76 if (getpwuid(static_cast<uid_t>(uid))) { 77 warnln("uid {} already exists", uid); 78 return 1; 79 } 80 81 target_account.set_uid(uid); 82 } 83 84 if (gid) { 85 if (gid < 0) { 86 warnln("invalid gid {}", gid); 87 return 1; 88 } 89 90 target_account.set_gid(gid); 91 } 92 93 if (lock) { 94 target_account.set_password_enabled(false); 95 } 96 97 if (unlock) { 98 target_account.set_password_enabled(true); 99 } 100 101 if (!new_home_directory.is_empty()) { 102 if (move_home) { 103 auto maybe_error = Core::System::rename(target_account.home_directory(), new_home_directory); 104 if (maybe_error.is_error()) { 105 if (maybe_error.error().code() == EXDEV) { 106 auto result = Core::DeprecatedFile::copy_file_or_directory( 107 new_home_directory, target_account.home_directory().characters(), 108 Core::DeprecatedFile::RecursionMode::Allowed, 109 Core::DeprecatedFile::LinkMode::Disallowed, 110 Core::DeprecatedFile::AddDuplicateFileMarker::No); 111 112 if (result.is_error()) { 113 warnln("usermod: could not move directory {} : {}", target_account.home_directory().characters(), static_cast<Error const&>(result.error())); 114 return 1; 115 } 116 maybe_error = Core::System::unlink(target_account.home_directory()); 117 if (maybe_error.is_error()) 118 warnln("usermod: unlink {} : {}", target_account.home_directory(), maybe_error.error().code()); 119 } else { 120 warnln("usermod: could not move directory {} : {}", target_account.home_directory(), maybe_error.error().code()); 121 } 122 } 123 } 124 125 target_account.set_home_directory(new_home_directory); 126 } 127 128 if (!shell.is_empty()) { 129 target_account.set_shell(shell); 130 } 131 132 if (!gecos.is_empty()) { 133 target_account.set_gecos(gecos); 134 } 135 136 TRY(Core::System::pledge("stdio wpath rpath cpath fattr")); 137 138 TRY(target_account.sync()); 139 140 return 0; 141}