From 462590a5095093babba47243ba7ceb734f315a19 Mon Sep 17 00:00:00 2001
From: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Date: Fri, 19 Sep 2008 17:24:40 +0200
Subject: [PATCH] Add full blowfish support to Linux-PAM.

Linux-PAM can check blowfish encrypted passwords (if the crypto library
supports it), however it did not support new passwords to be encrypted
by blowfish. The patch adds the full blowfish support (and "blowfish" keyword)
to pam_unix.
---
 configure.in                       |    2 +-
 modules/pam_unix/pam_unix_passwd.c |    2 +-
 modules/pam_unix/passverify.c      |   44 ++++++++++++++++++++++++++---------
 modules/pam_unix/passverify.h      |    6 ++--
 modules/pam_unix/support.c         |   32 ++++++++++++++++++--------
 modules/pam_unix/support.h         |    4 ++-
 6 files changed, 62 insertions(+), 28 deletions(-)

diff --git a/configure.in b/configure.in
index b4c362c..0b929a4 100644
--- a/configure.in
+++ b/configure.in
@@ -360,7 +360,7 @@ AM_CONDITIONAL([HAVE_AUDIT_TTY_STATUS],
 AC_CHECK_HEADERS(xcrypt.h crypt.h)
 BACKUP_LIBS=$LIBS
 AC_SEARCH_LIBS([crypt],[xcrypt crypt], LIBCRYPT="-l$ac_lib", LIBCRYPT="")
-AC_CHECK_FUNCS(crypt_r)
+AC_CHECK_FUNCS(crypt_r crypt_gensalt_rn)
 LIBS=$BACKUP_LIBS
 AC_SUBST(LIBCRYPT)
 if test "$LIBCRYPT" = "-lxcrypt" -a "$ac_cv_header_xcrypt_h" = "yes" ; then
diff --git a/modules/pam_unix/pam_unix_passwd.c b/modules/pam_unix/pam_unix_passwd.c
index d221220..ea39408 100644
--- a/modules/pam_unix/pam_unix_passwd.c
+++ b/modules/pam_unix/pam_unix_passwd.c
@@ -745,7 +745,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
 		 * First we encrypt the new password.
 		 */
 
-		tpass = create_password_hash(pass_new, ctrl, rounds);
+		tpass = create_password_hash(pamh, pass_new, ctrl, rounds);
 		if (tpass == NULL) {
 			pam_syslog(pamh, LOG_CRIT,
 				"out of memory for password");
diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c
index 729d797..9563ca6 100644
--- a/modules/pam_unix/passverify.c
+++ b/modules/pam_unix/passverify.c
@@ -363,17 +363,19 @@ crypt_md5_wrapper(const char *pass_new)
         return cp;
 }
 
-char *
-create_password_hash(const char *password, unsigned int ctrl, int rounds)
+PAMH_ARG_DECL(char * create_password_hash,
+	const char *password, unsigned int ctrl, int rounds)
 {
 	const char *algoid;
 	char salt[64]; /* contains rounds number + max 16 bytes of salt + algo id */
 	char *sp;
 
 	if (on(UNIX_MD5_PASS, ctrl)) {
+		/* algoid = "$1" */
 		return crypt_md5_wrapper(password);
-	}
-	if (on(UNIX_SHA256_PASS, ctrl)) {
+	} else if (on(UNIX_BLOWFISH_PASS, ctrl)) {
+		algoid = "$2a$";
+	} else if (on(UNIX_SHA256_PASS, ctrl)) {
 		algoid = "$5$";
 	} else if (on(UNIX_SHA512_PASS, ctrl)) {
 		algoid = "$6$";
@@ -393,17 +395,35 @@ create_password_hash(const char *password, unsigned int ctrl, int rounds)
 		return crypted;
 	}
 
-	sp = stpcpy(salt, algoid);
-	if (on(UNIX_ALGO_ROUNDS, ctrl)) {
-		sp += snprintf(sp, sizeof(salt) - 3, "rounds=%u$", rounds);
+#ifdef HAVE_CRYPT_GENSALT_RN
+	if (on(UNIX_BLOWFISH_PASS, ctrl)) {
+		char entropy[17];
+		crypt_make_salt(entropy, sizeof(entropy) - 1);
+		sp = crypt_gensalt_rn(algoid, rounds,
+				      entropy, sizeof(entropy),
+				      salt, sizeof(salt));
+	} else {
+#endif
+		sp = stpcpy(salt, algoid);
+		if (on(UNIX_ALGO_ROUNDS, ctrl)) {
+			sp += snprintf(sp, sizeof(salt) - 3, "rounds=%u$", rounds);
+		}
+		crypt_make_salt(sp, 8);
+		/* For now be conservative so the resulting hashes
+		 * are not too long. 8 bytes of salt prevents dictionary
+		 * attacks well enough. */
+#ifdef HAVE_CRYPT_GENSALT_RN
 	}
-	crypt_make_salt(sp, 8);
-	/* For now be conservative so the resulting hashes
-	 * are not too long. 8 bytes of salt prevents dictionary
-	 * attacks well enough. */
+#endif
 	sp = crypt(password, salt);
 	if (strncmp(algoid, sp, strlen(algoid)) != 0) {
-	/* libc doesn't know the algorithm, use MD5 */
+		/* libxcrypt/libc doesn't know the algorithm, use MD5 */
+		pam_syslog(pamh, LOG_ERR,
+			   "Algo %s not supported by the crypto backend, "
+			   "falling back to MD5\n",
+			   on(UNIX_BLOWFISH_PASS, ctrl) ? "blowfish" :
+			   on(UNIX_SHA256_PASS, ctrl) ? "sha256" :
+			   on(UNIX_SHA512_PASS, ctrl) ? "sha512" : algoid);
 		memset(sp, '\0', strlen(sp));
 		return crypt_md5_wrapper(password);
 	}
diff --git a/modules/pam_unix/passverify.h b/modules/pam_unix/passverify.h
index 78d1b95..888548d 100644
--- a/modules/pam_unix/passverify.h
+++ b/modules/pam_unix/passverify.h
@@ -21,9 +21,6 @@ is_pwd_shadowed(const struct passwd *pwd);
 char *
 crypt_md5_wrapper(const char *pass_new);
 
-char *
-create_password_hash(const char *password, unsigned int ctrl, int rounds);
-
 int
 unix_selinux_confined(void);
 
@@ -62,6 +59,9 @@ read_passwords(int fd, int npass, char **passwords);
 #define PAMH_ARG(...)			pamh, __VA_ARGS__
 #endif
 
+PAMH_ARG_DECL(char * create_password_hash,
+	const char *password, unsigned int ctrl, int rounds);
+
 PAMH_ARG_DECL(int get_account_info,
 	const char *name, struct passwd **pwd, struct spwd **spwdent);
 
diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c
index b82cad2..400b1b9 100644
--- a/modules/pam_unix/support.c
+++ b/modules/pam_unix/support.c
@@ -109,16 +109,8 @@ int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int *rounds,
 						*remember = 400;
 				}
 			}
-			if (rounds != NULL) {
-				if (j == UNIX_ALGO_ROUNDS) {
-					*rounds = strtol(*argv + 7, NULL, 10);
-					if ((*rounds < 1000) || (*rounds == INT_MAX))
-						/* don't care about bogus values */
-						unset(UNIX_ALGO_ROUNDS, ctrl);
-					if (*rounds >= 10000000)
-						*rounds = 9999999;
-				}
-			}
+			if (rounds != NULL && j == UNIX_ALGO_ROUNDS)
+				*rounds = strtol(*argv + 7, NULL, 10);
 		}
 
 		++argv;		/* step to next argument */
@@ -128,6 +120,26 @@ int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int *rounds,
 		D(("DISALLOW_NULL_AUTHTOK"));
 		set(UNIX__NONULL, ctrl);
 	}
+	
+	/* Set default rounds for blowfish */
+	if (on(UNIX_BLOWFISH_PASS, ctrl) && off(UNIX_ALGO_ROUNDS, ctrl)) {
+		*rounds = 5;
+		set(UNIX_ALGO_ROUNDS, ctrl);
+	}
+	
+	/* Enforce sane "rounds" values */
+	if (on(UNIX_ALGO_ROUNDS, ctrl)) {
+		if (on(UNIX_BLOWFISH_PASS, ctrl)) {
+			if (*rounds < 4 || *rounds > 31)
+				*rounds = 5;
+		} else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl)) {
+			if ((*rounds < 1000) || (*rounds == INT_MAX))
+				/* don't care about bogus values */
+				unset(UNIX_ALGO_ROUNDS, ctrl);
+			if (*rounds >= 10000000)
+				*rounds = 9999999;
+		}
+	}
 
 	/* auditing is a more sensitive version of debug */
 
diff --git a/modules/pam_unix/support.h b/modules/pam_unix/support.h
index 3ccdc5c..fadd978 100644
--- a/modules/pam_unix/support.h
+++ b/modules/pam_unix/support.h
@@ -88,8 +88,9 @@ typedef struct {
 #define UNIX_SHA512_PASS         24	/* new password hashes will use SHA512 */
 #define UNIX_ALGO_ROUNDS         25	/* optional number of rounds for new 
 					   password hash algorithms */
+#define UNIX_BLOWFISH_PASS       26	/* new password hashes will use blowfish */
 /* -------------- */
-#define UNIX_CTRLS_              26	/* number of ctrl arguments defined */
+#define UNIX_CTRLS_              27	/* number of ctrl arguments defined */
 
 
 static const UNIX_Ctrls unix_args[UNIX_CTRLS_] =
@@ -123,6 +124,7 @@ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] =
 /* UNIX_SHA256_PASS */     {"sha256",        _ALL_ON_^(040420000), 020000000},
 /* UNIX_SHA512_PASS */     {"sha512",        _ALL_ON_^(020420000), 040000000},
 /* UNIX_ALGO_ROUNDS */     {"rounds=",         _ALL_ON_,          0100000000},
+/* UNIX_BLOWFISH_PASS */   {"blowfish",      _ALL_ON_^(060420000),0200000000},
 };
 
 #define UNIX_DEFAULTS  (unix_args[UNIX__NONULL].flag)
-- 
1.5.3.4

