From f9a1ec21bc908ce4814b3ca2cfc7f1028204e466 Mon Sep 17 00:00:00 2001 From: vadv Date: Mon, 4 Jul 2022 13:01:34 +0300 Subject: [PATCH] Add HBA config file support, add hba reload logic. (#442) Add HBA config file support, add hba reload logic. Currently supported: local/host/hostssl/hostnossl IPv4/IPv6 address ranges rules reload on receiving SIGHUP simple tests What to improve: default db/user @ support in db/user fields ? set auth method for each hba rule ? replace rw_lock with coroutine synchronization ? make parser kind of configurable Authored-by: Alexander Belev Co-authored-by: Dmitry Vasiliev --- docker/Dockerfile | 1 + docker/bin/setup | 12 +- docker/entrypoint.sh | 10 +- docker/hba/common.conf | 56 +++++ docker/hba/odyssey_hba.conf | 4 + docker/hba/tcp.conf | 6 + docker/hba/test.sh | 91 ++++++++ docker/hba/unix.conf | 5 + documentation/configuration.md | 15 ++ sources/CMakeLists.txt | 5 +- sources/config.c | 3 + sources/config.h | 1 + sources/config_common.h | 1 + sources/config_reader.c | 35 ++- sources/config_reader.h | 3 +- sources/frontend.c | 10 +- sources/global.h | 5 +- sources/hba.c | 164 ++++++++++++++ sources/hba.h | 24 ++ sources/hba_reader.c | 397 +++++++++++++++++++++++++++++++++ sources/hba_reader.h | 6 + sources/hba_rule.c | 75 +++++++ sources/hba_rule.h | 59 +++++ sources/instance.c | 6 +- sources/odyssey.h | 3 + sources/system.c | 9 +- 26 files changed, 994 insertions(+), 12 deletions(-) create mode 100644 docker/hba/common.conf create mode 100644 docker/hba/odyssey_hba.conf create mode 100644 docker/hba/tcp.conf create mode 100755 docker/hba/test.sh create mode 100644 docker/hba/unix.conf create mode 100644 sources/hba.c create mode 100644 sources/hba.h create mode 100644 sources/hba_reader.c create mode 100644 sources/hba_reader.h create mode 100644 sources/hba_rule.c create mode 100644 sources/hba_rule.h diff --git a/docker/Dockerfile b/docker/Dockerfile index 03962f4b..b14c9c88 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -62,6 +62,7 @@ COPY ./docker/prep_stmts/pstmts.conf /etc/odyssey/pstmts.conf COPY --from=base /ody-integration-test/pkg/ody-integration-test /ody-integration-test COPY --from=base /prep_stmts/pkg/pstmts-test /pstmts-test COPY ./docker/scram /scram +COPY ./docker/hba /hba COPY ./docker/auth_query /auth_query COPY ./docker/ldap /ldap COPY ./docker/lagpolling /lagpolling diff --git a/docker/bin/setup b/docker/bin/setup index a0fda458..04b11547 100755 --- a/docker/bin/setup +++ b/docker/bin/setup @@ -24,6 +24,8 @@ local all postgres trust host postgres user1 127.0.0.1/32 trust host db1 user1 127.0.0.1/32 trust host all postgres 127.0.0.1/32 trust +host all user_allow 127.0.0.1/32 trust +host all user_reject 127.0.0.1/32 trust EOF sed -i 's/max_connections = 100/max_connections = 2000/g' /etc/postgresql/14/main/postgresql.conf @@ -36,7 +38,7 @@ if ! /usr/bin/pg_ctlcluster 14 main start && psql -h localhost -p 5432 -U postgr fi # Create databases -for database_name in db scram_db ldap_db auth_query_db db1; do +for database_name in db scram_db ldap_db auth_query_db db1 hba_db; do sudo -u postgres createdb $database_name >> "$SETUP_LOG" 2>&1 || { echo "ERROR: 'createdb $database_name' failed, examine the log" cat "$SETUP_LOG" @@ -102,6 +104,14 @@ psql -h localhost -p 5432 -U user1 -d db1 -c "CREATE SCHEMA sh1" >> $SETUP_LOG 2 exit 1 } +# Create users +psql -h localhost -p 5432 -U postgres -c "create user user_allow password 'correct_password'; create user user_reject password 'correct_password'; create user user_unknown password 'correct_password';" >> $SETUP_LOG 2>&1 || { + echo "ERROR: users creation failed, examine the log" + cat "$SETUP_LOG" + cat "$PG_LOG" + exit 1 +} + for i in `seq 0 9` do # Create users diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index e7d22ae8..790596f6 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -20,8 +20,6 @@ then exit 1 fi -ody-stop - # auth query /auth_query/test_auth_query.sh if [ $? -eq 1 ] @@ -29,8 +27,16 @@ then exit 1 fi +# odyssey hba test +/hba/test.sh +if [ $? -eq 1 ] +then + exit 1 +fi + #prepared statements in transaction pooling /usr/bin/odyssey /etc/odyssey/pstmts.conf +sleep 1 /pstmts-test ody-stop diff --git a/docker/hba/common.conf b/docker/hba/common.conf new file mode 100644 index 00000000..f9fad009 --- /dev/null +++ b/docker/hba/common.conf @@ -0,0 +1,56 @@ +storage "postgres_server" { + type "remote" + host "127.0.0.1" + port 5432 +} + +database "hba_db" { + user "user_allow" { + authentication "clear_text" + password "correct_password" + + storage "postgres_server" + storage_db "hba_db" + + pool "session" + } + + user "user_reject" { + authentication "clear_text" + password "correct_password" + + storage "postgres_server" + storage_db "hba_db" + + pool "session" + } + + user "user_unknown" { + authentication "clear_text" + password "correct_password" + + storage "postgres_server" + storage_db "hba_db" + + pool "session" + } +} + +daemonize yes +pid_file "/var/run/odyssey.pid" + +unix_socket_dir "/tmp" +unix_socket_mode "0644" + +locks_dir "/tmp" + +log_format "%p %t %l [%i %s] (%c) %m\n" +log_file "/var/log/odyssey.log" +log_to_stdout no +log_config yes +log_debug yes +log_session yes +log_stats no +log_query yes + +hba_file "/hba/odyssey_hba.conf" diff --git a/docker/hba/odyssey_hba.conf b/docker/hba/odyssey_hba.conf new file mode 100644 index 00000000..57c2adaf --- /dev/null +++ b/docker/hba/odyssey_hba.conf @@ -0,0 +1,4 @@ +local hba_db user_allow allow +local hba_db user_reject deny +host hba_db user_allow 127.0.0.0/24 allow +host hba_db user_reject 127.0.0.0/24 deny diff --git a/docker/hba/tcp.conf b/docker/hba/tcp.conf new file mode 100644 index 00000000..ecbdee29 --- /dev/null +++ b/docker/hba/tcp.conf @@ -0,0 +1,6 @@ +include "/hba/common.conf" + +listen { + host "*" + port 6432 +} diff --git a/docker/hba/test.sh b/docker/hba/test.sh new file mode 100755 index 00000000..d7726368 --- /dev/null +++ b/docker/hba/test.sh @@ -0,0 +1,91 @@ +#!/bin/bash -x + +set -ex + +# +# TCP +# + +/usr/bin/odyssey /hba/tcp.conf + +PGPASSWORD=correct_password psql -h localhost -p 6432 -U user_allow -c "SELECT 1" hba_db > /dev/null 2>&1 || { + echo "ERROR: failed auth with hba trust, correct password and plain password in config" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +PGPASSWORD=incorrect_password psql -h localhost -p 6432 -U user_allow -c "SELECT 1" hba_db > /dev/null 2>&1 && { + echo "ERROR: successfully auth with hba trust, but incorrect password" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +PGPASSWORD=correct_password psql -h localhost -p 6432 -U user_reject -c "SELECT 1" hba_db > /dev/null 2>&1 && { + echo "ERROR: successfully auth with hba reject" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +PGPASSWORD=correct_password psql -h localhost -p 6432 -U user_unknown -c "SELECT 1" hba_db > /dev/null 2>&1 && { + echo "ERROR: successfully auth without hba rule" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +ody-stop + +# +# Unix +# + +/usr/bin/odyssey /hba/unix.conf + +PGPASSWORD=correct_password psql -h /tmp -p 6432 -U user_allow -c "SELECT 1" hba_db > /dev/null 2>&1 || { + echo "ERROR: failed auth with hba trust, correct password and plain password in config" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +PGPASSWORD=correct_password psql -h /tmp -p 6432 -U user_reject -c "SELECT 1" hba_db > /dev/null 2>&1 && { + echo "ERROR: successfully auth with hba reject" + + cat /var/log/odyssey.log + echo " + + " + cat /var/log/postgresql/postgresql-14-main.log + + exit 1 +} + +ody-stop diff --git a/docker/hba/unix.conf b/docker/hba/unix.conf new file mode 100644 index 00000000..c8566aa5 --- /dev/null +++ b/docker/hba/unix.conf @@ -0,0 +1,5 @@ +include "/hba/common.conf" + +listen { + port 6432 +} diff --git a/documentation/configuration.md b/documentation/configuration.md index 6ea9d492..f066d971 100644 --- a/documentation/configuration.md +++ b/documentation/configuration.md @@ -258,6 +258,21 @@ reply with 'too many connections'. `client_max 100` +#### hba\_file *string* + +Path to file containing host based authentication rules. +Omit this option to disable HBA. + +`hba_file "path"` + +HBA file format follows the format of the PostgreSQL `pg_hba.conf` file. +* Supported record types: `local`, `host`, `hostssl`, `hostnossl`. +* Database field: `all`, `sameuser`, multiple names. +* User field: `all`, multiple names. +* Address field: IPv4 or IPv6 range. +* Auth-method field: `deny` or `reject` (equivalent keywords), which leads to immediate disconnection, +`allow` or `trust` (also equivalent keywords), which means applying auth method specified in matching route. + ### Listen Listen section defines listening servers used for accepting diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt index 81f91a9f..f1c6729e 100644 --- a/sources/CMakeLists.txt +++ b/sources/CMakeLists.txt @@ -46,7 +46,10 @@ set(od_src query.c storage.c murmurhash.c - hashmap.c) + hashmap.c + hba.c + hba_reader.c + hba_rule.c) if (PAM_FOUND) list(APPEND od_src pam.c) diff --git a/sources/config.c b/sources/config.c index a9e7d8ca..b763a18b 100644 --- a/sources/config.c +++ b/sources/config.c @@ -54,6 +54,7 @@ void od_config_init(od_config_t *config) config->cache_coroutine = 0; config->cache_msg_gc_size = 0; config->coroutine_stack_size = 4; + config->hba_file = NULL; od_list_init(&config->listen); } @@ -90,6 +91,8 @@ void od_config_free(od_config_t *config) free(config->log_syslog_facility); if (config->locks_dir) { free(config->locks_dir); + if (config->hba_file) + free(config->hba_file); } } diff --git a/sources/config.h b/sources/config.h index c044ba06..ebb85cdd 100644 --- a/sources/config.h +++ b/sources/config.h @@ -73,6 +73,7 @@ struct od_config { int cache_coroutine; int cache_msg_gc_size; int coroutine_stack_size; + char *hba_file; od_list_t listen; }; diff --git a/sources/config_common.h b/sources/config_common.h index aef78cd2..9d7733cb 100644 --- a/sources/config_common.h +++ b/sources/config_common.h @@ -14,6 +14,7 @@ typedef struct { od_rules_t *rules; od_error_t *error; char *config_file; + od_hba_rules_t *hba_rules; char *data; int data_size; } od_config_reader_t; diff --git a/sources/config_reader.c b/sources/config_reader.c index 2eeed930..e1475c3b 100644 --- a/sources/config_reader.c +++ b/sources/config_reader.c @@ -134,6 +134,7 @@ typedef enum { OD_LCATCHUP_TIMEOUT, OD_LCATCHUP_CHECKS, OD_LOPTIONS, + OD_LHBA_FILE, } od_lexeme_t; static od_keyword_t od_config_keywords[] = { @@ -261,6 +262,7 @@ static od_keyword_t od_config_keywords[] = { od_keyword("auth_module", OD_LAUTH_MODULE), od_keyword("password_passthrough", OD_LAUTH_PASSWORD_PASSTHROUGH), od_keyword("load_module", OD_LMODULE), + od_keyword("hba_file", OD_LHBA_FILE), /* ldap */ od_keyword("ldap_endpoint", OD_LLDAP_ENDPOINT), @@ -1630,6 +1632,23 @@ error: return NOT_OK_RESPONSE; } +static int od_config_reader_hba_import(od_config_reader_t *config_reader) +{ + od_config_reader_t reader; + memset(&reader, 0, sizeof(reader)); + reader.config = config_reader->config; + reader.error = config_reader->error; + reader.hba_rules = config_reader->hba_rules; + int rc; + rc = od_config_reader_open(&reader, config_reader->config->hba_file); + if (rc == -1) + return -1; + rc = od_hba_reader_parse(&reader); + od_config_reader_close(&reader); + + return rc; +} + static int od_config_reader_parse(od_config_reader_t *reader, od_extention_t *extentions) { @@ -1665,7 +1684,8 @@ static int od_config_reader_parse(od_config_reader_t *reader, return NOT_OK_RESPONSE; rc = od_config_reader_import( reader->config, reader->rules, reader->error, - extentions, reader->global, config_file); + extentions, reader->global, reader->hba_rules, + config_file); free(config_file); if (rc == -1) { goto error; @@ -2035,6 +2055,15 @@ static int od_config_reader_parse(od_config_reader_t *reader, } continue; } + case OD_LHBA_FILE: { + rc = od_config_reader_string(reader, &config->hba_file); + if (rc == -1) + goto error; + rc = od_config_reader_hba_import(reader); + if (rc == -1) + goto error; + continue; + } default: od_config_reader_error(reader, &token, "unexpected parameter"); @@ -2055,13 +2084,15 @@ success: int od_config_reader_import(od_config_t *config, od_rules_t *rules, od_error_t *error, od_extention_t *extentions, - od_global_t *global, char *config_file) + od_global_t *global, od_hba_rules_t *hba_rules, + char *config_file) { od_config_reader_t reader; memset(&reader, 0, sizeof(reader)); reader.error = error; reader.config = config; reader.rules = rules; + reader.hba_rules = hba_rules; reader.global = global; int rc; rc = od_config_reader_open(&reader, config_file); diff --git a/sources/config_reader.h b/sources/config_reader.h index 37759827..905d7a60 100644 --- a/sources/config_reader.h +++ b/sources/config_reader.h @@ -8,7 +8,8 @@ */ extern int od_config_reader_import(od_config_t *, od_rules_t *, od_error_t *, - od_extention_t *, od_global_t *, char *); + od_extention_t *, od_global_t *, + od_hba_rules_t *, char *); #define OD_READER_ERROR_MAX_LEN 1 << 8 diff --git a/sources/frontend.c b/sources/frontend.c index 80d14e3d..4264bc47 100644 --- a/sources/frontend.c +++ b/sources/frontend.c @@ -2125,8 +2125,16 @@ void od_frontend(void *arg) } } + /* HBA check */ + rc = od_hba_process(client); + /* client authentication */ - rc = od_auth_frontend(client); + if (rc == OK_RESPONSE) { + rc = od_auth_frontend(client); + } else { + od_frontend_error(client, KIWI_INVALID_PASSWORD, + "host based authentication rejected"); + } if (rc != OK_RESPONSE) { /* rc == -1 diff --git a/sources/global.h b/sources/global.h index 8585ef89..480cc155 100644 --- a/sources/global.h +++ b/sources/global.h @@ -16,11 +16,13 @@ struct od_global { void *cron; void *worker_pool; void *extentions; + void *hba; }; static inline void od_global_init(od_global_t *global, void *instance, void *system, void *router, void *cron, - void *worker_pool, void *extentions) + void *worker_pool, void *extentions, + void *hba) { global->instance = instance; global->system = system; @@ -28,6 +30,7 @@ static inline void od_global_init(od_global_t *global, void *instance, global->cron = cron; global->worker_pool = worker_pool; global->extentions = extentions; + global->hba = hba; } #endif /* ODYSSEY_GLOBAL_H */ diff --git a/sources/hba.c b/sources/hba.c new file mode 100644 index 00000000..b1fa6df5 --- /dev/null +++ b/sources/hba.c @@ -0,0 +1,164 @@ + +/* + * Odyssey. + * + * Scalable PostgreSQL connection pooler. + */ + +#include + +void od_hba_init(od_hba_t *hba) +{ + pthread_mutexattr_init(&hba->attr); + pthread_rwlockattr_setkind_np( + &hba->attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + pthread_mutex_init(&hba->lock, &hba->attr); + od_hba_rules_init(&hba->rules); +} + +void od_hba_free(od_hba_t *hba) +{ + od_hba_rules_free(&hba->rules); + pthread_mutex_destroy(&hba->lock); + pthread_mutexattr_destroy(&hba->attr); +} + +void od_hba_lock(od_hba_t *hba) +{ + pthread_mutex_lock(&hba->lock); +} + +void od_hba_unlock(od_hba_t *hba) +{ + pthread_mutex_unlock(&hba->lock); +} + +void od_hba_reload(od_hba_t *hba, od_hba_rules_t *rules) +{ + od_hba_lock(hba); + + od_list_init(&hba->rules); + memcpy(&hba->rules, &rules, sizeof(hba->rules)); + + od_hba_unlock(hba); +} + +bool od_hba_validate_addr(od_hba_rule_t *rule, struct sockaddr_storage *sa) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + struct sockaddr_in *rule_addr = (struct sockaddr_in *)&rule->addr; + struct sockaddr_in *rule_mask = (struct sockaddr_in *)&rule->mask; + in_addr_t client_addr = sin->sin_addr.s_addr; + in_addr_t client_net = rule_mask->sin_addr.s_addr & client_addr; + return (client_net ^ rule_addr->sin_addr.s_addr) == 0; +} + +bool od_hba_validate_addr6(od_hba_rule_t *rule, struct sockaddr_storage *sa) +{ + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa; + struct sockaddr_in6 *rule_addr = (struct sockaddr_in6 *)&rule->addr; + struct sockaddr_in6 *rule_mask = (struct sockaddr_in6 *)&rule->mask; + for (int i = 0; i < 16; ++i) { + uint8_t client_net_byte = rule_mask->sin6_addr.s6_addr[i] & + sin->sin6_addr.s6_addr[i]; + if (client_net_byte ^ rule_addr->sin6_addr.s6_addr[i]) { + return false; + } + } + + return true; +} + +bool od_hba_validate_name(char *client_name, od_hba_rule_name_t *name, + char *client_other_name) +{ + if (name->flags & OD_HBA_NAME_ALL) { + return true; + } + + if ((name->flags & OD_HBA_NAME_SAMEUSER) && + strcmp(client_name, client_other_name) == 0) { + return true; + } + + od_list_t *i; + od_hba_rule_name_item_t *item; + od_list_foreach(&name->values, i) + { + item = od_container_of(i, od_hba_rule_name_item_t, link); + if (item->value != NULL && + strcmp(client_name, item->value) == 0) { + return true; + } + } + + return false; +} + +int od_hba_process(od_client_t *client) +{ + od_instance_t *instance = client->global->instance; + od_hba_t *hba = client->global->hba; + od_list_t *i; + od_hba_rule_t *rule; + od_hba_rules_t *rules; + + if (instance->config.hba_file == NULL) { + return OK_RESPONSE; + } + + struct sockaddr_storage sa; + int salen = sizeof(sa); + struct sockaddr *saddr = (struct sockaddr *)&sa; + int rc = machine_getpeername(client->io.io, saddr, &salen); + if (rc == -1) + return -1; + + od_hba_lock(hba); + rules = &hba->rules; + od_hba_unlock(hba); + + od_list_foreach(rules, i) + { + rule = od_container_of(i, od_hba_rule_t, link); + if (sa.ss_family == AF_UNIX) { + if (rule->connection_type != OD_CONFIG_HBA_LOCAL) + continue; + } else if (rule->connection_type == OD_CONFIG_HBA_LOCAL) { + continue; + } else if (rule->connection_type == OD_CONFIG_HBA_HOSTSSL && + !client->startup.is_ssl_request) { + continue; + } else if (rule->connection_type == OD_CONFIG_HBA_HOSTNOSSL && + client->startup.is_ssl_request) { + continue; + } else if (sa.ss_family == AF_INET) { + if (rule->addr.ss_family != AF_INET || + !od_hba_validate_addr(rule, &sa)) { + continue; + } + } else if (sa.ss_family == AF_INET6) { + if (rule->addr.ss_family != AF_INET6 || + !od_hba_validate_addr6(rule, &sa)) { + continue; + } + } + + if (!od_hba_validate_name(client->rule->db_name, + &rule->database, + client->rule->user_name)) { + continue; + } + if (!od_hba_validate_name(client->rule->user_name, &rule->user, + client->rule->db_name)) { + continue; + } + + rc = rule->auth_method == OD_CONFIG_HBA_ALLOW ? OK_RESPONSE : + NOT_OK_RESPONSE; + + return rc; + } + + return NOT_OK_RESPONSE; +} diff --git a/sources/hba.h b/sources/hba.h new file mode 100644 index 00000000..7902c2a7 --- /dev/null +++ b/sources/hba.h @@ -0,0 +1,24 @@ +#ifndef ODYSSEY_HBA_H +#define ODYSSEY_HBA_H + +/* + * Odyssey. + * + * Scalable PostgreSQL connection pooler. + */ + +typedef struct od_hba od_hba_t; + +struct od_hba { + pthread_mutex_t lock; + pthread_mutexattr_t attr; + + od_hba_rules_t rules; +}; + +void od_hba_init(od_hba_t *hba); +void od_hba_free(od_hba_t *hba); +void od_hba_reload(od_hba_t *hba, od_hba_rules_t *rules); +int od_hba_process(od_client_t *client); + +#endif // ODYSSEY_HBA_H diff --git a/sources/hba_reader.c b/sources/hba_reader.c new file mode 100644 index 00000000..26354220 --- /dev/null +++ b/sources/hba_reader.c @@ -0,0 +1,397 @@ + +/* + * Odyssey. + * + * Scalable PostgreSQL connection pooler. + */ + +#include +#include +#include +#include + +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), +}; + +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 = 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) + 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_address(struct sockaddr_storage *dest, + const char *addr) +{ + int rc; + rc = inet_pton(AF_INET, addr, &((struct sockaddr_in *)dest)->sin_addr); + if (rc > 0) { + dest->ss_family = AF_INET; + return 0; + } + if (inet_pton(AF_INET6, addr, + &((struct sockaddr_in6 *)dest)->sin6_addr) > 0) { + dest->ss_family = AF_INET6; + return 0; + } + return -1; +} + +static int od_hba_reader_prefix(od_hba_rule_t *hba, char *prefix) +{ + char *end = NULL; + unsigned long int len = strtoul(prefix, &end, 10); + + if (hba->addr.ss_family == AF_INET) { + if (len > 32) + return -1; + uint32_t mask = 0; + unsigned int i; + struct sockaddr_in *addr = (struct sockaddr_in *)&hba->mask; + for (i = 0; i < len / 8; ++i) { + mask = 0xff | (mask << 8); + } + if (len % 8 != 0) + mask = mask | ((len % 8) << (i * 8)); + addr->sin_addr.s_addr = mask; + return 0; + } else if (hba->addr.ss_family == AF_INET6) { + if (len > 128) + return -1; + unsigned int i; + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&hba->mask; + for (i = 0; i < len / 8; ++i) { + addr->sin6_addr.s6_addr[i] = 0xff; + } + if (len % 8 != 0) + addr->sin6_addr.s6_addr[i] = len & 7; + return 0; + } + + 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; + while (1) { + 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) { + 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_hba_reader_address(&hba->addr, address) == -1) { + od_hba_reader_error(reader, + "invalid IP address"); + goto error; + } + + /* network mask */ + if (mask) { + if (od_hba_reader_prefix(hba, 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_hba_reader_address(&hba->mask, + address) == -1) { + od_hba_reader_error( + reader, "invalid network mask"); + goto error; + } + } + } + + /* 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; +} diff --git a/sources/hba_reader.h b/sources/hba_reader.h new file mode 100644 index 00000000..98b86662 --- /dev/null +++ b/sources/hba_reader.h @@ -0,0 +1,6 @@ +#ifndef ODYSSEY_HBA_READER_H +#define ODYSSEY_HBA_READER_H + +int od_hba_reader_parse(od_config_reader_t *reader); + +#endif // ODYSSEY_HBA_READER_H diff --git a/sources/hba_rule.c b/sources/hba_rule.c new file mode 100644 index 00000000..7b7ffced --- /dev/null +++ b/sources/hba_rule.c @@ -0,0 +1,75 @@ + +/* + * Odyssey. + * + * Scalable PostgreSQL connection pooler. + */ + +#include +#include +#include + +od_hba_rule_name_item_t *od_hba_rule_name_item_add(od_hba_rule_name_t *name) +{ + od_hba_rule_name_item_t *item; + item = (od_hba_rule_name_item_t *)malloc(sizeof(*item)); + if (item == NULL) + return NULL; + memset(item, 0, sizeof(*item)); + od_list_init(&item->link); + od_list_append(&name->values, &item->link); + return item; +} + +od_hba_rule_t *od_hba_rule_create() +{ + od_hba_rule_t *hba; + hba = (od_hba_rule_t *)malloc(sizeof(*hba)); + if (hba == NULL) + return NULL; + memset(hba, 0, sizeof(*hba)); + od_list_init(&hba->database.values); + od_list_init(&hba->user.values); + return hba; +} + +void od_hba_rule_free(od_hba_rule_t *hba) +{ + od_list_t *i, *n; + od_hba_rule_name_item_t *item; + od_list_foreach_safe(&hba->database.values, i, n) + { + item = od_container_of(i, od_hba_rule_name_item_t, link); + free(item->value); + free(item); + } + od_list_foreach_safe(&hba->user.values, i, n) + { + item = od_container_of(i, od_hba_rule_name_item_t, link); + free(item->value); + free(item); + } + free(hba); +} + +void od_hba_rules_init(od_hba_rules_t *rules) +{ + od_list_init(rules); +} + +void od_hba_rules_free(od_hba_rules_t *rules) +{ + od_list_t *i, *n; + od_list_foreach_safe(rules, i, n) + { + od_hba_rule_t *hba; + hba = od_container_of(i, od_hba_rule_t, link); + od_hba_rule_free(hba); + } +} + +void od_hba_rules_add(od_hba_rules_t *rules, od_hba_rule_t *rule) +{ + od_list_init(&rule->link); + od_list_append(rules, &rule->link); +} diff --git a/sources/hba_rule.h b/sources/hba_rule.h new file mode 100644 index 00000000..02900227 --- /dev/null +++ b/sources/hba_rule.h @@ -0,0 +1,59 @@ +#ifndef ODYSSEY_HBA_RULE_H +#define ODYSSEY_HBA_RULE_H + +/* + * Odyssey. + * + * Scalable PostgreSQL connection pooler. + */ + +#define OD_HBA_NAME_ALL 1 +#define OD_HBA_NAME_SAMEUSER 2 + +typedef struct od_hba_rule od_hba_rule_t; + +typedef enum { + OD_CONFIG_HBA_LOCAL, + OD_CONFIG_HBA_HOST, + OD_CONFIG_HBA_HOSTSSL, + OD_CONFIG_HBA_HOSTNOSSL +} od_hba_rule_conn_type_t; + +typedef enum { + OD_CONFIG_HBA_ALLOW, + OD_CONFIG_HBA_DENY, +} od_hba_rule_auth_method_t; + +typedef struct od_hba_rule_name_item od_hba_rule_name_item_t; + +struct od_hba_rule_name_item { + char *value; + od_list_t link; +}; + +typedef struct od_hba_rule_name od_hba_rule_name_t; + +struct od_hba_rule_name { + unsigned int flags; + od_list_t values; +}; + +struct od_hba_rule { + od_hba_rule_conn_type_t connection_type; + od_hba_rule_name_t database; + od_hba_rule_name_t user; + struct sockaddr_storage addr; + struct sockaddr_storage mask; + od_hba_rule_auth_method_t auth_method; + od_list_t link; +}; + +typedef od_list_t od_hba_rules_t; + +od_hba_rule_name_item_t *od_hba_rule_name_item_add(od_hba_rule_name_t *name); +od_hba_rule_t *od_hba_rule_create(); +void od_hba_rule_free(od_hba_rule_t *hba); +void od_hba_rules_init(od_hba_rules_t *rules); +void od_hba_rules_free(od_hba_rules_t *rules); +void od_hba_rules_add(od_hba_rules_t *rules, od_hba_rule_t *rule); +#endif /* ODYSSEY_HBA_RULE_H */ diff --git a/sources/instance.c b/sources/instance.c index 48394f1e..dfa4a5a5 100644 --- a/sources/instance.c +++ b/sources/instance.c @@ -95,21 +95,23 @@ int od_instance_main(od_instance_t *instance, int argc, char **argv) od_worker_pool_t worker_pool; od_extention_t extentions; od_global_t global; + od_hba_t hba; od_system_init(&system); od_router_init(&router, &global); od_cron_init(&cron); od_worker_pool_init(&worker_pool); od_extentions_init(&extentions); + od_hba_init(&hba); od_global_init(&global, instance, &system, &router, &cron, &worker_pool, - &extentions); + &extentions, &hba); /* read config file */ od_error_t error; od_error_init(&error); int rc; rc = od_config_reader_import(&instance->config, &router.rules, &error, - &extentions, &global, + &extentions, &global, &hba.rules, instance->config_file); if (rc == -1) { od_error(&instance->logger, "config", NULL, NULL, "%s", diff --git a/sources/odyssey.h b/sources/odyssey.h index 536b462a..6368e3c1 100644 --- a/sources/odyssey.h +++ b/sources/odyssey.h @@ -54,6 +54,7 @@ #include "sources/storage.h" #include "sources/pool.h" #include "sources/rules.h" +#include "sources/hba_rule.h" #include "sources/config_common.h" @@ -95,11 +96,13 @@ #include "sources/module.h" #include "sources/extention.h" +#include "sources/hba_reader.h" #include "sources/config_reader.h" #include "sources/auth.h" #include "sources/query.h" #include "sources/auth_query.h" +#include "sources/hba.h" #include "sources/od_dlsym.h" #include "sources/daemon.h" diff --git a/sources/system.c b/sources/system.c index bde01dfa..035e5e2e 100644 --- a/sources/system.c +++ b/sources/system.c @@ -349,6 +349,7 @@ void od_system_config_reload(od_system_t *system) od_instance_t *instance = system->global->instance; od_router_t *router = system->global->router; od_extention_t *extentions = system->global->extentions; + od_hba_t *hba = system->global->hba; od_log(&instance->logger, "config", NULL, NULL, "importing changes from '%s'", instance->config_file); @@ -366,9 +367,13 @@ void od_system_config_reload(od_system_t *system) od_rules_t rules; od_rules_init(&rules); + od_hba_rules_t hba_rules; + od_hba_rules_init(&hba_rules); + int rc; rc = od_config_reader_import(&config, &rules, &error, extentions, - system->global, instance->config_file); + system->global, &hba_rules, + instance->config_file); if (rc == -1) { od_error(&instance->logger, "config", NULL, NULL, "%s", error.error); @@ -394,6 +399,7 @@ void od_system_config_reload(od_system_t *system) return; } od_config_reload(&instance->config, &config); + od_hba_reload(hba, &hba_rules); pthread_mutex_unlock(&router->rules.mu); @@ -451,6 +457,7 @@ void od_system_config_reload(od_system_t *system) } od_config_free(&config); + od_hba_rules_free(&hba_rules); if (instance->config.log_config) od_rules_print(&rules, &instance->logger);