useradd.cpp 5.4 KB

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