/*
 * luascrypt - Lua binding to libscrypt
 *
 * Copyright 2015 Daniel Silverstone <dsilvers@digital-scurf.org>
 *
 * Please see the file COPYING for licence details.
 */

#include <stdint.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "lua.h"
#include "lauxlib.h"

#include "libscrypt.h"

#include "base64.h"

static void
luascrypt_salt_gen(char *salt, int saltlen)
{
	int fd;
	/* We'd go with libscrypt's implementation, but since libscrypt's salt
	 * generation is time based, we cannot fully trust it to generate
	 * unique salts so to improve our chances we assume we have urandom
	 * and fall back to libscrypt's implementation if we don't.  Since the
	 * libscrypt implementation is fast, call it, and then overwrite it
	 * if we can...
	 */
	libscrypt_salt_gen(salt, saltlen);
	
	fd = open("/dev/urandom", O_RDONLY);
	if (fd >= 0) {
		read(fd, salt, saltlen); /* Ignore errors in these two calls */
		close(fd);               /* Since we have our fallback.      */
	}
}

static int
luascrypt_hash_password(lua_State *L)
{
	// password {N, r, p} -> crypted
	size_t passwd_len;
	const char *passwd = luaL_checklstring(L, 1, &passwd_len);
	char buffer[256];         /* This value is nasty here, but      */
	char salt[16];            /* while I am not normally into magic */
	uint8_t hashbuf[64];      /* numbers, these are taken from the  */
	char saltbuf[256];        /* libscrypt_hash() source.           */
	char outbuf[256];         /* Icky, I know, but what can I do?   */
	size_t bufused;
	uint32_t N = SCRYPT_N;
	uint32_t r = SCRYPT_r;
	uint32_t p = SCRYPT_p;
	if (lua_gettop(L) > 1) {
		N = (uint32_t)luaL_checknumber(L, 2);
		r = (uint32_t)luaL_checknumber(L, 3);
		p = (uint32_t)luaL_checknumber(L, 4);
	}
	
	/* We know that libscrypt is limited to N of 2^15 or less
	 * so raise an error if N is too large
	 */
	if (N > 32768) {
		return luaL_error(L, "Unable to generate password hash: %s",
				  "N is too large (limited to 2^15)");
	}
	
	luascrypt_salt_gen(salt, sizeof(salt));
	
	if (libscrypt_scrypt((uint8_t*)passwd, passwd_len,
			     (uint8_t*)salt, sizeof(salt),
			     N, r, p,
			     hashbuf, sizeof(hashbuf)) < 0) {
		return luaL_error(L, "Unable to generate password hash: %s",
				  (errno == EFBIG) ? "r and p are too large" :
				  (errno == EINVAL) ? "N is not a power of 2" :
				  (errno == ENOMEM) ? "Buffer sizes are bad" :
				  "Unknown error");
	}
	
	bufused = base64_encode(outbuf, (char *)hashbuf, sizeof(hashbuf));
	outbuf[bufused] = '\0';
	
	bufused = base64_encode(saltbuf, salt, sizeof(salt));
	saltbuf[bufused] = '\0';
	
	if (libscrypt_mcf(N, r, p, saltbuf, outbuf, buffer) < 1) {
		return luaL_error(L, "Unable to mcf encode password.");
	}


	/* some versions of libscrypt fail to include the final equals
	 * after mcf encoding -- check and if it's missing, add it back.
	 * known bad: jessie's libscrypt0 package
	 * known good: master of libscrypt as of 8th July 2015 (check date)
	 */
	{
		int oblen = strlen(outbuf);
		int bulen = strlen(buffer);
		if (outbuf[oblen-1] == outbuf[oblen-2] &&
		    outbuf[oblen-1] == '=' &&
		    buffer[bulen-1] == '=' && buffer[bulen-2] != '=') {
			strcat(buffer, "=");
		}
	}

	lua_pushstring(L, buffer);
	return 1;
}

static int
luascrypt_verify_password(lua_State *L)
{
	// crypted password -> okbool
	const char *crypted = luaL_checkstring(L, 1);
	const char *passwd  = luaL_checkstring(L, 2);
	
	/* libscrypt_check() mutates the provided crypted data, so copy it
	 * otherwise we damage the memory Lua holds.
	 */
	char *crypted_copy = (char *)lua_newuserdata(L, strlen(crypted)+1);
	strcpy(crypted_copy, crypted);
	
	int r = libscrypt_check(crypted_copy, (char *)passwd);
	
	if (r < 0) {
		return luaL_error(L, "Unable to verify password.  Bad crypt.");
	}
	
	lua_pushboolean(L, r);
	return 1;
}

static const struct luaL_Reg
luascrypt_functions[] = {
	{ "hash_password", luascrypt_hash_password },
	{ "verify_password", luascrypt_verify_password },
	{ NULL, NULL }
};

int
luaopen_scrypt(lua_State *L)
{
#if LUA_VERSION_NUM > 501
	lua_newtable(L);
	luaL_setfuncs(L, luascrypt_functions, 0);
#else
	luaL_register(L, "scrypt", luascrypt_functions);
#endif
	return 1;
}
