From efa043690fa724ff715573d5f9cf64a08b58b9b0 Mon Sep 17 00:00:00 2001 From: Ashutosh Sharma Date: Mon, 4 Mar 2024 14:29:03 +0530 Subject: [PATCH] [#378] Vault Implementation --- AUTHORS | 2 +- doc/CONFIGURATION.md | 1 + doc/VAULT.md | 50 ++ src/CMakeLists.txt | 16 + src/admin.c | 53 +- src/include/configuration.h | 73 ++ src/include/management.h | 23 + src/include/network.h | 20 + src/include/pgagroal.h | 54 +- src/include/security.h | 15 + src/include/uthash.h | 1140 +++++++++++++++++++++++++++++++ src/include/utils.h | 19 + src/libpgagroal/configuration.c | 427 +++++++++++- src/libpgagroal/management.c | 66 ++ src/libpgagroal/network.c | 161 +++++ src/libpgagroal/remote.c | 28 + src/libpgagroal/security.c | 35 + src/main.c | 71 +- src/vault.c | 566 +++++++++++++++ 19 files changed, 2754 insertions(+), 66 deletions(-) create mode 100644 doc/VAULT.md create mode 100644 src/include/uthash.h create mode 100644 src/vault.c diff --git a/AUTHORS b/AUTHORS index a81701ca..0252cc12 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,4 +9,4 @@ Nikita Bugrovsky Lawrence Wu Yongting You <2010youy01@gmail.com> Ashutosh Sharma -Henrique de Carvalho \ No newline at end of file +Henrique de Carvalho diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 72c234b7..4e83e428 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -61,6 +61,7 @@ The available keys and their accepted values are reported in the table below. | log_disconnections | `off` | Bool | No | Log disconnects | | blocking_timeout | 30 | Int | No | The number of seconds the process will be blocking for a connection (disable = 0) | | idle_timeout | 0 | Int | No | The number of seconds a connection is been kept alive (disable = 0) | +| rotate_frontend_password_timeout | 0 | Int | No | The number of seconds after which the passwords of frontend users updated periodically (disable = 0) | | max_connection_age | 0 | Int | No | The maximum number of seconds that a connection will live (disable = 0) | | validation | `off` | String | No | Should connection validation be performed. Valid options: `off`, `foreground` and `background` | | background_interval | 300 | Int | No | The interval between background validation scans in seconds | diff --git a/doc/VAULT.md b/doc/VAULT.md new file mode 100644 index 00000000..4a037a7a --- /dev/null +++ b/doc/VAULT.md @@ -0,0 +1,50 @@ +# pgagroal-vault configuration + +The configuration which is mandatory is loaded from either the path specified by the `-c` flag or `/etc/pgagroal/pgagroal_vault.conf`. + +The configuration of `pgagroal-vault` is split into sections using the `[` and `]` characters. + +The pgagroal-vault section, called `[pgagroal-vault]`, is where you configure the overall properties of the vault's server. + +The other section provide configuration for the management port of pgagroal. For now there can be only one pgagroal management port to connect. +This section don't have any requirements to their naming so you can give them +meaningful names but generally named as `[main]`. + +All properties within a section are in the format `key = value`. + +The characters `#` and `;` can be used for comments. A line is totally ignored if the +very first non-space character is a comment one, but it is possible to put a comment at the end of a line. +The `Bool` data type supports the following values: `on`, `yes`, `1`, `true`, `off`, `no`, `0` and `false`. + +See a more complete [sample](./etc/pgagroal_vault.conf) configuration for running `pgagroal-vault` on `localhost`. + +## [pgagroal-vault] + +This section is mandatory and the pooler will refuse to start if the configuration file does not specify one and only one. Usually this section is place on top of the configuration file, but its position within the file does not really matter. +The available keys and their accepted values are reported in the table below. + +| Property | Default | Unit | Required | Description | +|----------|---------|------|----------|-------------| +| host | | String | Yes | The bind address for pgagroal-vault | +| port | | Int | Yes | The bind port for pgagroal-vault | +| tls | `off` | Bool | No | Enable Transport Layer Security (TLS) | +| tls_cert_file | | String | No | Certificate file for TLS. This file must be owned by either the user running pgagroal-vault or root. | +| tls_key_file | | String | No | Private key file for TLS. This file must be owned by either the user running pgagroal-vault or root. Additionally permissions must be at least `0640` when owned by root or `0600` otherwise. | +| tls_ca_file | | String | No | Certificate Authority (CA) file for TLS. This file must be owned by either the user running pgagroal-vault or root. | + + +## [main] + +The section with a name different from `pgagroal-vault` will be treated as an main section. + +| Property | Default | Unit | Required | Description | +|----------|---------|------|----------|-------------| +| host | | String | Yes | The address of the pgagroal running the management server | +| port | | Int | Yes | The management port of pgagroal | +| user | | String | Yes | The admin user of the pgagroal remote management service | +| tls | `off` | Bool | No | Enable Transport Layer Security (TLS) support (Experimental - no pooling) | +| tls_cert_file | | String | No | Certificate file for TLS. This file must be owned by either the user running pgagroal or root. | +| tls_key_file | | String | No | Private key file for TLS. This file must be owned by either the user running pgagroal or root. Additionally permissions must be at least `0640` when owned by root or `0600` otherwise. | +| tls_ca_file | | String | No | Certificate Authority (CA) file for TLS. This file must be owned by either the user running pgagroal or root. | + +Note: For `pgagroal-vault` to function and connect properly to pgagroal, the remote server for management of the `pgagroal` should be enabled i.e. `management` should be greater than 0. \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fea97ab9..d0cea712 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -321,3 +321,19 @@ target_link_libraries(pgagroal-admin-bin pgagroal) install(TARGETS pgagroal-admin-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) + + +# +# Build pgagroal-vault +# +add_executable(pgagroal-vault-bin vault.c ${RESOURCE_OBJECT}) +if (CMAKE_C_LINK_PIE_SUPPORTED) + set_target_properties(pgagroal-vault-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE TRUE OUTPUT_NAME pgagroal-vault) +else() + set_target_properties(pgagroal-vault-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE FALSE OUTPUT_NAME pgagroal-vault) +endif() +target_link_libraries(pgagroal-vault-bin pgagroal) + +install(TARGETS pgagroal-vault-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) + + diff --git a/src/admin.c b/src/admin.c index 91205fad..0890c749 100644 --- a/src/admin.c +++ b/src/admin.c @@ -45,9 +45,6 @@ #include #include -#define DEFAULT_PASSWORD_LENGTH 64 -#define MIN_PASSWORD_LENGTH 8 - #define ACTION_UNKNOWN 0 #define ACTION_MASTER_KEY 1 #define ACTION_ADD_USER 2 @@ -55,18 +52,11 @@ #define ACTION_REMOVE_USER 4 #define ACTION_LIST_USERS 5 -static char CHARS[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{', ']', '}', '\\', '|', ';', ':', - '\'', '\"', ',', '<', '.', '>', '/', '?'}; - static int master_key(char* password, bool generate_pwd, int pwd_length); static int add_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length); static int update_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length); static int remove_user(char* users_path, char* username); static int list_users(char* users_path); -static char* generate_password(int pwd_length); static void version(void) @@ -359,8 +349,10 @@ master_key(char* password, bool generate_pwd, int pwd_length) } else { - password = generate_password(pwd_length); - do_free = false; + if(pgagroal_generate_password(pwd_length, &password)){ + do_free = false; + goto error; + } } } else @@ -481,7 +473,10 @@ add_user(char* users_path, char* username, char* password, bool generate_pwd, in password: if (generate_pwd) { - password = generate_password(pwd_length); + if(pgagroal_generate_password(pwd_length, &password)){ + do_free = false; + goto error; + } do_verify = false; printf("Password : %s", password); } @@ -655,7 +650,10 @@ update_user(char* users_path, char* username, char* password, bool generate_pwd, password: if (generate_pwd) { - password = generate_password(pwd_length); + if(pgagroal_generate_password(pwd_length, &password)){ + do_free = false; + goto error; + } do_verify = false; printf("Password : %s", password); } @@ -903,30 +901,3 @@ list_users(char* users_path) return 1; } - -static char* -generate_password(int pwd_length) -{ - char* pwd; - size_t s; - time_t t; - - s = pwd_length + 1; - - pwd = calloc(1, s); - if (pwd == NULL) - { - pgagroal_log_fatal("Couldn't allocate memory while generating password"); - return NULL; - } - - srand((unsigned)time(&t)); - - for (int i = 0; i < s; i++) - { - *((char*)(pwd + i)) = CHARS[rand() % sizeof(CHARS)]; - } - *((char*)(pwd + pwd_length)) = '\0'; - - return pwd; -} diff --git a/src/include/configuration.h b/src/include/configuration.h index a1e2b67e..53ea8e72 100644 --- a/src/include/configuration.h +++ b/src/include/configuration.h @@ -40,6 +40,11 @@ extern "C" { * configuration file. */ #define PGAGROAL_MAIN_INI_SECTION "pgagroal" +/* + * The main section that must be present in the `pgagroal_vault.conf` + * configuration file. + */ +#define PGAGROAL_VAULT_INI_SECTION "pgagroal-vault" /* * The following constants are used to clearly identify @@ -70,6 +75,14 @@ extern "C" { int pgagroal_init_configuration(void* shmem); +/** + * Initialize the vault configuration structure + * @param shmem The shared memory segment + * @return 0 upon success, otherwise 1 + */ +int +pgagroal_vault_init_configuration(void* shmem); + /** * Read the configuration from a file * @param shmem The shared memory segment @@ -96,6 +109,30 @@ pgagroal_read_configuration(void* shmem, char* filename, bool emitWarnings); int pgagroal_validate_configuration(void* shmem, bool has_unix_socket, bool has_main_sockets); +/** + * Read the configuration of vault from a file + * @param shmem The shared memory segment + * @param filename The file name + * @param emitWarnings true if unknown parameters have to + * reported on stderr + * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise + * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists + * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many sections + * - a positive value to indicate how many errors (with regard to sections) have been found + * - PGAGROAL_CONFIGURATION_STATUS_KO if the file has generic errors, most notably it lacks + * a [pgagroal-vault] section + */ +int +pgagroal_vault_read_configuration(void* shmem, char* filename, bool emitWarnings); + +/** + * Validate the configuration of vault + * @param shmem The shared memory segment + * @return 0 upon success, otherwise 1 + */ +int +pgagroal_vault_validate_configuration(void* shmem); + /** * Read the HBA configuration from a file * @param shmem The shared memory segment @@ -193,6 +230,19 @@ pgagroal_validate_frontend_users_configuration(void* shmem); int pgagroal_read_admins_configuration(void* shmem, char* filename); +/** + * Read the USERS configuration of vault from a file + * @param shmem The shared memory segment + * @param filename The file name + * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise + * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists + * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many users + * (i.e., more users than the number defined in the limits) + * - PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT to indicate a problem reading the content of the file + */ +int +pgagroal_vault_read_users_configuration(void* shmem, char* filename); + /** * Validate the ADMINS configuration from a file * @param shmem The shared memory segment @@ -323,6 +373,29 @@ pgagroal_apply_main_configuration(struct configuration* config, char* key, char* value); +/** + * Function to apply a single configuration parameter. + * + * This is the backbone function used when parsing the main configuration file + * and is used to set any of the allowed parameters. + * + * @param config the configuration to apply values onto + * @param srv the server to which the configuration parameter refers to, if needed + * @param section the section of the file, main or server + * @param key the parameter name of the configuration + * @param value the value of the configuration + * @return 0 on success + * + * Examples of usage: + * pgagroal_apply_vault_configuration( config, NULL, PGAGROAL_VAULT_INI_SECTION, "log_level", "info" ); + */ +int +pgagroal_apply_vault_configuration(struct vault_configuration* config, + struct vault_server* srv, + char* section, + char* key, + char* value); + /** * Function to set a configuration value. * diff --git a/src/include/management.h b/src/include/management.h index 167473e4..a7f01704 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -62,6 +62,8 @@ extern "C" { #define MANAGEMENT_CONFIG_GET 20 #define MANAGEMENT_CONFIG_SET 21 #define MANAGEMENT_CONFIG_LS 22 +#define MANAGEMENT_GET_PASSWORD 23 + /** * Status for the 'ping' (i.e., is-alive) command @@ -75,6 +77,27 @@ extern "C" { #define COMMAND_OUTPUT_FORMAT_TEXT 'T' #define COMMAND_OUTPUT_FORMAT_JSON 'J' + +/** + * Get the frontend password of a user + * @param ssl The SSL connection + * @param socket The socket descriptor + * @param user The frontend user + * @param password The desired password + * @return 0 upon success, otherwise 1 +*/ +int +pgagroal_management_get_password(SSL* ssl, int socket, char* username, char* password); + +/** + * Write the frontend password of a user to the socket + * @param socket The socket descriptor + * @param password The frontend user + * @return 0 upon success, otherwise 1 +*/ +int +pgagroal_management_write_get_password(int socket, char* password); + /** * Read the management header * @param socket The socket descriptor diff --git a/src/include/network.h b/src/include/network.h index e72ca291..7b6f54ab 100644 --- a/src/include/network.h +++ b/src/include/network.h @@ -57,6 +57,16 @@ pgagroal_bind(const char* hostname, int port, int** fds, int* length); int pgagroal_bind_unix_socket(const char* directory, const char* file, int* fd); +/** + * Bind sockets for a host + * @param hostname The host name + * @param port The port number + * @param fd The resulting descriptor + * @return 0 upon success, otherwise 1 + */ +int +pgagroal_vault_bind(const char* hostname, int port, int** fd); + /** * Remove Unix Domain Socket directory * @param directory The directory @@ -76,6 +86,16 @@ pgagroal_remove_unix_socket(const char* directory, const char* file); int pgagroal_connect(const char* hostname, int port, int* fd); +/** + * Connect vault to pgagroal management port + * @param hostname The host name + * @param port The port number + * @param fd The resulting descriptor + * @return 0 upon success, otherwise 1 + */ +int +pgagroal_vault_connect(const char* hostname, int port, int* fd); + /** * Connect to a Unix Domain Socket * @param directory The directory diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index b76c72cf..487d07f8 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -62,22 +62,28 @@ extern "C" { #define PGAGROAL_DEFAULT_FRONTEND_USERS_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_frontend_users.conf" #define PGAGROAL_DEFAULT_ADMINS_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_admins.conf" #define PGAGROAL_DEFAULT_SUPERUSER_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_superuser.conf" +#define PGAGROAL_DEFAULT_VAULT_CONF_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_vault.conf" +#define PGAGROAL_DEFAULT_VAULT_USERS_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_vault_users.conf" #define MAX_PROCESS_TITLE_LENGTH 256 #define MAX_BUFFER_SIZE 65535 #define DEFAULT_BUFFER_SIZE 65535 #define SECURITY_BUFFER_SIZE 1024 +#define HTTP_BUFFER_SIZE 1024 -#define MAX_USERNAME_LENGTH 128 -#define MAX_DATABASE_LENGTH 256 -#define MAX_TYPE_LENGTH 16 -#define MAX_ADDRESS_LENGTH 64 -#define MAX_PASSWORD_LENGTH 1024 -#define MAX_APPLICATION_NAME 64 -#define MAX_PATH 1024 -#define MISC_LENGTH 128 +#define MAX_USERNAME_LENGTH 128 +#define MAX_DATABASE_LENGTH 256 +#define MAX_TYPE_LENGTH 16 +#define MAX_ADDRESS_LENGTH 64 +#define DEFAULT_PASSWORD_LENGTH 64 +#define MIN_PASSWORD_LENGTH 8 +#define MAX_PASSWORD_LENGTH 1024 +#define MAX_APPLICATION_NAME 64 + +#define MAX_PATH 1024 +#define MISC_LENGTH 128 #define NUMBER_OF_SERVERS 64 #ifdef DEBUG #define MAX_NUMBER_OF_CONNECTIONS 8 @@ -326,6 +332,23 @@ struct user char password[MAX_PASSWORD_LENGTH]; /**< The password */ } __attribute__ ((aligned (64))); +/** @struct + * Defines a vault server + */ +struct vault_server +{ + char name[MISC_LENGTH]; /**< The name of section */ + char host[MISC_LENGTH]; /**< The host */ + int port; /**< The port */ + char user[MAX_USERNAME_LENGTH]; /**< The user */ + char password[MAX_PASSWORD_LENGTH]; /**< The password */ + bool tls; /**< Is TLS enabled */ + char tls_cert_file[MISC_LENGTH]; /**< TLS certificate path */ + char tls_key_file[MISC_LENGTH]; /**< TLS key path */ + char tls_ca_file[MISC_LENGTH]; /**< TLS CA certificate path */ + int lineno; +} __attribute__ ((aligned (64))); + /** @struct * Defines the Prometheus connection metric */ @@ -403,6 +426,20 @@ struct prometheus } __attribute__ ((aligned (64))); + +/** @struct + * Defines the configuration of pgagroal-vault +*/ +struct vault_configuration +{ + char configuration_path[MAX_PATH];/**< The configuration path */ + char host[MISC_LENGTH]; /**< The host */ + int port; /**< The port */ + char users_path[MAX_PATH]; /**< The configuration path */ + int number_of_users; /**< The number of users */ + struct vault_server vault_server; /**< The vault servers */ +} __attribute__ ((aligned (64))); + /** @struct * Defines the configuration and state of pgagroal */ @@ -457,6 +494,7 @@ struct configuration int blocking_timeout; /**< The blocking timeout in seconds */ int idle_timeout; /**< The idle timeout in seconds */ + int rotate_frontend_password_timeout; /**< The rotation frontend password timeout in seconds */ int max_connection_age; /**< The max connection age in seconds */ int validation; /**< Validation mode */ int background_interval; /**< Background validation timer in seconds */ diff --git a/src/include/security.h b/src/include/security.h index e3c8c41c..d2b9dd08 100644 --- a/src/include/security.h +++ b/src/include/security.h @@ -138,6 +138,21 @@ pgagroal_user_known(char* user); int pgagroal_tls_valid(void); +/** + * @brief Generate a random ASCII password have size of pwd_length + * @param password the resultant password + * @param password_length length of the password + * @return Generated password + */ +int +pgagroal_generate_password(int password_length, char** password); + +/** + * @brief Initialize RNG + * + */ +void +pgagroal_initialize_random(void); #ifdef __cplusplus } #endif diff --git a/src/include/uthash.h b/src/include/uthash.h new file mode 100644 index 00000000..070f7b44 --- /dev/null +++ b/src/include/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ \ No newline at end of file diff --git a/src/include/utils.h b/src/include/utils.h index adbdd7f9..896249be 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -47,6 +47,25 @@ struct signal_info int slot; /**< The slot */ }; +/** @struct + * Defines the accept io structure +*/ +struct accept_io +{ + struct ev_io io; + int socket; + char** argv; +}; + +/** @struct + * Defines the client structure +*/ +struct client +{ + pid_t pid; + struct client* next; +}; + /** * Get the request identifier * @param msg The message diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 2cdc64e7..41a8d1b0 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -129,9 +129,9 @@ pgagroal_init_configuration(void* shm) config->gracefully = false; config->pipeline = PIPELINE_AUTO; config->authquery = false; - config->blocking_timeout = 30; config->idle_timeout = 0; + config->rotate_frontend_password_timeout = 0; config->max_connection_age = 0; config->validation = VALIDATION_OFF; config->background_interval = 300; @@ -646,6 +646,11 @@ pgagroal_validate_configuration(void* shm, bool has_unix_socket, bool has_main_s pgagroal_log_warn("pgagroal: Using idle_timeout for the transaction pipeline is not recommended"); } + if (config->rotate_frontend_password_timeout > 0) + { + pgagroal_log_warn("pgagroal: Using rotate_frontend_password_timeout for the transaction pipeline is not recommended"); + } + if (config->max_connection_age > 0) { pgagroal_log_warn("pgagroal: Using max_connection_age for the transaction pipeline is not recommended"); @@ -689,6 +694,228 @@ pgagroal_validate_configuration(void* shm, bool has_unix_socket, bool has_main_s return 0; } +/** + * +*/ +int +pgagroal_vault_init_configuration(void* shm) +{ + struct vault_configuration* config; + + config = (struct vault_configuration*)shm; + + config->port = 0; + + config->vault_server.port = 0; + config->vault_server.tls = false; + config->number_of_users = 0; + + memset(config->vault_server.password, 0, MAX_PASSWORD_LENGTH); + + return 0; +} + +/** + * +*/ +int +pgagroal_vault_read_configuration(void* shm, char* filename, bool emitWarnings) +{ + FILE* file; + char section[LINE_LENGTH]; + char line[LINE_LENGTH]; + char* key = NULL; + char* value = NULL; + struct vault_configuration* config; + int idx_server = 0; + struct vault_server srv; + bool has_vault_section = false; + + // the max number of sections allowed in the configuration + // file is done by the max number of servers plus the main `pgagroal` + // configuration section + struct config_section sections[1 + 1]; + int idx_sections = 0; + int lineno = 0; + int return_value = 0; + + file = fopen(filename, "r"); + + if (!file) + { + return PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; + } + + memset(§ion, 0, LINE_LENGTH); + memset(§ions, 0, sizeof(struct config_section) * 2); + config = (struct vault_configuration*)shm; + + while (fgets(line, sizeof(line), file)) + { + lineno++; + if (!is_empty_string(line) && !is_comment_line(line)) + { + if (section_line(line, section)) + { + // check we don't overflow the number of available sections + if (idx_sections >= 2) + { + warnx("Max number of sections (%d) in configuration file <%s> reached!", + 2, + filename); + return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; + } + + // initialize the section structure + memset(sections[idx_sections].name, 0, LINE_LENGTH); + memcpy(sections[idx_sections].name, section, strlen(section)); + sections[idx_sections].lineno = lineno; + sections[idx_sections].main = !strncmp(section, PGAGROAL_VAULT_INI_SECTION, LINE_LENGTH); + if (sections[idx_sections].main) + { + has_vault_section = true; + } + + idx_sections++; + + if (strcmp(section, PGAGROAL_VAULT_INI_SECTION)) + { + if (idx_server > 0 && idx_server <= 2) + { + memcpy(&(config->vault_server), &srv, sizeof(struct vault_server)); + } + else if (idx_server > 1) + { + printf("Maximum number of servers exceeded\n"); + } + + memset(&srv, 0, sizeof(struct vault_server)); + memcpy(&srv.name, §ion, strlen(section)); + srv.lineno = lineno; + idx_server++; + } + } + else + { + extract_key_value(line, &key, &value); + + if (key && value) + { + bool unknown = false; + + //printf("\nSection <%s> key <%s> = <%s>", section, key, value); + + // apply the configuration setting + if (pgagroal_apply_vault_configuration(config, &srv, section, key, value)) + { + unknown = true; + } + + if (unknown && emitWarnings) + { + // we cannot use logging here... + // if we have a section, the key is not known, + // otherwise it is outside of a section at all + if (strlen(section) > 0) + { + warnx("Unknown key <%s> with value <%s> in section [%s] (line %d of file <%s>)", + key, + value, + section, + lineno, + filename); + } + else + { + warnx("Key <%s> with value <%s> out of any section (line %d of file <%s>)", + key, + value, + lineno, + filename); + } + } + + free(key); + free(value); + key = NULL; + value = NULL; + } + } + } + } + + if (strlen(srv.name) > 0) + { + memcpy(&(config->vault_server), &srv, sizeof(struct vault_server)); + } + + + fclose(file); + + // check there is at least one main section + if (!has_vault_section) + { + warnx("No vault configuration section [%s] found in file <%s>", + PGAGROAL_VAULT_INI_SECTION, + filename); + return PGAGROAL_CONFIGURATION_STATUS_KO; + } + + // validate the sections: + // do a nested loop to scan over all the sections that have a duplicated + // name and warn the user about them. + return return_value; +} + +/** + * +*/ +int +pgagroal_vault_validate_configuration (void* shm) +{ + struct vault_configuration* config; + config = (struct vault_configuration*)shm; + + if (strlen(config->host) == 0) { + printf("pgagroal-vault: No host defined\n"); + return 1; + } + + if (config->port <= 0) { + printf("pgagroal-vault: No port defined\n"); + return 1; + } + + if (strlen(config->vault_server.host) == 0) + { + printf("pgagroal-vault: No host defined for server [%s] (%s:%d)\n", + config->vault_server.name, + config->configuration_path, + config->vault_server.lineno); + return 1; + } + + if (config->vault_server.port == 0) + { + printf("pgagroal-vault: No port defined for server [%s] (%s:%d)\n", + config->vault_server.name, + config->configuration_path, + config->vault_server.lineno); + return 1; + } + + if (strlen(config->vault_server.user) == 0) + { + printf("pgagroal-vault: No user defined for server [%s] (%s:%d)\n", + config->vault_server.name, + config->configuration_path, + config->vault_server.lineno); + return 1; + } + + return 0; +} + /** * */ @@ -1387,6 +1614,92 @@ pgagroal_read_admins_configuration(void* shm, char* filename) return status; } +/** + * + */ +int +pgagroal_vault_read_users_configuration(void* shm, char* filename) +{ + FILE* file; + char line[LINE_LENGTH]; + int index; + char* username = NULL; + char* password = NULL; + char* ptr = NULL; + struct vault_configuration* config; + int status = PGAGROAL_CONFIGURATION_STATUS_OK; + + file = fopen(filename, "r"); + + if (!file) + { + status = PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; + goto error; + } + + index = 0; + config = (struct vault_configuration*)shm; + + while (fgets(line, sizeof(line), file)) + { + if (!is_empty_string(line) && !is_comment_line(line)) + { + ptr = strtok(line, ":"); + + username = ptr; + + ptr = strtok(NULL, ":"); + + password = ptr; + + if (ptr == NULL) + { + status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; + goto error; + } + + if (strlen(username) < MAX_USERNAME_LENGTH && + strlen(password) < MAX_PASSWORD_LENGTH) + { + if(!strcmp(config->vault_server.user, username)) + { + memcpy(config->vault_server.password, password, strlen(password)); + break; + } + } + else + { + printf("pgagroal-vault: Invalid USER entry\n"); + printf("%s\n", line); + } + + password = NULL; + index++; + } + } + + config->number_of_users = index; + + if (config->number_of_users > NUMBER_OF_ADMINS) + { + status = PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; + goto error; + } + + fclose(file); + + return PGAGROAL_CONFIGURATION_STATUS_OK; + +error: + + if (file) + { + fclose(file); + } + + return status; +} + /** * */ @@ -2268,6 +2581,7 @@ transfer_configuration(struct configuration* config, struct configuration* reloa config->blocking_timeout = reload->blocking_timeout; config->idle_timeout = reload->idle_timeout; + config->rotate_frontend_password_timeout = reload->rotate_frontend_password_timeout; config->max_connection_age = reload->max_connection_age; config->validation = reload->validation; config->background_interval = reload->background_interval; @@ -2660,7 +2974,7 @@ key_in_section(char* wanted, char* section, char* key, bool global, bool* unknow // if here there is a match on the key, ensure the section is // appropriate - if (global && !strncmp(section, PGAGROAL_MAIN_INI_SECTION, MISC_LENGTH)) + if (global && (!strncmp(section, PGAGROAL_MAIN_INI_SECTION, MISC_LENGTH) | !strncmp(section, PGAGROAL_VAULT_INI_SECTION, MISC_LENGTH))) { return true; } @@ -3197,6 +3511,10 @@ pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) { return to_int(buffer, config->idle_timeout); } + else if (!strncmp(key, "rotate_frontend_password_timeout", MISC_LENGTH)) + { + return to_int(buffer, config->rotate_frontend_password_timeout); + } else if (!strncmp(key, "max_connection_age", MISC_LENGTH)) { return to_int(buffer, config->max_connection_age); @@ -4052,6 +4370,13 @@ pgagroal_apply_main_configuration(struct configuration* config, unknown = true; } } + else if (key_in_section("rotate_frontend_password_timeout", section, key, true, &unknown)) + { + if (as_int(value, &config->rotate_frontend_password_timeout)) + { + unknown = true; + } + } else if (key_in_section("max_connection_age", section, key, true, &unknown)) { if (as_int(value, &config->max_connection_age)) @@ -4279,6 +4604,104 @@ pgagroal_apply_main_configuration(struct configuration* config, } } + +int +pgagroal_apply_vault_configuration(struct vault_configuration* config, + struct vault_server* srv, + char* section, + char* key, + char* value) +{ + size_t max = 0; + bool unknown = false; + + // pgagroal_log_trace( "Configuration setting [%s] <%s> -> <%s>", section, key, value ); + + if (key_in_section("host", section, key, true, NULL)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(config->host, value, max); + } + else if (key_in_section("host", section, key, false, &unknown)) + { + max = strlen(section); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->name, section, max); + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->host, value, max); + } + else if (key_in_section("port", section, key, true, NULL)) + { + if (as_int(value, &config->port)) + { + unknown = true; + } + } + else if (key_in_section("port", section, key, false, &unknown)) + { + memcpy(&srv->name, section, strlen(section)); + if (as_int(value, &srv->port)) + { + unknown = true; + } + } + else if (key_in_section("user", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->user, value, max); + } + else if (key_in_section("tls", section, key, false, &unknown)) + { + if (as_bool(value, &srv->tls)) + { + unknown = true; + } + } + else if (key_in_section("tls_ca_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->tls_ca_file, value, max); + } + else if (key_in_section("tls_cert_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->tls_cert_file, value, max); + } + else if (key_in_section("tls_key_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(&srv->tls_key_file, value, max); + } + return 0; +} + int pgagroal_apply_configuration(char* config_key, char* config_value) { diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 62615bd9..3002d2bb 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -186,6 +186,7 @@ pgagroal_management_read_payload(int socket, signed char id, int* payload_i, cha case MANAGEMENT_ENABLEDB: case MANAGEMENT_DISABLEDB: case MANAGEMENT_CONFIG_GET: + case MANAGEMENT_GET_PASSWORD: case MANAGEMENT_CONFIG_SET: if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) @@ -466,6 +467,50 @@ pgagroal_management_enabledb(SSL* ssl, int fd, char* database) return 1; } +int +pgagroal_management_get_password(SSL* ssl, int fd, char* username, char* pass) +{ + char buf[4]; + char password[MIN_PASSWORD_LENGTH]; + + if(write_header(ssl, fd, MANAGEMENT_GET_PASSWORD, -1)) + { + printf("pgagroal_management_get_password: write: %d\n", fd); + errno = 0; + goto error; + } + + pgagroal_write_int32(&buf, strlen(username)); + if (write_complete(ssl, fd, &buf, sizeof(buf))) + { + printf("pgagroal_management_get_password: write: %d %s\n", fd, strerror(errno)); + errno = 0; + goto error; + } + + if (write_complete(ssl, fd, username, strlen(username))) + { + printf("pgagroal_management_get_password: write: %d %s\n", fd, strerror(errno)); + errno = 0; + goto error; + } + + if (read_complete(ssl, fd, password, MIN_PASSWORD_LENGTH)) + { + printf("pgagroal_management_get_password: write: %d %s\n", fd, strerror(errno)); + errno = 0; + goto error; + } + + memcpy(pass, password, MIN_PASSWORD_LENGTH); + + return 0; + +error: + + return 1; +} + int pgagroal_management_disabledb(SSL* ssl, int fd, char* database) { @@ -1140,6 +1185,27 @@ pgagroal_management_write_isalive(int socket, bool gracefully) return 1; } +int +pgagroal_management_write_get_password(int socket, char* password) +{ + char buffer[MIN_PASSWORD_LENGTH+1]; + memset(buffer, 0, MIN_PASSWORD_LENGTH+1); + memcpy(buffer, password, MIN_PASSWORD_LENGTH); + + if (write_complete(NULL, socket, buffer, MIN_PASSWORD_LENGTH+1)) + { + pgagroal_log_info("pgagroal_management_write_get_password: write: %d %s\n", socket, strerror(errno)); + errno = 0; + goto error; + } + + return 0; + +error: + + return 1; +} + int pgagroal_management_reset(SSL* ssl, int fd) { diff --git a/src/libpgagroal/network.c b/src/libpgagroal/network.c index 42125a36..28e628db 100644 --- a/src/libpgagroal/network.c +++ b/src/libpgagroal/network.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -200,6 +201,90 @@ pgagroal_bind_unix_socket(const char* directory, const char* file, int* fd) return 1; } +/** + * +*/ +int +pgagroal_vault_bind(const char* hostname, int port, int** fd) +{ + int* result = NULL; + int sockfd; + struct addrinfo hints, * servinfo, * addr; + int yes = 1; + int rv; + char* sport; + + + sport = calloc(1, 5); + if (sport == NULL) + { + printf("Couldn't allocate memory while binding host\n"); + return 1; + } + sprintf(sport, "%d", port); + + /* Find all SOCK_STREAM addresses */ + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + if ((rv = getaddrinfo(hostname, sport, &hints, &servinfo)) != 0) + { + free(sport); + printf("getaddrinfo: %s:%d (%s)\n", hostname, port, gai_strerror(rv)); + return 1; + } + + free(sport); + + result = calloc(1, sizeof(int)); + if (sport == NULL) + { + printf("Couldn't allocate memory while binding host\n"); + return 1; + } + + /* Loop through all the results and bind to the first we can */ + for (addr = servinfo; addr != NULL; addr = addr->ai_next) + { + if ((sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) == -1) + { + printf("vault-server: socket: %s:%d (%s)\n", hostname, port, strerror(errno)); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + printf("vault-server: so_reuseaddr: %d %s\n", sockfd, strerror(errno)); + pgagroal_disconnect(sockfd); + continue; + } + + if (bind(sockfd, addr->ai_addr, addr->ai_addrlen) == -1) + { + pgagroal_disconnect(sockfd); + printf("vault-server: bind: %s:%d (%s)\n", hostname, port, strerror(errno)); + continue; + } + + if (listen(sockfd, 0) == -1) + { + pgagroal_disconnect(sockfd); + printf("vault-server: listen: %s:%d (%s)\n", hostname, port, strerror(errno)); + continue; + } + + *result = sockfd; + break; + } + + *fd = result; + freeaddrinfo(servinfo); + + return 0; +} + /** * */ @@ -348,6 +433,82 @@ pgagroal_connect(const char* hostname, int port, int* fd) return 1; } +/** + * +*/ +int +pgagroal_vault_connect(const char* hostname, int port, int* fd) +{ + struct addrinfo hints = {0}; + struct addrinfo* servinfo = NULL; + struct addrinfo* p = NULL; + int rv; + char sport[5]; + int error = 0; + + memset(&sport, 0, sizeof(sport)); + sprintf(&sport[0], "%d", port); + + /* Connect to server */ + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((rv = getaddrinfo(hostname, &sport[0], &hints, &servinfo)) != 0) + { + pgagroal_log_debug("getaddrinfo: %s", gai_strerror(rv)); + if (servinfo != NULL) + { + freeaddrinfo(servinfo); + } + return 1; + } + + *fd = -1; + + /* Loop through all the results and connect to the first we can */ + for (p = servinfo; *fd == -1 && p != NULL; p = p->ai_next) + { + if ((*fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) + { + error = errno; + errno = 0; + } + + if (*fd != -1) + { + + if (connect(*fd, p->ai_addr, p->ai_addrlen) == -1) + { + error = errno; + pgagroal_disconnect(*fd); + errno = 0; + *fd = -1; + continue; + } + } + } + + if (*fd == -1) + { + goto error; + } + + freeaddrinfo(servinfo); + return 0; + +error: + + printf("pgagroal_vault_connect: %s\n", strerror(error)); + + if (servinfo != NULL) + { + freeaddrinfo(servinfo); + } + + return 1; +} + /** * */ diff --git a/src/libpgagroal/remote.c b/src/libpgagroal/remote.c index 515fd6b2..4d7fed1f 100644 --- a/src/libpgagroal/remote.c +++ b/src/libpgagroal/remote.c @@ -150,6 +150,34 @@ pgagroal_remote_management(int client_fd, char* address) goto done; } + break; + + case MANAGEMENT_GET_PASSWORD: + // Read username size from local + status = pgagroal_read_timeout_message(client_ssl, client_fd, config->authentication_timeout, &msg); + if (status != MESSAGE_STATUS_OK) + { + goto done; + } + + status = pgagroal_write_message(NULL, server_fd, msg); + if (status != MESSAGE_STATUS_OK) + { + goto done; + } + + status = pgagroal_read_timeout_message(NULL, server_fd, config->authentication_timeout, &msg); + if (status != MESSAGE_STATUS_OK) + { + goto done; + } + + status = pgagroal_write_message(client_ssl, client_fd, msg); + if (status != MESSAGE_STATUS_OK) + { + goto done; + } + break; default: pgagroal_log_warn("Unknown management operation: %d", type); diff --git a/src/libpgagroal/security.c b/src/libpgagroal/security.c index 40487747..b96dac14 100644 --- a/src/libpgagroal/security.c +++ b/src/libpgagroal/security.c @@ -5840,3 +5840,38 @@ create_client_tls_connection(int fd, SSL** ssl, char* tls_key_file, char* tls_ce return AUTH_ERROR; } + +void +pgagroal_initialize_random() { + time_t t; + srand((unsigned)time(&t)); +} + +int +pgagroal_generate_password(int pwd_length, char** password) +{ + char* pwd; + + pwd = (char*) malloc((pwd_length + 1) * sizeof(char)); + if (!pwd) + { + pgagroal_log_fatal("Couldn't allocate memory while generating password"); + return 1; + } + + for (int i = 0; i < pwd_length; i++) { + pwd[i] = (char) (32 + rand() % (126 - 32 + 1)); + } + pwd[pwd_length] = '\0'; + + // avoid leading/trailing/consecutive spaces. + if (pwd[0] == ' ') pwd[0] = (char) (33 + rand() % (126 - 33 + 1)); + if (pwd[pwd_length - 1] == ' ') pwd[pwd_length - 1] = (char) (33 + rand() % (126 - 33 + 1)); + for (int i = 2; i < pwd_length - 1; i++) { + if (pwd[i] == ' ' && pwd[i-1] == ' ') { + pwd[i] = (char) (33 + rand() % (126 - 33 + 1)); + } + } + *password = pwd; + return 0; +} diff --git a/src/main.c b/src/main.c index aef6e287..f5610f49 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,7 @@ #include #include #include +#include /* system */ #include @@ -78,6 +79,7 @@ static void graceful_cb(struct ev_loop* loop, ev_signal* w, int revents); static void coredump_cb(struct ev_loop* loop, ev_signal* w, int revents); static void idle_timeout_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void max_connection_age_cb(struct ev_loop* loop, ev_periodic* w, int revents); +static void rotate_frontend_password_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void validation_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void disconnect_client_cb(struct ev_loop* loop, ev_periodic* w, int revents); static bool accept_fatal(int error); @@ -88,19 +90,6 @@ static void create_pidfile_or_exit(void); static void remove_pidfile(void); static void shutdown_ports(void); -struct accept_io -{ - struct ev_io io; - int socket; - char** argv; -}; - -struct client -{ - pid_t pid; - struct client* next; -}; - static volatile int keep_running = 1; static char** argv_ptr; static struct ev_loop* main_loop = NULL; @@ -314,6 +303,7 @@ main(int argc, char** argv) struct ev_periodic max_connection_age; struct ev_periodic validation; struct ev_periodic disconnect_client; + struct ev_periodic rotate_frontend_password; struct rlimit flimit; size_t shmem_size; size_t pipeline_shmem_size = 0; @@ -325,7 +315,6 @@ main(int argc, char** argv) int c; bool conf_file_mandatory; char message[MISC_LENGTH]; // a generic message used for errors - argv_ptr = argv; while (1) @@ -886,6 +875,7 @@ main(int argc, char** argv) create_pidfile_or_exit(); pgagroal_pool_init(); + pgagroal_initialize_random(); pgagroal_set_proc_title(argc, argv, "main", NULL); @@ -1043,6 +1033,13 @@ main(int argc, char** argv) ev_periodic_start (main_loop, &disconnect_client); } + if (config->rotate_frontend_password_timeout > 0) + { + ev_periodic_init (&rotate_frontend_password, rotate_frontend_password_cb, 0., + config->rotate_frontend_password_timeout, 0); + ev_periodic_start (main_loop, &rotate_frontend_password); + } + if (config->metrics > 0) { /* Bind metrics socket */ @@ -1558,6 +1555,24 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) pgagroal_log_debug("pgagroal: Management config-set for key <%s> setting value to <%s>", payload_s, secondary_payload_s); pgagroal_management_write_config_set(client_fd, payload_s, secondary_payload_s); break; + case MANAGEMENT_GET_PASSWORD: + { + // get frontend password + char frontend_password[MIN_PASSWORD_LENGTH + 1]; + memset(frontend_password, 0, MIN_PASSWORD_LENGTH + 1); + for (int i = 0; i < config->number_of_frontend_users; i++) + { + if (!strcmp(&config->frontend_users[i].username[0], payload_s)) + { + memcpy(frontend_password, config->frontend_users[i].password, MIN_PASSWORD_LENGTH); + } + } + + // Send password to the vault + pgagroal_management_write_get_password(client_fd, frontend_password); + pgagroal_disconnect(client_fd); + return; + } default: pgagroal_log_debug("pgagroal: Unknown management id: %d", id); break; @@ -1573,6 +1588,7 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) } } + pgagroal_disconnect(client_fd); pgagroal_prometheus_self_sockets_sub(); @@ -1849,6 +1865,33 @@ disconnect_client_cb(struct ev_loop* loop, ev_periodic* w, int revents) } } +static void +rotate_frontend_password_cb(struct ev_loop* loop, ev_periodic* w, int revents) +{ + char* pwd; + + if (EV_ERROR & revents) + { + pgagroal_log_trace("rotate_frontend_password_cb: got invalid event: %s", strerror(errno)); + return; + } + + struct configuration* config; + + config = (struct configuration*)shmem; + + for (int i = 0; i < config->number_of_frontend_users; i++) + { + if(pgagroal_generate_password(MIN_PASSWORD_LENGTH, &pwd)){ + pgagroal_log_debug("rotate_frontend_password_cb: unable to rotate password"); + return; + } + memcpy(&config->frontend_users[i].password, pwd, strlen(pwd)+1); + pgagroal_log_debug("rotate_frontend_password_cb: current pass for username=%s:%s",config->frontend_users[i].username, config->frontend_users[i].password); + free(pwd); + } +} + static bool accept_fatal(int error) { diff --git a/src/vault.c b/src/vault.c new file mode 100644 index 00000000..fa219ad2 --- /dev/null +++ b/src/vault.c @@ -0,0 +1,566 @@ +#include +#include +#include +#include +#include +#include +#include +#include "uthash.h" +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LINUX +#include +#endif + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_FDS 64 + +struct KV +{ + char *key; + char *value; + UT_hash_handle hh; +}; + +static void +usage(void) +{ + printf("pgagroal %s\n", PGAGROAL_VERSION); + printf(" High-performance connection pool for PostgreSQL\n"); + printf("\n"); + + printf("Usage:\n"); + printf(" pgagroal [ -c CONFIG_FILE ] [ -a HBA_FILE ] [ -d ]\n"); + printf("\n"); + printf("Options:\n"); + printf(" -c, --config CONFIG_FILE Set the path to the pgagroal.conf file\n"); + printf(" Default: %s\n", PGAGROAL_DEFAULT_CONF_FILE); + printf(" -u, --password PASSWORD Set the password for the admin user of management port\n"); + printf(" -?, --help Display help\n"); + printf("\n"); + printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); + printf("Report bugs: %s\n", PGAGROAL_ISSUES); +} + +static void parse_query_string(const char *query_string, struct KV **hash_map); +static void free_hash_map(struct KV **hash_map); +static void route_users(int client_fd, char *username, struct KV *params, char *response, size_t response_size); +static void route_not_found(char *response, size_t response_size); +static int router(int client_fd, SSL *ssl); +static int connect_pgagroal(struct vault_configuration* config, char* username, char* password, SSL* s_ssl, int* client_socket); + +static void parse_query_string(const char *path, struct KV **map) +{ + char *token, *key, *value, *saveptr1, *saveptr2, *query; + query = strchr(path, '?'); + if (query) + { + *query = '\0'; + token = strtok_r(query + 1, "&", &saveptr1); + while (token) + { + key = strtok_r(token, "=", &saveptr2); + value = strtok_r(NULL, "=", &saveptr2); + struct KV *kv = malloc(sizeof(struct KV)); + kv->key = strcpy(malloc(strlen(key)), key); + kv->value = strcpy(malloc(strlen(value)), value); + HASH_ADD_STR(*map, key, kv); + token = strtok_r(NULL, "&", &saveptr1); + } + } +} + +static void free_hash_map(struct KV **hash_map) +{ + struct KV *entry, *tmp; + HASH_ITER(hh, *hash_map, entry, tmp) + { + HASH_DEL(*hash_map, entry); + free((void *)entry->key); + free((void *)entry->value); + free(entry); + } +} + + +static int router(int client_fd, SSL *ssl) +{ + int exit_code = 0; + char buffer[HTTP_BUFFER_SIZE]; + ssize_t bytes_read; + + // Read the request + bytes_read = read(client_fd, buffer, sizeof(buffer) - 1); + buffer[bytes_read] = '\0'; + + char method[8]; + char path[128]; + char contents[HTTP_BUFFER_SIZE]; + char response[HTTP_BUFFER_SIZE]; + char format_string[HTTP_BUFFER_SIZE]; + + sscanf(buffer, "%7s %127s", method, path); + + // Extract the POST data + char *body = strstr(buffer, "\r\n\r\n"); + if (body) + { + strcpy(contents, body + 4); + } + + struct KV *map = NULL; + + // Parse URL parameters for GET requests only + if (strcmp(method, "GET") == 0) + { + parse_query_string(path, &map); + + // Call the appropriate handler function for the URL path + if (strncmp(path, "/users/", 7) == 0 && strcmp(method, "GET") == 0) // Only one '/' + { + // Extract the username from the path + char username[MAX_USERNAME_LENGTH + 1]; // Assuming username is less than 120 characters + snprintf(format_string, sizeof(format_string), "/users/%%%ds", MAX_USERNAME_LENGTH); + sscanf(path, "/users/%128s", username); + // Call the appropriate handler function with the username + route_users(client_fd, username, map, response, sizeof(response)); + } + else + { + route_not_found(response, sizeof(response)); + } + }else + { + route_not_found(response, sizeof(response)); + } + + // Send the response + ssize_t bytes_write = write(client_fd, response, strlen(response)); + + if(bytes_write <= 0) + { + exit_code = 1; + } + + free_hash_map(&map); + return exit_code; +} + +static void route_users(int client_fd, char *username, struct KV *params, char *response, size_t response_size) +{ + struct vault_configuration* config = (struct vault_configuration*)shmem; + int client_pgagroal_fd = -1; + // Connect to pgagroal management port + if(connect_pgagroal(config, config->vault_server.user, config->vault_server.password, NULL, &client_pgagroal_fd)) // Change NULL to ssl + { + // Send Error Response + route_not_found(response, response_size); + return; + } + + char password[MIN_PASSWORD_LENGTH + 1]; + memset(password, 0, MIN_PASSWORD_LENGTH + 1); + + // Call GET_PASSWORD at management port + if (pgagroal_management_get_password(NULL, client_pgagroal_fd, username, password)) + { + // Send Error Response + route_not_found(response, response_size); + return; + } + + if(strlen(password) < MIN_PASSWORD_LENGTH){ // user not found + route_not_found(response, response_size); + } + else + { + snprintf(response, response_size, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "\r\n\r\n" + "Password: %7s\r\n", + password); + } +} + +static void accept_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); +static void shutdown_cb(struct ev_loop* loop, ev_signal* w, int revents); +static bool accept_fatal(int error); + + +static volatile int keep_running = 1; +static char** argv_ptr; +static struct ev_loop* main_loop = NULL; +static struct accept_io io_main; +static int* server_fd = NULL; + +static void route_not_found(char *response, size_t response_size) +{ + snprintf(response, response_size, "HTTP/1.1 404 Not Found\r\n\r\n"); +} + +static void +start_vault_io(void) +{ + int sockfd = *server_fd; + + memset(&io_main, 0, sizeof(struct accept_io)); + ev_io_init((struct ev_io*)&io_main, accept_vault_cb, sockfd, EV_READ); + io_main.socket = sockfd; + io_main.argv = argv_ptr; + ev_io_start(main_loop, (struct ev_io*)&io_main); +} + +static void +shutdown_vault_io(void) +{ + ev_io_stop(main_loop, (struct ev_io*)&io_main); + pgagroal_disconnect(io_main.socket); + errno = 0; +} + +int main(int argc, char **argv) +{ + int ret; + int exit_code = 0; + char* configuration_path = NULL; + char* users_path = NULL; + struct signal_info signal_watcher[1]; // Can add more + int c; + int option_index = 0; + size_t size; + struct vault_configuration* config = NULL; + char message[MISC_LENGTH]; // a generic message used for errors + + while (1) + { + static struct option long_options[] = + { + {"config", required_argument, 0, 'c'}, + {"users", required_argument, 0, 'u'}, + {"help", no_argument, 0, '?'} + }; + + c = getopt_long(argc, argv, "?c:u", + long_options, &option_index); + + if (c == -1) + { + break; + } + + switch (c) + { + case 'c': + configuration_path = optarg; + break; + case 'u': + users_path = optarg; + break; + case '?': + usage(); + exit(1); + break; + default: + break; + } + } + + if (getuid() == 0) + { + errx(1, "pgagroal-vault: Using the root account is not allowed"); + } + + size = sizeof(struct vault_configuration); + if (pgagroal_create_shared_memory(size, HUGEPAGE_OFF, &shmem)) + { + errx(1, "pgagroal-vault: Error creating shared memory"); + } + + memset(message, 0, MISC_LENGTH); + + pgagroal_vault_init_configuration(shmem); + config = (struct vault_configuration*)shmem; + + configuration_path = configuration_path != NULL ? configuration_path : PGAGROAL_DEFAULT_VAULT_CONF_FILE; + if ((ret = pgagroal_vault_read_configuration(shmem, configuration_path, false)) != PGAGROAL_CONFIGURATION_STATUS_OK) + { + // the configuration has some problem, build up a descriptive message + if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) + { + snprintf(message, MISC_LENGTH, "Configuration file not found"); + } + else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) + { + snprintf(message, MISC_LENGTH, "Too many sections"); + } + else if (ret == PGAGROAL_CONFIGURATION_STATUS_KO) + { + snprintf(message, MISC_LENGTH, "Invalid configuration file"); + } + else if (ret > 0) + { + snprintf(message, MISC_LENGTH, "%d problematic or duplicated section%c", + ret, + ret > 1 ? 's' : ' '); + } + + errx(1, "pgagroal-vault: %s (file <%s>)", message, configuration_path); + } + + memcpy(&config->configuration_path[0], configuration_path, MIN(strlen(configuration_path), MAX_PATH - 1)); + + if (pgagroal_vault_validate_configuration(shmem)) + { + errx(1, "pgagroal-vault: Invalid VAULT configuration"); + } + + config = (struct vault_configuration*)shmem; + + // -- Read the USERS file -- +read_users_path: + if (users_path != NULL) + { + memset(message, 0, MISC_LENGTH); + ret = pgagroal_vault_read_users_configuration(shmem, users_path); + if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) + { + + snprintf(message, MISC_LENGTH, "USERS configuration file not found"); + errx(1, "pgagroal-vault: %s (file <%s>)", message, users_path); + } + else if (ret == PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT) + { + errx(1, "pgagroal-vault: Invalid entry in the file"); + } + else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) + { + snprintf(message, MISC_LENGTH, "Too many users defined %d (max %d)", config->number_of_users, NUMBER_OF_ADMINS); + errx(1, "pgagroal-vault: %s (file <%s>)", message, users_path); + } + else if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) + { + memcpy(&config->users_path[0], users_path, MIN(strlen(users_path), MAX_PATH - 1)); + } + } + else + { + // the user did not specify a file on the command line + // so try the default one and allow it to be missing + users_path = PGAGROAL_DEFAULT_VAULT_USERS_FILE; + goto read_users_path; + } + + // -- Bind & Listen at the given hostname and port -- + + if(pgagroal_vault_bind(config->host, config->port, &server_fd)){ + errx(1, "pgagroal-vault: Could not bind to %s:%d", config->host, config->port); + } + + // -- Initialize the watcher and start loop -- + main_loop = ev_default_loop(0); + + if (!main_loop) + { + errx(1, "pgagroal-vault: No loop implementation"); + } + + ev_signal_init((struct ev_signal*)&signal_watcher[0], shutdown_cb, SIGTERM); + + for (int i = 0; i < 1; i++) + { + signal_watcher[i].slot = -1; + ev_signal_start(main_loop, (struct ev_signal*)&signal_watcher[i]); + } + + start_vault_io(); + + printf("pgagroal-vault: Started on %s:%d\n", + config->host, + config->port); + + while (keep_running) + { + ev_loop(main_loop, 0); + } + + // -- Free all memory -- + pgagroal_destroy_shared_memory(shmem, size); + free(server_fd); + + return exit_code; +} + +static int +connect_pgagroal(struct vault_configuration* config, char* username, char* password, SSL* s_ssl, int *client_socket) +{ + if (pgagroal_vault_connect(config->vault_server.host, config->vault_server.port, client_socket)) + { + printf("pgagroal-vault: couldn't connect to %s:%d\n", config->vault_server.host, config->vault_server.port); + pgagroal_disconnect(*client_socket); + return 1; + } + + printf("connect_pgagroal: Authenticating the remote management access to %s:%d\n", config->vault_server.host, config->vault_server.port); + username = config->vault_server.user; + + if (strlen(password) == 0) { + // Comment above and uncomment the below + warnx("pgagroal-vault: Please provide a password for (%s)\n", username); + return 1; + } + + for (int i = 0; i < strlen(password); i++) + { + if ((unsigned char)(*(password + i)) & 0x80) + { + + warnx("pgagroal-vault: Bad credentials for %s\n", username); + return 1; + } + } + + /* Authenticate */ + if (pgagroal_remote_management_scram_sha256(username, password, *client_socket, &s_ssl) != AUTH_SUCCESS) + { + printf("pgagroal-vault: Bad credentials for %s\n", username); + pgagroal_disconnect(*client_socket); + return 1; + } + + return 0; +} + +static void +shutdown_cb(struct ev_loop* loop, ev_signal* w, int revents) +{ + printf("pgagroal-vault: Shutdown requested\n"); + ev_break(loop, EVBREAK_ALL); + keep_running = 0; +} + +static void +accept_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) +{ + struct sockaddr_in6 client_addr; + socklen_t client_addr_length; + int client_fd; + char address[INET6_ADDRSTRLEN]; + pid_t pid; + struct vault_configuration* config; + + if (EV_ERROR & revents) + { + printf("accept_vault_cb: Invalid event: %s\n", strerror(errno)); + errno = 0; + return; + } + + config = (struct vault_configuration*)shmem; + + memset(&address, 0, sizeof(address)); + + client_addr_length = sizeof(client_addr); + client_fd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_addr_length); + + if (client_fd == -1) + { + if (accept_fatal(errno) && keep_running) + { + printf("accept_vault_cb: Restarting listening port due to: %s (%d)\n", strerror(errno), watcher->fd); + + shutdown_vault_io(); + + free(server_fd); + server_fd = NULL; + + if (pgagroal_vault_bind(config->host, config->port, &server_fd)) + { + errx(1, "pgagroal-vault: Could not bind to %s:%d", config->host, config->port); + } + + if (!fork()) + { + shutdown_vault_io(); + } + + start_vault_io(); + printf("Socket: %d\n", *server_fd); + } + else + { + printf("accept: %s (%d)\n", strerror(errno), watcher->fd); + } + errno = 0; + return; + } + pgagroal_get_address((struct sockaddr*)&client_addr, (char*)&address, sizeof(address)); + + printf("accept_vault_cb: client address: %s\n", address); + + pid = fork(); + if (pid == -1) + { + /* No process */ + printf("accept_vault_cb: cannot create process\n"); + } + else if(pid == 0) + { + char* addr = calloc(1, strlen(address) + 1); + if (addr == NULL) + { + printf("accept_vault_cb: cannot allocate memory for client address\n"); + return; + } + memcpy(addr, address, strlen(address)); + + ev_loop_fork(loop); + shutdown_vault_io(); + + if(router(client_fd, NULL)) + { + exit(1); + } + + exit(0); + } + + pgagroal_disconnect(client_fd); +} + +static bool +accept_fatal(int error) +{ + switch (error) + { + case EAGAIN: + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: +#ifdef HAVE_LINUX + case ENONET: +#endif + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + return false; + break; + } + + return true; +} \ No newline at end of file