diff options
-rwxr-xr-x | scripts/make.sh | 2 | ||||
-rw-r--r-- | toys.h | 3 | ||||
-rw-r--r-- | toys/login.c | 329 |
3 files changed, 333 insertions, 1 deletions
diff --git a/scripts/make.sh b/scripts/make.sh index 52380ecf..c5cee5bb 100755 --- a/scripts/make.sh +++ b/scripts/make.sh @@ -121,7 +121,7 @@ do_loudly() } do_loudly ${CROSS_COMPILE}${CC} $CFLAGS -I . -o toybox_unstripped $OPTIMIZE \ - main.c lib/*.c $TOYFILES -Wl,--as-needed,-lutil,--no-as-needed || exit 1 + main.c lib/*.c $TOYFILES -Wl,--as-needed,-lutil,--no-as-needed -Wl,--as-needed,-lcrypt,--no-as-needed || exit 1 do_loudly ${CROSS_COMPILE}${STRIP} toybox_unstripped -o toybox || exit 1 # gcc 4.4's strip command is buggy, and doesn't set the executable bit on # its output the way SUSv4 suggests it do so. @@ -8,6 +8,7 @@ #include "lib/portability.h" +#include <crypt.h> #include <ctype.h> #include <dirent.h> #include <errno.h> @@ -21,6 +22,7 @@ #include <pwd.h> #include <sched.h> #include <setjmp.h> +#include <shadow.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -37,6 +39,7 @@ #include <sys/types.h> #include <sys/utsname.h> #include <sys/wait.h> +#include <syslog.h> #include <unistd.h> #include <utime.h> #include <utmpx.h> diff --git a/toys/login.c b/toys/login.c new file mode 100644 index 00000000..ca62081a --- /dev/null +++ b/toys/login.c @@ -0,0 +1,329 @@ +/* vi: set sw=4 ts=4: + * + * login.c - Start a session on the system. + * + * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> + * + * Not in SUSv4. + * No support for PAM/securetty/selinux/login script/issue/utmp + * Relies on libcrypt for hash calculation. + +USE_LOGIN(NEWTOY(login, ">1fph:", TOYFLAG_BIN)) + +config LOGIN + bool "login" + default y + help + usage: login [-p] [-h host] [[-f] username] + + Establish a new session with the system. + -p Preserve environment + -h The name of the remote host for this login + -f Do not perform authentication +*/ + +#include "toys.h" + +#define LOGIN_TIMEOUT 60 +#define LOGIN_FAIL_TIMEOUT 3 +#define USER_NAME_MAX_SIZE 32 +#define HOSTNAME_SIZE 32 + +DEFINE_GLOBALS( + char * hostname; +) +#define TT this.login + +static void login_timeout_handler(int sig __attribute__((unused))) +{ + printf("\nLogin timed out after %d seconds.\n", LOGIN_TIMEOUT); + exit(0); +} + +static const char *forbid[] = { + "BASH_ENV", + "ENV", + "HOME", + "IFS", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_TRACE_LOADED_OBJECTS", + "LD_BIND_NOW", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + "LD_NOWARN", + "LD_KEEPDIR", + "SHELL", + NULL +}; + +// Unset dangerous environment variables. +void sanitize_env() +{ + const char **p = forbid; + do { + unsetenv(*p); + p++; + } while (*p); +} + +int read_password(char * buff, int buflen) +{ + int i = 0; + struct termios termio, oldtermio; + tcgetattr(0, &oldtermio); + tcflush(0, TCIFLUSH); + termio = oldtermio; + + termio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); + termio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); + tcsetattr(0, TCSANOW, &termio); + + fputs("Password: ", stdout); + fflush(stdout); + + while (1) { + int ret = read(0, &buff[i], 1); + if ( ret < 0 ) + { + buff[0] = 0; + tcsetattr(0, TCSANOW, &oldtermio); + return 1; + } + else if ( ret == 0 || buff[i] == '\n' || + buff[i] == '\r' || buflen == i+1) + { + buff[i] = '\0'; + break; + } + i++; + } + + tcsetattr(0, TCSANOW, &oldtermio); + puts("\n"); + fflush(stdout); + return 0; +} + +int verify_password(char * pwd) +{ + char * pass; + + if (read_password(toybuf, sizeof(toybuf))) + return 1; + if (!pwd) + return 1; + if (pwd[0] == '!' || pwd[0] == '*') + return 1; + + pass = crypt(toybuf, pwd); + if (pass != NULL && strcmp(pass, pwd)==0) + return 0; + + return 1; +} + +void read_user(char * buff, int size) +{ + char hostname[HOSTNAME_SIZE+1]; + int i = 0; + hostname[HOSTNAME_SIZE] = 0; + if(!gethostname(hostname, HOSTNAME_SIZE)) + fputs(hostname, stdout); + + fputs(" login: ", stdout); + fflush(stdout); + + do { + buff[0] = getchar(); + if (buff[0] == EOF) + exit(EXIT_FAILURE); + } while (isblank(buff[0])); + + if (buff[0] != '\n') + if(!fgets(&buff[1], HOSTNAME_SIZE-1, stdin)) + _exit(1); + + while(i<HOSTNAME_SIZE-1 && isgraph(buff[i])) + { + i++; + } + buff[i] = 0; +} + +void handle_nologin(void) +{ + int fd = open("/etc/nologin", O_RDONLY); + int size; + if (fd == -1) + return; + + size = readall(fd, toybuf,sizeof(toybuf)-1); + toybuf[size] = 0; + if (!size) + puts("System closed for routine maintenance\n"); + else + puts(toybuf); + + close(fd); + fflush(stdout); + exit(EXIT_FAILURE); +} + +void handle_motd(void) +{ + int fd = open("/etc/motd", O_RDONLY); + int size; + if (fd == -1) + return; + + size = readall(fd, toybuf,sizeof(toybuf)-1); + toybuf[size] = 0; + puts(toybuf); + + close(fd); + fflush(stdout); +} + +int change_identity(const struct passwd *pwd) +{ + if (initgroups(pwd->pw_name,pwd->pw_gid)) + return 1; + if (setgid(pwd->pw_uid)) + return 1; + if (setuid(pwd->pw_uid)) + return 1; + + return 0; +} + +void spawn_shell(const char *shell) +{ + const char * exec_name = strrchr(shell,'/'); + if (exec_name) + exec_name++; + else + exec_name = shell; + + snprintf(toybuf,sizeof(toybuf)-1, "-%s", shell); + execl(shell, toybuf, NULL); + error_exit("Failed to spawn shell"); +} + +void setup_environment(const struct passwd *pwd, int clear_env) +{ + if (chdir(pwd->pw_dir)) + printf("can't chdir to home directory: %s\n", pwd->pw_dir); + + if (clear_env) + { + const char * term = getenv("TERM"); + clearenv(); + if (term) setenv("TERM", term, 1); + } + + setenv("USER", pwd->pw_name, 1); + setenv("LOGNAME", pwd->pw_name, 1); + setenv("HOME", pwd->pw_dir, 1); + setenv("SHELL", pwd->pw_shell, 1); +} + +void login_main(void) +{ + int f_flag = (toys.optflags & 4) >> 2; + int p_flag = (toys.optflags & 2) >> 1; + int h_flag = toys.optflags & 1; + char username[USER_NAME_MAX_SIZE+1]; + struct passwd * pwd = NULL; + struct spwd * spwd = NULL; + char *pass = NULL; + int auth_fail_cnt = 0; + + if (f_flag && toys.optc != 1) + error_exit("-f requires username"); + + if (geteuid() != 0 ) + error_exit("Cannot possibly work without effective root"); + + if (!isatty(0) || !isatty(1) || !isatty(2)) + error_exit("Not connected to a tty"); + + openlog("login", LOG_PID | LOG_CONS, LOG_AUTH); + signal(SIGALRM, login_timeout_handler); + alarm(LOGIN_TIMEOUT); + sanitize_env(); + + while (1) { + tcflush(0, TCIFLUSH); + + username[USER_NAME_MAX_SIZE] = 0; + if (toys.optargs[0]) + strncpy(username, toys.optargs[0], USER_NAME_MAX_SIZE); + else { + read_user(username, USER_NAME_MAX_SIZE+1); + if (username[0] == 0) + continue; + } + + pwd = getpwnam(username); + if (!pwd) + goto query_pass; // Non-existing user + + if (pwd->pw_passwd[0] == '!' || pwd->pw_passwd[0] == '*') + goto query_pass; // Locked account + + if (f_flag) + break; // Pre-authenticated + + if (pwd->pw_passwd[0] == '\0') + break; // Password-less account + + pass = pwd->pw_passwd; + if (pwd->pw_passwd[0] == 'x') { + spwd = getspnam (username); + if (spwd) + pass = spwd->sp_pwdp; + } + + query_pass: + if (!verify_password(pass)) + break; + + f_flag = 0; + syslog(LOG_WARNING, "invalid password for '%s' on %s %s %s", username, + ttyname(0), + (h_flag)?"from":"", + (h_flag)?TT.hostname:""); + + sleep(LOGIN_FAIL_TIMEOUT); + puts("Login incorrect"); + + if (++auth_fail_cnt == 3) + { + error_exit("Maximum number of tries exceeded (%d)\n", auth_fail_cnt); + } + + username[0] = 0; + pwd = NULL; + spwd = NULL; + } + + alarm(0); + + if (pwd->pw_uid) + handle_nologin(); + + if (change_identity(pwd)) + error_exit("Failed to change identity"); + + setup_environment(pwd, !p_flag); + + handle_motd(); + + syslog(LOG_INFO, "%s logged in on %s %s %s", pwd->pw_name, + ttyname(0), + (h_flag)?"from":"", + (h_flag)?TT.hostname:""); + + spawn_shell(pwd->pw_shell); +} |