Serenity Operating System
at master 334 lines 10 kB view raw
1/* 2 * Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org> 3 * Copyright (c) 2021-2022, Brian Gianforcaro <bgianf@serenityos.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/Base64.h> 9#include <AK/Memory.h> 10#include <AK/Random.h> 11#include <AK/ScopeGuard.h> 12#include <LibCore/Account.h> 13#include <LibCore/Directory.h> 14#include <LibCore/System.h> 15#include <LibCore/UmaskScope.h> 16#include <errno.h> 17#include <grp.h> 18#include <pwd.h> 19#ifndef AK_OS_BSD_GENERIC 20# include <crypt.h> 21# include <shadow.h> 22#endif 23#include <stdio.h> 24#include <string.h> 25#include <sys/stat.h> 26#include <unistd.h> 27 28namespace Core { 29 30static DeprecatedString get_salt() 31{ 32 char random_data[12]; 33 fill_with_random(random_data, sizeof(random_data)); 34 35 StringBuilder builder; 36 builder.append("$5$"sv); 37 38 // FIXME: change to TRY() and make method fallible 39 auto salt_string = MUST(encode_base64({ random_data, sizeof(random_data) })); 40 builder.append(salt_string); 41 42 return builder.to_deprecated_string(); 43} 44 45static Vector<gid_t> get_extra_gids(passwd const& pwd) 46{ 47 StringView username { pwd.pw_name, strlen(pwd.pw_name) }; 48 Vector<gid_t> extra_gids; 49 setgrent(); 50 for (auto* group = getgrent(); group; group = getgrent()) { 51 if (group->gr_gid == pwd.pw_gid) 52 continue; 53 for (size_t i = 0; group->gr_mem[i]; ++i) { 54 if (username == group->gr_mem[i]) { 55 extra_gids.append(group->gr_gid); 56 break; 57 } 58 } 59 } 60 endgrent(); 61 return extra_gids; 62} 63 64ErrorOr<Account> Account::from_passwd(passwd const& pwd, spwd const& spwd) 65{ 66 Account account(pwd, spwd, get_extra_gids(pwd)); 67 endpwent(); 68#ifndef AK_OS_BSD_GENERIC 69 endspent(); 70#endif 71 return account; 72} 73 74ErrorOr<Account> Account::self([[maybe_unused]] Read options) 75{ 76 Vector<gid_t> extra_gids = TRY(Core::System::getgroups()); 77 78 auto pwd = TRY(Core::System::getpwuid(getuid())); 79 if (!pwd.has_value()) 80 return Error::from_string_literal("No such user"); 81 82 spwd spwd = {}; 83#ifndef AK_OS_BSD_GENERIC 84 if (options != Read::PasswdOnly) { 85 auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) })); 86 if (!maybe_spwd.has_value()) 87 return Error::from_string_literal("No shadow entry for user"); 88 spwd = maybe_spwd.release_value(); 89 } 90#endif 91 92 return Account(*pwd, spwd, extra_gids); 93} 94 95ErrorOr<Account> Account::from_name(StringView username, [[maybe_unused]] Read options) 96{ 97 auto pwd = TRY(Core::System::getpwnam(username)); 98 if (!pwd.has_value()) 99 return Error::from_string_literal("No such user"); 100 101 spwd spwd = {}; 102#ifndef AK_OS_BSD_GENERIC 103 if (options != Read::PasswdOnly) { 104 auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) })); 105 if (!maybe_spwd.has_value()) 106 return Error::from_string_literal("No shadow entry for user"); 107 spwd = maybe_spwd.release_value(); 108 } 109#endif 110 return from_passwd(*pwd, spwd); 111} 112 113ErrorOr<Account> Account::from_uid(uid_t uid, [[maybe_unused]] Read options) 114{ 115 auto pwd = TRY(Core::System::getpwuid(uid)); 116 if (!pwd.has_value()) 117 return Error::from_string_literal("No such user"); 118 119 spwd spwd = {}; 120#ifndef AK_OS_BSD_GENERIC 121 if (options != Read::PasswdOnly) { 122 auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) })); 123 if (!maybe_spwd.has_value()) 124 return Error::from_string_literal("No shadow entry for user"); 125 spwd = maybe_spwd.release_value(); 126 } 127#endif 128 return from_passwd(*pwd, spwd); 129} 130 131ErrorOr<Vector<Account>> Account::all([[maybe_unused]] Read options) 132{ 133 Vector<Account> accounts; 134 char buffer[1024] = { 0 }; 135 136 ScopeGuard pwent_guard([] { endpwent(); }); 137 setpwent(); 138 139 while (true) { 140 auto pwd = TRY(Core::System::getpwent({ buffer, sizeof(buffer) })); 141 if (!pwd.has_value()) 142 break; 143 144 spwd spwd = {}; 145 146#ifndef AK_OS_BSD_GENERIC 147 ScopeGuard spent_guard([] { endspent(); }); 148 if (options != Read::PasswdOnly) { 149 auto maybe_spwd = TRY(Core::System::getspnam({ pwd->pw_name, strlen(pwd->pw_name) })); 150 if (!maybe_spwd.has_value()) 151 return Error::from_string_literal("No shadow entry for user"); 152 spwd = maybe_spwd.release_value(); 153 } 154#endif 155 156 accounts.append({ *pwd, spwd, get_extra_gids(*pwd) }); 157 } 158 159 return accounts; 160} 161 162bool Account::authenticate(SecretString const& password) const 163{ 164 // If there was no shadow entry for this account, authentication always fails. 165 if (m_password_hash.is_null()) 166 return false; 167 168 // An empty passwd field indicates that no password is required to log in. 169 if (m_password_hash.is_empty()) 170 return true; 171 172 // FIXME: Use crypt_r if it can be built in lagom. 173 char* hash = crypt(password.characters(), m_password_hash.characters()); 174 return hash != nullptr && AK::timing_safe_compare(hash, m_password_hash.characters(), m_password_hash.length()); 175} 176 177ErrorOr<void> Account::login() const 178{ 179 TRY(Core::System::setgroups(m_extra_gids)); 180 TRY(Core::System::setgid(m_gid)); 181 TRY(Core::System::setuid(m_uid)); 182 183 return {}; 184} 185 186void Account::set_password(SecretString const& password) 187{ 188 m_password_hash = crypt(password.characters(), get_salt().characters()); 189} 190 191void Account::set_password_enabled(bool enabled) 192{ 193 if (enabled && m_password_hash != "" && m_password_hash[0] == '!') { 194 m_password_hash = m_password_hash.substring(1, m_password_hash.length() - 1); 195 } else if (!enabled && (m_password_hash == "" || m_password_hash[0] != '!')) { 196 StringBuilder builder; 197 builder.append('!'); 198 builder.append(m_password_hash); 199 m_password_hash = builder.to_deprecated_string(); 200 } 201} 202 203void Account::delete_password() 204{ 205 m_password_hash = ""; 206} 207 208Account::Account(passwd const& pwd, spwd const& spwd, Vector<gid_t> extra_gids) 209 : m_username(pwd.pw_name) 210 , m_password_hash(spwd.sp_pwdp) 211 , m_uid(pwd.pw_uid) 212 , m_gid(pwd.pw_gid) 213 , m_gecos(pwd.pw_gecos) 214 , m_home_directory(pwd.pw_dir) 215 , m_shell(pwd.pw_shell) 216 , m_extra_gids(move(extra_gids)) 217{ 218} 219 220ErrorOr<DeprecatedString> Account::generate_passwd_file() const 221{ 222 StringBuilder builder; 223 char buffer[1024] = { 0 }; 224 225 ScopeGuard pwent_guard([] { endpwent(); }); 226 setpwent(); 227 228 while (true) { 229 auto pwd = TRY(Core::System::getpwent({ buffer, sizeof(buffer) })); 230 if (!pwd.has_value()) 231 break; 232 233 if (pwd->pw_name == m_username) { 234 if (m_deleted) 235 continue; 236 builder.appendff("{}:!:{}:{}:{}:{}:{}\n", 237 m_username, 238 m_uid, m_gid, 239 m_gecos, 240 m_home_directory, 241 m_shell); 242 243 } else { 244 builder.appendff("{}:!:{}:{}:{}:{}:{}\n", 245 pwd->pw_name, pwd->pw_uid, 246 pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir, 247 pwd->pw_shell); 248 } 249 } 250 251 return builder.to_deprecated_string(); 252} 253 254#ifndef AK_OS_BSD_GENERIC 255ErrorOr<DeprecatedString> Account::generate_shadow_file() const 256{ 257 StringBuilder builder; 258 259 setspent(); 260 261 struct spwd* p; 262 errno = 0; 263 while ((p = getspent())) { 264 if (p->sp_namp == m_username) { 265 if (m_deleted) 266 continue; 267 builder.appendff("{}:{}", m_username, m_password_hash); 268 } else 269 builder.appendff("{}:{}", p->sp_namp, p->sp_pwdp); 270 271 builder.appendff(":{}:{}:{}:{}:{}:{}:{}\n", 272 (p->sp_lstchg == -1) ? "" : DeprecatedString::formatted("{}", p->sp_lstchg), 273 (p->sp_min == -1) ? "" : DeprecatedString::formatted("{}", p->sp_min), 274 (p->sp_max == -1) ? "" : DeprecatedString::formatted("{}", p->sp_max), 275 (p->sp_warn == -1) ? "" : DeprecatedString::formatted("{}", p->sp_warn), 276 (p->sp_inact == -1) ? "" : DeprecatedString::formatted("{}", p->sp_inact), 277 (p->sp_expire == -1) ? "" : DeprecatedString::formatted("{}", p->sp_expire), 278 (p->sp_flag == 0) ? "" : DeprecatedString::formatted("{}", p->sp_flag)); 279 } 280 endspent(); 281 282 if (errno) 283 return Error::from_errno(errno); 284 285 return builder.to_deprecated_string(); 286} 287#endif 288 289ErrorOr<void> Account::sync() 290{ 291 Core::UmaskScope umask_scope(0777); 292 293 auto new_passwd_file_content = TRY(generate_passwd_file()); 294#ifndef AK_OS_BSD_GENERIC 295 auto new_shadow_file_content = TRY(generate_shadow_file()); 296#endif 297 298 char new_passwd_file[] = "/etc/passwd.XXXXXX"; 299#ifndef AK_OS_BSD_GENERIC 300 char new_shadow_file[] = "/etc/shadow.XXXXXX"; 301#endif 302 303 { 304 auto new_passwd_fd = TRY(Core::System::mkstemp(new_passwd_file)); 305 ScopeGuard new_passwd_fd_guard = [new_passwd_fd] { close(new_passwd_fd); }; 306 TRY(Core::System::fchmod(new_passwd_fd, 0644)); 307 308#ifndef AK_OS_BSD_GENERIC 309 auto new_shadow_fd = TRY(Core::System::mkstemp(new_shadow_file)); 310 ScopeGuard new_shadow_fd_guard = [new_shadow_fd] { close(new_shadow_fd); }; 311 TRY(Core::System::fchmod(new_shadow_fd, 0600)); 312#endif 313 314 auto nwritten = TRY(Core::System::write(new_passwd_fd, new_passwd_file_content.bytes())); 315 VERIFY(static_cast<size_t>(nwritten) == new_passwd_file_content.length()); 316 317#ifndef AK_OS_BSD_GENERIC 318 nwritten = TRY(Core::System::write(new_shadow_fd, new_shadow_file_content.bytes())); 319 VERIFY(static_cast<size_t>(nwritten) == new_shadow_file_content.length()); 320#endif 321 } 322 323 auto new_passwd_file_view = StringView { new_passwd_file, sizeof(new_passwd_file) }; 324 TRY(Core::System::rename(new_passwd_file_view, "/etc/passwd"sv)); 325#ifndef AK_OS_BSD_GENERIC 326 auto new_shadow_file_view = StringView { new_shadow_file, sizeof(new_shadow_file) }; 327 TRY(Core::System::rename(new_shadow_file_view, "/etc/shadow"sv)); 328#endif 329 330 return {}; 331 // FIXME: Sync extra groups. 332} 333 334}