<commit_before>#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include <jansson.h>
#include <evhttp.h>
#include <b64/cencode.h>
#include "conf.h"
#include "acl.h"

static struct acl *
conf_parse_acls(json_t *jtab);

#define ACL_ERROR_PREFIX "Config error with 'redis_auth': "
#define ACL_ERROR_SUFFIX ". Starting with auth disabled.\n"

static struct auth *
conf_auth_legacy(char *password);
static struct auth *
conf_auth_username_password(json_t *jarray);

int
conf_str_allcaps(const char *s, const size_t sz) {
	size_t i;
	for(i = 0; i < sz; i++) {
		if(s[i] != toupper(s[i])) {
			return 0;
		}
	}
	return 1;
}

char *
conf_string_or_envvar(const char *val) {
	if(!val) {
		return strdup(""); /* safe value for atoi/aol */
	}
	size_t val_len = strlen(val);
	if(val_len >= 2 && val[0] == '$' && conf_str_allcaps(val+1, val_len-1)) {
		char *env_val = getenv(val + 1);
		if(env_val) { /* found in environment */
			return strdup(env_val);
		} else {
			fprintf(stderr, "No value found for env var %s\n", val+1);
		}
	}
	/* duplicate string coming from JSON parser */
	return strdup(val);
}

int
atoi_free(char *s) {
	int val = atoi(s);
	free(s);
	return val;
}

int
is_true_free(char *s) {
	int val = strcasecmp(s, "true") == 0 ? 1 : 0;
	free(s);
	return val;
}

struct conf *
conf_read(const char *filename) {

	json_t *j;
	json_error_t error;
	struct conf *conf;
	void *kv;

	/* defaults */
	conf = calloc(1, sizeof(struct conf));
	conf->redis_host = strdup("127.0.0.1");
	conf->redis_port = 6379;
	conf->http_host = strdup("0.0.0.0");
	conf->http_port = 7379;
	conf->http_max_request_size = 128*1024*1024;
	conf->http_threads = 4;
	conf->user = getuid();
	conf->group = getgid();
	conf->logfile = "webdis.log";
	conf->log_fsync.mode = LOG_FSYNC_AUTO;
	conf->verbosity = WEBDIS_NOTICE;
	conf->daemonize = 0;
	conf->pidfile = "webdis.pid";
	conf->database = 0;
	conf->pool_size_per_thread = 2;

	j = json_load_file(filename, 0, &error);
	if(!j) {
		fprintf(stderr, "Error: %s (line %d)\n", error.text, error.line);
		return conf;
	}

	for(kv = json_object_iter(j); kv; kv = json_object_iter_next(j, kv)) {
		json_t *jtmp = json_object_iter_value(kv);

		if(strcmp(json_object_iter_key(kv), "redis_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
			free(conf->redis_host);
			conf->redis_host = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->redis_port = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->redis_port = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0) {
			if(json_typeof(jtmp) == JSON_STRING) {
				conf->redis_auth = conf_auth_legacy(conf_string_or_envvar(json_string_value(jtmp)));
			} else if(json_typeof(jtmp) == JSON_ARRAY) {
				conf->redis_auth = conf_auth_username_password(jtmp);
			} else if(json_typeof(jtmp) != JSON_NULL) {
				fprintf(stderr, ACL_ERROR_PREFIX "expected a string or an array of two strings" ACL_ERROR_SUFFIX);
			}
		} else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
			free(conf->http_host);
			conf->http_host = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_port = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_port = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "http_max_request_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_max_request_size = (size_t)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "http_max_request_size") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_max_request_size = (size_t) atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "threads") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_threads = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "threads") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_threads = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "acl") == 0 && json_typeof(jtmp) == JSON_ARRAY) {
			conf->perms = conf_parse_acls(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "user") == 0 && json_typeof(jtmp) == JSON_STRING) {
			struct passwd *u;
			if((u = getpwnam(conf_string_or_envvar(json_string_value(jtmp))))) {
				conf->user = u->pw_uid;
			}
		} else if(strcmp(json_object_iter_key(kv), "group") == 0 && json_typeof(jtmp) == JSON_STRING) {
			struct group *g;
			if((g = getgrnam(conf_string_or_envvar(json_string_value(jtmp))))) {
				conf->group = g->gr_gid;
			}
		} else if(strcmp(json_object_iter_key(kv),"logfile") == 0 && json_typeof(jtmp) == JSON_STRING){
			conf->logfile = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv),"log_fsync") == 0) {
			if(json_typeof(jtmp) == JSON_STRING && strcmp(json_string_value(jtmp), "auto") == 0) {
				conf->log_fsync.mode = LOG_FSYNC_AUTO;
			} else if(json_typeof(jtmp) == JSON_STRING && strcmp(json_string_value(jtmp), "all") == 0) {
				conf->log_fsync.mode = LOG_FSYNC_ALL;
			} else if(json_typeof(jtmp) == JSON_INTEGER && json_integer_value(jtmp) > 0) {
				conf->log_fsync.mode = LOG_FSYNC_MILLIS;
				conf->log_fsync.period_millis = (int)json_integer_value(jtmp);
			} else if(json_typeof(jtmp) != JSON_NULL) {
				fprintf(stderr, "Unexpected value for \"log_fsync\", defaulting to \"auto\"\n");
			}
		} else if(strcmp(json_object_iter_key(kv),"verbosity") == 0 && json_typeof(jtmp) == JSON_INTEGER){
			int tmp = json_integer_value(jtmp);
			if(tmp < 0 || tmp > (int)WEBDIS_TRACE) {
				fprintf(stderr, "Invalid log verbosity: %d. Acceptable range: [%d .. %d]\n",
					tmp, WEBDIS_ERROR, WEBDIS_TRACE);
			}
			conf->verbosity = (tmp < 0 ? WEBDIS_ERROR : (tmp > WEBDIS_TRACE ? WEBDIS_TRACE : (log_level)tmp));
		} else if(strcmp(json_object_iter_key(kv), "daemonize") == 0 && json_typeof(jtmp) == JSON_TRUE) {
			conf->daemonize = 1;
		} else if(strcmp(json_object_iter_key(kv), "daemonize") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->daemonize = is_true_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv),"pidfile") == 0 && json_typeof(jtmp) == JSON_STRING){
			conf->pidfile = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "websockets") == 0 && json_typeof(jtmp) == JSON_TRUE) {
			conf->websockets = 1;
		} else if(strcmp(json_object_iter_key(kv), "websockets") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->websockets = is_true_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "database") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->database = json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "database") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->database = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "pool_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->pool_size_per_thread = json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "pool_size") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->pool_size_per_thread = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "default_root") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->default_root = conf_string_or_envvar(json_string_value(jtmp));
		}
	}

	json_decref(j);<commit_msg>Report invalid config keys or value types<commit_after>#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include <jansson.h>
#include <evhttp.h>
#include <b64/cencode.h>
#include "conf.h"
#include "acl.h"

static struct acl *
conf_parse_acls(json_t *jtab);

#define ACL_ERROR_PREFIX "Config error with 'redis_auth': "
#define ACL_ERROR_SUFFIX ". Starting with auth disabled.\n"

static struct auth *
conf_auth_legacy(char *password);
static struct auth *
conf_auth_username_password(json_t *jarray);

int
conf_str_allcaps(const char *s, const size_t sz) {
	size_t i;
	for(i = 0; i < sz; i++) {
		if(s[i] != toupper(s[i])) {
			return 0;
		}
	}
	return 1;
}

char *
conf_string_or_envvar(const char *val) {
	if(!val) {
		return strdup(""); /* safe value for atoi/aol */
	}
	size_t val_len = strlen(val);
	if(val_len >= 2 && val[0] == '$' && conf_str_allcaps(val+1, val_len-1)) {
		char *env_val = getenv(val + 1);
		if(env_val) { /* found in environment */
			return strdup(env_val);
		} else {
			fprintf(stderr, "No value found for env var %s\n", val+1);
		}
	}
	/* duplicate string coming from JSON parser */
	return strdup(val);
}

int
atoi_free(char *s) {
	int val = atoi(s);
	free(s);
	return val;
}

int
is_true_free(char *s) {
	int val = strcasecmp(s, "true") == 0 ? 1 : 0;
	free(s);
	return val;
}

struct conf *
conf_read(const char *filename) {

	json_t *j;
	json_error_t error;
	struct conf *conf;
	void *kv;

	/* defaults */
	conf = calloc(1, sizeof(struct conf));
	conf->redis_host = strdup("127.0.0.1");
	conf->redis_port = 6379;
	conf->http_host = strdup("0.0.0.0");
	conf->http_port = 7379;
	conf->http_max_request_size = 128*1024*1024;
	conf->http_threads = 4;
	conf->user = getuid();
	conf->group = getgid();
	conf->logfile = "webdis.log";
	conf->log_fsync.mode = LOG_FSYNC_AUTO;
	conf->verbosity = WEBDIS_NOTICE;
	conf->daemonize = 0;
	conf->pidfile = "webdis.pid";
	conf->database = 0;
	conf->pool_size_per_thread = 2;

	j = json_load_file(filename, 0, &error);
	if(!j) {
		fprintf(stderr, "Error: %s (line %d)\n", error.text, error.line);
		return conf;
	}

	for(kv = json_object_iter(j); kv; kv = json_object_iter_next(j, kv)) {
		json_t *jtmp = json_object_iter_value(kv);

		if(strcmp(json_object_iter_key(kv), "redis_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
			free(conf->redis_host);
			conf->redis_host = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->redis_port = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->redis_port = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0) {
			if(json_typeof(jtmp) == JSON_STRING) {
				conf->redis_auth = conf_auth_legacy(conf_string_or_envvar(json_string_value(jtmp)));
			} else if(json_typeof(jtmp) == JSON_ARRAY) {
				conf->redis_auth = conf_auth_username_password(jtmp);
			} else if(json_typeof(jtmp) != JSON_NULL) {
				fprintf(stderr, ACL_ERROR_PREFIX "expected a string or an array of two strings" ACL_ERROR_SUFFIX);
			}
		} else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
			free(conf->http_host);
			conf->http_host = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_port = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_port = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "http_max_request_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_max_request_size = (size_t)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "http_max_request_size") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_max_request_size = (size_t) atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "threads") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->http_threads = (int)json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "threads") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->http_threads = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "acl") == 0 && json_typeof(jtmp) == JSON_ARRAY) {
			conf->perms = conf_parse_acls(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "user") == 0 && json_typeof(jtmp) == JSON_STRING) {
			struct passwd *u;
			if((u = getpwnam(conf_string_or_envvar(json_string_value(jtmp))))) {
				conf->user = u->pw_uid;
			}
		} else if(strcmp(json_object_iter_key(kv), "group") == 0 && json_typeof(jtmp) == JSON_STRING) {
			struct group *g;
			if((g = getgrnam(conf_string_or_envvar(json_string_value(jtmp))))) {
				conf->group = g->gr_gid;
			}
		} else if(strcmp(json_object_iter_key(kv),"logfile") == 0 && json_typeof(jtmp) == JSON_STRING){
			conf->logfile = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv),"log_fsync") == 0) {
			if(json_typeof(jtmp) == JSON_STRING && strcmp(json_string_value(jtmp), "auto") == 0) {
				conf->log_fsync.mode = LOG_FSYNC_AUTO;
			} else if(json_typeof(jtmp) == JSON_STRING && strcmp(json_string_value(jtmp), "all") == 0) {
				conf->log_fsync.mode = LOG_FSYNC_ALL;
			} else if(json_typeof(jtmp) == JSON_INTEGER && json_integer_value(jtmp) > 0) {
				conf->log_fsync.mode = LOG_FSYNC_MILLIS;
				conf->log_fsync.period_millis = (int)json_integer_value(jtmp);
			} else if(json_typeof(jtmp) != JSON_NULL) {
				fprintf(stderr, "Unexpected value for \"log_fsync\", defaulting to \"auto\"\n");
			}
		} else if(strcmp(json_object_iter_key(kv),"verbosity") == 0 && json_typeof(jtmp) == JSON_INTEGER){
			int tmp = json_integer_value(jtmp);
			if(tmp < 0 || tmp > (int)WEBDIS_TRACE) {
				fprintf(stderr, "Invalid log verbosity: %d. Acceptable range: [%d .. %d]\n",
					tmp, WEBDIS_ERROR, WEBDIS_TRACE);
			}
			conf->verbosity = (tmp < 0 ? WEBDIS_ERROR : (tmp > WEBDIS_TRACE ? WEBDIS_TRACE : (log_level)tmp));
		} else if(strcmp(json_object_iter_key(kv), "daemonize") == 0 && (json_typeof(jtmp) == JSON_TRUE || json_typeof(jtmp) == JSON_FALSE)) {
			conf->daemonize = (json_typeof(jtmp) == JSON_TRUE) ? 1 : 0;
		} else if(strcmp(json_object_iter_key(kv), "daemonize") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->daemonize = is_true_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv),"pidfile") == 0 && json_typeof(jtmp) == JSON_STRING){
			conf->pidfile = conf_string_or_envvar(json_string_value(jtmp));
		} else if(strcmp(json_object_iter_key(kv), "websockets") == 0 && (json_typeof(jtmp) == JSON_TRUE || json_typeof(jtmp) == JSON_FALSE)) {
			conf->websockets = (json_typeof(jtmp) == JSON_TRUE) ? 1 : 0;
		} else if(strcmp(json_object_iter_key(kv), "websockets") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->websockets = is_true_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "database") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->database = json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "database") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->database = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "pool_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
			conf->pool_size_per_thread = json_integer_value(jtmp);
		} else if(strcmp(json_object_iter_key(kv), "pool_size") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->pool_size_per_thread = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
		} else if(strcmp(json_object_iter_key(kv), "default_root") == 0 && json_typeof(jtmp) == JSON_STRING) {
			conf->default_root = conf_string_or_envvar(json_string_value(jtmp));
		} else {
			fprintf(stderr, "Warning! Unexpected key or incorrect value in %s: '%s'\n", filename, json_object_iter_key(kv));
		}
	}

	json_decref(j);
{'fraction_non_alphanumeric': 0.16421374045801526, 'fraction_numerical': 0.010259541984732825, 'mean_word_length': 5.576706827309237, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 133, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5764011', 'n_tokens_mistral': 6935, 'n_tokens_neox': 6506, 'n_words': 1058}