From ab175a9a160a6aad848b0a75bc9cdf9e046635eb Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 17:36:42 -0500 Subject: [PATCH 01/10] Ensure we have a crypt implementation Improve the checks to find crypt() and error if we can't find it. Previously, this might have failed during building if there is no libcrypt, which is becoming very slightly more a possibility since glibc may be obsoleting their libcrypt implementation. Additionally, obsolete the fallback behavior if we have no crypt() implementation. Modern systems should have crypt() available, either through the standard library, or something more modern, like libxcrypt, and we shouldn't fall back to the insecure "just copy the string" crypt() implementation without explicitly being told to. --- configure.ac | 14 +++++++++++++- flake.nix | 5 +++++ src/list.c | 9 ++++----- src/oldconfig.h | 1 - 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/configure.ac b/configure.ac index 54a1f23..c11031c 100644 --- a/configure.ac +++ b/configure.ac @@ -81,8 +81,20 @@ AC_PROG_SED AC_SEARCH_LIBS([atan2], [m]) +# Figure out what crypt we have. + +# Find a crypt.h. +AC_CHECK_HEADERS([crypt]) + # Link the crypt library. -AC_SEARCH_LIBS([crypt], [crypt]) +# If we don't find a valid crypt() implementation, be loud and give up. +# We can technically use the no-op crypt that's just duplicating the string, but +# failing to an insecure mode without making the user explicitly request/allow +# it feels wrong. +AC_SEARCH_LIBS([crypt], + [crypt], + [], + AC_MSG_ERROR([cannot find a crypt() implementation])) # Generate the configure script. diff --git a/flake.nix b/flake.nix index 2eaedcd..5d46c4a 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,11 @@ # The gperf perfect hash generator for recognizing keywords. gperf + + # A more modern libcrypt. + # This is necessary, since libcrypt might be being phased out of + # glibc, and we won't get a libcrypt for free from stdenv. + libxcrypt ]; # Extra flags to pass to ./configure. diff --git a/src/list.c b/src/list.c index f60f22a..0032bd6 100644 --- a/src/list.c +++ b/src/list.c @@ -38,6 +38,10 @@ #include "utils.h" #include "hash_lookup.h" +#if HAVE_CRYPT_H +#include +#endif + #define TRY_REALLOC_TRICKS 1 Var @@ -634,7 +638,6 @@ bf_crypt(Var arglist, Byte next, void *vdata, Objid progr) { /* (string, [salt]) */ Var r; -#if HAVE_CRYPT char salt[3]; static char saltstuff[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"; @@ -650,10 +653,6 @@ bf_crypt(Var arglist, Byte next, void *vdata, Objid progr) salt[2] = '\0'; r.type = TYPE_STR; r.v.str = str_dup(crypt(arglist.v.list[1].v.str, salt)); -#else /* !HAVE_CRYPT */ - r.type = TYPE_STR; - r.v.str = str_ref(arglist.v.list[1].v.str); -#endif free_var(arglist); return make_var_pack(r); diff --git a/src/oldconfig.h b/src/oldconfig.h index f67da5b..a6b1430 100644 --- a/src/oldconfig.h +++ b/src/oldconfig.h @@ -166,7 +166,6 @@ * system provides the named functions. */ -#define HAVE_CRYPT 1 #define HAVE_MATHERR 1 #define HAVE_MKFIFO 1 #define HAVE_REMOVE 1 From 685ba2c7253c8b60cd3ad6f7cac0758bf84a7a77 Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 18:27:08 -0500 Subject: [PATCH 02/10] Fix the crypt header check --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c11031c..968caa8 100644 --- a/configure.ac +++ b/configure.ac @@ -84,7 +84,7 @@ AC_SEARCH_LIBS([atan2], [m]) # Figure out what crypt we have. # Find a crypt.h. -AC_CHECK_HEADERS([crypt]) +AC_CHECK_HEADERS([crypt.h]) # Link the crypt library. # If we don't find a valid crypt() implementation, be loud and give up. From 2d7da4237a67ae7748e677128e0bbf8f7e7a7432 Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 18:50:58 -0500 Subject: [PATCH 03/10] Fix the crypt library search --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 968caa8..d1ed744 100644 --- a/configure.ac +++ b/configure.ac @@ -92,9 +92,9 @@ AC_CHECK_HEADERS([crypt.h]) # failing to an insecure mode without making the user explicitly request/allow # it feels wrong. AC_SEARCH_LIBS([crypt], - [crypt], + [crypt xcrypt], [], - AC_MSG_ERROR([cannot find a crypt() implementation])) + [AC_MSG_ERROR([cannot find a crypt implementation])]) # Generate the configure script. From 824210bd38024a9e61e6169aa54e97ab3e74bae4 Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 18:51:27 -0500 Subject: [PATCH 04/10] Look for crypt_gensalt during configuration To call crypt(), we need to supply a salt/valid settings. It's not very easy to portably know what might be supported or available in a given crypt implementation, but there's fortunately a function more modern crypt libraries have to make salts for us. Check if it's there so we can use it. --- configure.ac | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/configure.ac b/configure.ac index d1ed744..4c33ee5 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,18 @@ AC_SEARCH_LIBS([crypt], [], [AC_MSG_ERROR([cannot find a crypt implementation])]) +# Check if we have the more modern helper function to generate a salt. +# If we do, we can get the benefits of whatever the best algorithm our crypt +# implementation has, without needing to know what algorithm it is or how we +# should generate a salt. +# If we don't find it, we fall back on the less secure DES crypt(). +AC_SEARCH_LIBS([crypt_gensalt], + [crypt xcrypt], + [AC_DEFINE([HAVE_CRYPT_GENSALT], + 1, + [Define to 1 if crypt_gensalt is available.])], + [AC_MSG_WARN([using insecure DES crypt salts as crypt_gensalt is unavailable])]) + # Generate the configure script. # (There probably shouldn't be any autoconf things after this.) From e9ce9b3ee5f0367dcd10f7ac02ef4687bfeb583f Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 19:55:08 -0500 Subject: [PATCH 05/10] Add autoconf editorconfig settings --- .editorconfig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.editorconfig b/.editorconfig index a9b978b..140760e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,6 +22,12 @@ indent_style = tab indent_style = space indent_size = 2 +# Settings for autoconf. +[*.ac] +# Indent with two spaces. +indent_style = space +indent_size = 2 + # Settings for C files. [*.{c,h}] # Indent with four spaces. From b0b8ed29dbcb49442518a7f86fc58e4be7354fa5 Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Tue, 3 Feb 2026 19:55:18 -0500 Subject: [PATCH 06/10] Allow disabling crypt_gensalt In case someone wants to avoid using crypt_gensalt if it's available, to stick with the legacy DES crypt hashes (e.g. in case they want to use the database with older versions of the engine), add a build option to disable crypt_gensalt usage. This would be more convenient as a run-time option, but that's more complicated. --- configure.ac | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 4c33ee5..052e949 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,14 @@ AC_SEARCH_LIBS([crypt], [], [AC_MSG_ERROR([cannot find a crypt implementation])]) +# Give a way to disable crypt_gensalt usage, in case someone wants to stick with +# the old DES crypt. +AC_ARG_ENABLE([crypt-gensalt], + [AS_HELP_STRING([--disable-crypt-gensalt], + [disable support for newer crypt algorithms])], + [], + [enable_crypt_gensalt=default]) + # Check if we have the more modern helper function to generate a salt. # If we do, we can get the benefits of whatever the best algorithm our crypt # implementation has, without needing to know what algorithm it is or how we @@ -103,10 +111,38 @@ AC_SEARCH_LIBS([crypt], # If we don't find it, we fall back on the less secure DES crypt(). AC_SEARCH_LIBS([crypt_gensalt], [crypt xcrypt], - [AC_DEFINE([HAVE_CRYPT_GENSALT], - 1, - [Define to 1 if crypt_gensalt is available.])], - [AC_MSG_WARN([using insecure DES crypt salts as crypt_gensalt is unavailable])]) + [crypt_gensalt_available=yes], + [crypt_gensalt_available=no]) + +# Validate our crypt_gensalt setup. +if test "x$enable_crypt_gensalt" = "xdefault"; then + # We should try to use crypt_gensalt, but it's only a warning if we can't. + use_crypt_gensalt="$crypt_gensalt_available" + if test "x$crypt_gensalt_available" = "xno"; then + # Warn that we could be using more secure salts. + AC_MSG_WARN([using insecure DES crypt salts as crypt_gensalt is unavailable]) + fi +elif test "x$enable_crypt_gensalt" = "xyes"; then + # They explicitly requested support, so die if we didn't find it. + if test "x$crypt_gensalt_available" = "xno"; then + AC_MSG_ERROR([crypt_gensalt is unavailable but newer hash salts were requested]) + fi + use_crypt_gensalt=yes +elif test "x$enable_crypt_gensalt" = "xno"; then + # Stick with the legacy salts. + # We don't need to warn about crypt_gensalt being unavailable, since we were + # asked to exclude it anyway. + use_crypt_gensalt=no +else + AC_MSG_ERROR([unrecognized value '$enable_crypt_gensalt' for --enable-crypt-gensalt]) +fi + +# If we should use crypt_gensalt, let our code know. +if test "x$use_crypt_gensalt" = "xyes"; then +AC_DEFINE([USE_CRYPT_GENSALT], + 1, + [Define to 1 if crypt_gensalt should be used.]) +fi # Generate the configure script. From 2ae23dbce7858694391f8dc932fce315d3b4ee9e Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Wed, 4 Feb 2026 01:10:23 -0500 Subject: [PATCH 07/10] Generate modern password hashes If enabled, use crypt_gensalt to make salts for our crypt() calls. This means we can always get a modern, secure hash, while maintaining backwards-compatibility. This should make passwords at rest more secure. --- src/list.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/src/list.c b/src/list.c index 0032bd6..8dac763 100644 --- a/src/list.c +++ b/src/list.c @@ -15,6 +15,8 @@ Pavel@Xerox.Com *****************************************************************************/ +#include + #include "my-ctype.h" #include "my-string.h" @@ -38,6 +40,8 @@ #include "utils.h" #include "hash_lookup.h" +#include + #if HAVE_CRYPT_H #include #endif @@ -633,28 +637,96 @@ bf_strsub(Var arglist, Byte next, void *vdata, Objid progr) } } +/* + * Generate a DES crypt salt. + */ +#if !USE_CRYPT_GENSALT +static char* +make_des_crypt_salt() +{ + static char salt[3]; + static char saltstuff[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"; + + salt[0] = saltstuff[RANDOM() % (int) strlen(saltstuff)]; + salt[1] = saltstuff[RANDOM() % (int) strlen(saltstuff)]; + salt[2] = '\0'; + + return salt; +} +#endif + static package bf_crypt(Var arglist, Byte next, void *vdata, Objid progr) { /* (string, [salt]) */ Var r; - char salt[3]; - static char saltstuff[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"; - extern const char *crypt(const char *, const char *); + const char* salt; + const char* hash; + int generated_salt = 0; if (arglist.v.list[0].v.num == 1 || strlen(arglist.v.list[2].v.str) < 2) { - salt[0] = saltstuff[RANDOM() % (int) strlen(saltstuff)]; - salt[1] = saltstuff[RANDOM() % (int) strlen(saltstuff)]; + /* Generate a salt. */ + generated_salt = 1; +#if USE_CRYPT_GENSALT + /* + * Ask crypt_gensalt for a salt for the best hash with default cost. + * We are explicitly allowed to give this pointer to crypt and not worry + * about freeing or anything. + */ + salt = crypt_gensalt(NULL, 0, NULL, 0); + + if (salt == NULL) { + /* Log whyever we failed to generate a salt and raise an error. */ + log_perror("Failed to generate crypt salt"); + free_var(arglist); + return make_raise_pack(E_QUOTA, "Failed to generate a crypt() salt", zero); + } +#else + /* Use a legacy salt. */ + salt = make_des_crypt_salt(); +#endif } else { - salt[0] = arglist.v.list[2].v.str[0]; - salt[1] = arglist.v.list[2].v.str[1]; + /* Use our second argument as a salt. */ + salt = arglist.v.list[2].v.str; } - salt[2] = '\0'; - r.type = TYPE_STR; - r.v.str = str_dup(crypt(arglist.v.list[1].v.str, salt)); + /* Hash our input. */ + hash = crypt(arglist.v.list[1].v.str, salt); + + /* Free our arguments since we don't need them later. */ free_var(arglist); + + if (hash == NULL) { + /* Crypt failed for some reason. */ + if (errno == EINVAL) { + /* We had an invalid salt. */ + if (generated_salt) { + /* Log a bit more specifically. */ + log_perror("crypt rejected generated salt"); + } else { + /* + * Don't log user-supplied salts being wrong, but do tell the + * user that explicitly since they were the one to supply it. + */ + return make_raise_pack(E_INVARG, "Invalid crypt() salt", zero); + } + } else { + /* Log whatever other error this is. */ + log_perror("crypt failed"); + } + /* Report the error. */ + return make_raise_pack(E_QUOTA, "Call to crypt() failed", zero); + } else if (hash[0] == '*') { + /* For an unspecified reason, hashing failed. */ + errlog("crypt returned an invalid/error hash starting with *"); + return make_raise_pack(E_QUOTA, "Call to crypt() failed", zero); + } + + /* Build up our return object. */ + r.type = TYPE_STR; + r.v.str = str_dup(hash); + return make_var_pack(r); } From cc435b567d4f83fe19b4b3dc2f842e89d7d306de Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Wed, 4 Feb 2026 01:12:22 -0500 Subject: [PATCH 08/10] Enable legacy hashes for nix builds Make sure that libxcrypt has all hashes enabled for nix builds, so that if a database has DES hashes, crypt won't refuse to process them. --- flake.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 5d46c4a..776cba2 100644 --- a/flake.nix +++ b/flake.nix @@ -69,7 +69,9 @@ # A more modern libcrypt. # This is necessary, since libcrypt might be being phased out of # glibc, and we won't get a libcrypt for free from stdenv. - libxcrypt + # We enable all hashes, so that databases with old DES hashes don't + # break. + (libxcrypt.override { enableHashes = "all"; }) ]; # Extra flags to pass to ./configure. From 19944ee9800fe4f8e53d8c68e73bbaefbe9a4e11 Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Thu, 5 Feb 2026 13:37:14 -0500 Subject: [PATCH 09/10] Update the README Add info on the crypt changes, and add tips in case an older build system gets confused by changes to configure.ac after updating. --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index 21c5354..e10eb51 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ Source code for the HellCore project, a fork of LambdaMOO. * bison * gcc * gperf +* libcrypt (any compliant implementation, libxcrypt is a good option if your + glibc no longer supplies a libcrypt and you're on a distro/in an environment + that for some reason hasn't already added a replacement) * libstdc++ * make * sed @@ -55,6 +58,49 @@ run ./autogen.sh && ./configure && make ``` +### Troubleshooting + +If you're having trouble building after updating this repository, parts of the +build system may be out of date. + +Before trying to build again, try running +```shell +./autogen.sh && make distclean +``` + +### Optional Features + +A few features can optionally be enabled/disabled when compiling. + +If you use `./build.sh`, you should get a reasonable set of defaults. + +#### More Secure Password Hashes + +If you have a more modern libcrypt implementation, such as libxcrypt, and it +provides `crypt_gensalt`, the moo will use this to generate more secure and +modern password hashes. + +This means that if your database uses the `crypt()` builtin to store passwords, +it will be able to use a format which is far more secure, as the old password +format can be be cracked fairly quickly on modern computers if someone can +manage to get a copy of your database. + +This should generally be fairly backwards-compatible with older databases, and +old password hashes will still be read fine after this change, but new passwords +hashed will be more secure. + +The new password hashes won't be properly understood by older versions of the +`crypt()` builtin, and may not be understood by versions of the moo built +without this feature, however, so if you want to disable the more secure hashes +to ensure that your database can be used with older versions of the moo binary, +you can pass `--disable-crypt-gensalt` to `./configure` when building. + +Note that if your distro has built your libcrypt provider without support for +old, insecure hashes, regardless of whether you have this feature enabled or +not, you may need to, e.g., install via your package manager a build of +libxcrypt which has support for insecure hashes enabled in order to authenticate +against password hashes from older databases. + ### USE AT YOUR OWN RISK. I DENY RESPONSIBILITY FOR: * Spontaneous hairy nose * Micropenis From 13324c9e7a6d0dbf9a3346a005a79ce123abde7e Mon Sep 17 00:00:00 2001 From: "diatomic.ge" Date: Thu, 5 Feb 2026 13:50:41 -0500 Subject: [PATCH 10/10] Add directories for various autoconf files Add a build-aux directory to store all the extra scripts autoconf and automake add to help the build process without cluttering up the main directory. Additionally, add an m4 directory for any future m4 macros we add, or which aclocal installs, so that, e.g., users don't need to have autoconf-archive. --- .gitignore | 1 + autogen.sh | 14 ++++++++++++++ configure.ac | 9 +++++++++ m4/.keep | 0 4 files changed, 24 insertions(+) create mode 100644 m4/.keep diff --git a/.gitignore b/.gitignore index 003d75d..cb02c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ Makefile Makefile.in aclocal.m4 autom4te.cache/ +build-aux/ compile config.h config.h.in diff --git a/autogen.sh b/autogen.sh index fcba41b..cf29185 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,6 +1,20 @@ #!/bin/sh +# Die on failures. +set -e + # Regenerate the build system. # You probably shouldn't need to modify this. +# Make sure we're in the project directory. +cd "$(dirname "$0")" + +# Make sure we've got an m4 macro directory. +mkdir -p m4 build-aux + +# If we use macros available on the system, copy them into our m4 directory if +# we don't have them, or if the system has a newer version. +aclocal --install + +# Regenerate our build system. autoreconf --install --force diff --git a/configure.ac b/configure.ac index 052e949..7efe531 100644 --- a/configure.ac +++ b/configure.ac @@ -19,11 +19,20 @@ AC_INIT([HellCore], [https://github.com/diatomic-ge/HellCore]) +# Put extra build tools in the build-aux directory to keep things tidy. +AC_CONFIG_AUX_DIR([build-aux]) + + # Initialize automake. # foreign: Tell automake not to expect the standard GNU files (NEWS, # AUTHORS, etc.), and not to error when it doesn't find them. AM_INIT_AUTOMAKE([foreign]) + +# Keep all our autoconf macros in the m4 directory. +AC_CONFIG_MACRO_DIRS([m4]) + + # By default, just show what's being compiled while building, rather than the # whole command. # (This can be overridden by passing --disable-silent-rules to configure, or by diff --git a/m4/.keep b/m4/.keep new file mode 100644 index 0000000..e69de29