Serenity Operating System
at master 184 lines 5.7 kB view raw
1/* 2 * Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com> 3 * Copyright (c) 2021, Brandon Pruitt <brapru@pm.me> 4 * Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan62@gmail.com> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/Base64.h> 10#include <AK/DeprecatedString.h> 11#include <AK/Random.h> 12#include <LibCore/ArgsParser.h> 13#include <LibCore/System.h> 14#include <LibMain/Main.h> 15#include <crypt.h> 16#include <ctype.h> 17#include <errno.h> 18#include <pwd.h> 19#include <shadow.h> 20#include <stdio.h> 21#include <string.h> 22#include <sys/stat.h> 23#include <sys/types.h> 24#include <unistd.h> 25 26constexpr uid_t BASE_UID = 1000; 27constexpr gid_t USERS_GID = 100; 28constexpr auto DEFAULT_SHELL = "/bin/sh"sv; 29 30ErrorOr<int> serenity_main(Main::Arguments arguments) 31{ 32 TRY(Core::System::pledge("stdio wpath rpath cpath chown")); 33 34 StringView home_path; 35 int uid = 0; 36 int gid = USERS_GID; 37 bool create_home_dir = false; 38 DeprecatedString password = ""; 39 DeprecatedString shell = DEFAULT_SHELL; 40 DeprecatedString gecos = ""; 41 DeprecatedString username; 42 43 Core::ArgsParser args_parser; 44 args_parser.add_option(home_path, "Home directory for the new user", "home-dir", 'd', "path"); 45 args_parser.add_option(uid, "User ID (uid) for the new user", "uid", 'u', "uid"); 46 args_parser.add_option(gid, "Group ID (gid) for the new user", "gid", 'g', "gid"); 47 args_parser.add_option(password, "Encrypted password of the new user", "password", 'p', "password"); 48 args_parser.add_option(create_home_dir, "Create home directory if it does not exist", "create-home", 'm'); 49 args_parser.add_option(shell, "Path to the default shell binary for the new user", "shell", 's', "path-to-shell"); 50 args_parser.add_option(gecos, "GECOS name of the new user", "gecos", 'n', "general-info"); 51 args_parser.add_positional_argument(username, "Login user identity (username)", "login"); 52 53 args_parser.parse(arguments); 54 55 // Let's run a quick sanity check on username 56 if (username.find_any_of("\\/!@#$%^&*()~+=`:\n"sv, DeprecatedString::SearchDirection::Forward).has_value()) { 57 warnln("invalid character in username, {}", username); 58 return 1; 59 } 60 61 // Disallow names starting with _ and - 62 if (username[0] == '_' || username[0] == '-' || !isalpha(username[0])) { 63 warnln("invalid username, {}", username); 64 return 1; 65 } 66 67 auto passwd = TRY(Core::System::getpwnam(username)); 68 if (passwd.has_value()) { 69 warnln("user {} already exists!", username); 70 return 1; 71 } 72 73 if (uid < 0) { 74 warnln("invalid uid {}!", uid); 75 return 3; 76 } 77 78 // First, let's sort out the uid for the user 79 if (uid > 0) { 80 auto pwd = TRY(Core::System::getpwuid(static_cast<uid_t>(uid))); 81 if (pwd.has_value()) { 82 warnln("uid {} already exists!", uid); 83 return 4; 84 } 85 } else { 86 for (uid = BASE_UID;; uid++) { 87 auto pwd = TRY(Core::System::getpwuid(static_cast<uid_t>(uid))); 88 if (!pwd.has_value()) 89 break; 90 } 91 } 92 93 if (gid < 0) { 94 warnln("invalid gid {}", gid); 95 return 3; 96 } 97 98 FILE* pwfile = fopen("/etc/passwd", "a"); 99 if (!pwfile) { 100 perror("failed to open /etc/passwd"); 101 return 1; 102 } 103 104 FILE* spwdfile = fopen("/etc/shadow", "a"); 105 if (!spwdfile) { 106 perror("failed to open /etc/shadow"); 107 return 1; 108 } 109 110 DeprecatedString home; 111 if (home_path.is_empty()) 112 home = DeprecatedString::formatted("/home/{}", username); 113 else 114 home = home_path; 115 116 if (create_home_dir) { 117 auto mkdir_error = Core::System::mkdir(home, 0700); 118 if (mkdir_error.is_error()) { 119 int code = mkdir_error.release_error().code(); 120 warnln("Failed to create directory {}: {}", home, strerror(code)); 121 return 12; 122 } 123 124 auto chown_error = Core::System::chown(home, static_cast<uid_t>(uid), static_cast<gid_t>(gid)); 125 if (chown_error.is_error()) { 126 int code = chown_error.release_error().code(); 127 warnln("Failed to change owner of {} to {}:{}: {}", home, uid, gid, strerror(code)); 128 129 if (rmdir(home.characters()) < 0) { 130 warnln("Failed to remove directory {}: {}", home, strerror(errno)); 131 } 132 133 return 12; 134 } 135 } 136 137 auto get_salt = []() -> ErrorOr<DeprecatedString> { 138 char random_data[12]; 139 fill_with_random(random_data, sizeof(random_data)); 140 141 StringBuilder builder; 142 builder.append("$5$"sv); 143 builder.append(TRY(encode_base64({ random_data, sizeof(random_data) }))); 144 145 return builder.to_deprecated_string(); 146 }; 147 148 char* hash = crypt(password.characters(), TRY(get_salt()).characters()); 149 150 struct passwd p; 151 p.pw_name = const_cast<char*>(username.characters()); 152 p.pw_passwd = const_cast<char*>("!"); 153 p.pw_dir = const_cast<char*>(home.characters()); 154 p.pw_uid = static_cast<uid_t>(uid); 155 p.pw_gid = static_cast<gid_t>(gid); 156 p.pw_shell = const_cast<char*>(shell.characters()); 157 p.pw_gecos = const_cast<char*>(gecos.characters()); 158 159 struct spwd s; 160 s.sp_namp = const_cast<char*>(username.characters()); 161 s.sp_pwdp = const_cast<char*>(hash); 162 s.sp_lstchg = 18727; 163 s.sp_min = 0; 164 s.sp_max = 99999; 165 s.sp_warn = -1; 166 s.sp_inact = -1; 167 s.sp_expire = -1; 168 s.sp_flag = -1; 169 170 if (putpwent(&p, pwfile) < 0) { 171 perror("putpwent"); 172 return 1; 173 } 174 175 if (putspent(&s, spwdfile) < 0) { 176 perror("putspent"); 177 return 1; 178 } 179 180 fclose(pwfile); 181 fclose(spwdfile); 182 183 return 0; 184}