sulogin.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /*++
  2. Copyright (c) 2015 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. sulogin.c
  5. Abstract:
  6. This module implements the sulogin command, which completes a single-user
  7. login, usually used during boot for emergencies.
  8. Author:
  9. Evan Green 11-Mar-2015
  10. Environment:
  11. POSIX
  12. --*/
  13. //
  14. // ------------------------------------------------------------------- Includes
  15. //
  16. #include <minoca/lib/types.h>
  17. #include <assert.h>
  18. #include <ctype.h>
  19. #include <errno.h>
  20. #include <fcntl.h>
  21. #include <getopt.h>
  22. #include <signal.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <termios.h>
  26. #include <unistd.h>
  27. #include "../swlib.h"
  28. #include "lutil.h"
  29. //
  30. // ---------------------------------------------------------------- Definitions
  31. //
  32. #define SULOGIN_VERSION_MAJOR 1
  33. #define SULOGIN_VERSION_MINOR 0
  34. #define SULOGIN_USAGE \
  35. "usage: sulogin [options] [TTY]\n" \
  36. "The sulogin utility performs a single-user login, usually used during\n" \
  37. "boot for emergencies. Options are:\n" \
  38. " -e -- If the root account information cannot be brought up, log \n" \
  39. " in anyway. This should only be used on the console to fix \n" \
  40. " damaged systems.\n" \
  41. " -p -- Invoke the shell as a login shell (prefixing argv[0] with a " \
  42. "dash).\n" \
  43. " -t secs -- Only wait the given number of seconds for user input.\n" \
  44. " --help -- Displays this help text and exits.\n" \
  45. " --version -- Displays the application version and exits.\n"
  46. #define SULOGIN_OPTIONS_STRING "ept:HV"
  47. #define SULOGIN_PROMPT \
  48. "Give root password for system maintenance\n" \
  49. "(or type Control-D for normal startup):"
  50. #define SULOGIN_INITIAL_PASSWORD_BUFFER_SIZE 64
  51. //
  52. // Define application options.
  53. //
  54. //
  55. // Set this option to simply log in if the password files appear to be
  56. // damaged or missing.
  57. //
  58. #define SULOGIN_OPTION_EMERGENCY 0x00000001
  59. //
  60. // Set this option to invoke a login shell.
  61. //
  62. #define SULOGIN_OPTION_LOGIN 0x00000002
  63. //
  64. // ------------------------------------------------------ Data Type Definitions
  65. //
  66. //
  67. // ----------------------------------------------- Internal Function Prototypes
  68. //
  69. INT
  70. SuloginGetAndCheckPassword (
  71. struct passwd *User,
  72. PSTR Prompt,
  73. INTN Timeout
  74. );
  75. INT
  76. SuloginGetPassword (
  77. const char *Prompt,
  78. char **String,
  79. size_t *Size,
  80. INTN Timeout
  81. );
  82. void
  83. SuloginSignalHandler (
  84. int Signal
  85. );
  86. //
  87. // -------------------------------------------------------------------- Globals
  88. //
  89. struct option SuloginLongOptions[] = {
  90. {"help", no_argument, 0, 'H'},
  91. {"version", no_argument, 0, 'V'},
  92. {NULL, 0, 0, 0},
  93. };
  94. int *SwSuloginGetPasswordSignals = NULL;
  95. //
  96. // ------------------------------------------------------------------ Functions
  97. //
  98. INT
  99. SuloginMain (
  100. INT ArgumentCount,
  101. CHAR **Arguments
  102. )
  103. /*++
  104. Routine Description:
  105. This routine is the main entry point for the sulogin (single-user login)
  106. utility.
  107. Arguments:
  108. ArgumentCount - Supplies the number of command line arguments the program
  109. was invoked with.
  110. Arguments - Supplies a tokenized array of command line arguments.
  111. Return Value:
  112. Returns an integer exit code. 0 for success, nonzero otherwise.
  113. --*/
  114. {
  115. PSTR AfterScan;
  116. ULONG ArgumentIndex;
  117. uid_t EffectiveId;
  118. int File;
  119. PSTR HashedPassword;
  120. BOOL LoginShell;
  121. INT Option;
  122. ULONG Options;
  123. uid_t RealId;
  124. struct spwd *Shadow;
  125. PSTR Shell;
  126. int Status;
  127. PSTR Terminal;
  128. INTN Timeout;
  129. struct passwd *User;
  130. LoginShell = FALSE;
  131. Options = 0;
  132. Shadow = NULL;
  133. Terminal = NULL;
  134. Timeout = -1;
  135. User = NULL;
  136. //
  137. // Process the control arguments.
  138. //
  139. while (TRUE) {
  140. Option = getopt_long(ArgumentCount,
  141. Arguments,
  142. SULOGIN_OPTIONS_STRING,
  143. SuloginLongOptions,
  144. NULL);
  145. if (Option == -1) {
  146. break;
  147. }
  148. if ((Option == '?') || (Option == ':')) {
  149. Status = 1;
  150. goto MainEnd;
  151. }
  152. switch (Option) {
  153. case 'e':
  154. Options |= SULOGIN_OPTION_EMERGENCY;
  155. break;
  156. case 'p':
  157. Options |= SULOGIN_OPTION_LOGIN;
  158. LoginShell = TRUE;
  159. break;
  160. case 't':
  161. Timeout = strtoul(optarg, &AfterScan, 10);
  162. if (optarg == AfterScan) {
  163. SwPrintError(0, optarg, "Invalid timeout specified");
  164. Timeout = -1;
  165. }
  166. break;
  167. case 'V':
  168. SwPrintVersion(SULOGIN_VERSION_MAJOR, SULOGIN_VERSION_MINOR);
  169. return 1;
  170. case 'H':
  171. printf(SULOGIN_USAGE);
  172. return 1;
  173. default:
  174. assert(FALSE);
  175. Status = 1;
  176. goto MainEnd;
  177. }
  178. }
  179. ArgumentIndex = optind;
  180. if (ArgumentIndex > ArgumentCount) {
  181. ArgumentIndex = ArgumentCount;
  182. }
  183. if (ArgumentIndex != ArgumentCount) {
  184. Terminal = Arguments[ArgumentIndex];
  185. ArgumentIndex += 1;
  186. }
  187. if (ArgumentIndex != ArgumentCount) {
  188. SwPrintError(0,
  189. Arguments[ArgumentIndex],
  190. "Unexpected argument ignored");
  191. }
  192. //
  193. // If a terminal was specified, close the standard descriptors and open
  194. // the terminal.
  195. //
  196. if (Terminal != NULL) {
  197. close(STDIN_FILENO);
  198. close(STDOUT_FILENO);
  199. File = open(Terminal, O_RDWR);
  200. if (File >= 0) {
  201. dup2(File, STDOUT_FILENO);
  202. close(STDERR_FILENO);
  203. dup2(File, STDERR_FILENO);
  204. //
  205. // On failure, try to copy stderr back to stdin and stdout.
  206. //
  207. } else {
  208. dup2(STDERR_FILENO, STDIN_FILENO);
  209. dup2(STDERR_FILENO, STDOUT_FILENO);
  210. }
  211. }
  212. if ((isatty(STDIN_FILENO) == 0) || (isatty(STDOUT_FILENO) == 0) ||
  213. (isatty(STDERR_FILENO) == 0)) {
  214. SwPrintError(0, NULL, "Not a terminal");
  215. Status = 1;
  216. goto MainEnd;
  217. }
  218. RealId = getuid();
  219. EffectiveId = geteuid();
  220. if (RealId != EffectiveId) {
  221. SwSanitizeEnvironment();
  222. }
  223. User = getpwuid(0);
  224. if (User == NULL) {
  225. if ((Options & SULOGIN_OPTION_EMERGENCY) == 0) {
  226. SwPrintError(0, NULL, "Failed to get root account information");
  227. Status = 1;
  228. goto MainEnd;
  229. }
  230. } else {
  231. Shadow = getspnam(User->pw_name);
  232. }
  233. HashedPassword = NULL;
  234. if (Shadow != NULL) {
  235. HashedPassword = Shadow->sp_pwdp;
  236. } else if (User != NULL) {
  237. HashedPassword = User->pw_passwd;
  238. }
  239. if ((HashedPassword == NULL) ||
  240. ((!isalnum(*HashedPassword)) &&
  241. (*HashedPassword != '\0') &&
  242. (*HashedPassword != '.') && (*HashedPassword != '/') &&
  243. (*HashedPassword != '$'))) {
  244. if ((Options & SULOGIN_OPTION_EMERGENCY) == 0) {
  245. SwPrintError(0, NULL, "Root account unavailable");
  246. Status = 1;
  247. goto MainEnd;
  248. }
  249. } else {
  250. while (TRUE) {
  251. Status = SuloginGetAndCheckPassword(User, SULOGIN_PROMPT, Timeout);
  252. if (Status == 0) {
  253. break;
  254. } else if (Status != EPERM) {
  255. SwPrintError(0, NULL, "Normal startup");
  256. Status = 0;
  257. goto MainEnd;
  258. }
  259. sleep(LOGIN_FAIL_DELAY);
  260. SwPrintError(0, NULL, "Incorrect password");
  261. }
  262. }
  263. printf("System maintenance mode\n");
  264. Shell = getenv("SUSHELL");
  265. if (Shell == NULL) {
  266. Shell = getenv("sushell");
  267. }
  268. if ((Shell == NULL) && (User != NULL) && (User->pw_shell != NULL) &&
  269. (User->pw_shell[0] != '\0')) {
  270. Shell = User->pw_shell;
  271. }
  272. SwExecuteShell(Shell, LoginShell, NULL, NULL);
  273. Status = 0;
  274. MainEnd:
  275. return Status;
  276. }
  277. //
  278. // --------------------------------------------------------- Internal Functions
  279. //
  280. INT
  281. SuloginGetAndCheckPassword (
  282. struct passwd *User,
  283. PSTR Prompt,
  284. INTN Timeout
  285. )
  286. /*++
  287. Routine Description:
  288. This routine asks for and validates the password for the given user.
  289. Arguments:
  290. User - Supplies a pointer to the user to get information for.
  291. Prompt - Supplies a pointer to the prompt to use.
  292. Timeout - Supplies the timeout before just continuing. Supply -1 to
  293. indicate no timeout.
  294. Return Value:
  295. 0 if the password matched.
  296. EPERM if the password did not match.
  297. EACCES if the account is locked or expired.
  298. ETIMEDOUT on timeout.
  299. Other error codes on other failures.
  300. --*/
  301. {
  302. BOOL Correct;
  303. PSTR HashedPassword;
  304. PSTR Password;
  305. size_t PasswordSize;
  306. struct spwd *Shadow;
  307. INT Status;
  308. Password = NULL;
  309. PasswordSize = 0;
  310. HashedPassword = User->pw_passwd;
  311. Shadow = getspnam(User->pw_name);
  312. if ((Shadow == NULL) && (errno != ENOENT)) {
  313. SwPrintError(errno,
  314. User->pw_name,
  315. "Error: Could not read password information for user");
  316. return EACCES;
  317. }
  318. if (Shadow != NULL) {
  319. HashedPassword = Shadow->sp_pwdp;
  320. }
  321. //
  322. // If the password is empty just return success, no need to ask really.
  323. //
  324. if (*HashedPassword == '\0') {
  325. return 0;
  326. }
  327. if (Prompt == NULL) {
  328. Prompt = "Enter password:";
  329. }
  330. Status = SuloginGetPassword(Prompt, &Password, &PasswordSize, Timeout);
  331. if (Status != 0) {
  332. goto GetAndCheckPasswordEnd;
  333. }
  334. if (*HashedPassword == '!') {
  335. SwPrintError(0, NULL, "Account locked");
  336. Status = EACCES;
  337. goto GetAndCheckPasswordEnd;
  338. }
  339. Status = EPERM;
  340. Correct = SwCheckPassword(Password, HashedPassword);
  341. if (Correct != FALSE) {
  342. Status = 0;
  343. }
  344. GetAndCheckPasswordEnd:
  345. if (Password != NULL) {
  346. memset(Password, 0, PasswordSize);
  347. free(Password);
  348. }
  349. return Status;
  350. }
  351. INT
  352. SuloginGetPassword (
  353. const char *Prompt,
  354. char **String,
  355. size_t *Size,
  356. INTN Timeout
  357. )
  358. /*++
  359. Routine Description:
  360. This routine reads outputs the given prompt, and reads in a line of input
  361. without echoing it. This routine attempts to use the process' controlling
  362. terminal, or stdin/stderr otherwise. This routine is neither thread-safe
  363. nor reentrant.
  364. Arguments:
  365. Prompt - Supplies a pointer to the prompt to print.
  366. String - Supplies a pointer where the allocated password will be returned.
  367. Size - Supplies a pointer where the size of the allocated password buffer
  368. will be returned on success.
  369. Timeout - Supplies the timeout, or -1 for no timeout.
  370. Return Value:
  371. Returns a pointer to the entered input on success. If this is a password,
  372. the caller should be sure to clear this buffer out as soon as possible.
  373. NULL on failure.
  374. --*/
  375. {
  376. ssize_t BytesRead;
  377. char Character;
  378. int FileIn;
  379. size_t LineSize;
  380. struct sigaction NewAction;
  381. void *NewBuffer;
  382. size_t NewBufferSize;
  383. struct termios NewSettings;
  384. struct termios OriginalSettings;
  385. struct sigaction SaveAlarm;
  386. struct sigaction SaveHup;
  387. struct sigaction SaveInt;
  388. struct sigaction SavePipe;
  389. struct sigaction SaveQuit;
  390. struct sigaction SaveTerm;
  391. struct sigaction SaveTstop;
  392. struct sigaction SaveTtin;
  393. struct sigaction SaveTtou;
  394. int Signal;
  395. int Signals[NSIG];
  396. memset(Signals, 0, sizeof(Signals));
  397. SwSuloginGetPasswordSignals = Signals;
  398. FileIn = STDIN_FILENO;
  399. //
  400. // Turn off echoing.
  401. //
  402. if (tcgetattr(FileIn, &OriginalSettings) != 0) {
  403. return errno;
  404. }
  405. memcpy(&NewSettings, &OriginalSettings, sizeof(struct termios));
  406. NewSettings.c_lflag &= ~ECHO;
  407. if (tcsetattr(FileIn, TCSAFLUSH, &NewSettings) != 0) {
  408. return errno;
  409. }
  410. //
  411. // Handle all signals so that the terminal settings can be put back.
  412. //
  413. sigemptyset(&(NewAction.sa_mask));
  414. NewAction.sa_flags = 0;
  415. NewAction.sa_handler = SuloginSignalHandler;
  416. sigaction(SIGALRM, &NewAction, &SaveAlarm);
  417. sigaction(SIGHUP, &NewAction, &SaveHup);
  418. sigaction(SIGINT, &NewAction, &SaveInt);
  419. sigaction(SIGPIPE, &NewAction, &SavePipe);
  420. sigaction(SIGQUIT, &NewAction, &SaveQuit);
  421. sigaction(SIGTERM, &NewAction, &SaveTerm);
  422. sigaction(SIGTSTP, &NewAction, &SaveTstop);
  423. sigaction(SIGTTIN, &NewAction, &SaveTtin);
  424. sigaction(SIGTTOU, &NewAction, &SaveTtou);
  425. //
  426. // Print the prompt.
  427. //
  428. fprintf(stderr, Prompt);
  429. fflush(stderr);
  430. //
  431. // Set the alarm if requested.
  432. //
  433. if (Timeout > 0) {
  434. alarm(Timeout);
  435. }
  436. //
  437. // Loop reading characters from the input.
  438. //
  439. LineSize = 0;
  440. while (TRUE) {
  441. do {
  442. BytesRead = read(FileIn, &Character, 1);
  443. if (SwSuloginGetPasswordSignals[SIGALRM] > 0) {
  444. BytesRead = 0;
  445. break;
  446. }
  447. } while ((BytesRead < 0) && (errno == EINTR));
  448. if (BytesRead <= 0) {
  449. break;
  450. }
  451. //
  452. // Reset the alarm since something was read.
  453. //
  454. if (Timeout > 0) {
  455. alarm(Timeout);
  456. }
  457. //
  458. // Reallocate the buffer if needed.
  459. //
  460. if (LineSize + 1 >= *Size) {
  461. if (*Size == 0) {
  462. NewBufferSize = SULOGIN_INITIAL_PASSWORD_BUFFER_SIZE;
  463. } else {
  464. NewBufferSize = *Size * 2;
  465. }
  466. NewBuffer = malloc(NewBufferSize);
  467. //
  468. // Whether or not the allocation succeeded, zero out the previous
  469. // buffer to avoid leaking potential passwords.
  470. //
  471. if (*Size != 0) {
  472. if (NewBuffer != NULL) {
  473. memcpy(NewBuffer, *String, *Size);
  474. }
  475. memset(*String, 0, *Size);
  476. free(*String);
  477. *String = NULL;
  478. *Size = 0;
  479. }
  480. if (NewBuffer == NULL) {
  481. LineSize = 0;
  482. BytesRead = -1;
  483. break;
  484. } else {
  485. *String = NewBuffer;
  486. *Size = NewBufferSize;
  487. }
  488. }
  489. if ((Character == '\r') || (Character == '\n')) {
  490. break;
  491. }
  492. //
  493. // Add the character to the buffer.
  494. //
  495. (*String)[LineSize] = Character;
  496. LineSize += 1;
  497. }
  498. if (BytesRead >= 0) {
  499. if ((BytesRead > 0) || (LineSize > 0)) {
  500. assert(LineSize + 1 < *Size);
  501. (*String)[LineSize] = '\0';
  502. LineSize += 1;
  503. } else {
  504. fputc('\n', stderr);
  505. BytesRead = -1;
  506. }
  507. }
  508. //
  509. // Restore the original terminal settings.
  510. //
  511. tcsetattr(FileIn, TCSAFLUSH, &OriginalSettings);
  512. //
  513. // Restore the original signal handlers.
  514. //
  515. sigaction(SIGALRM, &SaveAlarm, NULL);
  516. sigaction(SIGHUP, &SaveHup, NULL);
  517. sigaction(SIGINT, &SaveInt, NULL);
  518. sigaction(SIGPIPE, &SavePipe, NULL);
  519. sigaction(SIGQUIT, &SaveQuit, NULL);
  520. sigaction(SIGTERM, &SaveTerm, NULL);
  521. sigaction(SIGTSTP, &SaveTstop, NULL);
  522. sigaction(SIGTTIN, &SaveTtin, NULL);
  523. sigaction(SIGTTOU, &SaveTtou, NULL);
  524. //
  525. // Replay any signals that were sent during the read.
  526. //
  527. for (Signal = 0; Signal < NSIG; Signal += 1) {
  528. if (Signal == SIGALRM) {
  529. continue;
  530. }
  531. while (SwSuloginGetPasswordSignals[Signal] != 0) {
  532. kill(getpid(), Signal);
  533. SwSuloginGetPasswordSignals[Signal] -= 1;
  534. }
  535. }
  536. SwSuloginGetPasswordSignals = NULL;
  537. if (BytesRead < 0) {
  538. return -1;
  539. }
  540. return 0;
  541. }
  542. void
  543. SuloginSignalHandler (
  544. int Signal
  545. )
  546. /*++
  547. Routine Description:
  548. This routine is the signal handler installed during the get password
  549. function. It simply tracks signals for replay later.
  550. Arguments:
  551. Signal - Supplies the signal number that fired.
  552. Return Value:
  553. None.
  554. --*/
  555. {
  556. assert((SwSuloginGetPasswordSignals != NULL) && (Signal < NSIG));
  557. SwSuloginGetPasswordSignals[Signal] += 1;
  558. return;
  559. }