login: optional support for PAM

diff --git a/Makefile.flags b/Makefile.flags
index d8817be..c64a5c8 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -60,18 +60,20 @@
 endif
 
 BBOX_LIB_LIST = m crypt
+
+ifeq ($(CONFIG_PAM),y)
+BBOX_LIB_LIST += pam pam_misc
+endif
+
 ifeq ($(CONFIG_SELINUX),y)
-#LDLIBS += -lselinux -lsepol
 BBOX_LIB_LIST += selinux sepol
 endif
 
 ifeq ($(CONFIG_EFENCE),y)
-#LDLIBS += -lefence
 BBOX_LIB_LIST += efence
 endif
 
 ifeq ($(CONFIG_DMALLOC),y)
-#LDLIBS += -ldmalloc
 BBOX_LIB_LIST += dmalloc
 endif
 
diff --git a/loginutils/Config.in b/loginutils/Config.in
index 63ae9b4..6f2702e 100644
--- a/loginutils/Config.in
+++ b/loginutils/Config.in
@@ -128,6 +128,13 @@
 	  Note that Busybox binary must be setuid root for this applet to
 	  work properly.
 
+config PAM
+	bool "Support for PAM (Pluggable Authentication Modules)"
+	default n
+	depends on LOGIN
+	help
+	  Use PAM in login(1) instead of direct access to password database.
+
 config LOGIN_SCRIPTS
 	bool "Support for login scripts"
 	depends on LOGIN
diff --git a/loginutils/login.c b/loginutils/login.c
index 7b60fd0..b633a7f 100644
--- a/loginutils/login.c
+++ b/loginutils/login.c
@@ -14,6 +14,15 @@
 #include <selinux/flask.h> /* for security class definitions  */
 #endif
 
+#if ENABLE_PAM
+#include <pam/pam_appl.h>
+#include <pam/pam_misc.h>
+static const struct pam_conv conv = {
+	misc_conv,
+	NULL
+};
+#endif
+
 enum {
 	TIMEOUT = 60,
 	EMPTY_USERNAME_COUNT = 10,
@@ -125,7 +134,7 @@
 static ALWAYS_INLINE void die_if_nologin_and_non_root(int amroot) {}
 #endif
 
-#if ENABLE_FEATURE_SECURETTY
+#if ENABLE_FEATURE_SECURETTY && !ENABLE_PAM
 static int check_securetty(void)
 {
 	FILE *fp;
@@ -214,7 +223,7 @@
 		LOGIN_OPT_h = (1<<1),
 		LOGIN_OPT_p = (1<<2),
 	};
-	char fromhost[512];
+	char *fromhost;
 	char username[USERNAME_SIZE];
 	const char *tmp;
 	int amroot;
@@ -226,6 +235,9 @@
 	char full_tty[TTYNAME_SIZE];
 	USE_SELINUX(security_context_t user_sid = NULL;)
 	USE_FEATURE_UTMP(struct utmp utent;)
+	USE_PAM(pam_handle_t *pamh;)
+	USE_PAM(int pamret;)
+	USE_PAM(const char *failed_msg;)
 
 	short_tty = full_tty;
 	username[0] = '\0';
@@ -265,13 +277,12 @@
 		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 <username>" from shell command line:
-	// bb_setpgrp();
+	/* Was breaking "login <username>" from shell command line: */
+	/*bb_setpgrp();*/
 
 	openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH);
 
@@ -279,6 +290,46 @@
 		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 = "pam_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 = "pam_set_item(TTY)";
+			goto pam_auth_failed;
+		}
+		pamret = pam_authenticate(pamh, 0);
+		if (pamret == PAM_SUCCESS) {
+			char *pamuser;
+			/* check that the account is healthy. */
+			pamret = pam_acct_mgmt(pamh, 0);
+			if (pamret != PAM_SUCCESS) {
+				failed_msg = "account setup";
+				goto pam_auth_failed;
+			}
+			/* read user back */
+			/* gcc: "dereferencing type-punned pointer breaks aliasing rules..."
+			 * thus we use double cast */
+			if (pam_get_item(pamh, PAM_USER, (const void **)(void*)&pamuser) != PAM_SUCCESS) {
+				failed_msg = "pam_get_item(USER)";
+				goto pam_auth_failed;
+			}
+			safe_strncpy(username, pamuser, sizeof(username));
+		}
+		/* If we get here, the user was authenticated, and is
+		 * granted access. */
+		pw = getpwnam(username);
+		if (pw)
+			break;
+		goto auth_failed;
+ pam_auth_failed:
+		bb_error_msg("%s failed: %s", failed_msg, pam_strerror(pamh, pamret));
+		safe_strncpy(username, "UNKNOWN", sizeof(username));
+#else /* not PAM */
 		pw = getpwnam(username);
 		if (!pw) {
 			strcpy(username, "UNKNOWN");
@@ -301,6 +352,7 @@
 		/* authorization takes place here */
 		if (correct_password(pw))
 			break;
+#endif /* ENABLE_PAM */
  auth_failed:
 		opt &= ~LOGIN_OPT_f;
 		bb_do_delay(FAIL_DELAY);