Account.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /*
  2. * Copyright (c) 2020, Peter Elliott <pelliott@ualberta.ca>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/Base64.h>
  27. #include <AK/Random.h>
  28. #include <LibCore/Account.h>
  29. #include <LibCore/File.h>
  30. #include <grp.h>
  31. #include <pwd.h>
  32. #include <stdio.h>
  33. #include <string.h>
  34. #include <unistd.h>
  35. namespace Core {
  36. static String get_salt()
  37. {
  38. char random_data[12];
  39. AK::fill_with_random(random_data, sizeof(random_data));
  40. StringBuilder builder;
  41. builder.append("$5$");
  42. builder.append(encode_base64(ReadonlyBytes(random_data, sizeof(random_data))));
  43. return builder.build();
  44. }
  45. static Vector<gid_t> get_gids(const StringView& username)
  46. {
  47. Vector<gid_t> extra_gids;
  48. for (auto* group = getgrent(); group; group = getgrent()) {
  49. for (size_t i = 0; group->gr_mem[i]; ++i) {
  50. if (username == group->gr_mem[i]) {
  51. extra_gids.append(group->gr_gid);
  52. break;
  53. }
  54. }
  55. }
  56. endgrent();
  57. return extra_gids;
  58. }
  59. Result<Account, String> Account::from_passwd(const passwd& pwd, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
  60. {
  61. RefPtr<Core::File> passwd_file;
  62. if (open_passwd_file != Core::Account::OpenPasswdFile::No) {
  63. auto open_mode = open_passwd_file == Core::Account::OpenPasswdFile::ReadOnly
  64. ? Core::File::OpenMode::ReadOnly
  65. : Core::File::OpenMode::ReadWrite;
  66. auto file_or_error = Core::File::open("/etc/passwd", open_mode);
  67. if (file_or_error.is_error())
  68. return file_or_error.error();
  69. passwd_file = file_or_error.value();
  70. }
  71. RefPtr<Core::File> shadow_file;
  72. if (open_shadow_file != Core::Account::OpenShadowFile::No) {
  73. auto open_mode = open_shadow_file == Core::Account::OpenShadowFile::ReadOnly
  74. ? Core::File::OpenMode::ReadOnly
  75. : Core::File::OpenMode::ReadWrite;
  76. auto file_or_error = Core::File::open("/etc/shadow", open_mode);
  77. if (file_or_error.is_error())
  78. return file_or_error.error();
  79. shadow_file = file_or_error.value();
  80. }
  81. Account account(pwd, get_gids(pwd.pw_name), move(passwd_file), move(shadow_file));
  82. endpwent();
  83. return account;
  84. }
  85. Result<Account, String> Account::from_name(const char* username, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
  86. {
  87. struct passwd* pwd = nullptr;
  88. errno = 0;
  89. pwd = getpwnam(username);
  90. if (!pwd) {
  91. if (errno == 0)
  92. return String("No such user");
  93. return String(strerror(errno));
  94. }
  95. return from_passwd(*pwd, open_passwd_file, open_shadow_file);
  96. }
  97. Result<Account, String> Account::from_uid(uid_t uid, Core::Account::OpenPasswdFile open_passwd_file, Core::Account::OpenShadowFile open_shadow_file)
  98. {
  99. struct passwd* pwd = nullptr;
  100. errno = 0;
  101. pwd = getpwuid(uid);
  102. if (!pwd) {
  103. if (errno == 0)
  104. return String("No such user");
  105. return String(strerror(errno));
  106. }
  107. return from_passwd(*pwd, open_passwd_file, open_shadow_file);
  108. }
  109. bool Account::authenticate(const char* password) const
  110. {
  111. // An empty passwd field indicates that no password is required to log in.
  112. if (m_password_hash.is_empty())
  113. return true;
  114. // FIXME: Use crypt_r if it can be built in lagom.
  115. char* hash = crypt(password, m_password_hash.characters());
  116. return hash != nullptr && strcmp(hash, m_password_hash.characters()) == 0;
  117. }
  118. bool Account::login() const
  119. {
  120. if (setgroups(m_extra_gids.size(), m_extra_gids.data()) < 0)
  121. return false;
  122. if (setgid(m_gid) < 0)
  123. return false;
  124. if (setuid(m_uid) < 0)
  125. return false;
  126. return true;
  127. }
  128. void Account::set_password(const char* password)
  129. {
  130. m_password_hash = crypt(password, get_salt().characters());
  131. }
  132. void Account::set_password_enabled(bool enabled)
  133. {
  134. if (enabled && m_password_hash != "" && m_password_hash[0] == '!') {
  135. m_password_hash = m_password_hash.substring(1, m_password_hash.length() - 1);
  136. } else if (!enabled && (m_password_hash == "" || m_password_hash[0] != '!')) {
  137. StringBuilder builder;
  138. builder.append('!');
  139. builder.append(m_password_hash);
  140. m_password_hash = builder.build();
  141. }
  142. }
  143. void Account::delete_password()
  144. {
  145. m_password_hash = "";
  146. }
  147. Account::Account(const passwd& pwd, Vector<gid_t> extra_gids, RefPtr<Core::File> passwd_file, RefPtr<Core::File> shadow_file)
  148. : m_passwd_file(move(passwd_file))
  149. , m_shadow_file(move(shadow_file))
  150. , m_username(pwd.pw_name)
  151. , m_uid(pwd.pw_uid)
  152. , m_gid(pwd.pw_gid)
  153. , m_gecos(pwd.pw_gecos)
  154. , m_home_directory(pwd.pw_dir)
  155. , m_shell(pwd.pw_shell)
  156. , m_extra_gids(extra_gids)
  157. {
  158. if (m_shadow_file) {
  159. load_shadow_file();
  160. }
  161. }
  162. String Account::generate_passwd_file() const
  163. {
  164. StringBuilder builder;
  165. setpwent();
  166. struct passwd* p;
  167. errno = 0;
  168. while ((p = getpwent())) {
  169. if (p->pw_uid == m_uid) {
  170. builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
  171. m_username,
  172. m_uid, m_gid,
  173. m_gecos,
  174. m_home_directory,
  175. m_shell);
  176. } else {
  177. builder.appendff("{}:!:{}:{}:{}:{}:{}\n",
  178. p->pw_name, p->pw_uid,
  179. p->pw_gid, p->pw_gecos, p->pw_dir,
  180. p->pw_shell);
  181. }
  182. }
  183. endpwent();
  184. if (errno) {
  185. dbgln("errno was non-zero after generating new passwd file.");
  186. return {};
  187. }
  188. return builder.to_string();
  189. }
  190. void Account::load_shadow_file()
  191. {
  192. ASSERT(m_shadow_file);
  193. ASSERT(m_shadow_file->is_open());
  194. if (!m_shadow_file->seek(0)) {
  195. ASSERT_NOT_REACHED();
  196. }
  197. Vector<ShadowEntry> entries;
  198. for (;;) {
  199. auto line = m_shadow_file->read_line();
  200. if (line.is_null())
  201. break;
  202. auto parts = line.split(':');
  203. if (parts.size() != 2) {
  204. dbgln("Malformed shadow entry, ignoring.");
  205. continue;
  206. }
  207. const auto& username = parts[0];
  208. const auto& password_hash = parts[1];
  209. entries.append({ username, password_hash });
  210. if (username == m_username) {
  211. m_password_hash = password_hash;
  212. }
  213. }
  214. m_shadow_entries = move(entries);
  215. }
  216. String Account::generate_shadow_file() const
  217. {
  218. StringBuilder builder;
  219. bool updated_entry_in_place = false;
  220. for (auto& entry : m_shadow_entries) {
  221. if (entry.username == m_username) {
  222. updated_entry_in_place = true;
  223. builder.appendff("{}:{}\n", m_username, m_password_hash);
  224. } else {
  225. builder.appendff("{}:{}\n", entry.username, entry.password_hash);
  226. }
  227. }
  228. if (!updated_entry_in_place)
  229. builder.appendff("{}:{}\n", m_username, m_password_hash);
  230. return builder.to_string();
  231. }
  232. bool Account::sync()
  233. {
  234. ASSERT(m_passwd_file);
  235. ASSERT(m_passwd_file->mode() == Core::File::OpenMode::ReadWrite);
  236. ASSERT(m_shadow_file);
  237. ASSERT(m_shadow_file->mode() == Core::File::OpenMode::ReadWrite);
  238. // FIXME: Maybe reorganize this to create temporary files and finish it completely before renaming them to /etc/{passwd,shadow}
  239. // If truncation succeeds but write fails, we'll have an empty file :(
  240. auto new_passwd_file = generate_passwd_file();
  241. auto new_shadow_file = generate_shadow_file();
  242. if (new_passwd_file.is_null() || new_shadow_file.is_null()) {
  243. ASSERT_NOT_REACHED();
  244. }
  245. if (!m_passwd_file->seek(0) || !m_shadow_file->seek(0)) {
  246. ASSERT_NOT_REACHED();
  247. }
  248. if (!m_passwd_file->truncate(0)) {
  249. dbgln("Truncating passwd file failed.");
  250. return false;
  251. }
  252. if (!m_passwd_file->write(new_passwd_file)) {
  253. // FIXME: Improve Core::File::write() error reporting.
  254. dbgln("Writing to passwd file failed.");
  255. return false;
  256. }
  257. if (!m_shadow_file->truncate(0)) {
  258. dbgln("Truncating shadow file failed.");
  259. return false;
  260. }
  261. if (!m_shadow_file->write(new_shadow_file)) {
  262. // FIXME: Improve Core::File::write() error reporting.
  263. dbgln("Writing to shadow file failed.");
  264. return false;
  265. }
  266. return true;
  267. // FIXME: Sync extra groups.
  268. }
  269. }