X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=loginutils%2Flogin.c;h=8732b99f1e2b3c28404f7871bb2198f894ffb6cd;hb=9b44613202a6f2f080ec23746d0680dcef88628d;hp=b6924b641453e9a0654d2f0f12a876d749e41055;hpb=65e14b458892a150681c42bb5837acf68f2d9b60;p=oweals%2Fbusybox.git diff --git a/loginutils/login.c b/loginutils/login.c index b6924b641..8732b99f1 100644 --- a/loginutils/login.c +++ b/loginutils/login.c @@ -4,9 +4,9 @@ */ #include "libbb.h" +#include #include #include -#include #if ENABLE_SELINUX #include /* for is_selinux_enabled() */ @@ -14,6 +14,19 @@ #include /* for security class definitions */ #endif +#if ENABLE_PAM +/* PAM may include . We may need to undefine bbox's stub define: */ +#undef setlocale +/* For some obscure reason, PAM is not in pam/xxx, but in security/xxx. + * Apparently they like to confuse people. */ +#include +#include +static const struct pam_conv conv = { + misc_conv, + NULL +}; +#endif + enum { TIMEOUT = 60, EMPTY_USERNAME_COUNT = 10, @@ -67,7 +80,7 @@ static void read_or_build_utent(struct utmp *utptr, int picky) * 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_time = time(NULL); + utptr->ut_tv.tv_sec = time(NULL); } if (!picky) /* root login */ memset(utptr->ut_host, 0, sizeof(utptr->ut_host)); @@ -83,7 +96,7 @@ 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_time = time(NULL); + utptr->ut_tv.tv_sec = time(NULL); /* other fields already filled in by read_or_build_utent above */ setutent(); pututline(utptr); @@ -100,80 +113,70 @@ static void write_utent(struct utmp *utptr, const char *username) #define write_utent(utptr, username) ((void)0) #endif /* !ENABLE_FEATURE_UTMP */ -static void die_if_nologin_and_non_root(int amroot) +#if ENABLE_FEATURE_NOLOGIN +static void die_if_nologin(void) { FILE *fp; int c; - if (access(bb_path_nologin_file, F_OK)) + if (access("/etc/nologin", F_OK)) return; - fp = fopen(bb_path_nologin_file, "r"); + fp = fopen_for_read("/etc/nologin"); if (fp) { while ((c = getc(fp)) != EOF) - putchar((c=='\n') ? '\r' : c); + bb_putchar((c=='\n') ? '\r' : c); fflush(stdout); fclose(fp); } else puts("\r\nSystem closed for routine maintenance\r"); - if (!amroot) - exit(1); - puts("\r\n[Disconnect bypassed -- root login allowed.]\r"); + exit(EXIT_FAILURE); } +#else +static ALWAYS_INLINE void die_if_nologin(void) {} +#endif -#if ENABLE_FEATURE_SECURETTY +#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM static int check_securetty(void) { - FILE *fp; - int i; - char buf[BUFSIZ]; - - fp = fopen(bb_path_securetty_file, "r"); - if (!fp) { - /* A missing securetty file is not an error. */ - return 1; - } - while (fgets(buf, sizeof(buf)-1, fp)) { - for (i = strlen(buf)-1; i>=0; --i) { - if (!isspace(buf[i])) - break; - } - buf[++i] = '\0'; - if ((buf[0]=='\0') || (buf[0]=='#')) - continue; - if (strcmp(buf, short_tty) == 0) { - fclose(fp); - return 1; - } + char *buf = (char*)"/etc/securetty"; /* any non-NULL is ok */ + parser_t *parser = config_open2("/etc/securetty", fopen_for_read); + while (config_read(parser, &buf, 1, 1, "# \t", PARSE_NORMAL)) { + if (strcmp(buf, short_tty) == 0) + break; + buf = NULL; } - fclose(fp); - return 0; + config_close(parser); + /* buf != NULL here if config file was not found, empty + * or line was found which equals short_tty */ + return buf != NULL; } #else -static inline int check_securetty(void) { return 1; } +static ALWAYS_INLINE int check_securetty(void) { return 1; } #endif static void get_username_or_die(char *buf, int size_buf) { int c, cntdown; + cntdown = EMPTY_USERNAME_COUNT; -prompt: - /* skip whitespace */ + prompt: print_login_prompt(); + /* skip whitespace */ do { c = getchar(); - if (c == EOF) exit(1); + if (c == EOF) exit(EXIT_FAILURE); if (c == '\n') { - if (!--cntdown) exit(1); + if (!--cntdown) exit(EXIT_FAILURE); goto prompt; } } while (isspace(c)); *buf++ = c; if (!fgets(buf, size_buf-2, stdin)) - exit(1); + exit(EXIT_FAILURE); if (!strchr(buf, '\n')) - exit(1); + exit(EXIT_FAILURE); while (isgraph(*buf)) buf++; *buf = '\0'; } @@ -183,65 +186,79 @@ static void motd(void) int fd; fd = open(bb_path_motd_file, O_RDONLY); - if (fd) { + if (fd >= 0) { fflush(stdout); bb_copyfd_eof(fd, STDOUT_FILENO); close(fd); } } -static void alarm_handler(int sig ATTRIBUTE_UNUSED) +static void alarm_handler(int sig UNUSED_PARAM) { /* 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); - ndelay_on(2); printf("\r\nLogin timed out after %d seconds\r\n", TIMEOUT); - exit(EXIT_SUCCESS); + fflush(stdout); + /* unix API is brain damaged regarding O_NONBLOCK, + * we should undo it, or else we can affect other processes */ + ndelay_off(1); + _exit(EXIT_SUCCESS); } -int login_main(int argc, char **argv); -int login_main(int argc, char **argv) +int login_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int login_main(int argc UNUSED_PARAM, char **argv) { enum { LOGIN_OPT_f = (1<<0), LOGIN_OPT_h = (1<<1), LOGIN_OPT_p = (1<<2), }; - char fromhost[512]; + char *fromhost; char username[USERNAME_SIZE]; const char *tmp; int amroot; unsigned opt; int count = 0; struct passwd *pw; - char *opt_host = NULL; - char *opt_user = NULL; + char *opt_host = opt_host; /* for compiler */ + 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;) +#if ENABLE_PAM + int pamret; + pam_handle_t *pamh; + const char *pamuser; + const char *failed_msg; + struct passwd pwdstruct; + char pwdbuf[256]; +#endif short_tty = full_tty; username[0] = '\0'; - amroot = (getuid() == 0); signal(SIGALRM, alarm_handler); alarm(TIMEOUT); + /* More of suid paranoia if called by non-root */ + amroot = !sanitize_env_if_suid(); /* Clear dangerous stuff, set PATH */ + /* Mandatory paranoia for suid applet: * ensure that fd# 0,1,2 are opened (at least to /dev/null) * and any extra open fd's are closed. * (The name of the function is misleading. Not daemonizing here.) */ bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE | DAEMON_CLOSE_EXTRA_FDS, NULL); - opt = getopt32(argc, argv, "f:h:p", &opt_user, &opt_host); + opt = getopt32(argv, "f:h:p", &opt_user, &opt_host); if (opt & LOGIN_OPT_f) { if (!amroot) bb_error_msg_and_die("-f is for root only"); safe_strncpy(username, opt_user, sizeof(username)); } - if (optind < argc) /* user from command line (getty) */ - safe_strncpy(username, argv[optind], sizeof(username)); + argv += optind; + 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)) @@ -256,24 +273,88 @@ int login_main(int argc, char **argv) read_or_build_utent(&utent, !amroot); - if (opt_host) { + if (opt & LOGIN_OPT_h) { USE_FEATURE_UTMP( safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host)); ) - snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s' from " - "'%.200s'", short_tty, opt_host); + fromhost = xasprintf(" on '%s' from '%s'", short_tty, opt_host); } else - snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s'", short_tty); + fromhost = xasprintf(" on '%s'", short_tty); - // Was breaking "login " from shell command line: - // bb_setpgrp(); + /* Was breaking "login " from shell command line: */ + /*bb_setpgrp();*/ openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); while (1) { + /* flush away any type-ahead (as getty does) */ + ioctl(0, TCFLSH, TCIFLUSH); + if (!username[0]) get_username_or_die(username, sizeof(username)); +#if ENABLE_PAM + pamret = pam_start("login", username, &conv, &pamh); + if (pamret != PAM_SUCCESS) { + failed_msg = "start"; + goto pam_auth_failed; + } + /* set TTY (so things like securetty work) */ + pamret = pam_set_item(pamh, PAM_TTY, short_tty); + if (pamret != PAM_SUCCESS) { + 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) + */ + } + /* check that the account is healthy */ + pamret = pam_acct_mgmt(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "acct_mgmt"; + goto pam_auth_failed; + } + /* read user back */ + pamuser = NULL; + /* gcc: "dereferencing type-punned pointer breaks aliasing rules..." + * thus we cast to (void*) */ + if (pam_get_item(pamh, PAM_USER, (void*)&pamuser) != PAM_SUCCESS) { + failed_msg = "get_item(USER)"; + goto pam_auth_failed; + } + if (!pamuser || !pamuser[0]) + goto auth_failed; + safe_strncpy(username, pamuser, sizeof(username)); + /* Don't use "pw = getpwnam(username);", + * PAM is said to be capable of destroying static storage + * used by getpwnam(). We are using safe(r) function */ + pw = NULL; + getpwnam_r(username, &pwdstruct, pwdbuf, sizeof(pwdbuf), &pw); + if (!pw) + goto auth_failed; + pamret = pam_open_session(pamh, 0); + if (pamret != PAM_SUCCESS) { + failed_msg = "open_session"; + goto pam_auth_failed; + } + pamret = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (pamret != PAM_SUCCESS) { + failed_msg = "setcred"; + goto pam_auth_failed; + } + break; /* success, continue login process */ + + pam_auth_failed: + bb_error_msg("pam_%s call failed: %s (%d)", failed_msg, + pam_strerror(pamh, pamret), pamret); + safe_strncpy(username, "UNKNOWN", sizeof(username)); +#else /* not PAM */ pw = getpwnam(username); if (!pw) { strcpy(username, "UNKNOWN"); @@ -296,9 +377,11 @@ int login_main(int argc, char **argv) /* authorization takes place here */ if (correct_password(pw)) break; +#endif /* ENABLE_PAM */ auth_failed: opt &= ~LOGIN_OPT_f; bb_do_delay(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", @@ -309,11 +392,12 @@ int login_main(int argc, char **argv) } alarm(0); - die_if_nologin_and_non_root(pw->pw_uid == 0); + if (!amroot) + die_if_nologin(); write_utent(&utent, username); -#ifdef CONFIG_SELINUX +#if ENABLE_SELINUX if (is_selinux_enabled()) { security_context_t old_tty_sid, new_tty_sid; @@ -341,7 +425,8 @@ int login_main(int argc, char **argv) fchown(0, pw->pw_uid, pw->pw_gid); fchmod(0, 0600); - if (ENABLE_LOGIN_SCRIPTS) { + /* 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"); @@ -352,9 +437,12 @@ int login_main(int argc, char **argv) xsetenv("LOGIN_UID", utoa(pw->pw_uid)); xsetenv("LOGIN_GID", utoa(pw->pw_gid)); xsetenv("LOGIN_SHELL", pw->pw_shell); - xspawn(t_argv); /* NOMMU-friendly */ - /* All variables are unset by setup_environment */ - wait(NULL); + spawn_and_wait(t_argv); /* NOMMU-friendly */ + unsetenv("LOGIN_TTY" ); + unsetenv("LOGIN_USER" ); + unsetenv("LOGIN_UID" ); + unsetenv("LOGIN_GID" ); + unsetenv("LOGIN_SHELL"); } } @@ -362,13 +450,14 @@ int login_main(int argc, char **argv) tmp = pw->pw_shell; if (!tmp || !*tmp) tmp = DEFAULT_SHELL; - setup_environment(tmp, 1, !(opt & LOGIN_OPT_p), pw); + /* setup_environment params: shell, clear_env, change_env, pw */ + setup_environment(tmp, !(opt & LOGIN_OPT_p), 1, pw); motd(); if (pw->pw_uid == 0) syslog(LOG_INFO, "root login%s", fromhost); -#ifdef CONFIG_SELINUX +#if ENABLE_SELINUX /* 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); @@ -393,7 +482,8 @@ int login_main(int argc, char **argv) * should it leave SIGINT etc enabled or disabled? */ signal(SIGINT, SIG_DFL); - run_shell(tmp, 1, 0, 0); /* exec the shell finally */ + /* Exec login shell with no additional parameters */ + run_shell(tmp, 1, NULL, NULL); - return EXIT_FAILURE; + /* return EXIT_FAILURE; - not reached */ }