From 00da1644e1d5afd9b502423bcfddef73869caf6c Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 23 Oct 2023 20:21:18 +0200 Subject: [PATCH] [#378] Vault Implementation --- .github/workflows/ci.yml | 4 + AUTHORS | 3 +- CMakeLists.txt | 12 +- CODE_OF_CONDUCT.md | 2 +- LICENSE | 2 +- README.md | 23 +- cmake/FindcJSON.cmake | 51 ++ contrib/shell_comp/pgagroal_comp.bash | 2 +- contrib/shell_comp/pgagroal_comp.zsh | 2 +- contrib/valgrind/README.md | 16 + contrib/valgrind/pgagroal.supp | 268 ++++++ doc/CLI.md | 226 ++++- doc/CONFIGURATION.md | 3 + doc/GETTING_STARTED.md | 58 +- doc/etc/pgagroal.conf | 3 + pgagroal.spec | 6 +- src/CMakeLists.txt | 8 +- src/admin.c | 2 +- src/cli.c | 188 ++-- src/include/configuration.h | 64 +- src/include/json.h | 175 ++++ src/include/logging.h | 2 +- src/include/management.h | 105 ++- src/include/memory.h | 2 +- src/include/message.h | 2 +- src/include/network.h | 22 +- src/include/pgagroal.h | 55 +- src/include/pipeline.h | 2 +- src/include/pool.h | 2 +- src/include/prometheus.h | 2 +- src/include/remote.h | 2 +- src/include/security.h | 2 +- src/include/server.h | 2 +- src/include/shmem.h | 2 +- src/include/tracker.h | 2 +- src/include/utils.h | 35 +- src/include/worker.h | 2 +- src/libpgagroal/configuration.c | 388 +++++++- src/libpgagroal/json.c | 270 ++++++ src/libpgagroal/logging.c | 2 +- src/libpgagroal/management.c | 1131 ++++++++++++++++++++---- src/libpgagroal/memory.c | 2 +- src/libpgagroal/message.c | 2 +- src/libpgagroal/network.c | 161 +++- src/libpgagroal/pipeline_perf.c | 2 +- src/libpgagroal/pipeline_session.c | 2 +- src/libpgagroal/pipeline_transaction.c | 2 +- src/libpgagroal/pool.c | 2 +- src/libpgagroal/prometheus.c | 4 +- src/libpgagroal/remote.c | 30 +- src/libpgagroal/security.c | 31 +- src/libpgagroal/server.c | 2 +- src/libpgagroal/shmem.c | 2 +- src/libpgagroal/tracker.c | 2 +- src/libpgagroal/utils.c | 35 +- src/libpgagroal/worker.c | 2 +- src/main.c | 236 ++--- src/vault.c | 738 +++++++--------- 58 files changed, 3508 insertions(+), 897 deletions(-) create mode 100644 cmake/FindcJSON.cmake create mode 100644 contrib/valgrind/README.md create mode 100644 contrib/valgrind/pgagroal.supp create mode 100644 src/include/json.h create mode 100644 src/libpgagroal/json.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb1429ec..9cd0fa05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ jobs: run: sudo apt update -y - name: Install libev run: sudo apt install -y libev4 libev-dev + - name: Install cJSON + run: sudo apt install -y libcjson1 libcjson-dev - name: Install systemd run: sudo apt install -y libsystemd-dev - name: Install rst2man @@ -63,6 +65,8 @@ jobs: run: brew install openssl - name: Install libev run: brew install libev + - name: Install cJSON + run: brew install cjson - name: Install rst2man run: brew install docutils - name: Install clang diff --git a/AUTHORS b/AUTHORS index 7a805e7d..3707d5a4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,6 @@ pgagroal was created by the following authors: -Jesper Pedersen +Jesper Pedersen David Fetter Will Leinweber Junduo Dong @@ -8,3 +8,4 @@ Luca Ferrari Nikita Bugrovsky Lawrence Wu Yongting You <2010youy01@gmail.com> +Ashutosh Sharma \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c34ae8..122f0dd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14.0) set(VERSION_MAJOR "1") -set(VERSION_MINOR "6") +set(VERSION_MINOR "7") set(VERSION_PATCH "0") set(VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) @@ -78,6 +78,16 @@ else () message(FATAL_ERROR "rst2man needed") endif() +# search for cJSON library +# +find_package(cJSON) +if (cJSON_FOUND) + message(STATUS "cJSON found version ${CJSON_VERSION}") +else () + message(FATAL_ERROR "cJSON needed") +endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") find_package(Libatomic) if (LIBATOMIC_FOUND) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 33c5d516..5d1b5d23 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -33,7 +33,7 @@ projects. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a maintainer of the project, Jesper Pedersen . +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a maintainer of the project, Jesper Pedersen . ### pgagroal Events Code of Conduct diff --git a/LICENSE b/LICENSE index 2ff07806..31384ba0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (C) 2023 Red Hat +Copyright (C) 2024 The pgagroal community Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index bebe5bb8..070703a4 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,31 @@ See [Architecture](./doc/ARCHITECTURE.md) for the architecture of `pgagroal`. * [OpenSSL](http://www.openssl.org/) * [systemd](https://www.freedesktop.org/wiki/Software/systemd/) * [rst2man](https://docutils.sourceforge.io/) +* [libatomic](https://gcc.gnu.org/wiki/Atomic) +* [cJSON](https://github.com/DaveGamble/cJSON) + +On Rocky Linux (and similar) operating systems, the dependencies +can be installed via `dnf(8)` as follows: + +```sh +dnf install git gcc cmake make \ + libev libev-devel \ + openssl openssl-devel \ + systemd systemd-devel \ + python3-docutils \ + libatomic \ + cjson cjson-devel +``` + +Please note that, on Rocky Linux, in order to install the `python3-docutils` +package (that provides `rst2man` executable), you need to enable the `crb` repository: ```sh -dnf install git gcc cmake make libev libev-devel openssl openssl-devel systemd systemd-devel python3-docutils +dnf config-manager --set-enabled crb ``` -Alternative [clang 8+](https://clang.llvm.org/) can be used. + +Alternatively to GCC, [clang 8+](https://clang.llvm.org/) can be used. ### Release build diff --git a/cmake/FindcJSON.cmake b/cmake/FindcJSON.cmake new file mode 100644 index 00000000..6f30e309 --- /dev/null +++ b/cmake/FindcJSON.cmake @@ -0,0 +1,51 @@ +# FindcJSON.cmake +# Tries to find cJSON libraries on the system +# (e.g., on Rocky Linux: cjson and cjson-devel) +# +# Inspired by +# +# If cJSON is found, sets the following variables: +# - CJSON_INCLUDE_DIRS +# - CJSON_LIBRARIES +# - CJSON_VERSION +# +# In the header file cJSON.h the library version is specified as: +# #define CJSON_VERSION_MAJOR 1 +# #define CJSON_VERSION_MINOR 7 +# #define CJSON_VERSION_PATCH 14 + + +find_path( + CJSON_INCLUDE_DIR + NAMES cjson/cJSON.h + PATH_SUFFIXES include) +find_library( + CJSON_LIBRARY + NAMES cjson + PATH_SUFFIXES lib) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(cJSON REQUIRED_VARS CJSON_INCLUDE_DIR + CJSON_LIBRARY) +if(CJSON_FOUND) + # these variables are needed for the build + set( CJSON_INCLUDE_DIRS "${CJSON_INCLUDE_DIR}" ) + set( CJSON_LIBRARIES "${CJSON_LIBRARY}" ) + + # try to get out the library version from the headers + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_MAJOR REGEX "^#define[ \t]+CJSON_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_MINOR REGEX "^#define[ \t]+CJSON_VERSION_MINOR[ \t]+[0-9]+") + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_PATCH REGEX "^#define[ \t]+CJSON_VERSION_PATCH[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_MAJOR "${CJSON_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_MINOR "${CJSON_VERSION_MINOR}") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_PATCH "${CJSON_VERSION_PATCH}") + set(CJSON_VERSION "${CJSON_VERSION_MAJOR}.${CJSON_VERSION_MINOR}.${CJSON_VERSION_PATCH}") + unset(CJSON_VERSION_MINOR) + unset(CJSON_VERSION_MAJOR) + unset(CJSON_VERSION_PATCH) +endif() + +mark_as_advanced( CJSON_INCLUDE_DIR CJSON_LIBRARY ) diff --git a/contrib/shell_comp/pgagroal_comp.bash b/contrib/shell_comp/pgagroal_comp.bash index cefc72c1..12b025e9 100644 --- a/contrib/shell_comp/pgagroal_comp.bash +++ b/contrib/shell_comp/pgagroal_comp.bash @@ -25,7 +25,7 @@ pgagroal_cli_completions() COMPREPLY+=($(compgen -W "server prometheus" "${COMP_WORDS[2]}")) ;; conf) - COMPREPLY+=($(compgen -W "reload get set" "${COMP_WORDS[2]}")) + COMPREPLY+=($(compgen -W "reload get set ls" "${COMP_WORDS[2]}")) ;; status) COMPREPLY+=($(compgen -W "details" "${COMP_WORDS[2]}")) diff --git a/contrib/shell_comp/pgagroal_comp.zsh b/contrib/shell_comp/pgagroal_comp.zsh index b0555423..5b0ef704 100644 --- a/contrib/shell_comp/pgagroal_comp.zsh +++ b/contrib/shell_comp/pgagroal_comp.zsh @@ -40,7 +40,7 @@ function _pgagroal_cli_conf() { local line _arguments -C \ - "1: :(reload get set)" \ + "1: :(reload get set ls)" \ "*::arg:->args" } diff --git a/contrib/valgrind/README.md b/contrib/valgrind/README.md new file mode 100644 index 00000000..592f4912 --- /dev/null +++ b/contrib/valgrind/README.md @@ -0,0 +1,16 @@ +# Valgrind + +The [Valgrind](https://valgrind.org/) tool suite provides a number of debugging and profiling tools that help you make your programs faster and more correct. The most popular and the default of these tools is called **Memcheck**. It can detect many memory-related errors that can lead to crashes and unpredictable behaviour. + +# Run memory management detection + +``` bash +valgrind --leak-check=full --show-leak-kinds=all --log-file=%p.log --trace-children=yes --track-origins=yes --read-var-info=yes ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf +``` + +# Generate valgrind report with suppressed rules + +``` bash +valgrind --suppressions=../../contrib/valgrind/pgagroal.supp --leak-check=full --show-leak-kinds=all --log-file=%p.log --trace-children=yes --track-origins=yes --read-var-info=yes ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf +``` + diff --git a/contrib/valgrind/pgagroal.supp b/contrib/valgrind/pgagroal.supp new file mode 100644 index 00000000..15582074 --- /dev/null +++ b/contrib/valgrind/pgagroal.supp @@ -0,0 +1,268 @@ +# +# pgagroal rules +# + +{ + pgagroal_bind_host + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:bind_host + fun:pgagroal_bind + fun:main +} + +{ + pgagroal_set_proc_title_calloc + Memcheck:Leak + match-leak-kinds: indirect + fun:calloc + fun:pgagroal_set_proc_title + fun:main +} + +{ + pgagroal_set_proc_title_malloc + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:pgagroal_set_proc_title + fun:main +} + +# +# non-pgagroal rules +# + +{ + dl_open_malloc_inline + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} + +{ + dl_open_calloc + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:UnknownInlinedFun + fun:_dl_check_map_versions + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:UnknownInlinedFun + fun:dlopen@@GLIBC_2.34 +} + +{ + dl_open_calloc_inline + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} + +{ + dl_open_worker_malloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run +} + + +{ + ev_loop_timers_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:feed_reverse + fun:timers_reify + fun:ev_run.cold + fun:ev_loop + fun:main +} + +{ + ev_io_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:fd_change + fun:ev_io_start.cold + fun:evtimerfd_init + fun:ev_periodic_start.cold + fun:main +} + +{ + ev_io_management_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_management + fun:main +} + +{ + ev_io_uds_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_uds + fun:main +} + +{ + ev_io_metrics_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_metrics + fun:main +} + +{ + ev_fd_change_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:fd_change + fun:ev_io_start.cold + fun:main +} + +{ + ev_periodics_start_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_periodic_start.cold + fun:main +} + +{ + ev_loop_evpipe_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:evpipe_init + fun:ev_signal_start.cold + fun:ev_default_loop + fun:main +} + +{ + ev_loop_epoll_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:epoll_init + fun:epoll_init.isra.0 + fun:loop_init + fun:ev_default_loop + fun:main +} + +{ + ev_run_feed_event_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_feed_event.cold + fun:fd_event_nocheck + fun:fd_event + fun:epoll_poll + fun:ev_run + fun:ev_run + fun:ev_loop + fun:main +} + +{ + ev_invoke_pending_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:add_client + fun:accept_main_cb + fun:ev_invoke_pending + fun:ev_run + fun:ev_run + fun:ev_loop + fun:main +} + +{ + ev_periodics_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:feed_reverse + fun:periodics_reify + fun:ev_run + fun:ev_run.cold + fun:ev_loop + fun:main +} \ No newline at end of file diff --git a/doc/CLI.md b/doc/CLI.md index 62f6ec1c..5bf6056f 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -22,6 +22,7 @@ Available options are the following ones: -U, --user USERNAME Set the user name -P, --password PASSWORD Set the password -L, --logfile FILE Set the log file +-F, --format text|json Set the output format -v, --verbose Output text string of result -V, --version Display version information -?, --help Display help @@ -30,6 +31,11 @@ Available options are the following ones: Options can be specified either in short or long form, in any position of the command line. +By default the command output, if any, is reported as text. It is possible to specify JSON as the output format, +and this is the suggested format if there is the need to automtically parse the command output, since the text format +could be subject to changes in future releases. For more information about the JSON output format, +please see the [JSON Output Format](#json-output-format) section. + ## Commands ### flush @@ -184,7 +190,8 @@ Manages the configuration of the running instance. This command requires one subcommand, that can be: - `reload` issue a reload of the configuration, applying at runtime any changes from the configuration files; - `get` provides a configuration parameter value; -- `set` modifies a configuration parameter at runtime. +- `set` modifies a configuration parameter at runtime; +- `ls` prints where the configuration files are located. Command @@ -205,7 +212,7 @@ pgagroal-cli conf set max_connections 25 The details about how to get and set values at run-time are explained in the following. -### conf get +#### conf get Given a configuration setting name, provides the current value for such setting. The configuration setting name must be the same as the one used in the configuration files. @@ -256,7 +263,7 @@ If the parameter name specified is not found or invalid, the program `pgagroal-c -### conf set +#### conf set Allows the setting of a configuration parameter at run-time, if possible. Examples @@ -303,7 +310,20 @@ WARN 1 settings cannot be applied DEBUG pgagroal_management_write_config_set: unable to apply changes to -> <100> ``` +#### conf ls + +The command `conf ls` provides information about the location of the configuration files. +As an example: +``` +Main Configuration file: /etc/pgagroal/pgagroal.conf +HBA file: /etc/pgagroal/pgagroal_hba.conf +Limit file: /etc/pgagroal/pgagroal_databases.conf +Frontend users file: /etc/pgagroal/pgagroal_frontend_users.conf +Admins file: /etc/pgagroal/pgagroal_admins.conf +Superuser file: +Users file: /etc/pgagroal/pgagroal_users.conf +``` ### clear Resets different parts of the pooler. It accepts an operational mode: @@ -366,3 +386,203 @@ pgagroal-cli reset-server 2>/dev/null There is a minimal shell completion support for `pgagroal-cli`. Please refer to the [Install pgagroal](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/01_install.md) tutorial for detailed information about how to enable and use shell completions. + + +## JSON Output Format + +It is possible to obtain the output of a command in a JSON format by specyfing the `-F` (`--format`) option on the command line. +Supported output formats are: +- `text` (the default) +- `json` + +As an example, the following are invocations of commands with different output formats: + +``` +pgagroal-cli status # defaults to text output format + +pgagroal-cli status --format text # same as above +pgagroal-cli status -F text # same as above + +pgagroal-cli status --format json # outputs as JSON text +pgagroal-cli status -F json # same as above +``` + +Whenever a command produces output, the latter can be obtained in a JSON format. +Every command output consists of an object that contains two other objects: +- a `command` object, with all the details about the command and its output; +- an `application` object, with all the details about the executable that launched the command (e.g., `pgagroal-cli`). + +In the following, details about every object are provided: + +### The `application` object + +The `application` object is made by the following attributes: +- `name` a string representing the name of the executable that launched the command; +- `version` a string representing the version of the executable; +- `major`, `minor`, `patch` are integers representing every single part of the version of the application. + +As an example, when `pgagroal-cli` launches a command, the output includes an `application` object like the following: + +``` + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +``` + + +### The `command` object + +The `command` object represents the launched command and contains also the answer from the `pgagroal`. +The object is made by the following attributes: +- `name` a string representing the command launched (e.g., `status`); +- `status` a string that contains either "OK" or an error string if the command failed; +- `error` an interger value used as a flag to indicate if the command was in error or not, where `0` means success and `1` means error; +- `exit-status` an integer that contains zero if the command run succesfully, another value depending on the specific command in case of failure; +- `output` an object that contains the details of the executed command. + +The `output` object is *the variable part* in the JSON command output, that means its effective content depends on the launched command. + +Whenever the command output includes an array of stuff, for example a connection list, such array is wrapped into a `list` JSON array with a sibling named `count` that contains the integer size of the array (number of elements). + + +The following are a few examples of commands that provide output in JSON: + + +``` +pgagroal-cli ping --format json +{ + "command": { + "name": "ping", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "status": 1, + "message": "running" + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} + + + +pgagroal-cli status --format json +{ + "command": { + "name": "status", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "status": { + "message": "Running", + "status": 1 + }, + "connections": { + "active": 0, + "total": 2, + "max": 15 + }, + "databases": { + "disabled": { + "count": 0, + "state": "disabled", + "list": [] + } + } + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` + +As an example, the following is the output of a faulty `conf set` command (note the `status`, `error` and `exist-status` values): + +``` +pgagroal-cli conf set max_connections 1000 --format json +{ + "command": { + "name": "conf set", + "status": "Current and expected values are different", + "error": true, + "exit-status": 2, + "output": { + "key": "max_connections", + "value": "15", + "expected": "1000" + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` + + +The `conf ls` command returns an array named `files` where each entry is made by a couple `description` and `path`, where the former +is the mnemonic name of the configuration file, and the latter is the value of the configuration file used: + +``` +$ pgagroal-cli conf ls --format json +{ + "command": { + "name": "conf ls", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "files": { + "list": [{ + "description": "Main Configuration file", + "path": "/etc/pgagroal/pgagroal.conf" + }, { + "description": "HBA File", + "path": "/etc/pgagroal/pgagroal_hba.conf" + }, { + "description": "Limit file", + "path": "/etc/pgagroal/pgagroal_databases.conf" + }, { + "description": "Frontend users file", + "path": "/etc/pgagroal/pgagroal_frontend_users.conf" + }, { + "description": "Admins file", + "path": "/etc/pgagroal/pgagroal_admins.conf" + }, { + "description": "Superuser file", + "path": "" + }, { + "description": "Users file", + "path": "/etc/pgagroal/pgagroal_users.conf" + }] + } + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 109309f3..72c234b7 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -106,6 +106,9 @@ There can be up to `64` host sections, each with an unique name and different co | port | | Int | Yes | The port of the PostgreSQL instance | | primary | | Bool | No | Identify the instance as primary (hint) | | 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, that if `host` starts with a `/` it represents a path and `pgagroal` will connect using a Unix Domain Socket. diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 4c2321bf..5f695947 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -4,7 +4,7 @@ First of all, make sure that `pgagroal` is installed and in your path by using `pgagroal -?`. You should see ``` -pgagroal 1.6.0 +pgagroal 1.7.0 High-performance connection pool for PostgreSQL Usage: @@ -124,39 +124,57 @@ the `SIGTERM` signal to the process using `kill `. You can see the commands it supports by using `pgagroal-cli -?` which will give ``` -pgagroal-cli 1.6.0 +pgagroal-cli 1.7.0 Command line utility for pgagroal Usage: - pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] + pgagroal-cli [ OPTIONS ] [ COMMAND ] Options: -c, --config CONFIG_FILE Set the path to the pgagroal.conf file + Default: /etc/pgagroal/pgagroal.conf -h, --host HOST Set the host name -p, --port PORT Set the port number -U, --user USERNAME Set the user name -P, --password PASSWORD Set the password -L, --logfile FILE Set the log file + -F, --format text|json Set the output format -v, --verbose Output text string of result -V, --version Display version information -?, --help Display help Commands: - flush-idle Flush idle connections - flush-gracefully Flush all connections gracefully - flush-all Flush all connections. USE WITH CAUTION ! - is-alive Is pgagroal alive - enable Enable a database - disable Disable a database - gracefully Stop pgagroal gracefully - stop Stop pgagroal - cancel-shutdown Cancel the graceful shutdown - status Status of pgagroal - details Detailed status of pgagroal - switch-to Switch to another primary - reload Reload the configuration - reset Reset the Prometheus statistics - reset-server Reset the state of a server + flush [mode] [database] Flush connections according to . + Allowed modes are: + - 'gracefully' (default) to flush all connections gracefully + - 'idle' to flush only idle connections + - 'all' to flush all connections. USE WITH CAUTION! + If no name is specified, applies to all databases. + ping Verifies if pgagroal is up and running + enable [database] Enables the specified databases (or all databases) + disable [database] Disables the specified databases (or all databases) + shutdown [mode] Stops pgagroal pooler. The can be: + - 'gracefully' (default) waits for active connections to quit + - 'immediate' forces connections to close and terminate + - 'cancel' avoid a previously issued 'shutdown gracefully' + status [details] Status of pgagroal, with optional details + switch-to Switches to the specified primary server + conf Manages the configuration (e.g., reloads the configuration + The subcommand can be: + - 'reload' to issue a configuration reload; + - 'get' to obtain information about a runtime configuration value; + conf get + - 'set' to modify a configuration value; + conf set ; + - 'ls' lists the configuration files used. + clear Resets either the Prometheus statistics or the specified server. + can be + - 'server' (default) followed by a server name + - a server name on its own + - 'prometheus' to reset the Prometheus metrics + +pgagroal: +Report bugs: ``` This tool can be used on the machine running `pgagroal` to flush connections. @@ -164,7 +182,7 @@ This tool can be used on the machine running `pgagroal` to flush connections. To flush all idle connections you would use ``` -pgagroal-cli -c pgagroal.conf flush-idle +pgagroal-cli -c pgagroal.conf flush idle ``` To stop pgagroal you would use @@ -193,7 +211,7 @@ registration with `pgagroal`. You can see the commands it supports by using `pgagroal-admin -?` which will give ``` -pgagroal-admin 1.6.0 +pgagroal-admin 1.7.0 Administration utility for pgagroal Usage: diff --git a/doc/etc/pgagroal.conf b/doc/etc/pgagroal.conf index caa84237..61046b4e 100644 --- a/doc/etc/pgagroal.conf +++ b/doc/etc/pgagroal.conf @@ -11,6 +11,9 @@ idle_timeout = 600 validation = off unix_socket_dir = /tmp/ +rotate_frontend_password_timeout = 30 +management = 2347 + [primary] host = localhost port = 5432 diff --git a/pgagroal.spec b/pgagroal.spec index 23861721..d19f98c0 100644 --- a/pgagroal.spec +++ b/pgagroal.spec @@ -1,5 +1,5 @@ Name: pgagroal -Version: 1.6.0 +Version: 1.7.0 Release: 1%{dist} Summary: High-performance connection pool for PostgreSQL License: BSD @@ -7,8 +7,8 @@ URL: https://github.com/agroal/pgagroal Source0: https://github.com/agroal/pgagroal/archive/%{version}.tar.gz BuildRequires: gcc cmake make python3-docutils -BuildRequires: libev libev-devel openssl openssl-devel systemd systemd-devel -Requires: libev openssl systemd +BuildRequires: libev libev-devel openssl openssl-devel systemd systemd-devel libatomic cjson cjson-devel +Requires: libev openssl systemd libatomic cjson %description pgagroal is a high-performance connection pool for PostgreSQL. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8dcbe686..6dd76eeb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${SYSTEMD_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -33,6 +34,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") ${OPENSSL_SSL_LIBRARY} ${SYSTEMD_LIBRARIES} ${LIBATOMIC_LIBRARY} + ${CJSON_LIBRARIES} ) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") @@ -69,6 +71,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -77,6 +80,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} + ${CJSON_LIBRARIES} ) else() @@ -98,6 +102,7 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -106,6 +111,7 @@ else() link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} + ${CJSON_LIBRARIES} ) endif() @@ -326,4 +332,4 @@ else() endif() target_link_libraries(pgagroal-vault-bin pgagroal) -install(TARGETS pgagroal-vault-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) \ No newline at end of file +install(TARGETS pgagroal-vault-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/admin.c b/src/admin.c index e96e8c1d..27187eb2 100644 --- a/src/admin.c +++ b/src/admin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/cli.c b/src/cli.c index 3b747c2e..c9bdc6c6 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -66,6 +66,7 @@ #define ACTION_RELOAD 13 #define ACTION_CONFIG_GET 14 #define ACTION_CONFIG_SET 15 +#define ACTION_CONFIG_LS 16 static int flush(SSL* ssl, int socket, int32_t mode, char* database); static int enabledb(SSL* ssl, int socket, char* database); @@ -73,15 +74,16 @@ static int disabledb(SSL* ssl, int socket, char* database); static int gracefully(SSL* ssl, int socket); static int stop(SSL* ssl, int socket); static int cancel_shutdown(SSL* ssl, int socket); -static int status(SSL* ssl, int socket); -static int details(SSL* ssl, int socket); -static int isalive(SSL* ssl, int socket); +static int status(SSL* ssl, int socket, char output_format); +static int details(SSL* ssl, int socket, char output_format); +static int isalive(SSL* ssl, int socket, char output_format); static int reset(SSL* ssl, int socket); static int reset_server(SSL* ssl, int socket, char* server); static int switch_to(SSL* ssl, int socket, char* server); static int reload(SSL* ssl, int socket); -static int config_get(SSL* ssl, int socket, char* config_key, bool verbose); -static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose); +static int config_ls(SSL* ssl, int socket, char output_format); +static int config_get(SSL* ssl, int socket, char* config_key, bool verbose, char output_format); +static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose, char output_format); static void version(void) @@ -108,21 +110,22 @@ usage(void) printf(" -U, --user USERNAME Set the user name\n"); printf(" -P, --password PASSWORD Set the password\n"); printf(" -L, --logfile FILE Set the log file\n"); + printf(" -F, --format text|json Set the output format\n"); printf(" -v, --verbose Output text string of result\n"); printf(" -V, --version Display version information\n"); printf(" -?, --help Display help\n"); printf("\n"); printf("Commands:\n"); - printf(" flush [mode] [database] Flush connections according to .\n"); + printf(" flush [mode] [database] Flush connections according to [mode].\n"); printf(" Allowed modes are:\n"); printf(" - 'gracefully' (default) to flush all connections gracefully\n"); printf(" - 'idle' to flush only idle connections\n"); printf(" - 'all' to flush all connections. USE WITH CAUTION!\n"); - printf(" If no name is specified, applies to all databases.\n"); + printf(" If no [database] name is specified, applies to all databases.\n"); printf(" ping Verifies if pgagroal is up and running\n"); printf(" enable [database] Enables the specified databases (or all databases)\n"); printf(" disable [database] Disables the specified databases (or all databases)\n"); - printf(" shutdown [mode] Stops pgagroal pooler. The can be:\n"); + printf(" shutdown [mode] Stops pgagroal pooler. The [mode] can be:\n"); printf(" - 'gracefully' (default) waits for active connections to quit\n"); printf(" - 'immediate' forces connections to close and terminate\n"); printf(" - 'cancel' avoid a previously issued 'shutdown gracefully'\n"); @@ -132,9 +135,10 @@ usage(void) printf(" The subcommand can be:\n"); printf(" - 'reload' to issue a configuration reload;\n"); printf(" - 'get' to obtain information about a runtime configuration value;\n"); - printf(" conf get \n."); + printf(" conf get \n"); printf(" - 'set' to modify a configuration value;\n"); - printf(" conf set \n."); + printf(" conf set ;\n"); + printf(" - 'ls' lists the configuration files used.\n"); printf(" clear Resets either the Prometheus statistics or the specified server.\n"); printf(" can be\n"); printf(" - 'server' (default) followed by a server name\n"); @@ -173,6 +177,7 @@ main(int argc, char** argv) long l_port; char* config_key = NULL; /* key for a configuration setting */ char* config_value = NULL; /* value for a configuration setting */ + char output_format = COMMAND_OUTPUT_FORMAT_TEXT; while (1) { @@ -184,12 +189,13 @@ main(int argc, char** argv) {"user", required_argument, 0, 'U'}, {"password", required_argument, 0, 'P'}, {"logfile", required_argument, 0, 'L'}, + {"format", required_argument, 0, 'F' }, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, '?'} }; - c = getopt_long(argc, argv, "vV?c:h:p:U:P:L:", + c = getopt_long(argc, argv, "vV?c:h:p:U:P:L:F:", long_options, &option_index); if (c == -1) @@ -217,6 +223,16 @@ main(int argc, char** argv) case 'L': logfile = optarg; break; + case 'F': + if (!strncmp(optarg, "json", MISC_LENGTH)) + { + output_format = COMMAND_OUTPUT_FORMAT_JSON; + } + else + { + output_format = COMMAND_OUTPUT_FORMAT_TEXT; + } + break; case 'v': verbose = true; break; @@ -431,20 +447,25 @@ main(int argc, char** argv) { action = ACTION_RELOAD; } - pgagroal_log_debug("Command: "); + pgagroal_log_trace("Command: "); } else if (parse_command(argc, argv, optind, "conf", "get", &config_key, NULL, NULL, NULL) || parse_deprecated_command(argc, argv, optind, "config-get", NULL, "conf get", 1, 6)) { action = config_key != NULL && strlen(config_key) > 0 ? ACTION_CONFIG_GET : ACTION_UNKNOWN; - pgagroal_log_debug("Command: [%s]", config_key); + pgagroal_log_trace("Command: [%s]", config_key); } else if (parse_command(argc, argv, optind, "conf", "set", &config_key, NULL, &config_value, NULL) || parse_deprecated_command(argc, argv, optind, "config-set", NULL, "conf set", 1, 6)) { // if there is no configuration key set the action to unknown, so the help screen will be printed action = config_key != NULL && strlen(config_key) > 0 ? ACTION_CONFIG_SET : ACTION_UNKNOWN; - pgagroal_log_debug("Command: [%s] = [%s]", config_key, config_value); + pgagroal_log_trace("Command: [%s] = [%s]", config_key, config_value); + } + else if (parse_command_simple(argc, argv, optind, "conf", "ls")) + { + pgagroal_log_debug("Command: "); + action = ACTION_CONFIG_LS; } if (action != ACTION_UNKNOWN) @@ -572,15 +593,15 @@ main(int argc, char** argv) } else if (action == ACTION_STATUS) { - exit_code = status(s_ssl, socket); + exit_code = status(s_ssl, socket, output_format); } else if (action == ACTION_STATUS_DETAILS) { - exit_code = details(s_ssl, socket); + exit_code = details(s_ssl, socket, output_format); } else if (action == ACTION_ISALIVE) { - exit_code = isalive(s_ssl, socket); + exit_code = isalive(s_ssl, socket, output_format); } else if (action == ACTION_RESET) { @@ -600,11 +621,15 @@ main(int argc, char** argv) } else if (action == ACTION_CONFIG_GET) { - exit_code = config_get(s_ssl, socket, config_key, verbose); + exit_code = config_get(s_ssl, socket, config_key, verbose, output_format); } else if (action == ACTION_CONFIG_SET) { - exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); + exit_code = config_set(s_ssl, socket, config_key, config_value, verbose, output_format); + } + else if (action == ACTION_CONFIG_LS) + { + exit_code = config_ls(s_ssl, socket, output_format); } done: @@ -731,11 +756,11 @@ cancel_shutdown(SSL* ssl, int socket) } static int -status(SSL* ssl, int socket) +status(SSL* ssl, int socket, char output_format) { if (pgagroal_management_status(ssl, socket) == 0) { - return pgagroal_management_read_status(ssl, socket); + return pgagroal_management_read_status(ssl, socket, output_format); } else { @@ -744,14 +769,12 @@ status(SSL* ssl, int socket) } static int -details(SSL* ssl, int socket) +details(SSL* ssl, int socket, char output_format) { if (pgagroal_management_details(ssl, socket) == 0) { - if (pgagroal_management_read_status(ssl, socket) == 0) - { - return pgagroal_management_read_details(ssl, socket); - } + return pgagroal_management_read_details(ssl, socket, output_format); + } // if here, an error occurred @@ -760,18 +783,18 @@ details(SSL* ssl, int socket) } static int -isalive(SSL* ssl, int socket) +isalive(SSL* ssl, int socket, char output_format) { int status = -1; if (pgagroal_management_isalive(ssl, socket) == 0) { - if (pgagroal_management_read_isalive(ssl, socket, &status)) + if (pgagroal_management_read_isalive(ssl, socket, &status, output_format)) { return EXIT_STATUS_CONNECTION_ERROR; } - if (status != 1 && status != 2) + if (status != PING_STATUS_RUNNING && status != PING_STATUS_SHUTDOWN_GRACEFULLY) { return EXIT_STATUS_CONNECTION_ERROR; } @@ -839,12 +862,12 @@ reload(SSL* ssl, int socket) * @param config_key the key of the configuration parameter, that is the name * of the configuration parameter to read. * @param verbose if true the function will print on STDOUT also the config key + * @param output_format the format for the output (e.g., json) * @returns 0 on success, 1 on network failure, 2 on data failure */ static int -config_get(SSL* ssl, int socket, char* config_key, bool verbose) +config_get(SSL* ssl, int socket, char* config_key, bool verbose, char output_format) { - char* buffer = NULL; if (!config_key || strlen(config_key) > MISC_LENGTH) { @@ -855,40 +878,10 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose) { goto error; } - else - { - buffer = calloc(1, MISC_LENGTH); - if (buffer == NULL) - { - goto error; - } - if (pgagroal_management_read_config_get(socket, &buffer)) - { - free(buffer); - goto error; - } - - // an empty response means that the - // requested configuration parameter has not been - // found, so throw an error - if (buffer && strlen(buffer)) - { - if (verbose) - { - printf("%s = %s\n", config_key, buffer); - } - else - { - printf("%s\n", buffer); - } - } - else - { - free(buffer); - return EXIT_STATUS_DATA_ERROR; - } - free(buffer); + if (pgagroal_management_read_config_get(socket, config_key, NULL, verbose, output_format)) + { + goto error; } return EXIT_STATUS_OK; @@ -911,10 +904,10 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose) * @return 0 on success */ static int -config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose) +config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose, char output_format) { - char* buffer = NULL; - int status = EXIT_STATUS_DATA_ERROR; + + int status = EXIT_STATUS_OK; if (!config_key || strlen(config_key) > MISC_LENGTH || !config_value || strlen(config_value) > MISC_LENGTH) @@ -926,47 +919,34 @@ config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verb { goto error; } - else - { - buffer = malloc(MISC_LENGTH); - memset(buffer, 0, MISC_LENGTH); - if (pgagroal_management_read_config_get(socket, &buffer)) - { - free(buffer); - goto error; - } - // if the setting we sent is different from the setting we get - // than the system has not applied, so it is an error - if (strncmp(config_value, buffer, MISC_LENGTH) == 0) - { - status = EXIT_STATUS_OK; - } - else - { - status = EXIT_STATUS_DATA_ERROR; - } + status = pgagroal_management_read_config_get(socket, config_key, config_value, verbose, output_format); - // assume an empty response is ok, - // do not throw an error to indicate no configuration - // setting with such name as been found - if (buffer && strlen(buffer)) - { - if (verbose) - { - printf("%s = %s\n", config_key, buffer); - } - else - { - printf("%s\n", buffer); - } - } + return status; +error: + return EXIT_STATUS_CONNECTION_ERROR; +} + +/** + * Asks the daemon about the configuration file location. + * + * @returns 0 on success + */ +static int +config_ls(SSL* ssl, int socket, char output_format) +{ - free(buffer); - return status; + if (pgagroal_management_conf_ls(ssl, socket)) + { + goto error; } - return status; + if (pgagroal_management_read_conf_ls(ssl, socket, output_format)) + { + goto error; + } + + return EXIT_STATUS_OK; error: return EXIT_STATUS_CONNECTION_ERROR; } diff --git a/src/include/configuration.h b/src/include/configuration.h index 8e0e2ecf..0b51f97d 100644 --- a/src/include/configuration.h +++ b/src/include/configuration.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -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 "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,32 @@ 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] section + */ +int +pgagroal_vault_read_configuration(void* shmem, char* filename, bool emitWarnings); + +/** + * Validate the configuration of vault + * @param shmem The shared memory segment + * @param has_unix_socket Has Unix socket + * @param has_main_sockets Has main sockets + * @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 @@ -323,6 +362,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/json.h b/src/include/json.h new file mode 100644 index 00000000..2ad78590 --- /dev/null +++ b/src/include/json.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * 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 HOLDER 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. + */ + +/* pgagroal */ +#include + +#include + +/** + * JSON related command tags, used to build and retrieve + * a JSON piece of information related to a single command + */ +#define JSON_TAG_COMMAND "command" +#define JSON_TAG_COMMAND_NAME "name" +#define JSON_TAG_COMMAND_STATUS "status" +#define JSON_TAG_COMMAND_ERROR "error" +#define JSON_TAG_COMMAND_OUTPUT "output" +#define JSON_TAG_COMMAND_EXIT_STATUS "exit-status" + +#define JSON_TAG_APPLICATION_NAME "name" +#define JSON_TAG_APPLICATION_VERSION_MAJOR "major" +#define JSON_TAG_APPLICATION_VERSION_MINOR "minor" +#define JSON_TAG_APPLICATION_VERSION_PATCH "patch" +#define JSON_TAG_APPLICATION_VERSION "version" + +#define JSON_TAG_ARRAY_NAME "list" + +/** + * JSON pre-defined values + */ +#define JSON_STRING_SUCCESS "OK" +#define JSON_STRING_ERROR "KO" +#define JSON_BOOL_SUCCESS 0 +#define JSON_BOOL_ERROR 1 + +/** + * Utility method to create a new JSON object that wraps a + * single command. This method should be called to initialize the + * object and then the other specific methods that read the + * answer from pgagroal should populate the object accordingly. + * + * Moreover, an 'application' object is placed to indicate from + * where the command has been launched (i.e., which executable) + * and at which version. + * + * @param command_name the name of the command this object wraps + * an answer for + * @param success true if the command is supposed to be succesfull + * @returns the new JSON object to use and populate + * @param executable_name the name of the executable that is creating this + * response object + */ +cJSON* +pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name); + +/** + * Utility method to "jump" to the output JSON object wrapped into + * a command object. + * + * The "output" object is the one that every single method that reads + * back an answer from pgagroal has to populate in a specific + * way according to the data received from pgagroal. + * + * @param json the command object that wraps the command + * @returns the pointer to the output object of NULL in case of an error + */ +cJSON* +pgagroal_json_extract_command_output_object(cJSON* json); + +/** + * Utility function to set a command JSON object as faulty, that + * means setting the 'error' and 'status' message accordingly. + * + * @param json the whole json object that must include the 'command' + * tag + * @param message the message to use to set the faulty diagnostic + * indication + * + * @param exit status + * + * @returns 0 on success + * + * Example: + * json_set_command_object_faulty( json, strerror( errno ) ); + */ +int +pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status); + +/** + * Utility method to inspect if a JSON object that wraps a command + * is faulty, that means if it has the error flag set to true. + * + * @param json the json object to analyzer + * @returns the value of the error flag in the object, or false if + * the object is not valid + */ +bool +pgagroal_json_is_command_object_faulty(cJSON* json); + +/** + * Utility method to extract the message related to the status + * of the command wrapped in the JSON object. + * + * @param json the JSON object to analyze + * #returns the status message or NULL in case the JSON object is not valid + */ +const char* +pgagroal_json_get_command_object_status(cJSON* json); + +/** + * Utility method to check if a JSON object wraps a specific command name. + * + * @param json the JSON object to analyze + * @param command_name the name to search for + * @returns true if the command name matches, false otherwise and in case + * the JSON object is not valid or the command name is not valid + */ +bool +pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name); + +/** + * Utility method to print out the JSON object + * on standard output. + * + * After the object has been printed, it is destroyed, so + * calling this method will make the pointer invalid + * and the jeon object cannot be used anymore. + * + * This should be the last method to be called + * when there is the need to print out the information + * contained in a json object. + * + * Since the JSON object will be invalidated, the method + * returns the status of the JSON command within it + * to be used. + * + * @param json the json object to print + * @return the command status within the JSON object + */ +int +pgagroal_json_print_and_free_json_object(cJSON* json); + +/** + * Utility function to get the exit status of a given command wrapped in a JSON object. + * + * @param json the json object + * @returns the exit status of the command + */ +int +pgagroal_json_command_object_exit_status(cJSON* json); diff --git a/src/include/logging.h b/src/include/logging.h index ef3d4dd0..f099a6ff 100644 --- a/src/include/logging.h +++ b/src/include/logging.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/management.h b/src/include/management.h index 11b26476..aea78d68 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -61,6 +61,40 @@ extern "C" { #define MANAGEMENT_REMOVE_FD 19 #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 + */ +#define PING_STATUS_RUNNING 1 +#define PING_STATUS_SHUTDOWN_GRACEFULLY 2 + +/** + * Available command output formats + */ +#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 @@ -178,10 +212,11 @@ pgagroal_management_status(SSL* ssl, int socket); /** * Management: Read status * @param socket The socket + * @param output_format a char describing the type of output (text or json) * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_status(SSL* ssl, int socket); +pgagroal_management_read_status(SSL* ssl, int socket, char output_format); /** * Management: Write status @@ -204,10 +239,11 @@ pgagroal_management_details(SSL* ssl, int socket); /** * Management: Read details * @param socket The socket + * @param output_format the output format for this command (text, json) * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_details(SSL* ssl, int socket); +pgagroal_management_read_details(SSL* ssl, int socket, char output_format); /** * Management: Write details @@ -232,7 +268,7 @@ pgagroal_management_isalive(SSL* ssl, int socket); * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_isalive(SSL* ssl, int socket, int* status); +pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format); /** * Management: Write isalive @@ -331,10 +367,14 @@ pgagroal_management_config_get(SSL* ssl, int socket, char* config_key); * @see pgagroal_management_read_payload * * @param ssl the socket file descriptor + * @param config_key the key to read (is used only to print in the output) + * @param verbose verbosity flag + * @param output_format the output format + * @param expected_value if set, a value that the configuration should match * @return 0 on success */ int -pgagroal_management_read_config_get(int socket, char** data); +pgagroal_management_read_config_get(int socket, char* config_key, char* expected_value, bool verbose, char output_format); /** * Management operation: write the result of a config_get action on the socket. @@ -386,6 +426,61 @@ pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* con int pgagroal_management_write_config_set(int socket, char* config_key, char* config_value); +/** + * Entry point for managing the `conf ls` command that + * will list all the configuration files used by the running + * daemon. + * + * @param ssl the SSL handler + * @param fd the socket file descriptor + * @returns 0 on success + */ +int +pgagroal_management_conf_ls(SSL* ssl, int fd); + +/** + * Reads out of the socket the list of configuration + * files and prints them out to the standard output. + * + * The order of the read paths is: + * - configuration path + * - HBA path + * - limit path + * - frontend users path + * - admins path + * - Superusers path + * - users path + * + * @param socket the file descriptor of the open socket + * @param ssl the SSL handler + * @param output_format the format to output the command result + * @returns 0 on success + */ +int +pgagroal_management_read_conf_ls(SSL* ssl, int socket, char output_format); + +/** + * The management function responsible for sending + * the configuration paths into the socket. + * + * The function sends every path following the path length, + * that must be limited to MAX_PATH size. + * + * The order of the sent paths is: + * - configuration path + * - HBA path + * - limit path + * - frontend users path + * - admins path + * - Superusers path + * - users path + * + * @params socket the file descriptor of the open socket + * @returns 0 on success + */ +int +pgagroal_management_write_conf_ls(int socket); + #ifdef __cplusplus } #endif diff --git a/src/include/memory.h b/src/include/memory.h index f8fed883..a4b42ce4 100644 --- a/src/include/memory.h +++ b/src/include/memory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/message.h b/src/include/message.h index 558b55e1..e7b8849e 100644 --- a/src/include/message.h +++ b/src/include/message.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/network.h b/src/include/network.h index c05809fd..7b6f54ab 100644 --- a/src/include/network.h +++ b/src/include/network.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -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 085d694b..95f8f67b 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -62,12 +62,14 @@ 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 MAX_PROCESS_TITLE_LENGTH 256 #define MAX_BUFFER_SIZE 65535 #define DEFAULT_BUFFER_SIZE 65535 -#define SECURITY_BUFFER_SIZE 512 +#define SECURITY_BUFFER_SIZE 1024 +#define HTTP_BUFFER_SIZE 1024 #define MAX_USERNAME_LENGTH 128 #define MAX_DATABASE_LENGTH 256 @@ -105,6 +107,9 @@ extern "C" { #define STATE_VALIDATION 6 #define STATE_REMOVE 7 +#define VAULT_STATE_FREE 0 +#define VAULT_STATE_IN_USE 1 + #define SECURITY_INVALID -2 #define SECURITY_REJECT -1 #define SECURITY_TRUST 0 @@ -254,12 +259,15 @@ extern void* prometheus_cache_shmem; */ struct server { - char name[MISC_LENGTH]; /**< The name of the server */ - char host[MISC_LENGTH]; /**< The host name of the server */ - int port; /**< The port of the server */ - bool tls; /**< Use TLS if possible */ - atomic_schar state; /**< The state of the server */ - int lineno; /**< The line number within the configuration file */ + char name[MISC_LENGTH]; /**< The name of the server */ + char host[MISC_LENGTH]; /**< The host name of the server */ + int port; /**< The port of the server */ + bool tls; /**< Use TLS if possible */ + 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 */ + atomic_schar state; /**< The state of the server */ + int lineno; /**< The line number within the configuration file */ } __attribute__ ((aligned (64))); /** @struct @@ -325,6 +333,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 */ @@ -402,6 +427,18 @@ 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 */ + struct vault_server vault_server; /**< The vault servers */ +} __attribute__ ((aligned (64))); + /** @struct * Defines the configuration and state of pgagroal */ @@ -456,6 +493,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 */ @@ -499,6 +537,7 @@ struct configuration } __attribute__ ((aligned (64))); + #ifdef __cplusplus } #endif diff --git a/src/include/pipeline.h b/src/include/pipeline.h index 6463e31b..8dc0b02a 100644 --- a/src/include/pipeline.h +++ b/src/include/pipeline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/pool.h b/src/include/pool.h index 434e7e9a..2ebd5233 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/prometheus.h b/src/include/prometheus.h index 51ea8b37..af2e61a6 100644 --- a/src/include/prometheus.h +++ b/src/include/prometheus.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/remote.h b/src/include/remote.h index 730f3317..e8b5a378 100644 --- a/src/include/remote.h +++ b/src/include/remote.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/security.h b/src/include/security.h index 4ae8bdb2..84beb2db 100644 --- a/src/include/security.h +++ b/src/include/security.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/server.h b/src/include/server.h index b6068416..bff9d32b 100644 --- a/src/include/server.h +++ b/src/include/server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/shmem.h b/src/include/shmem.h index 92e0128b..d2b9ecc6 100644 --- a/src/include/shmem.h +++ b/src/include/shmem.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/tracker.h b/src/include/tracker.h index c167f4c0..7d1ccee7 100644 --- a/src/include/tracker.h +++ b/src/include/tracker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/utils.h b/src/include/utils.h index 4cb000ed..896249be 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -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 @@ -494,6 +513,20 @@ parse_deprecated_command(int argc, char* deprecated_by, unsigned int deprecated_since_major, unsigned int deprecated_since_minor); + +/** + * Given a server state, it returns a string that + * described the state in a human-readable form. + * + * If the state cannot be determined, the numeric + * form of the state is returned as a string. + * + * @param state the value of the sate for the server + * @returns the string representing the state + */ +char* +pgagroal_server_state_as_string(signed char state); + #ifdef __cplusplus } #endif diff --git a/src/include/worker.h b/src/include/worker.h index 7c1a87a1..4196ab93 100644 --- a/src/include/worker.h +++ b/src/include/worker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index f138eb5a..1f4686d7 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -132,6 +132,7 @@ pgagroal_init_configuration(void* shm) 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 +647,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 +695,227 @@ 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; + + 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; +} + /** * */ @@ -2268,6 +2495,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; @@ -2524,6 +2752,7 @@ restart_server(struct server* src, struct server* dst) restart_string(restart_message, dst->host, src->host, false); snprintf(restart_message, sizeof(restart_message), "Server <%s>, parameter ", src->name); restart_int(restart_message, dst->port, src->port); + /* TODO - TLS */ return 1; } @@ -2659,7 +2888,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; } @@ -3163,7 +3392,6 @@ pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) else if (!strncmp(key, "pipeline", MISC_LENGTH)) { return to_pipeline(buffer, config->pipeline); - } else if (!strncmp(key, "failover_script", MISC_LENGTH)) { @@ -3197,6 +3425,12 @@ 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); @@ -3345,6 +3579,22 @@ pgagroal_write_server_config_value(char* buffer, char* server_name, char* config return to_bool(buffer, primary); } + else if (!strncmp(config_key, "tls", MISC_LENGTH)) + { + return to_bool(buffer, config->servers[server_index].tls); + } + else if (!strncmp(config_key, "tls_cert_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_cert_file, buffer_size); + } + else if (!strncmp(config_key, "tls_key_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_key_file, buffer_size); + } + else if (!strncmp(config_key, "tls_ca_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_ca_file, buffer_size); + } else { goto error; @@ -3976,6 +4226,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_ca_file, value, max); } + 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, true, &unknown)) { max = strlen(value); @@ -3985,6 +4244,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_cert_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, true, &unknown)) { max = strlen(value); @@ -3994,6 +4262,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_key_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); + } else if (key_in_section("blocking_timeout", section, key, true, &unknown)) { @@ -4009,6 +4286,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)) @@ -4236,6 +4520,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/json.c b/src/libpgagroal/json.c new file mode 100644 index 00000000..766bcc51 --- /dev/null +++ b/src/libpgagroal/json.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * 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 HOLDER 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. + */ + +/* pgagroal */ +#include +#include + +cJSON* +pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name) +{ + // root of the JSON structure + cJSON* json = cJSON_CreateObject(); + + if (!json) + { + goto error; + } + + // the command structure + cJSON* command = cJSON_CreateObject(); + if (!command) + { + goto error; + } + + // insert meta-data about the command + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_NAME, command_name); + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_STATUS, success ? JSON_STRING_SUCCESS : JSON_STRING_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_ERROR, success ? JSON_BOOL_SUCCESS : JSON_BOOL_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_EXIT_STATUS, success ? 0 : EXIT_STATUS_DATA_ERROR); + + // the output of the command, this has to be filled by the caller + cJSON* output = cJSON_CreateObject(); + if (!output) + { + goto error; + } + + cJSON_AddItemToObject(command, JSON_TAG_COMMAND_OUTPUT, output); + + // who has launched the command ? + cJSON* application = cJSON_CreateObject(); + if (!application) + { + goto error; + } + + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_NAME, executable_name); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MAJOR, PGAGROAL_MAJOR_VERSION); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MINOR, PGAGROAL_MINOR_VERSION); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_PATCH, PGAGROAL_PATCH_VERSION); + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_VERSION, PGAGROAL_VERSION); + + // add objects to the whole json thing + cJSON_AddItemToObject(json, "command", command); + cJSON_AddItemToObject(json, "application", application); + + return json; + +error: + if (json) + { + cJSON_Delete(json); + } + + return NULL; + +} + +cJSON* +pgagroal_json_extract_command_output_object(cJSON* json) +{ + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + return cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_OUTPUT); + +error: + return NULL; + +} + +bool +pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name) +{ + if (!json || !command_name || strlen(command_name) <= 0) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* cName = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_NAME); + if (!cName || !cJSON_IsString(cName) || !cName->valuestring) + { + goto error; + } + + return !strncmp(command_name, + cName->valuestring, + MISC_LENGTH); + +error: + return false; +} + +int +pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetValuestring(current, message); + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, JSON_BOOL_ERROR); // cannot use cJSON_SetBoolValue unless cJSON >= 1.7.16 + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, exit_status); + + return 0; + +error: + return 1; + +} + +bool +pgagroal_json_is_command_object_faulty(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); + if (!status || !cJSON_IsNumber(status)) + { + goto error; + } + + return status->valueint == JSON_BOOL_SUCCESS ? false : true; + +error: + return false; + +} + +int +pgagroal_json_command_object_exit_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!status || !cJSON_IsNumber(status)) + { + goto error; + } + + return status->valueint; + +error: + return EXIT_STATUS_DATA_ERROR; +} + +const char* +pgagroal_json_get_command_object_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!cJSON_IsString(status) || (status->valuestring == NULL)) + { + goto error; + } + + return status->valuestring; +error: + return NULL; + +} + +int +pgagroal_json_print_and_free_json_object(cJSON* json) +{ + int status = pgagroal_json_command_object_exit_status(json); + printf("%s\n", cJSON_Print(json)); + cJSON_Delete(json); + return status; +} diff --git a/src/libpgagroal/logging.c b/src/libpgagroal/logging.c index 6f0be7be..a1c6301f 100644 --- a/src/libpgagroal/logging.c +++ b/src/libpgagroal/logging.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 1b104149..851925b6 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -35,6 +35,7 @@ #include #include #include +#include /* system */ #include @@ -59,6 +60,17 @@ static int write_socket(int socket, void* buf, size_t size); static int write_ssl(SSL* ssl, void* buf, size_t size); static int write_header(SSL* ssl, int fd, signed char type, int slot); +static int pgagroal_management_write_conf_ls_detail(int socket, char* what); +static int pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer); + +static int pgagroal_management_json_print_status_details(cJSON* json); + +static cJSON* pgagroal_management_json_read_status_details(SSL* ssl, int socket, bool include_details); +static cJSON* pgagroal_managment_json_read_config_get(int socket, char* config_key, char* expected_value); + +static cJSON* pgagroal_management_json_read_conf_ls(SSL* ssl, int socket); +static int pgagroal_management_json_print_conf_ls(cJSON* json); + int pgagroal_management_read_header(int socket, signed char* id, int32_t* slot) { @@ -174,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))) { @@ -213,6 +226,7 @@ pgagroal_management_read_payload(int socket, signed char id, int* payload_i, cha case MANAGEMENT_DETAILS: case MANAGEMENT_RESET: case MANAGEMENT_RELOAD: + case MANAGEMENT_CONFIG_LS: break; default: goto error; @@ -452,6 +466,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) { @@ -555,7 +613,48 @@ pgagroal_management_status(SSL* ssl, int fd) } int -pgagroal_management_read_status(SSL* ssl, int socket) +pgagroal_management_read_status(SSL* ssl, int socket, char output_format) +{ + cJSON* json = pgagroal_management_json_read_status_details(ssl, socket, false); + + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + return pgagroal_json_print_and_free_json_object(json); + } + else + { + return pgagroal_management_json_print_status_details(json); + } + +error: + pgagroal_log_warn("pgagroal_management_read_status: command error [%s]", + (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); + return 1; +} + +/** + * Utility method that reads the answer from pgagroal about + * either the 'status' or the 'status details' command. + * The answer is then wrapped into a JSON object + * that contains all the information needed to be printed out in either + * JSON format or text format. + * + * @param ssl the SSL file descriptor for the socket + * @param socket the socket file descriptor + * @param include_details true if the method has to handle the 'status details' command + * or false if the answer is related only to the 'status' command + * + * @returns the json object, faulty if something goes wrong + */ +static cJSON* +pgagroal_management_json_read_status_details(SSL* ssl, int socket, bool include_details) { char buf[16]; char disabled[NUMBER_OF_DISABLED][MAX_DATABASE_LENGTH]; @@ -563,21 +662,27 @@ pgagroal_management_read_status(SSL* ssl, int socket) int active; int total; int max; + int max_connections = 0; + int limits = 0; + int servers = 0; + char header[12 + MAX_NUMBER_OF_CONNECTIONS]; memset(&buf, 0, sizeof(buf)); memset(&disabled, 0, sizeof(disabled)); + memset(&header, 0, sizeof(header)); + + cJSON* json = pgagroal_json_create_new_command_object(include_details ? "status details" : "status", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); if (read_complete(ssl, socket, &buf[0], sizeof(buf))) { - pgagroal_log_warn("pgagroal_management_read_status: read: %d %s", socket, strerror(errno)); - errno = 0; + pgagroal_log_warn("pgagroal_management_json_read_status_details: read: %d %s", socket, strerror(errno)); goto error; } if (read_complete(ssl, socket, &disabled[0], sizeof(disabled))) { - pgagroal_log_warn("pgagroal_management_read_status: read: %d %s", socket, strerror(errno)); - errno = 0; + pgagroal_log_warn("pgagroal_management_json_read_status_details: read: %d %s", socket, strerror(errno)); goto error; } @@ -586,10 +691,24 @@ pgagroal_management_read_status(SSL* ssl, int socket) total = pgagroal_read_int32(&(buf[8])); max = pgagroal_read_int32(&(buf[12])); - printf("Status: %s\n", (status == 1 ? "Running" : "Graceful shutdown")); - printf("Active connections: %d\n", active); - printf("Total connections: %d\n", total); - printf("Max connections: %d\n", max); + // status information + cJSON* status_json = cJSON_CreateObject(); + cJSON_AddStringToObject(status_json, "message", (status == 1 ? "Running" : "Graceful shutdown")); + cJSON_AddNumberToObject(status_json, "status", status); + cJSON_AddItemToObject(output, "status", status_json); + + // define all the information about connections + cJSON* connections = cJSON_CreateObject(); + cJSON_AddNumberToObject(connections, "active", active); + cJSON_AddNumberToObject(connections, "total", total); + cJSON_AddNumberToObject(connections, "max", max); + cJSON_AddItemToObject(output, "connections", connections); + + // define all the information about disabled databases + cJSON* databases = cJSON_CreateObject(); + cJSON* databases_array = cJSON_CreateArray(); + + int counter = 0; for (int i = 0; i < NUMBER_OF_DISABLED; i++) { @@ -597,20 +716,165 @@ pgagroal_management_read_status(SSL* ssl, int socket) { if (!strcmp(disabled[i], "*")) { - printf("Disabled database: ALL\n"); + cJSON_AddItemToArray(databases_array, cJSON_CreateString("ALL")); + counter = -1; } else { - printf("Disabled database: %s\n", disabled[i]); + cJSON_AddItemToArray(databases_array, cJSON_CreateString(disabled[i])); + counter++; } } } - return 0; + cJSON* disabled_databases = cJSON_CreateObject(); + cJSON_AddNumberToObject(disabled_databases, "count", counter); + cJSON_AddStringToObject(disabled_databases, "state", "disabled"); + cJSON_AddItemToObject(disabled_databases, JSON_TAG_ARRAY_NAME, databases_array); + cJSON_AddItemToObject(databases, "disabled", disabled_databases); + cJSON_AddItemToObject(output, "databases", databases); -error: + // the 'status' command ends here + if (!include_details) + { + goto end; + } - return 1; + /*********** 'status details ************/ + + memset(&header, 0, sizeof(header)); + + if (read_complete(ssl, socket, &header[0], sizeof(header))) + { + goto error; + } + + // quantity informations + max_connections = pgagroal_read_int32(&header); + limits = pgagroal_read_int32(&(header[4])); + servers = pgagroal_read_int32(&(header[8])); + + cJSON* json_servers = cJSON_CreateObject(); + cJSON* json_servers_array = cJSON_CreateArray(); + cJSON_AddItemToObject(output, "servers", json_servers); + cJSON_AddNumberToObject(json_servers, "count", servers); + + // details about the servers + for (int i = 0; i < servers; i++) + { + char server[5 + MISC_LENGTH + MISC_LENGTH]; + + memset(&server, 0, sizeof(server)); + + if (read_complete(ssl, socket, &server[0], sizeof(server))) + { + goto error; + } + + cJSON* current_server_json = cJSON_CreateObject(); + cJSON_AddStringToObject(current_server_json, "server", pgagroal_read_string(&(server[0]))); + cJSON_AddStringToObject(current_server_json, "host", pgagroal_read_string(&(server[MISC_LENGTH]))); + cJSON_AddNumberToObject(current_server_json, "port", pgagroal_read_int32(&(server[MISC_LENGTH + MISC_LENGTH]))); + cJSON_AddStringToObject(current_server_json, "state", pgagroal_server_state_as_string(pgagroal_read_byte(&(server[MISC_LENGTH + MISC_LENGTH + 4])))); + + cJSON_AddItemToArray(json_servers_array, current_server_json); + } + + cJSON_AddItemToObject(json_servers, JSON_TAG_ARRAY_NAME, json_servers_array); + + // details about the limits + cJSON* json_limits = cJSON_CreateObject(); + cJSON* json_limits_array = cJSON_CreateArray(); + cJSON_AddItemToObject(json_limits, JSON_TAG_ARRAY_NAME, json_limits_array); + cJSON_AddItemToObject(output, "limits", json_limits); + cJSON_AddNumberToObject(json_limits, "count", limits); + + for (int i = 0; i < limits; i++) + { + char limit[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]; + memset(&limit, 0, sizeof(limit)); + + if (read_complete(ssl, socket, &limit[0], sizeof(limit))) + { + goto error; + } + + cJSON* current_limit_json = cJSON_CreateObject(); + + cJSON_AddStringToObject(current_limit_json, "database", pgagroal_read_string(&(limit[16]))); + cJSON_AddStringToObject(current_limit_json, "username", pgagroal_read_string(&(limit[16 + MAX_DATABASE_LENGTH]))); + + cJSON* current_connections = cJSON_CreateObject(); + + cJSON_AddNumberToObject(current_connections, "active", pgagroal_read_int32(&(limit))); + cJSON_AddNumberToObject(current_connections, "max", pgagroal_read_int32(&(limit[4]))); + cJSON_AddNumberToObject(current_connections, "initial", pgagroal_read_int32(&(limit[8]))); + cJSON_AddNumberToObject(current_connections, "min", pgagroal_read_int32(&(limit[12]))); + + cJSON_AddItemToObject(current_limit_json, "connections", current_connections); + cJSON_AddItemToArray(json_limits_array, current_limit_json); + + } + + // max connections details (note that the connections json object has been created + // as part of the status output) + cJSON* connections_array = cJSON_CreateArray(); + cJSON_AddItemToObject(connections, JSON_TAG_ARRAY_NAME, connections_array); + + for (int i = 0; i < max_connections; i++) + { + char details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH + MAX_APPLICATION_NAME]; + signed char state; + long time; + time_t t; + char ts[20] = {0}; + int pid; + char p[10] = {0}; + int fd; + char f[10] = {0}; + + memset(&details, 0, sizeof(details)); + + if (read_complete(ssl, socket, &details[0], sizeof(details))) + { + + goto error; + } + + state = (signed char)header[12 + i]; + time = pgagroal_read_long(&(details[0])); + pid = pgagroal_read_int32(&(details[8])); + fd = pgagroal_read_int32(&(details[12])); + + t = time; + strftime(ts, 20, "%Y-%m-%d %H:%M:%S", localtime(&t)); + + sprintf(p, "%d", pid); + sprintf(f, "%d", fd); + + cJSON* current_connection_json = cJSON_CreateObject(); + + cJSON_AddNumberToObject(current_connection_json, "number", i); + cJSON_AddStringToObject(current_connection_json, "state", pgagroal_server_state_as_string(state)); + cJSON_AddStringToObject(current_connection_json, "time", time > 0 ? ts : ""); + cJSON_AddStringToObject(current_connection_json, "pid", pid > 0 ? p : ""); + cJSON_AddStringToObject(current_connection_json, "fd", fd > 0 ? f : ""); + cJSON_AddStringToObject(current_connection_json, "database", pgagroal_read_string(&(details[16]))); + cJSON_AddStringToObject(current_connection_json, "user", pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH]))); + cJSON_AddStringToObject(current_connection_json, "detail", pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]))); + + cJSON_AddItemToArray(connections_array, current_connection_json); + + } + +end: + return json; + +error: + // set the json object as faulty and erase the errno + pgagroal_json_set_command_object_faulty(json, strerror(errno), errno); + errno = 0; + return json; } int @@ -701,143 +965,29 @@ pgagroal_management_details(SSL* ssl, int fd) } int -pgagroal_management_read_details(SSL* ssl, int socket) +pgagroal_management_read_details(SSL* ssl, int socket, char output_format) { - char header[12 + MAX_NUMBER_OF_CONNECTIONS]; - int max_connections = 0; - int limits = 0; - int servers = 0; - - memset(&header, 0, sizeof(header)); + cJSON* json = pgagroal_management_json_read_status_details(ssl, socket, true); - if (read_complete(ssl, socket, &header[0], sizeof(header))) + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; goto error; } - max_connections = pgagroal_read_int32(&header); - limits = pgagroal_read_int32(&(header[4])); - servers = pgagroal_read_int32(&(header[8])); - - for (int i = 0; i < servers; i++) - { - char server[5 + MISC_LENGTH + MISC_LENGTH]; - signed char state; - - memset(&server, 0, sizeof(server)); - - if (read_complete(ssl, socket, &server[0], sizeof(server))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - state = pgagroal_read_byte(&(server[MISC_LENGTH + MISC_LENGTH + 4])); - - printf("---------------------\n"); - printf("Server: %s\n", pgagroal_read_string(&(server[0]))); - printf("Host: %s\n", pgagroal_read_string(&(server[MISC_LENGTH]))); - printf("Port: %d\n", pgagroal_read_int32(&(server[MISC_LENGTH + MISC_LENGTH]))); - - switch (state) - { - case SERVER_NOTINIT: - printf("State: Not init\n"); - break; - case SERVER_NOTINIT_PRIMARY: - printf("State: Not init (primary)\n"); - break; - case SERVER_PRIMARY: - printf("State: Primary\n"); - break; - case SERVER_REPLICA: - printf("State: Replica\n"); - break; - case SERVER_FAILOVER: - printf("State: Failover\n"); - break; - case SERVER_FAILED: - printf("State: Failed\n"); - break; - default: - printf("State: %d\n", state); - break; - } - } - - printf("---------------------\n"); - - for (int i = 0; i < limits; i++) + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - char limit[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]; - - memset(&limit, 0, sizeof(limit)); - - if (read_complete(ssl, socket, &limit[0], sizeof(limit))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - printf("Database: %s\n", pgagroal_read_string(&(limit[16]))); - printf("Username: %s\n", pgagroal_read_string(&(limit[16 + MAX_DATABASE_LENGTH]))); - printf("Active connections: %d\n", pgagroal_read_int32(&(limit))); - printf("Max connections: %d\n", pgagroal_read_int32(&(limit[4]))); - printf("Initial connections: %d\n", pgagroal_read_int32(&(limit[8]))); - printf("Min connections: %d\n", pgagroal_read_int32(&(limit[12]))); - printf("---------------------\n"); + return pgagroal_json_print_and_free_json_object(json); } - - for (int i = 0; i < max_connections; i++) + else { - char details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH + MAX_APPLICATION_NAME]; - signed char state; - long time; - time_t t; - char ts[20] = {0}; - int pid; - char p[10] = {0}; - int fd; - char f[10] = {0}; - - memset(&details, 0, sizeof(details)); - - if (read_complete(ssl, socket, &details[0], sizeof(details))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - state = (signed char)header[12 + i]; - time = pgagroal_read_long(&(details[0])); - pid = pgagroal_read_int32(&(details[8])); - fd = pgagroal_read_int32(&(details[12])); - - t = time; - strftime(ts, 20, "%Y-%m-%d %H:%M:%S", localtime(&t)); - - sprintf(p, "%d", pid); - sprintf(f, "%d", fd); - - printf("Connection %4d: %-15s %-19s %-6s %-6s %s %s %s\n", - i, - pgagroal_get_state_string(state), - time > 0 ? ts : "", - pid > 0 ? p : "", - fd > 0 ? f : "", - pgagroal_read_string(&(details[16])), - pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH])), - pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]))); + return pgagroal_management_json_print_status_details(json); } - return 0; - error: + pgagroal_log_warn("pgagroal_management_read_details: command error [%s]", + (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); return 1; } @@ -957,7 +1107,7 @@ pgagroal_management_isalive(SSL* ssl, int fd) } int -pgagroal_management_read_isalive(SSL* ssl, int socket, int* status) +pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format) { char buf[4]; @@ -972,27 +1122,52 @@ pgagroal_management_read_isalive(SSL* ssl, int socket, int* status) *status = pgagroal_read_int32(&buf); - return 0; - -error: + // do I need to provide JSON output? + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + cJSON* json = pgagroal_json_create_new_command_object("ping", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); - return 1; -} + cJSON_AddNumberToObject(output, "status", *status); -int -pgagroal_management_write_isalive(int socket, bool gracefully) -{ - char buf[4]; + if (*status == PING_STATUS_RUNNING) + { + cJSON_AddStringToObject(output, "message", "running"); + } + else if (*status == PING_STATUS_SHUTDOWN_GRACEFULLY) + { + cJSON_AddStringToObject(output, "message", "shutdown gracefully"); + } + else + { + cJSON_AddStringToObject(output, "message", "unknown"); + } + + return pgagroal_json_print_and_free_json_object(json); + + } + + return 0; + +error: + + return 1; +} + +int +pgagroal_management_write_isalive(int socket, bool gracefully) +{ + char buf[4]; memset(&buf, 0, sizeof(buf)); if (!gracefully) { - pgagroal_write_int32(buf, 1); + pgagroal_write_int32(buf, PING_STATUS_RUNNING); } else { - pgagroal_write_int32(buf, 2); + pgagroal_write_int32(buf, PING_STATUS_SHUTDOWN_GRACEFULLY); } if (write_complete(NULL, socket, &buf, sizeof(buf))) @@ -1009,6 +1184,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) { @@ -1603,11 +1799,116 @@ pgagroal_management_write_config_get(int socket, char* config_key) } -int -pgagroal_management_read_config_get(int socket, char** data) +/** + * Utility method to wrap the answer about a configuration setting + * into a JSON object. + * + * @param socket the socket from which reading the data from + * @param config_key the key requested, used only to populate the json + * @param expected_value the config value expected in the case of a `config set`. + * If the expetced_value is not null, the function checks if the obtained config value and + * the expected one are equal, and in case are not set the JSON object as faulty. + * + * @return the JSON object + */ +static cJSON* +pgagroal_managment_json_read_config_get(int socket, char* config_key, char* expected_value) { + int size = MISC_LENGTH; - return pgagroal_management_read_payload(socket, MANAGEMENT_CONFIG_GET, &size, data); + char* buffer = NULL; + bool is_config_set = false; + + buffer = calloc(1, size); + if (buffer == NULL) + { + goto error; + } + + if (pgagroal_management_read_payload(socket, MANAGEMENT_CONFIG_GET, &size, &buffer)) + { + goto error; + } + + // is this the answer from a 'conf set' command ? + is_config_set = (expected_value && strlen(expected_value) > 0); + + cJSON* json = pgagroal_json_create_new_command_object(is_config_set ? "conf set" : "conf get", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); + cJSON_AddStringToObject(output, "key", config_key); + cJSON_AddStringToObject(output, "value", buffer); + + if (is_config_set) + { + cJSON_AddStringToObject(output, "expected", expected_value); + + // if the expected value is not what we get, this means there is an error + // (e.g., cannot apply the config set) + if (strncmp(buffer, expected_value, size)) + { + pgagroal_json_set_command_object_faulty(json, "Current and expected values are different", EXIT_STATUS_DATA_ERROR); + } + } + + free(buffer); + return json; +error: + if (buffer) + { + free(buffer); + } + return NULL; +} + +int +pgagroal_management_read_config_get(int socket, char* config_key, char* expected_value, bool verbose, char output_format) +{ + + cJSON* json = pgagroal_managment_json_read_config_get(socket, config_key, expected_value); + int status = EXIT_STATUS_OK; + + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // extract the command status + status = pgagroal_json_command_object_exit_status(json); + + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + pgagroal_json_print_and_free_json_object(json); + json = NULL; + goto end; + } + + // if here, print out in text format + cJSON* output = pgagroal_json_extract_command_output_object(json); + cJSON* value = cJSON_GetObjectItemCaseSensitive(output, "value"); + cJSON* key = cJSON_GetObjectItemCaseSensitive(output, "key"); + if (verbose) + { + printf("%s = %s\n", key->valuestring, value->valuestring); + } + else + { + printf("%s\n", value->valuestring); + } + + goto end; + +error: + + pgagroal_log_warn("pgagroal_management_read_config_get : error retrieving configuration for <%s> : %s", config_key, strerror(errno)); + errno = 0; + status = EXIT_STATUS_DATA_ERROR; +end: + if (json) + { + cJSON_Delete(json); + } + + return status; } int @@ -1726,3 +2027,523 @@ pgagroal_management_write_config_set(int socket, char* config_key, char* config_ return 1; } + +int +pgagroal_management_conf_ls(SSL* ssl, int fd) +{ + if (write_header(ssl, fd, MANAGEMENT_CONFIG_LS, -1)) + { + pgagroal_log_warn("pgagroal_management_conf_ls: write: %d", fd); + errno = 0; + goto error; + } + + return 0; + +error: + + return 1; +} + +int +pgagroal_management_read_conf_ls(SSL* ssl, int socket, char output_format) +{ + + // get the JSON output + cJSON* json = pgagroal_management_json_read_conf_ls(ssl, socket); + + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + return pgagroal_json_print_and_free_json_object(json); + } + else + { + return pgagroal_management_json_print_conf_ls(json); + } + +error: + pgagroal_log_warn("pgagroal_management_read_conf_ls: read: %d %s", socket, strerror(errno)); + errno = 0; + + return 1; +} + +int +pgagroal_management_write_conf_ls(int socket) +{ + struct configuration* config; + + config = (struct configuration*)shmem; + + if (pgagroal_management_write_conf_ls_detail(socket, config->configuration_path)) + { + goto error; + } + + if (pgagroal_management_write_conf_ls_detail(socket, config->hba_path)) + { + goto error; + } + + if (pgagroal_management_write_conf_ls_detail(socket, config->limit_path)) + { + goto error; + } + + // 4 + if (pgagroal_management_write_conf_ls_detail(socket, config->frontend_users_path)) + { + goto error; + } + //5 + if (pgagroal_management_write_conf_ls_detail(socket, config->admins_path)) + { + goto error; + } + //6 + if (pgagroal_management_write_conf_ls_detail(socket, config->superuser_path)) + { + goto error; + } + // 7 + if (pgagroal_management_write_conf_ls_detail(socket, config->users_path)) + { + goto error; + } + + return 0; + +error: + pgagroal_log_debug("pgagroal_management_write_conf_ls: error writing out file paths"); + return 1; +} + +/** + * Utility function to write a single configuration path to the socket. + * + * @param socket the file descriptor of the open socket + * @param what the pointer to the path to send out on the socket. It cannot + * exceed in size MAX_PATH - 1. + * @returns 0 on success + */ +static int +pgagroal_management_write_conf_ls_detail(int socket, char* what) +{ + char buf[4]; + size_t size = 0; + char data[MAX_PATH]; + + if (what && strlen(what) > MAX_PATH) + { + goto error; + } + + memset(&buf, 0, sizeof(buf)); + memset(&data, 0, sizeof(data)); + + size = what ? strlen(what) + 1 : 0; + if (size > MAX_PATH) + { + errno = EMSGSIZE; + goto error; + } + + pgagroal_write_int32(&buf, size); + + if (write_complete(NULL, socket, &buf, sizeof(buf))) + { + goto error; + } + + memcpy(&data[0], what, size); + if (write_complete(NULL, socket, data, size)) + { + goto error; + } + + pgagroal_log_trace("pgagroal_management_write_conf_ls_deail: writing <%s> with %d bytes", what, size); + return 0; + +error: + pgagroal_log_debug("pgagroal_management_write_conf_ls_detail: error %d %s", errno, strerror(errno)); + errno = 0; + return 1; +} + +/** + * Utility function to read back from the socket a configuration path. + * + * It does zero fill the buffer pointed by its argument, so + * it is safe to call this function with a prefilled buffer, but its content + * will be lost. + * + * The buffer will be considered able to store MAX_PATH bytes. + * + * @param socket the file descriptor of the open socket + * @param buffer an already allocated buffer where to place the read value. Only + * MAX_PATH bytes will be read out of socket. + * @return 0 on success + */ +static int +pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer) +{ + char buf[4]; + int size = 0; + + memset(&buf, 0, sizeof(buf)); + memset(buffer, 0, MAX_PATH); + + if (read_complete(ssl, socket, &buf[0], sizeof(buf))) + { + goto error; + } + + size = pgagroal_read_int32(&buf); + + if (size > MAX_PATH) + { + errno = EMSGSIZE; + goto error; + } + + if (read_complete(ssl, socket, buffer, size)) + { + goto error; + } + + return 0; + +error: + memset(buffer, 0, MAX_PATH); + pgagroal_log_warn("pgagroal_management_read_conf_ls_detail: read: %d %s", socket, strerror(errno)); + errno = 0; + + return 1; +} + +/** + * Utility function to print out the result of a 'status' + * or a 'status details' command already wrapped into a + * JSON object. + * The function tries to understand from the command name + * within the JSON object if the output refers to the + * 'status' or 'status details' command. + * + * If the command is faulty, this method does nothing, therefore + * printing out information about faulty commands has to be done + * at an higher level. + * + * @param json the JSON object + * + * @returns 0 on success + */ +int +pgagroal_management_json_print_status_details(cJSON* json) +{ + bool is_command_details = false; /* is this command 'status details' ? */ + int status = EXIT_STATUS_OK; + + // sanity check + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // the command must be 'status' or 'status details' + if (pgagroal_json_is_command_name_equals_to(json, "status")) + { + is_command_details = false; + } + else if (pgagroal_json_is_command_name_equals_to(json, "status details")) + { + is_command_details = true; + } + else + { + goto error; + } + + // now get the output and start printing it + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // overall status + printf("Status: %s\n", + cJSON_GetObjectItemCaseSensitive(cJSON_GetObjectItemCaseSensitive(output, "status"), "message")->valuestring); + + // connections + cJSON* connections = cJSON_GetObjectItemCaseSensitive(output, "connections"); + if (!connections) + { + goto error; + } + + printf("Active connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "active")->valueint); + printf("Total connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "total")->valueint); + printf("Max connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "max")->valueint); + + // databases + cJSON* databases = cJSON_GetObjectItemCaseSensitive(output, "databases"); + if (!databases) + { + goto error; + } + + cJSON* disabled_databases = cJSON_GetObjectItemCaseSensitive(databases, "disabled"); + if (!disabled_databases) + { + goto error; + } + + cJSON* disabled_databases_list = cJSON_GetObjectItemCaseSensitive(disabled_databases, JSON_TAG_ARRAY_NAME); + cJSON* current; + cJSON_ArrayForEach(current, disabled_databases_list) + { + printf("Disabled database: %s\n", current->valuestring); + } + + // the status command ends here + if (!is_command_details) + { + goto end; + } + + // dump the servers information + cJSON* servers = cJSON_GetObjectItemCaseSensitive(output, "servers"); + if (!servers) + { + goto error; + } + + cJSON* servers_list = cJSON_GetObjectItemCaseSensitive(servers, JSON_TAG_ARRAY_NAME); + cJSON_ArrayForEach(current, servers_list) + { + printf("---------------------\n"); + printf("Server: %s\n", cJSON_GetObjectItemCaseSensitive(current, "server")->valuestring); + printf("Host: %s\n", cJSON_GetObjectItemCaseSensitive(current, "host")->valuestring); + printf("Port: %d\n", cJSON_GetObjectItemCaseSensitive(current, "port")->valueint); + printf("State: %s\n", cJSON_GetObjectItemCaseSensitive(current, "state")->valuestring); + printf("---------------------\n"); + + } + + // dump the limits information + cJSON* limits = cJSON_GetObjectItemCaseSensitive(output, "limits"); + cJSON* limits_list = cJSON_GetObjectItemCaseSensitive(limits, JSON_TAG_ARRAY_NAME); + cJSON_ArrayForEach(current, limits_list) + { + printf("---------------------\n"); + printf("Database: %s\n", cJSON_GetObjectItemCaseSensitive(current, "database")->valuestring); + printf("Username: %s\n", cJSON_GetObjectItemCaseSensitive(current, "username")->valuestring); + cJSON* current_connections = cJSON_GetObjectItemCaseSensitive(current, "connections"); + printf("Active connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "active")->valueint); + printf("Max connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "max")->valueint); + printf("Initial connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "initial")->valueint); + printf("Min connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "min")->valueint); + printf("---------------------\n"); + } + + // print the connection information + int i = 0; + cJSON_ArrayForEach(current, cJSON_GetObjectItemCaseSensitive(connections, JSON_TAG_ARRAY_NAME)) + { + printf("Connection %4d: %-15s %-19s %-6s %-6s %s %s %s\n", + i++, + cJSON_GetObjectItemCaseSensitive(current, "state")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "time")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "pid")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "fd")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "user")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "database")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "detail")->valuestring); + + } + +error: + status = 1; +end: + if (json) + { + cJSON_Delete(json); + } + + return status; + +} + +/** + * Utility method to get the information about the `conf ls` command. + * This method produces a cJSON object that needs to be printed out in textual format. + * + * @param ssl the SSL file descriptor + * @param socket the file descriptor for the socket + * + * @returns the cJSON object, faulty if something went wrong + */ +static cJSON* +pgagroal_management_json_read_conf_ls(SSL* ssl, int socket) +{ + char buf[4]; + char* buffer; + + cJSON* json = pgagroal_json_create_new_command_object("conf ls", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // add an array that will contain the files + cJSON* files = cJSON_CreateObject(); + cJSON* files_array = cJSON_CreateArray(); + cJSON_AddItemToObject(output, "files", files); + cJSON_AddItemToObject(files, JSON_TAG_ARRAY_NAME, files_array); + + memset(&buf, 0, sizeof(buf)); + buffer = calloc(1, MAX_PATH); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the main configuration file entry + cJSON* mainConf = cJSON_CreateObject(); + cJSON_AddStringToObject(mainConf, "description", "Main Configuration file"); + cJSON_AddStringToObject(mainConf, "path", buffer); + cJSON_AddItemToArray(files_array, mainConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the HBA file + cJSON* hbaConf = cJSON_CreateObject(); + cJSON_AddStringToObject(hbaConf, "description", "HBA File"); + cJSON_AddStringToObject(hbaConf, "path", buffer); + cJSON_AddItemToArray(files_array, hbaConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the limit file + cJSON* limitConf = cJSON_CreateObject(); + cJSON_AddStringToObject(limitConf, "description", "Limit file"); + cJSON_AddStringToObject(limitConf, "path", buffer); + cJSON_AddItemToArray(files_array, limitConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the frontend file + cJSON* frontendConf = cJSON_CreateObject(); + cJSON_AddStringToObject(frontendConf, "description", "Frontend users file"); + cJSON_AddStringToObject(frontendConf, "path", buffer); + cJSON_AddItemToArray(files_array, frontendConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the admins file + cJSON* adminsConf = cJSON_CreateObject(); + cJSON_AddStringToObject(adminsConf, "description", "Admins file"); + cJSON_AddStringToObject(adminsConf, "path", buffer); + cJSON_AddItemToArray(files_array, adminsConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the superuser file + cJSON* superuserConf = cJSON_CreateObject(); + cJSON_AddStringToObject(superuserConf, "description", "Superuser file"); + cJSON_AddStringToObject(superuserConf, "path", buffer); + cJSON_AddItemToArray(files_array, superuserConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the users file + cJSON* usersConf = cJSON_CreateObject(); + cJSON_AddStringToObject(usersConf, "description", "Users file"); + cJSON_AddStringToObject(usersConf, "path", buffer); + cJSON_AddItemToArray(files_array, usersConf); + + // all done + goto end; + +error: + free(buffer); + pgagroal_log_warn("pgagroal_management_json_read_conf_ls: read: %d %s", socket, strerror(errno)); + errno = 0; + pgagroal_json_set_command_object_faulty(json, strerror(errno), errno); + +end: + free(buffer); + return json; + +} + +/** + * Utility function to handle a JSON object and print it out + * as normal text. + * + * @param json the JSON object + * @returns 0 on success + */ +static int +pgagroal_management_json_print_conf_ls(cJSON* json) +{ + int status = EXIT_STATUS_OK; + + // sanity check + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // now get the output and start printing it + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // files + cJSON* files = cJSON_GetObjectItemCaseSensitive(output, "files"); + if (!files) + { + goto error; + } + + cJSON* files_array = cJSON_GetObjectItemCaseSensitive(files, JSON_TAG_ARRAY_NAME); + cJSON* current; + cJSON_ArrayForEach(current, files_array) + { + // the current JSON object is made by two different values + printf("%-25s : %s\n", + cJSON_GetObjectItemCaseSensitive(current, "description")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "path")->valuestring); + } + + status = pgagroal_json_command_object_exit_status(json); + goto end; + +error: + status = EXIT_STATUS_DATA_ERROR; +end: + if (json) + { + cJSON_Delete(json); + } + + return status; +} diff --git a/src/libpgagroal/memory.c b/src/libpgagroal/memory.c index 25c231c5..5efecbde 100644 --- a/src/libpgagroal/memory.c +++ b/src/libpgagroal/memory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/message.c b/src/libpgagroal/message.c index f4bdbf39..00b4e807 100644 --- a/src/libpgagroal/message.c +++ b/src/libpgagroal/message.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/network.c b/src/libpgagroal/network.c index 4e0b7f11..6ecb8a74 100644 --- a/src/libpgagroal/network.c +++ b/src/libpgagroal/network.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -200,6 +200,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 +432,81 @@ 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: + + pgagroal_log_debug("pgagroal_vault_connect: %s", strerror(error)); + + if (servinfo != NULL) + { + freeaddrinfo(servinfo); + } + + return 1; +} + /** * */ diff --git a/src/libpgagroal/pipeline_perf.c b/src/libpgagroal/pipeline_perf.c index e36f1af4..c12c8701 100644 --- a/src/libpgagroal/pipeline_perf.c +++ b/src/libpgagroal/pipeline_perf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pipeline_session.c b/src/libpgagroal/pipeline_session.c index 08a672e2..562aea8a 100644 --- a/src/libpgagroal/pipeline_session.c +++ b/src/libpgagroal/pipeline_session.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pipeline_transaction.c b/src/libpgagroal/pipeline_transaction.c index c8e0c4ed..20de6211 100644 --- a/src/libpgagroal/pipeline_transaction.c +++ b/src/libpgagroal/pipeline_transaction.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pool.c b/src/libpgagroal/pool.c index 98c9aab5..ee72d020 100644 --- a/src/libpgagroal/pool.c +++ b/src/libpgagroal/pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/prometheus.c b/src/libpgagroal/prometheus.c index d5bd1f20..e7582a01 100644 --- a/src/libpgagroal/prometheus.c +++ b/src/libpgagroal/prometheus.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -2101,7 +2101,7 @@ send_chunk(int client_fd, char* data) return MESSAGE_STATUS_ERROR; } - sprintf(m, "%lX\r\n", strlen(data)); + sprintf(m, "%zX\r\n", strlen(data)); m = pgagroal_append(m, data); m = pgagroal_append(m, "\r\n"); diff --git a/src/libpgagroal/remote.c b/src/libpgagroal/remote.c index 472f4645..4d7fed1f 100644 --- a/src/libpgagroal/remote.c +++ b/src/libpgagroal/remote.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -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 fd162173..3cfc1c65 100644 --- a/src/libpgagroal/security.c +++ b/src/libpgagroal/security.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -125,7 +125,7 @@ static int create_ssl_ctx(bool client, SSL_CTX** ctx); static int create_ssl_client(SSL_CTX* ctx, char* key, char* cert, char* root, int socket, SSL** ssl); static int create_ssl_server(SSL_CTX* ctx, int socket, SSL** ssl); static int establish_client_tls_connection(int server, int fd, SSL** ssl); -static int create_client_tls_connection(int fd, SSL** ssl); +static int create_client_tls_connection(int fd, SSL** ssl, char* tls_key_file, char* tls_cert_file, char* tls_ca_file); static int auth_query(SSL* c_ssl, int client_fd, int slot, char* username, char* database, int hba_method); static int auth_query_get_connection(char* username, char* password, char* database, int* server_fd, SSL** server_ssl); @@ -2128,6 +2128,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2155,6 +2156,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2180,6 +2182,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd { if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2203,6 +2206,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2233,6 +2237,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd { if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2321,6 +2326,7 @@ server_authenticate(struct message* msg, int auth_type, char* username, char* pa if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2439,6 +2445,7 @@ server_password(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2450,6 +2457,7 @@ server_password(char* username, char* password, int slot, SSL* server_ssl) { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2556,6 +2564,7 @@ server_md5(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2567,6 +2576,7 @@ server_md5(char* username, char* password, int slot, SSL* server_ssl) { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2686,6 +2696,7 @@ server_scram256(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &msg); if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2750,6 +2761,7 @@ server_scram256(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &msg); if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -4472,7 +4484,7 @@ create_ssl_client(SSL_CTX* ctx, char* key, char* cert, char* root, int socket, S goto error; } - if (have_cert && strlen(key) > 0) + if (have_cert && key != NULL && strlen(key) > 0) { if (SSL_use_PrivateKey_file(s, key, SSL_FILETYPE_PEM) != 1) { @@ -5004,6 +5016,7 @@ auth_query_server_md5(struct message* startup_response_msg, char* username, char status = pgagroal_read_block_message(server_ssl, socket, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -5015,6 +5028,7 @@ auth_query_server_md5(struct message* startup_response_msg, char* username, char { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -5702,7 +5716,6 @@ auth_query_client_scram256(SSL* c_ssl, int client_fd, char* username, char* shad static int establish_client_tls_connection(int server, int fd, SSL** ssl) { - bool use_ssl = false; struct configuration* config = NULL; struct message* ssl_msg = NULL; struct message* msg = NULL; @@ -5710,9 +5723,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) config = (struct configuration*)shmem; - use_ssl = config->servers[server].tls; - - if (use_ssl) + if (config->servers[server].tls) { status = pgagroal_create_ssl_message(&ssl_msg); if (status != MESSAGE_STATUS_OK) @@ -5734,7 +5745,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) if (msg->kind == 'S') { - create_client_tls_connection(fd, ssl); + create_client_tls_connection(fd, ssl, config->servers[server].tls_key_file, config->servers[server].tls_cert_file, config->servers[server].tls_ca_file); } } @@ -5752,7 +5763,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) } static int -create_client_tls_connection(int fd, SSL** ssl) +create_client_tls_connection(int fd, SSL** ssl, char* tls_key_file, char* tls_cert_file, char* tls_ca_file) { SSL_CTX* ctx = NULL; SSL* s = NULL; @@ -5766,7 +5777,7 @@ create_client_tls_connection(int fd, SSL** ssl) } /* Create SSL structure */ - if (create_ssl_client(ctx, NULL, NULL, NULL, fd, &s)) + if (create_ssl_client(ctx, tls_key_file, tls_cert_file, tls_ca_file, fd, &s)) { pgagroal_log_error("Client failed"); goto error; diff --git a/src/libpgagroal/server.c b/src/libpgagroal/server.c index 74ba0d11..b4b48c3b 100644 --- a/src/libpgagroal/server.c +++ b/src/libpgagroal/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/shmem.c b/src/libpgagroal/shmem.c index 61a37812..bb24487b 100644 --- a/src/libpgagroal/shmem.c +++ b/src/libpgagroal/shmem.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/tracker.c b/src/libpgagroal/tracker.c index 56ab6363..bf444cd5 100644 --- a/src/libpgagroal/tracker.c +++ b/src/libpgagroal/tracker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index bfc86de1..d24064fa 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -1061,7 +1061,7 @@ parse_deprecated_command(int argc, if (deprecated_by && pgagroal_version_ge(deprecated_since_major, deprecated_since_minor, 0)) { - warnx("command <%s> has been deprecated by <%s> since version %d.%d\n", + warnx("command <%s> has been deprecated by <%s> since version %d.%d", command, deprecated_by, deprecated_since_major, deprecated_since_minor); } @@ -1077,3 +1077,34 @@ parse_command_simple(int argc, { return parse_command(argc, argv, offset, command, subcommand, NULL, NULL, NULL, NULL); } + +/** + * Given a server state, it returns a string that + * described the state in a human-readable form. + * + * If the state cannot be determined, the numeric + * form of the state is returned as a string. + * + * @param state the value of the sate for the server + * @returns the string representing the state + */ +char* +pgagroal_server_state_as_string(signed char state) +{ + char* buf; + + switch (state) + { + case SERVER_NOTINIT: return "Not init"; + case SERVER_NOTINIT_PRIMARY: return "Not init (primary)"; + case SERVER_PRIMARY: return "Primary"; + case SERVER_REPLICA: return "Replica"; + case SERVER_FAILOVER: return "Failover"; + case SERVER_FAILED: return "Failed"; + default: + buf = malloc(5); + memset(buf, 0, 5); + snprintf(buf, 5, "%d", state); + return buf; + } +} diff --git a/src/libpgagroal/worker.c b/src/libpgagroal/worker.c index 1b1838e3..d0c01deb 100644 --- a/src/libpgagroal/worker.c +++ b/src/libpgagroal/worker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/main.c b/src/main.c index 50a17ff6..a4e9eeb0 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -89,21 +89,9 @@ static void create_pidfile_or_exit(void); static void remove_pidfile(void); static void shutdown_ports(void); -static void handle_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); +// static void handle_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static void rotate_frontend_password_cb(struct ev_loop* loop, ev_periodic* w, int revents); -static void accept_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); -struct accept_io -{ - struct ev_io io; - int socket; - char** argv; -}; - -struct client -{ - pid_t pid; - struct client* next; -}; +// static void accept_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static volatile int keep_running = 1; static char** argv_ptr; @@ -111,12 +99,10 @@ static struct ev_loop* main_loop = NULL; static struct accept_io io_main[MAX_FDS]; static struct accept_io io_mgt; static struct accept_io io_uds; -static struct accept_io io_vault; static int* main_fds = NULL; static int main_fds_length = -1; static int unix_management_socket = -1; static int unix_pgsql_socket = -1; -static int unix_vault_socket = -1; static struct accept_io io_metrics[MAX_FDS]; static int* metrics_fds = NULL; static int metrics_fds_length = -1; @@ -332,8 +318,6 @@ main(int argc, char** argv) int c; bool conf_file_mandatory; char message[MISC_LENGTH]; // a generic message used for errors - - bool enable_vault = false; argv_ptr = argv; while (1) @@ -388,9 +372,6 @@ main(int argc, char** argv) case 'd': daemon = true; break; - case 'v': - enable_vault = true; - break; case 'V': version(); break; @@ -898,6 +879,7 @@ main(int argc, char** argv) create_pidfile_or_exit(); pgagroal_pool_init(); + initialize_random(); pgagroal_set_proc_title(argc, argv, "main", NULL); @@ -1055,53 +1037,11 @@ main(int argc, char** argv) ev_periodic_start (main_loop, &disconnect_client); } - if (enable_vault) { - /* TODO: - * 1. start rotate cb - * 2. start async vault accept cb - * 3. fork exec vault - */ - pid_t pid; - - initialize_random(); - ev_periodic_init(&rotate_frontend_password, rotate_frontend_password_cb, 0., 60, 0); + 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); - - unix_vault_socket = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in server_addr; - - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(6789); - server_addr.sin_addr.s_addr = INADDR_ANY; - - // set_nonblock(unix_vault_socket); - - bind(unix_vault_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); - if(listen(unix_vault_socket, 10) < 0) { - pgagroal_log_error("error in listen"); - goto error; - } - - memset(&io_vault, 0, sizeof(struct accept_io)); - ev_io_init((struct ev_io*)&io_vault, accept_vault_cb, unix_vault_socket, EV_READ); - io_vault.socket = unix_vault_socket; - io_vault.argv = argv_ptr; - ev_io_start(main_loop, (struct ev_io*)&io_vault); - - pid = fork(); - - if (pid == -1) - { - /* No process */ - pgagroal_log_error("Cannot create process"); - } - else if(pid == 0) { - // exec - execlp("/home/pgmoneta/pgagroal/build/src/pgagroal-vault", "pgagroal-vault", NULL); - } else { - - } - } if (config->metrics > 0) @@ -1557,6 +1497,10 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) pgagroal_log_debug("pgagroal: Management isalive"); pgagroal_management_write_isalive(client_fd, config->gracefully); break; + case MANAGEMENT_CONFIG_LS: + pgagroal_log_debug("pgagroal: Management conf ls"); + pgagroal_management_write_conf_ls(client_fd); + break; case MANAGEMENT_RESET: pgagroal_log_debug("pgagroal: Management reset"); pgagroal_prometheus_reset(); @@ -1615,6 +1559,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; @@ -1630,6 +1592,7 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) } } + pgagroal_disconnect(client_fd); pgagroal_prometheus_self_sockets_sub(); @@ -1920,81 +1883,78 @@ rotate_frontend_password_cb(struct ev_loop* loop, ev_periodic* w, int revents) struct configuration* config; config = (struct configuration*)shmem; - // TODO: get pwd length from config or random length + for (int i = 0; i < config->number_of_frontend_users; i++) { - // if (!strcmp(&config->frontend_users[i].username[0], "test")) - // { - pwd = generate_password(MIN_PASSWORD_LENGTH); - memcpy(&config->frontend_users[i].password, pwd, strlen(pwd)+1); - pgagroal_log_info("rotate_pass: current pass for username=%s:%s",config->frontend_users[i].username, config->frontend_users[i].password); - // } + pwd = generate_password(MIN_PASSWORD_LENGTH); + memcpy(&config->frontend_users[i].password, pwd, strlen(pwd)+1); + pgagroal_log_debug("rotate_pass: current pass for username=%s:%s",config->frontend_users[i].username, config->frontend_users[i].password); } } -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]; - - if (EV_ERROR & revents) - { - pgagroal_log_debug("accept_vault_cb: invalid event: %s", strerror(errno)); - errno = 0; - return; - } - - 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) - { - //TODO: error handling - } - - ev_io *vault_watcher = (ev_io*) malloc(sizeof(ev_io)); - ev_io_init(vault_watcher, handle_vault_cb, client_fd, EV_READ); - ev_io_start(main_loop, vault_watcher); -} - -static void -handle_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) -{ - struct configuration* config; - struct message* msg; - - if (EV_ERROR & revents) - { - pgagroal_log_debug("accept_vault_cb: invalid event: %s", strerror(errno)); - errno = 0; - return; - } - - config = (struct configuration*)shmem; - - pgagroal_memory_init(); - if (pgagroal_read_socket_message(watcher->fd, &msg) == MESSAGE_STATUS_OK) - { - for (int i = 0; i < config->number_of_frontend_users; i++) - { - if (!strcmp(&config->frontend_users[i].username[0], (char *)msg->data)) - { - pgagroal_write_frontend_password_response(0, watcher->fd, config->frontend_users[i].password); - pgagroal_memory_destroy(); - return; - } - } - } - - // No such username - pgagroal_write_frontend_password_response(0, watcher->fd, ""); - pgagroal_memory_destroy(); -} +// 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]; + +// if (EV_ERROR & revents) +// { +// pgagroal_log_debug("accept_vault_cb: invalid event: %s", strerror(errno)); +// errno = 0; +// return; +// } + +// 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) +// { +// //TODO: error handling +// } + +// ev_io *vault_watcher = (ev_io*) malloc(sizeof(ev_io)); +// ev_io_init(vault_watcher, handle_vault_cb, client_fd, EV_READ); +// ev_io_start(main_loop, vault_watcher); +// } + +// static void +// handle_vault_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) +// { +// struct configuration* config; +// struct message* msg; + +// if (EV_ERROR & revents) +// { +// pgagroal_log_debug("accept_vault_cb: invalid event: %s", strerror(errno)); +// errno = 0; +// return; +// } + +// config = (struct configuration*)shmem; + +// pgagroal_memory_init(); +// if (pgagroal_read_socket_message(watcher->fd, &msg) == MESSAGE_STATUS_OK) +// { +// for (int i = 0; i < config->number_of_frontend_users; i++) +// { +// if (!strcmp(&config->frontend_users[i].username[0], (char *)msg->data)) +// { +// pgagroal_write_frontend_password_response(0, watcher->fd, config->frontend_users[i].password); +// pgagroal_memory_destroy(); +// return; +// } +// } +// } + +// // No such username +// pgagroal_write_frontend_password_response(0, watcher->fd, ""); +// pgagroal_memory_destroy(); +// } static bool accept_fatal(int error) diff --git a/src/vault.c b/src/vault.c index 13a05224..2c1e718f 100644 --- a/src/vault.c +++ b/src/vault.c @@ -18,16 +18,17 @@ #include +#include #include #include #include #include #include +#include +#include #include -#define HTTP_PORT 80 -#define HTTPS_PORT 443 -#define BUFFER_SIZE 1024 +#define MAX_FDS 64 struct KV { @@ -36,63 +37,32 @@ struct KV UT_hash_handle hh; }; -static void initialize_ssl(void); -static SSL_CTX *create_ssl_context(void); -static void configure_ssl_context(SSL_CTX *ctx); -static void parse_query_string(const char *query_string, struct KV **hash_map); -static void free_hash_map(struct KV **hash_map); -static void handle(int client_fd, SSL *ssl, int is_http); -static void handle_url(const char *buffer, int client_fd, SSL *ssl, int is_http); -static void *http_thread_func(void *arg); -static void *https_thread_func(void *arg); -static void handle_users(const char *method, char *username, struct KV *params, char *response, size_t response_size); -static void handle_info(const char *method, const char *contents, char *response, size_t response_size); -static void handle_default(char *response, size_t response_size); - -static SSL_CTX *ssl_ctx; -static int client_socket; - -static void initialize_ssl(void) +static void +usage(void) { - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); + 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 SSL_CTX *create_ssl_context() -{ - const SSL_METHOD *method; - SSL_CTX *ctx; - - method = SSLv23_server_method(); - ctx = SSL_CTX_new(method); - if (ctx == NULL) - { - pgagroal_log_error("pgagroal_vault: failed to create ssl context"); - exit(EXIT_FAILURE); - } - - return ctx; -} - -static void configure_ssl_context(SSL_CTX *ctx) -{ - char crt[100]; - char key[100]; - sprintf(crt, "%s/.pgagroal/server.crt", pgagroal_get_home_directory()); - sprintf(key, "%s/.pgagroal/server.key", pgagroal_get_home_directory()); - if (SSL_CTX_use_certificate_file(ctx, crt, SSL_FILETYPE_PEM) <= 0) - { - pgagroal_log_error("pgagroal_vault: failed to use cert file %s", "server.crt"); - exit(EXIT_FAILURE); - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) - { - pgagroal_log_error("pgagroal_vault: failed to use cert file %s", "server.key"); - exit(EXIT_FAILURE); - } -} +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) { @@ -127,38 +97,22 @@ static void free_hash_map(struct KV **hash_map) } } -static void handle(int client_fd, SSL *ssl, int is_http) + +static int router(int client_fd, SSL *ssl) { - char buffer[BUFFER_SIZE]; + int exit_code = 0; + char buffer[HTTP_BUFFER_SIZE]; ssize_t bytes_read; - if (is_http) - { - bytes_read = read(client_fd, buffer, sizeof(buffer) - 1); - } - else - { - bytes_read = SSL_read(ssl, buffer, sizeof(buffer) - 1); - } - if (bytes_read <= 0) - { - if (!is_http) - { - SSL_free(ssl); - } - close(client_fd); - return; - } + + // Read the request + bytes_read = read(client_fd, buffer, sizeof(buffer) - 1); buffer[bytes_read] = '\0'; - handle_url(buffer, client_fd, ssl, is_http); -} -static void handle_url(const char *buffer, int client_fd, SSL *ssl, int is_http) -{ char method[8]; char path[128]; - char contents[BUFFER_SIZE]; - char response[BUFFER_SIZE]; - char format_string[BUFFER_SIZE]; + char contents[HTTP_BUFFER_SIZE]; + char response[HTTP_BUFFER_SIZE]; + char format_string[HTTP_BUFFER_SIZE]; sscanf(buffer, "%7s %127s", method, path); @@ -171,243 +125,139 @@ static void handle_url(const char *buffer, int client_fd, SSL *ssl, int is_http) struct KV *map = NULL; - // Parse URL parameters for GET requests + // 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) - { - // 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 - // handle_users(username, response, sizeof(response)); - handle_users(method, username, map, response, sizeof(response)); - } - else if (strcmp(path, "/info") == 0) - { - handle_info(method, contents, response, sizeof(response)); - } - else + // 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 { - handle_default(response, sizeof(response)); + route_not_found(response, sizeof(response)); } // Send the response - if (is_http) - { - write(client_fd, response, strlen(response)); - } - else + ssize_t bytes_write = write(client_fd, response, strlen(response)); + + if(bytes_write <= 0) { - SSL_write(ssl, response, strlen(response)); - SSL_shutdown(ssl); - SSL_free(ssl); + exit_code = 1; } - close(client_fd); free_hash_map(&map); + return exit_code; } -static void handle_users(const char *method, char *username, struct KV *params, char *response, size_t response_size) +static void route_users(int client_fd, char *username, struct KV *params, char *response, size_t response_size) { - if (strcmp(method, "GET") == 0) - { - struct KV *kv; - HASH_FIND_STR(params, "username", kv); - struct message *msg; - pgagroal_write_frontend_password_request(NULL, client_socket, username); - pgagroal_memory_init(); - pgagroal_read_socket_message(client_socket, &msg); - if(strcmp((char *)msg->data, "") != 0) { - snprintf(response, response_size, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "Your temporary password is: %s\r\n", - (char *)msg->data); - } else { - snprintf(response, response_size, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "No such user\r\n"); - } - pgagroal_memory_destroy(); - } - else - { - snprintf(response, response_size, "HTTP/1.1 405 Method Not Allowed\r\n\r\n"); + struct vault_configuration* config = (struct vault_configuration*)shmem; + int client_pgagroal_fd = -1; + // struct KV *kv; + // 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; } -} -static void handle_info(const char *method, const char *contents, char *response, size_t response_size) -{ - snprintf(response, response_size, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "vault is good\r\n"); -} + char password[MIN_PASSWORD_LENGTH + 1]; + memset(password, 0, MIN_PASSWORD_LENGTH + 1); -static void handle_default(char *response, size_t response_size) -{ - snprintf(response, response_size, "HTTP/1.1 404 Not Found\r\n\r\n"); -} - -static void *http_thread_func(void *arg) -{ - int server_fd, client_fd; - struct sockaddr_in server_addr, client_addr; - socklen_t client_len = sizeof(client_addr); - - server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) + // Call GET_PASSWORD at management port + if (pgagroal_management_get_password(NULL, client_pgagroal_fd, username, password)) { - pgagroal_log_error("Cannot create socket"); - return NULL; + // Send Error Response + route_not_found(response, response_size); + return; } - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(HTTP_PORT); - - if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) - { - pgagroal_log_error("Bind failed"); - return NULL; + if(strlen(password) < MIN_PASSWORD_LENGTH){ // user not found + route_not_found(response, response_size); } - // TODO: ? connection requests will be queued - if (listen(server_fd, 10) < 0) + else { - pgagroal_log_error("Listen failed"); - return NULL; + 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); } +} - printf("HTTP server is listening on port %d...\n", HTTP_PORT); +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); - while (1) - { - client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); - if (client_fd < 0) - { - pgagroal_log_error("Accept failed"); - continue; - } - handle(client_fd, NULL, 1); // 1 indicates HTTP - } +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; - close(server_fd); - return 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 *https_thread_func(void *arg) +static void +start_vault_io(void) { - int server_fd, client_fd; - struct sockaddr_in server_addr, client_addr; - socklen_t client_len = sizeof(client_addr); + int sockfd = *server_fd; - server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) - { - pgagroal_log_error("Cannot create socket"); - return NULL; - } - - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(HTTPS_PORT); - - if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) - { - pgagroal_log_error("Bind failed"); - return NULL; - } - - if (listen(server_fd, 10) < 0) - { - pgagroal_log_error("Listen failed"); - return NULL; - } - - printf("HTTPS server is listening on port %d...\n", HTTPS_PORT); - - while (1) - { - client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); - if (client_fd < 0) - { - pgagroal_log_error("Accept failed"); - continue; - } - - SSL *ssl = SSL_new(ssl_ctx); - SSL_set_fd(ssl, client_fd); - - if (SSL_accept(ssl) <= 0) - { - pgagroal_log_error("pgagroal_vault: ssl filed to accept"); - } - else - { - handle(client_fd, ssl, 0); // 0 indicates HTTPS - } - - // SSL_free(ssl); - } + 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); +} - close(server_fd); - return NULL; +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 socket = -1; - // SSL* s_ssl = NULL; int ret; - // int exit_code = 0; + int exit_code = 0; char* configuration_path = NULL; - char* host = NULL; - char* port = NULL; - char* username = NULL; - char* password = NULL; - // bool verbose = false; - char* logfile = NULL; - // bool do_free = true; + char* admin_password = NULL; + struct signal_info signal_watcher[0]; // Can add more + // SSL* s_ssl = NULL; int c; int option_index = 0; size_t size; - // int32_t mode = FLUSH_IDLE; - // char* database = NULL; - // char un[MAX_USERNAME_LENGTH]; - // char* server = NULL; - struct configuration* config = NULL; - bool remote_connection = false; - // long l_port; - // char* config_key = NULL; /* key for a configuration setting */ - // char* config_value = NULL; /* value for a configuration setting */ + 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'}, - {"host", required_argument, 0, 'h'}, - {"port", required_argument, 0, 'p'}, - {"user", required_argument, 0, 'U'}, - {"password", required_argument, 0, 'P'}, - {"logfile", required_argument, 0, 'L'}, - {"verbose", no_argument, 0, 'v'}, - {"version", no_argument, 0, 'V'}, + {"password", required_argument, 0, 'u'}, {"help", no_argument, 0, '?'} }; - c = getopt_long(argc, argv, "vV?c:h:p:U:P:L:", + c = getopt_long(argc, argv, "?c:u:", long_options, &option_index); if (c == -1) @@ -420,31 +270,13 @@ int main(int argc, char **argv) case 'c': configuration_path = optarg; break; - case 'h': - host = optarg; - break; - case 'p': - port = optarg; - break; - case 'U': - username = optarg; + case 'u': + admin_password = optarg; break; - case 'P': - password = optarg; + case '?': + usage(); + exit(1); break; - case 'L': - logfile = optarg; - break; - // case 'v': - // verbose = true; - // break; - // case 'V': - // version(); - // break; - // case '?': - // usage(); - // exit(1); - // break; default: break; } @@ -455,166 +287,260 @@ int main(int argc, char **argv) errx(1, "Using the root account is not allowed"); } - // if the user has specified the host and port - // options, she wants a remote connection - // but both remote connection parameters have to be set - if (host != NULL || port != NULL) - { - remote_connection = host != NULL && port != NULL; - if (!remote_connection) - { - printf("pgagroal-cli: you need both -h and -p options to perform a remote connection\n"); - exit(1); - } - } - - // if the user has specified either a username or a password - // there must be all the other pieces for a remote connection - if ((username != NULL || password != NULL) && !remote_connection) + // Set the password + if (!admin_password) { - errx(1, "you need also -h and -p options to perform a remote connection"); + printf("vault-pgagroal: provide a admin password using -u option\n"); + exit_code = 1; + goto done; } - // and she cannot use "local" and "remote" connections at the same time - if (configuration_path != NULL && remote_connection) - { - errx(1, "Use either -c or -h/-p to define endpoint"); - } - - // if (argc <= 1) - // { - // usage(); - // exit(1); - // } - - size = sizeof(struct configuration); + size = sizeof(struct vault_configuration); if (pgagroal_create_shared_memory(size, HUGEPAGE_OFF, &shmem)) { errx(1, "Error creating shared memory"); } - pgagroal_init_configuration(shmem); - if (configuration_path != NULL) + 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) { - ret = pgagroal_read_configuration(shmem, configuration_path, false); + // the configuration has some problem, build up a descriptive message if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) { - errx(1, "Configuration not found: <%s>", configuration_path); + snprintf(message, MISC_LENGTH, "Configuration file not found"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { - errx(1, "Too many sections in the configuration file <%s>", configuration_path); + snprintf(message, MISC_LENGTH, "Too many sections"); } - - if (logfile) + else if (ret == PGAGROAL_CONFIGURATION_STATUS_KO) { - config = (struct configuration*)shmem; - - config->log_type = PGAGROAL_LOGGING_TYPE_FILE; - memset(&config->log_path[0], 0, MISC_LENGTH); - memcpy(&config->log_path[0], logfile, MIN(MISC_LENGTH - 1, strlen(logfile))); + snprintf(message, MISC_LENGTH, "Invalid configuration file"); } - - if (pgagroal_start_logging()) + else if (ret > 0) { - errx(1, "Cannot start the logging subsystem"); + snprintf(message, MISC_LENGTH, "%d problematic or duplicated section%c", + ret, + ret > 1 ? 's' : ' '); } - config = (struct configuration*)shmem; + errx(1, "%s (file <%s>)", message, configuration_path); } - else + + memcpy(&config->configuration_path[0], configuration_path, MIN(strlen(configuration_path), MAX_PATH - 1)); + + if (pgagroal_vault_validate_configuration(shmem)) { - ret = pgagroal_read_configuration(shmem, PGAGROAL_DEFAULT_CONF_FILE, false); - if (ret != PGAGROAL_CONFIGURATION_STATUS_OK) - { - if (!remote_connection) - { - errx(1, "Host (-h) and port (-p) must be specified to connect to the remote host"); - } - } - else - { - configuration_path = PGAGROAL_DEFAULT_CONF_FILE; + errx(1, "Invalid VAULT configuration"); + } - if (logfile) - { - config = (struct configuration*)shmem; + config = (struct vault_configuration*)shmem; - config->log_type = PGAGROAL_LOGGING_TYPE_FILE; - memset(&config->log_path[0], 0, MISC_LENGTH); - memcpy(&config->log_path[0], logfile, MIN(MISC_LENGTH - 1, strlen(logfile))); - } + memcpy(&config->vault_server.password, admin_password, strlen(admin_password)); - if (pgagroal_start_logging()) - { - errx(1, "Cannot start the logging subsystem"); - } + // -- Bind & Listen at the given hostname and port -- - config = (struct configuration*)shmem; - } + if(pgagroal_vault_bind(config->host, config->port, &server_fd)){ + errx(1, "vault-pgagroal: Could not bind to %s:%d", config->host, config->port); } - // sock - int reuse = 1; - initialize_ssl(); - ssl_ctx = create_ssl_context(); - configure_ssl_context(ssl_ctx); + // -- Initialize the watcher and start loop -- + main_loop = ev_default_loop(0); - client_socket = socket(AF_INET, SOCK_STREAM, 0); - if (client_socket == -1) + if (!main_loop) { - perror("Socket creation error"); - exit(EXIT_FAILURE); + errx(1, "vault-pgagroal: No loop implementation"); } - setsockopt(client_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - - struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; - serverAddress.sin_port = htons(6789); - serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); + ev_signal_init((struct ev_signal*)&signal_watcher[0], shutdown_cb, SIGTERM); - if (connect(client_socket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) == -1) + for (int i = 0; i < 1; i++) { - perror("Connection error"); - close(client_socket); - exit(EXIT_FAILURE); + signal_watcher[i].slot = -1; + ev_signal_start(main_loop, (struct ev_signal*)&signal_watcher[i]); } - pid_t http_pid, https_pid; + start_vault_io(); - http_pid = fork(); - if (http_pid == 0) + printf("pgagroal-vault: started on %s:%d\n", + config->host, + config->port); + + while (keep_running) { - // This is the HTTP child process - http_thread_func(NULL); - exit(0); // Terminate child process + ev_loop(main_loop, 0); } - else if (http_pid < 0) + + // -- Free all memory -- +done: + 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)) { - // Handle fork failure - perror("Failed to fork HTTP process"); - exit(EXIT_FAILURE); + printf("pgagroal-vault: Couldn't connect to %s:%d\n", config->vault_server.host, config->vault_server.port); + pgagroal_disconnect(*client_socket); + return 1; } - https_pid = fork(); - if (https_pid == 0) + printf("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("Please provide a password for (%s)\n", username); + return 1; + } + + for (int i = 0; i < strlen(password); i++) { - // This is the HTTPS child process - https_thread_func(NULL); - exit(0); // Terminate child process + if ((unsigned char)(*(password + i)) & 0x80) + { + + warnx("Bad credentials for %s\n", username); + return 1; + } } - else if (https_pid < 0) + + /* Authenticate */ + if (pgagroal_remote_management_scram_sha256(username, password, *client_socket, &s_ssl) != AUTH_SUCCESS) { - // Handle fork failure - perror("Failed to fork HTTPS process"); - exit(EXIT_FAILURE); + printf("pgagroal-vault: Bad credentials for %s\n", username); + pgagroal_disconnect(*client_socket); + return 1; } - // Parent process waits for both HTTP and HTTPS child processes - int status; - waitpid(http_pid, &status, 0); - waitpid(https_pid, &status, 0); - SSL_CTX_free(ssl_ctx); return 0; } + +static void +shutdown_cb(struct ev_loop* loop, ev_signal* w, int revents) +{ + printf("vault-pgagroal: 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("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, "vault-pgagroal: 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("Cannot create process\n"); + } + else if(pid == 0) + { + char* addr = calloc(1, strlen(address) + 1); + if (addr == NULL) + { + printf("Cannot allocate memory for client address\n"); + return; + } + memcpy(addr, address, strlen(address)); + + ev_loop_fork(loop); + shutdown_vault_io(); + + // send(client_fd, buffer, 1024, 0); + 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