/* * Copyright (c) 2017 Digi International Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * ======================================================================= */ #include #include #include #include #include #include #include #include "aws_config.h" /*------------------------------------------------------------------------------ D E F I N I T I O N S ------------------------------------------------------------------------------*/ #define SETTING_THING_NAME "thing_name" #define SETTING_CLIENT_ID "client_id" #define SETTING_HOST "host" #define SETTING_PORT "port" #define SETTING_CERTS_PATH "certs_path" #define SETTING_ROOTCA_NAME "rootca_filename" #define SETTING_SCERT_NAME "signed_cert_filename" #define SETTING_PRIVKEY_NAME "private_key_filename" #define SETTING_SHADOW_REPORT_RATE "shadow_report_rate" #define SETTING_SHADOW_REPORT_RATE_MIN 1 /* second */ #define SETTING_SHADOW_REPORT_RATE_MAX (365 * 24 * 60 * 60UL) /* A year */ #define SETTING_TEMP_VARIATION "temperature_variation" #define SETTING_TEMP_VARIATION_MIN 0.1 /* C */ #define SETTING_TEMP_VARIATION_MAX 10.0 /* C */ #define SETTING_CPULOAD_VARIATION "cpu_load_variation" #define SETTING_CPULOAD_VARIATION_MIN 1 /* % */ #define SETTING_CPULOAD_VARIATION_MAX 50.0 /* % */ #define SETTING_USER_LED "user_led" #define SETTING_UNKNOWN "__unknown" /*------------------------------------------------------------------------------ F U N C T I O N D E C L A R A T I O N S ------------------------------------------------------------------------------*/ static int fill_aws_iot_config(aws_iot_cfg_t *aws_cfg); static int cfg_check_thing_name(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_client_id(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_port(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_certificates_path(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_cert_file(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_shadow_report_rate(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_temp_variation(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_cpuload_variation(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_int_range(cfg_t *cfg, cfg_opt_t *opt, uint32_t min, uint32_t max); static int cfg_check_float_range(cfg_t *cfg, cfg_opt_t *opt, float min, float max); static int cfg_check_empty_string(cfg_t *cfg, cfg_opt_t *opt); static int cfg_check_string_length(cfg_t *cfg, cfg_opt_t *opt, uint16_t min, uint16_t max); static int file_exists(const char *const filename); static int file_readable(const char *const filename); /*------------------------------------------------------------------------------ G L O B A L V A R I A B L E S ------------------------------------------------------------------------------*/ static cfg_t *cfg; /*------------------------------------------------------------------------------ F U N C T I O N D E F I N I T I O N S ------------------------------------------------------------------------------*/ /* * parse_configuration() - Parse and save the settings of a configuration file * * @filename: Name of the file containing the configuration settings. * @aws_cfg: AWS IoT Device SDK configuration struct (aws_iot_cfg_t) where * the settings parsed from the configuration file are saved. * * Read the provided configuration file and save the settings in the given * aws_iot_cfg_t struct. If the file does not exist or cannot be read, the * configuration struct is initialized with the default settings. * * Return: 0 if the file is parsed successfully, -1 if there is an error * parsing the file. */ int parse_configuration(const char *const filename, aws_iot_cfg_t *aws_cfg) { /* Overall structure of the settings. */ static cfg_opt_t opts[] = { /* --------------------------------------------------------------------- */ /*| TYPE | SETTING NAME | DEFAULT VALUE | FLAGS |*/ /* --------------------------------------------------------------------- */ CFG_STR (SETTING_THING_NAME, AWS_IOT_MY_THING_NAME, CFGF_NONE), CFG_STR (SETTING_CLIENT_ID, AWS_IOT_MQTT_CLIENT_ID, CFGF_NONE), CFG_STR (SETTING_HOST, AWS_IOT_MQTT_HOST, CFGF_NONE), CFG_INT (SETTING_PORT, AWS_IOT_MQTT_PORT, CFGF_NONE), CFG_STR (SETTING_CERTS_PATH, DEFAULT_CERTS_PATH, CFGF_NONE), CFG_STR (SETTING_ROOTCA_NAME, AWS_IOT_ROOT_CA_FILENAME,CFGF_NONE), CFG_STR (SETTING_SCERT_NAME, AWS_IOT_CERTIFICATE_FILENAME,CFGF_NONE), CFG_STR (SETTING_PRIVKEY_NAME, AWS_IOT_PRIVATE_KEY_FILENAME,CFGF_NONE), CFG_INT (SETTING_SHADOW_REPORT_RATE, 60, CFGF_NONE), CFG_FLOAT (SETTING_TEMP_VARIATION, 1, CFGF_NONE), CFG_FLOAT (SETTING_CPULOAD_VARIATION, 10, CFGF_NONE), CFG_INT (SETTING_USER_LED, -1, CFGF_NONE), /* Needed for unknown settings. */ CFG_STR (SETTING_UNKNOWN, NULL, CFGF_NONE), CFG_END() }; if (!file_exists(filename)) { IOT_ERROR("File '%s' does not exist.", filename); return -1; } if (!file_readable(filename)) { IOT_ERROR("File '%s' cannot be read.", filename); return -1; } cfg = cfg_init(opts, CFGF_IGNORE_UNKNOWN); cfg_set_validate_func(cfg, SETTING_THING_NAME, cfg_check_thing_name); cfg_set_validate_func(cfg, SETTING_CLIENT_ID, cfg_check_client_id); cfg_set_validate_func(cfg, SETTING_HOST, cfg_check_empty_string); cfg_set_validate_func(cfg, SETTING_PORT, cfg_check_port); cfg_set_validate_func(cfg, SETTING_CERTS_PATH, cfg_check_certificates_path); cfg_set_validate_func(cfg, SETTING_ROOTCA_NAME, cfg_check_cert_file); cfg_set_validate_func(cfg, SETTING_SCERT_NAME, cfg_check_cert_file); cfg_set_validate_func(cfg, SETTING_PRIVKEY_NAME, cfg_check_cert_file); cfg_set_validate_func(cfg, SETTING_SHADOW_REPORT_RATE, cfg_check_shadow_report_rate); cfg_set_validate_func(cfg, SETTING_TEMP_VARIATION, cfg_check_temp_variation); cfg_set_validate_func(cfg, SETTING_CPULOAD_VARIATION, cfg_check_cpuload_variation); /* Parse the configuration file. */ switch (cfg_parse(cfg, filename)) { case CFG_FILE_ERROR: IOT_ERROR("Configuration file '%s' could not be read: %s\n", filename, strerror(errno)); return -1; case CFG_SUCCESS: break; case CFG_PARSE_ERROR: IOT_ERROR("Error parsing configuration file '%s'\n", filename); return -1; } return fill_aws_iot_config(aws_cfg); } /* * free_configuration() - Release the configuration var */ void free_configuration(void) { cfg_free(cfg); } /* * fill_aws_iot_config() - Fill the AWS IoT Device SDK configuration struct * * @aws_cfg: AWS IoT Device SDK configuration struct (aws_iot_cfg_t). * * Return: 0 if the configuration is filled successfully, -1 otherwise. */ static int fill_aws_iot_config(aws_iot_cfg_t *aws_cfg) { aws_cfg->thing_name = cfg_getstr(cfg, SETTING_THING_NAME); if (aws_cfg->thing_name == NULL) return -1; aws_cfg->client_id = cfg_getstr(cfg, SETTING_CLIENT_ID); if (aws_cfg->client_id == NULL) return -1; aws_cfg->host = cfg_getstr(cfg, SETTING_HOST); if (aws_cfg->host == NULL) return -1; aws_cfg->port = cfg_getint(cfg, SETTING_PORT); aws_cfg->certs_path = cfg_getstr(cfg, SETTING_CERTS_PATH); if (aws_cfg->certs_path == NULL) return -1; aws_cfg->rootca_fname = cfg_getstr(cfg, SETTING_ROOTCA_NAME); if (aws_cfg->rootca_fname == NULL) return -1; aws_cfg->signed_cert_fname = cfg_getstr(cfg, SETTING_SCERT_NAME); if (aws_cfg->signed_cert_fname == NULL) return -1; aws_cfg->priv_key_fname = cfg_getstr(cfg, SETTING_PRIVKEY_NAME); if (aws_cfg->priv_key_fname == NULL) return -1; aws_cfg->shadow_report_rate = cfg_getint(cfg, SETTING_SHADOW_REPORT_RATE); aws_cfg->temp_variation = cfg_getfloat(cfg, SETTING_TEMP_VARIATION); aws_cfg->cpuload_variation = cfg_getfloat(cfg, SETTING_CPULOAD_VARIATION); aws_cfg->led_gpio = cfg_getint(cfg, SETTING_USER_LED); return 0; } /* * cfg_check_thing_name() - Validate thing_name in the configuration file * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_thing_name(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_string_length(cfg, opt, 1, MAX_SIZE_OF_THING_NAME); } /* * cfg_check_client_id() - Validate client_id in the configuration file * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_client_id(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_string_length(cfg, opt, 1, MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES); } /* * cfg_check_port() - Validate port is between 0 and 65535 * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_port(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_int_range(cfg, opt, 0, 65535); } /* * cfg_check_certificates_path() - Validate certs_path value * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_certificates_path(cfg_t *cfg, cfg_opt_t *opt) { DIR *dir = NULL; char *val = cfg_opt_getnstr(opt, 0); dir = opendir(val); if (dir) { closedir(dir); return 0; } else { cfg_error(cfg, "Invalid %s: cannot find directory '%s'", opt->name, val); return -1; } } /* * cfg_check_cert_file() - Validate certificate file name value * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_cert_file(cfg_t *cfg, cfg_opt_t *opt) { char *certs_path = NULL; char *file_path = NULL; char *val = cfg_opt_getnstr(opt, 0); if (cfg_check_string_length(cfg, opt, 1, 0) != 0) return -1; certs_path = cfg_getstr(cfg, SETTING_CERTS_PATH); if (certs_path == NULL) { cfg_error(cfg, "Invalid %s: NULL value", SETTING_CERTS_PATH); return -1; } file_path = calloc(1, sizeof(*file_path) * (strlen(certs_path) + strlen(val) + 2)); if (file_path == NULL) { cfg_error(cfg, "Error checking %s: unable to allocate memory", opt->name); return -1; } sprintf(file_path, "%s/%s", certs_path, val); if (!file_readable(file_path)) { cfg_error(cfg, "Invalid %s: cannot find file '%s'", opt->name, val); free(file_path); return -1; } free(file_path); return 0; } /* * cfg_check_shadow_report_rate() - Validate shadow report rate value is between * 1s and a year * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_shadow_report_rate(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_int_range(cfg, opt, SETTING_SHADOW_REPORT_RATE_MIN, SETTING_SHADOW_REPORT_RATE_MAX); } /* * cfg_check_temp_variation() - Validate temperature variation value is between * 0.1 and 10.0 * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_temp_variation(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_float_range(cfg, opt, SETTING_TEMP_VARIATION_MIN, SETTING_TEMP_VARIATION_MAX); } /* * cfg_check_cpuload_variation() - Validate CPU load variation value is between * 0.1 and 50.0 * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_cpuload_variation(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_float_range(cfg, opt, SETTING_CPULOAD_VARIATION_MIN, SETTING_CPULOAD_VARIATION_MAX); } /* * cfg_check_int_range() - Validate a parameter int value is between the given range * * @cfg: The section where the option is defined. * @opt: The option to check. * @min: Minimum value of the parameter. * @max: Maximum value of the parameter. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_int_range(cfg_t *cfg, cfg_opt_t *opt, uint32_t min, uint32_t max) { unsigned long val = cfg_opt_getnint(opt, 0); if (val > max || val < min) { cfg_error(cfg, "Invalid %s (%d): value must be between %d and %d", opt->name, val, min, max); return -1; } return 0; } /* * cfg_check_float_range() - Validate a parameter float value is between the given range * * @cfg: The section where the option is defined. * @opt: The option to check. * @min: Minimum value of the parameter. * @max: Maximum value of the parameter. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_float_range(cfg_t *cfg, cfg_opt_t *opt, float min, float max) { float val = cfg_opt_getnfloat(opt, 0); if (val > max || val < min) { cfg_error(cfg, "Invalid %s (%f): value must be between %f and %f", opt->name, val, min, max); return -1; } return 0; } /* * cfg_check_empty_string() - Validate string is empty * * @cfg: The section where the option is defined. * @opt: The option to check. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_empty_string(cfg_t *cfg, cfg_opt_t *opt) { return cfg_check_string_length(cfg, opt, 1, 0); } /* * cfg_check_string_length() - Validate the length of a string is in range * * @cfg: The section where the option is defined. * @opt: The option to check. * @min: The string minimum length. * @max: The string maximum length. 0 to unlimited. * * @Return: 0 on success, any other value otherwise. */ static int cfg_check_string_length(cfg_t *cfg, cfg_opt_t *opt, uint16_t min, uint16_t max) { char *val = cfg_opt_getnstr(opt, 0); if ((val == NULL) || ((strlen(val) == 0) && (min > 0))) { cfg_error(cfg, "Invalid %s (%s): cannot be empty", opt->name, val); return -1; } if (strlen(val) < min) { cfg_error(cfg, "Invalid %s (%s): cannot be shorter than %d character(s)", opt->name, val, min); return -1; } if (max != 0 && strlen(val) > max) { cfg_error(cfg, "Invalid %s (%s): cannot be longer than %d character(s)", opt->name, val, max); return -1; } return 0; } /** * file_exists() - Check that the file with the given name exists * * @filename: Full path of the file to check if it exists. * * Return: 1 if the file exits, 0 if it does not exist. */ static int file_exists(const char *const filename) { return access(filename, F_OK) == 0; } /** * file_readable() - Check that the file with the given name can be read * * @filename: Full path of the file to check if it is readable. * * Return: 1 if the file is readable, 0 if it cannot be read. */ static int file_readable(const char *const filename) { return access(filename, R_OK) == 0; }