OpenSSL Engine加載


author: "Techsum"
title: "OpenSSL Engine加載"
date: 2020-05-07T14:03:23
description: "OpenSSL Engine插件的加載過(guò)程源碼分析"
draft: false
hideToc: false
enableToc: true
enableTocContent: false
author: Techsum
categories:

  • 密碼學(xué)
    tags:
  • OpenSSL

問(wèn)題來(lái)源

OpenSSL Engine是啥喇嘱,在這個(gè)地方就不細(xì)說(shuō)了茉贡,資料很多,可以看看知乎這篇中文文檔:

https://zhuanlan.zhihu.com/p/70444766

英文文檔:

https://wiki.openssl.org/index.php/Creating_an_OpenSSL_Engine_to_use_indigenous_ECDH_ECDSA_and_HASH_Algorithms#Author

直接進(jìn)入正題者铜,我們首先查看一個(gè)OpenSSL Engine的例子:

https://github.com/nibrunie/OSSL_EngineX

直接查看bind代碼:

static int bind(ENGINE* e, const char* id) 
{
  int ret = 0;
  if (!ENGINE_set_id(e, engine_id)) {
    fprintf(stderr, "ENGINE_set_id failed\n");
    goto end;
  }
  if (!ENGINE_set_name(e, engine_name)) {
    printf("ENGINE_set_name failed\n");
    goto end;
  }
  if (!ENGINE_set_digests(e, digest_selector)) {
    printf("ENGINE_set_digest failed\n");
    goto end;
  }

  ret = 1;
end:
  return ret;
}

IMPLEMENT_DYNAMIC_BIND_FN(bind)
IMPLEMENT_DYNAMIC_CHECK_FN()

可以看到OpenSSL去加載Engine的動(dòng)態(tài)庫(kù)時(shí)腔丧,需要?jiǎng)討B(tài)庫(kù)去調(diào)用 IMPLEMENT_DYNAMIC_BIND_FN 完成engine綁定初始化放椰。

基本上所以教你寫(xiě)engine的教程到這就結(jié)束了,但是內(nèi)部到底是怎么要關(guān)聯(lián)上這個(gè)函數(shù)愉粤,并且觸發(fā)上面的bind函數(shù)的呢砾医?我們先來(lái)看看這個(gè)宏的具體定義:

\# define IMPLEMENT_DYNAMIC_BIND_FN(fn) \
        OPENSSL_EXPORT \
        int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns); \
        OPENSSL_EXPORT \
        int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns) { \
            if (ENGINE_get_static_state() == fns->static_state) goto skip_cbs; \
            CRYPTO_set_mem_functions(fns->mem_fns.malloc_fn, \
                                     fns->mem_fns.realloc_fn, \
                                     fns->mem_fns.free_fn); \
        skip_cbs: \
            if (!fn(e, id)) return 0; \ /* 調(diào)用了上面例子中的bind函數(shù) */
            return 1; }

可以看到此處定義了函數(shù)bind_engine,他會(huì)去執(zhí)行用宏包裹住的函數(shù)衣厘,以完成初始化如蚜。然而你去搜索這個(gè)函數(shù)在OpenSSL中調(diào)用你一定會(huì)很失望,肯定沒(méi)有你想要的結(jié)果影暴。果然不是這么簡(jiǎn)單的错邦,又是什么鉤子掛在了什么ctx上吧,應(yīng)該也不難坤检。

我找了不少資料兴猩,基本沒(méi)發(fā)現(xiàn)啥靠譜的分析,沒(méi)辦法自己看源碼吧早歇。結(jié)果經(jīng)過(guò)分析倾芝,我深刻的理解了OpenSSL的魔鬼調(diào)用,鉤子的掛載可以說(shuō)是很魔幻箭跳。此處源碼分析基于目前的主線(xiàn)master晨另,應(yīng)該也是未來(lái)OpenSSL 3.0的架構(gòu)了。

至于Engine是怎么設(shè)置上重置后的密碼算法的谱姓,將在后續(xù)更新借尿。

從加載Engine的main函數(shù)分析起

還是上面的例子,我們查看執(zhí)行engine加載的可執(zhí)行程序的源碼:

int main(void)
{
  // initializing OpenSSL library
  OPENSSL_load_builtin_modules();
  ENGINE_load_dynamic();

  // building OpenSSL's configuration file path
  char openssl_cnf_path[] = "./openssl.cnf"; 

  // loading configuration
  if (CONF_modules_load_file(openssl_cnf_path, "openssl_conf", 0) != 1) {
    fprintf(stderr, "OpenSSL failed to load required configuration\n");
    ERR_print_errors_fp(stderr);
    return 1;
  }

  ENGINE* eng = ENGINE_by_id("engineX");
  if(NULL == eng) {
    printf("failed to retrieve engine by id (mppa)\n");
    return 1;
  }

  printf("EngineX has been successfully loaded \n");
  ...
}

可以看到我們這個(gè)例子是從一個(gè)cnf配置文件去加載對(duì)應(yīng)的engine的屉来,這里提一句路翻,加載engine有幾個(gè)方式,如命令行加載茄靠,手動(dòng)代碼加載等茂契。這里用配置文件加載做例子是因?yàn)檫@個(gè)場(chǎng)景更加接近實(shí)際業(yè)務(wù)場(chǎng)景,而且流程基本涵蓋全流程慨绳,值得源碼去分析掉冶。接下來(lái)我們按照調(diào)用順序來(lái)分析這樣一個(gè)漫長(zhǎng)的調(diào)用過(guò)程。

OPENSSL_load_builtin_modules

第一個(gè)函數(shù)脐雪,初始化了一個(gè)默認(rèn)的conf_module, 且名字叫做'engines'厌小。直接看源碼:

void OPENSSL_load_builtin_modules(void)
{
    ...
    /* 我們其他的都不重要,直接看這個(gè)和Engine相關(guān)的 */
#ifndef OPENSSL_NO_ENGINE
    ENGINE_add_conf_module();
#endif
    ...
}

void ENGINE_add_conf_module(void)
{
    CONF_module_add("engines",
                    int_engine_module_init, int_engine_module_finish);
}

來(lái)到我們的第一個(gè)大坑战秋,OpenSSL的動(dòng)態(tài)配置文件加載璧亚,但這里我們不需要細(xì)致了解,先簡(jiǎn)單分析下:

int CONF_module_add(const char *name, conf_init_func *ifunc,
                    conf_finish_func *ffunc)
{
    if (module_add(NULL, name, ifunc, ffunc))
        return 1;
    else
        return 0;
}

/* 重要的結(jié)構(gòu)體與全局變量 */
static STACK_OF(CONF_MODULE) *supported_modules = NULL;
static STACK_OF(CONF_IMODULE) *initialized_modules = NULL;

struct conf_module_st {
    /* DSO of this module or NULL if static */
    DSO *dso;
    /* Name of the module */
    char *name;
    /* Init function */
    conf_init_func *init;
    /* Finish function */
    conf_finish_func *finish;
    /* Number of successfully initialized modules */
    int links;
    void *usr_data;
};

typedef struct conf_module_st CONF_MODULE;


static CONF_MODULE *module_add(DSO *dso, const char *name,
                               conf_init_func *ifunc, conf_finish_func *ffunc)
{
    CONF_MODULE *tmod = NULL;
    /* 若supported_modules為空脂信, 則初始化此全局變量涨岁,即堆棧的初始化 */
    if (supported_modules == NULL)
        supported_modules = sk_CONF_MODULE_new_null();
    if (supported_modules == NULL)
        return NULL;
    /* 申請(qǐng)配置文件模塊結(jié)構(gòu)體conf_module_st的空間 */
    if ((tmod = OPENSSL_zalloc(sizeof(*tmod))) == NULL) {
        CONFerr(CONF_F_MODULE_ADD, ERR_R_MALLOC_FAILURE);
        return NULL;
    }
    
    /* 
     * 此處第一次調(diào)用拐袜,dso為NULL; 
     * dso = dynamic shared object, 可以理解為是一個(gè)OpenSSL去加載動(dòng)態(tài)庫(kù)的結(jié)構(gòu)體梢薪;
     */
    tmod->dso = dso;
    /* 此處記住蹬铺,將初始化一個(gè)叫"engines"的conf_module */
    tmod->name = OPENSSL_strdup(name);
    /* 配置文件init函數(shù), 此處即int_engine_module_init秉撇。這個(gè)函數(shù)是關(guān)鍵 */
    tmod->init = ifunc;
    /* 配置文件finish函數(shù)甜攀, 此處即int_engine_module_finish */
    tmod->finish = ffunc;
    if (tmod->name == NULL) {
        OPENSSL_free(tmod);
        return NULL;
    }

    /* 將這個(gè)的conf_module結(jié)構(gòu)體入棧進(jìn)supported_modules這個(gè)全局變量棧中 */
    if (!sk_CONF_MODULE_push(supported_modules, tmod)) {
        OPENSSL_free(tmod->name);
        OPENSSL_free(tmod);
        return NULL;
    }

    return tmod;
}

此處有一個(gè)OpenSSL的一個(gè)知識(shí)點(diǎn),OpenSSL中可以定義任意類(lèi)型的安全棧琐馆,并且生成操作這個(gè)類(lèi)型棧的函數(shù)族规阀。例如有一個(gè)結(jié)構(gòu)體叫XX,則可以通過(guò)DEFINE_STACK_OF(XX)這個(gè)宏來(lái)定義XX結(jié)構(gòu)體的棧和函數(shù)族瘦麸,通過(guò)STACK_OF(XX)來(lái)聲明一個(gè)棧谁撼。事實(shí)上,當(dāng)我們看OpenSSL源碼時(shí)看到sk_這種前綴的都是堆棧操作滋饲,而且是搜索不到實(shí)現(xiàn)的 (1.0.2版本應(yīng)該可以找到厉碟,之后的版本都泛化了,代碼寫(xiě)的秀屠缭,看代碼的自閉)箍鼓。

詳見(jiàn)官方文檔:https://www.openssl.org/docs/man1.1.0/man3/DEFINE_STACK_OF.html

此處有兩個(gè)棧操作: 初始化時(shí)supported_modules為空,所以將調(diào)用sk_CONF_MODULE_new_null先建立上一個(gè)空容器呵曹。之后sk_CONF_MODULE_push使上面初始化的的CONF_MODULE入棧款咖,之后想要取到這個(gè)module則需要通過(guò)supported_modules這個(gè)全局棧來(lái)取。

此處多提一句奄喂,OpenSSL還有一個(gè)類(lèi)似的結(jié)構(gòu)體LHASH铐殃,它是OpenSSL內(nèi)部的哈希表,如果這篇文章有下我們應(yīng)該會(huì)碰到它跨新,直接理解成是一個(gè)kv_map就好富腊。所有lh_前綴開(kāi)頭的也都是哈希表操作。

ENGINE_load_dynamic

第二個(gè)函數(shù)玻蝌,比較繞,簡(jiǎn)單理解就是:初始化了一個(gè)engine, 名字叫做dynamic词疼,OpenSSL用這個(gè)engine來(lái)動(dòng)態(tài)加載別的engine...

順便提一句俯树,ENGINE_load_dynamic 在1.1.x版本已經(jīng)廢棄了,統(tǒng)一都是調(diào)用OPENSSL_init_crypto這個(gè)函數(shù)贰盗,opts = OPENSSL_INIT_ENGINE_DYNAMIC许饿。這又是OpenSSL非常惡心的地方了,版本兼容可以說(shuō)是相當(dāng)emmmmmmmm

\# define ENGINE_load_dynamic() \
    OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_DYNAMIC, NULL)

int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
{
    ...
        /* 
         * RUN_ONCE是多線(xiàn)程時(shí)需要關(guān)心的舵盈,我們這里不關(guān)心陋率,就等于調(diào)用ossl_init_engine_dynamic 
         * 最后一波宏展開(kāi)球化,調(diào)用的是 engine_load_dynamic_int 這個(gè)函數(shù)
         */
        if ((opts & OPENSSL_INIT_ENGINE_DYNAMIC)
            && !RUN_ONCE(&engine_dynamic, ossl_init_engine_dynamic)) 
            return 0;
    ...
}

void engine_load_dynamic_int(void)
{
    ENGINE *toadd = engine_dynamic(); /* 這命名真是絕了Orz */
    if (!toadd)
        return;
    ENGINE_add(toadd);
    /*
     * If the "add" worked, it gets a structural reference. So either way, we
     * release our just-created reference.
     */
    ENGINE_free(toadd);
    /*
     * If the "add" didn't work, it was probably a conflict because it was
     * already added (eg. someone calling ENGINE_load_blah then calling
     * ENGINE_load_builtin_engines() perhaps).
     */
    ERR_clear_error();
}

engine_dynamic

兩個(gè)核心函數(shù),第一個(gè) engine_dynamic 新建了一個(gè)id叫做'dynamic'的engine瓦糟,掛上了這個(gè)engine的具體處理函數(shù):

static ENGINE *engine_dynamic(void)
{
    /* OpenSSL申請(qǐng)結(jié)構(gòu)體空間經(jīng)常使用的xx_new */
    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;
}

我們掃一眼ENGINE結(jié)構(gòu)體筒愚,首先要有一個(gè)概念,ENGINE_set_xx 就是去設(shè)置這個(gè)結(jié)構(gòu)體的相應(yīng)字段菩浙,所以可以記錄一下這個(gè)結(jié)構(gòu)體被初始化成啥樣了:

struct engine_st {
    const char *id;
    const char *name;
    const RSA_METHOD *rsa_meth;
    const DSA_METHOD *dsa_meth;
    const DH_METHOD *dh_meth;
    const EC_KEY_METHOD *ec_meth;
    const RAND_METHOD *rand_meth;
    /* Cipher handling is via this callback */
    ENGINE_CIPHERS_PTR ciphers;
    /* Digest handling is via this callback */
    ENGINE_DIGESTS_PTR digests;
    /* Public key handling via this callback */
    ENGINE_PKEY_METHS_PTR pkey_meths;
    /* ASN1 public key handling via this callback */
    ENGINE_PKEY_ASN1_METHS_PTR pkey_asn1_meths;
    ENGINE_GEN_INT_FUNC_PTR destroy;
    ENGINE_GEN_INT_FUNC_PTR init;
    ENGINE_GEN_INT_FUNC_PTR finish;
    ENGINE_CTRL_FUNC_PTR ctrl;
    ENGINE_LOAD_KEY_PTR load_privkey;
    ENGINE_LOAD_KEY_PTR load_pubkey;
    ENGINE_SSL_CLIENT_CERT_PTR load_ssl_client_cert;
    const ENGINE_CMD_DEFN *cmd_defns;
    int flags;
    /* reference count on the structure itself */
    CRYPTO_REF_COUNT struct_ref;
    /*
     * reference count on usability of the engine type. NB: This controls the
     * loading and initialisation of any functionality required by this
     * engine, whereas the previous count is simply to cope with
     * (de)allocation of this structure. Hence, running_ref <= struct_ref at
     * all times.
     */
    int funct_ref;
    /* A place to store per-ENGINE data */
    CRYPTO_EX_DATA ex_data;
    /* Used to maintain the linked-list of engines. */
    struct engine_st *prev;
    struct engine_st *next;
}

整理如下:

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}
};   /* 加載動(dòng)態(tài)engine時(shí)的命令 */

# define ENGINE_FLAGS_BY_ID_COPY         (int)0x0004

ENGINE dynamic = {.id = engine_dynamic_id,
                  .name = engine_dynamic_name,
                  .init = dynamic_init, /* 空函數(shù)巢掺,直接return 0 */
                  .finish = dynamic_finish, /* 空函數(shù)算途,直接return 0 */
                  .ctrl = dynamic_ctrl, /* 最重要的函數(shù)屁使,后文將分析如何調(diào)用到這來(lái) */
                  .flags = ENGINE_FLAGS_BY_ID_COPY,
                  .cmd_defns = dynamic_cmd_defns /*定義了dynamic這個(gè)engine ctrl下的合法cmd*/
                  .prev = NULL, .next = NULL /* 說(shuō)明engine都是以雙向鏈表形式管理 */
                 };

完成初始化后砂豌,將返回上這個(gè)new出來(lái)的ENGINE結(jié)構(gòu)體先嬉。隨后丟到ENGINE_add 里轧苫。

ENGINE_add

上面結(jié)構(gòu)體分析其實(shí)已經(jīng)可以看到,所有的engine都將以雙向鏈表形式管理疫蔓,鏈表建立簡(jiǎn)單粗暴含懊,直接定義全局變量一頭一尾,添加時(shí)就往尾巴加鳄袍,搜索就從頭結(jié)點(diǎn)開(kāi)始搜索:

static ENGINE *engine_list_head = NULL;
static ENGINE *engine_list_tail = NULL;

/* Add another "ENGINE" type into the list. */
int ENGINE_add(ENGINE *e)
{
    int to_return = 1;
    /* 一些入?yún)z查绢要,omit */
    ...
    /* 全局變量操作時(shí)需要加鎖以支持多線(xiàn)程 */
    CRYPTO_THREAD_write_lock(global_engine_lock);
    /* 核心函數(shù),將剛剛new出來(lái)的dynamic加入全局鏈表中 */
    if (!engine_list_add(e)) {
        ENGINEerr(ENGINE_F_ENGINE_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
        to_return = 0;
    }
    CRYPTO_THREAD_unlock(global_engine_lock);
    return to_return;
}

static int engine_list_add(ENGINE *e)
{
    int conflict = 0;
    ENGINE *iterator = NULL;

    if (e == NULL) {
        ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    
    /* 從鏈表頭開(kāi)始迭代 */
    iterator = engine_list_head;
    /* 直接遍歷到尾部查看有沒(méi)有重id的情況拗小,重id直接報(bào)錯(cuò)退出 */
    while (iterator && !conflict) {
        conflict = (strcmp(iterator->id, e->id) == 0);
        iterator = iterator->next;
    }
    if (conflict) {
        ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_CONFLICTING_ENGINE_ID);
        return 0;
    }
    if (engine_list_head == NULL) {
        /* We are adding to an empty list. */
        if (engine_list_tail) {
            ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
            return 0;
        }
        /* engine_list為空的話(huà)則鏈表頭為新建的engine */
        engine_list_head = e;
        e->prev = NULL;
        /*
         * The first time the list allocates, we should register the cleanup.
         */
        engine_cleanup_add_last(engine_list_cleanup);
    } else {
        /* We are adding to the tail of an existing list. */
        if ((engine_list_tail == NULL) || (engine_list_tail->next != NULL)) {
            ENGINEerr(ENGINE_F_ENGINE_LIST_ADD, ENGINE_R_INTERNAL_LIST_ERROR);
            return 0;
        }
        /* 將新engine加到隊(duì)尾的后面 */
        engine_list_tail->next = e;
        e->prev = engine_list_tail;
    }
    /*
     * Having the engine in the list assumes a structural reference.
     */
    e->struct_ref++;
    engine_ref_debug(e, 0, 1);
    /* 將隊(duì)尾指向新engine */
    engine_list_tail = e;
    e->next = NULL;
    return 1;
}

這樣重罪,id'dynamic'被加入了全局engine列表當(dāng)中,被管理起來(lái)哀九。

CONF

我們這里對(duì)OpenSSL的動(dòng)態(tài)配置conf不需要細(xì)致分析剿配,隨著代碼分析即可。官方文檔其實(shí)對(duì)conf格式講解的很清楚阅束,可以學(xué)習(xí):

https://www.openssl.org/docs/man1.1.1/man5/config.html

Engine Configuration Module這個(gè)小節(jié)

例子中conf文件

首先我們來(lái)看engineX例子中的conf是怎么寫(xiě)的:

openssl_conf            = openssl_def
[openssl_def]
engines = engine_section
[engine_section]
engine_x = engine_x_section
[engine_x_section]
engine_id = engineX
dynamic_path = ${ENV::PWD}/build/engine_ex.so 
default_algorithms = ALL
init = 1

簡(jiǎn)單學(xué)習(xí)一下conf之后呼胚,我們之后這個(gè)配置文件核心的section就是engine_section,其中dynamic_path定義上了該engine共享庫(kù)的路徑息裸。我們看看例子中是如何根據(jù)這個(gè)配置文件去加載對(duì)應(yīng)的engine的

CONF_modules_load_file

...  
  char openssl_cnf_path[] = "./openssl.cnf"; 

  // loading configuration
  if (CONF_modules_load_file(openssl_cnf_path, "openssl_conf", 0) != 1) {
    ...
  }
...

CONF_modules_load_file是去加載配置并使能配置的接口蝇更,這里我們主要關(guān)心如何去根據(jù)配置文件去加載動(dòng)態(tài)庫(kù),具體怎么完成配置文件解析的流程這里不討論呼盆。

int CONF_modules_load_file(const char *filename,
                           const char *appname, unsigned long flags)
{
    return CONF_modules_load_file_with_libctx(NULL, filename, appname, flags);
}

int CONF_modules_load_file_with_libctx(OPENSSL_CTX *libctx,
                                       const char *filename,
                                       const char *appname, unsigned long flags)
{
    char *file = NULL;
    CONF *conf = NULL;
    int ret = 0;

    conf = NCONF_new_with_libctx(libctx, NULL);
    if (conf == NULL)
        goto err;

    if (filename == NULL) {
        file = CONF_get1_default_config_file();
        if (file == NULL)
            goto err;
    } else {
        file = (char *)filename;
    }

    if (NCONF_load(conf, file, NULL) <= 0) {
        if ((flags & CONF_MFLAGS_IGNORE_MISSING_FILE) &&
            (ERR_GET_REASON(ERR_peek_last_error()) == CONF_R_NO_SUCH_FILE)) {
            ERR_clear_error();
            ret = 1;
        }
        goto err;
    }

    ret = CONF_modules_load(conf, appname, flags);

 err:
    if (filename == NULL)
        OPENSSL_free(file);
    NCONF_free(conf);

    if (flags & CONF_MFLAGS_IGNORE_RETURN_CODES)
        return 1;

    return ret;
}

可以看到這里主要有三步操作NCONF_new_with_libctx年扩、NCONF_loadCONF_modules_load访圃,我們一個(gè)一個(gè)分析厨幻。

NCONF_new_with_libctx

這個(gè)函數(shù)主要是初始化上了一個(gè)CONF結(jié)構(gòu)體,同時(shí)將這個(gè)結(jié)構(gòu)體的METHOD定義成了默認(rèn)方法。

/* 配置文件的method模板 */
struct conf_method_st {
    const char *name;
    CONF *(*create) (CONF_METHOD *meth);
    int (*init) (CONF *conf);
    int (*destroy) (CONF *conf);
    int (*destroy_data) (CONF *conf);
    int (*load_bio) (CONF *conf, BIO *bp, long *eline);
    int (*dump) (const CONF *conf, BIO *bp);
    int (*is_number) (const CONF *conf, char c);
    int (*to_int) (const CONF *conf, char c);
    int (*load) (CONF *conf, const char *name, long *eline);
};

/* 
 * 所有的 AA = BB 都會(huì)按照這個(gè)格式保存 
 * 如[openssl_def] engines = engine_section
 * 此時(shí)這個(gè)底下conf_st的哈希表中將保存上一份
 * {.section = "openssl_def", .name = "engines", value = "engine_section"}
 */
typedef struct {
    char *section;
    char *name;
    char *value;
} CONF_VALUE; 

struct conf_st {
    CONF_METHOD *meth;      /* 動(dòng)態(tài)配置的方法况脆,這里使用default */
    void *meth_data;
    LHASH_OF(CONF_VALUE) *data;    /* 上文有提到的哈希表 */
    unsigned int flag_dollarid:1;
    OPENSSL_CTX *libctx;
};

/*
 * The following section contains the "New CONF" functions.  They are
 * completely centralised around a new CONF structure that may contain
 * basically anything, but at least a method pointer and a table of data.
 * These functions are also written in terms of the bridge functions used by
 * the "CONF classic" functions, for consistency.
   */

CONF *NCONF_new_with_libctx(OPENSSL_CTX *libctx, CONF_METHOD *meth)
{
    CONF *ret;

    if (meth == NULL)
        meth = NCONF_default();
    
    ret = meth->create(meth);
    if (ret == NULL) {
        CONFerr(0, ERR_R_MALLOC_FAILURE);
        return NULL;
    }
    /* 這個(gè)流程中是NULL饭宾,不需要分析 */
    ret->libctx = libctx;
    
    return ret;

}

我們先看NCONF_default

/* 標(biāo)記上這些方法,相關(guān)定義后續(xù)會(huì)給出格了,且將會(huì)使用 */
static CONF_METHOD default_method = {
    "OpenSSL default",
    def_create,
    def_init_default,
    def_destroy,
    def_destroy_data,
    def_load_bio,
    def_dump,
    def_is_number,
    def_to_int,
    def_load
};

CONF_METHOD *NCONF_default(void)
{
    return &default_method;
}

第一個(gè)在default_method被使用的方法就是def_create, 很明顯是去申請(qǐng)一塊CONF結(jié)構(gòu)體內(nèi)存看铆,之后調(diào)用def_init_default去初始化結(jié)構(gòu)體 :

static CONF *def_create(CONF_METHOD *meth)
{
    CONF *ret;

    ret = OPENSSL_malloc(sizeof(*ret));
    if (ret != NULL)
        /* 這里調(diào)用`def_init_default` */
        if (meth->init(ret) == 0) {
            OPENSSL_free(ret);
            ret = NULL;
        }
    return ret;

}

static int def_init_default(CONF *conf)
{
    if (conf == NULL)
        return 0;

    memset(conf, 0, sizeof(*conf));
    /* 將新申請(qǐng)的CONF結(jié)構(gòu)體的method字段設(shè)置為默認(rèn)method */
    conf->meth = &default_method;
    /* meth_data的設(shè)置,這個(gè)是.conf文件字符解析時(shí)候使用的笆搓,我們這里不講 */
    conf->meth_data = (void *)CONF_type_default;

    return 1;
}
NCONF_load

初始化好CONF結(jié)構(gòu)體性湿,確定好對(duì)應(yīng)配置文件名,開(kāi)始對(duì)配置文件進(jìn)行解析满败,NCONF_load (OpenSSL連配置文件格式都自己定義自己解析肤频,硬核硬核)將調(diào)用到默認(rèn)方法之 def_load

int NCONF_load(CONF *conf, const char *file, long *eline)
{
    if (conf == NULL) {
        CONFerr(CONF_F_NCONF_LOAD, CONF_R_NO_CONF);
        return 0;
    }

    return conf->meth->load(conf, file, eline);

}

static int def_load(CONF *conf, const char *name, long *line)
{
    int ret;
    BIO *in = NULL;
    
    /* 這里通過(guò)BIO讀入文件(Binary IO, openssl自己定義的io,簡(jiǎn)單理解就是一塊內(nèi)存Orz) */
#ifdef OPENSSL_SYS_VMS
    in = BIO_new_file(name, "r");
#else
    in = BIO_new_file(name, "rb");
#endif
    ...
    
    /* 正式解析算墨,按段解析宵荒;
     * 這里不分析咋解析的,很復(fù)雜很長(zhǎng)净嘀,甚至能處理一些環(huán)境變量$(xxx)... 服
     * 最后結(jié)果都存在哈希表data中
     */
    ret = def_load_bio(conf, in, line);
    BIO_free(in);

    return ret;
}

CONF_modules_load

核心過(guò)程报咳,從CONF去加載第一部分提到的'engines'這個(gè)module:

int CONF_modules_load(const CONF *cnf, const char *appname,
                      unsigned long flags)
{
    STACK_OF(CONF_VALUE) *values;
    CONF_VALUE *vl;
    char *vsection = NULL;

    int ret, i;
    
    if (!cnf)
        return 1;
    
    /* 先獲取到對(duì)應(yīng)的section名,這里就是"openssl_conf" */
    if (appname)
        vsection = NCONF_get_string(cnf, NULL, appname);
    
    if (!appname || (!vsection && (flags & CONF_MFLAGS_DEFAULT_SECTION)))
        vsection = NCONF_get_string(cnf, NULL, "openssl_conf");
    
    if (!vsection) {
        ERR_clear_error();
        return 1;
    }
    
    OSSL_TRACE1(CONF, "Configuration in section %s\n", vsection);
    /* 
     * 找到第一個(gè)段 openssl_conf
     * [openssl_def]
     * engines = engine_section
     */
    values = NCONF_get_section(cnf, vsection);
    
    if (!values)
        return 0;
    
    for (i = 0; i < sk_CONF_VALUE_num(values); i++) {
        vl = sk_CONF_VALUE_value(values, i);
        /* 遍歷所有的value挖藏,這里只有一個(gè) 'engines' */
        ret = module_run(cnf, vl->name, vl->value, flags);
        OSSL_TRACE3(CONF, "Running module %s (%s) returned %d\n",
                    vl->name, vl->value, ret);
        if (ret <= 0)
            if (!(flags & CONF_MFLAGS_IGNORE_ERRORS))
                return ret;
    }
    
    return 1;

}

static int module_run(const CONF *cnf, const char *name, const char *value,
                      unsigned long flags)
{
    CONF_MODULE *md;
    int ret;

    if (!RUN_ONCE(&load_builtin_modules, do_load_builtin_modules))
        return -1;

    /* 這里會(huì)在supported_modules這個(gè)棧上找到'engines'這個(gè)CONF_MODULE暑刃,開(kāi)始魔幻表演 */
    md = module_find(name);
    
    ...
    /* init這個(gè)module,這里將去調(diào)用到'dynamic'這個(gè)engine膜眠,下面將分析 */
    ret = module_init(md, name, value, cnf);
    ...
    return ret;
}

/* initialize a module */
/* 此處將申請(qǐng)上一個(gè)所謂的initialized module,
 * 之后調(diào)用'engines'的init函數(shù)
 * 若成功宵膨,將'engines' push進(jìn)的全局變量棧 initialized_modules */
static int module_init(CONF_MODULE *pmod, const char *name, const char *value,
                       const CONF *cnf)
{
    int ret = 1;
    int init_called = 0;
    CONF_IMODULE *imod = NULL;

    /* Otherwise add initialized module to list */
    imod = OPENSSL_malloc(sizeof(*imod));
    if (imod == NULL)
        goto err;

    imod->pmod = pmod;
    imod->name = OPENSSL_strdup(name); /* 即'engines' */
    imod->value = OPENSSL_strdup(value);
    imod->usr_data = NULL;

    if (!imod->name || !imod->value)
        goto memerr;

    /* Try to initialize module */
    if (pmod->init) {
        /* 調(diào)用engines的init辟躏,即第一部分提到的int_engine_module_init函數(shù) */
        ret = pmod->init(imod, cnf);
        init_called = 1;
        /* Error occurred, exit */
        if (ret <= 0)
            goto err;
    }

    if (initialized_modules == NULL) {
        initialized_modules = sk_CONF_IMODULE_new_null();
        if (!initialized_modules) {
            CONFerr(CONF_F_MODULE_INIT, ERR_R_MALLOC_FAILURE);
            goto err;
        }
    }
    
    /* 將'engines' push進(jìn)的全局變量棧 initialized_modules */
    if (!sk_CONF_IMODULE_push(initialized_modules, imod)) {
        CONFerr(CONF_F_MODULE_INIT, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    pmod->links++;

    return ret;

 err:
    ...

}

CONF的第一部分處理完畢,開(kāi)始查看如何繼續(xù)解析這個(gè)配置

int_engine_module_init

這部分開(kāi)始取engines這個(gè)section下的數(shù)據(jù):

static int int_engine_module_init(CONF_IMODULE *md, const CONF *cnf)
{
    STACK_OF(CONF_VALUE) *elist;
    CONF_VALUE *cval;
    int i;
    OSSL_TRACE2(CONF, "Called engine module: name %s, value %s\n",
                CONF_imodule_get_name(md), CONF_imodule_get_value(md));
    /* Value is a section containing ENGINEs to configure */
    elist = NCONF_get_section(cnf, CONF_imodule_get_value(md));
    
    /* 
     * 獲取engine_section下的列表会涎,這里就一個(gè)section叫做engine_x_section 
     *  [engine_section]
     *  engine_x = engine_x_section
     */
    if (!elist) {
        ENGINEerr(ENGINE_F_INT_ENGINE_MODULE_INIT,
                  ENGINE_R_ENGINES_SECTION_ERROR);
        return 0;
    }
    
    for (i = 0; i < sk_CONF_VALUE_num(elist); i++) {
        cval = sk_CONF_VALUE_value(elist, i);
        /* 
         * name: engine_x, value: engine_x_section 
         * 準(zhǔn)備開(kāi)始加載了
         */
        if (!int_engine_configure(cval->name, cval->value, cnf))
            return 0;
    }
    
    return 1;

}

int_engine_configure 是加載engine的主要流程末秃,我們按順序來(lái)一步一步分析內(nèi)部的循環(huán)

int_engine_configure

  1. 首先加載上value的section:
static int int_engine_configure(const char *name, const char *value, const CONF *cnf)
{
    int i;
    int ret = 0;
    long do_init = -1;
    STACK_OF(CONF_VALUE) *ecmds;
    CONF_VALUE *ecmd = NULL;
    const char *ctrlname, *ctrlvalue;
    ENGINE *e = NULL;
    int soft = 0;

    name = skip_dot(name);
    OSSL_TRACE1(CONF, "Configuring engine %s\n", name);
    /* Value is a section containing ENGINE commands */
    /* 在conf的哈希表中找 叫做engine_x_section的section */
    ecmds = NCONF_get_section(cnf, value);
    
    /* 
     * 此時(shí)ecmds是一個(gè)棧,按順序有以下CONF_VALUE (共有section = "engine_x_section")
     * {.name = "engine_id", .value = "engineX"}
     * {.name = "dynamic_path", .value = "${ENV::PWD}/build/engine_ex.so"(這里已經(jīng)通配符解析      *  了)}
     * {.name = "default_algorithms", .value = "ALL"}
     * {.name = "init", .value = "1"}
     */
    if (!ecmds) {
        ENGINEerr(ENGINE_F_INT_ENGINE_CONFIGURE,
                  ENGINE_R_ENGINE_SECTION_ERROR);
        return 0;
    }
    ...
}
  1. 按照順序解析:

    第一個(gè)是engine_id:

    static int int_engine_configure(const char *name, const char *value, const CONF *cnf)
    {
     ...
        /* 開(kāi)始對(duì)ecmds中棧上的CONF_VALUE遍歷拨黔,這部分代碼都在這個(gè)for循環(huán)中 */
        for (i = 0; i < sk_CONF_VALUE_num(ecmds); i++) {
            ecmd = sk_CONF_VALUE_value(ecmds, i);
            /* 解析出ctrlname和ctrlvalue蛔溃,對(duì)應(yīng)結(jié)構(gòu)體中.name和.value, 下同 */
            ctrlname = skip_dot(ecmd->name);
            ctrlvalue = ecmd->value;
            OSSL_TRACE2(CONF, "ENGINE: doing ctrl(%s,%s)\n",
                        ctrlname, ctrlvalue);
    
            /* First handle some special pseudo ctrls */
    
            /* Override engine name to use */
            if (strcmp(ctrlname, "engine_id") == 0)
                /* 把name制成conf文件中engine_id */
                name = ctrlvalue;
         ...
          }
          ...
    }
    

    第二個(gè)是dynamic_path, 這個(gè)定義最關(guān)鍵篱蝇,找到這個(gè)name贺待,開(kāi)始按照指定路徑加載動(dòng)態(tài)庫(kù)engine:

    for(...) {
    ...
        else if (strcmp(ctrlname, "dynamic_path") == 0) {
                 /* 
                  * 看到這里是不是豁然開(kāi)朗,首先找到第二部分初始化的叫做dynamic的engine 
                  * 但這個(gè)地方有個(gè)值得注意的點(diǎn)零截,底下分析ENGINE_by_id
                  */
                    e = ENGINE_by_id("dynamic");
                 /* 拿到'dynamic'這個(gè)ENGINE結(jié)構(gòu)體后麸塞,進(jìn)行三步操作,完成了engineX這個(gè)so的加載 */
                 /* 之后我們將單獨(dú)把ENGINE_ctrl_cmd_string拿出來(lái)分析涧衙,觀察它是如何去加載的*/
                    if (!e)
                        goto err;
                    if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", ctrlvalue, 0))
                        goto err;
                    if (!ENGINE_ctrl_cmd_string(e, "LIST_ADD", "2", 0))
                        goto err;
                    if (!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
                        goto err;
            ...
     }
     /* 
      * 完成這三步操作后哪工,'dynamic'副本這個(gè)engine已經(jīng)被重寫(xiě)成了 'engineX'!
      * 同時(shí)這個(gè)engineX也加入了engines的隊(duì)列中弧哎。
     */
         
     ENGINE *ENGINE_by_id(const char *id)
        {
         /* 入?yún)z查和環(huán)境初始化檢查 omit */ 
            ...
            /* 加鎖后開(kāi)始遍歷鏈表雁比,匹配id = "dynamic" */
            CRYPTO_THREAD_write_lock(global_engine_lock);
            iterator = engine_list_head;
    
            while (iterator && (strcmp(id, iterator->id) != 0))
                iterator = iterator->next;
            if (iterator != NULL) {
                /*
                 * We need to return a structural reference. If this is an ENGINE
                 * type that returns copies, make a duplicate - otherwise increment
                 * the existing ENGINE's reference count.
                 */
                
                /* 匹配成功后的小操作:看ENGINE_load_dynamic源碼可以看到 dynamic->flag 被設(shè)置成了                 ENGINE_FLAGS_BY_ID_COPY */
                if (iterator->flags & ENGINE_FLAGS_BY_ID_COPY) {
                    ENGINE *cp = ENGINE_new();
                    if (cp == NULL)
                        iterator = NULL;
                    else {
                        /* 此處很重要! */
                        /* 此處取出的dynamic撤嫩,不是直接取出鏈表中的engine節(jié)點(diǎn)偎捎,而是復(fù)制了一個(gè)節(jié)點(diǎn) */
                        engine_cpy(cp, iterator);
                        iterator = cp;
                    }
                } else {
                    iterator->struct_ref++;
                    engine_ref_debug(iterator, 0, 1);
                }
           }
            CRYPTO_THREAD_unlock(global_engine_lock);
         if (iterator != NULL)
                /* 作為取出返回值,得到了一個(gè)dynamic的副本 */
             return iterator; 
     }
    

注意序攘,此時(shí)e這個(gè)局部變量已經(jīng)是一個(gè)id'engineX'的ENGINE結(jié)構(gòu)體了茴她,也就是完成了動(dòng)態(tài)加載的engine丈牢!

第三步是default_algorithms:

for (...) {
    else if (strcmp(ctrlname, "default_algorithms") == 0) {
                if (!ENGINE_set_default_string(e, ctrlvalue))
    ...
}

第四步,完成Init:

for (...) {
 if (strcmp(ctrlname, "init") == 0) {
            if (!NCONF_get_number_e(cnf, value, "init", &do_init))
                goto err;
            if (do_init == 1) {
                /* 
                 * 此處為1泛粹,完成engine init, 
                 * 具體代碼就是調(diào)用ENGINE_init去執(zhí)行e->init, 增加引用數(shù)之類(lèi)的,我們這里其實(shí)是空的 
                 * 之后去把這個(gè)engine同時(shí)加入initialized_engines這個(gè)全局變量棧中们衙。代碼不看了
                 */
                if (!int_engine_init(e))
                    goto err;
    ...
}
 

就此CONF_modules_load全部運(yùn)行完成,engineX加載完畢忆蚀。后續(xù)只需要像main函數(shù)中的使用ENGINE_by_id("engineX");就可以取得這個(gè)engine了男旗。圓滿(mǎn)。

但是 bind_engine 在哪調(diào)用的呢什荣,還是沒(méi)看到,那必然是在ENGINE_ctrl_cmd_string流程中因篇。所以下面重點(diǎn)講講這個(gè)函數(shù)。

ENGINE_ctrl_cmd_string

從cmd_name去獲取cmd_num

int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
                           int cmd_optional)
{
    int num, flags;
    long l;
    char *ptr;

 ...
     /* 宏的命名已經(jīng)暴露了一切商佑,通過(guò)cmd_name得到cmd_num */
    if (e->ctrl == NULL
        || (num = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME,
                              0, (void *)cmd_name, NULL)) <= 0) {
         ...
    }
    ...
}

int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
 ...
    /*
     * Intercept any "root-level" commands before trying to hand them on to
     * ctrl() handlers.
     */
    switch (cmd) {
    /* 這部分是通用的ctrl,范圍為10 ~ 18, 全部進(jìn)入int_ctrl_helper */
    case ENGINE_CTRL_HAS_CTRL_FUNCTION:
        return ctrl_exists;
    case ENGINE_CTRL_GET_FIRST_CMD_TYPE:
    case ENGINE_CTRL_GET_NEXT_CMD_TYPE:
    case ENGINE_CTRL_GET_CMD_FROM_NAME:
    case ENGINE_CTRL_GET_NAME_LEN_FROM_CMD:
    case ENGINE_CTRL_GET_NAME_FROM_CMD:
    case ENGINE_CTRL_GET_DESC_LEN_FROM_CMD:
    case ENGINE_CTRL_GET_DESC_FROM_CMD:
    case ENGINE_CTRL_GET_CMD_FLAGS:
        /* 
         * 這里dynamic的flag為ENGINE_FLAGS_BY_ID_COPY,0x0004 
         * ENGINE_FLAGS_MANUAL_CMD_CTRL = 0x0002笛求,與的結(jié)果為0
         */
        if (ctrl_exists && !(e->flags & ENGINE_FLAGS_MANUAL_CMD_CTRL))
            return int_ctrl_helper(e, cmd, i, p, f);
        if (!ctrl_exists) {
            ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_NO_CONTROL_FUNCTION);
            /*
             * For these cmd-related functions, failure is indicated by a -1
             * return value (because 0 is used as a valid return in some
             * places).
             */
            return -1;
        }
    default:
        break;
    }
    /* Anything else requires a ctrl() handler to exist. */
    /* 這里是確定當(dāng)前engine->ctrl != NULL */
    if (!ctrl_exists) {
        ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_NO_CONTROL_FUNCTION);
        return 0;
    }
    
    /* 調(diào)用上面看到的 dynamic->ctrl = dynamic_ctrl, 后面會(huì)調(diào)用到這來(lái) */
    return e->ctrl(e, cmd, i, p, f);
}

/* 這個(gè)函數(shù)也將反復(fù)調(diào)用(吐槽下openssl這鬼之設(shè)計(jì)),我們這里先看當(dāng)前的cmd */
static int int_ctrl_helper(ENGINE *e, int cmd, long i, void *p,
                           void (*f) (void))
{
    int idx;
    char *s = (char *)p;
    const ENGINE_CMD_DEFN *cdp;
 ...
        
    /* Now handle cmd_name -> cmd_num conversion */
    if (cmd == ENGINE_CTRL_GET_CMD_FROM_NAME) {
        /* 從dynamic的cmd_defns中去匹配cmd_name,假設(shè)是"SO_PATH", 
           直接去查第二部分的dynamic_cmd_defns,剛好匹配上idx = 0 */
        if ((e->cmd_defns == NULL)
            || ((idx = int_ctrl_cmd_by_name(e->cmd_defns, s)) < 0)) {
            ENGINEerr(ENGINE_F_INT_CTRL_HELPER, ENGINE_R_INVALID_CMD_NAME);
            return -1;
        }
        /* 查idx = 0時(shí)的 cmd_num = 200 = DYNAMIC_CMD_SO_PATH */
        return e->cmd_defns[idx].cmd_num;
    }
 ...
}

可以看到這里的num返回回來(lái)的DYNAMIC_CMD_SO_PATH植旧,是靠dynamic.cmd_defns中的ENGINE_CMD_DEFN數(shù)組表查詢(xún)得到的界阁。往下接著看ENGINE_ctrl_cmd_string

int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
                           int cmd_optional)

{
    /* 繼續(xù)調(diào)用公用ctrl贮竟,進(jìn)入到int_ctrl_helper
       (看底下開(kāi)源的注釋?zhuān)瑑蓚€(gè)函數(shù)做的ctrl操作一樣的技健,為啥這么搞也許就是未解之謎吧) */
    ...
    if (!ENGINE_cmd_is_executable(e, num)) {
        ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                  ENGINE_R_CMD_NOT_EXECUTABLE);
        return 0;
    }
 
    /* 顧名思義,拿到dynamic的flag欣孤,這里將得到idx = 0時(shí),cmd_defns表中0處的第四個(gè)元素 */
    flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, num, NULL, NULL);
    if (flags < 0) {
        /*
         * Shouldn't happen, given that ENGINE_cmd_is_executable() returned
         * success.
         */
        ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                  ENGINE_R_INTERNAL_LIST_ERROR);
        return 0;
    }
}

static int int_ctrl_helper(ENGINE *e, int cmd, long i, void *p,
                           void (*f) (void))
{
 ...
    if ((e->cmd_defns == NULL)
        || ((idx = int_ctrl_cmd_by_num(e->cmd_defns, (unsigned int)i)) < 0)) {
        ENGINEerr(ENGINE_F_INT_CTRL_HELPER, ENGINE_R_INVALID_CMD_NUMBER);
        return -1;
    }
    /* Now the logic splits depending on command type */
    cdp = &e->cmd_defns[idx];
    switch (cmd) {
 ...
    case ENGINE_CTRL_GET_CMD_FLAGS:
        /* 可以查出來(lái)上面的是 ENGINE_CMD_FLAG_STRING = 0x0002 */
        return cdp->cmd_flags;
    }
 ...
}

別問(wèn)為啥不一次查出來(lái)婆排,要多次遍歷,問(wèn)就是架構(gòu)赞枕。繼續(xù)看ENGINE_ctrl_cmd_string,終于要做真正的操作了古话, 可以看到杖们,最后進(jìn)入了dynamic_ctrl

int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
                           int cmd_optional)

{
    ... 
    /* ENGINE_CMD_FLAG_NO_INPUT = 0x0004 */
  if (flags & ENGINE_CMD_FLAG_NO_INPUT) {
        /* 如果命令查出來(lái)的flag應(yīng)該沒(méi)有arg_input, 但arg非空,直接退出???? */
        if (arg != NULL) {
            ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                      ENGINE_R_COMMAND_TAKES_NO_INPUT);
            return 0;
        }
        /*
         * We deliberately force the result of ENGINE_ctrl() to 0 or 1 rather
         * than returning it as "return data". This is to ensure usage of
         * these commands is consistent across applications and that certain
         * applications don't understand it one way, and others another.
         */
        /* 最后"LOAD"命令走的這 */
        if (ENGINE_ctrl(e, num, 0, (void *)arg, NULL) > 0)
            return 1;
        return 0;
    }
    /* So, we require input */
    if (arg == NULL) {
        ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                  ENGINE_R_COMMAND_TAKES_INPUT);
        return 0;
    }
    /* 一定有更好的寫(xiě)法吧孝治,這種判斷也太迷惑了。杭措。 */
    /* If it takes string input, that's easy */
    if (flags & ENGINE_CMD_FLAG_STRING) {
        /* Same explanation as above */
        /* 所以應(yīng)該調(diào)用到這手素,注意此時(shí)num 將大于200, 肯定不是默認(rèn)的流程泉懦,
           這就走到了return e->ctrl(e, cmd, i, p, f); 即 dynamic_ctrl */
        if (ENGINE_ctrl(e, num, 0, (void *)arg, NULL) > 0)
            return 1;
        return 0;
    }
    
    /* 此時(shí)arg是數(shù)字,需要從str轉(zhuǎn)int琢锋,LIST_ADD走這 */
     if (!(flags & ENGINE_CMD_FLAG_NUMERIC)) {
         ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                   ENGINE_R_INTERNAL_LIST_ERROR);
         return 0;
     }

     l = strtol(arg, &ptr, 10);
     if ((arg == ptr) || (*ptr != '\0')) {
         ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
                   ENGINE_R_ARGUMENT_IS_NOT_A_NUMBER);
         return 0;
     }
     /*
      * Force the result of the control command to 0 or 1, for the reasons
      * mentioned before.
      */
     if (ENGINE_ctrl(e, num, l, NULL, NULL) > 0)
         return 1;
 ...
}

所以這個(gè)函數(shù)的主要步驟就是根據(jù)輸入的cmd_namedynamic中掛載的cmd_defns取出對(duì)應(yīng)的cmd_numflag,之后用cmd_num調(diào)用到dynamic掛載的ctrl字段函數(shù)去做真正的操作鲸阻。我們用一張表統(tǒng)計(jì)下三次取到的結(jié)果:

cmd_name cmd_num flag
"SO_PATH" DYNAMIC_CMD_SO_PATH = 200 ENGINE_CMD_FLAG_STRING 0x0002
"LIST_ADD" DYNAMIC_CMD_LIST_ADD = 203 ENGINE_CMD_FLAG_NUMERIC 0x0001
"LOAD" DYNAMIC_CMD_LOAD = 206 ENGINE_CMD_FLAG_NO_INPUT 0x0004

根據(jù)這個(gè)表,我們?nèi)タ磳?duì)于dynamic->ctrldynamic_ctrl函數(shù)對(duì)這幾個(gè)cmd的操作

dynamic_ctrl

先看這個(gè)函數(shù)的公共部分细诸,對(duì)相同的engine會(huì)初始化上一個(gè)ctx上下文:

/* 動(dòng)態(tài)庫(kù)加載的上下文 */
struct st_dynamic_data_ctx {
    /* The DSO object we load that supplies the ENGINE code */
    DSO *dynamic_dso;
    /*
     * The function pointer to the version checking shared library function
     */
    dynamic_v_check_fn v_check;
    /*
     * The function pointer to the engine-binding shared library function
     */
    dynamic_bind_engine bind_engine;
    /* The default name/path for loading the shared library */
    char *DYNAMIC_LIBNAME;
    /* Whether to continue loading on a version check failure */
    int no_vcheck;
    /* If non-NULL, stipulates the 'id' of the ENGINE to be loaded */
    char *engine_id;
    /*
     * If non-zero, a successfully loaded ENGINE should be added to the
     * internal ENGINE list. If 2, the add must succeed or the entire load
     * should fail.
     */
    int list_add_value;
    /* The symbol name for the version checking function */
    const char *DYNAMIC_F1;
    /* The symbol name for the "initialise ENGINE structure" function */
    const char *DYNAMIC_F2;
    /*
     * Whether to never use 'dirs', use 'dirs' as a fallback, or only use
     * 'dirs' for loading. Default is to use 'dirs' as a fallback.
     */
    int dir_load;
    /* A stack of directories from which ENGINEs could be loaded */
    STACK_OF(OPENSSL_STRING) *dirs;
};

static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
    /* 這個(gè)函數(shù)將會(huì)初始化并保存動(dòng)態(tài)庫(kù)數(shù)據(jù)的ctx,這也是為什么可以反復(fù)調(diào)用這個(gè)接口的原因 */
    dynamic_data_ctx *ctx = dynamic_get_data_ctx(e);
    int initialised;

    if (!ctx) {
        ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_NOT_LOADED);
        return 0;
    }
    
    /* 可以看到媚送,加載完成的標(biāo)志是dynamic_dso鉤子已經(jīng)掛上了 */
    initialised = ((ctx->dynamic_dso == NULL) ? 0 : 1);
    /* All our control commands require the ENGINE to be uninitialised */
    if (initialised) {
        ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_ALREADY_LOADED);
        return 0;
    }
   
    /* cmd解析,底下逐個(gè)分析 */
    ...
}

/*
 * This function retrieves the context structure from an ENGINE's "ex_data",
 * or if it doesn't exist yet, sets it up.
 */
static dynamic_data_ctx *dynamic_get_data_ctx(ENGINE *e)
{
    dynamic_data_ctx *ctx;
    if (dynamic_ex_data_idx < 0) {
        /*
         * Create and register the ENGINE ex_data, and associate our "free"
         * function with it to ensure any allocated contexts get freed when
         * an ENGINE goes underground.
         */
        int new_idx = ENGINE_get_ex_new_index(0, NULL, NULL, NULL,
                                              dynamic_data_ctx_free_func);
        if (new_idx == -1) {
            ENGINEerr(ENGINE_F_DYNAMIC_GET_DATA_CTX, ENGINE_R_NO_INDEX);
            return NULL;
        }
        CRYPTO_THREAD_write_lock(global_engine_lock);
        /* Avoid a race by checking again inside this lock */
        if (dynamic_ex_data_idx < 0) {
            /* Good, someone didn't beat us to it */
            dynamic_ex_data_idx = new_idx;
            new_idx = -1;
        }
        CRYPTO_THREAD_unlock(global_engine_lock);
        /*
         * In theory we could "give back" the index here if (new_idx>-1), but
         * it's not possible and wouldn't gain us much if it were.
         */
    }
    ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e, dynamic_ex_data_idx);
    /* Check if the context needs to be created */
    if ((ctx == NULL) && !dynamic_set_data_ctx(e, &ctx))
        /* "set_data" will set errors if necessary */
        return NULL;
    return ctx;
}

/* 
 * 簡(jiǎn)單的說(shuō)就是去查掛在engine->ex_data吟秩,
 * 這個(gè)就是動(dòng)態(tài)庫(kù)加載的上下文收恢,ex_data是個(gè)棧可能有多個(gè)上下文硼补,
 * 根據(jù)一個(gè)全局變量dynamic_ex_data_idx確定當(dāng)前使用上下文
 * 當(dāng)然第一次調(diào)用ctx是空的离钝,所以需要調(diào)用一下dynamic_set_data_ctx初始化
 */
static int dynamic_set_data_ctx(ENGINE *e, dynamic_data_ctx **ctx)
{
    /* 申請(qǐng)ctx的mem */
    dynamic_data_ctx *c = OPENSSL_zalloc(sizeof(*c));
    int ret = 1;

    if (c == NULL) {
        ENGINEerr(ENGINE_F_DYNAMIC_SET_DATA_CTX, ERR_R_MALLOC_FAILURE);
        return 0;
    }
    c->dirs = sk_OPENSSL_STRING_new_null();
    if (c->dirs == NULL) {
        ENGINEerr(ENGINE_F_DYNAMIC_SET_DATA_CTX, ERR_R_MALLOC_FAILURE);
        OPENSSL_free(c);
        return 0;
    }
    /* 初始化一些字段,下面總結(jié) */ 
    c->DYNAMIC_F1 = "v_check", ;
    c->DYNAMIC_F2 = "bind_engine";
    c->dir_load = 1;
    CRYPTO_THREAD_write_lock(global_engine_lock);
    /* 第一次進(jìn)來(lái)為NULL(然而正常是為ctx = NULL才會(huì)調(diào)用這個(gè)函數(shù)浪读,可能是冗余校驗(yàn))*/
    if ((*ctx = (dynamic_data_ctx *)ENGINE_get_ex_data(e,
                                                       dynamic_ex_data_idx))
        == NULL) {
        /* Good, we're the first */
        /* 把ctx掛在engine->ex_data上 */
        ret = ENGINE_set_ex_data(e, dynamic_ex_data_idx, c);
        if (ret) {
            *ctx = c;
            c = NULL;
        }
    }
    CRYPTO_THREAD_unlock(global_engine_lock);
    /*
     * If we lost the race to set the context, c is non-NULL and *ctx is the
     * context of the thread that won.
     */
    if (c)
        sk_OPENSSL_STRING_free(c->dirs);
    OPENSSL_free(c);
    return ret;
}

/* 
 * 得到最后的結(jié)果 dynamic->ex_data = ctx;
 * ctx = {.DYNAMIC_F1 = "v_check", .DYNAMIC_F2 = "bind_engine", c->dir_load = 1}
 * 驚奇的發(fā)現(xiàn)了 bind_engine 雖然他只是個(gè)字符串,但是我相信你已經(jīng)知道原因了
 * 他需要在動(dòng)態(tài)庫(kù)中去尋找這個(gè)符號(hào)
 */

之后我們逐一分析這三個(gè)cmd

DYNAMIC_CMD_SO_PATH和DYNAMIC_CMD_LIST_ADD

static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
    ...
    switch (cmd) {
    /* 注意痘拆, p就是ctrlvalue坟瓢,即從conf中取下來(lái)的值 */
    case DYNAMIC_CMD_SO_PATH:
        /* a NULL 'p' or a string of zero-length is the same thing */
        if (p && (strlen((const char *)p) < 1))
            p = NULL;
        OPENSSL_free(ctx->DYNAMIC_LIBNAME);
        if (p)
            /* 很明顯只是做了個(gè)簡(jiǎn)單的復(fù)制,此時(shí)路徑已經(jīng)賦值上了 */
            ctx->DYNAMIC_LIBNAME = OPENSSL_strdup(p);
        else
            ctx->DYNAMIC_LIBNAME = NULL;
        return (ctx->DYNAMIC_LIBNAME ? 1 : 0);
    case DYNAMIC_CMD_LIST_ADD:
        if ((i < 0) || (i > 2)) {
           ENGINEerr(ENGINE_F_DYNAMIC_CTRL, ENGINE_R_INVALID_ARGUMENT);
           return 0;
        }
        /* 很簡(jiǎn)單诚镰,賦值而已 */
        ctx->list_add_value = (int)i;
        return 1;
    ... 
    }
}

這兩個(gè)都很簡(jiǎn)單,最后難點(diǎn)都給了LOAD

DYNAMIC_CMD_LOAD

最關(guān)鍵的函數(shù)抠艾,完成了全部的加載,解釋都在注釋里:

static int dynamic_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void))
{
    ...
    switch (cmd) {
    case DYNAMIC_CMD_LOAD:
        return dynamic_load(e, ctx);    
    ... 
    }
}

static int dynamic_load(ENGINE *e, dynamic_data_ctx *ctx)
{
    ENGINE cpy;
    dynamic_fns fns;
    
    /* 
     * 先new一個(gè)DSO結(jié)構(gòu)體齐苛,DSO這一套函數(shù)怎么玩的這里先不講了,
     * 可以理解為內(nèi)部也有一個(gè)加載鉤子玛痊,有4個(gè)掛載點(diǎn),估計(jì)再展開(kāi)講讀者瘋了
     */
    if (ctx->dynamic_dso == NULL)
        ctx->dynamic_dso = DSO_new();
    if (ctx->dynamic_dso == NULL)
        return 0;
    /* 此處檢查DYNAMIC_LIBNAME不能為空颈娜,這個(gè)就是dso的加載地址 */
    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);
    }
    
    /* 核心加載函數(shù)int_load,看下面分析 */
    if (!int_load(ctx)) {
        ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_DSO_NOT_FOUND);
        DSO_free(ctx->dynamic_dso);
        ctx->dynamic_dso = NULL;
        return 0;
    }
    
    /* We have to find a bind function otherwise it'll always end badly */
    /* 
     * 此時(shí)engine動(dòng)態(tài)庫(kù)已經(jīng)加載如內(nèi)存,符號(hào)表與對(duì)應(yīng)地址也準(zhǔn)備完成 
     * 所以肯定是需要去尋找這個(gè)綁定engine完成加載的函數(shù)了俗批,勝利的曙光
     * DSO_bind_func會(huì)在符號(hào)表中去匹配第二個(gè)參數(shù)字符串岁忘,這里就是我們要的"bind_engine"
     * 并返回上它的函數(shù)地址帅腌,掛載在ctx->bind_engine上
     */
    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;
        ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_DSO_FAILURE);
        return 0;
    }
    /* Do we perform version checking? */
    if (!ctx->no_vcheck) {
        unsigned long vcheck_res = 0;
        /*
         * Now we try to find a version checking function and decide how to
         * cope with failure if/when it fails.
         */
        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);
        /*
         * We fail if the version checker veto'd the load *or* if it is
         * deferring to us (by returning its version) and we think it is too
         * old.
         */
        if (vcheck_res < OSSL_DYNAMIC_OLDEST) {
            /* Fail */
            ctx->bind_engine = NULL;
            ctx->v_check = NULL;
            DSO_free(ctx->dynamic_dso);
            ctx->dynamic_dso = NULL;
            ENGINEerr(ENGINE_F_DYNAMIC_LOAD,
                      ENGINE_R_VERSION_INCOMPATIBILITY);
            return 0;
        }
    }
    /*
     * First binary copy the ENGINE structure so that we can roll back if the
     * hand-over fails
     */
    memcpy(&cpy, e, sizeof(ENGINE));
    /*
     * Provide the ERR, "ex_data", memory, and locking callbacks so the
     * loaded library uses our state rather than its own. FIXME: As noted in
     * engine.h, much of this would be simplified if each area of code
     * provided its own "summary" structure of all related callbacks. It
     * would also increase opaqueness.
     */
    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);
    /*
     * Now that we've loaded the dynamic engine, make sure no "dynamic"
     * ENGINE elements will show through.
     */
    engine_set_all_null(e);

    /* Try to bind the ENGINE onto our own ENGINE structure */
    /* !!!!Attension, 終于調(diào)用成功了五鲫,我們的engineX終于被設(shè)置好了溺职! */
    if (!ctx->bind_engine(e, ctx->engine_id, &fns)) {
        ctx->bind_engine = NULL;
        ctx->v_check = NULL;
        DSO_free(ctx->dynamic_dso);
        ctx->dynamic_dso = NULL;
        ENGINEerr(ENGINE_F_DYNAMIC_LOAD, ENGINE_R_INIT_FAILED);
        /* Copy the original ENGINE structure back */
        memcpy(e, &cpy, sizeof(ENGINE));
        return 0;
    }
    /* Do we try to add this ENGINE to the internal list too? */
    /* 把這個(gè)engine的副本add進(jìn)上面engine全局鏈表,大功告成位喂!*/
    if (ctx->list_add_value > 0) {
        if (!ENGINE_add(e)) {
            /* Do we tolerate this or fail? */
            if (ctx->list_add_value > 1) {
                /*
                 * Fail - NB: By this time, it's too late to rollback, and
                 * trying to do so allows the bind_engine() code to have
                 * created leaks. We just have to fail where we are, after
                 * the ENGINE has changed.
                 */
                ENGINEerr(ENGINE_F_DYNAMIC_LOAD,
                          ENGINE_R_CONFLICTING_ENGINE_ID);
                return 0;
            }
            /* Tolerate */
            ERR_clear_error();
        }
    }
    return 1;
}

static int int_load(dynamic_data_ctx *ctx)
{
    int num, loop;
    /* Unless told not to, try a direct load */
    /* 
     * DSO_load去打開(kāi)ctx->DYNAMIC_LIBNAME浪耘,把egine對(duì)應(yīng)的lib庫(kù)加載進(jìn)內(nèi)存
     * 解析符號(hào)表和對(duì)應(yīng)地址到上面申請(qǐng)好的ctx->dynamic_dso結(jié)構(gòu)體中
     */
    if ((ctx->dir_load != 2) && (DSO_load(ctx->dynamic_dso,
                                          ctx->DYNAMIC_LIBNAME, NULL,
                                          0)) != NULL)
        return 1;
    /* If we're not allowed to use 'dirs' or we have none, fail */
    if (!ctx->dir_load || (num = sk_OPENSSL_STRING_num(ctx->dirs)) < 1)
        return 0;
    for (loop = 0; loop < num; loop++) {
        /* 還有鏈接的dso這里會(huì)處理遞歸的去加載,對(duì)應(yīng)的需要在ctx->dirs中 */
        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)) {
            /* Found what we're looking for */
            OPENSSL_free(merge);
            return 1;
        }
        OPENSSL_free(merge);
    }
    return 0;
}

終于終于終于忆某,找到目標(biāo)了点待,這個(gè)叫做'dynamic'的engine副本完成了變成engineX的蛻變。

后續(xù)

難怪這么多人噴OpenSSL爛颠区,這復(fù)雜的流程朋截,這一個(gè)又一個(gè)的鉤子奉芦。不過(guò)這一串源碼讀下來(lái)看明白的時(shí)候還是有神清氣爽的感覺(jué)。

有緣后面會(huì)分析密碼算法具體掛載朝卒,如ENGINE_set_digests龙宏。

我很菜,有錯(cuò)誤的地方歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市造虎,隨后出現(xiàn)的幾起案子浸卦,更是在濱河造成了極大的恐慌奢讨,老刑警劉巖季率,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜睛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甥角,“玉大人拓诸,你說(shuō)我怎么就攤上這事您旁。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)触菜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)迷郑,這世上最難降的妖魔是什么念搬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任温亲,我火速辦了婚禮熔酷,結(jié)果婚禮上量愧,老公的妹妹穿的比我還像新娘。我一直安慰自己帅矗,他們只是感情好偎肃,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著浑此,像睡著了一般累颂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凛俱,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天紊馏,我揣著相機(jī)與錄音,去河邊找鬼蒲犬。 笑死朱监,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的原叮。 我是一名探鬼主播赫编,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奋隶!你這毒婦竟也來(lái)了擂送?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤唯欣,失蹤者是張志新(化名)和其女友劉穎嘹吨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體黍聂,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躺苦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年身腻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了产还。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匹厘。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脐区,靈堂內(nèi)的尸體忽然破棺而出愈诚,到底是詐尸還是另有隱情,我是刑警寧澤牛隅,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布炕柔,位于F島的核電站,受9級(jí)特大地震影響媒佣,放射性物質(zhì)發(fā)生泄漏匕累。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一默伍、第九天 我趴在偏房一處隱蔽的房頂上張望欢嘿。 院中可真熱鬧,春花似錦也糊、人聲如沸炼蹦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)掐隐。三九已至,卻和暖如春钞馁,著一層夾襖步出監(jiān)牢的瞬間虑省,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工僧凰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慷妙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓允悦,卻偏偏與公主長(zhǎng)得像膝擂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子隙弛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359