source: MondoRescue/branches/3.3/mindi-busybox/loginutils/login.c@ 3803

Last change on this file since 3803 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 17.2 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
4 */
5//config:config LOGIN
6//config: bool "login"
7//config: default y
8//config: select FEATURE_SYSLOG
9//config: help
10//config: login is used when signing onto a system.
11//config:
12//config: Note that Busybox binary must be setuid root for this applet to
13//config: work properly.
14//config:
15//config:config LOGIN_SESSION_AS_CHILD
16//config: bool "Run logged in session in a child process"
17//config: default y if PAM
18//config: depends on LOGIN
19//config: help
20//config: Run the logged in session in a child process. This allows
21//config: login to clean up things such as utmp entries or PAM sessions
22//config: when the login session is complete. If you use PAM, you
23//config: almost always would want this to be set to Y, else PAM session
24//config: will not be cleaned up.
25//config:
26//config:config LOGIN_SCRIPTS
27//config: bool "Support for login scripts"
28//config: depends on LOGIN
29//config: default y
30//config: help
31//config: Enable this if you want login to execute $LOGIN_PRE_SUID_SCRIPT
32//config: just prior to switching from root to logged-in user.
33//config:
34//config:config FEATURE_NOLOGIN
35//config: bool "Support for /etc/nologin"
36//config: default y
37//config: depends on LOGIN
38//config: help
39//config: The file /etc/nologin is used by (some versions of) login(1).
40//config: If it exists, non-root logins are prohibited.
41//config:
42//config:config FEATURE_SECURETTY
43//config: bool "Support for /etc/securetty"
44//config: default y
45//config: depends on LOGIN
46//config: help
47//config: The file /etc/securetty is used by (some versions of) login(1).
48//config: The file contains the device names of tty lines (one per line,
49//config: without leading /dev/) on which root is allowed to login.
50
51//applet:/* Needs to be run by root or be suid root - needs to change uid and gid: */
52//applet:IF_LOGIN(APPLET(login, BB_DIR_BIN, BB_SUID_REQUIRE))
53
54//kbuild:lib-$(CONFIG_LOGIN) += login.o
55
56//usage:#define login_trivial_usage
57//usage: "[-p] [-h HOST] [[-f] USER]"
58//usage:#define login_full_usage "\n\n"
59//usage: "Begin a new session on the system\n"
60//usage: "\n -f Don't authenticate (user already authenticated)"
61//usage: "\n -h HOST Host user came from (for network logins)"
62//usage: "\n -p Preserve environment"
63
64#include "libbb.h"
65#include "common_bufsiz.h"
66#include <syslog.h>
67#include <sys/resource.h>
68
69#if ENABLE_SELINUX
70# include <selinux/selinux.h> /* for is_selinux_enabled() */
71# include <selinux/get_context_list.h> /* for get_default_context() */
72# include <selinux/flask.h> /* for security class definitions */
73#endif
74
75#if ENABLE_PAM
76/* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
77# undef setlocale
78/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx.
79 * Apparently they like to confuse people. */
80# include <security/pam_appl.h>
81# include <security/pam_misc.h>
82
83# if 0
84/* This supposedly can be used to avoid double password prompt,
85 * if used instead of standard misc_conv():
86 *
87 * "When we want to authenticate first with local method and then with tacacs for example,
88 * the password is asked for local method and if not good is asked a second time for tacacs.
89 * So if we want to authenticate a user with tacacs, and the user exists localy, the password is
90 * asked two times before authentication is accepted."
91 *
92 * However, code looks shaky. For example, why misc_conv() return value is ignored?
93 * Are msg[i] and resp[i] indexes handled correctly?
94 */
95static char *passwd = NULL;
96static int my_conv(int num_msg, const struct pam_message **msg,
97 struct pam_response **resp, void *data)
98{
99 int i;
100 for (i = 0; i < num_msg; i++) {
101 switch (msg[i]->msg_style) {
102 case PAM_PROMPT_ECHO_OFF:
103 if (passwd == NULL) {
104 misc_conv(num_msg, msg, resp, data);
105 passwd = xstrdup(resp[i]->resp);
106 return PAM_SUCCESS;
107 }
108
109 resp[0] = xzalloc(sizeof(struct pam_response));
110 resp[0]->resp = passwd;
111 passwd = NULL;
112 resp[0]->resp_retcode = PAM_SUCCESS;
113 resp[1] = NULL;
114 return PAM_SUCCESS;
115
116 default:
117 break;
118 }
119 }
120
121 return PAM_SUCCESS;
122}
123# endif
124
125static const struct pam_conv conv = {
126 misc_conv,
127 NULL
128};
129#endif
130
131enum {
132 TIMEOUT = 60,
133 EMPTY_USERNAME_COUNT = 10,
134 /* Some users found 32 chars limit to be too low: */
135 USERNAME_SIZE = 64,
136 TTYNAME_SIZE = 32,
137};
138
139struct globals {
140 struct termios tty_attrs;
141} FIX_ALIASING;
142#define G (*(struct globals*)bb_common_bufsiz1)
143#define INIT_G() do { setup_common_bufsiz(); } while (0)
144
145
146#if ENABLE_FEATURE_NOLOGIN
147static void die_if_nologin(void)
148{
149 FILE *fp;
150 int c;
151 int empty = 1;
152
153 fp = fopen_for_read("/etc/nologin");
154 if (!fp) /* assuming it does not exist */
155 return;
156
157 while ((c = getc(fp)) != EOF) {
158 if (c == '\n')
159 bb_putchar('\r');
160 bb_putchar(c);
161 empty = 0;
162 }
163 if (empty)
164 puts("\r\nSystem closed for routine maintenance\r");
165
166 fclose(fp);
167 fflush_all();
168 /* Users say that they do need this prior to exit: */
169 tcdrain(STDOUT_FILENO);
170 exit(EXIT_FAILURE);
171}
172#else
173# define die_if_nologin() ((void)0)
174#endif
175
176#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
177static int check_securetty(const char *short_tty)
178{
179 char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */
180 parser_t *parser = config_open2("/etc/securetty", fopen_for_read);
181 while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) {
182 if (strcmp(buf, short_tty) == 0)
183 break;
184 buf = NULL;
185 }
186 config_close(parser);
187 /* buf != NULL here if config file was not found, empty
188 * or line was found which equals short_tty */
189 return buf != NULL;
190}
191#else
192static ALWAYS_INLINE int check_securetty(const char *short_tty UNUSED_PARAM) { return 1; }
193#endif
194
195#if ENABLE_SELINUX
196static void initselinux(char *username, char *full_tty,
197 security_context_t *user_sid)
198{
199 security_context_t old_tty_sid, new_tty_sid;
200
201 if (!is_selinux_enabled())
202 return;
203
204 if (get_default_context(username, NULL, user_sid)) {
205 bb_error_msg_and_die("can't get SID for %s", username);
206 }
207 if (getfilecon(full_tty, &old_tty_sid) < 0) {
208 bb_perror_msg_and_die("getfilecon(%s) failed", full_tty);
209 }
210 if (security_compute_relabel(*user_sid, old_tty_sid,
211 SECCLASS_CHR_FILE, &new_tty_sid) != 0) {
212 bb_perror_msg_and_die("security_change_sid(%s) failed", full_tty);
213 }
214 if (setfilecon(full_tty, new_tty_sid) != 0) {
215 bb_perror_msg_and_die("chsid(%s, %s) failed", full_tty, new_tty_sid);
216 }
217}
218#endif
219
220#if ENABLE_LOGIN_SCRIPTS
221static void run_login_script(struct passwd *pw, char *full_tty)
222{
223 char *t_argv[2];
224
225 t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT");
226 if (t_argv[0]) {
227 t_argv[1] = NULL;
228 xsetenv("LOGIN_TTY", full_tty);
229 xsetenv("LOGIN_USER", pw->pw_name);
230 xsetenv("LOGIN_UID", utoa(pw->pw_uid));
231 xsetenv("LOGIN_GID", utoa(pw->pw_gid));
232 xsetenv("LOGIN_SHELL", pw->pw_shell);
233 spawn_and_wait(t_argv); /* NOMMU-friendly */
234 unsetenv("LOGIN_TTY");
235 unsetenv("LOGIN_USER");
236 unsetenv("LOGIN_UID");
237 unsetenv("LOGIN_GID");
238 unsetenv("LOGIN_SHELL");
239 }
240}
241#else
242void run_login_script(struct passwd *pw, char *full_tty);
243#endif
244
245#if ENABLE_LOGIN_SESSION_AS_CHILD && ENABLE_PAM
246static void login_pam_end(pam_handle_t *pamh)
247{
248 int pamret;
249
250 pamret = pam_setcred(pamh, PAM_DELETE_CRED);
251 if (pamret != PAM_SUCCESS) {
252 bb_error_msg("pam_%s failed: %s (%d)", "setcred",
253 pam_strerror(pamh, pamret), pamret);
254 }
255 pamret = pam_close_session(pamh, 0);
256 if (pamret != PAM_SUCCESS) {
257 bb_error_msg("pam_%s failed: %s (%d)", "close_session",
258 pam_strerror(pamh, pamret), pamret);
259 }
260 pamret = pam_end(pamh, pamret);
261 if (pamret != PAM_SUCCESS) {
262 bb_error_msg("pam_%s failed: %s (%d)", "end",
263 pam_strerror(pamh, pamret), pamret);
264 }
265}
266#endif /* ENABLE_PAM */
267
268static void get_username_or_die(char *buf, int size_buf)
269{
270 int c, cntdown;
271
272 cntdown = EMPTY_USERNAME_COUNT;
273 prompt:
274 print_login_prompt();
275 /* skip whitespace */
276 do {
277 c = getchar();
278 if (c == EOF)
279 exit(EXIT_FAILURE);
280 if (c == '\n') {
281 if (!--cntdown)
282 exit(EXIT_FAILURE);
283 goto prompt;
284 }
285 } while (isspace(c)); /* maybe isblank? */
286
287 *buf++ = c;
288 if (!fgets(buf, size_buf-2, stdin))
289 exit(EXIT_FAILURE);
290 if (!strchr(buf, '\n'))
291 exit(EXIT_FAILURE);
292 while ((unsigned char)*buf > ' ')
293 buf++;
294 *buf = '\0';
295}
296
297static void motd(void)
298{
299 int fd;
300
301 fd = open(bb_path_motd_file, O_RDONLY);
302 if (fd >= 0) {
303 fflush_all();
304 bb_copyfd_eof(fd, STDOUT_FILENO);
305 close(fd);
306 }
307}
308
309static void alarm_handler(int sig UNUSED_PARAM)
310{
311 /* This is the escape hatch! Poor serial line users and the like
312 * arrive here when their connection is broken.
313 * We don't want to block here */
314 ndelay_on(STDOUT_FILENO);
315 /* Test for correct attr restoring:
316 * run "getty 0 -" from a shell, enter bogus username, stop at
317 * password prompt, let it time out. Without the tcsetattr below,
318 * when you are back at shell prompt, echo will be still off.
319 */
320 tcsetattr_stdin_TCSANOW(&G.tty_attrs);
321 printf("\r\nLogin timed out after %u seconds\r\n", TIMEOUT);
322 fflush_all();
323 /* unix API is brain damaged regarding O_NONBLOCK,
324 * we should undo it, or else we can affect other processes */
325 ndelay_off(STDOUT_FILENO);
326 _exit(EXIT_SUCCESS);
327}
328
329int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
330int login_main(int argc UNUSED_PARAM, char **argv)
331{
332 enum {
333 LOGIN_OPT_f = (1<<0),
334 LOGIN_OPT_h = (1<<1),
335 LOGIN_OPT_p = (1<<2),
336 };
337 char *fromhost;
338 char username[USERNAME_SIZE];
339 int run_by_root;
340 unsigned opt;
341 int count = 0;
342 struct passwd *pw;
343 char *opt_host = NULL;
344 char *opt_user = opt_user; /* for compiler */
345 char *full_tty;
346 char *short_tty;
347 IF_SELINUX(security_context_t user_sid = NULL;)
348#if ENABLE_PAM
349 int pamret;
350 pam_handle_t *pamh;
351 const char *pamuser;
352 const char *failed_msg;
353 struct passwd pwdstruct;
354 char pwdbuf[256];
355 char **pamenv;
356#endif
357#if ENABLE_LOGIN_SESSION_AS_CHILD
358 pid_t child_pid;
359#endif
360
361 INIT_G();
362
363 /* More of suid paranoia if called by non-root: */
364 /* Clear dangerous stuff, set PATH */
365 run_by_root = !sanitize_env_if_suid();
366
367 /* Mandatory paranoia for suid applet:
368 * ensure that fd# 0,1,2 are opened (at least to /dev/null)
369 * and any extra open fd's are closed.
370 * (The name of the function is misleading. Not daemonizing here.) */
371 bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL);
372
373 username[0] = '\0';
374 opt = getopt32(argv, "f:h:p", &opt_user, &opt_host);
375 if (opt & LOGIN_OPT_f) {
376 if (!run_by_root)
377 bb_error_msg_and_die("-f is for root only");
378 safe_strncpy(username, opt_user, sizeof(username));
379 }
380 argv += optind;
381 if (argv[0]) /* user from command line (getty) */
382 safe_strncpy(username, argv[0], sizeof(username));
383
384 /* Save tty attributes - and by doing it, check that it's indeed a tty */
385 if (tcgetattr(STDIN_FILENO, &G.tty_attrs) < 0
386 || !isatty(STDOUT_FILENO)
387 /*|| !isatty(STDERR_FILENO) - no, guess some people might want to redirect this */
388 ) {
389 return EXIT_FAILURE; /* Must be a terminal */
390 }
391
392 /* We install timeout handler only _after_ we saved G.tty_attrs */
393 signal(SIGALRM, alarm_handler);
394 alarm(TIMEOUT);
395
396 /* Find out and memorize our tty name */
397 full_tty = xmalloc_ttyname(STDIN_FILENO);
398 if (!full_tty)
399 full_tty = xstrdup("UNKNOWN");
400 short_tty = skip_dev_pfx(full_tty);
401
402 if (opt_host) {
403 fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host);
404 } else {
405 fromhost = xasprintf(" on '%s'", short_tty);
406 }
407
408 /* Was breaking "login <username>" from shell command line: */
409 /*bb_setpgrp();*/
410
411 openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH);
412
413 while (1) {
414 /* flush away any type-ahead (as getty does) */
415 tcflush(0, TCIFLUSH);
416
417 if (!username[0])
418 get_username_or_die(username, sizeof(username));
419
420#if ENABLE_PAM
421 pamret = pam_start("login", username, &conv, &pamh);
422 if (pamret != PAM_SUCCESS) {
423 failed_msg = "start";
424 goto pam_auth_failed;
425 }
426 /* set TTY (so things like securetty work) */
427 pamret = pam_set_item(pamh, PAM_TTY, short_tty);
428 if (pamret != PAM_SUCCESS) {
429 failed_msg = "set_item(TTY)";
430 goto pam_auth_failed;
431 }
432 /* set RHOST */
433 if (opt_host) {
434 pamret = pam_set_item(pamh, PAM_RHOST, opt_host);
435 if (pamret != PAM_SUCCESS) {
436 failed_msg = "set_item(RHOST)";
437 goto pam_auth_failed;
438 }
439 }
440 if (!(opt & LOGIN_OPT_f)) {
441 pamret = pam_authenticate(pamh, 0);
442 if (pamret != PAM_SUCCESS) {
443 failed_msg = "authenticate";
444 goto pam_auth_failed;
445 /* TODO: or just "goto auth_failed"
446 * since user seems to enter wrong password
447 * (in this case pamret == 7)
448 */
449 }
450 }
451 /* check that the account is healthy */
452 pamret = pam_acct_mgmt(pamh, 0);
453 if (pamret != PAM_SUCCESS) {
454 failed_msg = "acct_mgmt";
455 goto pam_auth_failed;
456 }
457 /* read user back */
458 pamuser = NULL;
459 /* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
460 * thus we cast to (void*) */
461 if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) {
462 failed_msg = "get_item(USER)";
463 goto pam_auth_failed;
464 }
465 if (!pamuser || !pamuser[0])
466 goto auth_failed;
467 safe_strncpy(username, pamuser, sizeof(username));
468 /* Don't use "pw = getpwnam(username);",
469 * PAM is said to be capable of destroying static storage
470 * used by getpwnam(). We are using safe(r) function */
471 pw = NULL;
472 getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw);
473 if (!pw)
474 goto auth_failed;
475 pamret = pam_open_session(pamh, 0);
476 if (pamret != PAM_SUCCESS) {
477 failed_msg = "open_session";
478 goto pam_auth_failed;
479 }
480 pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
481 if (pamret != PAM_SUCCESS) {
482 failed_msg = "setcred";
483 goto pam_auth_failed;
484 }
485 break; /* success, continue login process */
486
487 pam_auth_failed:
488 /* syslog, because we don't want potential attacker
489 * to know _why_ login failed */
490 syslog(LOG_WARNING, "pam_%s call failed: %s (%d)", failed_msg,
491 pam_strerror(pamh, pamret), pamret);
492 safe_strncpy(username, "UNKNOWN", sizeof(username));
493#else /* not PAM */
494 pw = getpwnam(username);
495 if (!pw) {
496 strcpy(username, "UNKNOWN");
497 goto fake_it;
498 }
499
500 if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')
501 goto auth_failed;
502
503 if (opt & LOGIN_OPT_f)
504 break; /* -f USER: success without asking passwd */
505
506 if (pw->pw_uid == 0 && !check_securetty(short_tty))
507 goto auth_failed;
508
509 /* Don't check the password if password entry is empty (!) */
510 if (!pw->pw_passwd[0])
511 break;
512 fake_it:
513 /* Password reading and authorization takes place here.
514 * Note that reads (in no-echo mode) trash tty attributes.
515 * If we get interrupted by SIGALRM, we need to restore attrs.
516 */
517 if (ask_and_check_password(pw) > 0)
518 break;
519#endif /* ENABLE_PAM */
520 auth_failed:
521 opt &= ~LOGIN_OPT_f;
522 bb_do_delay(LOGIN_FAIL_DELAY);
523 /* TODO: doesn't sound like correct English phrase to me */
524 puts("Login incorrect");
525 if (++count == 3) {
526 syslog(LOG_WARNING, "invalid password for '%s'%s",
527 username, fromhost);
528
529 if (ENABLE_FEATURE_CLEAN_UP)
530 free(fromhost);
531
532 return EXIT_FAILURE;
533 }
534 username[0] = '\0';
535 } /* while (1) */
536
537 alarm(0);
538 /* We can ignore /etc/nologin if we are logging in as root,
539 * it doesn't matter whether we are run by root or not */
540 if (pw->pw_uid != 0)
541 die_if_nologin();
542
543#if ENABLE_LOGIN_SESSION_AS_CHILD
544 child_pid = vfork();
545 if (child_pid != 0) {
546 if (child_pid < 0)
547 bb_perror_msg("vfork");
548 else {
549 if (safe_waitpid(child_pid, NULL, 0) == -1)
550 bb_perror_msg("waitpid");
551 update_utmp_DEAD_PROCESS(child_pid);
552 }
553 IF_PAM(login_pam_end(pamh);)
554 return 0;
555 }
556#endif
557
558 IF_SELINUX(initselinux(username, full_tty, &user_sid);)
559
560 /* Try these, but don't complain if they fail.
561 * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */
562 fchown(0, pw->pw_uid, pw->pw_gid);
563 fchmod(0, 0600);
564
565 update_utmp(getpid(), USER_PROCESS, short_tty, username, run_by_root ? opt_host : NULL);
566
567 /* We trust environment only if we run by root */
568 if (ENABLE_LOGIN_SCRIPTS && run_by_root)
569 run_login_script(pw, full_tty);
570
571 change_identity(pw);
572 setup_environment(pw->pw_shell,
573 (!(opt & LOGIN_OPT_p) * SETUP_ENV_CLEARENV) + SETUP_ENV_CHANGEENV,
574 pw);
575
576#if ENABLE_PAM
577 /* Modules such as pam_env will setup the PAM environment,
578 * which should be copied into the new environment. */
579 pamenv = pam_getenvlist(pamh);
580 if (pamenv) while (*pamenv) {
581 putenv(*pamenv);
582 pamenv++;
583 }
584#endif
585
586 if (access(".hushlogin", F_OK) != 0)
587 motd();
588
589 if (pw->pw_uid == 0)
590 syslog(LOG_INFO, "root login%s", fromhost);
591
592 if (ENABLE_FEATURE_CLEAN_UP)
593 free(fromhost);
594
595 /* well, a simple setexeccon() here would do the job as well,
596 * but let's play the game for now */
597 IF_SELINUX(set_current_security_context(user_sid);)
598
599 // util-linux login also does:
600 // /* start new session */
601 // setsid();
602 // /* TIOCSCTTY: steal tty from other process group */
603 // if (ioctl(0, TIOCSCTTY, 1)) error_msg...
604 // BBox login used to do this (see above):
605 // bb_setpgrp();
606 // If this stuff is really needed, add it and explain why!
607
608 /* Set signals to defaults */
609 /* Non-ignored signals revert to SIG_DFL on exec anyway */
610 /*signal(SIGALRM, SIG_DFL);*/
611
612 /* Is this correct? This way user can ctrl-c out of /etc/profile,
613 * potentially creating security breach (tested with bash 3.0).
614 * But without this, bash 3.0 will not enable ctrl-c either.
615 * Maybe bash is buggy?
616 * Need to find out what standards say about /bin/login -
617 * should we leave SIGINT etc enabled or disabled? */
618 signal(SIGINT, SIG_DFL);
619
620 /* Exec login shell with no additional parameters */
621 run_shell(pw->pw_shell, 1, NULL, NULL);
622
623 /* return EXIT_FAILURE; - not reached */
624}
Note: See TracBrowser for help on using the repository browser.