| /* |
| * Copyright (c) 2011-2012 Hunter Morris <hunter.morris@smarkets.com> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "erl_nif.h" |
| #include "erl_blf.h" |
| #include "bcrypt_nif.h" |
| |
| void free_task(task_t* task) |
| { |
| if (task->env != NULL) |
| enif_free_env(task->env); |
| enif_free(task); |
| } |
| |
| task_t* alloc_task(task_type_t type) |
| { |
| task_t* task = (task_t*)enif_alloc(sizeof(task_t)); |
| if (task == NULL) |
| return NULL; |
| (void)memset(task, 0, sizeof(task_t)); |
| task->type = type; |
| return task; |
| } |
| |
| task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, int num_orig_terms, const ERL_NIF_TERM orig_terms[]) |
| { |
| task_t* task = alloc_task(type); |
| task->pid = pid; |
| task->env = enif_alloc_env(); |
| if (task->env == NULL) { |
| free_task(task); |
| return NULL; |
| } |
| |
| if (type == HASH) { |
| assert(num_orig_terms == 2); |
| if (!enif_inspect_iolist_as_binary( |
| task->env, enif_make_copy(task->env, orig_terms[0]), |
| &task->data.hash.salt)) { |
| free_task(task); |
| return NULL; |
| } |
| if (!enif_inspect_iolist_as_binary( |
| task->env, enif_make_copy(task->env, orig_terms[1]), |
| &task->data.hash.password)) { |
| free_task(task); |
| return NULL; |
| } |
| } |
| |
| task->ref = enif_make_copy(task->env, ref); |
| return task; |
| } |
| |
| static ERL_NIF_TERM hashpw(task_t* task) |
| { |
| char password[1024] = { 0 }; |
| char salt[1024] = { 0 }; |
| char *ret = NULL; |
| |
| size_t password_sz = 1024; |
| if (password_sz > task->data.hash.password.size) |
| password_sz = task->data.hash.password.size; |
| (void)memcpy(&password, task->data.hash.password.data, password_sz); |
| |
| size_t salt_sz = 1024; |
| if (salt_sz > task->data.hash.salt.size) |
| salt_sz = task->data.hash.salt.size; |
| (void)memcpy(&salt, task->data.hash.salt.data, salt_sz); |
| |
| if (NULL == (ret = bcrypt(password, salt)) || 0 == strcmp(ret, ":")) { |
| return enif_make_tuple3( |
| task->env, |
| enif_make_atom(task->env, "error"), |
| task->ref, |
| enif_make_string(task->env, "bcrypt failed", ERL_NIF_LATIN1)); |
| } |
| |
| return enif_make_tuple3( |
| task->env, |
| enif_make_atom(task->env, "ok"), |
| task->ref, |
| enif_make_string(task->env, ret, ERL_NIF_LATIN1)); |
| } |
| |
| void* async_worker(void* arg) |
| { |
| ctx_t* ctx; |
| task_t* task; |
| |
| ERL_NIF_TERM result; |
| |
| ctx = (ctx_t*)arg; |
| |
| while (1) { |
| task = (task_t*)async_queue_pop(ctx->queue); |
| |
| if (task->type == SHUTDOWN) { |
| free_task(task); |
| break; |
| } else if (task->type == HASH) { |
| result = hashpw(task); |
| } else { |
| errx(1, "Unexpected task type: %i", task->type); |
| } |
| |
| enif_send(NULL, &task->pid, task->env, result); |
| free_task(task); |
| } |
| |
| return NULL; |
| } |
| |
| static ERL_NIF_TERM bcrypt_encode_salt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| ErlNifBinary csalt, bin; |
| unsigned long log_rounds; |
| |
| if (!enif_inspect_binary(env, argv[0], &csalt) || 16 != csalt.size) { |
| return enif_make_badarg(env); |
| } |
| |
| if (!enif_get_ulong(env, argv[1], &log_rounds)) { |
| enif_release_binary(&csalt); |
| return enif_make_badarg(env); |
| } |
| |
| if (!enif_alloc_binary(64, &bin)) { |
| enif_release_binary(&csalt); |
| return enif_make_badarg(env); |
| } |
| |
| encode_salt((char *)bin.data, (u_int8_t*)csalt.data, csalt.size, log_rounds); |
| enif_release_binary(&csalt); |
| |
| return enif_make_string(env, (char *)bin.data, ERL_NIF_LATIN1); |
| } |
| |
| static ERL_NIF_TERM bcrypt_hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| ctx_t *ctx; |
| task_t *task; |
| ErlNifPid pid; |
| |
| if (argc != 5) |
| return enif_make_badarg(env); |
| |
| bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env); |
| |
| if (!enif_get_resource(env, argv[0], priv->bcrypt_rt, (void**)(&ctx))) |
| return enif_make_badarg(env); |
| |
| if (!enif_is_ref(env, argv[1])) |
| return enif_make_badarg(env); |
| |
| if (!enif_get_local_pid(env, argv[2], &pid)) |
| return enif_make_badarg(env); |
| |
| ERL_NIF_TERM orig_terms[] = { argv[4], argv[3] }; |
| task = alloc_init_task(HASH, argv[1], pid, 2, orig_terms); |
| |
| if (!task) |
| return enif_make_badarg(env); |
| |
| async_queue_push(ctx->queue, task); |
| |
| return enif_make_atom(env, "ok"); |
| } |
| |
| static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) |
| { |
| ERL_NIF_TERM ret; |
| bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env); |
| ctx_t* ctx = (ctx_t*)enif_alloc_resource(priv->bcrypt_rt, sizeof(ctx_t)); |
| if (ctx == NULL) |
| return enif_make_badarg(env); |
| ctx->queue = async_queue_create("bcrypt_queue_mutex", "bcrypt_queue_condvar"); |
| ctx->topts = enif_thread_opts_create("bcrypt_thread_opts"); |
| if (enif_thread_create("bcrypt_worker", &ctx->tid, async_worker, ctx, ctx->topts) != 0) { |
| enif_release_resource(ctx); |
| return enif_make_badarg(env); |
| } |
| ret = enif_make_resource(env, ctx); |
| enif_release_resource(ctx); |
| return ret; |
| } |
| |
| static ErlNifFunc bcrypt_nif_funcs[] = |
| { |
| {"encode_salt", 2, bcrypt_encode_salt}, |
| {"hashpw", 5, bcrypt_hashpw}, |
| {"create_ctx", 0, bcrypt_create_ctx}, |
| }; |
| |
| static void bcrypt_rt_dtor(ErlNifEnv* env, void* obj) |
| { |
| ctx_t *ctx = (ctx_t*)obj; |
| task_t *task = alloc_task(SHUTDOWN); |
| void *result = NULL; |
| |
| async_queue_push(ctx->queue, task); |
| enif_thread_join(ctx->tid, &result); |
| async_queue_destroy(ctx->queue); |
| enif_thread_opts_destroy(ctx->topts); |
| } |
| |
| static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) |
| { |
| const char *mod = "bcrypt_nif"; |
| const char *name = "nif_resource"; |
| |
| ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); |
| |
| bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_alloc(sizeof(bcrypt_privdata_t)); |
| priv->bcrypt_rt = enif_open_resource_type(env, mod, name, bcrypt_rt_dtor, flags, NULL); |
| if (priv->bcrypt_rt == NULL) |
| return -1; |
| *priv_data = priv; |
| return 0; |
| } |
| |
| ERL_NIF_INIT(bcrypt_nif, bcrypt_nif_funcs, &on_load, NULL, NULL, NULL) |