login.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. /*++
  2. Copyright (c) 2015 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. login.c
  5. Abstract:
  6. This module implements the login utility, which reads a user's password and
  7. sets up the login.
  8. Author:
  9. Evan Green 16-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 <syslog.h>
  26. #include <termios.h>
  27. #include <unistd.h>
  28. #include <utmpx.h>
  29. #include "../swlib.h"
  30. #include "lutil.h"
  31. //
  32. // ---------------------------------------------------------------- Definitions
  33. //
  34. #define LOGIN_VERSION_MAJOR 1
  35. #define LOGIN_VERSION_MINOR 0
  36. #define LOGIN_USAGE \
  37. "usage: login [options] [username] [ENV=var]\n" \
  38. "The login utility authenticates a user and establishes a new session. \n" \
  39. "Options are:\n" \
  40. " -f -- Do not perform authentication, user is preauthenticated." \
  41. " -h host -- Name of the remote host for this login.\n" \
  42. " -p -- Preserve the environment.\n" \
  43. " --help -- Displays this help text and exits.\n" \
  44. " --version -- Displays the application version and exits.\n"
  45. #define LOGIN_OPTIONS_STRING "fh:pHV"
  46. //
  47. // Define the number of seconds before login times out.
  48. //
  49. #define LOGIN_TIMEOUT 60
  50. #define LOGIN_ATTEMPT_COUNT 3
  51. #define LOGIN_MAX_EMPTY_USER_NAME_TRIES 5
  52. #define LOGIN_SECURE_TERMINALS_PATH "/etc/securetty"
  53. #define LOGIN_NOLOGIN_PATH "/etc/nologin"
  54. #define LOGIN_MOTD_PATH "/etc/motd"
  55. //
  56. // Define application options.
  57. //
  58. //
  59. // Set this option to skip authentication.
  60. //
  61. #define LOGIN_OPTION_NO_AUTHENTICATION 0x00000001
  62. //
  63. // Set this option to preserve the environment.
  64. //
  65. #define LOGIN_OPTION_PRESERVE_ENVIRONMENT 0x00000002
  66. //
  67. // ------------------------------------------------------ Data Type Definitions
  68. //
  69. //
  70. // ----------------------------------------------- Internal Function Prototypes
  71. //
  72. INT
  73. LoginCheckSecureTerminal (
  74. PSTR Terminal
  75. );
  76. INT
  77. LoginCheckNologin (
  78. VOID
  79. );
  80. VOID
  81. LoginPrintMessageOfTheDay (
  82. VOID
  83. );
  84. void
  85. LoginAlarmSignalHandler (
  86. int Signal
  87. );
  88. //
  89. // -------------------------------------------------------------------- Globals
  90. //
  91. struct option LoginLongOptions[] = {
  92. {"help", no_argument, 0, 'H'},
  93. {"version", no_argument, 0, 'V'},
  94. {NULL, 0, 0, 0},
  95. };
  96. struct termios *SwLoginTerminalSettings = NULL;
  97. volatile BOOL SwLoginTimeout;
  98. //
  99. // ------------------------------------------------------------------ Functions
  100. //
  101. INT
  102. LoginMain (
  103. INT ArgumentCount,
  104. CHAR **Arguments
  105. )
  106. /*++
  107. Routine Description:
  108. This routine is the main entry point for the login utility.
  109. Arguments:
  110. ArgumentCount - Supplies the number of command line arguments the program
  111. was invoked with.
  112. Arguments - Supplies a tokenized array of command line arguments.
  113. Return Value:
  114. Returns an integer exit code. 0 for success, nonzero otherwise.
  115. --*/
  116. {
  117. PSTR Argument;
  118. ULONG ArgumentIndex;
  119. ULONG Attempt;
  120. uid_t EffectiveUserId;
  121. ULONG EmptyUserNameCount;
  122. BOOL Failed;
  123. PSTR Host;
  124. PSTR Line;
  125. size_t LineBufferSize;
  126. ssize_t LineSize;
  127. INT Option;
  128. ULONG Options;
  129. ULONG SetupFlags;
  130. int Status;
  131. struct termios TerminalSettings;
  132. PSTR TtyName;
  133. struct passwd *User;
  134. uid_t UserId;
  135. PSTR UserName;
  136. Attempt = 0;
  137. EmptyUserNameCount = 0;
  138. Host = NULL;
  139. Line = NULL;
  140. LineBufferSize = 0;
  141. Options = 0;
  142. TtyName = NULL;
  143. UserName = NULL;
  144. //
  145. // Process the control arguments.
  146. //
  147. while (TRUE) {
  148. Option = getopt_long(ArgumentCount,
  149. Arguments,
  150. LOGIN_OPTIONS_STRING,
  151. LoginLongOptions,
  152. NULL);
  153. if (Option == -1) {
  154. break;
  155. }
  156. if ((Option == '?') || (Option == ':')) {
  157. Status = 1;
  158. goto MainEnd;
  159. }
  160. switch (Option) {
  161. case 'f':
  162. Options |= LOGIN_OPTION_NO_AUTHENTICATION;
  163. break;
  164. case 'p':
  165. Options |= LOGIN_OPTION_PRESERVE_ENVIRONMENT;
  166. break;
  167. case 'h':
  168. Host = optarg;
  169. break;
  170. case 'V':
  171. SwPrintVersion(LOGIN_VERSION_MAJOR, LOGIN_VERSION_MINOR);
  172. return 1;
  173. case 'H':
  174. printf(LOGIN_USAGE);
  175. return 1;
  176. default:
  177. assert(FALSE);
  178. Status = 1;
  179. goto MainEnd;
  180. }
  181. }
  182. ArgumentIndex = optind;
  183. if (ArgumentIndex > ArgumentCount) {
  184. ArgumentIndex = ArgumentCount;
  185. }
  186. EffectiveUserId = geteuid();
  187. UserId = getuid();
  188. if (EffectiveUserId != UserId) {
  189. SwSanitizeEnvironment();
  190. }
  191. //
  192. // Only root can skip authentication and set a host.
  193. //
  194. if (EffectiveUserId != 0) {
  195. Options &= ~LOGIN_OPTION_NO_AUTHENTICATION;
  196. Host = NULL;
  197. }
  198. //
  199. // Grab the username argument if it's there.
  200. //
  201. if (ArgumentIndex < ArgumentCount) {
  202. UserName = Arguments[ArgumentIndex];
  203. ArgumentIndex += 1;
  204. }
  205. if ((UserName == NULL) &&
  206. ((Options & LOGIN_OPTION_NO_AUTHENTICATION) != 0)) {
  207. SwPrintError(0, NULL, "Username required with -f");
  208. Status = 1;
  209. goto MainEnd;
  210. }
  211. //
  212. // Set environment values for the remainder of the arguments.
  213. //
  214. while (ArgumentIndex < ArgumentCount) {
  215. Argument = Arguments[ArgumentIndex];
  216. if (strchr(Argument, '=') == NULL) {
  217. SwPrintError(0, Argument, "Unexpected argument");
  218. Status = 1;
  219. goto MainEnd;
  220. }
  221. putenv(Argument);
  222. }
  223. if ((tcgetattr(STDIN_FILENO, &TerminalSettings) < 0) ||
  224. (isatty(STDOUT_FILENO) == 0)) {
  225. SwPrintError(0, NULL, "Not a terminal");
  226. Status = 1;
  227. goto MainEnd;
  228. }
  229. SwLoginTerminalSettings = &TerminalSettings;
  230. SwLoginTimeout = FALSE;
  231. signal(SIGINT, SIG_DFL);
  232. signal(SIGALRM, LoginAlarmSignalHandler);
  233. alarm(LOGIN_TIMEOUT);
  234. //
  235. // Get the current terminal name.
  236. //
  237. TtyName = ttyname(STDIN_FILENO);
  238. if (TtyName == NULL) {
  239. TtyName = "(unknown terminal)";
  240. }
  241. TtyName = strdup(TtyName);
  242. openlog("login", LOG_PID, LOG_AUTH);
  243. while (TRUE) {
  244. if (SwLoginTimeout != FALSE) {
  245. Status = 1;
  246. goto MainEnd;
  247. }
  248. //
  249. // Get rid of anything built up.
  250. //
  251. tcflush(0, TCIFLUSH);
  252. //
  253. // Read in the username if not already supplied.
  254. //
  255. if (UserName == NULL) {
  256. SwPrintLoginPrompt();
  257. LineSize = getline(&Line, &LineBufferSize, stdin);
  258. if (LineSize <= 0) {
  259. Status = 1;
  260. goto MainEnd;
  261. }
  262. while ((LineSize != 0) && (isspace(Line[LineSize - 1]))) {
  263. LineSize -= 1;
  264. }
  265. UserName = Line;
  266. UserName[LineSize] = '\0';
  267. //
  268. // Get past whitespace.
  269. //
  270. while (isspace(*UserName)) {
  271. UserName += 1;
  272. }
  273. if (*UserName == '\0') {
  274. EmptyUserNameCount += 1;
  275. if (EmptyUserNameCount >= LOGIN_MAX_EMPTY_USER_NAME_TRIES) {
  276. Status = 1;
  277. goto MainEnd;
  278. }
  279. UserName = NULL;
  280. continue;
  281. }
  282. }
  283. Failed = FALSE;
  284. User = getpwnam(UserName);
  285. if (User != NULL) {
  286. if ((Options & LOGIN_OPTION_NO_AUTHENTICATION) != 0) {
  287. break;
  288. }
  289. //
  290. // Root login may be restricted to a few terminals.
  291. //
  292. if ((User->pw_uid == 0) &&
  293. (LoginCheckSecureTerminal(TtyName) != 0)) {
  294. Failed = TRUE;
  295. }
  296. }
  297. if (Failed != FALSE) {
  298. Status = EPERM;
  299. } else {
  300. Status = SwGetAndCheckPassword(User, NULL);
  301. }
  302. if (Status == 0) {
  303. break;
  304. }
  305. //
  306. // Handle an authentication failure.
  307. //
  308. sleep(LOGIN_FAIL_DELAY);
  309. printf("Login incorrect\n");
  310. Attempt += 1;
  311. if (Attempt >= LOGIN_ATTEMPT_COUNT) {
  312. if (Host != NULL) {
  313. syslog(LOG_WARNING,
  314. "invalid password for %s on %s from %s",
  315. UserName,
  316. TtyName,
  317. Host);
  318. } else {
  319. syslog(LOG_WARNING,
  320. "invalid password for %s on %s",
  321. UserName,
  322. TtyName);
  323. }
  324. syslog(LOG_WARNING,
  325. "Authentication failure: uid=%ld, euid=%ld, tty=%s "
  326. "user=%s rhost=%s",
  327. (long int)UserId,
  328. (long int)EffectiveUserId,
  329. TtyName,
  330. UserName,
  331. Host);
  332. SwPrintError(0,
  333. NULL,
  334. "Maximum number of tries exceeded (%d)",
  335. LOGIN_ATTEMPT_COUNT);
  336. Status = 1;
  337. goto MainEnd;
  338. }
  339. }
  340. //
  341. // Authentication was successful.
  342. //
  343. alarm(0);
  344. if (User->pw_uid != 0) {
  345. if (LoginCheckNologin() != 0) {
  346. Status = 1;
  347. goto MainEnd;
  348. }
  349. }
  350. Status = fchown(STDIN_FILENO, User->pw_uid, User->pw_gid);
  351. if (Status != 0) {
  352. Status = errno;
  353. goto MainEnd;
  354. }
  355. fchmod(STDIN_FILENO, S_IRUSR | S_IWUSR);
  356. SwUpdateUtmp(getpid(), USER_PROCESS, TtyName, UserName, Host);
  357. SwBecomeUser(User);
  358. SetupFlags = SETUP_USER_ENVIRONMENT_CHANGE_ENVIRONMENT;
  359. if ((Options & LOGIN_OPTION_PRESERVE_ENVIRONMENT) == 0) {
  360. SetupFlags |= SETUP_USER_ENVIRONMENT_CLEAR_ENVIRONMENT;
  361. }
  362. SwSetupUserEnvironment(User, User->pw_shell, SetupFlags);
  363. LoginPrintMessageOfTheDay();
  364. if (Host != NULL) {
  365. syslog(LOG_INFO, "login as %s on %s from %s", UserName, TtyName, Host);
  366. if (User->pw_uid == 0) {
  367. syslog(LOG_INFO, "root login %s from %s", TtyName, Host);
  368. }
  369. } else {
  370. syslog(LOG_INFO, "login as %s on %s", UserName, TtyName);
  371. if (User->pw_uid == 0) {
  372. syslog(LOG_INFO, "root login on %s", TtyName);
  373. }
  374. }
  375. signal(SIGINT, SIG_DFL);
  376. closelog();
  377. SwCloseFrom(STDERR_FILENO + 1);
  378. SwExecuteShell(User->pw_shell, TRUE, NULL, NULL);
  379. Status = 1;
  380. MainEnd:
  381. closelog();
  382. if (Line != NULL) {
  383. free(Line);
  384. }
  385. SwLoginTerminalSettings = NULL;
  386. return Status;
  387. }
  388. //
  389. // --------------------------------------------------------- Internal Functions
  390. //
  391. INT
  392. LoginCheckSecureTerminal (
  393. PSTR Terminal
  394. )
  395. /*++
  396. Routine Description:
  397. This routine checks the secure terminals list. If the list exists
  398. (/etc/securetty) and this terminal is not on it, then root cannot log in.
  399. Arguments:
  400. Terminal - Supplies a pointer to the name of this terminal.
  401. Return Value:
  402. Returns an integer exit code. 0 for success, nonzero otherwise.
  403. --*/
  404. {
  405. PSTR Line;
  406. size_t LineBufferSize;
  407. ssize_t LineSize;
  408. PSTR SecureTerminal;
  409. FILE *SecureTerminals;
  410. INT Status;
  411. Line = NULL;
  412. LineBufferSize = 0;
  413. //
  414. // Open the file. If it does not exist, root can log in anywhere.
  415. //
  416. SecureTerminals = fopen(LOGIN_SECURE_TERMINALS_PATH, "r");
  417. if (SecureTerminals == NULL) {
  418. if (errno != ENOENT) {
  419. return EPERM;
  420. }
  421. return 0;
  422. }
  423. Status = EPERM;
  424. while (TRUE) {
  425. LineSize = getline(&Line, &LineBufferSize, stdin);
  426. if (LineSize < 0) {
  427. break;
  428. }
  429. while ((LineSize != 0) && (isspace(Line[LineSize - 1]))) {
  430. LineSize -= 1;
  431. }
  432. SecureTerminal = Line;
  433. SecureTerminal[LineSize] = '\0';
  434. //
  435. // Get past whitespace.
  436. //
  437. while (isspace(*SecureTerminal)) {
  438. SecureTerminal += 1;
  439. }
  440. //
  441. // Skip any commented lines.
  442. //
  443. if ((*SecureTerminal == '\0') || (*SecureTerminal == '#')) {
  444. continue;
  445. }
  446. if (strcmp(SecureTerminal, Terminal) == 0) {
  447. Status = 0;
  448. break;
  449. }
  450. }
  451. if (Line != NULL) {
  452. free(Line);
  453. }
  454. fclose(SecureTerminals);
  455. return Status;
  456. }
  457. INT
  458. LoginCheckNologin (
  459. VOID
  460. )
  461. /*++
  462. Routine Description:
  463. This routine checks /etc/nologin, and prevents logging anyone but root in
  464. if the file exists.
  465. Arguments:
  466. None.
  467. Return Value:
  468. 0 if login should proceed.
  469. Non-zero if login is prevented.
  470. --*/
  471. {
  472. INT Character;
  473. BOOL Empty;
  474. FILE *Nologin;
  475. Nologin = fopen(LOGIN_NOLOGIN_PATH, "r");
  476. if (Nologin == NULL) {
  477. return 0;
  478. }
  479. //
  480. // Spit out the contents of the nologin file.
  481. //
  482. Empty = TRUE;
  483. while (TRUE) {
  484. Character = fgetc(Nologin);
  485. if (Character == EOF) {
  486. break;
  487. }
  488. if (Character == '\n') {
  489. fputc('\r', stdout);
  490. }
  491. fputc(Character, stdout);
  492. Empty = FALSE;
  493. }
  494. if (Empty != FALSE) {
  495. printf("\r\nSystem temporarily closed.\r\n");
  496. }
  497. fclose(Nologin);
  498. fflush(NULL);
  499. tcdrain(STDOUT_FILENO);
  500. return 1;
  501. }
  502. VOID
  503. LoginPrintMessageOfTheDay (
  504. VOID
  505. )
  506. /*++
  507. Routine Description:
  508. This routine is the timeout alarm signal handler.
  509. Arguments:
  510. Signal - Supplies the signal number that fired.
  511. Return Value:
  512. None.
  513. --*/
  514. {
  515. int Character;
  516. FILE *File;
  517. File = fopen(LOGIN_MOTD_PATH, "r");
  518. if (File == NULL) {
  519. return;
  520. }
  521. while (TRUE) {
  522. Character = fgetc(File);
  523. if (Character == EOF) {
  524. break;
  525. }
  526. fputc(Character, stdout);
  527. }
  528. fclose(File);
  529. return;
  530. }
  531. void
  532. LoginAlarmSignalHandler (
  533. int Signal
  534. )
  535. /*++
  536. Routine Description:
  537. This routine is the timeout alarm signal handler.
  538. Arguments:
  539. Signal - Supplies the signal number that fired.
  540. Return Value:
  541. None.
  542. --*/
  543. {
  544. int Flags;
  545. Flags = fcntl(STDOUT_FILENO, F_GETFL);
  546. fcntl(STDOUT_FILENO, F_SETFL, Flags | O_NONBLOCK);
  547. tcsetattr(STDOUT_FILENO, TCSANOW, SwLoginTerminalSettings);
  548. printf("\r\nLogin timed out after %u seconds.\r\n", LOGIN_TIMEOUT);
  549. fflush(NULL);
  550. fcntl(STDOUT_FILENO, F_SETFL, Flags);
  551. _Exit(1);
  552. return;
  553. }