X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=loginutils%2Flogin.c;h=b9d910331e0a97bae3e91cc77f9cc1006730ee79;hb=bbd53216f80912944da0d4ca72bf3ed3188ca156;hp=c3e31d05d9e0d9fe81de97be4f42e017e532e309;hpb=85ca327529afeceed41c1d66d0b7d48c2ffc2a4b;p=oweals%2Fbusybox.git diff --git a/loginutils/login.c b/loginutils/login.c index c3e31d05d..b9d910331 100644 --- a/loginutils/login.c +++ b/loginutils/login.c @@ -1,26 +1,33 @@ /* vi: set sw=4 ts=4: */ /* - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ +//usage:#define login_trivial_usage +//usage: "[-p] [-h HOST] [[-f] USER]" +//usage:#define login_full_usage "\n\n" +//usage: "Begin a new session on the system\n" +//usage: "\n -f Don't authenticate (user already authenticated)" +//usage: "\n -h Name of the remote host" +//usage: "\n -p Preserve environment" + #include "libbb.h" #include -#include #include #if ENABLE_SELINUX -#include /* for is_selinux_enabled() */ -#include /* for get_default_context() */ -#include /* for security class definitions */ +# include /* for is_selinux_enabled() */ +# include /* for get_default_context() */ +# include /* for security class definitions */ #endif #if ENABLE_PAM /* PAM may include . We may need to undefine bbox's stub define: */ -#undef setlocale +# undef setlocale /* For some obscure reason, PAM is not in pam/xxx, but in security/xxx. * Apparently they like to confuse people. */ -#include -#include +# include +# include static const struct pam_conv conv = { misc_conv, NULL @@ -30,91 +37,17 @@ static const struct pam_conv conv = { enum { TIMEOUT = 60, EMPTY_USERNAME_COUNT = 10, - USERNAME_SIZE = 32, + /* Some users found 32 chars limit to be too low: */ + USERNAME_SIZE = 64, TTYNAME_SIZE = 32, }; -static char* short_tty; +struct globals { + struct termios tty_attrs; +} FIX_ALIASING; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { } while (0) -#if ENABLE_FEATURE_UTMP -/* vv Taken from tinylogin utmp.c vv */ -/* - * read_or_build_utent - see if utmp file is correct for this process - * - * System V is very picky about the contents of the utmp file - * and requires that a slot for the current process exist. - * The utmp file is scanned for an entry with the same process - * ID. If no entry exists the process exits with a message. - * - * The "picky" flag is for network and other logins that may - * use special flags. It allows the pid checks to be overridden. - * This means that getty should never invoke login with any - * command line flags. - */ - -static void read_or_build_utent(struct utmp *utptr, int run_by_root) -{ - struct utmp *ut; - pid_t pid = getpid(); - - setutent(); - - /* First, try to find a valid utmp entry for this process. */ - /* If there is one, just use it. */ - while ((ut = getutent()) != NULL) - if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] - && (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS) - ) { - *utptr = *ut; /* struct copy */ - if (run_by_root) /* why only for root? */ - memset(utptr->ut_host, 0, sizeof(utptr->ut_host)); - return; - } - -// Why? Do we require non-root to exec login from another -// former login process (e.g. login shell)? Some login's have -// login shells as children, so it won't work... -// if (!run_by_root) -// bb_error_msg_and_die("no utmp entry found"); - - /* Otherwise create a new one. */ - memset(utptr, 0, sizeof(*utptr)); - utptr->ut_type = LOGIN_PROCESS; - utptr->ut_pid = pid; - strncpy(utptr->ut_line, short_tty, sizeof(utptr->ut_line)); - /* This one is only 4 chars wide. Try to fit something - * remotely meaningful by skipping "tty"... */ - strncpy(utptr->ut_id, short_tty + 3, sizeof(utptr->ut_id)); - strncpy(utptr->ut_user, "LOGIN", sizeof(utptr->ut_user)); - utptr->ut_tv.tv_sec = time(NULL); -} - -/* - * write_utent - put a USER_PROCESS entry in the utmp file - * - * write_utent changes the type of the current utmp entry to - * USER_PROCESS. the wtmp file will be updated as well. - */ -static void write_utent(struct utmp *utptr, const char *username) -{ - utptr->ut_type = USER_PROCESS; - strncpy(utptr->ut_user, username, sizeof(utptr->ut_user)); - utptr->ut_tv.tv_sec = time(NULL); - /* other fields already filled in by read_or_build_utent above */ - setutent(); - pututline(utptr); - endutent(); -#if ENABLE_FEATURE_WTMP - if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) { - close(creat(bb_path_wtmp_file, 0664)); - } - updwtmp(bb_path_wtmp_file, utptr); -#endif -} -#else /* !ENABLE_FEATURE_UTMP */ -#define read_or_build_utent(utptr, run_by_root) ((void)0) -#define write_utent(utptr, username) ((void)0) -#endif /* !ENABLE_FEATURE_UTMP */ #if ENABLE_FEATURE_NOLOGIN static void die_if_nologin(void) @@ -137,17 +70,17 @@ static void die_if_nologin(void) puts("\r\nSystem closed for routine maintenance\r"); fclose(fp); - fflush(NULL); + fflush_all(); /* Users say that they do need this prior to exit: */ tcdrain(STDOUT_FILENO); exit(EXIT_FAILURE); } #else -static ALWAYS_INLINE void die_if_nologin(void) {} +# define die_if_nologin() ((void)0) #endif #if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM -static int check_securetty(void) +static int check_securetty(const char *short_tty) { char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */ parser_t *parser = config_open2("/etc/securetty", fopen_for_read); @@ -162,7 +95,7 @@ static int check_securetty(void) return buf != NULL; } #else -static ALWAYS_INLINE int check_securetty(void) { return 1; } +static ALWAYS_INLINE int check_securetty(const char *short_tty UNUSED_PARAM) { return 1; } #endif #if ENABLE_SELINUX @@ -175,7 +108,7 @@ static void initselinux(char *username, char *full_tty, return; if (get_default_context(username, NULL, user_sid)) { - bb_error_msg_and_die("cannot get SID for %s", username); + bb_error_msg_and_die("can't get SID for %s", username); } if (getfilecon(full_tty, &old_tty_sid) < 0) { bb_perror_msg_and_die("getfilecon(%s) failed", full_tty); @@ -203,7 +136,7 @@ static void run_login_script(struct passwd *pw, char *full_tty) xsetenv("LOGIN_UID", utoa(pw->pw_uid)); xsetenv("LOGIN_GID", utoa(pw->pw_gid)); xsetenv("LOGIN_SHELL", pw->pw_shell); - spawn_and_wait(t_argv); /* NOMMU-friendly */ + spawn_and_wait(t_argv); /* NOMMU-friendly */ unsetenv("LOGIN_TTY"); unsetenv("LOGIN_USER"); unsetenv("LOGIN_UID"); @@ -215,6 +148,29 @@ static void run_login_script(struct passwd *pw, char *full_tty) void run_login_script(struct passwd *pw, char *full_tty); #endif +#if ENABLE_LOGIN_SESSION_AS_CHILD && ENABLE_PAM +static void login_pam_end(pam_handle_t *pamh) +{ + int pamret; + + pamret = pam_setcred(pamh, PAM_DELETE_CRED); + if (pamret != PAM_SUCCESS) { + bb_error_msg("pam_%s failed: %s (%d)", "setcred", + pam_strerror(pamh, pamret), pamret); + } + pamret = pam_close_session(pamh, 0); + if (pamret != PAM_SUCCESS) { + bb_error_msg("pam_%s failed: %s (%d)", "close_session", + pam_strerror(pamh, pamret), pamret); + } + pamret = pam_end(pamh, pamret); + if (pamret != PAM_SUCCESS) { + bb_error_msg("pam_%s failed: %s (%d)", "end", + pam_strerror(pamh, pamret), pamret); + } +} +#endif /* ENABLE_PAM */ + static void get_username_or_die(char *buf, int size_buf) { int c, cntdown; @@ -250,7 +206,7 @@ static void motd(void) fd = open(bb_path_motd_file, O_RDONLY); if (fd >= 0) { - fflush(stdout); + fflush_all(); bb_copyfd_eof(fd, STDOUT_FILENO); close(fd); } @@ -258,15 +214,21 @@ static void motd(void) static void alarm_handler(int sig UNUSED_PARAM) { - /* This is the escape hatch! Poor serial line users and the like + /* This is the escape hatch! Poor serial line users and the like * arrive here when their connection is broken. * We don't want to block here */ - ndelay_on(1); - printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT); - fflush(NULL); + ndelay_on(STDOUT_FILENO); + /* Test for correct attr restoring: + * run "getty 0 -" from a shell, enter bogus username, stop at + * password prompt, let it time out. Without the tcsetattr below, + * when you are back at shell prompt, echo will be still off. + */ + tcsetattr_stdin_TCSANOW(&G.tty_attrs); + printf("\r\nLogin timed out after %u seconds\r\n", TIMEOUT); + fflush_all(); /* unix API is brain damaged regarding O_NONBLOCK, * we should undo it, or else we can affect other processes */ - ndelay_off(1); + ndelay_off(STDOUT_FILENO); _exit(EXIT_SUCCESS); } @@ -280,16 +242,15 @@ int login_main(int argc UNUSED_PARAM, char **argv) }; char *fromhost; char username[USERNAME_SIZE]; - const char *tmp; int run_by_root; unsigned opt; int count = 0; struct passwd *pw; - char *opt_host = opt_host; /* for compiler */ + char *opt_host = NULL; char *opt_user = opt_user; /* for compiler */ char *full_tty; + char *short_tty; IF_SELINUX(security_context_t user_sid = NULL;) - IF_FEATURE_UTMP(struct utmp utent;) #if ENABLE_PAM int pamret; pam_handle_t *pamh; @@ -297,11 +258,13 @@ int login_main(int argc UNUSED_PARAM, char **argv) const char *failed_msg; struct passwd pwdstruct; char pwdbuf[256]; + char **pamenv; +#endif +#if ENABLE_LOGIN_SESSION_AS_CHILD + pid_t child_pid; #endif - username[0] = '\0'; - signal(SIGALRM, alarm_handler); - alarm(TIMEOUT); + INIT_G(); /* More of suid paranoia if called by non-root: */ /* Clear dangerous stuff, set PATH */ @@ -313,6 +276,7 @@ int login_main(int argc UNUSED_PARAM, char **argv) * (The name of the function is misleading. Not daemonizing here.) */ bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL); + username[0] = '\0'; opt = getopt32(argv, "f:h:p", &opt_user, &opt_host); if (opt & LOGIN_OPT_f) { if (!run_by_root) @@ -323,20 +287,25 @@ int login_main(int argc UNUSED_PARAM, char **argv) if (argv[0]) /* user from command line (getty) */ safe_strncpy(username, argv[0], sizeof(username)); - /* Let's find out and memorize our tty */ - if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) - return EXIT_FAILURE; /* Must be a terminal */ + /* Save tty attributes - and by doing it, check that it's indeed a tty */ + if (tcgetattr(STDIN_FILENO, &G.tty_attrs) < 0 + || !isatty(STDOUT_FILENO) + /*|| !isatty(STDERR_FILENO) - no, guess some people might want to redirect this */ + ) { + return EXIT_FAILURE; /* Must be a terminal */ + } + + /* We install timeout handler only _after_ we saved G.tty_attrs */ + signal(SIGALRM, alarm_handler); + alarm(TIMEOUT); + + /* Find out and memorize our tty name */ full_tty = xmalloc_ttyname(STDIN_FILENO); if (!full_tty) full_tty = xstrdup("UNKNOWN"); - short_tty = full_tty; - if (strncmp(full_tty, "/dev/", 5) == 0) - short_tty += 5; + short_tty = skip_dev_pfx(full_tty); - read_or_build_utent(&utent, run_by_root); - - if (opt & LOGIN_OPT_h) { - IF_FEATURE_UTMP(safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host));) + if (opt_host) { fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host); } else { fromhost = xasprintf(" on '%s'", short_tty); @@ -349,7 +318,7 @@ int login_main(int argc UNUSED_PARAM, char **argv) while (1) { /* flush away any type-ahead (as getty does) */ - ioctl(0, TCFLSH, TCIFLUSH); + tcflush(0, TCIFLUSH); if (!username[0]) get_username_or_die(username, sizeof(username)); @@ -366,14 +335,24 @@ int login_main(int argc UNUSED_PARAM, char **argv) failed_msg = "set_item(TTY)"; goto pam_auth_failed; } - pamret = pam_authenticate(pamh, 0); - if (pamret != PAM_SUCCESS) { - failed_msg = "authenticate"; - goto pam_auth_failed; - /* TODO: or just "goto auth_failed" - * since user seems to enter wrong password - * (in this case pamret == 7) - */ + /* set RHOST */ + if (opt_host) { + pamret = pam_set_item(pamh, PAM_RHOST, opt_host); + if (pamret != PAM_SUCCESS) { + failed_msg = "set_item(RHOST)"; + goto pam_auth_failed; + } + } + if (!(opt & LOGIN_OPT_f)) { + pamret = pam_authenticate(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "authenticate"; + goto pam_auth_failed; + /* TODO: or just "goto auth_failed" + * since user seems to enter wrong password + * (in this case pamret == 7) + */ + } } /* check that the account is healthy */ pamret = pam_acct_mgmt(pamh, 0); @@ -430,25 +409,32 @@ int login_main(int argc UNUSED_PARAM, char **argv) if (opt & LOGIN_OPT_f) break; /* -f USER: success without asking passwd */ - if (pw->pw_uid == 0 && !check_securetty()) + if (pw->pw_uid == 0 && !check_securetty(short_tty)) goto auth_failed; /* Don't check the password if password entry is empty (!) */ if (!pw->pw_passwd[0]) break; fake_it: - /* authorization takes place here */ - if (correct_password(pw)) + /* Password reading and authorization takes place here. + * Note that reads (in no-echo mode) trash tty attributes. + * If we get interrupted by SIGALRM, we need to restore attrs. + */ + if (ask_and_check_password(pw) > 0) break; #endif /* ENABLE_PAM */ auth_failed: opt &= ~LOGIN_OPT_f; - bb_do_delay(FAIL_DELAY); + bb_do_delay(LOGIN_FAIL_DELAY); /* TODO: doesn't sound like correct English phrase to me */ puts("Login incorrect"); if (++count == 3) { syslog(LOG_WARNING, "invalid password for '%s'%s", username, fromhost); + + if (ENABLE_FEATURE_CLEAN_UP) + free(fromhost); + return EXIT_FAILURE; } username[0] = '\0'; @@ -460,31 +446,57 @@ int login_main(int argc UNUSED_PARAM, char **argv) if (pw->pw_uid != 0) die_if_nologin(); - write_utent(&utent, username); +#if ENABLE_LOGIN_SESSION_AS_CHILD + child_pid = vfork(); + if (child_pid != 0) { + if (child_pid < 0) + bb_perror_msg("vfork"); + else { + if (safe_waitpid(child_pid, NULL, 0) == -1) + bb_perror_msg("waitpid"); + update_utmp_DEAD_PROCESS(child_pid); + } + IF_PAM(login_pam_end(pamh);) + return 0; + } +#endif - IF_SELINUX(initselinux(username, full_tty, &user_sid)); + IF_SELINUX(initselinux(username, full_tty, &user_sid);) /* Try these, but don't complain if they fail. * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */ fchown(0, pw->pw_uid, pw->pw_gid); fchmod(0, 0600); + update_utmp(getpid(), USER_PROCESS, short_tty, username, run_by_root ? opt_host : NULL); + /* We trust environment only if we run by root */ if (ENABLE_LOGIN_SCRIPTS && run_by_root) run_login_script(pw, full_tty); change_identity(pw); - tmp = pw->pw_shell; - if (!tmp || !*tmp) - tmp = DEFAULT_SHELL; - /* setup_environment params: shell, clear_env, change_env, pw */ - setup_environment(tmp, !(opt & LOGIN_OPT_p), 1, pw); + setup_environment(pw->pw_shell, + (!(opt & LOGIN_OPT_p) * SETUP_ENV_CLEARENV) + SETUP_ENV_CHANGEENV, + pw); + +#if ENABLE_PAM + /* Modules such as pam_env will setup the PAM environment, + * which should be copied into the new environment. */ + pamenv = pam_getenvlist(pamh); + if (pamenv) while (*pamenv) { + putenv(*pamenv); + pamenv++; + } +#endif motd(); if (pw->pw_uid == 0) syslog(LOG_INFO, "root login%s", fromhost); + if (ENABLE_FEATURE_CLEAN_UP) + free(fromhost); + /* well, a simple setexeccon() here would do the job as well, * but let's play the game for now */ IF_SELINUX(set_current_security_context(user_sid);) @@ -511,7 +523,7 @@ int login_main(int argc UNUSED_PARAM, char **argv) signal(SIGINT, SIG_DFL); /* Exec login shell with no additional parameters */ - run_shell(tmp, 1, NULL, NULL); + run_shell(pw->pw_shell, 1, NULL, NULL); /* return EXIT_FAILURE; - not reached */ }