Current section

Files

Jump to
sasl_auth c_src sasl_auth.c
Raw

c_src/sasl_auth.c

#include <erl_nif.h>
#include <krb5.h>
#include <sasl/sasl.h>
#include <string.h>
// MIT is broken
#ifdef KRB5_KRB5_H_INCLUDED
#define KRB5KDC_ERR_KEY_EXPIRED KRB5KDC_ERR_KEY_EXP
#endif
#ifdef UNUSED
#elif defined(__GNUC__)
#define UNUSED(x) UNUSED_##x __attribute__((unused))
#elif defined(__LCLINT__)
#define UNUSED(x) /*@unused@*/ x
#else
#define UNUSED(x) x
#endif
ERL_NIF_TERM ATOM_OK;
ERL_NIF_TERM ATOM_ERROR;
ERL_NIF_TERM ATOM_OOM;
ERL_NIF_TERM ATOM_UNKNOWN;
#define OK_TUPLE(env, ret) enif_make_tuple2(env, ATOM_OK, ret)
#define SASL_STEP_TUPLE(env, code, out, outlen) \
OK_TUPLE(env, enif_make_tuple2(env, enif_make_int(env, code), str_to_bin(env, out, outlen)))
#define ERROR_TUPLE(env, ret) enif_make_tuple2(env, ATOM_ERROR, ret)
#define SASL_ERROR_TUPLE(env, state, code) \
ERROR_TUPLE(env, enif_make_tuple2(env, enif_make_int(env, code), sasl_error(env, state)));
typedef struct {
sasl_conn_t* conn;
sasl_callback_t callbacks[16];
int mech_set;
} sasl_state_t;
// SASL connection state
static ErlNifResourceType* sasl_resource = NULL;
static void destroy_resource(ErlNifEnv* UNUSED(env), sasl_state_t* res)
{
if (res != NULL) {
sasl_dispose(&res->conn);
}
}
static ErlNifResourceType* init_resource_type(ErlNifEnv* env)
{
return enif_open_resource_type(env, NULL, "sasl_auth_state",
(ErlNifResourceDtor*)destroy_resource, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, NULL);
}
static int load(ErlNifEnv* env, void** UNUSED(priv), ERL_NIF_TERM UNUSED(info))
{
ATOM_OK = enif_make_atom(env, "ok");
ATOM_ERROR = enif_make_atom(env, "error");
ATOM_OOM = enif_make_atom(env, "out_of_memory");
ATOM_UNKNOWN = enif_make_atom(env, "unknown");
sasl_resource = init_resource_type(env);
int result;
result = sasl_client_init(NULL);
return !sasl_resource && !(result == SASL_OK);
}
static int upgrade(
ErlNifEnv* UNUSED(env), void** UNUSED(priv), void** UNUSED(old_priv), ERL_NIF_TERM UNUSED(info))
{
return 0;
}
static void unload(ErlNifEnv* UNUSED(env), void* UNUSED(priv))
{
sasl_done();
return;
}
static ERL_NIF_TERM str_to_bin(ErlNifEnv* env, const char* string, unsigned long len)
{
ERL_NIF_TERM ret;
unsigned char* ptr = enif_make_new_binary(env, len, &ret);
(void)memcpy(ptr, string, len);
return ret;
}
static ERL_NIF_TERM sasl_error(ErlNifEnv* env, sasl_state_t* state)
{
const char* string = sasl_errdetail(state->conn);
return str_to_bin(env, string, strlen(string));
}
static int sasl_cyrus_cb_getsimple(void* context, int id, const char** result, unsigned int* len)
{
switch (id) {
case SASL_CB_USER:
case SASL_CB_AUTHNAME:
*result = context;
break;
default:
*result = NULL;
}
if (len) {
*len = *result ? (unsigned int)strlen(*result) : 0;
}
return *result ? SASL_OK : SASL_FAIL;
}
static ERL_NIF_TERM sasl_cli_new(ErlNifEnv* env, int UNUSED(argc), const ERL_NIF_TERM argv[])
{
ErlNifBinary service, host, principal;
sasl_state_t* state = NULL;
if ((!enif_inspect_binary(env, argv[0], &service))
|| (!enif_inspect_binary(env, argv[1], &host))
|| (!enif_inspect_binary(env, argv[2], &principal))) {
return enif_make_badarg(env);
}
state = enif_alloc_resource(sasl_resource, sizeof(sasl_state_t));
if (!state) {
enif_release_resource(state);
return ERROR_TUPLE(env, ATOM_OOM);
}
state->mech_set = 0;
sasl_callback_t callbacks[16]
= { { SASL_CB_USER, (void*)sasl_cyrus_cb_getsimple, principal.data },
{ SASL_CB_AUTHNAME, (void*)sasl_cyrus_cb_getsimple, principal.data },
{ SASL_CB_LIST_END } };
memcpy(state->callbacks, callbacks, sizeof(callbacks));
int result;
result = sasl_client_new((const char*)service.data, (const char*)host.data, NULL, NULL,
state->callbacks, 0, &state->conn);
ERL_NIF_TERM term = enif_make_resource(env, state);
switch (result) {
case SASL_OK:
enif_release_resource(state);
return OK_TUPLE(env, term);
default:
enif_release_resource(state);
return ERROR_TUPLE(env, enif_make_int(env, result));
}
}
static ERL_NIF_TERM sasl_list_mech(ErlNifEnv* env, int UNUSED(argc), const ERL_NIF_TERM argv[])
{
const char* avail_mechs;
sasl_state_t* state;
int res;
if ((!enif_get_resource(env, argv[0], sasl_resource, (void**)&state))) {
return enif_make_badarg(env);
}
res = sasl_listmech(state->conn, NULL, NULL, " ", NULL, &avail_mechs, NULL, NULL);
if (SASL_OK == res) {
return OK_TUPLE(env, str_to_bin(env, avail_mechs, strlen(avail_mechs)));
} else {
return ERROR_TUPLE(
env, enif_make_tuple2(env, enif_make_int(env, res), sasl_error(env, state)));
}
}
static ERL_NIF_TERM sasl_cli_start(ErlNifEnv* env, int UNUSED(argc), const ERL_NIF_TERM argv[])
{
sasl_state_t* state;
int result;
const char *out, *mech;
unsigned int outlen;
if ((!enif_get_resource(env, argv[0], sasl_resource, (void**)&state))) {
return enif_make_badarg(env);
}
result = sasl_client_start(state->conn, "GSSAPI", NULL, &out, &outlen, &mech);
ERL_NIF_TERM ret;
if (SASL_CONTINUE == result) {
state->mech_set = 1;
ret = SASL_STEP_TUPLE(env, result, out, (unsigned long)outlen);
} else {
ret = SASL_ERROR_TUPLE(env, state, result);
}
return ret;
}
static ERL_NIF_TERM sasl_cli_step(ErlNifEnv* env, int UNUSED(argc), const ERL_NIF_TERM argv[])
{
ErlNifBinary challenge;
sasl_state_t* state;
if ((!enif_get_resource(env, argv[0], sasl_resource, (void**)&state))
|| (!enif_inspect_binary(env, argv[1], &challenge))) {
return enif_make_badarg(env);
}
int result = 0;
const char* out;
unsigned int outlen;
ERL_NIF_TERM ret;
sasl_interact_t* interact = NULL;
if (state->mech_set) {
result
= sasl_client_step(state->conn, challenge.size > 0 ? (const char*)challenge.data : NULL,
(unsigned int)challenge.size, &interact, &out, &outlen);
switch (result) {
case SASL_OK:
ret = SASL_STEP_TUPLE(env, result, out, (unsigned long)outlen);
break;
case SASL_CONTINUE:
case SASL_INTERACT:
ret = SASL_STEP_TUPLE(env, result, out, (unsigned long)outlen);
break;
default:
ret = SASL_ERROR_TUPLE(env, state, result);
}
} else {
ret = ERROR_TUPLE(
env, enif_make_tuple2(env, enif_make_int(env, -4), str_to_bin(env, "No MECH set", 12)));
}
return ret;
}
static ERL_NIF_TERM sasl_kinit(ErlNifEnv* env, int UNUSED(argc), const ERL_NIF_TERM argv[])
{
ErlNifBinary keytab;
ErlNifBinary principal_in;
ERL_NIF_TERM ret;
ERL_NIF_TERM error_tag;
ERL_NIF_TERM error_code;
ERL_NIF_TERM error_message;
ERL_NIF_TERM error_tup;
krb5_error_code error = 0;
krb5_principal principal = NULL;
krb5_context context = NULL;
krb5_creds creds = { .magic = 0 };
krb5_keytab kt_handle = NULL;
krb5_ccache defcache = NULL;
krb5_get_init_creds_opt* options = NULL;
const char* krb_error_msg;
const char* tag;
int handle_alive = 0;
int cache_alive = 0;
if ((!enif_inspect_binary(env, argv[0], &keytab)
|| !enif_inspect_binary(env, argv[1], &principal_in)))
return enif_make_badarg(env);
if ((error = krb5_init_context(&context)) != 0) {
tag = "krb5_parse_context";
goto kinit_finish;
}
if ((error = krb5_parse_name(context, (const char*)principal_in.data, &principal)) != 0) {
tag = "krb5_parse_name";
goto kinit_finish;
}
if ((error = krb5_kt_resolve(context, (const char*)keytab.data, &kt_handle)) != 0) {
tag = "krb5_kt_resolve";
goto kinit_finish;
}
handle_alive = 1;
if ((error = krb5_cc_default(context, &defcache)) != 0) {
tag = "krb5_cc_default";
goto kinit_finish;
}
cache_alive = 1;
if ((error = krb5_get_init_creds_opt_alloc(context, &options)) != 0) {
tag = "krb5_get_init_creds_opt_alloc";
goto kinit_finish;
}
/* It's not clear why this call fails on mac. For the time being initially keytab init must
* be done on the command line */
#if (!defined __APPLE__ || !defined __MACH__)
if ((error = krb5_get_init_creds_opt_set_out_ccache(context, options, defcache)) != 0) {
tag = "krb5_get_init_creds_opt_set_out_ccache";
goto kinit_finish;
}
#endif
if ((error
= krb5_get_init_creds_keytab(context, &creds, principal, kt_handle, 0, NULL, options))
!= 0) {
tag = "krb5_get_init_creds_keytab";
goto kinit_finish;
}
kinit_finish:
if (error == 0) {
ret = ATOM_OK;
} else {
krb_error_msg = krb5_get_error_message(context, error);
error_tag = str_to_bin(env, (const char*)tag, strlen((const char*)tag));
error_code = enif_make_int(env, error);
error_message = str_to_bin(env, krb_error_msg, strlen(krb_error_msg));
error_tup = enif_make_tuple3(env, error_tag, error_code, error_message);
krb5_free_error_message(context, krb_error_msg);
ret = ERROR_TUPLE(env, error_tup);
}
if (1 == cache_alive) {
krb5_cc_close(context, defcache);
}
if (1 == handle_alive) {
krb5_kt_close(context, kt_handle);
}
krb5_free_cred_contents(context, &creds);
krb5_free_principal(context, principal);
if (options != NULL) {
krb5_get_init_creds_opt_free(context, options);
}
krb5_free_context(context);
return ret;
}
static ErlNifFunc nif_funcs[]
= { { "sasl_client_new", 3, sasl_cli_new, ERL_NIF_DIRTY_JOB_CPU_BOUND },
{ "sasl_listmech", 1, sasl_list_mech, ERL_NIF_DIRTY_JOB_CPU_BOUND },
{ "sasl_client_start", 1, sasl_cli_start, ERL_NIF_DIRTY_JOB_CPU_BOUND },
{ "sasl_client_step", 2, sasl_cli_step, ERL_NIF_DIRTY_JOB_CPU_BOUND },
{ "sasl_kinit", 2, sasl_kinit, ERL_NIF_DIRTY_JOB_CPU_BOUND } };
ERL_NIF_INIT(sasl_auth, nif_funcs, &load, NULL, &upgrade, &unload)