wazuh-modules-sca-scan

 sca模块主函数wm_sca_main -> wm_sca_start

 检查policy文件中的每一个项目wm_sca_check_policy

static int wm_sca_check_policy(const cJSON * const policy, const cJSON * const checks, OSHash *global_check_list)
{
    if(!policy) {
        return 1;
    }

    const cJSON * const id = cJSON_GetObjectItem(policy, "id");
    if(!id) {
        mwarn("Field 'id' not found in policy header.");
        return 1;
    }

    if(!id->valuestring){
        mwarn("Invalid format for field 'id'");
        return 1;
    }

    char *coincident_policy_file;
    if((coincident_policy_file = OSHash_Get(global_check_list,id->valuestring)), coincident_policy_file) {
        mwarn("Found duplicated policy ID: %s. File '%s' contains the same ID.", id->valuestring, coincident_policy_file);
        return 1;
    }

    const cJSON * const name = cJSON_GetObjectItem(policy, "name");
    if(!name) {
        mwarn("Field 'name' not found in policy header.");
        return 1;
    }

    if(!name->valuestring){
        mwarn("Invalid format for field 'name'");
        return 1;
    }

    const cJSON * const file = cJSON_GetObjectItem(policy, "file");
    if(!file) {
        mwarn("Field 'file' not found in policy header.");
        return 1;
    }

    if(!file->valuestring){
        mwarn("Invalid format for field 'file'");
        return 1;
    }

    const cJSON * const description = cJSON_GetObjectItem(policy, "description");
    if(!description) {
        mwarn("Field 'description' not found in policy header.");
        return 1;
    }

    const cJSON * const regex_type = cJSON_GetObjectItem(policy, "regex_type");
    if(!regex_type) {
        mdebug1("Field 'regex_type' not found in policy header. The OS_REGEX engine shall be used.");
    }

    if(!description->valuestring) {
        mwarn("Invalid format for field 'description'");
        return 1;
    }

    // Check for policy rules with duplicated IDs */
    if (!checks) {
        mwarn("Section 'checks' not found.");
        return 1;
    }

    int *read_id;
    os_calloc(1, sizeof(int), read_id);
    read_id[0] = 0;

    const cJSON *check;
    cJSON_ArrayForEach(check, checks) {
        const cJSON * const check_id = cJSON_GetObjectItem(check, "id");
        if (check_id == NULL) {
            mwarn("Check ID not found.");
            free(read_id);
            return 1;
        }

        if (check_id->valueint <= 0) {
            // Invalid ID
            mwarn("Invalid check ID: %d", check_id->valueint);
            free(read_id);
            return 1;
        }

        char *coincident_policy;
        char *key_id;
        size_t key_length = snprintf(NULL, 0, "%d", check_id->valueint);
        os_malloc(key_length + 1, key_id);
        snprintf(key_id, key_length + 1, "%d", check_id->valueint);

        if((coincident_policy = (char *)OSHash_Get(global_check_list, key_id)), coincident_policy){
            // Invalid ID
            mwarn("Found duplicated check ID: %d. First appearance at policy '%s'", check_id->valueint, coincident_policy);
            os_free(key_id);
            os_free(read_id);
            return 1;
        }
        os_free(key_id);

        int i;
        for (i = 0; read_id[i] != 0; i++) {
            if (check_id->valueint == read_id[i]) {
                // Duplicated ID
                mwarn("Found duplicated check ID: %d", check_id->valueint);
                free(read_id);
                return 1;
            }
        }

        os_realloc(read_id, sizeof(int) * (i + 2), read_id);
        read_id[i] = check_id->valueint;
        read_id[i + 1] = 0;

        const cJSON * const rules = cJSON_GetObjectItem(check, "rules");

        if (rules == NULL) {
            mwarn("Invalid check %d: no rules found.", check_id->valueint);
            free(read_id);
            return 1;
        }

        int rules_n = 0;
        const cJSON *rule;
        cJSON_ArrayForEach(rule, rules) {
            if (!rule->valuestring) {
                mwarn("Invalid check %d: Empty rule.", check_id->valueint);
                free(read_id);
                return 1;
            }

            char *valuestring_ref = rule->valuestring;
            valuestring_ref += 4 * (!strncmp(valuestring_ref, "NOT ", 4) || !strncmp(valuestring_ref, "not ", 4));

            switch (*valuestring_ref) {
#ifdef WIN32
                case 'r':
#endif
                case 'f':
                case 'd':
                case 'p':
                case 'c':
                    break;
                case '\0':
                    mwarn("Invalid check %d: Empty rule.", check_id->valueint);
                    free(read_id);
                    return 1;
                default:
                    mwarn("Invalid check %d: Invalid rule format.", check_id->valueint);
                    free(read_id);
                    return 1;
            }

            rules_n++;
            if (rules_n > 255) {
                free(read_id);
                mwarn("Invalid check %d: Maximum number of rules is 255.", check_id->valueint);
                return 1;
            }
        }

        if (rules_n == 0) {
            mwarn("Invalid check %d: no rules found.", check_id->valueint);
            free(read_id);
            return 1;
        }

    }

    char *policy_file = NULL;
    os_strdup(file->valuestring, policy_file);
    const int id_add_retval = OSHash_Add(global_check_list, id->valuestring, policy_file);
    if (id_add_retval == 0){
        os_free(policy_file);
        os_free(read_id);
        merror_exit("(1102): Could not acquire memory");
    }

    if (id_add_retval == 1){
        merror("Error validating duplicated ID. Policy %s in file %s is duplicated", id->valuestring, policy_file);
        os_free(policy_file);
        os_free(read_id);
        return 1;
    }

    int i;
    for (i = 0; read_id[i] != 0; ++i) {
        char *policy_id = NULL;
        os_strdup(id->valuestring, policy_id);
        const int check_add_retval = OSHash_Numeric_Add_ex(global_check_list, read_id[i], policy_id);
        if (check_add_retval == 0){
            os_free(policy_id);
            os_free(read_id);
            merror_exit("(1102): Could not acquire memory");
        }

        if (check_add_retval == 1){
            merror("Error validating duplicated ID. Check %s in policy %s is duplicated", id->valuestring, policy_id);
            os_free(policy_id);
            os_free(read_id);
            return 1;
        }
    }

    os_free(read_id);
    return 0;
}

policy文件中的具体rules项目,其中规则之一:

# 1.1.1.3 udf: filesystem
  - id: 6002
    title: "Ensure mounting of udf filesystems is disabled"
    description: "The udf filesystem type is the universal disk format used to implement ISO/IEC 13346 and ECMA-167 specifications. This is an open vendor filesystem type for data storage on a broad range of media. This filesystem type is necessary to support writing DVDs and newer optical disc formats."
    rationale: "Removing support for unneeded filesystem types reduces the local attack surface of the system. If this filesystem type is not needed, disable it."
    remediation: "Edit or create the file /etc/modprobe.d/CIS.conf and add the following line: install udf /bin/true. Run the following command to unload the udf module: rmmod udf"
    compliance:
      - cis: ["1.1.1.3"]
      - cis_csc: ["5.1"]
      - pci_dss: ["2.2.5"]
      - tsc: ["CC6.3"]
    references:
      - AJ Lewis, "LVM HOWTO", https://tldp.org/HOWTO/LVM-HOWTO/
    condition: all
    rules:
      - 'c:modprobe -n -v udf -> r:install /bin/true|Module udf not found'
      - 'not c:lsmod -> r:udf'

rules中的每一项是r (读取), f,d,p,c,not,NOT开头  "->"表示前一个动作之后的接着的下一个动作,或者条件

 sca扫描核心函数

/*
Rules that match always return 1, and the other way arround.

Rule aggregators logic:

##########################################################

ALL:
    r_1 -f -> r:123
    ...
    r_n -f -> r:234

For an ALL to succeed, every rule shall return 1, in other words,

               |  = n -> ALL = RETURN_FOUND
SUM(r_i, 0, n) |
               | != n -> ALL = RETURN_NOT_FOUND

##########################################################

ANY:
    r_1 -f -> r:123
    ...
    r_n -f -> r:234

For an ANY to succeed, a rule shall return 1, in other words,

               | > 0 -> ANY = RETURN_FOUND
SUM(r_i, 0, n) |
               | = 0 -> ANY = RETURN_NOT_FOUND

##########################################################

NONE:
    r_1 -f -> r:123
    ...
    r_n -f -> r:234

For a NONE to succeed, all rules shall return RETURN_NOT_FOUND, in other words,

               |  > 0 -> NONE = RETURN_NOT_FOUND
SUM(r_i, 0, n) |
               |  = 0 -> NONE = RETURN_FOUND

##########################################################

ANY and NONE aggregators are complementary.

*/

static int wm_sca_do_scan(cJSON * checks,
                          OSStore * vars,
                          wm_sca_t * data,
                          int id,
                          cJSON * policy,
                          int requirements_scan,
                          int cis_db_index,
                          unsigned int remote_policy,
                          int first_scan,
                          int * checks_number,
                          char ** sorted_variables,
                          char * policy_engine)
{
    int type = 0;
    char buf[OS_SIZE_1024 + 2];
    char final_file[2048 + 1];
    char *reason = NULL;

    int ret_val = 0;
    OSList *p_list = NULL;

    /* Initialize variables */
    memset(buf, '\0', sizeof(buf));
    memset(final_file, '\0', sizeof(final_file));

    int check_count = 0;
    cJSON *check = NULL;
    cJSON_ArrayForEach(check, checks) {
        char _check_id_str[50];
        if (requirements_scan) {
            snprintf(_check_id_str, sizeof(_check_id_str), "Requirements check");
        } else {
            const cJSON * const c_id = cJSON_GetObjectItem(check, "id");
            if (!c_id || !c_id->valueint) {
                merror("Skipping check. Check ID is invalid. Offending check number: %d", check_count);
                ret_val = 1;
                continue;
            }
            snprintf(_check_id_str, sizeof(_check_id_str), "id: %d", c_id->valueint);
        }

        const cJSON * const c_title = cJSON_GetObjectItem(check, "title");
        if (!c_title || !c_title->valuestring) {
            merror("Skipping check with %s: Check name is invalid.", _check_id_str);
            if (requirements_scan) {
                ret_val = 1;
                goto clean_return;
            }
            continue;
        }

        const cJSON * const c_condition = cJSON_GetObjectItem(check, "condition");
        if (!c_condition || !c_condition->valuestring) {
            merror("Skipping check '%s: %s': Check condition not found.", _check_id_str, c_title->valuestring);
            if (requirements_scan) {
                ret_val = 1;
                goto clean_return;
            }
            continue;
        }

        int condition = 0;
        wm_sca_set_condition(c_condition->valuestring, &condition);

        if (condition == WM_SCA_COND_INV) {
            merror("Skipping check '%s: %s': Check condition (%s) is invalid.",_check_id_str, c_title->valuestring, c_condition->valuestring);
            if (requirements_scan) {
                ret_val = 1;
                goto clean_return;
            }
            continue;
        }

        int g_found = RETURN_NOT_FOUND;
        if ((condition & WM_SCA_COND_ANY) || (condition & WM_SCA_COND_NON)) {
            /* aggregators ANY and NONE break by matching, so they shall return NOT_FOUND if they never break */
            g_found = RETURN_NOT_FOUND;
        } else if (condition & WM_SCA_COND_ALL) {
            /* aggregator ALL breaks the moment a rule does not match. If it doesn't break, all rules have matched */
            g_found = RETURN_FOUND;
        }

        mdebug1("Beginning evaluation of check %s '%s'", _check_id_str, c_title->valuestring);
        mdebug1("Rule aggregation strategy for this check is '%s'", c_condition->valuestring);
        mdebug2("Initial rule-aggregator value por this type of rule is '%d'",  g_found);
        mdebug1("Beginning rules evaluation.");

        const cJSON *const rules = cJSON_GetObjectItem(check, "rules");
        if (!rules) {
            merror("Skipping check %s '%s': No rules found.", _check_id_str, c_title->valuestring);
            if (requirements_scan) {
                ret_val = 1;
                goto clean_return;
            }
            continue;
        }

        w_expression_t * regex_engine = NULL;
        cJSON * engine = cJSON_GetObjectItem(check, "regex_type");
        if (engine) {
            if (strcmp(PCRE2_STR, cJSON_GetStringValue(engine)) == 0) {
                w_calloc_expression_t(&regex_engine, EXP_TYPE_PCRE2);
            } else {
                w_calloc_expression_t(&regex_engine, EXP_TYPE_OSREGEX);
            }
        } else {
            if(strcmp(PCRE2_STR, policy_engine) == 0) {
                w_calloc_expression_t(&regex_engine, EXP_TYPE_PCRE2);
            } else {
                w_calloc_expression_t(&regex_engine, EXP_TYPE_OSREGEX);
            }
        }
        mdebug1("SCA will use '%s' engine to check the rules.", w_expression_get_regex_type(regex_engine));

        char *rule_cp = NULL;
        const cJSON *rule_ref;
        cJSON_ArrayForEach(rule_ref, rules) {
            /* this free is responsible of freeing the copy of the previous rule if
            the loop 'continues', i.e, does not reach the end of its block. */
            os_free(rule_cp);

            if(!rule_ref->valuestring) {
                mdebug1("Field 'rule' must be a string.");
                ret_val = 1;
                os_free(regex_engine);
                goto clean_return;
            }

            mdebug1("Considering rule: '%s'", rule_ref->valuestring);

            os_strdup(rule_ref->valuestring, rule_cp);
            char *rule_cp_ref = NULL;

        #ifdef WIN32
            char expanded_rule[2048] = {0};
            ExpandEnvironmentStrings(rule_cp, expanded_rule, 2048);
            rule_cp_ref = expanded_rule;
            mdebug2("Rule after variable expansion: '%s'", rule_cp_ref);
        #else
            rule_cp_ref = rule_cp;
        #endif

            int rule_is_negated = 0;
            if (rule_cp_ref &&
                    (strncmp(rule_cp_ref, "NOT ", 4) == 0 ||
                     strncmp(rule_cp_ref, "not ", 4) == 0))
            {
                mdebug2("Rule is negated.");
                rule_is_negated = 1;
                rule_cp_ref += 4;
            }

            /* Get value to look for. char *value is a reference
            to rule_cp memory. Do not release value!  */
            char *value = wm_sca_get_value(rule_cp_ref, &type);

            if (value == NULL) {
                merror("Invalid rule: '%s'. Skipping policy.", rule_ref->valuestring);
                os_free(rule_cp);
                ret_val = 1;
                os_free(regex_engine);
                goto clean_return;
            }

            int found = RETURN_NOT_FOUND;
            if (type == WM_SCA_TYPE_FILE) {
                /* Check files */
                char *pattern = wm_sca_get_pattern(value);
                char *rule_location = NULL;
                char *aux = NULL;

                os_strdup(value, rule_location);

                /* If any, replace the variables by their respective values */
                if (sorted_variables) {
                    int i = 0;
                    for (i = 0; sorted_variables[i]; i++) {
                        if (strstr(rule_location, sorted_variables[i])) {
                            mdebug2("Variable '%s' found at rule '%s'. Replacing it.", sorted_variables[i], rule_location);
                            aux = wstr_replace(rule_location, sorted_variables[i], OSStore_Get(vars, sorted_variables[i]));
                            os_free(rule_location);
                            rule_location = aux;
                            if (!rule_location) {
                                merror("Invalid variable replacement: '%s'. Skipping check.", sorted_variables[i]);
                                break;
                            }
                            mdebug2("Variable replaced: '%s'", rule_location);
                        }
                    }
                }

                if (!rule_location) {
                    continue;
                }
                const int result = wm_sca_check_file_list(rule_location, pattern, &reason, regex_engine);
                if (result == RETURN_FOUND || result == RETURN_INVALID) {
                    found = result;
                }

                char _b_msg[OS_SIZE_1024 + 1];
                _b_msg[OS_SIZE_1024] = '\0';
                snprintf(_b_msg, OS_SIZE_1024, " File: %s", rule_location);
                append_msg_to_vm_scat(data, _b_msg);
                os_free(rule_location);

            } else if (type == WM_SCA_TYPE_COMMAND) {
                /* Check command output */
                char *pattern = wm_sca_get_pattern(value);
                char *rule_location = NULL;
                char *aux = NULL;

                os_strdup(value, rule_location);

                if (!data->remote_commands && remote_policy) {
                    mwarn("Ignoring check for policy '%s'. The internal option 'sca.remote_commands' is disabled.", cJSON_GetObjectItem(policy, "name")->valuestring);
                    if (reason == NULL) {
                        os_malloc(snprintf(NULL, 0, "Ignoring check for running command '%s'. The internal option 'sca.remote_commands' is disabled", rule_location) + 1, reason);
                        sprintf(reason, "Ignoring check for running command '%s'. The internal option 'sca.remote_commands' is disabled", rule_location);
                    }
                    found = RETURN_INVALID;

                } else {
                    /* If any, replace the variables by their respective values */
                    if (sorted_variables) {
                        int i = 0;
                        for (i = 0; sorted_variables[i]; i++) {
                            if (strstr(rule_location, sorted_variables[i])) {
                                mdebug2("Variable '%s' found at rule '%s'. Replacing it.", sorted_variables[i], rule_location);
                                aux = wstr_replace(rule_location, sorted_variables[i], OSStore_Get(vars, sorted_variables[i]));
                                os_free(rule_location);
                                rule_location = aux;
                                if (!rule_location) {
                                    merror("Invalid variable: '%s'. Skipping check.", sorted_variables[i]);
                                    break;
                                }
                                mdebug2("Variable replaced: '%s'", rule_location);
                            }
                        }
                    }

                    if (!rule_location) {
                        continue;
                    }

                    mdebug2("Running command: '%s'", rule_location);
                    const int val = wm_sca_read_command(rule_location, pattern, data, &reason, regex_engine);
                    if (val == RETURN_FOUND) {
                        mdebug2("Command output matched.");
                        found = RETURN_FOUND;
                    } else if (val == RETURN_INVALID){
                        mdebug2("Command output did not match.");
                        found = RETURN_INVALID;
                    }
                }

                char _b_msg[OS_SIZE_1024 + 1];
                _b_msg[OS_SIZE_1024] = '\0';
                snprintf(_b_msg, OS_SIZE_1024, " Command: %s", rule_location);
                append_msg_to_vm_scat(data, _b_msg);
                os_free(rule_location);

            } else if (type == WM_SCA_TYPE_DIR) {
                /* Check directory */
                mdebug2("Processing directory rule '%s'", value);
                char * const file = wm_sca_get_pattern(value);
                char *rule_location = NULL;
                char *aux = NULL;

                os_strdup(value, rule_location);

                /* If any, replace the variables by their respective values */
                if (sorted_variables) {
                    int i = 0;
                    for (i = 0; sorted_variables[i]; i++) {
                        if (strstr(rule_location, sorted_variables[i])) {
                            mdebug2("Variable '%s' found at rule '%s'. Replacing it.", sorted_variables[i], rule_location);
                            aux = wstr_replace(rule_location, sorted_variables[i], OSStore_Get(vars, sorted_variables[i]));
                            os_free(rule_location);
                            rule_location = aux;
                            if (!rule_location) {
                                merror("Invalid variable: '%s'. Skipping check.", sorted_variables[i]);
                                break;
                            }
                            mdebug2("Variable replaced: '%s'", rule_location);
                        }
                    }
                }

                if (!rule_location) {
                    continue;
                }

                char * const pattern = wm_sca_get_pattern(file);
                found = wm_sca_check_dir_list(data, rule_location, file, pattern, &reason, regex_engine);
                mdebug2("Check directory rule result: %d", found);
                os_free(rule_location);

            } else if (type == WM_SCA_TYPE_PROCESS) {
                /* Check process existence */
                if (!p_list) {
                    /* Lazy evaluation */
                    p_list = w_os_get_process_list();
                }

                mdebug2("Checking process: '%s'", value);
                if (wm_sca_check_process_is_running(p_list, value, &reason, regex_engine)) {
                    mdebug2("Process found.");
                    found = RETURN_FOUND;
                } else {
                    mdebug2("Process not found.");
                }

                char _b_msg[OS_SIZE_1024 + 1];
                _b_msg[OS_SIZE_1024] = '\0';
                snprintf(_b_msg, OS_SIZE_1024, " Process: %s", value);
                append_msg_to_vm_scat(data, _b_msg);
            }
        #ifdef WIN32
            else if (type == WM_SCA_TYPE_REGISTRY) {
                /* Check windows registry */
                char * const entry = wm_sca_get_pattern(value);
                char * const pattern = wm_sca_get_pattern(entry);
                found = wm_sca_is_registry(value, entry, pattern, &reason, regex_engine);

                char _b_msg[OS_SIZE_1024 + 1];
                _b_msg[OS_SIZE_1024] = '\0';
                snprintf(_b_msg, OS_SIZE_1024, " Registry: %s", value);
                append_msg_to_vm_scat(data, _b_msg);
            }
        #endif

            /* Rule result processing */

            if (found != RETURN_INVALID) {
                found = rule_is_negated ^ found;
            }

            mdebug1("Result for rule '%s': %d", rule_ref->valuestring, found);

            if (((condition & WM_SCA_COND_ALL) && found == RETURN_NOT_FOUND) ||
                ((condition & WM_SCA_COND_ANY) && found == RETURN_FOUND) ||
                ((condition & WM_SCA_COND_NON) && found == RETURN_FOUND))
            {
                g_found = found;
                mdebug1("Breaking from rule aggregator '%s' with found = %d", c_condition->valuestring, g_found);
                break;
            }

            if (found == RETURN_INVALID) {
                /* Rules that agreggate by ANY are the only that can success after an INVALID
                On the other hand ALL and NONE agregators can fail after an INVALID. */
                g_found = found;
                mdebug1("Rule evaluation returned INVALID. Continuing.");
            }
        }

        if ((condition & WM_SCA_COND_NON) && g_found != RETURN_INVALID) {
            g_found = !g_found;
        }

        mdebug1("Result for check %s '%s' -> %d", _check_id_str, c_title->valuestring, g_found);

        if (g_found != RETURN_INVALID) {
            os_free(reason);
        }

        /* if the loop breaks, rule_cp shall be released.
            Also frees the the memory reserved on the last iteration */
        os_free(rule_cp);

        /* Determine if requirements are satisfied */
        if (requirements_scan) {
            /*  return value for requirement scans is the inverse of the result,
                unless the result is INVALID */
            ret_val = g_found == RETURN_INVALID ? 1 : !g_found;
            int i;
            for (i=0; data->alert_msg[i]; i++){
                free(data->alert_msg[i]);
                data->alert_msg[i] = NULL;
            }
            w_free_expression_t(&regex_engine);
            goto clean_return;
        }

        /* Event construction */
        const char failed[] = "failed";
        const char passed[] = "passed";
        const char invalid[] = ""; //NOT AN ERROR!
        const char *message_ref = NULL;

        if (g_found == RETURN_NOT_FOUND) {
            wm_sca_summary_increment_failed();
            message_ref = failed;
        } else if (g_found == RETURN_FOUND) {
            wm_sca_summary_increment_passed();
            message_ref = passed;
        } else {
            wm_sca_summary_increment_invalid();
            message_ref = invalid;

            if (reason == NULL) {
                os_malloc(snprintf(NULL, 0, "Unknown reason") + 1, reason);
                sprintf(reason, "Unknown reason");
                mdebug1("A check returned INVALID for an unknown reason.");
            }
        }

        cJSON *event = wm_sca_build_event(check, policy, data->alert_msg, id, message_ref, reason);
        if (event) {
            /* Alert if necessary */
            if(!cis_db_for_hash[cis_db_index].elem[check_count]) {
                os_realloc(cis_db_for_hash[cis_db_index].elem, sizeof(cis_db_info_t *) * (check_count + 2), cis_db_for_hash[cis_db_index].elem);
                cis_db_for_hash[cis_db_index].elem[check_count] = NULL;
                cis_db_for_hash[cis_db_index].elem[check_count + 1] = NULL;
            }

            if (wm_sca_check_hash(cis_db[cis_db_index], message_ref, check, event, check_count, cis_db_index) && !first_scan) {
                wm_sca_send_event_check(data,event);
            }

            check_count++;

            cJSON_Delete(event);
        } else {
            merror("Error constructing event for check: %s. Set debug mode for more information.", c_title->valuestring);
            ret_val = 1;
        }

        int i;
        for (i=0; data->alert_msg[i]; i++){
            free(data->alert_msg[i]);
            data->alert_msg[i] = NULL;
        }

        os_free(reason);
        w_free_expression_t(&regex_engine);
    }

    *checks_number = check_count;

/* Clean up memory */
clean_return:
    os_free(reason);
    w_del_plist(p_list);

    return ret_val;
}

迭代每一个检查项cJSON_ArrayForEach(check, checks)

/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/937871.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud微服务实战系列:03spring-cloud-gateway业务网关灰度发布

目录 spring-cloud-gateway 和zuul spring webflux 和 spring mvc spring-cloud-gateway 的两种模式 spring-cloud-gateway server 模式下配置说明 grayLb://system-server 灰度发布代码实现 spring-cloud-gateway 和zuul zuul 是spring全家桶的第一代网关组件&#x…

Arm Cortex-M处理器对比表

Arm Cortex-M处理器对比表 当前MCU处理器上主要流行RISC-V和ARM处理器&#xff0c;其他的内核相对比较少&#xff1b;在这两种内核中&#xff0c;又以Arm Cortex-M生态环境相对健全&#xff0c;大部分的厂家都在使用ARM的处理器。本文主要介绍Arm Cortex-M各个不同系列的参数对…

如何实现规范化LabVIEW编程

规范编写LabVIEW程序的目的是提高代码的可读性、可维护性、可扩展性&#xff0c;并确保团队成员能够高效地理解和修改代码。以下是一些关键建议&#xff0c;帮助您编写更专业的LabVIEW代码&#xff0c;并确保它易于后续的升级和维护&#xff1a; ​ 1. 合理的项目结构 目录结构…

深入C语言文件操作:从库函数到系统调用

引言 文件操作是编程中不可或缺的一部分&#xff0c;尤其在C语言中&#xff0c;文件操作不仅是处理数据的基本手段&#xff0c;也是连接程序与外部世界的重要桥梁。C语言提供了丰富的库函数来处理文件&#xff0c;如 fopen、fclose、fread、fwrite 等。然而&#xff0c;这些库…

游戏引擎学习第52天

仓库 : https://gitee.com/mrxiao_com/2d_game 这节的内容相当多 回顾 在游戏中&#xff0c;实体被分为不同的类别&#xff1a;接近玩家的“高频实体”、距离较远并正在模拟的“低频实体”和不进行更新的“休眠实体”。这些实体会根据它们与玩家的距离进行处理&#xff0c;接…

docker 安装mysql 5.7 详细保姆级教程

1. 安装mysql(5.7) docker pull mysql:5.7 若是拉取不了&#xff0c;可以配置下 docker 源 2. 查看是否安装成功 docker images 下图就是成功了 3.创建mysql专用目录、数据挂载目录、配置文件目录 &#xff0c;演示目录在于/home/下 //命令逐条执行cd /home/ mkdir mysql …

宝塔SSL证书申请失败,报错:申请SSL证书错误 module ‘OpenSSL.crypto‘ has no attribute ‘sign‘(已解决)

刚安装宝塔申请SSL就报错&#xff1a;申请SSL证书错误 module OpenSSL.crypto has no attribute sign 面板、插件版本&#xff1a;9.2.0 系统版本&#xff1a;Alibaba Cloud Linux 3.2104 LTS 问题&#xff1a;申请SSL证书错误 module OpenSSL.crypto has no attribute sign…

Three使用WebGPU的关键TSL

Three.js 使用 WebGPU 的关键 TSL TSL: three.js shader language 介绍 three.js 材质转为webgpu的关键流程, 从而引出 TSL. 1、关键类关系 WebGPURenderer|-- library: StandardNodeLibrary|-- _nodes: Nodes|-- _objects: RenderObjects|-- createRenderObject()StandardN…

【蓝桥杯国赛真题15】python质因数个数 蓝桥杯青少年组python编程国赛真题详细解析

目录 python质因数个数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python质因数个数 第十二届蓝桥杯青少年组python比赛国赛真题详细解析 …

发布/部署WebApi服务器(IIS+.NET8+ASP.NETCore)

CS软件授权注册系统-发布/部署WebApi服务器(IIS.NET8ASP.NETCore) 目录 本文摘要VS2022配置发布VS2022发布WebApiIIS服务器部署WebApi 将程序文件复制到云服务器添加网站配置应用程序池配置dns域名配置端口阿里云ECS服务器配置19980端口配置https协议 (申请ssl证书)测试WebAp…

MybatisPlus-配置加密

配置加密 目前配置文件中的很多参数都是明文&#xff0c;如果开发人员发生流动&#xff0c;很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。 我们以数据库的用户名和密码为例。 生成秘钥 首先&#xff0c;我们利用AES工具生成一个随机秘钥&#…

九个任务调度框架

一、背景介绍 说到定时任务&#xff0c;相信大家都不陌生&#xff0c;在我们实际的工作中&#xff0c;用到定时任务的场景可以说非常的多&#xff0c;例如&#xff1a; 双 11 的 0 点&#xff0c;定时开启秒杀每月1号&#xff0c;财务系统自动拉取每个人的绩效工资&#xff0…

photoshop的2个形状-箭头

有时候用ps画一些教程类图文&#xff0c;需要用到箭头. 另外自己画了一个镂空的长方形和正方形 形状的路径一般在Custom Shapes文件夹内 例如 E:\photoshopCS4\Adobe Photoshop CS4\Presets\Custom Shapes

R-Studio Technician,无网络负担地进行远程数据分析和数据恢复任务

对于数据恢复技术人员和技术支持团队来说&#xff0c;时间就是金钱。这不仅包括您在客户机器上花费的时间 - 还包括您往返公司办公室的时间&#xff0c;这可能会带来巨大的不便&#xff0c;特别是如果客户位于其他省市。电话支持通常不适用于需要数小时才能完成的复杂任务&…

将PDF流使用 canvas 绘制展示在页面上(一)

将PDF流展示在页面上 使用 pdfjs-dist 库来渲染 PDF 页面到 canvas 上进行绘制展示 安装 pdfjs-dist 依赖 npm install pdfjs-dist 或者 yarn add pdfjs-dist创建一个组件来处理 PDF 流的加载和渲染 该组件中是一个包含 PDF 文件的 Base64。 将 pdf 流传入该组件中使用 /** fo…

【Java 学习】详细讲解---包和导包、Scanner类、输入源

1. 包 1.1 什么是包&#xff1f; 举个例子&#xff0c;你和你的同学有不同的家庭&#xff0c;你们都有自己的爸爸妈妈&#xff0c;都有自己的家。在自己的家中你们可以按照自己爱好摆放东西&#xff0c;都互不干扰。但是&#xff0c;假如你们的家都在一起&#xff0c;你们就不…

在线预约陪诊小程序

一、前言 随着社会老龄化加剧以及人们健康意识的提高&#xff0c;就医过程中的陪伴需求日益增长。许多患者在面对复杂的医院环境、繁琐的就医流程时&#xff0c;需要有人协助挂号、候诊、取药等&#xff0c;而家属可能因工作繁忙无法全程陪同。同时&#xff0c;异地就医的患者更…

贪心算法【1】

文章目录 860. 柠檬水找零题目解析算法原理代码实现交换论证法 2208. 将数组和减半的最少操作次数题目解析算法原理代码实现交换论证法 179. 最大数题目解析算法原理代码实现 860. 柠檬水找零 题目链接&#xff1a;860. 柠檬水找零 题目解析 一杯柠檬水5块钱&#xff0c;每个…

【一文概述】常见的几种内外网数据交换方案介绍

一、内外网数据交换的核心需求 内外网数据交换的需求核心在于“安全、效率、合规”&#xff0c;而应用场景的多样性使得不同企业需要定制化的解决方案。通过结合业务特性和安全等级要求&#xff0c;企业能够选择适合的技术方案来实现高效、安全的内外网数据交换。 1、数据安全…

C# 中的Task

文章目录 前言一、Task 的基本概念二、创建 Task使用异步方法使用 Task.Run 方法 三、等待 Task 完成使用 await 关键字使用 Task.Wait 方法 四、处理 Task 的异常使用 try-catch 块使用 Task.Exception 属性 五、Task 的延续使用 ContinueWith 方法使用 await 关键字和异步方法…