← Back to context

Comment by kazinator

15 hours ago

In the TXR Lisp run-time, I spent a disproportionate amount of time on the crypt wrapper, making sure that it detects errors on numerous version of crypt, and converts them to an exception.

I settled on this. There is a block comment on the error handling, dating back to just 2023. There is a tiny infelicity in the wrapper in that we rely on the value of errno in the error path, but we do not set errno to zero and check for it being nonzero (evidence that the function set the value).

Applications using crypt directly can do the same thing: if it returns null, that's an error, of course, but don't use the result if it is less than 13 characters: that's also some kind of error token. The classic crypt() puts out a 13 character hash. Everything else is longer.

  static val crypt_wrap(val wkey, val wsalt)
  {
    val self = lit("crypt");
    const wchar_t *cwkey = c_str(wkey, self);
    const wchar_t *cwsalt = c_str(wsalt, self);
    char *key = utf8_dup_to(cwkey);
    char *salt = utf8_dup_to(cwsalt);
  #if HAVE_CRYPT_R
    struct crypt_data *cd = coerce(struct crypt_data *, chk_malloc(sizeof *cd));
    char *hash = (cd->initialized = 0, crypt_r(key, salt, cd));
  #else
    char *hash = crypt(key, salt);
  #endif

    free(key);
    free(salt);

    /* libraries cannot agree on how to report unrecognized or bad hashes:
     *
     * - older glibc versions, other libraries return null
     * - libxcrypt, integrated into newer glibc puts out two
     *   possible failure tokens "*0" or "*1", documenting
     *   that an error token starts with "*" and is less than 13
     *   characters long.
     * - musl uses "*" and "x", the latter being in the valid hash charset!
     *
     * let's go with: null or less than 13 chars means error.
     */
    if (hash != 0 && memchr(hash, 0, 13) == 0) {
      val ret = string_utf8(hash);
  #if HAVE_CRYPT_R
      free(cd);
  #endif
      return ret;
    }

  #if HAVE_CRYPT_R
    free(cd);
  #endif

    uw_ethrowf(error_s, lit("crypt failed: ~d/~s"), num(errno),
               errno_to_str(errno), nao);
  }