diff --git a/docs/man.txt b/docs/man.txt
index 0e724586..fa4639aa 100644
--- a/docs/man.txt
+++ b/docs/man.txt
@@ -4610,6 +4610,7 @@ Parameters available:
(bool) ignore_support - Enable support for @ignoring players
(int) instr_slice - Max. uninterrupted instructions per timeslice
(str) leave_mesg - Logoff message for QUIT
+ (bool) legacy_password_hash - Use Legacy (insecure, compatible) Password Hash
(int) link_cost - Cost to link an exit
(int) listen_mlev - Mucker Level required for Listener programs
(bool) lock_envcheck - Locks check environment for properties
diff --git a/docs/mufman.html b/docs/mufman.html
index 501907ec..10b41004 100644
--- a/docs/mufman.html
+++ b/docs/mufman.html
@@ -7337,6 +7337,7 @@
SYSPARM ( s -- s )
(bool) ignore_support - Enable support for @ignoring players
(int) instr_slice - Max. uninterrupted instructions per timeslice
(str) leave_mesg - Logoff message for QUIT
+ (bool) legacy_password_hash - Use Legacy (insecure, compatible) Password Hash
(int) link_cost - Cost to link an exit
(int) listen_mlev - Mucker Level required for Listener programs
(bool) lock_envcheck - Locks check environment for properties
diff --git a/include/fbmath.h b/include/fbmath.h
index 1b7b684a..a06ff809 100644
--- a/include/fbmath.h
+++ b/include/fbmath.h
@@ -164,6 +164,32 @@ void MD5hex(void *dest, const void *orig, size_t len);
*/
int no_good(double test);
+/**
+ * Generate a PBKDF2 password hash with the given password and salt.
+ *
+ * If salt is passed as NULL, we will generate a random 10 byte salt.
+ *
+ * If the MUCK wasn't compiled with SSL, this will transparently
+ * run MD5base64.
+ *
+ * The buffer provided should be at least 40 characters long, but
+ * bigger is better. 128 should be pretty good. The entire buffer
+ * will be filled with 1 byte to spare if the hash is run (if not,
+ * @see MD5base64). If you provide your own salt, the buffer must
+ * be large enough to contain the seed + 4 bytes.
+ *
+ * Seed cannot contain a $ symbol as that is reserved.
+ *
+ * @param password the password to hash
+ * @param password_len the strlen of the password
+ * @param salt the salt portion of the hash
+ * @param salt_len the length of the salt
+ * @param buffer the buffer to put the result into
+ * @param buffer_len the size of the buffer
+ */
+void pbkdf2_hash(const char* password, int password_len, const char* salt,
+ int salt_len, char* buffer, int buffer_len);
+
/**
* Do a seeded random number generation
*
diff --git a/include/tune.h b/include/tune.h
index 9a07d94d..c9402f35 100644
--- a/include/tune.h
+++ b/include/tune.h
@@ -163,6 +163,7 @@ extern bool tp_ignore_bidirectional; /**< Tune variable */
extern bool tp_ignore_support; /**< Tune variable */
extern int tp_instr_slice; /**< Tune variable */
extern const char *tp_leave_mesg; /**< Tune variable */
+extern bool tp_legacy_password_hash; /**< Tune variable */
extern int tp_link_cost; /**< Tune variable */
extern int tp_listen_mlev; /**< Tune variable */
extern bool tp_lock_envcheck; /**< Tune variable */
diff --git a/include/tunelist.h b/include/tunelist.h
index 1033d07d..c47f6494 100644
--- a/include/tunelist.h
+++ b/include/tunelist.h
@@ -110,6 +110,7 @@ bool tp_ignore_bidirectional; /**> Described below */
bool tp_ignore_support; /**> Described below */
int tp_instr_slice; /**> Described below */
const char *tp_leave_mesg; /**> Described below */
+bool tp_legacy_password_hash; /**> Described below */
int tp_link_cost; /**> Described below */
int tp_listen_mlev; /**> Described below */
bool tp_lock_envcheck; /**> Described below */
@@ -1161,6 +1162,18 @@ struct tune_entry tune_list[] = {
MLEV_WIZARD,
true
},
+ {
+ "legacy_password_hash",
+ "Use Legacy (insecure, compatible) Password Hash",
+ "Misc",
+ "",
+ TP_TYPE_BOOLEAN,
+ .defaultval.b=false,
+ .currentval.b=&tp_legacy_password_hash,
+ MLEV_WIZARD,
+ MLEV_WIZARD,
+ true
+ },
{
"link_cost",
"Cost to link an exit",
diff --git a/src/fbmath.c b/src/fbmath.c
index d86cc4c0..a39451e4 100644
--- a/src/fbmath.c
+++ b/src/fbmath.c
@@ -18,6 +18,16 @@
#include "fbmath.h"
#include "inst.h"
+#ifdef USE_SSL
+# ifdef HAVE_OPENSSL
+# include
+# include
+# else
+# include
+# include
+# endif
+#endif
+
/**
* Generate a random floating point number
*
@@ -106,6 +116,12 @@ no_good(double test)
*
**************************************************************************/
+/*
+ * We will use this in a couple places.
+ */
+static const unsigned char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
/* The four core functions - F1 is optimized somewhat */
/* #define F1(x, y, z) (x & y | ~x & z) */
@@ -388,7 +404,6 @@ MD5hash(void *dest, const void *orig, size_t len)
static void
Base64Encode(char *outbuf, const void *inbuf, size_t inlen)
{
- const unsigned char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const unsigned char *inb = inbuf;
unsigned char *out = NULL;
size_t numb;
@@ -562,3 +577,107 @@ rnd(void *buffer)
MD5hash(digest, digest, sizeof(digest));
return (digest[0]);
}
+
+/*********************************************************************
+ *
+ * PBKDF2 Password Hashing stuff
+ *
+ *********************************************************************/
+
+#ifdef USE_SSL
+static void
+PBKDF2_HMAC_SHA_512(const char* pass, const unsigned char* salt,
+ int32_t iterations, uint32_t outputBytes,
+ char* hexResult)
+{
+ unsigned int i;
+ unsigned char digest[outputBytes];
+ PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations,
+ EVP_sha512(), outputBytes, digest);
+ for (i = 0; i < sizeof(digest); i++)
+ sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
+}
+#endif
+
+/**
+ * Generate a PBKDF2 password hash with the given password and salt.
+ *
+ * If salt is passed as NULL, we will generate a random 10 byte salt.
+ *
+ * If the MUCK wasn't compiled with SSL, this will transparently
+ * run MD5base64.
+ *
+ * The buffer provided should be at least 40 characters long, but
+ * bigger is better. 128 should be pretty good. The entire buffer
+ * will be filled with 1 byte to spare if the hash is run (if not,
+ * @see MD5base64). If you provide your own salt, the buffer must
+ * be large enough to contain the seed + 4 bytes.
+ *
+ * Seed cannot contain a $ symbol as that is reserved.
+ *
+ * @param password the password to hash
+ * @param password_len the strlen of the password
+ * @param salt the salt portion of the hash
+ * @param salt_len the length of the salt
+ * @param buffer the buffer to put the result into
+ * @param buffer_len the size of the buffer
+ */
+void
+pbkdf2_hash(const char* password, int password_len, const char* salt,
+ int salt_len, char* buffer, int buffer_len)
+{
+#ifdef USE_SSL
+ char salt_buf[11];
+ unsigned int i, digest_len;
+ unsigned char* digest;
+
+ /*
+ * Generate a salt if we need to
+ */
+ if (!salt) {
+ for (i = 0; i < 10; i++) {
+ salt_buf[i] = b64[RANDOM()%sizeof(b64)];
+ }
+
+ salt_buf[10] = '\0';
+
+ salt = salt_buf;
+ salt_len = 10;
+ }
+
+ /*
+ * Clear the buffer
+ */
+ memset(buffer, 0, buffer_len);
+
+ /*
+ * Copy the salt into the buffer along with the markers.
+ */
+ snprintf(buffer, buffer_len, "$1$%s$", salt);
+
+ /*
+ * Calculate our digest size
+ */
+ digest_len = ((buffer_len - salt_len - 4)/2);
+ digest = (unsigned char*)malloc(digest_len+1);
+
+ /*
+ * Generate a hash with the rest of the buffer. Use 1000 iterations.
+ */
+ PKCS5_PBKDF2_HMAC(password, password_len, salt, salt_len, 1000,
+ EVP_sha512(), digest_len, digest);
+
+ for (i = 0; i < digest_len; i++) {
+ sprintf(buffer + salt_len + 4 + (i * 2), "%02x", 255 & digest[i]);
+ }
+
+ free(digest);
+
+ /*
+ * That should be it! Fingers crossed
+ */
+
+#else
+ MD5base64(buffer, password, password_len);
+#endif
+}
\ No newline at end of file
diff --git a/src/player.c b/src/player.c
index a129aad9..e8ef978f 100644
--- a/src/player.c
+++ b/src/player.c
@@ -79,23 +79,50 @@ lookup_player(const char *name)
int
check_password(dbref player, const char *password)
{
- char md5buf[64];
+ char md5buf[128];
+ int len_hash = 0;
+ int len_salt = 0;
+
+ const char* salt;
+
const char *processed = password;
const char *pword = PLAYER_PASSWORD(player);
- if (password == NULL) {
- MD5base64(md5buf, "", 0);
+ if (!pword || !*pword) {
+ return 1;
+ }
+
+ /*
+ * Get the hash length
+ */
+ len_hash = strlen(pword);
+
+ /*
+ * Is it a seeded hash? If so, let's extract the seed.
+ */
+ if ((len_hash > 4) && (!strncmp(pword, "$1$", 3))) {
+ /* Figure out the seed */
+ salt = pword + 3;
+
+ for ( ; (salt[len_salt] != '$') && ((len_salt+3) < len_hash);
+ len_salt++) { }
+
+ pbkdf2_hash(password, strlen(password), salt, len_salt, md5buf,
+ sizeof(md5buf));
+
processed = md5buf;
} else {
- if (*password) {
- MD5base64(md5buf, password, strlen(password));
+ if (password == NULL) {
+ MD5base64(md5buf, "", 0);
processed = md5buf;
+ } else {
+ if (*password) {
+ MD5base64(md5buf, password, strlen(password));
+ processed = md5buf;
+ }
}
}
- if (!pword || !*pword)
- return 1;
-
if (!strcmp(pword, processed))
return 1;
@@ -128,11 +155,17 @@ set_password_raw(dbref player, const char *password)
void
set_password(dbref player, const char *password)
{
- char md5buf[64];
+ char md5buf[128];
const char *processed = password;
if (*password) {
- MD5base64(md5buf, password, strlen(password));
+ if (tp_legacy_password_hash) {
+ MD5base64(md5buf, password, strlen(password));
+ } else {
+ pbkdf2_hash(password, strlen(password), NULL, 0, md5buf,
+ sizeof(md5buf));
+ }
+
processed = md5buf;
}
@@ -375,7 +408,13 @@ void
delete_player(dbref who)
{
int result;
- char buf[BUFFER_LEN];
+ /*
+ * TODO: I increased the buffer size here to stifle a warning.
+ * The underlying error is namebuf is actually too large.
+ * The snprinft below for "Renaming %s(#%d)..." was throwing
+ * a warning.
+ */
+ char buf[BUFFER_LEN+BUFFER_LEN];
char namebuf[BUFFER_LEN];
dbref found, ren;
int j;
@@ -398,6 +437,17 @@ delete_player(dbref who)
ren = (i == who) ? found : i;
j = 0;
+ /*
+ * TODO: This, technically, can enable player names
+ * to exceed the max name length. I'm not sure
+ * we care since this should be a super rare
+ * occasion? But exceeding the max name length
+ * could cause chaos in other areas.
+ *
+ * I'm not even sure how to test this, it seems
+ * like it would be from a catastrophic DB failure.
+ */
+
do {
snprintf(namebuf, sizeof(namebuf), "%s%d", NAME(ren),
++j);