login.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
  4. */
  5. #include "libbb.h"
  6. #include <syslog.h>
  7. #include <utmp.h>
  8. #include <sys/resource.h>
  9. #if ENABLE_SELINUX
  10. #include <selinux/selinux.h> /* for is_selinux_enabled() */
  11. #include <selinux/get_context_list.h> /* for get_default_context() */
  12. #include <selinux/flask.h> /* for security class definitions */
  13. #endif
  14. #if ENABLE_PAM
  15. /* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
  16. #undef setlocale
  17. /* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
  18. * Apparently they like to confuse people. */
  19. #include <security/pam_appl.h>
  20. #include <security/pam_misc.h>
  21. static const struct pam_conv conv = {
  22. misc_conv,
  23. NULL
  24. };
  25. #endif
  26. enum {
  27. TIMEOUT = 60,
  28. EMPTY_USERNAME_COUNT = 10,
  29. USERNAME_SIZE = 32,
  30. TTYNAME_SIZE = 32,
  31. };
  32. static char* short_tty;
  33. #if ENABLE_FEATURE_UTMP
  34. /* vv Taken from tinylogin utmp.c vv */
  35. /*
  36. * read_or_build_utent - see if utmp file is correct for this process
  37. *
  38. * System V is very picky about the contents of the utmp file
  39. * and requires that a slot for the current process exist.
  40. * The utmp file is scanned for an entry with the same process
  41. * ID. If no entry exists the process exits with a message.
  42. *
  43. * The "picky" flag is for network and other logins that may
  44. * use special flags. It allows the pid checks to be overridden.
  45. * This means that getty should never invoke login with any
  46. * command line flags.
  47. */
  48. static void read_or_build_utent(struct utmp *utptr, int picky)
  49. {
  50. struct utmp *ut;
  51. pid_t pid = getpid();
  52. setutent();
  53. /* First, try to find a valid utmp entry for this process. */
  54. while ((ut = getutent()))
  55. if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] &&
  56. (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS))
  57. break;
  58. /* If there is one, just use it, otherwise create a new one. */
  59. if (ut) {
  60. *utptr = *ut;
  61. } else {
  62. if (picky)
  63. bb_error_msg_and_die("no utmp entry found");
  64. memset(utptr, 0, sizeof(*utptr));
  65. utptr->ut_type = LOGIN_PROCESS;
  66. utptr->ut_pid = pid;
  67. strncpy(utptr->ut_line, short_tty, sizeof(utptr->ut_line));
  68. /* This one is only 4 chars wide. Try to fit something
  69. * remotely meaningful by skipping "tty"... */
  70. strncpy(utptr->ut_id, short_tty + 3, sizeof(utptr->ut_id));
  71. strncpy(utptr->ut_user, "LOGIN", sizeof(utptr->ut_user));
  72. utptr->ut_time = time(NULL);
  73. }
  74. if (!picky) /* root login */
  75. memset(utptr->ut_host, 0, sizeof(utptr->ut_host));
  76. }
  77. /*
  78. * write_utent - put a USER_PROCESS entry in the utmp file
  79. *
  80. * write_utent changes the type of the current utmp entry to
  81. * USER_PROCESS. the wtmp file will be updated as well.
  82. */
  83. static void write_utent(struct utmp *utptr, const char *username)
  84. {
  85. utptr->ut_type = USER_PROCESS;
  86. strncpy(utptr->ut_user, username, sizeof(utptr->ut_user));
  87. utptr->ut_time = time(NULL);
  88. /* other fields already filled in by read_or_build_utent above */
  89. setutent();
  90. pututline(utptr);
  91. endutent();
  92. #if ENABLE_FEATURE_WTMP
  93. if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) {
  94. close(creat(bb_path_wtmp_file, 0664));
  95. }
  96. updwtmp(bb_path_wtmp_file, utptr);
  97. #endif
  98. }
  99. #else /* !ENABLE_FEATURE_UTMP */
  100. #define read_or_build_utent(utptr, picky) ((void)0)
  101. #define write_utent(utptr, username) ((void)0)
  102. #endif /* !ENABLE_FEATURE_UTMP */
  103. #if ENABLE_FEATURE_NOLOGIN
  104. static void die_if_nologin(void)
  105. {
  106. FILE *fp;
  107. int c;
  108. if (access("/etc/nologin", F_OK))
  109. return;
  110. fp = fopen("/etc/nologin", "r");
  111. if (fp) {
  112. while ((c = getc(fp)) != EOF)
  113. bb_putchar((c=='\n') ? '\r' : c);
  114. fflush(stdout);
  115. fclose(fp);
  116. } else
  117. puts("\r\nSystem closed for routine maintenance\r");
  118. exit(1);
  119. }
  120. #else
  121. static ALWAYS_INLINE void die_if_nologin(void) {}
  122. #endif
  123. #if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
  124. static int check_securetty(void)
  125. {
  126. FILE *fp;
  127. int i;
  128. char buf[256];
  129. fp = fopen("/etc/securetty", "r");
  130. if (!fp) {
  131. /* A missing securetty file is not an error. */
  132. return 1;
  133. }
  134. while (fgets(buf, sizeof(buf)-1, fp)) {
  135. for (i = strlen(buf)-1; i >= 0; --i) {
  136. if (!isspace(buf[i]))
  137. break;
  138. }
  139. buf[++i] = '\0';
  140. if (!buf[0] || (buf[0] == '#'))
  141. continue;
  142. if (strcmp(buf, short_tty) == 0) {
  143. fclose(fp);
  144. return 1;
  145. }
  146. }
  147. fclose(fp);
  148. return 0;
  149. }
  150. #else
  151. static ALWAYS_INLINE int check_securetty(void) { return 1; }
  152. #endif
  153. static void get_username_or_die(char *buf, int size_buf)
  154. {
  155. int c, cntdown;
  156. cntdown = EMPTY_USERNAME_COUNT;
  157. prompt:
  158. print_login_prompt();
  159. /* skip whitespace */
  160. do {
  161. c = getchar();
  162. if (c == EOF) exit(1);
  163. if (c == '\n') {
  164. if (!--cntdown) exit(1);
  165. goto prompt;
  166. }
  167. } while (isspace(c));
  168. *buf++ = c;
  169. if (!fgets(buf, size_buf-2, stdin))
  170. exit(1);
  171. if (!strchr(buf, '\n'))
  172. exit(1);
  173. while (isgraph(*buf)) buf++;
  174. *buf = '\0';
  175. }
  176. static void motd(void)
  177. {
  178. int fd;
  179. fd = open(bb_path_motd_file, O_RDONLY);
  180. if (fd >= 0) {
  181. fflush(stdout);
  182. bb_copyfd_eof(fd, STDOUT_FILENO);
  183. close(fd);
  184. }
  185. }
  186. static void alarm_handler(int sig ATTRIBUTE_UNUSED)
  187. {
  188. /* This is the escape hatch! Poor serial line users and the like
  189. * arrive here when their connection is broken.
  190. * We don't want to block here */
  191. ndelay_on(1);
  192. printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT);
  193. fflush(stdout);
  194. /* unix API is brain damaged regarding O_NONBLOCK,
  195. * we should undo it, or else we can affect other processes */
  196. ndelay_off(1);
  197. _exit(EXIT_SUCCESS);
  198. }
  199. int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  200. int login_main(int argc ATTRIBUTE_UNUSED, char **argv)
  201. {
  202. enum {
  203. LOGIN_OPT_f = (1<<0),
  204. LOGIN_OPT_h = (1<<1),
  205. LOGIN_OPT_p = (1<<2),
  206. };
  207. char *fromhost;
  208. char username[USERNAME_SIZE];
  209. const char *tmp;
  210. int amroot;
  211. unsigned opt;
  212. int count = 0;
  213. struct passwd *pw;
  214. char *opt_host = opt_host; /* for compiler */
  215. char *opt_user = opt_user; /* for compiler */
  216. char full_tty[TTYNAME_SIZE];
  217. USE_SELINUX(security_context_t user_sid = NULL;)
  218. USE_FEATURE_UTMP(struct utmp utent;)
  219. #if ENABLE_PAM
  220. int pamret;
  221. pam_handle_t *pamh;
  222. const char *pamuser;
  223. const char *failed_msg;
  224. struct passwd pwdstruct;
  225. char pwdbuf[256];
  226. #endif
  227. short_tty = full_tty;
  228. username[0] = '\0';
  229. signal(SIGALRM, alarm_handler);
  230. alarm(TIMEOUT);
  231. /* More of suid paranoia if called by non-root */
  232. amroot = !sanitize_env_if_suid(); /* Clear dangerous stuff, set PATH */
  233. /* Mandatory paranoia for suid applet:
  234. * ensure that fd# 0,1,2 are opened (at least to /dev/null)
  235. * and any extra open fd's are closed.
  236. * (The name of the function is misleading. Not daemonizing here.) */
  237. bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL);
  238. opt = getopt32(argv, "f:h:p", &opt_user, &opt_host);
  239. if (opt & LOGIN_OPT_f) {
  240. if (!amroot)
  241. bb_error_msg_and_die("-f is for root only");
  242. safe_strncpy(username, opt_user, sizeof(username));
  243. }
  244. argv += optind;
  245. if (argv[0]) /* user from command line (getty) */
  246. safe_strncpy(username, argv[0], sizeof(username));
  247. /* Let's find out and memorize our tty */
  248. if (!isatty(0) || !isatty(1) || !isatty(2))
  249. return EXIT_FAILURE; /* Must be a terminal */
  250. safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty));
  251. tmp = ttyname(0);
  252. if (tmp) {
  253. safe_strncpy(full_tty, tmp, sizeof(full_tty));
  254. if (strncmp(full_tty, "/dev/", 5) == 0)
  255. short_tty = full_tty + 5;
  256. }
  257. read_or_build_utent(&utent, !amroot);
  258. if (opt & LOGIN_OPT_h) {
  259. USE_FEATURE_UTMP(
  260. safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host));
  261. )
  262. fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host);
  263. } else
  264. fromhost = xasprintf(" on '%s'", short_tty);
  265. /* Was breaking "login <username>" from shell command line: */
  266. /*bb_setpgrp();*/
  267. openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
  268. while (1) {
  269. /* flush away any type-ahead (as getty does) */
  270. ioctl(0, TCFLSH, TCIFLUSH);
  271. if (!username[0])
  272. get_username_or_die(username, sizeof(username));
  273. #if ENABLE_PAM
  274. pamret = pam_start("login", username, &conv, &pamh);
  275. if (pamret != PAM_SUCCESS) {
  276. failed_msg = "start";
  277. goto pam_auth_failed;
  278. }
  279. /* set TTY (so things like securetty work) */
  280. pamret = pam_set_item(pamh, PAM_TTY, short_tty);
  281. if (pamret != PAM_SUCCESS) {
  282. failed_msg = "set_item(TTY)";
  283. goto pam_auth_failed;
  284. }
  285. pamret = pam_authenticate(pamh, 0);
  286. if (pamret != PAM_SUCCESS) {
  287. failed_msg = "authenticate";
  288. goto pam_auth_failed;
  289. /* TODO: or just "goto auth_failed"
  290. * since user seems to enter wrong password
  291. * (in this case pamret == 7)
  292. */
  293. }
  294. /* check that the account is healthy */
  295. pamret = pam_acct_mgmt(pamh, 0);
  296. if (pamret != PAM_SUCCESS) {
  297. failed_msg = "acct_mgmt";
  298. goto pam_auth_failed;
  299. }
  300. /* read user back */
  301. pamuser = NULL;
  302. /* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
  303. * thus we cast to (void*) */
  304. if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) {
  305. failed_msg = "get_item(USER)";
  306. goto pam_auth_failed;
  307. }
  308. if (!pamuser || !pamuser[0])
  309. goto auth_failed;
  310. safe_strncpy(username, pamuser, sizeof(username));
  311. /* Don't use "pw = getpwnam(username);",
  312. * PAM is said to be capable of destroying static storage
  313. * used by getpwnam(). We are using safe(r) function */
  314. pw = NULL;
  315. getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw);
  316. if (!pw)
  317. goto auth_failed;
  318. pamret = pam_open_session(pamh, 0);
  319. if (pamret != PAM_SUCCESS) {
  320. failed_msg = "open_session";
  321. goto pam_auth_failed;
  322. }
  323. pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
  324. if (pamret != PAM_SUCCESS) {
  325. failed_msg = "setcred";
  326. goto pam_auth_failed;
  327. }
  328. break; /* success, continue login process */
  329. pam_auth_failed:
  330. bb_error_msg("pam_%s call failed: %s (%d)", failed_msg,
  331. pam_strerror(pamh, pamret), pamret);
  332. safe_strncpy(username, "UNKNOWN", sizeof(username));
  333. #else /* not PAM */
  334. pw = getpwnam(username);
  335. if (!pw) {
  336. strcpy(username, "UNKNOWN");
  337. goto fake_it;
  338. }
  339. if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')
  340. goto auth_failed;
  341. if (opt & LOGIN_OPT_f)
  342. break; /* -f USER: success without asking passwd */
  343. if (pw->pw_uid == 0 && !check_securetty())
  344. goto auth_failed;
  345. /* Don't check the password if password entry is empty (!) */
  346. if (!pw->pw_passwd[0])
  347. break;
  348. fake_it:
  349. /* authorization takes place here */
  350. if (correct_password(pw))
  351. break;
  352. #endif /* ENABLE_PAM */
  353. auth_failed:
  354. opt &= ~LOGIN_OPT_f;
  355. bb_do_delay(FAIL_DELAY);
  356. /* TODO: doesn't sound like correct English phrase to me */
  357. puts("Login incorrect");
  358. if (++count == 3) {
  359. syslog(LOG_WARNING, "invalid password for '%s'%s",
  360. username, fromhost);
  361. return EXIT_FAILURE;
  362. }
  363. username[0] = '\0';
  364. }
  365. alarm(0);
  366. if (!amroot)
  367. die_if_nologin();
  368. write_utent(&utent, username);
  369. #if ENABLE_SELINUX
  370. if (is_selinux_enabled()) {
  371. security_context_t old_tty_sid, new_tty_sid;
  372. if (get_default_context(username, NULL, &user_sid)) {
  373. bb_error_msg_and_die("cannot get SID for %s",
  374. username);
  375. }
  376. if (getfilecon(full_tty, &old_tty_sid) < 0) {
  377. bb_perror_msg_and_die("getfilecon(%s) failed",
  378. full_tty);
  379. }
  380. if (security_compute_relabel(user_sid, old_tty_sid,
  381. SECCLASS_CHR_FILE, &new_tty_sid) != 0) {
  382. bb_perror_msg_and_die("security_change_sid(%s) failed",
  383. full_tty);
  384. }
  385. if (setfilecon(full_tty, new_tty_sid) != 0) {
  386. bb_perror_msg_and_die("chsid(%s, %s) failed",
  387. full_tty, new_tty_sid);
  388. }
  389. }
  390. #endif
  391. /* Try these, but don't complain if they fail.
  392. * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */
  393. fchown(0, pw->pw_uid, pw->pw_gid);
  394. fchmod(0, 0600);
  395. /* We trust environment only if we run by root */
  396. if (ENABLE_LOGIN_SCRIPTS && amroot) {
  397. char *t_argv[2];
  398. t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT");
  399. if (t_argv[0]) {
  400. t_argv[1] = NULL;
  401. xsetenv("LOGIN_TTY", full_tty);
  402. xsetenv("LOGIN_USER", pw->pw_name);
  403. xsetenv("LOGIN_UID", utoa(pw->pw_uid));
  404. xsetenv("LOGIN_GID", utoa(pw->pw_gid));
  405. xsetenv("LOGIN_SHELL", pw->pw_shell);
  406. spawn_and_wait(t_argv); /* NOMMU-friendly */
  407. unsetenv("LOGIN_TTY" );
  408. unsetenv("LOGIN_USER" );
  409. unsetenv("LOGIN_UID" );
  410. unsetenv("LOGIN_GID" );
  411. unsetenv("LOGIN_SHELL");
  412. }
  413. }
  414. change_identity(pw);
  415. tmp = pw->pw_shell;
  416. if (!tmp || !*tmp)
  417. tmp = DEFAULT_SHELL;
  418. /* setup_environment params: shell, clear_env, change_env, pw */
  419. setup_environment(tmp, !(opt & LOGIN_OPT_p), 1, pw);
  420. motd();
  421. if (pw->pw_uid == 0)
  422. syslog(LOG_INFO, "root login%s", fromhost);
  423. #if ENABLE_SELINUX
  424. /* well, a simple setexeccon() here would do the job as well,
  425. * but let's play the game for now */
  426. set_current_security_context(user_sid);
  427. #endif
  428. // util-linux login also does:
  429. // /* start new session */
  430. // setsid();
  431. // /* TIOCSCTTY: steal tty from other process group */
  432. // if (ioctl(0, TIOCSCTTY, 1)) error_msg...
  433. // BBox login used to do this (see above):
  434. // bb_setpgrp();
  435. // If this stuff is really needed, add it and explain why!
  436. /* set signals to defaults */
  437. signal(SIGALRM, SIG_DFL);
  438. /* Is this correct? This way user can ctrl-c out of /etc/profile,
  439. * potentially creating security breach (tested with bash 3.0).
  440. * But without this, bash 3.0 will not enable ctrl-c either.
  441. * Maybe bash is buggy?
  442. * Need to find out what standards say about /bin/login -
  443. * should it leave SIGINT etc enabled or disabled? */
  444. signal(SIGINT, SIG_DFL);
  445. /* Exec login shell with no additional parameters */
  446. run_shell(tmp, 1, NULL, NULL);
  447. /* return EXIT_FAILURE; - not reached */
  448. }