


#define OPENSSL_SUPPRESS_DEPRECATED

#include "eng_local.h"
#include "internal/dso.h"
#include <openssl/crypto.h>




static int dynamic_init(ENGINE *e);
static int dynamic_finish(ENGINE *e);
static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p,
                        void (*f) (void));

typedef struct st_dynamic_data_ctx dynamic_data_ctx;

static int dynamic_load(ENGINE *e, dynamic_data_ctx *ctx);

#define DYNAMIC_CMD_SO_PATH             ENGINE_CMD_BASE
#define DYNAMIC_CMD_NO_VCHECK           (ENGINE_CMD_BASE + 1)
#define DYNAMIC_CMD_ID                  (ENGINE_CMD_BASE + 2)
#define DYNAMIC_CMD_LIST_ADD            (ENGINE_CMD_BASE + 3)
#define DYNAMIC_CMD_DIR_LOAD            (ENGINE_CMD_BASE + 4)
#define DYNAMIC_CMD_DIR_ADD             (ENGINE_CMD_BASE + 5)
#define DYNAMIC_CMD_LOAD                (ENGINE_CMD_BASE + 6)


static const char *engine_dynamic_id = "dynamic";
static const char *engine_dynamic_name = "Dynamic engine loading support";
static const ENGINE_CMD_DEFN dynamic_cmd_defns[] = {
    {DYNAMIC_CMD_SO_PATH,
     "SO_PATH",
     "Specifies the path to the new ENGINE shared library",
     ENGINE_CMD_FLAG_STRING},
    {DYNAMIC_CMD_NO_VCHECK,
     "NO_VCHECK",
     "Specifies to continue even if version checking fails (boolean)",
     ENGINE_CMD_FLAG_NUMERIC},
    {DYNAMIC_CMD_ID,
     "ID",
     "Specifies an ENGINE id name for loading",
     ENGINE_CMD_FLAG_STRING},
    {DYNAMIC_CMD_LIST_ADD,
     "LIST_ADD",
     "Whether to add a loaded ENGINE to the internal list (0=no,1=yes,2=mandatory)",
     ENGINE_CMD_FLAG_NUMERIC},
    {DYNAMIC_CMD_DIR_LOAD,
     "DIR_LOAD",
     "Specifies whether to load from 'DIR_ADD' directories (0=no,1=yes,2=mandatory)",
     ENGINE_CMD_FLAG_NUMERIC},
    {DYNAMIC_CMD_DIR_ADD,
     "DIR_ADD",
     "Adds a directory from which ENGINEs can be loaded",
     ENGINE_CMD_FLAG_STRING},
    {DYNAMIC_CMD_LOAD,
     "LOAD",
     "Load up the ENGINE specified by other settings",
     ENGINE_CMD_FLAG_NO_INPUT},
    {0, NULL, NULL, 0}
};


struct st_dynamic_data_ctx {
    
    DSO *dynamic_dso;
    
    dynamic_v_check_fn v_check;
    
    dynamic_bind_engine bind_engine;
    
    char *DYNAMIC_LIBNAME;
    
    int no_vcheck;
    
    char *engine_id;
    
    int list_add_value;
    
    const char *DYNAMIC_F1;
    
    const char *DYNAMIC_F2;
    
    int dir_load;
    
    STACK_OF(OPENSSL_STRING) *dirs;
};


static int dynamic_ex_data_idx = -1;

static void int_free_str(char *s)
{
    OPENSSL_free(s);
}


static void dynamic_data_ctx_free_func(void *parent, void *ptr,
                                       CRYPTO_EX_DATA *ad, int idx, long argl,
                                       void *argp)
{
    if (ptr) {
        dynamic_data_ctx *ctx = (dynamic_data_ctx *)ptr;
        DSO_free(ctx->dynamic_dso);
        OPENSSL_free(ctx->DYNAMIC_LIBNAME);
        OPENSSL_free(ctx->engine_id);
        sk_OPENSSL_STRING_pop_free(ctx->dirs, int_free_str);
        OPENSSL_free(ctx);
    }
}


static int dynamic_set_data_ctx(ENGINE *e, dynamic_data_ctx **ctx)
{
    dynamic_data_ctx *c = OPENSSL_zalloc(sizeof(*c));
    int ret = 0;

    if (c == NULL)
        return 0;
    c->dirs = sk_OPENSSL_STRING_new_null();
    if (c->dirs == NULL) {
        ERR_raise(ERR_LIB_ENGINE, ERR_R_CRYPTO_LIB);
        goto end;
    }
    c->DYNAMIC_F1 = "v_check";
    c->DYNAMIC_F2 = "bind_engine";
    c->dir_load = 1;
    if (!CRYPTO_THREAD_write_lock(global_engine_lock))
        goto end;
    if ((*ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e,
                                                       dynamic_ex_data_idx))
        == NULL) {
        
        ret = ENGINE_set_ex_data(e, dynamic_ex_data_idx, c);
        if (ret) {
            *ctx = c;
            c = NULL;
        }
    }
    CRYPTO_THREAD_unlock(global_engine_lock);
    ret = 1;
    
end:
    if (c != NULL)
        sk_OPENSSL_STRING_free(c->dirs);
    OPENSSL_free(c);
    return ret;
}


static dynamic_data_ctx *dynamic_get_data_ctx(ENGINE *e)
{
    dynamic_data_ctx *ctx;
    if (dynamic_ex_data_idx < 0) {
        
        int new_idx = ENGINE_get_ex_new_index(0, NULL, NULL, NULL,
                                              dynamic_data_ctx_free_func);
        if (new_idx == -1) {
            ERR_raise(ERR_LIB_ENGINE, ENGINE_R_NO_INDEX);
            return NULL;
        }
        if (!CRYPTO_THREAD_write_lock(global_engine_lock))
            return NULL;
        
        if (dynamic_ex_data_idx < 0) {
            
            dynamic_ex_data_idx = new_idx;
            new_idx = -1;
        }
        CRYPTO_THREAD_unlock(global_engine_lock);
        
    }
    ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e, dynamic_ex_data_idx);
    
    if ((ctx == NULL) && !dynamic_set_data_ctx(e, &ctx))
        
        return NULL;
    return ctx;
}

static ENGINE *engine_dynamic(void)
{
    ENGINE *ret = ENGINE_new();
    if (ret == NULL)
        return NULL;
    if (!ENGINE_set_id(ret, engine_dynamic_id) ||
        !ENGINE_set_name(ret, engine_dynamic_name) ||
        !ENGINE_set_init_function(ret, dynamic_init) ||
        !ENGINE_set_finish_function(ret, dynamic_finish) ||
        !ENGINE_set_ctrl_function(ret, dynamic_ctrl) ||
        !ENGINE_set_flags(ret, ENGINE_FLAGS_BY_ID_COPY) ||
        !ENGINE_set_cmd_defns(ret, dynamic_cmd_defns)) {
        ENGINE_free(ret);
        return NULL;
    }
    return ret;
}

void engine_load_dynamic_int(void)
{
    ENGINE *toadd = engine_dynamic();
    if (!toadd)
        return;

    ERR_set_mark();
    ENGINE_add(toadd);
    
    ENGINE_free(toadd);
    
    ERR_pop_to_mark();
}

static int dynamic_init(ENGINE *e)
{
    
    return 0;
}

static int dynamic_finish(ENGINE *e)
{
    
    return 0;
}

static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
    dynamic_data_ctx *ctx = dynamic_get_data_ctx(e);
    int initialised;

    if (!ctx) {
        ERR_raise(ERR_LIB_ENGINE, ENGINE_R_NOT_LOADED);
        return 0;
    }
    initialised = ((ctx->dynamic_dso == NULL) ? 0 : 1);
    
    if (initialised) {
        ERR_raise(ERR_LIB_ENGINE, ENGINE_R_ALREADY_LOADED);
        return 0;
    }
    switch (cmd) {
    case DYNAMIC_CMD_SO_PATH:
        
        if (p && (strlen((const char *)p) < 1))
            p = NULL;
        OPENSSL_free(ctx->DYNAMIC_LIBNAME);
        if (p)
            ctx->DYNAMIC_LIBNAME = OPENSSL_strdup(p);
        else
            ctx->DYNAMIC_LIBNAME = NULL;
        return (ctx->DYNAMIC_LIBNAME ? 1 : 0);
    case DYNAMIC_CMD_NO_VCHECK:
        ctx->no_vcheck = ((i == 0) ? 0 : 1);
        return 1;
    case DYNAMIC_CMD_ID:
        
        if (p && (strlen((const char *)p) < 1))
            p = NULL;
        OPENSSL_free(ctx->engine_id);
        if (p)
            ctx->engine_id = OPENSSL_strdup(p);
        else
            ctx->engine_id = NULL;
        return (ctx->engine_id ? 1 : 0);
    case DYNAMIC_CMD_LIST_ADD:
        if ((i < 0) || (i > 2)) {
            ERR_raise(ERR_LIB_ENGINE, ENGINE_R_INVALID_ARGUMENT);
            return 0;
        }
        ctx->list_add_value = (int)i;
        return 1;
    case DYNAMIC_CMD_LOAD:
        return dynamic_load(e, ctx);
    case DYNAMIC_CMD_DIR_LOAD:
        if ((i < 0) || (i > 2)) {
            ERR_raise(ERR_LIB_ENGINE, ENGINE_R_INVALID_ARGUMENT);
            return 0;
        }
        ctx->dir_load = (int)i;
        return 1;
    case DYNAMIC_CMD_DIR_ADD:
        
        if (p == NULL || (strlen((const char *)p) < 1)) {
            ERR_raise(ERR_LIB_ENGINE, ENGINE_R_INVALID_ARGUMENT);
            return 0;
        }
        {
            char *tmp_str = OPENSSL_strdup(p);
            if (tmp_str == NULL)
                return 0;
            if (!sk_OPENSSL_STRING_push(ctx->dirs, tmp_str)) {
                OPENSSL_free(tmp_str);
                ERR_raise(ERR_LIB_ENGINE, ERR_R_CRYPTO_LIB);
                return 0;
            }
        }
        return 1;
    default:
        break;
    }
    ERR_raise(ERR_LIB_ENGINE, ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED);
    return 0;
}

static int int_load(dynamic_data_ctx *ctx)
{
    int num, loop;
    
    if ((ctx->dir_load != 2) && (DSO_load(ctx->dynamic_dso,
                                          ctx->DYNAMIC_LIBNAME, NULL,
                                          0)) != NULL)
        return 1;
    
    if (!ctx->dir_load || (num = sk_OPENSSL_STRING_num(ctx->dirs)) < 1)
        return 0;
    for (loop = 0; loop < num; loop++) {
        const char *s = sk_OPENSSL_STRING_value(ctx->dirs, loop);
        char *merge = DSO_merge(ctx->dynamic_dso, ctx->DYNAMIC_LIBNAME, s);
        if (!merge)
            return 0;
        if (DSO_load(ctx->dynamic_dso, merge, NULL, 0)) {
            
            OPENSSL_free(merge);
            return 1;
        }
        OPENSSL_free(merge);
    }
    return 0;
}


static int using_libcrypto_11(dynamic_data_ctx *ctx)
{
    int ret;

    ERR_set_mark();
    ret = DSO_bind_func(ctx->dynamic_dso, "EVP_PKEY_base_id") != NULL;
    ERR_pop_to_mark();

    return ret;
}

static int dynamic_load(ENGINE *e, dynamic_data_ctx *ctx)
{
    ENGINE cpy;
    dynamic_fns fns;

    if (ctx->dynamic_dso == NULL)
        ctx->dynamic_dso = DSO_new();
    if (ctx->dynamic_dso == NULL)
        return 0;
    if (!ctx->DYNAMIC_LIBNAME) {
        if (!ctx->engine_id)
            return 0;
        DSO_ctrl(ctx->dynamic_dso, DSO_CTRL_SET_FLAGS,
                 DSO_FLAG_NAME_TRANSLATION_EXT_ONLY, NULL);
        ctx->DYNAMIC_LIBNAME =
            DSO_convert_filename(ctx->dynamic_dso, ctx->engine_id);
    }
    if (!int_load(ctx)) {
        ERR_raise(ERR_LIB_ENGINE, ENGINE_R_DSO_NOT_FOUND);
        DSO_free(ctx->dynamic_dso);
        ctx->dynamic_dso = NULL;
        return 0;
    }
    
    if (!
        (ctx->bind_engine =
         (dynamic_bind_engine) DSO_bind_func(ctx->dynamic_dso,
                                             ctx->DYNAMIC_F2))) {
        ctx->bind_engine = NULL;
        DSO_free(ctx->dynamic_dso);
        ctx->dynamic_dso = NULL;
        ERR_raise(ERR_LIB_ENGINE, ENGINE_R_DSO_FAILURE);
        return 0;
    }
    
    if (!ctx->no_vcheck) {
        unsigned long vcheck_res = 0;
        
        ctx->v_check =
            (dynamic_v_check_fn) DSO_bind_func(ctx->dynamic_dso,
                                               ctx->DYNAMIC_F1);
        if (ctx->v_check)
            vcheck_res = ctx->v_check(OSSL_DYNAMIC_VERSION);
        
        if (vcheck_res < OSSL_DYNAMIC_OLDEST || using_libcrypto_11(ctx)) {
            
            ctx->bind_engine = NULL;
            ctx->v_check = NULL;
            DSO_free(ctx->dynamic_dso);
            ctx->dynamic_dso = NULL;
            ERR_raise(ERR_LIB_ENGINE, ENGINE_R_VERSION_INCOMPATIBILITY);
            return 0;
        }
    }
    
    memcpy(&cpy, e, sizeof(ENGINE));
    
    fns.static_state = ENGINE_get_static_state();
    CRYPTO_get_mem_functions(&fns.mem_fns.malloc_fn, &fns.mem_fns.realloc_fn,
                             &fns.mem_fns.free_fn);
    
    engine_set_all_null(e);

    
    if (!engine_add_dynamic_id(e, (ENGINE_DYNAMIC_ID)ctx->bind_engine, 1)
            || !ctx->bind_engine(e, ctx->engine_id, &fns)) {
        engine_remove_dynamic_id(e, 1);
        ctx->bind_engine = NULL;
        ctx->v_check = NULL;
        DSO_free(ctx->dynamic_dso);
        ctx->dynamic_dso = NULL;
        ERR_raise(ERR_LIB_ENGINE, ENGINE_R_INIT_FAILED);
        
        memcpy(e, &cpy, sizeof(ENGINE));
        return 0;
    }
    
    if (ctx->list_add_value > 0) {
        if (!ENGINE_add(e)) {
            
            if (ctx->list_add_value > 1) {
                
                ERR_raise(ERR_LIB_ENGINE, ENGINE_R_CONFLICTING_ENGINE_ID);
                return 0;
            }
            
            ERR_clear_error();
        }
    }
    return 1;
}
