700 lines
18 KiB
C
700 lines
18 KiB
C
#include "conf_file_context.h"
|
|
#include "gconf_file.h"
|
|
#include "types.h"
|
|
|
|
#include <assert.h>
|
|
|
|
#define KEY_FILE_DEFAULT_LIST_SEPARATOR ' '
|
|
|
|
|
|
typedef GKeyFile conf_file_context_s;
|
|
|
|
#define is_allowed_tcp_port(port_num) (port_num >= 0 && port_num < 65536)
|
|
|
|
/** Checks if character @c is allowed in location.
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_location_char(char c)
|
|
{
|
|
if (c >= '0' && c <= '9') return 1; /* true */
|
|
|
|
unsigned char ch = (unsigned char) (c | 0x20); /* lower */
|
|
if (ch >= 'a' && ch <= 'z') return 1; /* true */
|
|
|
|
switch (c) {
|
|
/* pct-encoding */
|
|
case '%':
|
|
|
|
/* unreserved */
|
|
case '-': case '.': case '_': case '~':
|
|
|
|
/* sub-delims */
|
|
case '!': case '$': case '&': case '\'':
|
|
case '(': case ')': case '*': case '+':
|
|
case ',': case ';': case '=':
|
|
|
|
/* gen-delims (allowed) */
|
|
case '/': case ':': case '@':
|
|
|
|
return 1; /* true */
|
|
|
|
/* gen-delims (not allowed) */
|
|
case '?': case '#': case '[': case ']':
|
|
default:
|
|
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
/** Checks if character @c is allowed in uri.
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_uri_char(char c)
|
|
{
|
|
if (c >= '0' && c <= '9') return 1; /* true */
|
|
|
|
unsigned char ch = (unsigned char) (c | 0x20); /* lower */
|
|
if (ch >= 'a' && ch <= 'z') return 1; /* true */
|
|
|
|
switch (c) {
|
|
/* pct-encoding */
|
|
case '%':
|
|
|
|
/* unreserved */
|
|
case '-': case '.': case '_': case '~':
|
|
|
|
/* sub-delims */
|
|
case '!': case '$': case '&': case '\'':
|
|
case '(': case ')': case '*': case '+':
|
|
case ',': case ';': case '=':
|
|
|
|
/* gen-delims */
|
|
case '/': case ':': case '@':
|
|
case '?': case '#': case '[': case ']':
|
|
|
|
return 1; /* true */
|
|
|
|
default:
|
|
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
/** Checks if character @c is allowed in host name.
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_hostname_char(char c)
|
|
{
|
|
if (c >= '0' && c <= '9') return 1; /* true */
|
|
|
|
unsigned char ch = (unsigned char) (c | 0x20); /* lower */
|
|
if (ch >= 'a' && ch <= 'z') return 1; /* true */
|
|
|
|
switch (c) {
|
|
/* pct-encoding */
|
|
case '%':
|
|
|
|
/* unreserved */
|
|
case '-': case '.': case '_': case '~':
|
|
|
|
/* sub-delims */
|
|
case '!': case '$': case '&': case '\'':
|
|
case '(': case ')': case '*': case '+':
|
|
case ',': case ';': case '=':
|
|
|
|
/* gen-delims (allowed) */
|
|
case ':': case '[': case ']':
|
|
|
|
return 1; /* true */
|
|
|
|
/* gen-delims (not allowed) */
|
|
case '/': case '@': case '?': case '#':
|
|
default:
|
|
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
/** Checks the syntax of str_t @location: it is allowed to consist of only
|
|
* rfc-3986 defined characters
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_location_strt(const str_t* location)
|
|
{
|
|
assert(location != NULL && !str_t_is_null(*location));
|
|
|
|
size_t i;
|
|
for (i = 0; i < location->len; i++) {
|
|
if (!allowed_location_char(location->data[i])) {
|
|
LOG_ERROR("location '%.*s' is not valid URI-path: '%c' character is not allowed",
|
|
(int)location->len, location->data, location->data[i]);
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
return 1; /* true */
|
|
}
|
|
|
|
/** Checks the syntax of NULL-terminated string @location: it is allowed
|
|
* to consist of only rfc-3986 defined characters.
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_location_string(const char* location)
|
|
{
|
|
assert(location != NULL);
|
|
|
|
const char* loc;
|
|
for (loc = location; *loc != '\0'; loc++) {
|
|
if (!allowed_location_char(*loc)) {
|
|
LOG_ERROR("location '%s' is not valid URI-path: '%c' character is not allowed",
|
|
location, *loc);
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
return 1; /* true */
|
|
}
|
|
|
|
/** Checks the syntax of str_t @uri: it is allowed to consist of only rfc-3986
|
|
* defined characters
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_uri_strt(const str_t* uri)
|
|
{
|
|
assert(uri != NULL && !str_t_is_null(*uri));
|
|
|
|
size_t i;
|
|
for (i = 0; i < uri->len; i++) {
|
|
if (!allowed_uri_char(uri->data[i])) {
|
|
LOG_ERROR("uri '%.*s' is not valid URI: '%c' character is not allowed",
|
|
(int)uri->len, uri->data, uri->data[i]);
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
return 1; /* true */
|
|
}
|
|
|
|
/** Checks the syntax of NULL-terminated string @uri: it is allowed
|
|
* to consist of only rfc-3986 defined characters.
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_uri_string(const char* uri)
|
|
{
|
|
assert(uri != NULL);
|
|
|
|
const char* u;
|
|
for (u = uri; *u != '\0'; u++) {
|
|
if (!allowed_uri_char(*u)) {
|
|
LOG_ERROR("uri '%s' is not valid URI-path: '%c' character is not allowed",
|
|
uri, *u);
|
|
return 0; /* false */
|
|
}
|
|
}
|
|
|
|
return 1; /* true */
|
|
}
|
|
|
|
/** Checks the syntax of NULL-terminated string @host: it is allowed
|
|
* to consist of only rfc-3986 defined characters. And limit hostname length is
|
|
* HOST_NAME_MAX
|
|
* @return 1 - true, 0 - false
|
|
*/
|
|
static int
|
|
allowed_hostname(const char* host)
|
|
{
|
|
assert(host != NULL);
|
|
|
|
size_t len = 0;
|
|
const char* h = host;
|
|
|
|
for (; *h != '\0' && len <= HOST_NAME_MAX ; h++, len++) {
|
|
if (!allowed_hostname_char(*h)) {
|
|
LOG_ERROR("Hostname '%s' is not valid: '%c' character is not allowed",
|
|
host, *h);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (len == 0 || len > HOST_NAME_MAX) {
|
|
LOG_ERROR("Hostname length (%zu) is not allowed: == 0 or > HOST_NAME_MAX(%d)",
|
|
len, HOST_NAME_MAX);
|
|
return 0; /*false*/
|
|
}
|
|
|
|
return 1; /*true*/
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_boolean_value(const conf_file_field_t *field, int value)
|
|
{
|
|
*((int*)field->value) = value;
|
|
LOG_DEBUG("%s::%s = %s", field->section, field->key, value == TRUE ? "true" : "false");
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_integer_value(const conf_file_field_t *field, int value)
|
|
{
|
|
*((int*)field->value) = value;
|
|
LOG_DEBUG("%s::%s = %d", field->section, field->key, value);
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_integer_list_value(const conf_file_field_t *field, const int_list_t *value)
|
|
{
|
|
*((int_list_t*)field->value) = *value;
|
|
|
|
LOG_DEBUG("%s::%s = {", field->section, field->key);;
|
|
int_list_print(value);
|
|
LOG_DEBUG("}");
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_string_value(const conf_file_field_t *field, char *value)
|
|
{
|
|
*(char**)field->value = value;
|
|
|
|
if (field->check != CONF_FILE_VALUE_PSWD) {
|
|
if (value != NULL) {
|
|
LOG_DEBUG("%s::%s = %s", field->section, field->key, value);
|
|
} else {
|
|
LOG_DEBUG("%s::%s = NULL", field->section, field->key);
|
|
}
|
|
} else {
|
|
LOG_DEBUG("%s::%s = ******", field->section, field->key);
|
|
}
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_string_list_value(const conf_file_field_t *field, const string_list_t *value)
|
|
{
|
|
*((string_list_t*)field->value) = *value;
|
|
|
|
LOG_DEBUG("%s::%s = {", field->section, field->key);
|
|
string_list_print(value);
|
|
LOG_DEBUG("}");
|
|
}
|
|
|
|
|
|
static inline void
|
|
conf_file_set_strt_value(const conf_file_field_t *field, const str_t *value)
|
|
{
|
|
*(str_t*)field->value = *value;
|
|
|
|
if (field->check != CONF_FILE_VALUE_PSWD) {
|
|
LOG_DEBUG("%s::%s = %.*s", field->section, field->key, (int)value->len, value->data);
|
|
} else {
|
|
LOG_DEBUG("%s::%s = ******", field->section, field->key);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
conf_file_set_default_value(const conf_file_field_t *field)
|
|
{
|
|
assert(field != NULL);
|
|
|
|
switch (field->type) {
|
|
case CONF_FILE_VALUE_BOOLEAN:
|
|
|
|
conf_file_set_boolean_value(field, *((int*)field->default_value));
|
|
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_INTEGER:
|
|
|
|
conf_file_set_integer_value(field, *((int*)field->default_value));
|
|
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_INTEGER_LIST:
|
|
{
|
|
int_list_t value = {0};
|
|
if (int_list_copy(&value, field->default_value)) {
|
|
goto error;
|
|
}
|
|
|
|
conf_file_set_integer_list_value(field, &value);
|
|
}
|
|
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRING:
|
|
{
|
|
char *value = NULL;
|
|
char *default_value = *((char**)field->default_value);
|
|
if (default_value != NULL) {
|
|
value = strdup(default_value);
|
|
if (value == NULL) {
|
|
LOG_ERROR("Could not copy default value for '%s::%s'",
|
|
field->section, field->key);
|
|
goto error;
|
|
}
|
|
}
|
|
conf_file_set_string_value(field, value);
|
|
}
|
|
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRING_LIST:
|
|
{
|
|
string_list_t value = {0};
|
|
if (string_list_copy(&value, field->default_value)) {
|
|
goto error;
|
|
}
|
|
|
|
conf_file_set_string_list_value(field, &value);
|
|
}
|
|
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRT:
|
|
{
|
|
str_t value = {0};
|
|
if (str_t_copy(&value, field->default_value)) {
|
|
goto error;
|
|
}
|
|
|
|
conf_file_set_strt_value(field, &value);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERROR("conf_file_set_default_value exit with error");
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
conf_file_load_value(GKeyFile* ini_file, const conf_file_field_t *field, GError** gerror)
|
|
{
|
|
assert(ini_file != NULL);
|
|
assert(field != NULL && field->value != NULL);
|
|
assert(field->key != NULL && field->section != NULL);
|
|
assert(gerror != NULL && *gerror == NULL);
|
|
|
|
switch (field->type) {
|
|
case CONF_FILE_VALUE_BOOLEAN:
|
|
{
|
|
gboolean value = g_key_file_get_boolean(ini_file, field->section, field->key, gerror);
|
|
conf_file_set_boolean_value(field, (int)value);
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_INTEGER:
|
|
{
|
|
gint value = g_key_file_get_integer(ini_file, field->section, field->key, gerror);
|
|
conf_file_set_integer_value(field, (int)value);
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_INTEGER_LIST:
|
|
{
|
|
int_list_t value = {0};
|
|
value.list = g_key_file_get_integer_list(ini_file, field->section, field->key, &(value.size), gerror);
|
|
if (value.list == NULL) break;
|
|
|
|
conf_file_set_integer_list_value(field, &value);
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRING:
|
|
{
|
|
char *value = gconf_file_get_string(ini_file, field->section, field->key, gerror);
|
|
if (value == NULL) break;
|
|
|
|
conf_file_set_string_value(field, value);
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRING_LIST:
|
|
{
|
|
string_list_t value = {0};
|
|
value.list = gconf_file_get_string_list(ini_file, field->section, field->key, &(value.size), gerror);
|
|
if (value.list == NULL) break;
|
|
|
|
conf_file_set_string_list_value(field, &value);
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_STRT:
|
|
{
|
|
str_t value = {0};
|
|
if (gconf_file_get_strt(ini_file, field->section, field->key, gerror, &value)) {
|
|
break;
|
|
}
|
|
if (str_t_is_null(value)) break;
|
|
|
|
conf_file_set_strt_value(field, &value);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
}
|
|
|
|
if (*gerror != NULL) {
|
|
if (field->default_value != NULL && (*gerror)->code != G_KEY_FILE_ERROR_INVALID_VALUE) {
|
|
LOG_DEBUG("Conf field '%s::%s' is optional. Skip error (%s)",
|
|
field->section, field->key, (*gerror)->message);
|
|
g_error_free(*gerror);
|
|
*gerror = NULL;
|
|
|
|
LOG_DEBUG("Set default value for field '%s::%s'", field->section, field->key);
|
|
if (conf_file_set_default_value(field)) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERROR("conf_file_load_value '%s':'%s' exit with error", field->section, field->key);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
conf_file_check_value(const conf_file_field_t *field)
|
|
{
|
|
assert(field != NULL);
|
|
|
|
switch (field->check) {
|
|
case CONF_FILE_VALUE_NONE:
|
|
case CONF_FILE_VALUE_PSWD:
|
|
/* do nothing */
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_HOSTNAME:
|
|
if (field->type == CONF_FILE_VALUE_STRING) {
|
|
char* host = *(char**)field->value;
|
|
if (!allowed_hostname(host)) {
|
|
LOG_ERROR("%s:%s '%s' is not allowed hostname",
|
|
field->section, field->key, host);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_LOCATION:
|
|
if (field->type == CONF_FILE_VALUE_STRING) {
|
|
char *location = *(char**)field->value;
|
|
if (!allowed_location_string(location)) {
|
|
LOG_ERROR("%s:%s '%s' is not allowed location",
|
|
field->section, field->key, location);
|
|
goto error;
|
|
}
|
|
|
|
} else if (field->type == CONF_FILE_VALUE_STRT) {
|
|
str_t *location = (str_t*)field->value;
|
|
if (!allowed_location_strt(location)) {
|
|
LOG_ERROR("%s:%s '%.*s' is not allowed location", field->section,
|
|
field->key, (int)location->len, location->data);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_TCP_PORT:
|
|
if (field->type == CONF_FILE_VALUE_INTEGER) {
|
|
int port = *(int*)field->value;
|
|
if (!is_allowed_tcp_port(port)) {
|
|
LOG_ERROR("%s:%s '%d' not allowed tcp port",
|
|
field->section, field->key, port);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_POSITIVE_INT:
|
|
if (field->type == CONF_FILE_VALUE_INTEGER) {
|
|
int value = *(int*)field->value;
|
|
if (value < 0) {
|
|
LOG_ERROR("%s:%s is not valid: %d (expected value >= 0)",
|
|
field->section, field->key, value);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONF_FILE_VALUE_URI:
|
|
if (field->type == CONF_FILE_VALUE_STRING) {
|
|
char *uri = *(char**)field->value;
|
|
if (uri != NULL) {
|
|
if (!allowed_uri_string(uri)) {
|
|
LOG_ERROR("%s:%s '%s' is not allowed uri",
|
|
field->section, field->key, uri);
|
|
goto error;
|
|
}
|
|
} else if (field->default_value == NULL) {
|
|
LOG_ERROR("%s:%s 'NULL' is not allowed uri",
|
|
field->section, field->key);
|
|
goto error;
|
|
}
|
|
} else if (field->type == CONF_FILE_VALUE_STRT) {
|
|
str_t *uri = (str_t*)field->value;
|
|
if (!str_t_is_null(*uri)) {
|
|
if (!allowed_uri_strt(uri)) {
|
|
LOG_ERROR("%s:%s '%.*s' is not allowed uri", field->section,
|
|
field->key, (int)uri->len, uri->data);
|
|
goto error;
|
|
}
|
|
} else if (field->default_value == NULL) {
|
|
LOG_ERROR("%s:%s 'NULL' is not allowed uri",
|
|
field->section, field->key);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
conf_file_load_from_file(const char *filename, conf_file_field_t* fields, int fields_count)
|
|
{
|
|
LOG_TRACE("conf_file_load_from_file enter. Config filename: %s", filename);
|
|
|
|
conf_file_context_t ctx = conf_file_open_file(filename);
|
|
if (ctx == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
if (conf_file_load_values(ctx, fields, fields_count)) {
|
|
goto error;
|
|
}
|
|
|
|
conf_file_close_file(ctx);
|
|
|
|
LOG_TRACE("conf_file_load_from_file exit");
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERROR("Load configuration from file '%s' FAILED", filename);
|
|
|
|
conf_file_close_file(ctx);
|
|
|
|
LOG_ERROR("conf_file_load_from_file exit with error");
|
|
return -1;
|
|
}
|
|
|
|
|
|
conf_file_context_t
|
|
conf_file_open_file(const char *filename)
|
|
{
|
|
GKeyFile* ini_file = NULL;
|
|
GError* gerror = NULL;
|
|
|
|
LOG_TRACE("conf_file_open_file enter. Config filename: %s", filename);
|
|
|
|
assert(filename != NULL);
|
|
|
|
ini_file = g_key_file_new();
|
|
if (ini_file == NULL) {
|
|
LOG_ERROR("conf_file_open_file failed. Desc: could not create key file instance");
|
|
goto error;
|
|
}
|
|
|
|
g_key_file_set_list_separator(ini_file, KEY_FILE_DEFAULT_LIST_SEPARATOR);
|
|
|
|
if (!g_key_file_load_from_file(ini_file, filename, G_KEY_FILE_KEEP_COMMENTS,
|
|
&gerror)) {
|
|
goto error;
|
|
}
|
|
|
|
return (conf_file_context_t)ini_file;
|
|
|
|
error:
|
|
LOG_ERROR("Open configuration file '%s' FAILED", filename);
|
|
|
|
if (gerror != NULL) {
|
|
LOG_ERROR("Desc: %s", gerror->message);
|
|
g_error_free(gerror);
|
|
}
|
|
|
|
if (ini_file != NULL) {
|
|
g_key_file_free(ini_file);
|
|
}
|
|
|
|
LOG_ERROR("conf_file_open_file exit with error");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
conf_file_close_file(conf_file_context_t ctx)
|
|
{
|
|
if (ctx) {
|
|
GKeyFile* ini_file = (GKeyFile*) ctx;
|
|
g_key_file_free(ini_file);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
conf_file_load_values(const conf_file_context_t ctx, const conf_file_field_t* fields, int fields_count)
|
|
{
|
|
GKeyFile* ini_file = (GKeyFile*) ctx;
|
|
GError* gerror = NULL;
|
|
int i = 0;
|
|
|
|
for (; i < fields_count; i++) {
|
|
if (conf_file_load_value(ini_file, &fields[i], &gerror)) {
|
|
goto error;
|
|
}
|
|
|
|
if (conf_file_check_value(&fields[i])) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERROR("Load configuration FAILED");
|
|
if (gerror != NULL) {
|
|
LOG_ERROR("Desc: %s", gerror->message);
|
|
g_error_free(gerror);
|
|
}
|
|
LOG_ERROR("conf_file_load_values exit with error");
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
conf_file_has_section(const conf_file_context_t ctx, const char *section)
|
|
{
|
|
GKeyFile* ini_file = (GKeyFile*) ctx;
|
|
|
|
return g_key_file_has_group(ini_file, section) ? 0 : 1;
|
|
}
|