
/*
 * Odyssey.
 *
 * Scalable PostgreSQL connection pooler.
 */

#include <odyssey.h>

#include <machinarium/machinarium.h>

#include <hba_reader.h>
#include <parser.h>
#include <config_reader.h>

enum {
	OD_LLOCAL,
	OD_LHOST,
	OD_LHOSTSSL,
	OD_LHOSTNOSSL,
	OD_LALL,
	OD_LSAMEUSER,
	OD_LALLOW,
	OD_LDENY,
};

static od_keyword_t od_hba_keywords[] = {
	/* connection types */
	od_keyword("local", OD_LLOCAL),
	od_keyword("host", OD_LHOST),
	od_keyword("hostssl", OD_LHOSTSSL),
	od_keyword("hostnossl", OD_LHOSTNOSSL),
	/* db/user */
	od_keyword("all", OD_LALL),
	od_keyword("sameuser", OD_LSAMEUSER),
	/* auth type */
	od_keyword("allow", OD_LALLOW),
	od_keyword("trust", OD_LALLOW),
	od_keyword("deny", OD_LDENY),
	od_keyword("reject", OD_LDENY),
	{ 0, 0, 0 },
};

static void od_hba_reader_error(od_config_reader_t *reader, char *msg)
{
	od_errorf(reader->error, "%s:%d %s", reader->config_file,
		  reader->parser.line, msg);
}

static int od_hba_parser_next(od_parser_t *parser, od_token_t *token)
{
	/* try to use backlog */
	if (parser->backlog_count > 0) {
		*token = parser->backlog[parser->backlog_count - 1];
		parser->backlog_count--;
		return token->type;
	}
	/* skip white spaces and comments */
	for (;;) {
		while (parser->pos < parser->end && isspace(*parser->pos)) {
			if (*parser->pos == '\n') {
				parser->line++;
			}
			parser->pos++;
		}
		if (od_unlikely(parser->pos == parser->end)) {
			token->type = OD_PARSER_EOF;
			return token->type;
		}
		if (*parser->pos != '#') {
			break;
		}
		while (parser->pos < parser->end && *parser->pos != '\n') {
			parser->pos++;
		}
		if (parser->pos == parser->end) {
			token->type = OD_PARSER_EOF;
			return token->type;
		}
		parser->line++;
	}

	/* symbols */
	if (*parser->pos != '\"' && ispunct(*parser->pos)) {
		token->type = OD_PARSER_SYMBOL;
		token->line = parser->line;
		token->value.num = *parser->pos;
		parser->pos++;
		return token->type;
	}

	if (isalnum(*parser->pos)) {
		token->type = OD_PARSER_KEYWORD;
		token->line = parser->line;
		token->value.string.pointer = parser->pos;
		while (parser->pos < parser->end && *parser->pos != ',' &&
		       (isalnum(*parser->pos) || ispunct(*parser->pos))) {
			parser->pos++;
		}
		token->value.string.size =
			parser->pos - token->value.string.pointer;
		return token->type;
	}

	if (*parser->pos == '\"') {
		token->type = OD_PARSER_STRING;
		token->line = parser->line;
		parser->pos++;
		token->value.string.pointer = parser->pos;
		while (parser->pos < parser->end && *parser->pos != '\"') {
			if (*parser->pos == '\n') {
				token->type = OD_PARSER_ERROR;
				return token->type;
			}
			parser->pos++;
		}
		if (od_unlikely(parser->pos == parser->end)) {
			token->type = OD_PARSER_ERROR;
			return token->type;
		}
		token->value.string.size =
			parser->pos - token->value.string.pointer;
		parser->pos++;
		return token->type;
	}

	/* error */
	token->type = OD_PARSER_ERROR;
	token->line = parser->line;
	return token->type;
}

static int od_hba_reader_match_string(od_token_t token, char **value)
{
	char *copy = od_malloc(token.value.string.size + 1);
	if (copy == NULL) {
		return NOT_OK_RESPONSE;
	}
	memcpy(copy, token.value.string.pointer, token.value.string.size);
	copy[token.value.string.size] = 0;
	if (*value) {
		od_free(*value);
	}
	*value = copy;
	return OK_RESPONSE;
}

static int od_hba_reader_value(od_config_reader_t *reader, void **dest)
{
	od_token_t token;
	int rc;
	char *string_value = NULL;
	rc = od_hba_parser_next(&reader->parser, &token);
	switch (rc) {
	case OD_PARSER_EOF:
		return rc;
	case OD_PARSER_KEYWORD: {
		od_keyword_t *match;
		match = od_keyword_match(od_hba_keywords, &token);
		if (match) {
			*dest = match;
			return OD_PARSER_KEYWORD;
		}
		if (od_hba_reader_match_string(token, &string_value) ==
		    OK_RESPONSE) {
			*dest = string_value;
			return OD_PARSER_STRING;
		}
		od_hba_reader_error(reader, "unable to read string");
		return -1;
	}
	case OD_PARSER_STRING:
		if (od_hba_reader_match_string(token, &string_value) ==
		    OK_RESPONSE) {
			*dest = string_value;
			return OD_PARSER_STRING;
		}
		od_hba_reader_error(reader, "unable to read string");
		return -1;
	default:
		od_hba_reader_error(reader, "expected string or keyword");
		return -1;
	}
}

static int od_hba_reader_name(od_config_reader_t *reader,
			      struct od_hba_rule_name *name, bool is_db)
{
	od_keyword_t *keyword = NULL;
	int rc;
	void *value = NULL;
	od_token_t token;
	for (;;) {
		rc = od_hba_reader_value(reader, &value);
		switch (rc) {
		case OD_PARSER_STRING: {
			struct od_hba_rule_name_item *item =
				od_hba_rule_name_item_add(name);
			item->value = (char *)value;
			break;
		}
		case OD_PARSER_KEYWORD:
			keyword = (od_keyword_t *)value;
			switch (keyword->id) {
			case OD_LALL:
				name->flags |= OD_HBA_NAME_ALL;
				break;
			case OD_LSAMEUSER:
				if (is_db) {
					name->flags |= OD_HBA_NAME_SAMEUSER;
				}
			}
			break;
		default:
			od_hba_reader_error(reader, "expected name or keyword");
			return -1;
		}

		rc = od_hba_parser_next(&reader->parser, &token);
		if (rc == OD_PARSER_SYMBOL && token.value.num == ',') {
			continue;
		}

		od_parser_push(&reader->parser, &token);
		return 0;
	}
}

int od_hba_reader_parse(od_config_reader_t *reader)
{
	od_hba_rule_t *hba = NULL;

	for (;;) {
		hba = od_hba_rule_create();
		if (hba == NULL) {
			od_hba_reader_error(reader, "memory allocation error");
			return -1;
		}

		/* connection type */
		od_keyword_t *keyword = NULL;
		void *connection_type = NULL;
		od_hba_rule_conn_type_t conn_type;
		int rc;
		rc = od_hba_reader_value(reader, &connection_type);
		if (rc == OD_PARSER_EOF) {
			od_hba_rule_free(hba);
			return 0;
		}
		if (rc != OD_PARSER_KEYWORD) {
			od_hba_reader_error(reader, "invalid connection type");
			goto error;
		}
		keyword = (od_keyword_t *)connection_type;
		switch (keyword->id) {
		case OD_LLOCAL:
			conn_type = OD_CONFIG_HBA_LOCAL;
			break;
		case OD_LHOST:
			conn_type = OD_CONFIG_HBA_HOST;
			break;
		case OD_LHOSTSSL:
			conn_type = OD_CONFIG_HBA_HOSTSSL;
			break;
		case OD_LHOSTNOSSL:
			conn_type = OD_CONFIG_HBA_HOSTNOSSL;
			break;
		default:
			od_hba_reader_error(reader, "invalid connection type");
			goto error;
		}
		hba->connection_type = conn_type;

		/* db & user name */
		if (od_hba_reader_name(reader, &hba->database, true) != 0) {
			goto error;
		}
		if (od_hba_reader_name(reader, &hba->user, false) != 0) {
			goto error;
		}

		if (conn_type != OD_CONFIG_HBA_LOCAL) {
			void *address = NULL;
			char *mask = NULL;

			/* ip address */
			rc = od_hba_reader_value(reader, &address);
			if (rc != OD_PARSER_STRING) {
				od_hba_reader_error(reader,
						    "expected IP address");
				goto error;
			}
			mask = strchr(address, '/');
			if (mask) {
				*mask++ = 0;
			}

			if (od_address_read(&hba->address_range.addr,
					    address) == NOT_OK_RESPONSE) {
				od_hba_reader_error(reader,
						    "invalid IP address");
				goto error;
			}

			/* network mask */
			if (mask) {
				if (od_address_range_read_prefix(
					    &hba->address_range, mask) == -1) {
					od_hba_reader_error(
						reader,
						"invalid network prefix length");
					goto error;
				}

			} else {
				rc = od_hba_reader_value(reader, &address);
				if (rc != OD_PARSER_STRING) {
					od_hba_reader_error(
						reader,
						"expected network mask");
					goto error;
				}
				if (od_address_read(&hba->address_range.mask,
						    address) == -1) {
					od_hba_reader_error(
						reader, "invalid network mask");
					goto error;
				}
			}

			if (address) {
				free(address);
			}
		}

		/* auth method */
		void *auth_method = NULL;
		rc = od_hba_reader_value(reader, &auth_method);
		if (rc != OD_PARSER_KEYWORD) {
			od_hba_reader_error(reader, "expected auth method");
			goto error;
		}

		keyword = (od_keyword_t *)auth_method;
		switch (keyword->id) {
		case OD_LALLOW:
			hba->auth_method = OD_CONFIG_HBA_ALLOW;
			break;
		case OD_LDENY:
			hba->auth_method = OD_CONFIG_HBA_DENY;
			break;
		default:
			od_hba_reader_error(
				reader,
				"invalid auth method: only allow/deny or trust/reject is now supported");
			goto error;
		}

		od_hba_rules_add(reader->hba_rules, hba);
	}
error:
	od_hba_rule_free(hba);
	return -1;
}
