changeset 572:8a88a9e3c30b

Adding initial version of login.c
author Elie De Brauwer <eliedebrauwer@gmail.com>
date Tue, 24 Apr 2012 23:09:27 +0200
parents 1a06fcaa1775
children c42ed3601b35
files scripts/make.sh toys.h toys/login.c
diffstat 3 files changed, 333 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/make.sh	Tue Apr 24 20:44:09 2012 -0500
+++ b/scripts/make.sh	Tue Apr 24 23:09:27 2012 +0200
@@ -121,7 +121,7 @@
 }
 
 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.
--- a/toys.h	Tue Apr 24 20:44:09 2012 -0500
+++ b/toys.h	Tue Apr 24 23:09:27 2012 +0200
@@ -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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/login.c	Tue Apr 24 23:09:27 2012 +0200
@@ -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);
+}