useradd.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. /*++
  2. Copyright (c) 2015 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. useradd.c
  5. Abstract:
  6. This module implements support for the useradd utility, which adds a new
  7. user to the system.
  8. Author:
  9. Evan Green 10-Mar-2015
  10. Environment:
  11. POSIX
  12. --*/
  13. //
  14. // ------------------------------------------------------------------- Includes
  15. //
  16. #include <minoca/lib/types.h>
  17. #include <assert.h>
  18. #include <dirent.h>
  19. #include <errno.h>
  20. #include <getopt.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <unistd.h>
  24. #include "../swlib.h"
  25. #include "lutil.h"
  26. //
  27. // ---------------------------------------------------------------- Definitions
  28. //
  29. #define USERADD_VERSION_MAJOR 1
  30. #define USERADD_VERSION_MINOR 0
  31. #define USERADD_USAGE \
  32. "usage: useradd [options] username\n" \
  33. "The useradd utility adds a new user to the system. Options are:\n" \
  34. " -b, --base-dir=dir -- Sets the base directory for the home directory \n"\
  35. " of the new account (eg. /home).\n" \
  36. " -c, --comment=gecos -- Sets the GECOS field.\n" \
  37. " -d, --home=dir -- Sets the home directory.\n" \
  38. " -g, --gid=group -- Sets the name or ID of the primary group for the\n" \
  39. " new account.\n" \
  40. " -G, --groups=group,group -- Sets the supplementary groups.\n" \
  41. " -k, --skel=dir -- Sets the alternate skeleton directory location.\n" \
  42. " -m, --create-home -- Creates the home directory if it does not exist.\n"\
  43. " -M, --no-create-home -- Do not create the home directory.\n" \
  44. " -N, --no-user-group -- Do not create a group with the same name as \n" \
  45. " the user.\n" \
  46. " -o, --non-unique -- Allow users with duplicate IDs.\n" \
  47. " -p, --password=pw -- Sets the user's password.\n" \
  48. " -R, --root=dir -- Chroot into the given directory before operating.\n" \
  49. " -r, --system -- Sets this as a system account.\n" \
  50. " -s, --shell=shell -- Sets the user's shell.\n" \
  51. " -u, --uid=id -- Sets the user ID of the new user.\n" \
  52. " -U, --user-group -- Create a group with the same name as the user.\n" \
  53. " --help -- Displays this help text and exits.\n" \
  54. " --version -- Displays the application version and exits.\n"
  55. #define USERADD_OPTIONS_STRING "b:c:d:g:G:k:mMNop:R:rs:u:UHV"
  56. #define USERADD_DEFAULT_GECOS ""
  57. #define USERADD_DEFAULT_SKELETON "/etc/skel"
  58. #define USERADD_DEFAULT_SHELL "/bin/sh"
  59. #define USERADD_DEFAULT_PASSWORD "x"
  60. #define USERADD_DEFAULT_SHADOW_PASSWORD "*"
  61. #define USERADD_DEFAULT_GROUP "nogroup"
  62. #define USERADD_DEFAULT_BASE_DIRECTORY "/home"
  63. #define USERADD_HOME_PERMISSIONS \
  64. (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_ISGID)
  65. #define USERADD_MAX_ID 0x7FFFFFFE
  66. //
  67. // Define application options.
  68. //
  69. //
  70. // Set this option to create the home directory if it does not exist.
  71. //
  72. #define USERADD_OPTION_CREATE_HOME 0x00000001
  73. //
  74. // Set this option to create a group with the same name as the user.
  75. //
  76. #define USERADD_OPTION_CREATE_GROUP 0x00000002
  77. //
  78. // Set this option to create a system account.
  79. //
  80. #define USERADD_OPTION_SYSTEM 0x00000004
  81. //
  82. // Set this option to allow non-unique user IDs.
  83. //
  84. #define USERADD_OPTION_NON_UNIQUE 0x00000008
  85. //
  86. // Define the default algorithm to use on new accounts: SHA-512.
  87. //
  88. #define USERADD_PASSWORD_ALGORITHM "$6$"
  89. //
  90. // ------------------------------------------------------ Data Type Definitions
  91. //
  92. //
  93. // ----------------------------------------------- Internal Function Prototypes
  94. //
  95. BOOL
  96. UseraddIsValidUserName (
  97. PSTR Name
  98. );
  99. INT
  100. UseraddCreateSelfieGroup (
  101. PSTR Name,
  102. gid_t Id
  103. );
  104. INT
  105. UseraddAddUserToSupplementaryGroups (
  106. PSTR User,
  107. PSTR SupplementaryGroups
  108. );
  109. //
  110. // -------------------------------------------------------------------- Globals
  111. //
  112. struct option UseraddLongOptions[] = {
  113. {"base-dir", required_argument, 0, 'b'},
  114. {"comment", required_argument, 0, 'c'},
  115. {"home", required_argument, 0, 'd'},
  116. {"gid", required_argument, 0, 'g'},
  117. {"groups", required_argument, 0, 'G'},
  118. {"skel", required_argument, 0, 'k'},
  119. {"create-home", no_argument, 0, 'm'},
  120. {"no-create-home", no_argument, 0, 'M'},
  121. {"no-user-group", no_argument, 0, 'N'},
  122. {"non-unique", no_argument, 0, 'o'},
  123. {"password", required_argument, 0, 'p'},
  124. {"root", required_argument, 0, 'R'},
  125. {"system", no_argument, 0, 'r'},
  126. {"shell", required_argument, 0, 's'},
  127. {"uid", required_argument, 0, 'u'},
  128. {"user-group", no_argument, 0, 'U'},
  129. {"help", no_argument, 0, 'H'},
  130. {"version", no_argument, 0, 'V'},
  131. {NULL, 0, 0, 0},
  132. };
  133. //
  134. // ------------------------------------------------------------------ Functions
  135. //
  136. INT
  137. UseraddMain (
  138. INT ArgumentCount,
  139. CHAR **Arguments
  140. )
  141. /*++
  142. Routine Description:
  143. This routine is the main entry point for the useradd utility.
  144. Arguments:
  145. ArgumentCount - Supplies the number of command line arguments the program
  146. was invoked with.
  147. Arguments - Supplies a tokenized array of command line arguments.
  148. Return Value:
  149. Returns an integer exit code. 0 for success, nonzero otherwise.
  150. --*/
  151. {
  152. PSTR AfterScan;
  153. ULONG ArgumentIndex;
  154. PSTR BaseDirectory;
  155. CHOWN_CONTEXT ChownContext;
  156. BOOL CreateGroupSpecified;
  157. uid_t ExistingId;
  158. PSTR GroupsString;
  159. PSTR GroupString;
  160. PSTR Home;
  161. ULONG HomeSize;
  162. INT Option;
  163. ULONG Options;
  164. mode_t OriginalUmask;
  165. PSTR Password;
  166. BOOL Result;
  167. PSTR RootDirectory;
  168. struct spwd Shadow;
  169. PSTR Skeleton;
  170. struct stat Stat;
  171. int Status;
  172. int TotalStatus;
  173. struct passwd User;
  174. PSTR UserName;
  175. memset(&Shadow, 0, sizeof(Shadow));
  176. memset(&User, 0, sizeof(User));
  177. User.pw_uid = -1;
  178. User.pw_gid = -1;
  179. BaseDirectory = USERADD_DEFAULT_BASE_DIRECTORY;
  180. CreateGroupSpecified = FALSE;
  181. GroupString = NULL;
  182. GroupsString = NULL;
  183. Home = NULL;
  184. Options = USERADD_OPTION_CREATE_GROUP | USERADD_OPTION_CREATE_HOME;
  185. Password = NULL;
  186. RootDirectory = NULL;
  187. Skeleton = USERADD_DEFAULT_SKELETON;
  188. TotalStatus = 0;
  189. UserName = NULL;
  190. User.pw_gecos = USERADD_DEFAULT_GECOS;
  191. User.pw_passwd = USERADD_DEFAULT_PASSWORD;
  192. User.pw_shell = USERADD_DEFAULT_SHELL;
  193. memcpy(&Shadow, &SwShadowTemplate, sizeof(struct spwd));
  194. Shadow.sp_lstchg = time(NULL) / (3600 * 24);
  195. OriginalUmask = umask(0);
  196. //
  197. // Process the control arguments.
  198. //
  199. while (TRUE) {
  200. Option = getopt_long(ArgumentCount,
  201. Arguments,
  202. USERADD_OPTIONS_STRING,
  203. UseraddLongOptions,
  204. NULL);
  205. if (Option == -1) {
  206. break;
  207. }
  208. if ((Option == '?') || (Option == ':')) {
  209. Status = 1;
  210. goto MainEnd;
  211. }
  212. switch (Option) {
  213. case 'b':
  214. BaseDirectory = optarg;
  215. break;
  216. case 'c':
  217. User.pw_gecos = optarg;
  218. break;
  219. case 'd':
  220. User.pw_dir = optarg;
  221. break;
  222. case 'g':
  223. GroupString = optarg;
  224. Options &= ~USERADD_OPTION_CREATE_GROUP;
  225. break;
  226. case 'G':
  227. GroupsString = optarg;
  228. break;
  229. case 'k':
  230. Skeleton = optarg;
  231. break;
  232. case 'm':
  233. Options |= USERADD_OPTION_CREATE_HOME;
  234. break;
  235. case 'M':
  236. Options &= ~USERADD_OPTION_CREATE_HOME;
  237. break;
  238. case 'N':
  239. Options &= ~USERADD_OPTION_CREATE_GROUP;
  240. break;
  241. case 'o':
  242. Options |= USERADD_OPTION_NON_UNIQUE;
  243. break;
  244. case 'p':
  245. Password = optarg;
  246. break;
  247. case 'R':
  248. RootDirectory = optarg;
  249. break;
  250. case 'r':
  251. Options |= USERADD_OPTION_SYSTEM;
  252. break;
  253. case 's':
  254. User.pw_shell = optarg;
  255. break;
  256. case 'u':
  257. User.pw_uid = strtoul(optarg, &AfterScan, 10);
  258. if (AfterScan == optarg) {
  259. SwPrintError(0, optarg, "Invalid user ID");
  260. Status = 1;
  261. goto MainEnd;
  262. }
  263. break;
  264. case 'U':
  265. Options |= USERADD_OPTION_CREATE_GROUP;
  266. CreateGroupSpecified = TRUE;
  267. break;
  268. case 'V':
  269. SwPrintVersion(USERADD_VERSION_MAJOR, USERADD_VERSION_MINOR);
  270. return 1;
  271. case 'H':
  272. printf(USERADD_USAGE);
  273. return 1;
  274. default:
  275. assert(FALSE);
  276. Status = 1;
  277. goto MainEnd;
  278. }
  279. }
  280. ArgumentIndex = optind;
  281. if (ArgumentIndex > ArgumentCount) {
  282. ArgumentIndex = ArgumentCount;
  283. }
  284. if (ArgumentIndex >= ArgumentCount) {
  285. SwPrintError(0, NULL, "Argument expected. Try --help for usage");
  286. return 1;
  287. }
  288. UserName = Arguments[ArgumentIndex];
  289. ArgumentIndex += 1;
  290. if (ArgumentIndex != ArgumentCount) {
  291. SwPrintError(0, NULL, "Unexpected additional arguments");
  292. return 1;
  293. }
  294. //
  295. // Fail on conflicting options.
  296. //
  297. if ((CreateGroupSpecified != FALSE) && (GroupString != NULL)) {
  298. SwPrintError(0, NULL, "-g and -U conflict");
  299. Status = EINVAL;
  300. goto MainEnd;
  301. }
  302. //
  303. // Enforce a valid user name.
  304. //
  305. if (SwIsValidUserName(UserName) == FALSE) {
  306. SwPrintError(0, UserName, "Invalid username");
  307. Status = 1;
  308. goto MainEnd;
  309. }
  310. //
  311. // Hash up the password. This is done so that accesses to things like
  312. // libcrypt and /dev/urandom can be done before chrooting.
  313. //
  314. if (Password != NULL) {
  315. Shadow.sp_pwdp = SwCreateHashedPassword(USERADD_PASSWORD_ALGORITHM,
  316. -1,
  317. 0,
  318. Password);
  319. if (Shadow.sp_pwdp == NULL) {
  320. SwPrintError(0,
  321. NULL,
  322. "Failed to create password, disabling account.");
  323. Shadow.sp_pwdp = USERADD_DEFAULT_SHADOW_PASSWORD;
  324. }
  325. }
  326. //
  327. // Chroot if requested.
  328. //
  329. if (RootDirectory != NULL) {
  330. Status = chroot(RootDirectory);
  331. if (Status != 0) {
  332. Status = errno;
  333. SwPrintError(Status, RootDirectory, "Failed to chroot");
  334. goto MainEnd;
  335. }
  336. Status = chdir("/");
  337. if (Status != 0) {
  338. Status = errno;
  339. SwPrintError(Status, RootDirectory, "Failed to chdir");
  340. goto MainEnd;
  341. }
  342. }
  343. User.pw_name = UserName;
  344. Shadow.sp_namp = UserName;
  345. //
  346. // Create the directory from the base if needed.
  347. //
  348. if (User.pw_dir == NULL) {
  349. Result = SwAppendPath(BaseDirectory,
  350. strlen(BaseDirectory) + 1,
  351. UserName,
  352. strlen(UserName) + 1,
  353. &Home,
  354. &HomeSize);
  355. if (Result == FALSE) {
  356. Status = ENOMEM;
  357. goto MainEnd;
  358. }
  359. User.pw_dir = Home;
  360. }
  361. //
  362. // Parse the primary and supplementary groups.
  363. //
  364. if ((Options & USERADD_OPTION_CREATE_GROUP) == 0) {
  365. if (GroupString == NULL) {
  366. GroupString = USERADD_DEFAULT_GROUP;
  367. }
  368. }
  369. if (GroupString != NULL) {
  370. Status = SwGetGroupIdFromName(GroupString, &(User.pw_gid));
  371. if (Status != 0) {
  372. SwPrintError(0, GroupString, "Invalid group");
  373. goto MainEnd;
  374. }
  375. }
  376. //
  377. // Ensure there are no duplicates in the user name.
  378. //
  379. if (SwGetUserIdFromName(UserName, &ExistingId) == 0) {
  380. SwPrintError(0,
  381. NULL,
  382. "User %s already exists (ID %d)",
  383. UserName,
  384. ExistingId);
  385. Status = 1;
  386. goto MainEnd;
  387. }
  388. //
  389. // Find a user ID.
  390. //
  391. if (User.pw_uid == (uid_t)-1) {
  392. if ((Options & USERADD_OPTION_SYSTEM) != 0) {
  393. User.pw_uid = BASE_SYSTEM_UID;
  394. } else {
  395. User.pw_uid = BASE_NON_SYSTEM_UID;
  396. }
  397. while (User.pw_uid < USERADD_MAX_ID) {
  398. if ((getpwuid(User.pw_uid) == NULL) &&
  399. (((Options & USERADD_OPTION_CREATE_GROUP) == 0) ||
  400. (getgrgid(User.pw_uid) == NULL))) {
  401. break;
  402. }
  403. User.pw_uid += 1;
  404. }
  405. if (User.pw_uid >= USERADD_MAX_ID) {
  406. SwPrintError(0, NULL, "User IDs exhausted");
  407. Status = 1;
  408. goto MainEnd;
  409. }
  410. if (User.pw_gid == (gid_t)-1) {
  411. User.pw_gid = User.pw_uid;
  412. if (getgrnam(UserName) != NULL) {
  413. SwPrintError(0, UserName, "Group already exists");
  414. Status = 1;
  415. goto MainEnd;
  416. }
  417. }
  418. //
  419. // The user wanted a specific ID.
  420. //
  421. } else {
  422. if ((Options & USERADD_OPTION_NON_UNIQUE) == 0) {
  423. if (getpwuid(User.pw_uid) != NULL) {
  424. SwPrintError(0, NULL, "User ID %d in use", User.pw_uid);
  425. Status = 1;
  426. goto MainEnd;
  427. }
  428. }
  429. }
  430. //
  431. // Create a group specifically for the user.
  432. //
  433. if ((Options & USERADD_OPTION_CREATE_GROUP) != 0) {
  434. User.pw_gid = User.pw_uid;
  435. Status = UseraddCreateSelfieGroup(UserName, User.pw_uid);
  436. if (Status != 0) {
  437. SwPrintError(Status, UserName, "Unable to create group");
  438. goto MainEnd;
  439. }
  440. }
  441. //
  442. // Add the passwd and shadow entries.
  443. //
  444. Status = SwUpdatePasswordLine(&User, &Shadow, UpdatePasswordAddLine);
  445. if (Status != 0) {
  446. SwPrintError(Status, UserName, "Failed to add user");
  447. goto MainEnd;
  448. }
  449. //
  450. // Add the user to all the supplementary groups.
  451. //
  452. if (GroupsString != NULL) {
  453. Result = UseraddAddUserToSupplementaryGroups(UserName, GroupsString);
  454. if (Result != 0) {
  455. goto MainEnd;
  456. }
  457. }
  458. //
  459. // Create the home directory.
  460. //
  461. if ((Options & USERADD_OPTION_CREATE_HOME) != 0) {
  462. Status = mkdir(User.pw_dir,
  463. USERADD_HOME_PERMISSIONS & (~S_ISGID));
  464. if ((Status != 0) && (errno != EEXIST)) {
  465. Status = errno;
  466. SwPrintError(Status,
  467. User.pw_dir,
  468. "Failed to create home directory");
  469. goto MainEnd;
  470. }
  471. //
  472. // If the directory was created, copy the skeleton contents over.
  473. //
  474. if ((Status == 0) && (stat(Skeleton, &Stat) == 0)) {
  475. SwCopy(COPY_OPTION_RECURSIVE, Skeleton, User.pw_dir);
  476. }
  477. //
  478. // Change the new user to be the owner of everything in there.
  479. //
  480. memset(&ChownContext, 0, sizeof(ChownContext));
  481. ChownContext.Options = CHOWN_OPTION_RECURSIVE;
  482. ChownContext.User = User.pw_uid;
  483. ChownContext.Group = User.pw_gid;
  484. ChownContext.FromUser = -1;
  485. ChownContext.FromGroup = -1;
  486. Status = ChownChangeOwnership(&ChownContext, User.pw_dir, 0);
  487. if (Status != 0) {
  488. SwPrintError(Status, User.pw_dir, "Failed to change ownership");
  489. Status = 0;
  490. }
  491. Status = chmod(User.pw_dir, USERADD_HOME_PERMISSIONS);
  492. if (Status != 0) {
  493. SwPrintError(errno, User.pw_dir, "Failed to change mode");
  494. Status = 0;
  495. }
  496. }
  497. Status = 0;
  498. MainEnd:
  499. umask(OriginalUmask);
  500. if (Home != NULL) {
  501. free(Home);
  502. }
  503. if ((TotalStatus == 0) && (Status != 0)) {
  504. TotalStatus = Status;
  505. }
  506. return TotalStatus;
  507. }
  508. //
  509. // --------------------------------------------------------- Internal Functions
  510. //
  511. INT
  512. UseraddCreateSelfieGroup (
  513. PSTR Name,
  514. gid_t Id
  515. )
  516. /*++
  517. Routine Description:
  518. This routine creates a group with the same name as the given user and with
  519. one member: the user.
  520. Arguments:
  521. Name - Supplies a pointer to the user/group name.
  522. Id - Supplies the ID of the user/group.
  523. Return Value:
  524. 0 on success.
  525. Non-zero on failure.
  526. --*/
  527. {
  528. struct group Group;
  529. memset(&Group, 0, sizeof(Group));
  530. Group.gr_name = Name;
  531. Group.gr_passwd = USERADD_DEFAULT_PASSWORD;
  532. Group.gr_gid = Id;
  533. return SwUpdateGroupLine(&Group, UpdatePasswordAddLine);
  534. }
  535. INT
  536. UseraddAddUserToSupplementaryGroups (
  537. PSTR User,
  538. PSTR SupplementaryGroups
  539. )
  540. /*++
  541. Routine Description:
  542. This routine adds the user to all the supplementary groups.
  543. Arguments:
  544. User - Supplies the user name to add.
  545. SupplementaryGroups - Supplies the supplementary groups to add the user to.
  546. Return Value:
  547. 0 on success.
  548. Non-zero on failure.
  549. --*/
  550. {
  551. PSTR Copy;
  552. PSTR Current;
  553. PSTR NextComma;
  554. INT Result;
  555. INT TotalResult;
  556. Copy = strdup(SupplementaryGroups);
  557. if (Copy == NULL) {
  558. return ENOMEM;
  559. }
  560. TotalResult = 0;
  561. Current = Copy;
  562. while (Current != NULL) {
  563. NextComma = strchr(Current, ',');
  564. if (NextComma != NULL) {
  565. *NextComma = '\0';
  566. NextComma += 1;
  567. }
  568. if (*Current != '\0') {
  569. Result = SwUpdatePasswordFile(GROUP_FILE_PATH,
  570. Current,
  571. NULL,
  572. User,
  573. UpdatePasswordAddGroupMember);
  574. if (Result != 0) {
  575. SwPrintError(Result, Current, "Failed to add user to group");
  576. TotalResult = Result;
  577. }
  578. }
  579. Current = NextComma;
  580. }
  581. free(Copy);
  582. return TotalResult;
  583. }