X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=loginutils%2Flogin.c;h=b9d910331e0a97bae3e91cc77f9cc1006730ee79;hb=bbd53216f80912944da0d4ca72bf3ed3188ca156;hp=861382f12159f8877ba7a7c1b1b827f33ae778d6;hpb=694b514148681193e1710daa073f5bc1263b0d95;p=oweals%2Fbusybox.git diff --git a/loginutils/login.c b/loginutils/login.c index 861382f12..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,88 +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; - -#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 picky) -{ - struct utmp *ut; - pid_t pid = getpid(); +struct globals { + struct termios tty_attrs; +} FIX_ALIASING; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { } while (0) - setutent(); - - /* First, try to find a valid utmp entry for this process. */ - while ((ut = getutent())) - if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] && - (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)) - break; - - /* If there is one, just use it, otherwise create a new one. */ - if (ut) { - *utptr = *ut; - } else { - if (picky) - bb_error_msg_and_die("no utmp entry found"); - - 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); - } - if (!picky) /* root login */ - memset(utptr->ut_host, 0, sizeof(utptr->ut_host)); -} - -/* - * 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, picky) ((void)0) -#define write_utent(utptr, username) ((void)0) -#endif /* !ENABLE_FEATURE_UTMP */ #if ENABLE_FEATURE_NOLOGIN static void die_if_nologin(void) @@ -134,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); @@ -159,9 +95,82 @@ 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 +static void initselinux(char *username, char *full_tty, + security_context_t *user_sid) +{ + security_context_t old_tty_sid, new_tty_sid; + + if (!is_selinux_enabled()) + return; + + if (get_default_context(username, NULL, user_sid)) { + 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); + } + if (security_compute_relabel(*user_sid, old_tty_sid, + SECCLASS_CHR_FILE, &new_tty_sid) != 0) { + bb_perror_msg_and_die("security_change_sid(%s) failed", full_tty); + } + if (setfilecon(full_tty, new_tty_sid) != 0) { + bb_perror_msg_and_die("chsid(%s, %s) failed", full_tty, new_tty_sid); + } +} +#endif + +#if ENABLE_LOGIN_SCRIPTS +static void run_login_script(struct passwd *pw, char *full_tty) +{ + char *t_argv[2]; + + t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT"); + if (t_argv[0]) { + t_argv[1] = NULL; + xsetenv("LOGIN_TTY", full_tty); + xsetenv("LOGIN_USER", pw->pw_name); + 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 */ + unsetenv("LOGIN_TTY"); + unsetenv("LOGIN_USER"); + unsetenv("LOGIN_UID"); + unsetenv("LOGIN_GID"); + unsetenv("LOGIN_SHELL"); + } +} +#else +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; @@ -172,19 +181,22 @@ static void get_username_or_die(char *buf, int size_buf) /* skip whitespace */ do { c = getchar(); - if (c == EOF) exit(EXIT_FAILURE); + if (c == EOF) + exit(EXIT_FAILURE); if (c == '\n') { - if (!--cntdown) exit(EXIT_FAILURE); + if (!--cntdown) + exit(EXIT_FAILURE); goto prompt; } - } while (isspace(c)); + } while (isspace(c)); /* maybe isblank? */ *buf++ = c; if (!fgets(buf, size_buf-2, stdin)) exit(EXIT_FAILURE); if (!strchr(buf, '\n')) exit(EXIT_FAILURE); - while (isgraph(*buf)) buf++; + while ((unsigned char)*buf > ' ') + buf++; *buf = '\0'; } @@ -194,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); } @@ -202,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(stdout); + 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); } @@ -224,16 +242,15 @@ int login_main(int argc UNUSED_PARAM, char **argv) }; char *fromhost; char username[USERNAME_SIZE]; - const char *tmp; - int amroot; + 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[TTYNAME_SIZE]; - USE_SELINUX(security_context_t user_sid = NULL;) - USE_FEATURE_UTMP(struct utmp utent;) + char *full_tty; + char *short_tty; + IF_SELINUX(security_context_t user_sid = NULL;) #if ENABLE_PAM int pamret; pam_handle_t *pamh; @@ -241,15 +258,17 @@ 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 - short_tty = full_tty; - username[0] = '\0'; - signal(SIGALRM, alarm_handler); - alarm(TIMEOUT); + INIT_G(); - /* More of suid paranoia if called by non-root */ - amroot = !sanitize_env_if_suid(); /* Clear dangerous stuff, set PATH */ + /* More of suid paranoia if called by non-root: */ + /* Clear dangerous stuff, set PATH */ + run_by_root = !sanitize_env_if_suid(); /* Mandatory paranoia for suid applet: * ensure that fd# 0,1,2 are opened (at least to /dev/null) @@ -257,9 +276,10 @@ 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 (!amroot) + if (!run_by_root) bb_error_msg_and_die("-f is for root only"); safe_strncpy(username, opt_user, sizeof(username)); } @@ -267,35 +287,38 @@ 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(0) || !isatty(1) || !isatty(2)) - return EXIT_FAILURE; /* Must be a terminal */ - safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty)); - tmp = ttyname(0); - if (tmp) { - safe_strncpy(full_tty, tmp, sizeof(full_tty)); - if (strncmp(full_tty, "/dev/", 5) == 0) - short_tty = full_tty + 5; + /* 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 */ } - read_or_build_utent(&utent, !amroot); + /* 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 = skip_dev_pfx(full_tty); - if (opt & LOGIN_OPT_h) { - USE_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 + } else { fromhost = xasprintf(" on '%s'", short_tty); + } /* Was breaking "login " from shell command line: */ /*bb_setpgrp();*/ - openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); + openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH); 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)); @@ -312,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); @@ -358,7 +391,9 @@ int login_main(int argc UNUSED_PARAM, char **argv) break; /* success, continue login process */ pam_auth_failed: - bb_error_msg("pam_%s call failed: %s (%d)", failed_msg, + /* syslog, because we don't want potential attacker + * to know _why_ login failed */ + syslog(LOG_WARNING, "pam_%s call failed: %s (%d)", failed_msg, pam_strerror(pamh, pamret), pamret); safe_strncpy(username, "UNKNOWN", sizeof(username)); #else /* not PAM */ @@ -374,101 +409,97 @@ 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'; - } + } /* while (1) */ alarm(0); - if (!amroot) + /* We can ignore /etc/nologin if we are logging in as root, + * it doesn't matter whether we are run by root or not */ + if (pw->pw_uid != 0) die_if_nologin(); - write_utent(&utent, username); - -#if ENABLE_SELINUX - if (is_selinux_enabled()) { - security_context_t old_tty_sid, new_tty_sid; - - if (get_default_context(username, NULL, &user_sid)) { - bb_error_msg_and_die("cannot get SID for %s", - username); - } - if (getfilecon(full_tty, &old_tty_sid) < 0) { - bb_perror_msg_and_die("getfilecon(%s) failed", - full_tty); - } - if (security_compute_relabel(user_sid, old_tty_sid, - SECCLASS_CHR_FILE, &new_tty_sid) != 0) { - bb_perror_msg_and_die("security_change_sid(%s) failed", - full_tty); - } - if (setfilecon(full_tty, new_tty_sid) != 0) { - bb_perror_msg_and_die("chsid(%s, %s) failed", - full_tty, new_tty_sid); +#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);) + /* 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 && amroot) { - char *t_argv[2]; - - t_argv[0] = getenv("LOGIN_PRE_SUID_SCRIPT"); - if (t_argv[0]) { - t_argv[1] = NULL; - xsetenv("LOGIN_TTY", full_tty); - xsetenv("LOGIN_USER", pw->pw_name); - 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 */ - unsetenv("LOGIN_TTY" ); - unsetenv("LOGIN_USER" ); - unsetenv("LOGIN_UID" ); - unsetenv("LOGIN_GID" ); - unsetenv("LOGIN_SHELL"); - } - } + 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_SELINUX + + 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 */ - set_current_security_context(user_sid); -#endif + IF_SELINUX(set_current_security_context(user_sid);) // util-linux login also does: // /* start new session */ @@ -479,18 +510,20 @@ int login_main(int argc UNUSED_PARAM, char **argv) // bb_setpgrp(); // If this stuff is really needed, add it and explain why! - /* set signals to defaults */ - signal(SIGALRM, SIG_DFL); + /* Set signals to defaults */ + /* Non-ignored signals revert to SIG_DFL on exec anyway */ + /*signal(SIGALRM, SIG_DFL);*/ + /* Is this correct? This way user can ctrl-c out of /etc/profile, * potentially creating security breach (tested with bash 3.0). * But without this, bash 3.0 will not enable ctrl-c either. * Maybe bash is buggy? * Need to find out what standards say about /bin/login - - * should it leave SIGINT etc enabled or disabled? */ + * should we leave SIGINT etc enabled or disabled? */ 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 */ }