pax_global_header00006660000000000000000000000064145656523020014522gustar00rootroot0000000000000052 comment=1dd4d1238a0cbb25298c9e392561f081e78d793f pgagroal-1.6.0/000077500000000000000000000000001456565230200133225ustar00rootroot00000000000000pgagroal-1.6.0/.github/000077500000000000000000000000001456565230200146625ustar00rootroot00000000000000pgagroal-1.6.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001456565230200170455ustar00rootroot00000000000000pgagroal-1.6.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000016361456565230200215450ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior. **Version** What is the version of pgagroal ? **PostgreSQL** What is the version of PostgreSQL ? **libev** What is the version of libev ? **OpenSSL** What is the version of OpenSSL ? **Access method** Which security access method is used (trust, password, md5, scram-sha-256) ? **OS** Which Operating System (OS) is used ? **ulimit** What is the output from `ulimit -a` ? **Configuration** Can you provide the configuration pgagroal ? * pgagroal.conf * pgagroal_hba.conf * pgagroal_databases.conf * pgagroal_users.conf **Debug logs** Can you provide any debug logs (`log_level = debug5`) of the issue ? **Tip** Use \`\`\` before and after the text to keep the output as is. pgagroal-1.6.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006701456565230200225750ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: feature assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional information** Any additional information you can provide, like an overall design description pgagroal-1.6.0/.github/workflows/000077500000000000000000000000001456565230200167175ustar00rootroot00000000000000pgagroal-1.6.0/.github/workflows/ci.yml000066400000000000000000000057021456565230200200410ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master jobs: build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Update system 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 run: sudo apt install -y python3-docutils - name: Install clang run: sudo apt install -y clang - name: GCC/mkdir run: mkdir build working-directory: /home/runner/work/pgagroal/pgagroal/ - name: GCC/cmake run: export CC=/usr/bin/gcc && cmake -DCMAKE_BUILD_TYPE=Debug .. working-directory: /home/runner/work/pgagroal/pgagroal/build/ - name: GCC/make run: make working-directory: /home/runner/work/pgagroal/pgagroal/build/ - name: rm -Rf run: rm -Rf build/ working-directory: /home/runner/work/pgagroal/pgagroal/ - name: CLANG/mkdir run: mkdir build working-directory: /home/runner/work/pgagroal/pgagroal/ - name: CLANG/cmake run: export CC=/usr/bin/clang && cmake -DCMAKE_BUILD_TYPE=Debug .. working-directory: /home/runner/work/pgagroal/pgagroal/build/ - name: CLANG/make run: make working-directory: /home/runner/work/pgagroal/pgagroal/build/ build-macos: runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Install Homebrew run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - name: Update system run: brew update - name: Install openssl 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 run: brew install llvm - name: GCC/mkdir run: mkdir build working-directory: /Users/runner/work/pgagroal/pgagroal/ - name: GCC/cmake run: export CC=/usr/bin/gcc && export OPENSSL_ROOT_DIR=`brew --prefix openssl` && cmake -DCMAKE_BUILD_TYPE=Debug .. working-directory: /Users/runner/work/pgagroal/pgagroal/build/ - name: GCC/make run: make working-directory: /Users/runner/work/pgagroal/pgagroal/build/ - name: rm -Rf run: rm -Rf build/ working-directory: /Users/runner/work/pgagroal/pgagroal/ - name: CLANG/mkdir run: mkdir build working-directory: /Users/runner/work/pgagroal/pgagroal/ - name: CLANG/cmake run: export CC=/usr/bin/clang && export OPENSSL_ROOT_DIR=`brew --prefix openssl` && cmake -DCMAKE_BUILD_TYPE=Debug .. working-directory: /Users/runner/work/pgagroal/pgagroal/build/ - name: CLANG/make run: make working-directory: /Users/runner/work/pgagroal/pgagroal/build/ pgagroal-1.6.0/.gitignore000066400000000000000000000000371456565230200153120ustar00rootroot00000000000000.bundle/ _site/ build/ vendor/ pgagroal-1.6.0/AUTHORS000066400000000000000000000006011456565230200143670ustar00rootroot00000000000000pgagroal was created by the following authors: Jesper Pedersen David Fetter Will Leinweber Junduo Dong Luca Ferrari Nikita Bugrovsky Lawrence Wu Yongting You <2010youy01@gmail.com> Ashutosh Sharma pgagroal-1.6.0/CMakeLists.txt000066400000000000000000000062611456565230200160670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14.0) set(VERSION_MAJOR "1") set(VERSION_MINOR "6") set(VERSION_PATCH "0") set(VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) # # Avoid source tree pollution # set(CMAKE_DISABLE_SOURCE_CHANGES ON) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) If(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-source builds are not permitted. Make a separate folder for building:\nmkdir build; cd build; cmake ..\nBefore that, remove the files already created:\nrm -rf CMakeCache.txt CMakeFiles") endif(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) project(pgagroal VERSION ${VERSION_STRING} LANGUAGES C) set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH}) set(CPACK_SOURCE_GENERATOR "TGZ") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") set(CPACK_SOURCE_IGNORE_FILES "/build/;/.git/;/.github/;/*.patch;/.bundle/;/_site/;/vendor/;~$;${CPACK_SOURCE_IGNORE_FILES}") include(CPack) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) message(STATUS "pgagroal ${VERSION_STRING}") include(CheckCCompilerFlag) include(CheckCSourceCompiles) include(CheckLinkerFlag) include(FindPackageHandleStandardArgs) include(GNUInstallDirs) if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: Debug Release Performance" FORCE) endif () message(STATUS "Build type is ${CMAKE_BUILD_TYPE}") message(STATUS "System is ${CMAKE_SYSTEM_NAME}") set(SUPPORTED_COMPILERS "GNU" "Clang" "AppleClang") # Check for a supported compiler if (NOT CMAKE_C_COMPILER_ID IN_LIST SUPPORTED_COMPILERS) message(FATAL_ERROR "Unsupported compiler ${CMAKE_C_COMPILER_ID}. Supported compilers are: ${SUPPORTED_COMPILERS}") endif () CHECK_C_COMPILER_FLAG("-std=c17" COMPILER_SUPPORTS_C17) if(NOT COMPILER_SUPPORTS_C17) message(FATAL_ERROR "The compiler ${CMAKE_C_COMPILER} has no C17 support. Please use a different C compiler.") endif() find_package(Libev 4.11) if (LIBEV_FOUND) message(STATUS "libev found") else () message(FATAL_ERROR "libev needed") endif() find_package(OpenSSL) if (OPENSSL_FOUND) message(STATUS "OpenSSL found") else () message(FATAL_ERROR "OpenSSL needed") endif() find_package(Rst2man) if (RST2MAN_FOUND) message(STATUS "rst2man found") 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) message(STATUS "libatomic found") else () message(FATAL_ERROR "libatomic needed") endif() find_package(Systemd) if (SYSTEMD_FOUND) message(STATUS "systemd found") else () message(FATAL_ERROR "systemd needed") endif() endif() file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/src/") add_subdirectory(doc) add_subdirectory(src) pgagroal-1.6.0/CODE_OF_CONDUCT.md000066400000000000000000000060351456565230200161250ustar00rootroot00000000000000## pgagroal Community Code of Conduct ### Contributor Code of Conduct As contributors and maintainers of the projects under the [pgagroal](https://github.com/agroal/pgagroal) repository, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities to any of the projects under the pgagroal umbrella. We are committed to making participation in these projects a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery. * Personal attacks. * Trolling or insulting/derogatory comments. * Public or private harassment. * Publishing other's private information, such as physical or electronic addresses, without explicit permission. * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing their project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team or teams if they are in multiple 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 . ### pgagroal Events Code of Conduct pgagroal events are working conferences intended for professional networking and collaboration in the pgagroal community. Attendees are expected to behave according to professional standards and in accordance with their employer's policies on appropriate workplace behavior. While at pgagroal events or related social networking opportunities, attendees should not engage in discriminatory or offensive speech or actions regarding gender, sexuality, race, or religion. Speakers should be especially aware of these concerns. The maintainers of the pgagroal project do not condone any statements by speakers contrary to these standards. The maintainers of the pgagroal project reserves the right to deny entrance and/or eject from an event (without refund) any individual found to be engaging in discriminatory or offensive speech or actions. Please bring any concerns to the immediate attention of the pgagroal maintainers. This Code of Conduct was adapted from the [CNCF Community Code of Conduct v1.0](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). pgagroal-1.6.0/CONTRIBUTING.md000066400000000000000000000075021456565230200155570ustar00rootroot00000000000000# Contributing guide **Want to contribute? Great!** All contributions are more than welcome ! This includes bug reports, bug fixes, enhancements, features, questions, ideas, and documentation. This document will hopefully help you contribute to pgagroal. * [Legal](#legal) * [Reporting an issue](#reporting-an-issue) * [Setup your build environment](#setup-your-build-environment) * [Building the master branch](#building-the-master-branch) * [Before you contribute](#before-you-contribute) * [Code reviews](#code-reviews) * [Coding Guidelines](#coding-guidelines) * [Discuss a Feature](#discuss-a-feature) * [Development](#development) * [Code Style](#code-style) ## Legal All contributions to pgagroal are licensed under the [The 3-Clause BSD License](https://opensource.org/licenses/BSD-3-Clause). ## Reporting an issue This project uses GitHub issues to manage the issues. Open an issue directly in GitHub. If you believe you found a bug, and it's likely possible, please indicate a way to reproduce it, what you are seeing and what you would expect to see. Don't forget to indicate your pgagroal version. ## Setup your build environment You can use the follow command, if you are using a [Fedora](https://getfedora.org/) based platform: ``` dnf install git gcc cmake make libev libev-devel openssl openssl-devel systemd systemd-devel python3-docutils ``` in order to get the necessary dependencies. ## Building the master branch To build the `master` branch: ``` git clone https://github.com/agroal/pgagroal.git cd pgagroal mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Debug .. make cd src cp ../../doc/etc/*.conf . ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf ``` and you will have a running instance. ## Before you contribute To contribute, use GitHub Pull Requests, from your **own** fork. Also, make sure you have set up your Git authorship correctly: ``` git config --global user.name "Your Full Name" git config --global user.email your.email@example.com ``` We use this information to acknowledge your contributions in release announcements. ## Code reviews GitHub pull requests can be reviewed by all such that input can be given to the author(s). See [GitHub Pull Request Review Process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews) for more information. ## Coding Guidelines * Discuss the feature * Do development + Follow the code style * Commits should be atomic and semantic. Therefore, squash your pull request before submission and keep it rebased until merged + If your feature has independent parts submit those as separate pull requests ## Discuss a Feature You can discuss bug reports, enhancements and features in our [forum](https://github.com/agroal/pgagroal/discussions). Once there is an agreement on the development plan you can open an issue that will used for reference in the pull request. ## Development You can follow this workflow for your development. Add your repository ``` git clone git@github.com:yourname/pgagroal.git cd pgagroal git remote add upstream https://github.com/agroal/pgagroal.git ``` Create a work branch ``` git checkout -b mywork master ``` During development ``` git commit -a -m "[#issue] My feature" git push -f origin mywork ``` If you have more commits then squash them ``` git rebase -i HEAD~2 git push -f origin mywork ``` If the `master` branch changes then ``` git fetch upstream git rebase -i upstream/master git push -f origin mywork ``` as all pull requests should be squashed and rebased. In your first pull request you need to add yourself to the `AUTHORS` file. ## Code Style Please, follow the coding style of the project. You can use the [uncrustify](http://uncrustify.sourceforge.net/) tool to help with the formatting, by running ``` ./uncrustify.sh ``` and verify the changes. pgagroal-1.6.0/LICENSE000066400000000000000000000027001456565230200143260ustar00rootroot00000000000000 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-1.6.0/README.md000066400000000000000000000120231456565230200145770ustar00rootroot00000000000000# pgagroal `pgagroal` is a high-performance protocol-native connection pool for [PostgreSQL](https://www.postgresql.org). Pronounced: p-g-a-gro-al, named after [A](https://www.visitportugal.com/en/content/praia-fluvial-do-agroal)[gro](https://www.google.com/maps/place/Agroal,+Portugal/@39.6775431,-8.4486056,14z/)[al](https://www.infatima.pt/en/nearby/sun-sea/fluvial-beaches/agroal/) in Portugal. ## Features * High performance * Connection pool * Limit connections for users and databases * Prefill support * Remove idle connections * Perform connection validation * Enable / disable database access * Graceful / fast shutdown * Prometheus support * Grafana 8 dashboard * Remote management * Authentication query support * Failover support * Transport Layer Security (TLS) v1.2+ support * Daemon mode * User vault See [Getting Started](./doc/GETTING_STARTED.md) on how to get started with `pgagroal`. See [Configuration](./doc/CONFIGURATION.md) on how to configure `pgagroal`. See [Performance](./doc/PERFORMANCE.md) for a performance run. ## Overview `pgagroal` makes use of * Process model * Shared memory model across processes * [libev](http://software.schmorp.de/pkg/libev.html) for fast network interactions * [Atomic operations](https://en.cppreference.com/w/c/atomic) are used to keep track of state * The [PostgreSQL](https://www.postgresql.org) native protocol [v3](https://www.postgresql.org/docs/11/protocol-message-formats.html) for its communication `pgagroal` will work with any [PostgreSQL](https://www.postgresql.org) compliant driver, for example [pgjdbc](https://jdbc.postgresql.org/), [Npgsql](https://www.npgsql.org/) and [pq](https://github.com/lib/pq). See [Architecture](./doc/ARCHITECTURE.md) for the architecture of `pgagroal`. ## Tested platforms * [Fedora](https://getfedora.org/) 28+ * [RHEL](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux) 7.x with [EPEL](https://access.redhat.com/solutions/3358) and [DevTools](https://developers.redhat.com/products/developertoolset/overview) 8+ * [RHEL](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux) 8.x with [AppStream](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/installing_managing_and_removing_user-space_components/using-appstream_using-appstream) * [FreeBSD](https://www.freebsd.org/) * [OpenBSD](http://www.openbsd.org/) ## Compiling the source `pgagroal` requires * [gcc 8+](https://gcc.gnu.org) (C17) * [cmake](https://cmake.org) * [make](https://www.gnu.org/software/make/) * [libev](http://software.schmorp.de/pkg/libev.html) * [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 config-manager --set-enabled crb ``` Alternatively to GCC, [clang 8+](https://clang.llvm.org/) can be used. ### Release build The following commands will install `pgagroal` in the `/usr/local` hierarchy and run the default configuration. ```sh git clone https://github.com/agroal/pgagroal.git cd pgagroal mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. make sudo make install /usr/local/bin/pgagroal -c /usr/local/etc/pgagroal/pgagroal.conf -a /usr/local/etc/pgagroal/pgagroal_hba.conf ``` See [RPM](./doc/RPM.md) for how to build a RPM of `pgagroal`. ### Debug build The following commands will create a `DEBUG` version of `pgagroal`. ```sh git clone https://github.com/agroal/pgagroal.git cd pgagroal mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Debug .. make cd src cp ../../doc/etc/*.conf . ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf ``` Remember to set the `log_level` configuration option to `debug5`. ## Contributing Contributions to `pgagroal` are managed on [GitHub.com](https://github.com/agroal/pgagroal/) * [Ask a question](https://github.com/agroal/pgagroal/discussions) * [Raise an issue](https://github.com/agroal/pgagroal/issues) * [Feature request](https://github.com/agroal/pgagroal/issues) * [Code submission](https://github.com/agroal/pgagroal/pulls) Contributions are most welcome ! Please, consult our [Code of Conduct](./CODE_OF_CONDUCT.md) policies for interacting in our community. Consider giving the project a [star](https://github.com/agroal/pgagroal/stargazers) on [GitHub](https://github.com/agroal/pgagroal/) if you find it useful. And, feel free to follow the project on [Twitter](https://twitter.com/pgagroal/) as well. ## License [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) pgagroal-1.6.0/cmake/000077500000000000000000000000001456565230200144025ustar00rootroot00000000000000pgagroal-1.6.0/cmake/FindLibatomic.cmake000066400000000000000000000007021456565230200201070ustar00rootroot00000000000000# - Try to find libatomic # Once done this will define # LIBATOMIC_FOUND - System has libatomic # LIBATOMIC_LIBRARY - The library needed to use libatomic FIND_LIBRARY(LIBATOMIC_LIBRARY NAMES atomic atomic.so.1 libatomic.so.1 HINTS /usr/local/lib64 /usr/local/lib /opt/local/lib64 /opt/local/lib /usr/lib64 /usr/lib /lib64 /lib ) IF (LIBATOMIC_LIBRARY) SET(LIBATOMIC_FOUND TRUE) ELSE () SET(LIBATOMIC_FOUND FALSE) ENDIF () pgagroal-1.6.0/cmake/FindLibev.cmake000066400000000000000000000025431456565230200172520ustar00rootroot00000000000000# - Try to find libev # Once done this will define # LIBEV_FOUND - System has libev # LIBEV_INCLUDE_DIRS - The libev include directories # LIBEV_LIBRARIES - The libraries needed to use libev find_path(LIBEV_INCLUDE_DIR NAMES ev.h ) find_library(LIBEV_LIBRARY NAMES ev ) if(LIBEV_INCLUDE_DIR) file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") unset(LIBEV_VERSION_MINOR) unset(LIBEV_VERSION_MAJOR) endif() include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE # if all listed variables are TRUE and the requested version matches. find_package_handle_standard_args(Libev REQUIRED_VARS LIBEV_LIBRARY LIBEV_INCLUDE_DIR VERSION_VAR LIBEV_VERSION) if(LIBEV_FOUND) set(LIBEV_LIBRARIES ${LIBEV_LIBRARY}) set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) endif() mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) pgagroal-1.6.0/cmake/FindRst2man.cmake000066400000000000000000000017671456565230200175460ustar00rootroot00000000000000# RST2MAN_FOUND - true if the program was found # RST2MAN_VERSION - version of rst2man # RST2MAN_EXECUTABLE - path to the rst2man program find_program(RST2MAN_EXECUTABLE NAMES rst2man rst2man.py rst2man-3 rst2man-3.py DOC "The Python Docutils generator of Unix Manpages from reStructuredText" ) if (RST2MAN_EXECUTABLE) # Get the version string execute_process( COMMAND ${RST2MAN_EXECUTABLE} --version OUTPUT_VARIABLE rst2man_version_str ) # Expected format: rst2man (Docutils 0.13.1 [release], Python 2.7.15, on linux2) string(REGEX REPLACE "^rst2man[\t ]+\\(Docutils[\t ]+([^\t ]*).*" "\\1" RST2MAN_VERSION "${rst2man_version_str}") unset(rst2man_version_str) endif() # handle the QUIETLY and REQUIRED arguments and set RST2MAN_FOUND to TRUE # if all listed variables are set include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Rst2man REQUIRED_VARS RST2MAN_EXECUTABLE VERSION_VAR RST2MAN_VERSION ) mark_as_advanced(RST2MAN_EXECUTABLE RST2MAN_VERSION) pgagroal-1.6.0/cmake/FindSystemd.cmake000066400000000000000000000015611456565230200176400ustar00rootroot00000000000000# - Try to find systemd # Once done this will define # SYSTEMD_FOUND - System has systemd # SYSTEMD_INCLUDE_DIRS - The systemd include directories # SYSTEMD_LIBRARIES - The libraries needed to use systemd find_path(SYSTEMD_INCLUDE_DIR NAMES systemd/sd-daemon.h ) find_library(SYSTEMD_LIBRARY NAMES systemd ) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set SYSTEMD_FOUND to TRUE # if all listed variables are TRUE and the requested version matches. find_package_handle_standard_args(Systemd REQUIRED_VARS SYSTEMD_LIBRARY SYSTEMD_INCLUDE_DIR VERSION_VAR SYSTEMD_VERSION) if(SYSTEMD_FOUND) set(SYSTEMD_LIBRARIES ${SYSTEMD_LIBRARY}) set(SYSTEMD_INCLUDE_DIRS ${SYSTEMD_INCLUDE_DIR}) endif() mark_as_advanced(SYSTEMD_INCLUDE_DIR SYSTEMD_LIBRARY) pgagroal-1.6.0/cmake/FindcJSON.cmake000066400000000000000000000036061456565230200171260ustar00rootroot00000000000000# 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 ) pgagroal-1.6.0/contrib/000077500000000000000000000000001456565230200147625ustar00rootroot00000000000000pgagroal-1.6.0/contrib/grafana/000077500000000000000000000000001456565230200163615ustar00rootroot00000000000000pgagroal-1.6.0/contrib/grafana/README.md000066400000000000000000000013371456565230200176440ustar00rootroot00000000000000# Grafana dashboard for pgagroal ## Getting Started ### Step1: Configure the Prometheus 1. Open the prometheus metric port like `8000` in `pgagroal.conf`. 2. Add the pgagroal instance in Prometheus configuration file `prometheus.yml` like follows: ``` ... scrape_configs: - job_name: 'pgagroal' static_configs: - targets: ['localhost:8000'] ... ``` ### Step2: Start the Grafana 1. Integrate the Prometheus instance as a data source into Grafana. 2. Open Grafana web page, click + -> Import. 3. Upload JSON file `contrib/grafana/dashboard.json` with Upload JSON file, then change the options if necessary. 4. Click Import. Let's start using it!pgagroal-1.6.0/contrib/grafana/dashboard.json000066400000000000000000001157701456565230200212160ustar00rootroot00000000000000{ "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 0, "id": 1, "links": [], "panels": [ { "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 28, "title": "Overview", "type": "row" }, { "datasource": null, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [ { "options": { "0": { "index": 0, "text": "Performance" }, "1": { "index": 1, "text": "Session" }, "2": { "index": 2, "text": "Transaction" } }, "type": "value" } ], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 }, "id": 54, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "text": {}, "textMode": "auto" }, "pluginVersion": "8.0.2", "targets": [ { "exemplar": true, "expr": "pgagroal_pipeline_mode", "interval": "", "legendFormat": "", "refId": "A" } ], "title": "Pipeline mode", "type": "stat" }, { "datasource": null, "description": "Active connections", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "max": 1, "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "#EAB839", "value": 0.8 }, { "color": "dark-red", "value": 0.9 } ] }, "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 8, "w": 5, "x": 6, "y": 1 }, "id": 30, "options": { "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true, "text": {} }, "pluginVersion": "8.0.2", "targets": [ { "exemplar": true, "expr": "pgagroal_active_connections/pgagroal_max_connections", "interval": "", "legendFormat": "", "refId": "active connections" } ], "title": "Active connections", "type": "gauge" }, { "datasource": null, "description": "Session time", "fieldConfig": { "defaults": { "color": { "fixedColor": "green", "mode": "fixed" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 13, "x": 11, "y": 1 }, "id": 32, "options": { "displayMode": "gradient", "orientation": "vertical", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showUnfilled": false, "text": {} }, "pluginVersion": "8.0.2", "targets": [ { "exemplar": true, "expr": "pgagroal_session_time_seconds_bucket", "format": "heatmap", "interval": "", "legendFormat": "{{le}}", "refId": "pgagroal_session_time_seconds_bucket" } ], "title": "Session time", "type": "bargauge" }, { "datasource": null, "description": "pgagroal state", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [ { "options": { "0": { "color": "red", "index": 1, "text": "DOWN" }, "1": { "color": "green", "index": 0, "text": "UP" }, "2": { "color": "yellow", "index": 2, "text": "GRACEFUL" } }, "type": "value" } ], "max": 1, "min": 0, "noValue": "UNKNOWN", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 0, "y": 5 }, "id": 2, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "text": {}, "textMode": "auto" }, "pluginVersion": "8.0.2", "targets": [ { "exemplar": true, "expr": "pgagroal_state", "interval": "", "legendFormat": "state", "refId": "pgagroal_state" } ], "title": "State", "type": "stat" }, { "datasource": null, "description": "Failed servers", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 3, "y": 5 }, "id": 20, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "text": {}, "textMode": "auto" }, "pluginVersion": "8.0.2", "targets": [ { "exemplar": true, "expr": "pgagroal_failed_servers", "interval": "", "legendFormat": "", "refId": "pgagroal_failed_servers" } ], "title": "Failed servers", "type": "stat" }, { "datasource": null, "description": "Connection state", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineStyle": { "fill": "solid" }, "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 9 }, "id": 26, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_active_connections", "interval": "", "legendFormat": "active connections", "refId": "pgagroal_active_connections" }, { "exemplar": true, "expr": "pgagroal_total_connections", "hide": false, "interval": "", "legendFormat": "total connections", "refId": "pgagroal_total_connections" }, { "exemplar": true, "expr": "pgagroal_max_connections", "hide": false, "interval": "", "legendFormat": "max connections", "refId": "pgagroal_max_connections" } ], "title": "Connection state", "type": "timeseries" }, { "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 17 }, "id": 14, "title": "Pool", "type": "row" }, { "datasource": null, "description": "Authentication details", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 18 }, "id": 16, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_auth_user_success", "interval": "", "legendFormat": "success", "refId": "pgagroal_auth_user_success" }, { "exemplar": true, "expr": "pgagroal_auth_user_error", "hide": false, "interval": "", "legendFormat": "error", "refId": "pgagroal_auth_user_error" }, { "exemplar": true, "expr": "pgagroal_auth_user_bad_password", "hide": false, "interval": "", "legendFormat": "bad password", "refId": "pgagroal_auth_user_bad_password" } ], "title": "Authentication", "type": "timeseries" }, { "datasource": null, "description": "The waiting time of clients", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 11, "y": 18 }, "id": 40, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_wait_time", "interval": "", "legendFormat": "", "refId": "pgagroal_wait_time" } ], "title": "Client waiting time", "type": "timeseries" }, { "datasource": null, "description": "Only session and transaction modes are supported", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "reqps" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 26 }, "id": 42, "interval": "1s", "maxDataPoints": 20000, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "irate(pgagroal_query_count[1m])", "interval": "", "legendFormat": "", "refId": "query rate" } ], "title": "Query rate", "type": "timeseries" }, { "datasource": null, "description": "Only session and transaction modes are supported", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "reqps" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 11, "y": 26 }, "id": 44, "interval": "1s", "maxDataPoints": 20000, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "irate(pgagroal_tx_count[1m])", "interval": "", "legendFormat": "", "refId": "A" } ], "title": "Transaction rate", "type": "timeseries" }, { "datasource": null, "description": "Only session and transaction modes are supported", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "reqps" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 34 }, "id": 56, "interval": "1s", "maxDataPoints": 20000, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "sum(irate(pgagroal_connection_query_count{database!=\"\"}[5s])) by (database)", "interval": "", "legendFormat": "", "refId": "A" } ], "title": "Query rate per database", "type": "timeseries" }, { "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 42 }, "id": 12, "title": "Internal", "type": "row" }, { "datasource": null, "description": "Only session and transaction modes are supported", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "Bps" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 43 }, "id": 46, "interval": "1s", "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "irate(pgagroal_network_sent[1m])", "instant": false, "interval": "", "legendFormat": "", "refId": "A" } ], "title": "Send traffic", "type": "timeseries" }, { "datasource": null, "description": "Only session and transaction modes are supported", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "Bps" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 11, "y": 43 }, "id": 48, "interval": "1s", "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "irate(pgagroal_network_received[1m])", "interval": "", "legendFormat": "", "refId": "A" } ], "title": "Receive traffic", "type": "timeseries" }, { "datasource": null, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 51 }, "id": 50, "interval": "1s", "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_client_sockets", "interval": "", "legendFormat": "client sockets", "refId": "client sockets" }, { "exemplar": true, "expr": "pgagroal_total_connections", "hide": false, "interval": "", "legendFormat": "server sockets", "refId": "server sockets" }, { "exemplar": true, "expr": "pgagroal_self_sockets", "hide": false, "interval": "", "legendFormat": "self sockets", "refId": "self sockets" } ], "title": "Sockets used", "type": "timeseries" }, { "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 59 }, "id": 10, "title": "Connection", "type": "row" }, { "datasource": null, "description": "Connection events", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 60 }, "id": 24, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_connection_error", "interval": "", "legendFormat": "error", "refId": "pgagroal_connection_error" }, { "exemplar": true, "expr": "pgagroal_connection_kill", "hide": false, "interval": "", "legendFormat": "kill", "refId": "pgagroal_connection_kill" }, { "exemplar": true, "expr": "pgagroal_connection_remove", "hide": false, "interval": "", "legendFormat": "remove", "refId": "pgagroal_connection_remove" }, { "exemplar": true, "expr": "pgagroal_connection_timeout", "hide": false, "interval": "", "legendFormat": "timeout", "refId": "pgagroal_connection_timeout" }, { "exemplar": true, "expr": "pgagroal_connection_return", "hide": false, "interval": "", "legendFormat": "return", "refId": "pgagroal_connection_return" }, { "exemplar": true, "expr": "pgagroal_connection_invalid", "hide": false, "interval": "", "legendFormat": "invalid", "refId": "pgagroal_connection_invalid" }, { "exemplar": true, "expr": "pgagroal_connection_get", "hide": false, "interval": "", "legendFormat": "get", "refId": "pgagroal_connection_get" }, { "exemplar": true, "expr": "pgagroal_connection_idletimeout", "hide": false, "interval": "", "legendFormat": "idletimeout", "refId": "pgagroal_connection_idletimeout" }, { "exemplar": true, "expr": "pgagroal_connection_flush", "hide": false, "interval": "", "legendFormat": "flush", "refId": "pgagroal_connection_flush" }, { "exemplar": true, "expr": "pgagroal_connection_success", "hide": false, "interval": "", "legendFormat": "success", "refId": "pgagroal_connection_success" } ], "title": "Connection events", "type": "timeseries" }, { "datasource": null, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 11, "y": 60 }, "id": 52, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "sum(pgagroal_connection) by (database) > 0", "interval": "", "legendFormat": "{{database}}", "refId": "A" } ], "title": "Connections per database", "type": "timeseries" }, { "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 68 }, "id": 8, "title": "Client", "type": "row" }, { "datasource": null, "description": "Client state", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 69 }, "id": 38, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "pgagroal_client_active", "interval": "", "legendFormat": "active clients", "refId": "pgagroal_client_active" }, { "exemplar": true, "expr": "pgagroal_client_wait", "hide": false, "interval": "", "legendFormat": "waiting clients", "refId": "pgagroal_client_wait" } ], "title": "Client state", "type": "timeseries" }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 77 }, "id": 6, "panels": [], "title": "Server", "type": "row" }, { "datasource": null, "description": "Server connections", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "decimals": 0, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null } ] }, "unit": "short" }, "overrides": [] }, "gridPos": { "h": 8, "w": 11, "x": 0, "y": 78 }, "id": 34, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "sum(pgagroal_connection) by (database) > 0", "interval": "", "legendFormat": "{{database}}", "refId": "pgagroal_connection per database" } ], "title": "Server connections", "type": "timeseries" }, { "datasource": null, "description": "Server errors", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 11, "y": 78 }, "id": 36, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } }, "targets": [ { "exemplar": true, "expr": "sum(pgagroal_server_error) by (name)", "interval": "", "legendFormat": "{{name}}", "refId": "pgagroal_server_error" } ], "title": "Server errors", "type": "timeseries" } ], "refresh": false, "schemaVersion": 30, "style": "dark", "tags": [], "templating": { "list": [] }, "time": { "from": "now-30m", "to": "now" }, "timepicker": {}, "timezone": "", "title": "pgagroal dashboard", "uid": "t_a1YcR7k", "version": 1 }pgagroal-1.6.0/contrib/shell_comp/000077500000000000000000000000001456565230200171075ustar00rootroot00000000000000pgagroal-1.6.0/contrib/shell_comp/pgagroal_comp.bash000066400000000000000000000034521456565230200225640ustar00rootroot00000000000000#/usr/bin/env bash # COMP_WORDS contains # at index 0 the executable name (pgagroal-cli) # at index 1 the command name (e.g., flush) # at index 2, if required, the subcommand name (e.g., all) pgagroal_cli_completions() { if [ "${#COMP_WORDS[@]}" == "2" ]; then # main completion: the user has specified nothing at all # or a single word, that is a command COMPREPLY=($(compgen -W "flush ping enable disable shutdown status switch-to conf clear" "${COMP_WORDS[1]}")) else # the user has specified something else # subcommand required? case ${COMP_WORDS[1]} in flush) COMPREPLY+=($(compgen -W "gracefully idle all" "${COMP_WORDS[2]}")) ;; shutdown) COMPREPLY+=($(compgen -W "gracefully immediate cancel" "${COMP_WORDS[2]}")) ;; clear) COMPREPLY+=($(compgen -W "server prometheus" "${COMP_WORDS[2]}")) ;; conf) COMPREPLY+=($(compgen -W "reload get set ls" "${COMP_WORDS[2]}")) ;; status) COMPREPLY+=($(compgen -W "details" "${COMP_WORDS[2]}")) esac fi } pgagroal_admin_completions() { if [ "${#COMP_WORDS[@]}" == "2" ]; then # main completion: the user has specified nothing at all # or a single word, that is a command COMPREPLY=($(compgen -W "master-key user" "${COMP_WORDS[1]}")) else # the user has specified something else # subcommand required? case ${COMP_WORDS[1]} in user) COMPREPLY+=($(compgen -W "add del edit ls" "${COMP_WORDS[2]}")) ;; esac fi } # install the completion functions complete -F pgagroal_cli_completions pgagroal-cli complete -F pgagroal_admin_completions pgagroal-admin pgagroal-1.6.0/contrib/shell_comp/pgagroal_comp.zsh000066400000000000000000000031721456565230200224520ustar00rootroot00000000000000#compdef _pgagroal_cli pgagroal-cli #compdef _pgagroal_admin pgagroal-admin function _pgagroal_cli() { local line _arguments -C \ "1: :(flush ping enable disable shutdown status switch-to conf clear)" \ "*::arg:->args" case $line[1] in flush) _pgagroal_cli_flush ;; shutdown) _pgagroal_cli_shutdown ;; clear) _pgagroal_cli_clear ;; conf) _pgagroal_cli_conf ;; status) _pgagroal_cli_status ;; esac } function _pgagroal_cli_flush() { local line _arguments -C \ "1: :(gracefully idle all)" \ "*::arg:->args" } function _pgagroal_cli_conf() { local line _arguments -C \ "1: :(reload get set ls)" \ "*::arg:->args" } function _pgagroal_cli_shutdown() { local line _arguments -C \ "1: :(gracefully immediate cancel)" \ "*::arg:->args" } function _pgagroal_cli_clear() { local line _arguments -C \ "1: :(server prometheus)" \ "*::arg:->args" } function _pgagroal_admin() { local line _arguments -C \ "1: :(master-key user)" \ "*::arg:->args" case $line[1] in user) _pgagroal_admin_user ;; esac } function _pgagroal_admin_user() { _arguments -C \ "1: :(add del edit ls)" \ "*::arg:->args" } function _pgagroal_cli_status() { local line _arguments -C \ "1: :(details)" \ "*::arg:->args" } pgagroal-1.6.0/contrib/valgrind/000077500000000000000000000000001456565230200165705ustar00rootroot00000000000000pgagroal-1.6.0/contrib/valgrind/README.md000066400000000000000000000015311456565230200200470ustar00rootroot00000000000000# 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 ``` pgagroal-1.6.0/contrib/valgrind/pgagroal.supp000066400000000000000000000103771456565230200213050ustar00rootroot00000000000000# # 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 }pgagroal-1.6.0/doc/000077500000000000000000000000001456565230200140675ustar00rootroot00000000000000pgagroal-1.6.0/doc/ADMIN.md000066400000000000000000000053031456565230200152420ustar00rootroot00000000000000# `pgagroal-admin` user guide `pgagroal-admin` is a command line interface to manage users known to the `pgagroal` connection pooler. The executable accepts a set of options, as well as a command to execute. If no command is provided, the program will show the help screen. The `pgagroal-admin` utility has the following synopsis: ``` pgagroal-admin [ OPTIONS ] [ COMMAND ] ``` ## Options Available options are the following ones: ``` -f, --file FILE Set the path to a user file -U, --user USER Set the user name -P, --password PASSWORD Set the password for the user -g, --generate Generate a password -l, --length Password length -V, --version Display version information -?, --help Display help ``` Options can be specified either in short or long form, in any position of the command line. The `-f` option is mandatory for every operation that involves user management. If no user file is specified, `pgagroal-admin` will silently use the default one (`pgagroal_users.conf`). ## Commands ### user The `user` command allows the management of the users known to the connection pooler. The command accepts the following subcommands: - `add` to add a new user to the system; - `del` to remove an existing user from the system; - `edit` to change the credentials of an existing user; - `ls` to list all known users within the system. The command will edit the `pgagroal_users.conf` file or any file specified by means of the `-f` option flag. Unless the command is run with the `-U` and/or `-P` flags, the execution will be interactive. Examples: ``` shell pgagroal-admin user add -U simon -P secret pgagroal-admin user del -U simon ``` ## master-key The `master-key` command allows the definition of a password to protect the vault of the users, that is the "container" for users' credentials. ## Deprecated commands The following commands have been deprecated and will be removed in later releases of `pgagroal`. For each command, this is the corresponding current mapping to the working command: - `add-user` is now `user add`; - `remove-user` is now `user del`; - `update-user` is now `user edit`; - `list-users` is now `user ls`. Whenever you use a deprecated command, the `pgagroal-admin` will print on standard error a warning message. If you don't want to get any warning about deprecated commands, you can redirect the `stderr` to `/dev/null` or any other location with: ``` pgagroal-admin user-add -U luca -P strongPassword 2>/dev/null ``` ## Shell completion There is a minimal shell completion support for `pgagroal-admin`. See the [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/main/doc/tutorial/01_install.md) for more details. pgagroal-1.6.0/doc/ARCHITECTURE.md000066400000000000000000000277251456565230200163100ustar00rootroot00000000000000# pgagroal architecture ## Overview `pgagroal` use a process model (`fork()`), where each process handles one connection to [PostgreSQL](https://www.postgresql.org). This was done such a potential crash on one connection won't take the entire pool down. The main process is defined in [main.c](../src/main.c). When a client connects it is processed in its own process, which is handle in [worker.h](../src/include/worker.h) ([worker.c](../src/libpgagroal/worker.c)). Once the client disconnects the connection is put back in the pool, and the child process is terminated. ## Shared memory A memory segment ([shmem.h](../src/include/shmem.h)) is shared among all processes which contains the `pgagroal` state containing the configuration of the pool, the list of servers and the state of each connection. The configuration of `pgagroal` (`struct configuration`), the configuration of the servers (`struct server`) and the state of each connection (`struct connection`) is initialized in this shared memory segment. These structs are all defined in [pgagroal.h](../src/include/pgagroal.h). The shared memory segment is created using the `mmap()` call. ## Atomic operations The [atomic operation library](https://en.cppreference.com/w/c/atomic) is used to define the state of each of the connection, and move them around in the connection state diagram. The state diagram has the follow states | State name | Description | |------------|-------------| | `STATE_NOTINIT` | The connection has not been initialized | | `STATE_INIT` | The connection is being initialized | | `STATE_FREE` | The connection is free | | `STATE_IN_USE` | The connection is in use | | `STATE_GRACEFULLY` | The connection will be killed upon return to the pool | | `STATE_FLUSH` | The connection is being flushed | | `STATE_IDLE_CHECK` | The connection is being idle timeout checked | | `STATE_MAX_CONNECTION_AGE` | The connection is being max connection age checked | | `STATE_VALIDATION` | The connection is being validated | | `STATE_REMOVE` | The connection is being removed | These state are defined in [pgagroal.h](../src/include/pgagroal.h). ## Pool The `pgagroal` pool API is defined in [pool.h](../src/include/pool.h) ([pool.c](../src/libpgagroal/pool.c)). This API defines the functionality of the pool such as getting a connection from the pool, and returning it. There is no ordering among processes, so a newly created process can obtain a connection before an older process. The pool operates on the `struct connection` data type defined in [pgagroal.h](../src/include/pgagroal.h). ## Network and messages All communication is abstracted using the `struct message` data type defined in [message.h](../src/include/message.h). Reading and writing messages are handled in the [message.h](../src/include/message.h) ([message.c](../src/libpgagroal/message.c)) files. Network operations are defined in [network.h](../src/include/network.h) ([network.c](../src/libpgagroal/network.c)). ## Memory Each process uses a fixed memory block for its network communication, which is allocated upon startup of the worker. That way we don't have to allocate memory for each network message, and more importantly free it after end of use. The memory interface is defined in [memory.h](../src/include/memory.h) ([memory.c](../src/libpgagroal/memory.c)). ## Management `pgagroal` has a management interface which serves two purposes. First, it defines the administrator abilities that can be performed on the pool when it is running. This include for example flushing the pool. The `pgagroal-cli` program is used for these operations ([cli.c](../src/cli.c)). Second, the interface is used internally to transfer the connection (socket descriptor) from the child process to the main `pgagroal` process after a new connection has been created. This is necessary since the socket descriptor needs to be available to subsequent client and hence processes. The management interface use Unix Domain Socket for communication. The management interface is defined in [management.h](../src/include/management.h). The management interface uses its own protocol which always consist of a header | Field | Type | Description | |------------|------|-------------| | `id` | Byte | The identifier of the message type | | `slot` | Int | The slot that the message is for | The rest of the message is depending on the message type. ### Remote management The remote management functionality uses the same protocol as the standard management method. However, before the management packet is sent the client has to authenticate using SCRAM-SHA-256 using the same message format that PostgreSQL uses, e.g. StartupMessage, AuthenticationSASL, AuthenticationSASLContinue, AuthenticationSASLFinal and AuthenticationOk. The SSLRequest message is supported. The remote management interface is defined in [remote.h](../src/include/remote.h) ([remote.c](../src/libpgagroal/remote.c)). ## libev usage [libev](http://software.schmorp.de/pkg/libev.html) is used to handle network interactions, which is "activated" upon an `EV_READ` event. Each process has its own event loop, such that the process only gets notified when data related only to that process is ready. The main loop handles the system wide "services" such as idle timeout checks and so on. ## Pipeline `pgagroal` has the concept of a pipeline that defines how communication is routed from the client through `pgagroal` to [PostgreSQL](https://www.postgresql.org). Likewise in the other direction. A pipeline is defined by ```C struct pipeline { initialize initialize; start start; callback client; callback server; stop stop; destroy destroy; periodic periodic; }; ``` in [pipeline.h](../src/include/pipeline.h). The functions in the pipeline are defined as | Function | Description | |----------|-------------| | `initialize` | Global initialization of the pipeline, may return a pointer to a shared memory segment | | `start` | Called when the pipeline instance is started | | `client` | Client to `pgagroal` communication | | `server` | [PostgreSQL](https://www.postgresql.org) to `pgagroal` communication | | `stop` | Called when the pipeline instance is stopped | | `destroy` | Global destruction of the pipeline | | `periodic` | Called periodic | The functions `start`, `client`, `server` and `stop` has access to the following information ```C struct worker_io { struct ev_io io; /* The libev base type */ int client_fd; /* The client descriptor */ int server_fd; /* The server descriptor */ int slot; /* The slot */ SSL* client_ssl; /* The client SSL context */ SSL* server_ssl; /* The server SSL context */ }; ``` defined in [worker.h](../src/include/worker.h). ### Performance pipeline One of the goals for `pgagroal` is performance, so the performance pipeline will only look for the [`Terminate`](https://www.postgresql.org/docs/11/protocol-message-formats.html) message from the client and act on that. Likewise the performance pipeline will only look for `FATAL` errors from the server. This makes the pipeline very fast, since there is a minimum overhead in the interaction. The pipeline is defined in [pipeline_perf.c](../src/libpgagroal/pipeline_perf.c) in the functions | Function | Description | |----------|-------------| | `performance_initialize` | Nothing | | `performance_start` | Nothing | | `performance_client` | Client to `pgagroal` communication | | `performance_server` | [PostgreSQL](https://www.postgresql.org) to `pgagroal` communication | | `performance_stop` | Nothing | | `performance_destroy` | Nothing | | `performance_periodic` | Nothing | ### Session pipeline The session pipeline works like the performance pipeline with the exception that it checks if a Transport Layer Security (TLS) transport should be used. The pipeline is defined in [pipeline_session.c](../src/libpgagroal/pipeline_session.c) in the functions | Function | Description | |----------|-------------| | `session_initialize` | Initialize memory segment if disconnect_client is active | | `session_start` | Prepares the client segment if disconnect_client is active | | `session_client` | Client to `pgagroal` communication | | `session_server` | [PostgreSQL](https://www.postgresql.org) to `pgagroal` communication | | `session_stop` | Updates the client segment if disconnect_client is active | | `session_destroy` | Destroys memory segment if initialized | | `session_periodic` | Checks if clients should be disconnected | ### Transaction pipeline The transaction pipeline will return the connection to the server after each transaction. The pipeline supports Transport Layer Security (TLS). The pipeline uses the [ReadyForQuery](https://www.postgresql.org/docs/current/protocol-message-formats.html) message to check the status of the transaction, and therefore needs to maintain track of the message headers. The pipeline has a management interface in order to receive the socket descriptors from the parent process when a new connection is added to the pool. The pool will retry if the client in question doesn't consider the socket descriptor valid. The pipeline is defined in [pipeline_transaction.c](../src/libpgagroal/pipeline_transaction.c) in the functions | Function | Description | |----------|-------------| | `transaction_initialize` | Nothing | | `transaction_start` | Setup process variables and returns the connection to the pool | | `transaction_client` | Client to `pgagroal` communication. Obtain connection if needed | | `transaction_server` | [PostgreSQL](https://www.postgresql.org) to `pgagroal` communication. Keep track of message headers | | `transaction_stop` | Return connection to the pool if needed. Possible rollback of active transaction | | `transaction_destroy` | Nothing | | `transaction_periodic` | Nothing | ## Signals The main process of `pgagroal` supports the following signals `SIGTERM`, `SIGINT` and `SIGALRM` as a mechanism for shutting down. The `SIGTRAP` signal will put `pgagroal` into graceful shutdown, meaning that exisiting connections are allowed to finish their session. The `SIGABRT` is used to request a core dump (`abort()`). The `SIGHUP` signal will trigger a reload of the configuration. The child processes support `SIGQUIT` as a mechanism to shutdown. This will not shutdown the pool itself. It should not be needed to use `SIGKILL` for `pgagroal`. Please, consider using `SIGABRT` instead, and share the core dump and debug logs with the `pgagroal` community. ## Reload The `SIGHUP` signal will trigger a reload of the configuration. However, some configuration settings requires a full restart of `pgagroal` in order to take effect. These are * `hugepage` * `libev` * `log_path` * `log_type` * `max_connections` * `pipeline` * `unix_socket_dir` * `pidfile` * Limit rules defined by `pgagroal_databases.conf` The configuration can also be reloaded using `pgagroal-cli -c pgagroal.conf reload`. The command is only supported over the local interface, and hence doesn't work remotely. ## Prometheus pgagroal has support for [Prometheus](https://prometheus.io/) when the `metrics` port is specified. The module serves two endpoints * `/` - Overview of the functionality (`text/html`) * `/metrics` - The metrics (`text/plain`) All other URLs will result in a 403 response. The metrics endpoint supports `Transfer-Encoding: chunked` to account for a large amount of data. The implementation is done in [prometheus.h](../src/include/prometheus.h) and [prometheus.c](../src/libpgagroal/prometheus.c). ## Failover support pgagroal can failover a PostgreSQL instance if clients can't write to it. This is done using an external script provided by the user. The implementation is done in [server.h](../src/include/server.h) and [server.c](../src/libpgagroal/server.c). ## Logging Simple logging implementation based on a `atomic_schar` lock. The implementation is done in [logging.h](../src/include/logging.h) and [logging.c](../src/libpgagroal/logging.c). ## Protocol The protocol interactions can be debugged using [Wireshark](https://www.wireshark.org/) or [pgprtdbg](https://github.com/jesperpedersen/pgprtdbg). pgagroal-1.6.0/doc/CLI.md000066400000000000000000000467571456565230200150430ustar00rootroot00000000000000# `pgagroal-cli` user guide `pgagroal-cli` is a command line interface to interact with `pgagroal`. The executable accepts a set of options, as well as a command to execute. If no command is provided, the program will show the help screen. The `pgagroal-cli` utility has the following synopsis: ``` pgagroal-cli [ OPTIONS ] [ COMMAND ] ``` ## Options Available options are the following ones: ``` -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -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 ``` 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 The `flush` command performs a connection flushing. It accepts a *mode* to operate the actual flushing: - `gracefully` (the default if not specified), flush connections when possible; - `idle` to flush only connections in state *idle*; - `all` to flush all the connections (**use with caution!**). The command accepts a database name, that if provided, restricts the scope of `flush` only to connections related to such database. If no database is provided, the `flush` command is operated against all databases. Command ``` pgagroal-cli flush [gracefully|idle|all] [*|] ``` Examples ``` pgagroal-cli flush # pgagroal-cli flush gracefully '*' pgagroal-cli flush idle # pgagroal-cli flush idle '*' pgagroal-cli flush all # pgagroal-cli flush all '*' pgagroal-cli flush pgbench # pgagroal-cli flush gracefully pgbench ``` ### ping The `ping` command checks if `pgagroal` is running. In case of success, the command does not print anything on the standard output unless the `--verbose` flag is used. Command ``` pgagroal-cli ping ``` Example ``` pgagroal-cli ping --verbose # pgagroal-cli: Success (0) pgagroal-cli ping # $? = 0 ``` In the case `pgagroal` is not running, a message is printed on the standard error and the exit status is set to a non-zero value: ``` pgagroal-cli ping # $? = 1 Connection error on /tmp ``` ### enable Enables a database (or all databases). Command ``` pgagroal-cli enable [|*] ``` Example ``` pgagroal-cli enable ``` ### disable Disables a database (or all databases). Command ``` pgagroal-cli disable [|*] ``` Example ``` pgagroal-cli disable ``` ### shutdown The `shutdown` command is used to stop the connection pooler. It supports the following operating modes: - `gracefully` (the default) closes the pooler as soon as no active connections are running; - `immediate` force an immediate stop. If the `gracefully` mode is requested, chances are the system will take some time to perform the effective shutdown, and therefore it is possible to abort the request issuing another `shutdown` command with the mode `cancel`. Command ``` pgagroal-cli shutdown [gracefully|immediate|cancel] ``` Examples ``` pgagroal-cli shutdown # pgagroal-cli shutdown gracefully ... pgagroal-cli shutdown cancel # stops the above command ``` ### status The `status` command reports the current status of the `pgagroal` pooler. Without any subcommand, `status` reports back a short set of information about the pooler. Command ``` pgagroal-cli status ``` Example ``` pgagroal-cli status ``` With the `details` subcommand, a more verbose output is printed with a detail about every connection. Example ``` pgagroal-cli status details ``` ### switch-to Switch to another primary server. Command ``` pgagroal-cli switch-to ``` Example ``` pgagroal-cli switch-to replica ``` ### conf 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; - `ls` prints where the configuration files are located. Command ``` pgagroal-cli conf ``` Examples ``` pgagroal-cli conf reload pgagroal-cli conf get max_connections 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 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. It is possible to specify the setting name with words separated by dots, so that it can assume the form `section.context.key` where: - `section` can be either - `pgagroal` (optional) the search will be performed into the main configuration settings, that is those under the `[pgagroal]` settings in the `pgagroal.conf` file; - `limit` the search will match against the dataabse/limit configuration, i.e., the file `pgagroal_databases.conf`; - `hba` the search will match against the Host Based Access configuration, i.e., the `pgagroal_hbs.conf`; - `server` the search will match against a single server defined in the main `pgagroal.conf` file. - `context` is the match criteria to find the information into a specific context, depending on the value of the `section`: - if the `section` is set to `limit`, than the `context` is the database name to match into the `pgagroal_databases.conf`. Please note that the same user could be listed more than once, in such case *only the first match* is reported back; - if the `section` is set to `hba`, than the `context` is the username to match into the `pgagroal_hba.conf`. Please note that the same user could be listed more than once, in such case *only the first match* is reported back; - if the `section` is set to `server`, than the `context` is the name of the server in the `pgagroal.conf` main file; - if the `section` is set to `pgagroal`, the `context` must be empty; - `key` is the configuration key to search for. Examples ``` pgagroal-cli conf get pipeline performance pgagroal-cli conf get limit.pgbench.max_size 2 pgagroal-cli conf get server.venkman.primary off ``` In the above examples, `pipeline` is equivalent to `pgagroal.pipeline` and looks for a global configuration setting named `pipeline`. The `limit.pgbench.max_size` looks for the `max_size` set into the *limit* file (`pgagroal_databases.conf`) for the database `pgbench`. The `server.venkman.primary` searches for the configuration parameter `primary` into the *server* section named `venkman` in the main configuration file `pgagraol.conf`. If the `--verbose` option is specified, a descriptive string of the configuration parameter is printed as *name = value*: ``` pgagroal-cli conf get max_connections --verbose max_connections = 4 Success (0) ``` If the parameter name specified is not found or invalid, the program `pgagroal-cli` exit normally without printing any value. #### conf set Allows the setting of a configuration parameter at run-time, if possible. Examples ``` pgagroal-cli conf set log_level debug pgagroal-cli conf set server.venkman.port 6432 pgagroal conf set limit.pgbench.max_size 2 ``` The syntax for setting parameters is the same as for the command `conf get`, therefore parameters are organized into namespaces: - `main` (optional) is the main pgagroal configuration namespace, for example `main.log_level` or simply `log_level`; - `server` is the namespace referred to a specific server. It has to be followed by the name of the server and the name of the parameter to change, in a dotted notation, like `server.venkman.port`; - `limit` is the namespace referred to a specific limit entry, followed by the name of the username used in the limit entry. When executed, the `conf set` command returns the run-time setting of the specified parameter: if such parameter is equal to the value supplied, the change has been applied, otherwise it means that the old setting has been kept. The `--verbose` flag can be used to understand if the change has been applied: ``` $ pgagroal-cli conf set log_level debug debug $ pgagroal-cli conf set log_level debug --verbose log_level = debug pgagroal-cli: Success (0) ``` When a setting modification cannot be applied, the system returns the "old" setting value and, if `--verbose` is specified, the error indication: ``` $ pgagroal-cli conf set max_connections 100 40 $ pgagroal-cli conf set max_connections 100 --verbose max_connections = 40 pgagroal-cli: Error (2) ``` When a `conf set` cannot be applied, the system will report in the logs an indication about the problem. With regard to the previous example, the system reports in the logs something like the following (depending on your `log_level`): ``` DEBUG Trying to change main configuration setting to <100> INFO Restart required for max_connections - Existing 40 New 100 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: - `prometheus` resets the metrics provided without altering the pooler status; - `server` resets the specified server status. ``` pgagroal-cli clear [prometheus|server ] ``` Examples ``` pgagroal-cli clear spengler # pgagroal-cli clear server spengler pgagroal-cli clear prometheus ``` ## Deprecated commands The following commands have been deprecated and will be removed in later releases of `pgagroal`. For each command, this is the corresponding current mapping to the working command: - `flush-idle` is equivalent to `flush idle`; - `flush-all` is equivalent to `flush all`; - `flush-gracefully` is equivalent to `flush gracefully` or simply `flush`; - `stop` is equivalent to `shutdown immediate`; - `gracefully` is equivalent to `shutdown gracefully` or simply `shutdown`; - `reset` is equivalent to `clear prometheus`; - `reset-server` is equivalent to `clear server` or simply `clear`; - `config-get` and `config-set` are respectively `conf get` and `conf set`; - `reload` is equivalent to `conf reload`; - `is-alive` is equivalent to `ping`; - `details` is equivalent to `status details`. Whenever you use a deprecated command, the `pgagroal-cli` will print on standard error a warning message. For example: ``` pgagroal-cli reset-server WARN: command has been deprecated by since version 1.6.x ``` If you don't want to get any warning about deprecated commands, you can redirect the `stderr` to `/dev/null` or any other location with: ``` pgagroal-cli reset-server 2>/dev/null ``` ## Shell completions 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" } } ``` pgagroal-1.6.0/doc/CMakeLists.txt000066400000000000000000000061701456565230200166330ustar00rootroot00000000000000# man target add_custom_target(man ALL) # man page definitions set(PGAGROAL_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal.1.rst") set(PGAGROAL_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal.1") set(PGAGROAL_CLI_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal-cli.1.rst") set(PGAGROAL_CLI_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal-cli.1") set(PGAGROAL_ADMIN_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal-admin.1.rst") set(PGAGROAL_ADMIN_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal-admin.1") set(PGAGROAL_CONF_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal.conf.5.rst") set(PGAGROAL_CONF_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal.conf.5") set(PGAGROAL_HBA_CONF_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal_hba.conf.5.rst") set(PGAGROAL_HBA_CONF_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal_hba.conf.5") set(PGAGROAL_DATABASES_CONF_SRC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/man/pgagroal_databases.conf.5.rst") set(PGAGROAL_DATABASES_CONF_DST_FILE "${CMAKE_CURRENT_BINARY_DIR}/pgagroal_databases.conf.5") # pgagroal.1 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_SRC_FILE} ${PGAGROAL_DST_FILE} OUTPUTS ${PGAGROAL_DST_FILE} ) # pgagroal-cli.1 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_CLI_SRC_FILE} ${PGAGROAL_CLI_DST_FILE} OUTPUTS ${PGAGROAL_CLI_DST_FILE} ) # pgagroal-admin.1 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_ADMIN_SRC_FILE} ${PGAGROAL_ADMIN_DST_FILE} OUTPUTS ${PGAGROAL_ADMIN_DST_FILE} ) # pgagroal.conf.5 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_CONF_SRC_FILE} ${PGAGROAL_CONF_DST_FILE} OUTPUTS ${PGAGROAL_CONF_DST_FILE} ) # pgagroal_hba.conf.5 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_HBA_CONF_SRC_FILE} ${PGAGROAL_HBA_CONF_DST_FILE} OUTPUTS ${PGAGROAL_HBA_CONF_DST_FILE} ) # pgagroal_databases.conf.5 add_custom_command( TARGET man COMMAND ${RST2MAN_EXECUTABLE} ${PGAGROAL_DATABASES_CONF_SRC_FILE} ${PGAGROAL_DATABASES_CONF_DST_FILE} OUTPUTS ${PGAGROAL_DATABASES_CONF_DST_FILE} ) # man pages add_custom_command( TARGET man DEPENDS ${PGAGROAL_DST_FILE} ${PGAGROAL_CLI_DST_FILE} ${PGAGROAL_ADMIN_DST_FILE} ${PGAGROAL_CONF_DST_FILE} ${PGAGROAL_HBA_CONF_DST_FILE} ${PGAGROAL_DATABASES_CONF_DST_FILE} ) # # Install configuration and documentation # install(FILES etc/pgagroal.conf DESTINATION share/doc/pgagroal/etc) install(FILES etc/pgagroal_hba.conf DESTINATION share/doc/pgagroal/etc) install(DIRECTORY . DESTINATION share/doc/pgagroal FILES_MATCHING PATTERN "*.md" PATTERN "etc" EXCLUDE PATTERN "images" EXCLUDE PATTERN "man" EXCLUDE) install(DIRECTORY images/ DESTINATION share/doc/pgagroal/images FILES_MATCHING PATTERN "*.png") install(FILES ${PGAGROAL_DST_FILE} DESTINATION share/man/man1) install(FILES ${PGAGROAL_CLI_DST_FILE} DESTINATION share/man/man1) install(FILES ${PGAGROAL_ADMIN_DST_FILE} DESTINATION share/man/man1) install(FILES ${PGAGROAL_CONF_DST_FILE} DESTINATION share/man/man5) install(FILES ${PGAGROAL_HBA_CONF_DST_FILE} DESTINATION share/man/man5) install(FILES ${PGAGROAL_DATABASES_CONF_DST_FILE} DESTINATION share/man/man5) pgagroal-1.6.0/doc/CONFIGURATION.md000066400000000000000000000356441456565230200164340ustar00rootroot00000000000000# pgagroal configuration The configuration is loaded from either the path specified by the `-c` flag or `/etc/pgagroal/pgagroal.conf`. The configuration of `pgagroal` is split into sections using the `[` and `]` characters. The main section, called `[pgagroal]`, is where you configure the overall properties of the connection pool. The other sections configure each one server. There can be no more than `64` server sections, therefore no more than `64` servers configured as backends. Server sections don't have any requirements to their naming so you can give them meaningful names like `[primary]` for the primary [PostgreSQL](https://www.postgresql.org) instance. In any case, it is not possible to have duplicated sections, that means section names must be unique within the configuration file. All properties within a section are in the format `key = value`. The characters `#` and `;` can be used for comments. A line is totally ignored if the very first non-space character is a comment one, but it is possible to put a comment at the end of a line. The `Bool` data type supports the following values: `on`, `yes`, `1`, `true`, `off`, `no`, `0` and `false`. Each value can be quoted by means of `"` or `'`. Quoted strings must begin and end with the very same quoting character. It is possible to nest quotes. As an example of configuration snippet including quoting and comments: ``` # This line is ignored since it starts with '#' # and so is this one log_line_prefix = "PGAGROAL #%Y-%m-%d-%H:%M:%S" # quoted because it contains spaces log_type= console#log to stdout pipeline = 'performance' # no need to quote since it does not contain any spaces ``` See a more complete [sample](./etc/pgagroal.conf) configuration for running `pgagroal` on `localhost`. ## [pgagroal] This section is mandatory and the pooler will refuse to start if the configuration file does not specify one and only one. Usually this section is place on top of the configuration file, but its position within the file does not really matter. The available keys and their accepted values are reported in the table below. | Property | Default | Unit | Required | Description | |----------|---------|------|----------|-------------| | host | | String | Yes | The bind address for pgagroal | | port | | Int | Yes | The bind port for pgagroal | | unix_socket_dir | | String | Yes | The Unix Domain Socket location | | metrics | 0 | Int | No | The metrics port (disable = 0) | | metrics_cache_max_age | 0 | String | No | The number of seconds to keep in cache a Prometheus (metrics) response. If set to zero, the caching will be disabled. Can be a string with a suffix, like `2m` to indicate 2 minutes | | metrics_cache_max_size | 256k | String | No | The maximum amount of data to keep in cache when serving Prometheus responses. Changes require restart. This parameter determines the size of memory allocated for the cache even if `metrics_cache_max_age` or `metrics` are disabled. Its value, however, is taken into account only if `metrics_cache_max_age` is set to a non-zero value. Supports suffixes: 'B' (bytes), the default if omitted, 'K' or 'KB' (kilobytes), 'M' or 'MB' (megabytes), 'G' or 'GB' (gigabytes).| | management | 0 | Int | No | The remote management port (disable = 0) | | log_type | console | String | No | The logging type (console, file, syslog) | | log_level | info | String | No | The logging level, any of the (case insensitive) strings `FATAL`, `ERROR`, `WARN`, `INFO` and `DEBUG` (that can be more specific as `DEBUG1` thru `DEBUG5`). Debug level greater than 5 will be set to `DEBUG5`. Not recognized values will make the log_level be `INFO` | | log_path | pgagroal.log | String | No | The log file location. Can be a strftime(3) compatible string. | | log_rotation_age | 0 | String | No | The age that will trigger a log file rotation. If expressed as a positive number, is managed as seconds. Supports suffixes: 'S' (seconds, the default), 'M' (minutes), 'H' (hours), 'D' (days), 'W' (weeks). A value of `0` disables. | | log_rotation_size | 0 | String | No | The size of the log file that will trigger a log rotation. Supports suffixes: 'B' (bytes), the default if omitted, 'K' or 'KB' (kilobytes), 'M' or 'MB' (megabytes), 'G' or 'GB' (gigabytes). A value of `0` (with or without suffix) disables. | | log_line_prefix | %Y-%m-%d %H:%M:%S | String | No | A strftime(3) compatible string to use as prefix for every log line. Must be quoted if contains spaces. | | log_mode | append | String | No | Append to or create the log file (append, create) | | log_connections | `off` | Bool | No | Log connects | | log_disconnections | `off` | Bool | No | Log disconnects | | blocking_timeout | 30 | Int | No | The number of seconds the process will be blocking for a connection (disable = 0) | | idle_timeout | 0 | Int | No | The number of seconds a connection is been kept alive (disable = 0) | | max_connection_age | 0 | Int | No | The maximum number of seconds that a connection will live (disable = 0) | | validation | `off` | String | No | Should connection validation be performed. Valid options: `off`, `foreground` and `background` | | background_interval | 300 | Int | No | The interval between background validation scans in seconds | | max_retries | 5 | Int | No | The maximum number of iterations to obtain a connection | | max_connections | 100 | Int | No | The maximum number of connections to PostgreSQL (max 10000) | | allow_unknown_users | `true` | Bool | No | Allow unknown users to connect | | authentication_timeout | 5 | Int | No | The number of seconds the process will wait for valid credentials | | pipeline | `auto` | String | No | The pipeline type (`auto`, `performance`, `session`, `transaction`) | | auth_query | `off` | Bool | No | Enable authentication query | | failover | `off` | Bool | No | Enable failover support | | failover_script | | String | No | The failover script to execute | | tls | `off` | Bool | No | Enable Transport Layer Security (TLS) | | tls_cert_file | | String | No | Certificate file for TLS. This file must be owned by either the user running pgagroal 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. | | libev | `auto` | String | No | Select the [libev](http://software.schmorp.de/pkg/libev.html) backend to use. Valid options: `auto`, `select`, `poll`, `epoll`, `iouring`, `devpoll` and `port` | | buffer_size | 65535 | Int | No | The network buffer size (`SO_RCVBUF` and `SO_SNDBUF`) | | keep_alive | on | Bool | No | Have `SO_KEEPALIVE` on sockets | | nodelay | on | Bool | No | Have `TCP_NODELAY` on sockets | | non_blocking | off | Bool | No | Have `O_NONBLOCK` on sockets | | backlog | `max_connections` / 4 | Int | No | The backlog for `listen()`. Minimum `16` | | hugepage | `try` | String | No | Huge page support (`off`, `try`, `on`) | | tracker | off | Bool | No | Track connection lifecycle | | track_prepared_statements | off | Bool | No | Track prepared statements (transaction pooling) | | pidfile | | String | No | Path to the PID file. If omitted, automatically set to `unix_socket_dir`/pgagroal.`port`.pid | | update_process_title | `verbose` | String | No | The behavior for updating the operating system process title, mainly related to connection processes. Allowed settings are: `never` (or `off`), does not update the process title; `strict` to set the process title without overriding the existing initial process title length; `minimal` to set the process title to `username/database`; `verbose` (or `full`) to set the process title to `user@host:port/database`. Please note that `strict` and `minimal` are honored only on those systems that do not provide a native way to set the process title (e.g., Linux). On other systems, there is no difference between `strict` and `minimal` and the assumed behaviour is `minimal` even if `strict` is used. `never` and `verbose` are always honored, on every system. On Linux systems the process title is always trimmed to 255 characters, while on system that provide a natve way to set the process title it can be longer. | __Danger zone__ | Property | Default | Unit | Required | Description | |----------|---------|------|----------|-------------| | disconnect_client | 0 | Int | No | Disconnect clients that have been idle for more than the specified seconds. This setting __DOES NOT__ take long running transactions into account | | disconnect_client_force | off | Bool | No | Disconnect clients that have been active for more than the specified seconds. This setting __DOES NOT__ take long running transactions into account | ## Server section Each section with a name different from `pgagroal` will be treated as an host section. There can be up to `64` host sections, each with an unique name and different combination of `host` and `port` settings, otherwise the pooler will complain about duplicated server configuration. | Property | Default | Unit | Required | Description | |----------|---------|------|----------|-------------| | host | | String | Yes | The address of the PostgreSQL instance | | 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. # pgagroal_hba configuration The `pgagroal_hba` configuration controls access to `pgagroal` through host-based authentication. The configuration is loaded from either the path specified by the `-a` flag or `/etc/pgagroal/pgagroal_hba.conf`. The format of the file follows the similar [PostgreSQL HBA configuration format](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html), and as such looks like ``` # # TYPE DATABASE USER ADDRESS METHOD # host all all all all ``` | Column | Required | Description | |--------|----------|-------------| | TYPE | Yes | Specifies the access method for clients. `host` and `hostssl` are supported | | DATABASE | Yes | Specifies the database for the rule. Either specific name or `all` for all databases | | USER | Yes | Specifies the user for the rule. Either specific name or `all` for all users | | ADDRESS | Yes | Specifies the network for the rule. `all` for all networks, or IPv4 address with a mask (`0.0.0.0/0`) or IPv6 address with a mask (`::0/0`) | | METHOD | Yes | Specifies the authentication mode for the user. `all` for all methods, otherwise `trust`, `reject`, `password`, `md5` or `scram-sha-256` | Remote management users needs to have their database set to `admin` in order for the entry to be considered. There could be up to `64` HBA entries in the configuration file. # pgagroal_databases configuration The `pgagroal_databases` configuration defines limits for a database or a user or both. The limits are the number of connections from `pgagroal` to PostgreSQL for each entry. The file also defines the initial and minimum pool size for a database and user pair. Note, that this feature requires a user definition file, see below. The configuration is loaded from either the path specified by the `-l` flag or `/etc/pgagroal/pgagroal_databases.conf`. ``` # # DATABASE USER MAX_SIZE INITIAL_SIZE MIN_SIZE # mydb myuser all anotherdb userB 10 5 3 ``` | Column | Required | Description | |--------|----------|-------------| | DATABASE | Yes | Specifies the database for the rule. `all` for all databases | | USER | Yes | Specifies the user for the rule. `all` for all users | | MAX_SIZE | Yes | Specifies the maximum pool size for the entry. `all` for all remaining counts from `max_connections` | | INITIAL_SIZE | No | Specifies the initial pool size for the entry. `all` for `MAX_SIZE` connections. Default is 0 | | MIN_SIZE | No | Specifies the minimum pool size for the entry. `all` for `MAX_SIZE` connections. Default is 0 | There can be up to `64` entries in the configuration file. In the case a limit entry has incoherent values, for example `INITIAL_SIZE` smaller than `MIN_SIZE`, the system will try to automatically adjust the settings on the fly, reporting messages in the logs. The system will find the best match limit entry for a given `DATABASE`-`USER` pair according to the following rules: 1. Use the first entry with an exact `DATABASE` and `USER` match. 2. If there is no exact match, use the entry with a `USER` match and `DATABASE` set to `all`. 3. If Rule 2 does not apply, use the entry with a `DATABASE` match and `USER` set to `all`. # pgagroal_users configuration The `pgagroal_users` configuration defines the users known to the system. This file is created and managed through the `pgagroal-admin` tool. The configuration is loaded from either the path specified by the `-u` flag or `/etc/pgagroal/pgagroal_users.conf`. There can be up to `64` users known to `pgagroal`. # pgagroal_frontend_users configuration The `pgagroal_frontend_users` configuration defines the passwords for the users connecting to pgagroal. This allows the setup to use different passwords for the `pgagroal` to PostgreSQL authentication. This file is created and managed through the `pgagroal-admin` tool. All users defined in the frontend authentication must be defined in the user vault (`-u`). Frontend users (`-F`) requires a user vault (`-u`) to be defined. The configuration is loaded from either the path specified by the `-F` flag or `/etc/pgagroal/pgagroal_frontend_users.conf`. # pgagroal_admins configuration The `pgagroal_admins` configuration defines the administrators known to the system. This file is created and managed through the `pgagroal-admin` tool. The configuration is loaded from either the path specified by the `-A` flag or `/etc/pgagroal/pgagroal_admins.conf`. If pgagroal has both Transport Layer Security (TLS) and `management` enabled then `pgagroal-cli` can connect with TLS using the files `~/.pgagroal/pgagroal.key` (must be 0600 permission), `~/.pgagroal/pgagroal.crt` and `~/.pgagroal/root.crt`. # pgagroal_superuser configuration The `pgagroal_superuser` configuration defines the superuser known to the system. This file is created and managed through the `pgagroal-admin` tool. It may only have one user defined. The configuration is loaded from either the path specified by the `-S` flag or `/etc/pgagroal/pgagroal_superuser.conf`. pgagroal-1.6.0/doc/FAILOVER.md000066400000000000000000000015201456565230200156160ustar00rootroot00000000000000# pgagroal failover pgagroal can failover a PostgreSQL instance if the clients can't write to it. In `pgagroal.conf` define ``` failover = on failover_script = /path/to/myscript.sh ``` The script will be run as the same user as the pgagroal process so proper permissions (access and execution) must be in place. The following information is passed to the script as parameters 1. Old primary host 2. Old primary port 3. New primary host 4. New primary port so a script could look like ```sh #!/bin/bash OLD_PRIMARY_HOST=$1 OLD_PRIMARY_PORT=$2 NEW_PRIMARY_HOST=$3 NEW_PRIMARY_PORT=$4 ssh -tt -o StrictHostKeyChecking=no postgres@${NEW_PRIMARY_HOST} pg_ctl promote -D /mnt/pgdata if [ $? -ne 0 ]; then exit 1 fi exit 0 ``` The script is assumed successful if it has an exit code of 0. Otherwise both servers will be recorded as failed. pgagroal-1.6.0/doc/GETTING_STARTED.md000066400000000000000000000230431456565230200167020ustar00rootroot00000000000000# Getting started with pgagroal First of all, make sure that `pgagroal` is installed and in your path by using `pgagroal -?`. You should see ``` pgagroal 1.6.0 High-performance connection pool for PostgreSQL Usage: pgagroal [ -c CONFIG_FILE ] [ -a HBA_FILE ] [ -d ] Options: -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -a, --hba HBA_FILE Set the path to the pgagroal_hba.conf file -l, --limit LIMIT_FILE Set the path to the pgagroal_databases.conf file -u, --users USERS_FILE Set the path to the pgagroal_users.conf file -F, --frontend FRONTEND_USERS_FILE Set the path to the pgagroal_frontend_users.conf file -A, --admins ADMINS_FILE Set the path to the pgagroal_admins.conf file -S, --superuser SUPERUSER_FILE Set the path to the pgagroal_superuser.conf file -d, --daemon Run as a daemon -V, --version Display version information -?, --help Display help ``` If you don't have `pgagroal` in your path see [README](../README.md) on how to compile and install `pgagroal` in your system. ## Configuration Lets create a simple configuration file called `pgagroal.conf` with the content ``` [pgagroal] host = * port = 2345 log_type = file log_level = info log_path = /tmp/pgagroal.log max_connections = 100 idle_timeout = 600 validation = off unix_socket_dir = /tmp/ [primary] host = localhost port = 5432 ``` In our main section called `[pgagroal]` we setup `pgagroal` to listen on all network addresses on port 2345. Logging will be performed at `info` level and put in a file called `/tmp/pgagroal.log`. We want a maximum of 100 connections that are being closed if they have been idle for 10 minutes, and we also specify that we don't want any connection validation to be performed. Last we specify the location of the `unix_socket_dir` used for management operations. Next we create a section called `[primary]` which has the information about our [PostgreSQL](https://www.postgresql.org) instance. In this case it is running on `localhost` on port `5432`. Now we need a host based authentication (HBA) file. Create one called `pgagroal_hba.conf` with the content ``` # # TYPE DATABASE USER ADDRESS METHOD # host all all all all ``` This tells `pgagroal` that it can accept connections from all network addresses for all databases and all user names. We are now ready to run `pgagroal`. See [Configuration](./CONFIGURATION.md) for all configuration options. ## Running We will run `pgagroal` using the command ``` pgagroal -c pgagroal.conf -a pgagroal_hba.conf ``` If this doesn't give an error, then we are ready to connect. We will assume that we have a user called `test` with the password `test` in our [PostgreSQL](https://www.postgresql.org) instance. See their [documentation](https://www.postgresql.org/docs/current/index.html) on how to setup [PostgreSQL](https://www.postgresql.org), [add a user](https://www.postgresql.org/docs/current/app-createuser.html) and [add a database](https://www.postgresql.org/docs/current/app-createdb.html). We will connect to `pgagroal` using the [psql](https://www.postgresql.org/docs/current/app-psql.html) application. ``` psql -h localhost -p 2345 -U test test ``` That should give you a password prompt where `test` should be typed in. You are now connected to [PostgreSQL](https://www.postgresql.org) through `pgagroal`. Type `\q` to quit [psql](https://www.postgresql.org/docs/current/app-psql.html) and `pgagroal` will now put the connection that you used into its pool. If you type the above `psql` command again `pgagroal` will reuse the existing connection and thereby lower the overhead of getting a connection to [PostgreSQL](https://www.postgresql.org). Now you are ready to point your applications to use `pgagroal` instead of going directly to [PostgreSQL](https://www.postgresql.org). `pgagroal` will work with any [PostgreSQL](https://www.postgresql.org) compliant driver, for example [pgjdbc](https://jdbc.postgresql.org/), [Npgsql](https://www.npgsql.org/) and [pq](https://github.com/lib/pq). `pgagroal` is stopped by pressing Ctrl-C (`^C`) in the console where you started it, or by sending the `SIGTERM` signal to the process using `kill `. ## Run-time administration `pgagroal` has a run-time administration tool called `pgagroal-cli`. You can see the commands it supports by using `pgagroal-cli -?` which will give ``` pgagroal-cli 1.6.0 Command line utility for pgagroal Usage: pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] Options: -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -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 -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 ``` 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 ``` To stop pgagroal you would use ``` pgagroal-cli -c pgagroal.conf stop ``` Check the outcome of the operations by verifying the exit code, like ``` echo $? ``` or by using the `-v` flag. If pgagroal has both Transport Layer Security (TLS) and `management` enabled then `pgagroal-cli` can connect with TLS using the files `~/.pgagroal/pgagroal.key` (must be 0600 permission), `~/.pgagroal/pgagroal.crt` and `~/.pgagroal/root.crt`. ## Administration `pgagroal` has an administration tool called `pgagroal-admin`, which is used to control user registration with `pgagroal`. You can see the commands it supports by using `pgagroal-admin -?` which will give ``` pgagroal-admin 1.6.0 Administration utility for pgagroal Usage: pgagroal-admin [ -f FILE ] [ COMMAND ] Options: -f, --file FILE Set the path to a user file -U, --user USER Set the user name -P, --password PASSWORD Set the password for the user -g, --generate Generate a password -l, --length Password length -V, --version Display version information -?, --help Display help Commands: master-key Create or update the master key add-user Add a user update-user Update a user remove-user Remove a user list-users List all users ``` In order to set the master key for all users you can use ``` pgagroal-admin -g master-key ``` The master key must be at least 8 characters. Then use the other commands to add, update, remove or list the current user names, f.ex. ``` pgagroal-admin -f pgagroal_users.conf add-user ``` ## Next Steps Next steps in improving pgagroal's configuration could be * Update `pgagroal.conf` with the required settings for your system * Set the access rights in `pgagroal_hba.conf` for each user and database * Add a `pgagroal_users.conf` file using `pgagroal-admin` with a list of known users * Disable access for unknown users by setting `allow_unknown_users` to `false` * Define a `pgagroal_databases.conf` file with the limits and prefill settings for each database * Enable Transport Layer Security v1.2+ (TLS) * Deploy Grafana dashboard See [Configuration](./CONFIGURATION.md) for more information on these subjects. ## Tutorials There are a few short tutorials available to help you better understand and configure `pgagroal`: - [Installing pgagroal](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/01_install.md) - [Enabling prefill](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/02_prefill.md) - [Enabling remote management](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/03_remote_management.md) - [Enabling Prometheus metrics](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/04_prometheus.md) - [Enabling split security](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/05_split_security.md) ## Closing The [pgagroal](https://github.com/agroal/pgagroal) community hopes that you find the project interesting. Feel free to * [Ask a question](https://github.com/agroal/pgagroal/discussions) * [Raise an issue](https://github.com/agroal/pgagroal/issues) * [Submit a feature request](https://github.com/agroal/pgagroal/issues) * [Write a code submission](https://github.com/agroal/pgagroal/pulls) All contributions are most welcome ! Please, consult our [Code of Conduct](../CODE_OF_CONDUCT.md) policies for interacting in our community. Consider giving the project a [star](https://github.com/agroal/pgagroal/stargazers) on [GitHub](https://github.com/agroal/pgagroal/) if you find it useful. And, feel free to follow the project on [Twitter](https://twitter.com/pgagroal/) as well. pgagroal-1.6.0/doc/PERFORMANCE.md000066400000000000000000000035001456565230200161500ustar00rootroot00000000000000# pgagroal performance Performance is an important goal for `pgagroal` and effort have been made to make `pgagroal` scale and use a limited number of resources. This report describe `pgagroal` in relationship to 3 other [PostgreSQL](https://www.postgresql.org) connection pool implementations, which we will call `a`, `b` and `c`. The [pgbench](https://www.postgresql.org/docs/11/pgbench.html) program was used in the runs. All pool configurations were made with performance in mind. All diagrams are using the same identifier for the connection pool in question, so `a` is `a` in all diagrams and so on. The runs were performed on [RHEL](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux) 7.7 / [EPEL](https://access.redhat.com/solutions/3358) / [DevTools](https://developers.redhat.com/products/developertoolset/overview) 8 based machines on 10G network. All connection pools were the latest versions as of January 14, 2020. `pgagroal` was using the `epoll` mode of [libev](http://software.schmorp.de/pkg/libev.html). ## Simple This run uses ``` pgbench -M simple ``` ![pgbench simple](https://github.com/agroal/pgagroal/raw/master/doc/images/perf-simple.png "pgbench simple") ## Extended This run uses ``` pgbench -M extended ``` ![pgbench extended](https://github.com/agroal/pgagroal/raw/master/doc/images/perf-extended.png "pgbench extended") ## Prepared This run uses ``` pgbench -M prepared ``` ![pgbench prepared](https://github.com/agroal/pgagroal/raw/master/doc/images/perf-prepared.png "pgbench prepared") ## ReadOnly This run uses ``` pgbench -S -M prepared ``` ![pgbench readonly](https://github.com/agroal/pgagroal/raw/master/doc/images/perf-readonly.png "pgbench readonly") ## Closing **Please**, run your own benchmarks to see how `pgagroal` compare to your existing connection pool deployment. pgagroal-1.6.0/doc/PIPELINES.md000066400000000000000000000057041456565230200157470ustar00rootroot00000000000000# pgagroal pipelines pgagroal supports 3 different pipelines * Performance * Session * Transaction The pipeline is defined in `pgagroal.conf` under the setting of ``` pipeline = auto ``` pgagroal will choose either the performance or the session pipeline based on the configuration settings by default. # Performance The performance pipeline is fastest pipeline as it is a minimal implementation of the pipeline architecture. However, it doesn't support Transport Layer Security (TLS), failover support and the `disconnect_client` setting. A `DISCARD ALL` query is run after each client session. Select the performance pipeline by ``` pipeline = performance ``` # Session The session pipeline supports all features of pgagroal. A `DISCARD ALL` query is run after each client session. Select the session pipeline by ``` pipeline = session ``` # Transaction The transaction pipeline will release the connection back to the pool after each transaction completes. This feature will support many more clients than there are database connections. The transaction pipeline requires that there are users defined such that connections can be kept in the pool in all security scenarios. However, there are some session based features of PostgreSQL that can't be supported in this pipeline. * `SET` / `RESET` * `LISTEN` / `NOTIFY` * `WITH HOLD CURSOR` * `PREPARE` / `DEALLOCATE` It is assumed that all clients using the same user name and database pair share the same startup parameters towards PostgreSQL. __`SET` / `RESET`__ The `SET` functionality is a session based feature. __`LISTEN` / `NOTIFY`__ The `LISTEN` functionality is a session based feature. __`WITH HOLD CURSOR`__ The `WITH HOLD CURSOR` functionality is a session based feature. __`PREPARE` / `DEALLOCATE`__ While using `PREPARE` and `EXECUTE` can be used the prepared statements are tied to the connection they were created in which means that clients can't be sure that they created the prepared statement on the connection unless it is issued within the same transaction where it is used. pgagroal can issue a `DEALLOCATE ALL` statement before a connection is returned back to the pool if the `track_prepared_statements` setting is set to `on`. If `off` then no statement is issued. Note, that pgagroal does not issue a `DISCARD ALL` statement when using the transaction pipeline. __Performance considerations__ Clients may need to wait for a connection between transactions leading to a higher latency. __Important__ Make sure that the `blocking_timeout` settings to set to 0. Otherwise active clients may timeout during their workload. Likewise it is best to disable idle connection timeout and max connection age by setting `idle_timeout` and `max_connection_age` to 0. It is highly recommended that you prefill all connections for each user. The transaction pipeline doesn't support the `disconnect_client` or `allow_unknown_users` settings. Select the transaction pipeline by ``` pipeline = transaction ``` pgagroal-1.6.0/doc/RPM.md000066400000000000000000000012551456565230200150520ustar00rootroot00000000000000# pgagroal rpm `pgagroal` can be built into a RPM for [Fedora](https://getfedora.org/) systems. ## Requirements ```sh dnf install gcc rpm-build rpm-devel rpmlint make python bash coreutils diffutils patch rpmdevtools chrpath ``` ## Setup RPM development ```sh rpmdev-setuptree ``` ## Create source package ```sh git clone https://github.com/agroal/pgagroal.git cd pgagroal mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. make package_source ``` ## Create RPM package ```sh cp pgagroal-$VERSION.tar.gz ~/rpmbuild/SOURCES QA_RPATHS=0x0001 rpmbuild -bb pgagroal.spec ``` The resulting RPM will be located in `~/rpmbuild/RPMS/x86_64/`, if your architecture is `x86_64`. pgagroal-1.6.0/doc/SECURITY.md000066400000000000000000000051001456565230200156540ustar00rootroot00000000000000# pgagroal security ## Pass-through security pgagroal use pass-through security by default. This means that pgagroal delegates to PostgreSQL to determine if the credentials used are valid. Once a connection is obtained pgagroal will replay the previous communication sequence to verify the new client. This only works for connections using `trust`, `password` or `md5` authentication methods, so `scram-sha-256` based connections are not cached. Note, that this can lead to replay attacks against the `md5` based connections since the hash doesn't change. Make sure that pgagroal is deployed on a private trusted network, but consider using either a user vault or authentication query instead. ## User vault A user vault is a vault which defines the known users and their password. The vault is static, and is managed through the `pgagroal-admin` tool. The user vault is specified using the `-u` or `--users` command line parameter. ### Frontend users The `-F` or `--frontend` command line parameter allows users to be defined for the client to `pgagroal` authentication. This allows the setup to use different passwords for the `pgagroal` to PostgreSQL authentication. All users defined in the frontend authentication must be defined in the user vault (`-u`). Frontend users (`-F`) requires a user vault (`-u`) to be defined. ## Authentication query Authentication query will use the below defined function to query the database for the user password ``` CREATE FUNCTION public.pgagroal_get_password( IN p_user name, OUT p_password text ) RETURNS text LANGUAGE sql SECURITY DEFINER SET search_path = pg_catalog AS $$SELECT passwd FROM pg_shadow WHERE usename = p_user$$; ``` This function needs to be installed in each database. The function requires a user that is able to execute it, like ``` -- Create a role used for the authentication query CREATE ROLE pgagroal LOGIN; -- Set the password \password pgagroal -- Only allow access to "pgagroal" REVOKE EXECUTE ON FUNCTION public.pgagroal_get_password(name) FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.pgagroal_get_password(name) TO pgagroal; ``` Make sure that the user is different from the actual application users accessing the database. The user accessing the function needs to have its credential present in the vault passed to the `-S` or `--superuser` command line parameter. The user executing the authentication query must use either a MD5 or a SCRAM-SHA-256 password protected based account. Note, that authentication query doesn't support user vaults - user vault (`-u`) and frontend users (`-F`) - as well as limits (`-l`). pgagroal-1.6.0/doc/etc/000077500000000000000000000000001456565230200146425ustar00rootroot00000000000000pgagroal-1.6.0/doc/etc/pgagroal.conf000066400000000000000000000003241456565230200173040ustar00rootroot00000000000000[pgagroal] host = localhost port = 2345 log_type = console log_level = info log_path = max_connections = 100 idle_timeout = 600 validation = off unix_socket_dir = /tmp/ [primary] host = localhost port = 5432 pgagroal-1.6.0/doc/etc/pgagroal.service000066400000000000000000000007141456565230200200220ustar00rootroot00000000000000# systemd service unit for pgagroal # # - Adjust the user running the service in User # - Adjust the path in ExecStart # [Unit] Description=High-performance connection pool for PostgreSQL Documentation=man:pgagroal(1) Documentation=https://agroal.github.io/pgagroal/ After=network.target [Service] Type=exec User=pgagroal ExecStart=/usr/bin/pgagroal ExecReload=/bin/kill -HUP $MAINPID KillSignal=SIGINT #LimitNOFILE=1024 [Install] WantedBy=multi-user.target pgagroal-1.6.0/doc/etc/pgagroal.socket000066400000000000000000000003341456565230200176500ustar00rootroot00000000000000# systemd socket unit for pgagroal # # See pgagroal.service for more information. # [Unit] Description=Sockets for pgagroal [Socket] ListenStream=/tmp/.s.PGSQL.2345 #ListenStream=2345 [Install] WantedBy=sockets.target pgagroal-1.6.0/doc/etc/pgagroal_hba.conf000066400000000000000000000001171456565230200201160ustar00rootroot00000000000000# # TYPE DATABASE USER ADDRESS METHOD # host all all all all pgagroal-1.6.0/doc/images/000077500000000000000000000000001456565230200153345ustar00rootroot00000000000000pgagroal-1.6.0/doc/images/perf-extended.png000066400000000000000000000634301456565230200206020ustar00rootroot00000000000000‰PNG  IHDR]Te‰~ pHYsÅÀ:ºfÊIDATxœíÝ \Õðß.÷}yƒ˜¦‚Bx¦y’âm¥¥I™¦•©å‘¥bi—–YóêðÊ1EóÈ£2ï[QðDA•›å\vÿ3³€ §»ìòûúYwwÞÌìv~ûæÍcµZý€. """¢ c Màš¥ãz4c]W€ˆˆˆ¨*`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è"¢'rtû*ì:u»˜9dèýÚ$´k`«µ:=‰Ô³ë`ÓòUÌ Ã ¿Æ:_U ]DôDŽ¯ÂœgѲm3˜Ë ›C¾ã…ÐUºõ…mý íæ_G̱U0*ÏŠU2 ]DôÄdf°áÐa4*‡”tôÈQ¨QýéWDDTÉ1tQ…¸°u>Ú œƒ·œÄ ?Oiš:= [{àŒÃ@œÙÿ>íé‚ï÷ߓʌe?cô×;±jro@•†À%s1ÿkqáÚY:¢m×¾Xðžw¯!ÌÁM­Ðc f´zˆIs– $üœÝ0îÓo1û!6ÂeÄ^Ç´wÞÆ¯Áÿ")Ë ­»ÀWowÈ_Ñ_«”ë!"*CUˆçúOż7vâã‰c1¨Ë?p³—#ðË÷±ý¶#öþ±Ær, lHKüü°®ïý––6Â’jlþl4†}~³/ÅÆ^m~ÿ*¾øðôèÚÇ.E³jr˜Ë_Ýó=æE¼€ïÖïFuÓü0ómÌ}{$:w¿]ðùØX¼W%kþ‚oëz¸v<}4Ox…¥y-Y)ÖCDT2†."zbêÌ(|?w6 éÓeQ× SÇôƒ\fŒ÷þ‚^^;ýG¾[ïÍß‚I?D×6Ò¼–¶ö°05‚ÌØ ŽŽŽRŸ.uò%|½¾ïÿŠÙoúiVZ×ËÖüˆ}õºàûÕû°|R7Èd2܉³Çßk¾C= MEfLÀO;ÞÄñS7ÑÍ2«þ¸€Þ“~ÁÛý;Kå.ÆáðÃè?}f;’/—øZ?´)q=DD¥ÁÐEDON•„#û÷âÐeëi%„.Íc#Ûgñó/ Ñ¢Ûè¼ßÕ{„€Q‹]uBÈ1„$ªápý(fϾž§$ æ–2œ96 ááŠ|eü^C–rë™™›!ßêòôÛ‚c^G÷³ÿ³š ^7«LÀ n>xiÊœ÷Ïü›ìæbÁÿf£Å§þ›Û¡Yû°é¯èënÒ^„ÌsVnEÒ¸q˜3æ%|¨2G«®~X²ü+ôì<ÊÌLq¦R¼J±"¢’1tÑyï§C­tóf}ôØ™Ï! ϤúÏD轑ùç“›cлŸK·Âcݹ´‚S@jžC•5›â[þÆÿ›/V•§}ªÄ×*åzˆˆJÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZÀÐEDDD¤ ]DDDDZ —¡+dßoxgÊ\œ½ñ&ÖÕ1xü§X4c$Ìe@ø±-ð? '¯EÃÜÑ–`ÆERFDDDTzº”÷cÀÀ‰¹lö m‡!§³~jëw;©0rÐhtœ» »FûàÞ™ té>MZ\Å€qå^6ÐÓ^×?"""Òzº2Õv˜þà —‘ ¨åÙÝšÛáÊÕ›¸¥<‰“fÞØ>ÊÆBY]/?ø÷ ÀÚ»àÕ!²ÜËz×õƒˆˆˆô„Þ….‹šn=Â-÷yJäQ쿨Æç?´DØÞ5¨åæ;yN© nîîXs>a‘å^¦–ž•LïBW^‰§1Ü÷xÏ\…žvÚš +ç|AÈÒÒ)É $+Ê¿ìqÁÁÁ¦Éd2¨Õê§ÝT""ÒsÏ<ó <==u] Ò!½ ]áÇÑø$øÎúot“B‘µ•R„”·J¡PÀʦ.l* ìq¾¾¾¦‰A¬°éDDTµˆû†®ªM/CWø‘õè1|f­Þ‡‘Þr§7ñð@Ôâ=ˆWÒá@5BB.ÂÃcPfWîe<´HDDD¥¥w¡++þ2†›„I¿Wƒ|e.ÞƒÐsðÙÊXàßG×cÅî,Û .®qå^FDDDTZzºB÷nÀ±ˆ8Û«)&å™ÞeÜbìZô~ \1cÇÂiJ¬ª7ÄÔ•AèÝØ2X—{Qié]èò2jõì"Ë]¼|±ûDá}¨*¢Œˆˆˆ¨4ô.té#†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""¢r”’ž‰/7-0ýÊ•hœŒû7÷¹_ÇÆhñlMmVtŒ¡‹ˆˆ¨¥¦+1gõ¡Â ÝÏ}(ΣÞ3]Kµ¢Ê€¡‹ˆˆˆH ºˆˆHëÎß¼Ã!‘%Î7¶oKÈd2-Ôˆ¨âégèRg`çòøO^¿ù{ðã„®98´ák|° q2de¡ûˆÉXô©?¬ä@ø±-ð? '¯EÃÜÑ–`ƈÎe-#"¢'·ÿÌ-LZúW‰óMY¶ÉÛ¦h¡F¥—ž™EZ&â’S‘¨HÏ~œ†„ìÇw&麊TIéaèRcñ¸žLlƒöMò•dÞÞ‡Á£¾Â¢ÃçñrËZH{x ]›{aQóN˜ÙË#Fǹ۰k´î B—îƒÑ¤ÅU hW¦²žöºùUbˆ)/Y*µŒÄ°”"¬Wó8MzœÇ%NOJEšºˆÊBCÐé/1¾M+Lî±yÿ3c‘(¯‰æM4gƒ˜Wk„&.ˆÃ­¿㤙7¶ò± ¨ëåÿ>X»q¼:D–©l çpÝüˆˆªœ°¤iUÊÈת$>§'¥fHÓŸžóX¼¥e(u½)TÅéaè’¡UÛ6½ª@‰¥G_|<äGŒ{uFú¶ÁýKûqBÞ [·CØÏ«PËÍvòGëqswÇšó¡sˆ,S™ZzFDD¥!†'1üDÇ&#<:¾ÔË÷ZPq•*¹Lk SX™›ÀÁÚvVf°ÌóX.—aùγº®&UBzºŠ#G —gñßm\»fèQ¨V»‘0U …"VÎùB’¥¥%R’H.cQEêþÑzì;^â|«¦ôÅèÍ*¾BQf©ð0!Rq?^!ܧ÷)ˆILElR*¢bˆïÅyÔj­W3—†l…°d+#1Ùäylk)ÞL¥{ Sciº¢Ä`e—ýØÎÊ\ WöÖfRð*Џý ]Tƒ ]á{–â½Õ·q5lꘋJÌÔS¾Þ„qNVHTÞÖ)…B+›º°±*[Ùゃƒ ­WQÓ‰ˆŠSšÀåÝØo|½Õ3n?õë©„D”’®B\J&’Ò²„{%’R•Â}–ð\‰xáybÎ}j„2•–B”©‘ VfFÒÍÌXksᱩðØDëÜéâc¹T&>§‰eâ<â¼VBYÑgBŠý´R³o€ô¡Ÿ$Ü ·øÂí ê*þìDâkØS“ÜDZIiO°V2†º®]ƒ¹KCÔ4Ïù£2Fýúu~M¼=µxâU€ƒt¨P‹ðð„&ve*{üO×××·@ÄÀUØt"¢-¾Pâ,ÎÎÎÀ•„B?gTB"ŠW¤K-/ÑqÉÒ}TœâSp/69·eJ|~?AØÄ4)xU$±%ÉÉÖʬ,D<(ÝY~Y»§Û²T-=’„.D,HÔ ßÑÆ\‹5¢ÊÀ B—g»öÈœ6 ÛÏ݃_óÚH»À?¡Ã¸©pñn„ΘƒÏVÀÿ.ˆ8º+vÇ`éÜ^pq+S‘®…EÆH÷ïý¸77TiB–æ>CY±gÚÙ[™¡º½%jØ[¡š…t_ÝÎRš^ÓÁJ Xâ}-k©ÜÜT³ÛùvˉR !Ò·À%úû›¦ñK8é_èÊA ÇVUÊÌ à`O¬š"G§7bß÷ocË’[˜örg|.ö7¡ËÐ9˜ÿvWÈŒeø%p5ÆŒ §)Q°ªÞSW¡wckÈ`]¦2""];}5Zº_¼õd¹¬O EbHª!§œÐTËÑN6æùBTµìé9!ŠˆJ¦-¦8›\ôqð“­0.^¾Ø}¢ðoe-#"*/jµ—nÇàïs·p@¸•‡œ%…§<÷b`[¤jØ[fß[åk‰ªhýZcúÊ%ŽyÕÉÃE+õ!Òý ]DD"S™…Ó×¢ñï…¹|Ÿ¿-| ë8àÚÝ8ÌÓEj}VNȪí`-mWÉeHÝñ¡®«A¤U ]DDZ"^>æÔÕ{R¸:t1R [â žO£[m)tMÖ¡œjID…¡‹ˆ¨‚¤¦+qär¤²"r[³J]l¡©=ïYS—ï×RM‰HºˆˆÊIbJº®Ä¡ÄûWîIˆ§–£•°¼Ÿ« ŸfõàñLµÜ³õÄÐ5Ô§I±Ë·oâŒõ.•Û6QÅaè""*#qœ«Î߯¿#qèbÎ\‹.qœ«z5í¤€•²Ä>YEˆðãÄžÿýîb×·ñïËØ9ohY7ˆ´ˆ¡‹ˆ¨”"$áPHž»%­K·–¸Œ{]')`=/Þ<ëJ¡«´Æ½ä%݈È00táÆ½x©Ó»xû/$Rê°^ñÚ~Íê×ÈX.BØr•Î$$"1t‘ÞûPí<~½ÄùÄðSÇ©øsÇÈÖa!`‰ãdÝI.vc9Z4¨‰.Í]¥ ÕÙ³®tad"¢Â0t‘ÞCÑËŸÿQâ|‟©ÁSóM;¸_¸ygwz/ÍYâzÄ!º4s•Î0ìØÔ6–¦Oµ DTu0t‘Á‡iÈ#ëqø†‹¥#ËÆÂš:çž]ØÖ½/{CDeÆO"ª.BJzf±óˆ‡Å,Ÿf®ðnV^ kÁØH®¥‘¡cè"¢*¡°À•3FÖ -êIý¾š¸:AVÔø DDOI?C—:;—ÀòøÍ߃'tÍ-ÊŒ»Ž)cý±vï¨Mmàëÿ –Î{Âçhø±-ð? '¯EÃÜÑ–`ƈ±e-#"ýñlm{)\uôp–Z´ÜëV+rŒ,"¢ò¦‡¡KÅãz"0± Ú7uz¬, óÇ FˆÃ«¸µFq!þÒËØê×Ã=9h4:Î݆]£}pïLºtŒ&-®b@ƒ¸2• ô´×Å€ˆ 9ÛðxØÝRϾzü‘EDTÞô0tÞøãÛ´Âäë÷€êþ,ß‘„-·&ÁÎLÔzÁ'.Jeá~“fÞØ>ÊÆÂ7Ûº^~ðwÁ«Cd™Êz×Í€¨ ;Äÿ~ _m:†R Nšƒ‹ˆtMC— ­Ú¶î ^Ï,êÂiMzÖšˆ$“Ë¥SÀ›xx jñÄ«éP¡!!áá1H(³+S-•/q”ø-‡Â°(ð8ކì$ïùLu¼?  Ft÷€™‰æãëê/ok»šDDebP¡Ë¬a7 o¥Àœ¹?cíüÑH»}Ë6ü‡¾ß~o{tÆ|¶òøwAÄÑõX±;Kçö‚‹k\™Êˆ¨|$¥d`å®sX¼õnF%(ïÞòLÒ=ZÕç8ZD¤·ô/te„ …c+„*…oÅ™ÀÁžX5EŽNo.ľïÇ㫵Açÿ6\ªO‘¹OXŠ='ÅóKàjŒ;NS¢`U½!¦® BïÆÖÁºLeDôt"$âû­'ñÓŽ³ÒÅ«ó256Âð®M0up{xÖ¯®£•ý ]¦8›œVd±­kk¬Ýs²Ð2/_ì>Qx¿Ž²–Ñ“;}5 ·džƒ—¥CŠy‰—âëÛïöoÚNürCD†CÿBé%±3üŽc×ð]ÐIì;^ ¼‘³ÞõkƒÑ=›ÁÊÜDû$"ª` ]DT¡RÓ3ñÛ_ñí–ˆ)P.^ŽgÒÀ¶ðëÔFrö×""ÃÅÐEDâaB ¾ÿã–ŸÆýø”|eb¸ØÙ  j‹öMœuTC""íbè"¢rƒ¯7Ãê¿.J—ìÉËÚÂcz5“#6¨m¯› éC=5ñâÓÎÝÂ7›ãÏã×ñøñ.Õl0ѯ5Æöm ;+3Ô‘ˆH׺ˆ¨Ì2”YÒˆßn9ŽÓ×¢ ”{5¬‰IƒÚb˜O˜é †DD•C=±Eºtñé%œ’ÆÚÊK¯oÛ†xw@ktoYË”ˆHƒ¡‹ˆJ-<:A: qÕ®sHJÍÈWfnjŒ×º{âýmÐĵšŽjHDTy1tQ‰Ž‡ÝÅ—"è¿+P©ò÷Ør²µÀÄþ­0î%/Ô°·ÒQ ‰ˆ*?†."*TV– ÛŽ^Ã7›á¿ÈåM]«I­Z#»{J­\DDT<~R¸C#Ðý£õ†oxÜŒ—;â³Ñ>HNÍÀÏ»ÏcñÖ“¸v7®À|]›×ÃäÁmÑ§í³¼ø4ÑÐÏÐ¥ÎÀÎåðŸ¼~ó÷àÇ ] Ì¢Œv^õY€þ7Qš~l üÇOÃÉkÑ0wtÅÄ€%˜1²§(#ªìN„Ý+1p‰>_XúaÙγˆILÍWfb,—Î@|`[´jT«¢ªJDdÐô0t©±x\O&¶Aû¦NEÌ£Äâ)oâŽQ ¸ä,•r#Fǹ۰Kø6ïLºtŒ&-®b@ƒ¸2• ô´×Ò6iÇüßä{nki†±}[`BÿVp­a§£Z= ]@§7¾Äø6­0¹Ç:dRºó;,½T3_sÁ¯ÙCÝú;'ͼ±}”Œe@]/?ø÷ ÀÚ»àÕ!²Le=‡ku»‰´¥^ [|0¸ÞèÙLEžˆˆžž†.Zµm#Ü« -UÆ]†ÿ¤Ÿ°0è(2·LÈŠZnî°“?Z›»;ÖœE˜Cd™ÊÔÒ3"Ãѱ©3& a«ÇF0’ËK^€ˆˆJMCW1ÔJ,úÀ Ç,DߦިºåQ‘B‘ +ç|!ÉÒÒ)É $—±ŒÈÐü÷íkº®‘Á2¨ÐuiÇB¬¼æŽÃËûh²¶²BŠ ò¶N) XÙÔ…MË\h½ŠšN¤ !—–z^þ®U ]j¬^¶ çãÐÔe‡4%]‘EV0\ΟÆO›#jñÄ«¹fþ‹ðð„&ve*{<Øùúú¨•¸+l:‘¶\M?ü{¯Tóòw•¨âðK Pè’aþ¶k˜ŸgJмW°èNiÈuZ:c>[y ü» âèz¬Øƒ¥s{ÁÅ5®LeD•]Ffö½¥ëjô1te„ …c+„*efp°'VM‘£Ó› ±ïûñE.&3¯‹_WcÌØ±pš«ê 1uez7¶âšu™Êˆ*³Ä”t ž„½§oæN³±0-òlÄ{±ÉÚªQ•¤¡ËÔg“ÓJ5ë€×a@žç.^¾Ø}¢ðÃ'e-#ªŒÄÕ{Æœ»q?ßtñ"Õ_¨:‡o»†Ú¨Q•¥¡‹ˆŠƒ^Bàº;M¼ÄϼQÞ¼l‘1tñ:‹ý?ÝŒØ$Mk°‘\†&öÄØ¾-u\3""bè"2A‡Âðê‚mHÍPJÏ-ÍLðûŒþx©C#׌ˆˆD ]D`ɧðÞ{¡R«¥çÕl-°mîthê¬ãšQ†."=¦BÖŒUcÁ†Gª~¦¦ö,ŽFÎŽ:¬=Ž¡‹HO‰cp½ñͬݒ;­U£ZØ1o(j:Xé°fDDT†."=¤ƒk öžÏÖ»Mlüx@‘ãp‘n1té™ÂÆàÕã9,{¿7LŒtX3""*C‘ ½ƒ^3óÁ5óåŽà\DD•C‘žø÷Büf?ƒËØHŽ'öÄ›}Zè¶bDDT* ]Dz`Ë¡0Œx| ®™ýñR{ŽÁED¤/ºˆ*¹ï‚Nàƒ¥ûÁegCÑÖ½ŽŽkFDTõ¨â®à‹%{ðÆô ¨ù„)J?C—:;—ÀòøÍ߃'tÍ- Ù÷Þ™2go<€‰uu ÿ)Í s~l üÇOÃÉkÑ0wtÅÄ€%˜1ÂbO˜²–U1dM_y_n<š;­Am{ìú|Çà"¢ w"ì.:°Fž¦(“·Å×ouÓb­tOó…Ðwj•]j,׉mо©S¾åýã0p"F.Ûƒ}CÛáAÈŸðé쇟ÚzãÝN*Œ4çnîÑ>¸w&]ºF“W1 A\™ÊzÚëæG@/]øóØ\mÜj#8`jØs ."ªxJU±«k‹zøfóñ*ºž††. Ó_b|›V˜Üc2óLÏTÛaúk0L\F2 –gOtkn‡+Woâ–ò$Nšycû( eu½üàß'k7î‚W‡È2• ô®³Ÿ®DE:Ì Äþ³·r§q ."24ÊkÛ`ßl> èŽÝ;N!*2®_Ú¥ŸÀÉTà>Ä»Ÿ¯‘Ú¼4V§æ£Úèm˜?Ò ×|ŽIóV">ÈR›á•¾Áço÷ª"—›÷ü}Tk> Ÿ~Ü Ÿ|²Bï£æÅe˜ ,&C–JŽž£gà»_‡¹L]äk< = ]2´jÛF¸W(±¨é†Ñ#ÜrŸ§DÅþ‹j|þCK„í]ƒZnî°“?Z›»;ÖœE˜Cd™ÊÔÒ3¢òù0 }?ÞˆóyÆàz£W3üô^oélE"¢§ÕdÌ2„FÄ<õzd1”õ˜_ªù—OêÿÞÍsŸËŒŒ O»ˆ0|Ž¿öuÊ]¼ÔÒó×öBÀó1zêj,ù¬WNCïïïãý7¡N×_oÄä_ÿÈvµs)M½Æ g¿H´OÙUär06†<õþ‹~W"¯Á:å0ž>n;q]ŸEJô9tkÕ _7m™=Š| ï§ø™éaè*ĈÓîû ¼g®ÂO;mM…•s¾dii‰”d’e+{\ppp¡u)j:Q^·bÒ0w[8î'=j¿Þ¶ü«±ëÏ:¬Q‘;âåὤ}¬Ì²|_l_ÿ=„ ²h¤¹÷À€µ¤Ùž=í?ùFz,³vÇŽ“ÇpéÜiìþó,²²’áhò·î(`òwÑË /"S©0òí ¨ådŠ“;‚]¯;FuyV*·¬Ù/û¶Äï»öàãA“Š| T+ûædè ?ˆþÃ'ÁwÖÏx£›ôfZ[Y!EPy[§ á ²© ›2–=Î××·À41p6(/q ®Y?oF\và[µþ÷nOá[a ÝVŒˆÊ ¿€Bæ'‡G­ø¶¶¶HˆJ@|\l¥n=!œÕuÖôgUÆ^ÂK>/â~­ÎèÑÎ fFÉPd©Å.ßÅ.—ó¼f5éaÌÃØT«³<­*ŽÂ²±a±Å¾ÆÓ0¸Ð~d=z ŸƒY«÷a¤÷£1Œšxx jñÄ«Íû«FHÈExx ÊìÊTÆC‹T6ÿ*Á•žÝaÕÊÜëgp ."ª—W¾Uªùþ ‰DçI«‹,;Ò‹‡Õ{¦—½2êXÄÄ ;XKÍ%Ìbccáàà[’b¤ŽDÒ®W;÷Rà,<<¾e9˽qw÷zX …jÅylør±´¼ÚŠZNC†œ‹wT«^‰®!]RÙÓ>|ˆjB+î5ž†A…®¬øË>l&ývX\ ò•¹xBgÌÁg+`D]»c°tn/¸¸Æ•©Œèi=>Wu;KÏ‚¶nƒ‹ˆªU~ýe ºÎ"ìïbó®Sèúùwðlv ˜0 ;/Å _Súu!ŽÅÈÐVXÄÔÔªÌT¤e©a©JÁòß#VH3ÉÉ x¶k_ärköb?8O胟^Çø®Ï"ùÎI¬Ù~ÃWþÓ‡+Š| T/ûæê_èÊA ÇVUÊÌ à`O¬š"G§7b±÷}‹x€³½šbRžEºŒ[Œ]‹ÞÂ/«1fìX8M‰‚Uõ†˜º2½[ ¹×ºLeDe%†¬i+â+ŽÁED•T{÷:p­a‹Û÷ -[¹†ú4yº1n«ãh×r.îEÇÀ«ÿ,|8´lŒžÃÿ>Þq]Ýñauˆ÷|/¿Ð÷®Å¥°¦s­ŠYNKºˆ´$òA"ú~¼ ço>ƒkL¯æXú^/ŽÁEDUŽÌ¾¦Ï*üR:2³:Øðßm•;ÑhÒ÷]œÜ5L‹?…­¬Ëi C‘\ ßY›p+:!wÚ'¯vÂìמ‡LV9> ˆˆ´IîÐX]‹,7µ­ƒao<ùáͲ.§ ]DìŸ ðût3â²û"Š­Zbë–ØÊEDDUCQÚð÷e¼þåö|cp‰×PûqQÕÂÐETA¾Ýr“z4WM{+é Å6nµu\3""Ò†.¢r¦R©ñáŠýøfóñÜiœ°cÞPŽÁEDT…1t•#ñ0¢x8Q<¬˜ƒcp‘ˆ¡‹¨œÄ'§aÀœ@Ùì‘Ïã“Á!¸ˆHï%Æ «pí¼æùáñW[†O;ì×|ŽIóV"^ÈrYj3¼òÑ7øüí>0Ô¯ªúºÔع<þ“Àoþü8¡knQø±-ð? '¯EÃÜÑ–`Æéb—QFUÛßço£ÿ§›¥Ëûˆ8”ÄX`òKÀ‹ù§Mé/¯?€Feÿ¬S'‡á˯7bò¯ÿaD»Úˆ¹ˆ¦^cг_$ºÖ1*‡ÊW>zºÔX<®'Û }S§ü%)71rÐhtœ» »FûàÞ™ té>MZ\Å€qå^6ÐÓ^7?ª~?x £¾ Î7צYл Çà"¢Jltàö•§[GRð¶÷“-3ù{ Ïk¹OeÖîØqò.;ÝžEVV2MâÖPÇöéêWIUxèR+Sq?>5«ÙIÏï\úì; çæ]ð’÷sejBìôÆ—ߦ&÷X‡Ì<Óoýˆ“fÞØ>ÊâÅÅëzùÁ¿OÖnܯ‘å^6Ðsx¹üŒHÿƒËÁ ÁCк1Çà""* eì%¼äó"î×êŒíÜ`f” E–Zl[1XºÔ©wñz÷ÎHí¾›æ Eüåhßþ4|¾ î-˜‹ÃŸÿ/^ïø„k•¡U[!¥CU $,4µÜÜa'4¯›»;ÖœE˜Cd¹—©¥gT•ˆcpM]¾ ÁÕØÙÁó†p ."¢'p|Ër–{ãîîõ°ö±jÅylør±®«U¡*4t]Ú±§ìýpdÖéùæeß¡ÙØoüÕh$…nEóW–`ökaQNÉE¡H…•s¾ dii‰”d’+ Œªñ0âk_nÇÆñdóÝ%ž¤9sÑNøréÒ )i~ „d™«ajj Uf*Ò²Ô°T¥`ù‚ï+¤’diÿÊËOìê•«è5hlÅãrêTüµï8ü–®“ŒmãÎh˜4‘Y@£rª…µ•R„”·J¡PÀʦ.l* ìqÁÁÁ…Ö«¨éTyœ OBrZV‘åJ•{/ÅâÒÝ”ÜimêÛ`ê 8vh¿6ªHD¤í{ ¡kµ&xÙUÓ®Ï7?Uày aë YÃF¨Q£6Þœý¦½†Ù¯uEý='з©M9m@åQ¡¡ËØÈª,ÍŽ,ëÁIüw»>m™Óù])ìÈd—ãñ¹&ˆZ¼ñ*ÀA:¨FHÈExx Êìʽìñªûúú¨“¸ ›N•Ëà¾_ævˆ/±}[bÉ„ƒ‹ˆJM¯¿€‹ÁkÎZ ì40d‚¸¬Ÿz•rëzX±ëtþ‰}ÿÁ¤§^såU¡¡«™—¦|ñ&lŠ¿} «®¾pË>–x÷h0®:6s9î³\¼¡3æà³•°À¿ "Ž®ÇŠÝ1X:·\\ãʽŒ Ç“.ŽÁEDUR»š•Y…†.×noà•_û¢~uGظ¶Âï;—Ig+Æ_؆ö/MÅ[Kÿ‚ù“î¸2BбB•€R<¾|°'VM‘£Ó› ±ïûñø%p5ÆŒ §)Q°ªÞSW¡wckÈ`]îeTµÉeÈR©ñéÈκ® 顊2BnƒOÖüƒ—%ÃØÜ ÆÙÇm]žÃÊGѽۓ¯ÓÔg“ÓŠ,vñòÅî…Ϋˆ2ª:ü:5Fà¿aº®é© §K•ž€£÷ã^2ÐúùnhTÛr‡úx±]E¿2Qù23Ññ„‰ˆ¨Ò¨Øqº·0äùŽ8œàˆúNr\|k"¾ûãoŒöáˆÝDDDTµThè:½å¸â:7¶,€…¸¼mzÍ^ˆ×üüªQá*4tݼŸoKKäÞ£7¬'MC’°ç™_TI(Ò2Kž‰ˆˆè)UìáEµ ÆÆ&¹ÏeÂc#aš_V‰ôÌļôÉ&]Wƒˆˆª€ ðwïÞÕ:þ*æ,Aß© ]å¢ÿ‡«4õ7˜›ý2FUäM•Ç?ço£ß§›‘ H—ž›˱jŠ/^}ÁCÇ5#"ªLäÀB:Ê(z–S„Û;Ú«’ž«ÐÈóÇ—oà€Ó8ü8¡kE¾ Q©ý†WçoCj†RzncaŠÍŸ DVõu\3"¢JFnØâ~/XfÚÈŠÊû=õËì^þ^]²w$ ÕKã±nùT35Ì~Glg¢*ã§gðÎ÷»¥KùˆjØ[bǼ¡hݸ¶ŽkFDTIÕ˜ÄoÔÊüÓl«ÖBøªûtëWÞÀñ{î8yù:Ôq¡ÂàVør]_|9Ê0GP¯ðÐ~ GZ^(³@³¶Íai˜–* µ±Ö§¿ý›;­Am{ìú|9;ê°fDD:pµ;~ýéÖ‘zNs‹ÛXúeœÃòO“YcÌØ‘0‡–rrÇÐÞ­±æßCCWÙì^9Ç~7+âÕcÿµýhÌö6ª Y*Æ¿ËvœÍÖ²aM©…«¶#/ZND¤Sr'Ô¬þh¸t{$\IÐa…*V…Ç×6j±O—‡6|–!.C†¬,#t1‹>õ‡•¢Ãmÿøi8y-掮˜°3FøHçe”µŒ*/±ßÖˆÛ°åУ‹T¿Ð¢¶|:vVE| ""íQ' 6^Xj‚W||„àe¨ ª)óö> õ>—[ÖBÚÃKèÚÜ ‹šwÂÌ^æ9h4:Î݆]£}pïLºtŒ&-®b@ƒ¸2• ô´×õ&Sâ’Ó0`v þ>;wÚ0Ÿ&øõC_^¸šˆª¶F=ù2™÷€0oÀì! µœ¿,Ÿº¨bñÛ/[ÑeÆ ¨â¯cË®Sè:ïÛòYw%T¡{Ÿš š¡‰ökfb,å5ѼIMé¹yµFhâbØØ8Üúû0Nšycû(Ë€º^~ðwÁ«Cd™Êz×Ú¶QéÝy˜„>37âüÍû¹ÓÞõkEow‡\ÎöI"¢'fRp|ˆ] Ô[Q.«TgeAeî /›ch×b6îEÇÀ«ÿÇøph‹rYeT¡¡Ë{äÇð®ÈxŒ¥G_|<äGŒ{uFú¶ÁýKûqBÞ [·CØÏ«PËÍvòœ¹epswÇšó¡sˆ,S™EG:"Ž.ßkÆÜŠ~Ô' `”7>~¥“kEDdj~8Lë•ËêŒöCrŠfȉ÷&–SËY%g`ÇYä¨áò 2þ»k×ì}# Õj7¦ª¡P¤ÀÂÊ9_H²´´DJ²Ée,{\ppp¡µ*j:•¯Ð¨ÌÝޤ´,鹑ð¦½ó‚3ZØÆñ= "zZFvš•™A…®ð=KñÞêÛ¸¶uÌؤĬA-0åëMçd…!@åmR(°²© «²•=Î××·À4qg_Øt*_;_ÇìeAPd.K3cü>Ó/µo¤ãšiðËVèºv æ. QÓ<'£~ýº¿‰&ÞˆZ¼âIÒ¡B5BB.ÂÃcšxØ•©Œ‡+‡_÷œ‡ÿ¢?¡ÌRIÏl-°mî`tlê¢ãš=bP¡Ë³]{dN›…íçîÁ¯ym¤ÇÝ@àŸÇÐaÜT¸x7BgÌÁg+`D]»c°tn/¸¸Æ•©ŒtïËG1mŨ³Ÿ×­n+ zÚ´^5Ö‹ˆˆèqºªµ~[–Ü´—;ãƒt@™%C—¡s0ÿí®ËðKàjŒ;NS¢`U½!¦® BïÆÖÁºLe¤;*•“~ú ‹ƒNæNóx¦þül˜¼ˆˆˆ*ƒ ]bGú“­0.^¾Ø}¢ðþUe-#íËĘ̀¯ƒ±þÀ¥Üi=]ðÇœÁp´)â’SDDD:f`¡‹ ]bJ:ÏÝ‚½§Ãs§õïØë§÷ƒ…™‰î*FDDT†.ÒÑq ôýx#N]ÊöVŸøabOÉ‹Y’ˆˆH÷ºH/\¿' zzM¸Ï1óåŽÒÀ§2Ï#%"¢Ê¡‹*½ÓW£Ð{æÜO‘ž‹—òùþ1þ¥V:®QÕqãᘙ¢®ƒ›®«¢·º¨RûëL8Í DbJ†ôÜÜÄ«§õÃàçÝu\3"¢ªC-ü[þ߇¸{ß ÜÚv t]%½ÄÐE•Öï/áõ¯‚¥³EöVfš=]š»ê¸fDDUË©[{p3æ"d¿ s‹1ÞûÛrYïùÝ+ðöäù¸— s§†X²¯xnKCUJßÀK÷A¥Ö {ZÇÉ;?†æ jè¸fDDúíƒÀ®¸›p­LËŠ-^ÿ\ ”nOê­Î_â…Æ/ç>ÏŒ:ŠÃgâãÇ1ªƒ+ÎlœƒÎCß@‡ðC¨on˜}uº¨RQ !kÆÏcÁïGr§¹¹8 k(ÔvÐa͈ˆ¨<]ükî¹vÃËíëIÏ[žˆ?€™a.CU™Y*¼¹p'~Ý{!wZ[·ÚŠêö–:¬•·‡ÂÚÁ&9Kn GSÖ©¢1tQ¥ HËİ϶bDZGMÞ½Ú4À¦ÀÚ°ÿ‰ˆ´iá ¥ž÷xøN,Ü?¶Àtäx¾á@Œ÷^TæzT«V ‰÷¯ M XŠÁK•аë¨ÝضÚÚÅÐE:÷0!ý>Ù„#—ïæN{­»'V|Ð&ÆF:¬QÕ%v÷Øt¦ðP¥† ‡oü-ÞC-ÛgÊ´~Ï3¾–ï Å»=ÜpqÛ·èôö6œ¹y†z]†.Ò©Û÷ÑsúïˆÉ6uH;|áß•ƒžéPbÚCÄ(îY®TeâÈÍíÐ|b™ÖoR«#6¯Ÿ±ïöÄçqi°ªÑÿÛø+žµ0ÜÏ~ƒ ]™q×1e¬?Öî=µ© |ý?ÁÒyoB|Ãmÿøi8y-掮˜°3Fø@|{ËZFewáæôšñ;îÆ$KÏÅŒõõ[/àƒAít\3""²³¨Ž¯^€\VøeÖTjU‘e¥Õ²ï8nU……®,Ì7!¯âzÔ>Å…`øK/c«_ ÷P`ä Ñè8wvöÁ½3AèÒ}0š´¸Š âÊT6ÐÓ^׬·þ½þŸnF\ršôÜÔXŽUS|ñê :®å(.T=mઊ *t©îÁòIØrkìÌ„_†ZÏ!øÄE©,üϯqÒÌÛGùÀXÔõòƒŸ¬Ý¸ ^"ËT6Ðs¸Ž·X?ý†WçoCj†RzncaŠÍŸ DVõu\3""¢ŠcP¡+êÂiÇ3ÐeçWÀ‚@ÀÕMH,zPÄo™ÉâXXÂ}–°+„m;¤“Ö½ ø½%ü. ;)‹æÂ^¸š®kKZdP¡ËÙÍÖéñˆOS£žµ&"Éäriè&ˆZ¼ñ*ÀA:T¨FHÈExx ÊìÊTf üåJì·5bÁ6l9–;í…õ°åÓA°³2ÓaÍÈ ÈŒ§×…öwEÏ#îØÌžÕ¿À%²ë—?t=Τ Š¾l¶Ñ^ÊKò?À­74G‰ÿåm­‚J²pIxhÛS_Kµ]»§#îîÎ2³‡[OÈng“]xˆ|;{>S Ñ>ÀÔE•$]0¨ÐeÖ°†·R`ÎÜŸ±vþh¤Ý>ŠeþCßo¿‚‹·=:c>[y ü» âèz¬Øƒ¥s{ÁÅ5®LeT<ñÌijñ÷ùÛ¹Ó†ù4Á¯úÂÌÄ ~õ*7ÅQà¦ðíªâçs.|s™¯•*•+§1ÀÃ•Âæ%,3}È~ÿ\¾Õv­ž\FºpKÓÜgfß§[ì€ê …/sýš*›7„·:ûýU ÷ÊŒGódf<*Ë*¦ìñåÄ×Ͼà۬¸ËQ‰¿ÓÆšP!é&VfÏ[‚ŒëšûÈIåõÓ,b ˆxMÜ„tñ^¸¥fßç–¢Ö<[O|- a‡öPø9Ø ÷še'¶’&Ä~D¹Ó,5ýyÄVñ>çÓU&Ô(ßMlöÉéß$Öå±úH­¯YšÇ¡§…í<¸d¯4^Xî¡ðþ>Ó¨Q§ðßA¨ ù-䱨ä”û»”YD0§r£~GS„ßËîBàò}C×µ!-cè¢Rûh½º ˜N¡q²µÀ¶¹ƒÑ±©žœ•#î¬l^B×Ï(Ðá\Ü!¥]Rå4Í ’‹Cؙȭ³ ·L#á[½"câ…€ ÞÔ×ttý±ðþÚg$3ͽ‰Ùcϋ볥cNBÚ8ɵöÂ(Õ–b1a‡Ýâ“ò-)Œ=ú3ó¶ì0¨[8 ›7#ÿr9óJaN™ÿ HÎ…tá ]ÊIœKPãá­·sÞ›îê$öÇ“ÕâUL†¦õ*éø3Ò‡üuÍá´´‹šûÔKš¾GÙ’•j˜ŸfR_­ô*®XR'pqG¡‚2+ë"³ÐA(…´l­ KFVÂ|–š{¹•fñ^ SF6Âs‹ì@%LS ;—Ø$áÜÂM´p!Üߢo ÷ÂÏ5¹ˆ3ÞBýjµ\êu„ûz«¶æ¬¯êΚÇb¹Ø!ý“W¡ügfÔ2•Æé›³±·â¯·iúÏè3ñ¬½¡ÿCП]ðçý[øì¹¨?¸—H *â®A‹ýŒT)8{¬#„Å “£)&6%¼×Óµ÷úM–í羆yB^lî4ª:x¦üº¨Üý÷íH)xU ¹Ki²ƒÖå;jÝUaW´ó=ŒàúøÙGÒ7]áOGnšç±É£`#MÏ~,Ë~,+ì±Iö.gšqáëÈ÷:yXGÎc“B^S/WÎ~Ø;BQj\5Vcî %dÕú¾£óüÌT@¢¦¢Åu';D]ξBU”pýèÔÿ§am¯ S5\„•'T‰!«šªjº<& 8bKÕܵؿl0â2Ï!>8®JBû¯· ËééëY $f&`ÏÃ{ÒW€@!ÏNÑu…Ê“æ7ÝÂ3bp4.ƒLº£Ž®ëTŽb-úà÷È/¥#æn–¯À•£ ðÏÿ&JÓÂmÿøi8y-掮˜°3FøH%ÊZfÈ"$¢ß§›qFØnqï¯4ÃWýŽ9¿"Ãûn4ǰ¶¶B¸]®+˸¢Ã¥C·ï\Ö¸%¬û‰·¥°Ò²4µ…³}#¸:¸IACü0ÆÉC \6¶+r9Í7ÓE˜Ümy©_«BˆP>÷/°÷wàðN͇…iÔxa°p„L‡jغ¡-PDfwn7žÇÙÈýhY·[…U½"ˆ- Aç¾/q¾-g¿Cûú¾z×Úµõü’b}‹¿—â™âû§o­]—¢Ž <&Dø¯XQø§iªR!„Êïð^×R\Œ¾’ :»Xj+Šøw'æº-Ób­H× 4t)±xÊ›¸cT9ã¡«Snbä Ñè8wvöÁ½3AèÒ}0š´¸Š âÊT6ÐÓ^—Y¡‡DbàÜ-ˆŽSäN˰}Uø¹*üx5­ ùzÑ9¢ý3‰hož \nQ急6÷D¬ÒT W·¤¾W{…Û÷RkEN§ÚÒEÕ¶k |‹l*„+7)\¹ !«šµK¡­9¿‰g,f}ɱEá”ð­[ÜIˆ!M«TB¨ ÂîÍÀÁ !¦ðùê¹]݆ÎÏæN>'|£ýž_äK$¦=Ķ Kõ.t‰‡.í-k >õ~‘óˆ;·”Ì$¤f$ÁÊÌ®Èù*#‹%Î#Ö[Uô-tÕµw“Q‹'¯Ç¼Ž·–jT~Äß¹£áÁèãá/|Ñ{4,Ê•+Wиñ£V»}aëp?é6jظꢚ¤ºBw~‡¥—êcæk.ø5Z3íÖß8iæí£|¤k¹ÖõòƒŸ¬Ý¸ ^"ËT6Ðs¸n7´‚üº÷Æ~û'Ò35ßÒÄ‹V;®;Þé× xD}.MÿöZ\Ìeâ"Ž+ 1µ¦E«(y–Es$É\©H–ú^ݾ'\%=Vd”~$sq§[ݦ®¬Ä€%†+±«ŽÝ³0’q©ŽBØš—<€¦¸s{˜©Ð%޽zV²öoÖ RZ˜ÚÏ>BÐê:hXøN·u½žøáŸIB¨ú±Ø—Øâ½§«³ˆý¿ôÿS×Õ¨0ýš—n†ÈÆÜ¿¾¦ëjTñ}§~ز¢@Ùù3;rW·®ËÀUÅ\èRÆ]†ÿ¤Ÿ°0è(2·LÈŠZnî°Ë=º ƒ›»;ÖœE˜Cd™Ê í|©,• 3Vý/7>êûãhcŽ 3ýÐÝ«~ö„ÀýE‰OÁ±X5NÈÔhï$C]‹Ç~FŽB°ò”néÆ p7ÊÜ~Š;ñ!¸»©Lý®Ä@åêÐD:$!ö…pB–¹‰åÓn:üšOn:~Y´n"Šh/{#†,o?À³}öÅ’‹÷óÈKå[O"*ÖèÒ-¯àà`øúúê¨FTVèR+±è4³}›:bë–GE a‡oaåœ/$YZZ"%Yä2–’E:^]ðv»ž;­‰«þ˜3œ52ïw¦K#¼o¾£éG%6Èl¿çá,k[y ü» âèz¬Øƒ¥s{ÁÅ5®LeúîÇí§ñÞ{¡ÌÒìüÍLŒðÓû½ñú‹ÙÑMÜ#®™Bðz(=/¥Êmå*ŒxPs¦`©Ã¹æÌÁF05¶¨ðí©”ÒÀ‘]šÃ‡Ç÷ ?ËBZŒ€æÏk†xx¾`­_gØQéPè*žÌ¼.~ \1cÇÂiJ¬ª7ÄÔ•AèÝØZˆkÖe*ÓW™Ê,¼÷¿¿ð?!tå¨ao‰­³£CSgÍà¦÷æñA¹å×j¬W㦢ð¾Yâ¨ámêõä˜3¢ŒtàÔ~!hÿ ßlÓ ¹nœØù½i;M-±S¼CÉC‘~3èÐ5àãu繋—/vŸ(üP_YËôMLb*ÏÝ‚ƒçoçNkñl ü1g\kØIÿw§ Éì®T–šlŒÌÂîè⇠•FX¾½GòAlݪr²”ÀÙ5gþó_ø|â°]>~šáˆˆ¨Ê0èÐEù…„?F˜¿q/>wÚowü<ÅV¦™BØúˆ][v4V…ßn«[ô˜¡ùH#,Ÿýïwý_9×\þÛD^|GkÎ,Œxêæ¥ãÀ@MЊ)b —g†]õª` %"" CW|ì^ùü$¥j”xÀ¬ðéÈç!O‚õ€Œ[RYtš?ßRálBþ¾[âE‹½¬…ðïlä(Òãaef_Q›RñÄC‚³_T¶þ» X°%?«!À¾ ÀÁ­@Ô­Â×Q­Ðm°&h‰—äÑÃkQùbè2pbcÌW›ŽbúʃB`Ò„(+sü2Õƒ;ÕÖÀCñz‚*(UšŽò[çX¢8Zûȶ³ÐéÙ%^»Nµ]ß®o—Ï!!pÍÉ\¢Ë'ý€Ißj:Ä‹—â¹}¥ðeíªÞý5—áñl§¹Ø4Q6†.––¡Ä[‹þÄê}s§9W³Á¶¹ƒáåü¸ÞH×ñŒÄUáYˆÈ×ç[†Ç+mfÀº”-W¸r„Þ.âúoâ¡ÇN}5‡½|#þIQḇ0Pwc’1hN ކÞÍÖ±©3?éZÊ_…Àõ=Ä ƒ'eª±6R…ƒòJÇÓÓi>Üj´ÖrÍu¤¨ÀU3K CO ÛP M7ÀĬâëGDDz¡Ë¼rçlAăÄÜiâØ[Kßnóè‘@Ú¨Õjü£ÆšÛ*$)-kfl‰A-ÞCÏ7 àÒÌ ìpê °ó·â—Ø'ËÔxi´ðÜ Xêï!DD¤ ]fÃÁKxã›HI×$)#¹ Æø`r×ÈnOÔ¸“ªÆò›*„&çoÝjU÷E¼Þ~¶á^õ^ UWÏgþ.ÎR“K^N<„ØÀHOF|ÄÀEDDeÂÐe T*5æ¬9„á–¥l-ͰnJKô­³ˆ:‰ až »*l»§FVž¼åhU£ÚÍAÛgzë¤îFøt5~¹¥ÂÑØü‡Vo ÿŽó¥ë#굄ÍÅ£Å~Y=œC[ÇG«e Î3çùfðAÀÈDsíD.""* ]zìÐÅ ž„èxEî´O|`v÷õPg%a×}6Dª¤Kùä°4µÅðV¢»ûHýÞ!%IÓëüÀ™kç4‡‹"ö¿jÖYY]€ç: ió¹’ÇϪ]X(¯£»‡óÔDDT.ºôÔª]ç0nñ.d(5£˜Ö²MÇÚ׎á…¡¸™¤Æòð,ÜPä_¦cƒ~x­ÝlØ[T×AË(3]3NÖÉBÈú[3Xi–²èùÅ3 ›¶ZtÖ´h5iS¶±³Äë"[æj=ÎàBWȾßðΔ¹8{ãL¬«cðøO±hÆH˜Ë€ðc[à?~N^‹†¹£+&,ÁŒ>Ò%qÊZ¦mÊ,¦­<€o6Ï6¨Y8–;3…t(qÏc§®eû ÆtœçêtÖ~…ŸTVpý´úÏuÐ/""¢JÆ B—òþq 8#—íÁ¾¡íð äOøtöÃOm½ñn'FŽs·a×hÜ;„.Ý£I‹«Ð ®Le=íµº}ñŠ4 Ÿ·»OÝ”ž;X¤ã»‡1²õUÍÅ©Ãò_œÚXnŠ~ÍÆc@ó 01ª¤xЇo…iBÖÙ졊=ÃP¦¹h´Ø'K¾¹$-m´V]""¢²2¨Ð•©¶ÃôÖ`˜¸Œ„}s-ÏžèÖÜW®ÞÄ-åIœ4óÆöQ>0ÊêzùÁ¿OÖnܯ‘e*è9\kÛv%2ý>Ý„°ˆXéy·,ú/Ì-’° ¬àÅ©Ÿ«ó9V¦™øºßQŒi;Ä‹S_/üâÔŸ™¬kw|/0ëeÀ±&ðÅÀÕ­èysÎ0ûe‰Ã8DÝ*~ÝNµ5‡ ÅÎï-»h:¶é9ƒ ]y%FœÆpßWà=sxÚ!hk ,¬œó…$KKK¤$+¬([Ùゃƒ ­KQÓKcûÙ‡Xñï=¨„„çÝà.Vÿ™¦ ˜RðâÔÌ;¡¥õ$„šaGèŽ2¿fIj„ŸCë?¾\áý~$Òß鎣ƒg"ÉÉE*7NO…ã˨~ë"ªE\„uÌbéÒÔ1.îxàúºz ÙÉYÚ©cÚ© Â*l[ˆˆˆ´Å CWø±@ô> ¾³~FÀݤ¾µ•R„•·uJ¡PÀʦ.lÊXö8__ßÓÄÀUØô’d(³0aÉ,ÿçÌ•˜ÓëÞê|¿ßÉ*äâÔMàßés4֯ũŮïå»N¡YJ"|¶}¥^!ä8zªøëŠÝ=ÛçöË2nØ 5åF¨Yñµ'"Ò™§ùN†ÁàBWø‘õè1|f­Þ‡‘Þr§7ñð@Ôâ=ˆWÒ¡B5BB.ÂÃcPfW¦²Š:xw?>C¶àŸ håò¿½rŒc1õba§~}<ýµsqêc{€O^É™œñMK _N¼´Ž{«ìIÅa„phü„ýÀˆˆˆôœA…®¬øË>l&ývX\ ò•¹xBgÌÁg+`D]»c°tn/¸¸Æ•©¬"œ»qç"":³{žÆëÎàçÛJ„rqêQ梺µK…Ô£€w€OG¸'>Ú¨ÐBìøÞ‘gÁÀBWèÞ 8ñg{5Ť<Ó»Œ[Œ]‹ÞÂ/«1fìX8M‰‚Uõ†˜º2½ë2••·­‡¯`äÛájƒ Âè>f^Vrqê¹hûLÅ„¾\âáÁ+g5­['÷k%U«Šž_€T¼V¡Ø±þëm€CŠ­‘ž1¨Ðå1d6ÔêÙE–»xùb÷‰ÂûW•µ¬<¨Õj,ØpŸüò7Þ}þ<†v:†µw2 \œºwÓ7¤‹S››XVLEij Å>[âåoÄ¡Š/+s+á‡ßxxO\Û…À¥G#Þi‰A….}”šž ÿ…;qü½y׌îbÑ -]œZý=ô$pbŸ&l]9Sôu ÅC† ›í^ÎÖ\ûP”¸b¢€o‚¸ˆˆˆŠÀÐ¥Cwc’á÷é&´pÚù#Ž ðAz‹S¿Üzº¹½Z~§[£NíBÖ_%·fÙ:m_Ôt€ïí«i¦§§3‡i7;Å‹ƒ›²…‹ˆˆ¨X ]:r<ì.ÞY´ovÚ†pÓÛØ•¿¼Ü.N-v|[³Žf÷ͺz@­Yr!ع !ªM7MÈrk©iázœ™0ïwà³1šöÍvÍÈôDDDT$†.X·?»,«Ýâ`b:TyN,—‹S‹—ç;À‹-YbÐR$=¯xIV/!«»¦EKlÝ* sK `}ÙëHDDTÅ0t•£ëW‚Ð ýä\}'S¥FбÇ' ®å£Q½¬SÍ`í¦ÀÑ[y ü» âèz¬Øƒ¥s{¿ ¹%ðÙ„ö>Ü_ªé4ODDDUCW dæuñKàjŒ;NS¢`U½!¦® BïÆ¥ ÞÜ ×Úõ‡;Q•ÇÐU .^¾Ø}Âp›¼‰ˆˆ¨â1tiCW3äN¡"CÞ>n›þ2äíã¶é/Cß>*C‘0tiC‘0tiC‘0tUuv.€ÿäð›¿?Nèªë=b¶'üØøŸ†“×¢aî芉K0c„OË$UV!û~Ã;Sæâì0±®ŽÁã?Å¢#a® G;§8“j yÎÆÈL°ît6у?¬âë¯ïïÛ¥Àyðzu^¾iYÊLxûû¾í¥ï]ÿÆôæ},jû à÷´ÈÏ™¼wTn*é§¾Scñ¸žLlƒöMt]™rPôö¨Snbä Ñè8wvöÁ½3AèÒ}0š´¸Šžöº©îPÞ?Ž'bä²=Ø7´„ü ŸÎ~ø©­7Þ}>ñ©5°çö]t©©ªUg]}ßDM}Œ4á–C_á}òÊK¶?Ô³÷®lcÄéÉûXÌöéùïi±Ÿ!TðÞQybèª ÞøãÛ´Âäë©ëÊ”ƒ¢¶çÖß8iæí£|`,|=«ëåÿ>X»q—ðÁ1\gõ-­Lµ¦ÿ°ÄK#¡þµ<{¢[s;\¹zêf2$¶úù½S_dýõý}+H &!Å{ ^ëà uô5½{ïÊò7æÕ!RoÞÇ¢¶OßO‹û ¹¥TØ ¢Ò7“[ÔtÃèn¹ÏS"bÿE5>ÿ¡%T (OÁg£zãØ¡³0²uưñ³0g‚L+û† T qEÖ_ßß·Ç¥Þþ3W†aÓÙõ7IY̶WÎ÷®lca‘zò>½}úþ{ZÜgHØÞ5ðÞQyb袧¢P¤ÀÂÊ9ß„¥¥%R’:«SY%FœÆpßWà=sxÚ!ý¦ütC¯×'â×Õž¸y,ƒ¼ãêÿ!`xK]W·DJ“¢ëßÒ€Þ7±•ë—/ç¢Ù¨iðªn"M)nÛõá½Ë«¸¿±dx é÷ôñÏ ­†ýÞÑ“c袧bme…áÃ#ï73…B+›ºº¬Ö ?ˆþÃ'ÁwÖÏx£›´-æõŸÇÚuÏçÎÓôùáø`Ä*,Ù¶S/vÜÅÕÿù6†ñ¾‰ÔI—𿵱àÔÀÜiúþÞåUÜߘüýÊïiaŸ!†þÞÑ“c袧ÒÄÃQ‹÷ ^8HÍäj„„\„‡Ç ½i?²=†ÏÁ¬Õû0Ò»Qîô¤û·pé^Ú6o”»-Y*ŒMôãϦ¸úÂû–#loÂ]»£k}‹ÜiúþÞåUÜ{ÕÄÃNïßGCø=-ê3ÄÐß;zrú÷ D•Š‹÷ tÆ|¶òøwAÄÑõX±;KçöÒuÕJ%+þ2†›„I¿>,ä+K¸ü'^è÷5~ûk¶©‡;çv`Éú#þÓ·º©ì*®þ.Þvzý¾åuôè4ñê ‹<{*}ïò*îoÌÅ5NïßG}ÿ=-î3ÄÐß;zr ]!#-[!T (33€ƒ=±jŠÞ\ˆ}ß×uíž\ ÛóKàjŒ;NS¢`U½!¦® BïÆÖº®u©„îÝ€cp¶WSLÊ3½Ë¸ÅصÐD`úÐöðOH‡¥½ ^›½ ðÔY}Ÿ„‹wÑõ— EŸß·¼"nßF­µóM+nÛ+¥2þÉ`­ïcqÛ·ø-½þ=-ö3dÑ[úÿÞQ¹b誦8›œ¦ëZ”Ÿ¶ÇÅË»Oøj±BåÇcÈl¨Õ³‹,÷÷™tÓK2ãbë¯Ïï[^³~¿Ppb Û^é<Åߘ^¼%lŸ>ÿž–ô¢÷ï•+†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è""""Ò†.""""-`è"ª¢Rã"±cû„ݺKÇÚðéõ¼ž­)•eF]À/àɯQçñå¦|4ñe˜Èt\i""=ÆÐET]?´½½‡†Ý‡¡[«Fˆ»q ÚOÃÀ¹k±p\O(…Ð5oñz¼/„.Sµ Ê,ÕÓ½ :CÚõÀ‚#‡ð¬Qùl‘¾aè"ªbÔi·áÿÊ8øÎÛ…oz#§ñê‘=1ü£M¸ûF8æ]@&‡±‘<÷©29[7"ìn´ì‚A½;ÀTX‰Ô¾Úzc^tÅúÀ=PZÕÁ°¯ÀÅÎ?LÇ?o`ñÜÏ0î½é0 ?€íC ,ж[?!ø= 6¢‘¡cè"ªbîý·ÿeµÁïoxç :5[ ÆÝƒ¥Ç©y¦gÞ;yßoÂ_†qj†>ï Ëž1¤ý³ØúÝ8l<4[>Ä„bÎ'Óym ^Ôÿü<½†ãü¦OÑèÙzÈ”Y£U§È<û+^» ŸÌžŒ:ª‡˜ýj7ÜZqc:»hõç@D¤m ]DULdD,ì4Fµ2滸mŽØôÄíùHý»ú´­†z“qõ“WÑ@x®JµÄ„¹ÓÐÄR/ó‘˜;p’Ô2x¶l S#têöâ×M‡Qø ðCuKcôî;j ‡òßP"¢J†¡‹¨Š±²2AJb”jÀè é]¿~  MË£ÙS2t×î©Ð@x&w¨šš•šš™ÊLéuòòü>Fÿ;Íë×EC¯ÎðòŒÑë©·‹ˆ¨²cè"ªbµkë+‹°çj^jló¨ ++–¬F_ÿñ°/bYkkk<ûâÛ8³á“}°”—„ÿd(±o–Ì¢&>]¶Ÿ„Óÿý…ÙSüq3uõ.ó6é†.¢*ÆÔõÌí±C†ÁzÍOèò\]¤ÇG`áÔ1XêŠï›Útë¨ÏpòÎhãl‡—wcÖ¯×ðíüwPìÑJ¹reR3ÕØÿÛ–wÅÌ×}Ðæ?øù,ÅŸ‘wÊ};‰ˆ*†.¢*Çï,Þ ›¯?Åû:àvl*L-íÐmàüü!eù:Òçeç1¿‚±=ÛBff†tµ>øâ'˜‰g/óŠr'OôöJE/føæ+œÿ~ <¿É„©< Ö.­ñ㊱¡DD• CQ$3¶ÅëÓI·ÂX´xi×^Ñ<ñ´«#r–D·×?Áiáö8ã¦CvkHþç9ϰòÐܲÑ~ìÃEDUC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘0tiC‘ü3‹jV ],­IEND®B`‚pgagroal-1.6.0/doc/images/perf-prepared.png000066400000000000000000000665101456565230200206060ustar00rootroot00000000000000‰PNG  IHDR]Te‰~ pHYsÆÀ¼jlúIDATxœíÝ`Õð/éÞ‹MÙ£@Q È†²wQhEAêAPP@A† A\,ÙÈP@†¤²¤Ð²GBé¤énò¿wiK7mÚ&MúýôHî½Kò®i“/ï^Þ™k4šnº€ˆˆˆˆJ9´k¦ÛADDDdÒÌ Ý"""¢ò€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆŠìøîo°ïß[ÙÊ”fæp«Z]zõ…gM7µL?Îl€C‹0wGf lhèæ‘‘`è"¢";±çÌY}­;¶„B[¦NIÀå ÿaâx Œÿh-–¿ñ,”†m&Q™ÂÐED:QXÕǺÿ¡Ùã²´„Ìz¹/L~ÝzÜ€¯§£áHDTÆ0tQ‰1³qô©°xóK8òçø6iAìßï3 ¶<Œ©Ëw"àó£X2º  NĶs±ðËõøïʘٺ¢u×þ˜·èCtjT ÐÄ¢]W$ø,Ãku®aÆÒµ¸ƒªõ[à­>Å›ƒÛAt²¥©îaÉ{oã›mûqíN,ìÅý<‹ÅŸ|ŒÖµ¥ûIÔ½ ’äÈ«˜öÚ«ø~ÏŸx”f…gºÂG¯¶3èÏ™ˆŒC•(µ: éÒÌÌ P˜ÃÊR‰+Ç~À*ëšXüÅj4o_[ªÕ`ëüQ¶àf._‰Í}Z!éÁe|øökèÕµNüwO»YÀÒø÷§%ø¬Í`¬Ùõœ5QX1s"Þ>5^Âà§]°â­¡˜¾þ>ùvúµ¬‹˜Ûÿaò+£Ðß/ WO®ƒ£²m¨ À‚±ƒ°üWV¬;ŸgjáÊ?{ðÎ;ÈûHDT ]DTbÔIQøèã/‘fY}ºµË”J%‚.Æáì xÊE{,Rwó–l‚Ï›ßcö+µ7®Q_¯û‡juÁgkaÕ¤ÎRhS <¾2Ž~¿õmµƒÇ>[½»juÇ÷öH¡ë<óìkXûl <ß¿ƒÜó…ºu1c¼zOþ ÷ÕèVU÷6|5ÒßüôúNú¯>×QÞÄ}Ð8¼ò7ž›¾NO?U"2 ]D¤MÒU¼Ô«l3Ò'âò…³¸óÈӾ܂Þõl¥ÒT¹®j«®ðty<ø+&è‚b5p¹z³g_Ír¯‰°–î0ðߥëå’­½Q/ãA$U½Ð¬š‚/^„fèÐÜNÆËãAÔ#¤©5¸{ö¼|X1!ñq”.mˆjUwR€Ñ-[fÛ÷6íÚA †."*†."ÒÒÏtì×ô<¤PšaЈWÑ£o4vwɶ©£³s¶o2>Љ†ZºŒ‹ êlÛvø"*µhyøÎÙÅЬ(ì`o§@¼*M>yµ¦¬9†N>~hëYÖæ0Sf»…Îmˆ{tKj‡öövÙêííûˆˆ ÆÐED:QXTÁÄY³³}{1ßmÙ#Š“‹+ÄÍz¾ô.ŽôÊçV‰ò¿=’Xæ=hTˆSi`ïà€„‹{0kÕa ›» ëßóÍÜæ·/±z÷ùb·!ü•Ò}j¤6Äe+ŽŠ”QQ0t‘Þ94iOG~Ý¿ó¥À“Ñ¥IÄÖ ûѲÏÔ­¤}yºx w“5¨n© M)agpînÚ½ÔÑwï@¥Qà™gžyÊÔ*lÙös‰´¡¶GcT’’™|¸sØS™·ýýÈoHODEÆÐEDz§°óÀ{S†aèìùߤ ÞÖJU~øh>Ü~?ýÛ73t9©/á¯-À·GÀQO¦MÅ=³ª=¢?*;œ»•?®ùšN‚YÌM¬þpÂ]›C¡Ù…“'N£S¥Æº·Á£=Föi€¥+gã¯jèÛ¢BþÞ‰Owž…ÀàEDEÂÐED Ààw¿Å§¹Xôål4?Jk'<ݶ¶<‚þœ‘qx±fÏqx±á éÚRž§«Zƒ–ølË.ôm(ÆUuÄúoâµËФöRT¬í‰Ñ“çaÝO¤^?/uFü{ŠÑ`Κx4n挀·ÕÖhÙu V¬ú½;FjJŠ>~XDd"ºˆ¨ÈÞøê¨´fKsl8›˜w•Ò~¯/—‚Y`ØÔOå%7: Ÿ†sÒ’ÓöÀWÆèÞ›ÊMðåößñeŽòH5û¹ˆ¨hºˆˆˆˆô€¡‹ˆˆˆHºˆ¨Œ2ǰ 3S½£¡BDT"ºˆ¨Œ2‡ÿÄY†nQ‰aè""""Ò†.""""=`è""""Ò†.""""=`è""""Ò†.""""=`è""""Ò†.""""=`è""""Ò†.""""=`è""""Ò†.""""=`è""""Ò†.""""=0ÊÐtè¼6e.Î\ ‡…}E ÿ>–Í kpãÄvŒŸ†À+÷aíZç­ÀŒ!U•JQa]èJ}ðùNÄȯàÐÐ6ú;ÄW­½ñz5FúBû¹»°oTgÜ;½]z Fãæ—1¨nT‰×ù6u6ôƒˆˆˆŒ„Ñ…®¦¾äÀe¦ª4íîÍœpéòuÜL D •7v¿ÜæR] ¯è7ë7ïƒW»Ð¯ómêoè £ ]6•=0j„Gæz|èq>¯Á‚Ï[ ä×u¨âÑNÊŒZ<5ºsÁq -ñ:¼FDDDôdFº²Š½} þ>ÏÃûÝo0¨©v쌇]õlAÈÖÖñq*Ä©J¾.§={öä*S(Ðh4ÅÝU""2ruêÔ§§§¡›Ad´¡ëƉmxÎ|f~‹y£»Ë¡ÈÞÎñRHÊÚ¥R©`çP¥P—“O®2Äò*'"¢òE¼0t•oFºnÛˆ^þs0sí!ŒônYÞXúe[~ÑjÀE>¨APÐyé—ÜOªs*ñ:Z$""¢Â2ºÐ•}þÃ&aÒK«n¶:wo?tÄÌ_s‹ºàöñX½?+çö{ͨ¯#"""*,£ ]Á¿n‰Ûá8Ó§ &e)ï2n9ö-û¾Û¶cÆŽ…Û”0ØU¬©kv oC{(`_âuDDDD…et¡ËsÈlh4³ó­w÷òÁþ“y¡*:"""¢Â0ºÐEDDDdŒºˆˆˆˆô€¡‹ˆˆˆHºˆˆˆˆô€¡‹ˆˆô.M­FjÚ“ÏÖaea¦‡ÖéCéÝ'ÛObÊׇŸ¸]ǦîøséH=´ˆ¨ô1tQ™uô|¨¡›Pdj1ª¤\åqIiˆŠKÌ\·³²€%{òʆ.""¢ô0&•‡~šwåW2¯:ÙY!zÇ[zj• ]DDT¦Ý¼+si±€¥9ÌÍ”†nR‰È«7ŒLCQõôØÕøïzø·û0  ÞÚN-Ê_JšáÑñˆz” B‹ˆM@X”JZOÌ\¿/Ö¥ë¢ìNÄ£Bßwí‘_d[W*°¶4—ƒ˜|i©½t°±„µÕãõÌK©LʳµÖ†¶ü¶±—nÿ¸Ü"s;[éR!=&Qq1t•Q… \#º7Å;«+±Ð¥Ñ±ñIrHŠTDLÆÆ#R\Õ.âðYD–u±¨SJäñ CŒ™ŠOJ‘}0S*2{ÙlÒƒ˜¼žõzz8×5š'+“Ê'ã ]šdü¼j&/ÂÀ…ðÅ„®8ºi Þš÷5¢’HK3C“±ìýØ)'¶#`ü4^¹kך˜8ofŒè ñùE×:"¢²*5M-÷*‰Ð”¢bE¯S¼|hëÜ3•½'J„«¤”4C7=›ª®öHHNABRªAÚ–¦Ö .!Y^ˆŠÃC—ËÇõƶØVhÛÄ-[MÊ­CüòGXö÷9 oQ‰/ k3/,kÖïö±ÆH¿Qh?wöêŒ{§w KÁhÜü2Õҩη©³a~DDéÎ]{ _,ý9Ûá;°D« ¯NÑäbo 7'¸9J‹ƒö²‚“­<`¼¢t)ê]¬QA*ß~ô>ØðW¡îûî³­‹à•˜œ*±T$JAL•˜ URжLZϬ˲M¬˜Do\B’6¼%ÈÛ¦¤_¦/ÉY×_'*)Fº€£c|«–˜Ük²v.§ÄF"VYÍW–×­+4@cwDFFáæï#ÐÊ»_î sPÃk úÍÃúÍûàÕ.T§:ߦþ†ù‘IŠ—BÁõû1¸a1…ºÍ¹ëÚеfßÙk‡µ¥\¥Ð$‘›–´J T޶p°µ”Ë+»ØiC”½¶¼’³-”ÊÂ÷ÿ>sSçö‰ SÅ"œ>dôte8β;æÄ’Q&¿Îúþ½´Œ‹†.Z¶n%]ªsÕØzöÇ{C¾À¸&`¤O+<¸p'•ݱ}p„|û ªx4‚“òñýx4j„uç‚âªSF^#"*1ØüNx,B>µ{Ѹ‰kRÀº./1rOUIƒÂÝr„$šªºÙk{¤söLÙÀÎÚ²ÄÛ‘SO¯:cÓŸÔgocQêmyÑ‹'^aCž8lËÐEy1ÂÐU%*¹×Fò_·påŠ3î_ C…ª ¤R TªxØØUÏ’lmm§BœŽuDDY‰Þ÷"â2C”˜êà¶²DÀËÍ1r¯IIjZ»"ÎßÇÊ7úd;|WÙÅ®ÒuºÊ¢§ëV‚zÿtC7ƒH¯Êæ_£ŽnX‰7ÖÞÂåC¨f-bR*fú5Ç”%[0ÎÍñR€ÊÚ;¥R©`çPvºÕå´gÏž<Û•_9OD\ Þüñ ¢ã wSÕÉ_¿äQ"—˜†°˜dÝ~4¤4›HDT( ]DT&=sÖÍó[‡<Ý1Ý¿úµ®‡ggm}b¨ò¨áZJ­$"*<†."*3DOÖžãWðá¦cøûBîÙôjY³Ft”CW†Ýó†è³‰DD:cè""ƒKKScëŸÁRØ:ŽÓWïg«3S*à×Ñï k¯U ÔB"¢âcè""ƒINIÃÿ“Ö8ÇaVæJ<ßÕS>ŒèQÃÍ@-$"*9 ]D¤wq ÉXýËY,Ýön‡Çf«sh½Ò·ÞÜ5+9¨…DD%¡‹ˆô&F•„/vý‹eÛO"<&>[£­ÆðÂ냞AUWžÛ”ˆLC•ºÑ*|¼õ|¹çÅ'g«ss´Ád¿ÖÿlK8ÙY¨…DD¥Ï8C—&?¯š‡€É‹0pá|1¡kfUJÔUL€õ¿ž†ÆÒ>³°òƒW`£nœØŽ€ñÓxå>¬]kb⼘1¢3Ä©ku­#¢ü‰Ùã?ÞzB>”˜söøš•1ypômÆÓòQ¹`„¡Kƒåãzc[l+´m’spmŽŒ —p5ìÌ¢‚à?`8v|þž*Œô…öswaߨθwzºôŒÆÍ/cPÝ(ê|›:â@Tæ] ÄüaÃáܳÇ7¨î‚©CÚ⥞OÁÒÂÌ@-$"Ò?# ]@‡Ñ‹1¾UKLîµ)YÊÕŽaÕÞGØ~sœ¬”@•§°çäy¹îÆ/KhåÝ/w†¹¨á5ýæaýæ}ðjªSoSÃüˆÊ(1{¼˜ck{>³Ç¿û|{øvl$OADTÞaèR eëVÒeîs¯…ýw áÕ›àòÚé¿ú'Ä)10`æL„à`Tñh'åãûñhÔëÎ#Ä%T§:¼FDböøùÿƾ“×rÕ‰‰L§ k‡þmêA¡à_ •_Fºò”ë‡ñwÊË8tòD^øû ƒmÕcðTÅÃÆ®z¶dkk‹ø8ât¬ËiÏž=y¶+¿r"c&>t^Å–Àp\¼Ÿ«¾y {ø·®ÏêvÀÃ`ìݬÿF•!&º¬¬­¥DÔSßô…£´gŽ^ý1ax{¬þåÚ4µC¼ ²öN©T*Ø9Ô€ƒnu9ùøøä*+¯r"c%Ï4D>ŒxúJöÙã•YfoÉÙ㉲áp2©ÐUݣ쓢¨A-{mDR(•ò!Æžž[~ÑjÀE>T¨APÐyxzúIuN:Õñ@ •'É©iØp8 <&”ÏÊÂL‰áÝšàí¡íàY«‚ZHDT¶™T貪ßþ-U˜3÷[¬_8 ‰·ŽãëM¡ÿ'ÁÝÛ1ó×Á¢€.¸}|#VïÀʹ}à^3J§:¢ò@•˜ŒUböø­¹g·¶4—§|˜:¤ g'"zã ]ÉAhîÚÁ©@jJ2ð[o|3E‰¯,Å¡ÏÆã£õ;0.àU¸Wœ3k ž°oû=1~÷»mk1fìX¸M ƒ]Åú˜ºfú6´‡ö:Õ™²ŒÙã?݈ûQÙÇ0ŠÙãÇ h7µâìñDD…d|¡ËÒgâó­v¬ù Ö̳ÎÝËûOæ=¾J×:¢²îìµ8r·Àm”Ò§’Ñ}šÉׯÄ˳DZû_Äæ1{üëŸÁDiq±·.µ6™"ã ]DT$ûN^Å´5¿=q»I+Ê–®Þw IÙg¯îf©CÛ╾ÍakÍÙ㉈tÁÐEDò—BD¯Ög?ý›­¼ú¬]kb⼘1¢³<·®uDeIl|ÎÞ†#gnf– íÜ?¼í+ ãüs'"2Fø*¬Áòq½±-¶Ú6qËg›T,Ÿò î˜U‚{Æ­â¯c¤ß(´Ÿ» ûFuƽÓ;Ð¥Ç`4n~ƒêFéTçÛÔYOûLôd÷£TèÿÞfü{9,³lü/,­Ì”üˆ@DdhFº€£c|«–˜ÜkRò¨þùS¬¼Pï¾èŽïïkËnþ¾ VÞØýrg˜Kï?5¼" ß<¬ß¼^íBuªómê¯×ý&ÊÏõ°hôžö#.ßÊ,›=²#fèN,ODT6aèR eëVÒeÞ9¦F]DÀ¤¯°tÇq¤lŸYŒ*à”y9<5ºsÁq Õ©Nð#ܹëÐgú&Ü‹Œ“וJ>{­—ÜËEDDe‡†®hR±ì­Ô³ý›¸bçöÇU*U(è<<=ýÐØÓI§ºœÁÎÇÇ'W«DàÊ«œ¨8¾Ùw‹~94µ6pUr¶Åކ♆U Ü2"Ê?€“ ….…YJv|ð<–Ýi'O¡I¼Ž˜ƒùkŽ`Q@Ü>¾«÷G`åÜ>p¯¥S‘!,úñ¦ó[æzíÊN8°È ª»®QDDôDƺ’ƒÐܵ%‚SÔ”dà·ÞøfŠ^YŠCŸÏ÷f ëønÛZŒ;nSÂ`W±>¦®Ù¾ í¥¸f¯S‘>©5Lùê0–mÿ'³ì©:±o?ª¹ñ÷‘ˆ¨¬3¾Ðeé‰3q‰…ÚtÐ{0(˺»—öŸÌûPŸ®uDú’š†ÑÿŒu‡Îg–uzªvÍ g;k¶Œˆˆ ËøBQ9£JLÁà¹Û±/ðZfÙsí`ãôç`ceaÀ–QQ0t•acã1`æ¿x7³ltï§ñÕ›}an¦,à–DDTÖ0t•Q¡á±è5ýG\¼‘Y6mX[,Ý N3ODdtºˆÊ ‹·¢÷ôM¸-/Ad¬e¯öÀƒZ¸eDD¤+†.¢2æøÅ;ò‰«#i¿0"#~3¹?Föhjà–Qq0t•!¿œ¼Š!óvȃç[+ l5}[Õ3pˈˆ¨¸ºˆÊˆõ‡‚0jɤ¤iOæîæhƒÝs‡ ]“ên•†.¢2`Ù¶0ùëCÐhÏêƒêp`¡?šÔª`؆Q‰aè"2 ”²f|û»|jŸ j¸a¿¸jVr4`ˈˆ¨¤1tHZš¯.߇տœÍ,kÓ¨š|âjqh‘ˆˆL C‘$$§ÂþNì:v9³¬o«ºØüÞ ØÛX°eDDTZºˆô,:.Ͻ¿üw;³ìùnžøvJXš›°eDDô$ê¨KøpÅŒž>•‹˜¢Œ3ti’ñóªy˜¼ÀºfVú¯M™‹3×Âaa_ƒÇ¿e3FÂZÜ8±ã§!ðÊ}X»ÖÄÄy+0cDgˆ¹½u­#*аÈ8ô™± g¯=È,›äÛ KÆv‡’³ÌQr"ø.¼ßZ‡äÔ´|·™2¸ >ú_7=¶Êð4Ñ—±PÊý§–‹Ð¥Áòq½±-¶Ú6qËV“úà òˆ‘_À¡¡mô :wˆ¯Z{ãõjŒô…öswaߨθwzºôŒÆÍ/cPÝ(ê|›:æG@FéÊÝ(ôžö#®…Eg–-ÝÓýÛ®QDDùa« ÀÕ½Em,Ùz¢Ü…®â0ÂÐt½ã[µÄä^’¥èôšÍÄûïµÄ¬Y[±)ø*Ÿÿ¤ÛÞOT M­DïQ3ðé{/ÁZ¡É÷1ŠÃC—-[‹óÏ©sÕØTöÀ¨™ëñ¡Çqø¼ >o_סŠG#8)ßG£FXw.!.¡:Õiä5¢‚:}¾s¶!6>Y^·¶4Ç3žÃsí¸eDT5ýBB#‹}?âµMPôZX¨í×¼Õ£³|ÐT˜™A™x!X€ƒ‡?†&þ.´h„…ëû`^§‡5u-VüuÃZTŸk¦¡ïgðæ+æÐÄ…`ñ’͘üý_Ѧ*".lC¯1èýl(ÚÆïË÷v07‡2á þºÿ<.…^}üßxÊÿ]¼½ë$Æu­‡øûgѽe,iò Þím–ïcxãgf„¡«pboŸ‚¿Ïóð~÷ jê„;ãacW=[H²µµE|œ q*ÝêrÚ³gOžmɯœLß_WbðñþÛHIÓÎzjg¥Ä¬5ayIú½¸dàÖ˜ÒÃýûÈï± Ûjðéù ¾ÿó(þSÜGb£^Ô¼Š¼Y§QSÑvÖÇòu…}#ì < gOaÿ/g–W‹‡¸yG» ßó¿ô µ#_€*n–Ü»÷kõÀË]´§Y³­Ü Ã}ZàÇ}ðžß¤|Ř³Ú$C×Ûðœÿ$øÌüóFw—ŸL{;;ÄK*kï”J%=A5à c]N>>>¹ÊDàÊ«œLß—»Oañ/ç¡NŸf¾Š‹ö-†fõ*¸eDdüž… Ü\2%ÁÑÑ1a1ˆŽŠ‚£««<¬G&…³Õíä«©‘0 sO<¨Ò½ÚxÀÊ,*ñÁVú¿ Ûe¬W®`!_x‡ a•¥WÅUºmdHdQ&ºnÛˆ^þs0sí!ŒônYÞØÓaË Z hŸ_ ‚‚ÎÃÓÓOªsÒ©Ž‡)/"cÍ[ïÿðgfY½jÎòi}êVu1`ˈˆ€àoÆj»?Ïß–¿½˜1^bÔ˜®{c4‘ˆˆ”Þ`mµÓåDFFÂÅÅŽ*ÄÅDȉä·^u4îÜ‹‡8í?ÛWáo¥7îîß;©R£:‡M‹—Ë·wB[~·ÓR ã‹â*VDlø$I¯Ù6ée>D)ˆôÅaR¡+-ú"ü‡M¤þ–WÝluîÞ~èˆ9˜¿ætÁíã±zVÎí÷šQ:Õ唦VãõÏÅ»Oe–yÕ¯Œ_ C%g»nIDT©cðýwÛÑõÝ!Ò{øelÝ÷/º.øMŸ¾L˜„Ÿ/DàÙ&.8úýRœˆP µtKKK¨S˜¦­:«}†H)ÍÄũдMÛ|o—ÓÓ=ŸEõ ýðíoW1¾k=ÄÝ ÄºÝçà¿æ;X>\ïc ¢î»k|¡+9Í]["8HMI~ëo¦(Ñá•¥Xîý'n‡ãLŸ&˜”å&]Æ-ǾeÿÃwÛÖbÌØ±p›»Šõ1uÍômh/å^{ꈲJJIË‹waóïÁ™eÝš×ÂŽ÷ýàhgeÀ–]ûÆÕåsÀÞz›g½èåÖ¹qñļ.<íþA›sqï~¼ž›‰·‡6‡ƒÙSøò½ý×µÞ®èŽNCßÀ³OÙÉG˜¼|Çc؆Axº~TªT¯ÌþÓ†‡`ö‹]QçÀ1év}ò¼]NUÚaÛbÜÄ>ø8IÊKø¾ýÞèÛfªü£æWstß]oi(–ž8—˜oµF3;ß:w/ì?™÷ø*]누GñIð›»¿žº‘Yæ×Ñë§? + ãû3#"23Sâæº×JùQè7î#L›ôQ¶RMr÷‡‹ï¬‚£ •Žž«Æ¢MåJPÚWÅê}§²ßMÿ?äÎMò#œÉïvÕ›áaòõl7kÖ÷ø[Zr±¯•ïc±ÉÃtÚ[¾SxL<ú½»—îe–½êÓ+&ô†™’#ÿˆˆŠLósú!ºéóÞ­1Îÿº* ºŽUJçvzÂÐET ¢Û½Ç;pùNTfÙÌ:`΋ÞàY}ˆˆò§pn€é3ó>•ŽÂª6ýu;¶îÀ¥;÷Ѹÿë8=ø9T²,ø…U×Ûé C‘Žþ»þ½§o½È8y]œ;qùk=ñÚ³- Ü2"¢²OéÒP ]ùOméX ÃFý𦮷Ó†."ˆ¯R?7k+¢ÒÇZš›aí;0´¸ƒJ‰ˆÈd1tÑîã—1ìƒHHN•×l,±c¶ŸXƒèd Mc…çßù ^íeqÛ\FgèÒ$ãçUó0y.<€/&tͬºqb;ÆOCà•û°v­‰‰óV`ƈÎòÉ.K£ŽÊºÕœåÐEDTnˆÀ5Y \×Î?.‹‰Ðöz-ùI ^Ít¾kM\/ÙŒÉßÿ…mª"âÂ64ñƒÞφ¢k5³h|Ùc„¡Kƒåãzc[l+´mâ–½&þ:FúBû¹»°oTgÜ;½]z Fãæ—1¨nT‰×ù6u6Ì€ˆˆHW/?ܾ\ÈÅ÷µóèbxŒõ.ÚãNYô™¹ª°o„½'páì)ìÿå ÒÒâàjñ7切jŽE»o#Qê¡K“š€Ñɨ\ÁI^¿sá/ütèª7ë‚ÞOéÔ…ØaôbŒoÕ“{m@J–ò›¿oC •7v¿Üâäâ5¼" ß<¬ß¼^íBK¼Î·©‰üŒˆˆˆÊ¦Ò;¦“y:÷ă*Ñ«¬Ìâ JÓ s^Tª¡K“p/õ舄‹°eÎPD_Ü‹¶mŸGýN]poÑ\ü½à'|øRû"Þ«-[·’.Õ¹jB‚ƒQÅ£œ”·õhÔëÎ#Ä%´ÄëòÉÿDDDôÿl_…¿•Þ¸»#ì¤÷Xê6-^nèf•ªR ]ö®Â¿Îqlæy}ëןâ鱟`ÏG£ð(x'š=¿³_l›J.*UÌßé RçfXZZB’€Ä4 lÕñXµè3DJw'¿¿òðb‘]¾t}ü&ÀQ—Ó$àà¡0på9À86ìˆú>@hР„Zaog‡x)$eíR©T°s¨‡R¨ËiÏž=y¶+¿r2¼ƒ"ñÙ¡;…ÚvÓoåK>ŸDT®´í#…®µÀûRðr®(ÍŠ¸/ßñ¶až®ß•*UÅ+³?Å´á!˜ýbWÔ9pý›8”Д¥ºÌÍÌ NK“¯§…â¯[µð~‹ŒÁï©HU+ ,Áãs==¶ü¢Õ€‹|8Pƒ  óðôô“êœJ¼.gÓ}||rµI¼AçUN†÷ñÖX~ð¿Ìáu«:cß‚ahÀÓúQ)0êl"xÍ]\: ~­ØKPÚ×Âê}§²öÿ“Š}ÏeW©†®§½¼0åÃ0Ù· .ûv]}à‘~,ñîñ=¸ìÚÕKp2wo?tÄÌ_s‹ºàöñX½?+çö{ͨ¯#ã¤Ñh0ãÛß±èÇc™eOÕ©ˆý üQ•§õ!"Ê[›^Ú…tVª¡«f÷Ñxþûþ¨SÑ5[âÇŸ¿–¿­ýß.´0ÿ[yÖEíéJBs×–Nã&¿õÆ7S”èðÊRúl<¾Û¶cÆŽ…Û”0ØU¬©kv oC{(`_âud|ÒÒÔxuù>¬þålfY»&Õ±gÞžÖ‡ˆˆJUéN¡tÀ¬uàí¯ã`nmóôc‰ŽîOaÍÏÇÑ£GÑïÓÒgâó­v÷òÁþ“yÎ+:2‰É©±h¶ É,ë׺¶ÌÄY扈¨Ô•ú<]ê¤ÿí0îÅÏtêŽU¡t©ƒžmJû‘‰{Ÿ„³·áð™›™eÏwk‚ï¦øÀÂÜ4g>&"¢²¥tçéRÝÄNíñwŒ+ê¸)qþñéO¿cTçz¥ù°DÙ<Œ‰G¿w7g;…ÏÄçZâ“q=¡,Éor TCשí_âRÍ‘¸¶}l”ÀÅ]‹ÐgöR¼xäs°oôáÖƒXô™ñ#.ÞŠÈ,›=²#ÞÙÉ€­""¢ò¨TC×õë7й׫ràõê ûIÓðH8³ƒJY°´zM߈Ûáäu…ô;·||OLxî·ŒˆˆÊ£Ò=¼¨QÃÜüñe…tÝL*3áÓ*Qxéú¾» cäu s%Ö¾=ú41pˈˆ¨¼*õôq1q÷î]íJꤦ%áž´ž Ð>|Ū•`Á^/*A¿žºß9Û— =ºøfâ¶YƒÐ§Ç•ê¨KøpÅŒž>•K=” ¥¾›kÞ(-ÙË<Ý«§?z%¡I9ùaSéÛög0^X´ I)Ú3!8Û[cï¼!hïénà–™´8àÑ~ ×ùWÒYHïåvºOE ‰¾Œ…óV ÿT†®ñÜÛßàÑÔ`m™ÿØ•“4•¾¯>ñË÷#M­=€]ÍÍúóvE·ŒˆÈ)”ÀéR:JÉ›ÊSŠãõ×&#Wª‘ç§Å£qÄm¾˜Ðµ4†H>¥Ïôo~Ë\¯WÍY\u«º®QDDÆLi 8ûQ›r×YÕR#§Å~˜ý«ÞÁ +¶ânx Z «æ ‚¥iŽ;b?5qÅ·WÁ’­'2ËZÔ¯,Ÿ¸º’sñOÈJDT®Uœ …®mÒ•ÔìåJÀñÀ²Fñî?õþ¹×¯BŒ^-[bñ†þXü²iΠ^ê¡ëþ 8~<ŸsÚ)lðtëf°5Í@K¥,5M1ïÅÏg–y?U»æ“•[FDT†]ê$_+üöbÄFÎ÷é„3Ú%¯^°üTÿpš½La1cGÂJL-åÖCû>ƒuºt³Ílœø1Ÿ7@ó†8|å0²¿Š(!9þówbױ˙e>mêcó{aÃó(•œÒìQº¡rÅÇÓ¥;»¸ æRL)> a•zÜyqÞf=ŽéÒàè¦%xkÞ׈JV -Í =FLƲ÷`'¥è'¶#`ü4^¹kך˜8ofŒè,ÿ>éZGúŸ„ggmÅïçne–½Üó)¬z«ÌÍ”l‰&‘ÑjÀV¼¢££àâbºcqMª)åÖ! ~ù#,ûû†·¨‚ćе™–5ë€wûXc¤ß(´Ÿ» ûFuƽÓ;Ð¥Ç`4n~ƒêFéTçÛÔÙл\îÜRɧõ9sõAfÙ$¿ÖXò¿nP*ƒ‰ˆž¨á¡¢ß&åpɰ¬-$/í¡Â’ ŽÄßíD—~PG_Åö}ÿ¢ëŸ”Ì}—A¥º*×}ô—XSb#«¬Œf+ËëÖ ±» "#£pó÷¿håÝ/w†¹ôÞ\Ãk úÍÃúÍûàÕ.T§:ߦþzÛ7ÒžG±Ç;pùNTfÙ¼—½ñÞó Ø*"¢rÀ¢*à2ˆÜÔZS"w©IKƒÚº)¼N MóÙ¸w?^Ͻ‡·‡6/‘û/‹J5ty|Þ¥ù9ØzöÇ{C¾À¸&`¤O+<¸p'•ݱ}p„|û ªx4‚SæÑ'<5ºsÁq Õ©.¯±…T:þ»ŽÞÓĽÈ8y]©Tàˉ½ñ¿þ- Ü2"¢r¢ò[€Û(À²f‰Üyýgÿ¬|ý‰‹Kä>Ë:“:¼(½£’{m$ÿu W®8ãþµ0T¨Ú@*Õ@¥Š‡]õl!ÉÖÖñq*ÄéX—Óž={òlU~åT8Á÷â1g÷ Ä%jg™7—×äÞ5PMsGúÙÞ1pëˆˆÊ 3gíB:3©ÐuãÀJ¼±ö.‡B5k“R1Ó¯9¦,Ù‚qnvˆ—TÖÞ)•J;‡p°Ó­.'Ÿ\e"påUN…sàßë˜ýõ6¨Ò—½%vÎöC÷µ Û0"¢"âp2­Ðuå ¬Ý룲uF<2G:5°çÆu4ööDØò_’p‘jtžž~hìé¤S-–®G.à¥v#%U-¯»9ÚàçùCÑÚ£š[FDDTt&ºš¶i‹”i3±ûì= lVIQ×°í—h7n*ܽ #æ`þš#XзoÄêýX9·ÜkFéTG¥ç‹]ÿbâç¿B­ÑžG±zù´>MjU0pˈˆˆtcR¡«Â3/`ûŠ›˜6¼#ÞJ3–+Ðeè,|µ+æ |·m-ÆŒ ·)a°«XS×ì@߆öPÀ^§:*y"cÍßøf~÷Gf™G W)p GÍJŽlQñ˜Tèé{¼ô¥%/î^>Ø2ïñUºÖQÉQ«5xó˃øì§À̲– ªà—ùÃPÑÙÖ€-#""*> ]d¬RRÓ0úã½Xw((³¬[óZØ1Û޶<"?†.28Ub †ÌÛ_N^Í,óíèõÓž…µ%E‰ˆÈ4ð **.fnÁ_A¡™e£{?¯ÞìËó(•'q1ÀÁÍ@óN@íF†nMÉKN2t ¨ `è"ƒ ‹ŒCï›pîÚãó(¾3¬-ŽîÏ£H”Ûõ @l$ðt{@aBJDàšú,pé àè,ù ¨÷”¡[UrB¯“}ÐIa´l T­mèéäÚÃs°0³B C7Åh1t‘A\½…^Óĵ{Ñ™e‹_銩CÚ®QDeÙµóÒ÷mèêÿ0éÓ^Y—¡ÝϤàÕ ™A›V"Dàz«?qNb]\ÿxP­Ž¡[V$é¿¯Ž¾ÛQÁXê{UœŒ«ýeCéèÙçQ ‹ÒžJIFüê>ÝÇ^`‰JCÖÀ%ìýHK“Ê>'"5lÛŠ#gàÊð(J*Îøƒ×­KÀ”gåÀ•éAèãàU½®áÚVD7àfdPbûÙåャDî÷ÜþÕxuòBÜŒJ€µ[}Ì[± Ï{›nOCéÕŸÿÝÆ€Y[£ÒŽo°¶0ÃÆwb`û†nQ•3peØ·N{YPð߉±Di©Ú%%IÖR“¥%E{=9P§i×S’µ×E™¨K“Ê’ÓËÄmS¥ûP§jïS.Ër?)é·Éz?·Éë±EýÝëÚà•¼^ëT¬d 7PšIïZ·ûmaùx]‘s]º…Uþë‚•uöuËëbû¬ÃÄýgýy›ç\·Ð¶SØó-y?÷¾…ß‘7*xMÚÖ÷b®ét[ ÔøãÊVy)ª±— kÃa™ë)aÇáëÿ.Þûù¼Ü®&NožƒŽCG£Ý£¨cmšCLºHoöœ¸‚¡óv !9U^w°±ÄOs£kóZnQ"BJÔéÍ: ¸|9#wàÊ ‚×±}RP°Ò†˜¤J£ÖoÛKška7 ÝŠÒ!‚×)Ls°)Ÿ“mŸ?¸ ÷jvÇð¶Ú÷€ƒgàvÏ·àdešK`è"½Xè<^^²©iÚ7Jζøùƒ¡hÙ°ª[F¤'"‰^ˆ0m —Qá@t¸özÆ"¶Q§þ~c–^›©ô´í \>gèV’8[É¡‡ÂÞÅw­´„«‹e·1v ]Tê>Ù~o}ué§QDŠŽ8øáp4tw5lÃÈôˆ±3Ó‡u=×?lõЃ z—"s„¦¬ÁJ^î§÷ViJ¿=9erË8ì%§e¦“•™i{ÊÌ̵‹8œff¦=l&¶×-Ÿpq]”‰:³·±°ÐÞ—(³H¿MÆýˆ…o燲·¹qKàÎuàÕyÀÓ¶Ì zðDˆÍw]£=DšU®u1ÄA“}]“e=gOaÎõŒC¤Y×Ó²¬‹^º€+y«6½€ÓÀÂ-éåZæ÷{¡·=q}/–y5W¹ÛåÝÀã:-Õ¹*T@ìƒKH”~ì¶"x©tUzÂÑD{»º¨Ôˆ×¯ÙkÿÄÜuG3˨6¾VýŒ×{yàü®OÐáÕ]8}ýo˜ê;C•Š4µWüŠ/÷œÊ,kÓ¨ö~0nŽ6l™$9põ×~=?ÃùR ñmϼUÙƒ”|˜OŒ¡z½‡*¿qTºáÇÁE¤\*i— U¥pUY[–±T¨Xåø < Ì®í­«]oéû,°dPÓÈ¿é%‚áìuÀû/HûtZ¸ÞøØø— zZEðO^áµ›¡Òeéy[¸Õ(¾•“ŽHÕÝ|ëSÕ)8~}76› Óý[Ti­bìë½± *v•âËÍߣžMÿ P &ºR¢®bÊØ¬ÿõ44–ð ˜…•¼ñÞ8±ã§!ðÊ}X»ÖÄÄy+0cDgùHµ®u”[rJF~¸ ›ÿÎ,ëéUÛß÷ƒ½i¯'È+peúë TrOXÒ¶ñJöñÅ7ל+>M¢‡J)ç ÙÔXr~s®°žé|ð#ðž?P«¡6p}´ÓøW¼æ¬v|%í“´íúºE%G¯[¤à5ö·¯IAy‡ÑLüêl[ «_8e>óÁ©5ê|ë «EÿqøGZÊ  ]iX8n0‚\^ÀÕ°C0‹ ‚ÿ€áØ9ð9ø{ª0ÒoÚÏÝ…}£:ãÞéèÒc07¿ŒAu£tªómêlè.sT‰Éð›³ûÿ½žY6´scüðöXY˜°ed2Äa61ÞGZßîÛó pÿvþÛß½¦]ŠJ¼™ä NyôL‰mt SEѲ«6xØ̘¢ '¦D¯a¯º¥C.^ö Žì݃þF¸2ªŠ¸Ê#“ ]êǰjï#l¿9 NVÒ/C•§°çäy¹îÆ/KhåÝ/w†¹¨á5ýæaýæ}ðjªSoSïqÙ›€~ïnÂ?!'|Õ§VLè 3%û©Ä€eq¸ï–ªîHéúE ô2pû ðàvöÏE%Æ:‰C{9éeöT¥‡*§ eoâQ¼ÄBÆÇÌ %?x–w&ºÂþ;…ðêMpyítŒ_ýâ”Î0 s& BHp0ªx4‚Sæk¨aݹ`„¸„êTW:_¢-›®Ü‰BÓÿ­BRJá¿Ê>Í¿ŒêÌó(RþT±Ú·/k{®DÀ³x‹ž)QW\µ<€û¡RúÿhÔ2}‚»·3:bæ¯9‚E]pûøF¬Þ•sûÀ½f”NudBÄ‹_øçOÞ®þ/Ò›x£ÒoOI½9= <ø H¾ž»ÎÚHº!µcMé=~i [ <üòÉÛ58(ý~Ö+ýö–:QÛ³(/ÑR¦ |]\f®ÇÈçÛƒ:ЕøŸv‰Ù 9Ĉ¿A±ˆ-Þº”ÚuñBi^/ʤ:…ÕãífëäíÌÓ·³LßÎ<ËmŶÖÚ6‰uùqò¹/±]ƺܦ½²®þÒßÖÒ¾ÞϽoÕµ½¯OêY&“cZ¡KâXó¬?˜g»—öŸÌ{l€®ud"*ßJá+!ïzqˆ*5B /õÚ¬bÓ¤qR»¿9<'K³<׉C†£€K¿iß7ª!ý Ú2qiž~;ñ>$Ιg-½ÉXI‹“‹ô"…«Ò~ÅÊÚ)4é§«Q‹7›0)À’–4m[ä1.jé2ý™®Ìýþ&^HÅ›_…W´!A§]—ßðSµa #¨E@HIICDæõ<ÊÅŠ(ÒÒï7YÛó&_f-Ër=cÛ¬·S§hÛ#Ú"—§>7¾òŽä½ïU¥—ªÒe“¢ ÄʽôåomY"å5‹Œñòâç’&-ª¿u»3ù ÔF `NÒ›¨6ˆÉ×mÓštÝ̱à '‡7ÏÄPa¬vlü{¢ÀÝ IO«‡CúsiÝXˆœŸ{ò}‰ÁÝêØÇÁ)k€’ƒUdzYzTêCmYÖÃÎz!’~š¶*#p—yYBá“°—«\cè¢''=ý|׿†nFéo6¶Íµc„¤7§µÒûš¹˜ÔU¼é$^²Å,íbÎÇjPKÊ æù1#6/8 nRÆy£~ LD)B«b‹9g˜Üóá»§-Ûu›ômÒž“vÝ¡'«vfË5ŽEj0ÃC‰§¥P¢Ž—ê»~ÓööÉaêAŽ@-­Gjƒ”èAÔ …¶íâÃŒèёéXÄz%m¹Yúbî¬ý»úœöƒˆèý±¬ ÔøD{WÏü<$¦XÉ(KJ¿Ìø`“œ}=ë‡uR–G9>äÈu9?à$¦ßWröûÏùáFîÍ*Âd½r/WEör•S ]T qâêñË÷ãëŸÏd–µmT Ý[ÔÎsûùuìQÐñB™tU Q!ÒrQZ‚µKÖqÒûÅ®{ì½§Æžf¨ikäÁ$M#¿¿Èc•¥¿ø@°ärº¹*ð¿zR0 —ê«7–êÒǶ@©ç"zä1+é=`ŠôSÝdŒ_ɶyúí,³lƒì÷'¶ËwÅã15(êýYdÞþßS¾¸wEÞ´‡ÊžO}—þ3x¤=,•¦Ò^f^M¿ŒKß&.Ëu1_1f¾ÏJüÞ‰^£4]O¢­Àí¸ÄoãV)|=å”…8|*1=Ai?óÌ€”–œs§!J ‚/ê¤ÈÎC¤ðxHÜ?ÎòøEp+‹2]fHËj)Ú`|{†øTWØÊ½\ºÅd ]”¯”Ô4ŒZ²ëe+?|W^òòLƒ*úhZáˆÁº¡JY!Ú7¨'ˆ—BÊÏaj¤Hï·ÛïªñffIzÈÈ›97ëÀܬm³ Еû¦Ú•çæ(2[æ¸n‘ã6YîW#…Žs'€¿~–.ÿĤ½j6`¥¥_Šmšu”RÈPhNmözÚ^Ë?¥7ð÷ÓPÉó ÀÃ4Æ,n½§ý&¤x»ßò ˜WÌþrGÚ,F;ž$'19éÊãÃI—´Kþ*ûc¹—*#HR¸z ]FJ—b=\ºŒË÷hbÆüQO&Þðŧ{ùŒôÉþFÄyÜŠº˜ç¶¢g`Ñs¿ ¦kãBÝw‘¥I;tæOàà&௽yÏo&=Í:=ýÒ›’]áßôNÞÜäY'‚KǺ1¶Ó][oP·ä¸1î+ä~ Îß=ЦÕ:ê¹eÅw+*X \?ç[/‘J +ïôúN¾.ˆño1 árø!ìÁ£ÛˆŒ“–({wñÉO!ÆÚE'<—kÏæ¹MÎC˜â²‚]u9¨U–®;ÛT*R¯Õ®s_àBØq¹­ÏÔì%‡o"SÃßjÂÅ[ÑS \wjgz67Sâ›Éý²Ÿ¸Z|+OþÎtyõ\Œg¢58+½8wrS Ž]úWþ]Ÿ—Š“ökߙߔ‚L’8L(Æ›¨å/Õ=”{¥)qy_¾Ô†ª”˜NÈÎÒI{ø$ëƒô ]\V”.³¾)¨’b0nS«|ïKöØzzÞêþuñ–•ÿëFà·ùˆoÐ\ ZÀ΃2Ä…xmÏ·^z“þóê l>Qz³¬Uäû7$yßN-}âvbÿ1tí=¿ VÎr¯S~Ä!Äs¡¿£™{y]ôºØV–—z›çyº"Ua¸/‚Xü=9‰ñŒÚ²›RØ3Ý<]FIÂŒKŠÂ¾ ßÊy?öŽ^ÝŽÎ †>éÇCdtºÊ¹ÀK÷ÐwÆ&<ŒÕžþÆÊ ?¾;Û7̽±Ë`íI¡SBåy‚2ˆëSW–jŠG÷×áaB &kÃUTzŠH?Rüv›)-äOÙ⓵xa¯`WM SÕ2ƒ•›tÝÆ²ðçÇÜõß—HNÍç@Ðö˜ÞÚ›‘Q«¸½]w¥ðyx+ph³ö„Òy'‚î!¾y8D{þÂb8wçÜŽ –ƒg~’¤}ßyæ3£ëíßZLJËÿyÄs'zŒb#ä䯤¾š~¿¼¤PRk »"ݯ­¥£¼¸»äý»%¾ *B˜|¸R e÷coÊ×EÙCU("T÷äm R”C˜âph²ü<*äÿ·ùUêÉœÄß±­…C‘÷±¬Ù~f9®Çß„LczÒɆ®ÔètmæE¿EøãˉrÙÛ0~¯Ü‡µkMLœ·3Ft–;åu­3f¿Ÿ»…ggmAl¼ö4vÖØ9{0zxÕÎûbŽ©JpîÂÛ¸§-Ÿ…OEk° ( ‘É )hAb ôR‰7m˜Ò†*ñœ9¦Dú´,B•™²ä¦®°6·}â6â°Íݘ«º…®è‡Ò|§vš‡ ãyoãTèê«â¡Ië¢O.™Z®MäÞüÞü2˜›•áéò!Æz-ó3Ý/Dôl4R^ôMÚ=SbÉKÎC˜÷åž²Ð,ÁìST>FÖC˜ZÚßwÑó*~Wgí˜m{Ò¬-ìaen#'¯òu)˜ÙKëVÒß°ƒ•‹\.‚š8üiké 6›,—âwFßÄÏeëé¥ò¾ÝŠ (½a Tæ™hèJÅò)¯àŽY%¸§—hâ¯c¤ß(´Ÿ» ûFuƽÓ;Ð¥Ç`4n~ƒêFéTçÛÔÙ;Y,{O\Áy;äù¸g;+ìý`(Ú{º|C§Øz÷lE"x‹Í¸öd9éÊcAìݳ…,ñb©Oƒš¿./%*QzÓ9¶8¸8yP;n+'+)ìµï ô´ê.½Û•üŸ¤³ô³^÷òµ¿_*¿ sS Œ—˜bR½cE=„™•iâ¦Xµ¢_¼Ã˜gRˆÍN¾î ¯‹:Ñ«&Bœ¸.B]ÖëâvÖ¶ò—O Kôr‰ž?ñÚ·õÌ'x«ÛW:·ŸŒ›I†®àŸ?ÅÊ uðî‹îø>ýì.7߆@+oì~¹3Äéæjx D@¿yX¿y¼Ú…êTçÛÔß°;ª£G.à¥Å»‘’¦í’ªìl‡} ‡¡y½Êß0!ûO½…KÒ ÜL|út³c9´Ê-ã ß!]w±«"Í$‰`uêwí¡Ã£»¥ŸYŸöE°já­ Z}›Â%2öVÎòRÛ-ïy©’Sq#2sö–'¸Í‹8¢×->ùÒÔ%0.A"&؇šÅR"˜É½irpËèIËèa‡oµ×Åk8D,ˆàx³„†)Q2¹Ð•u“¾ÂÒÇ‘²}BfyHp0ªx4Âã³f(àѨÖ FˆK¨NuEŸ=Çð¾Ú{Z>—¢Z£ý„Y£¢#~8 Ý]ó¿‘ô‚so%Öü³ÿDæýÂ'>Á5¬Ô“{¬ƒµKæ7©Êñ³¼|øuð{â=¼´AKBt©¤×&•5býÉ›ûò \âK.âpáR¿ßäà%BZBJœü%€xÑã•—~ùèñeò#yÑŸe]ÜFÛK¦’ñ•qøT,¶ËM<þ¶ÒøRÓ ]Òï²·PÌRôoâŠÛW©Tñ°±«ž- ØÚÚ">N…8ëŒÍÇ[O`êׇ3;ô=j¸â×EÃåà•¯¤Ë8vî-|sñŒ<v~Ä'¸Ëá§äC ŽÖ8cñã'ÀµóÀK3€êuóÞF ˆS<ˆAñ·/ç½MõzÚñât<ùÝQ9ô(1ã‹yß~w ^ÝŽ. †Ê!M,N6Šõ¸"¼©’c”/‡2ñÍeÚĺ|=9ýº´Mœ´.¾`#B[\òãëªdñMoÝN†.÷vÉ_ʹ ·¤òŤB×…½K±æJ#ü½ª®~{;;ÄK*kï”J¥‚C 8èX—Óž={òlW~åú":b6œ¸ÿy<¢nEkÌì]gOü¼gáQ£‚íüóNF®[_¼H~¾ïtu~­DÚm(õNîAã£åë 'áØàwï¢=‘·e|,ª…Cuiq¹—wÐJ²qÀ]v¸Ó¸#¢+×Õˆ?}A»‘ì^òEùš¯);¯B\È“¿èR|Öòb…Êò©«s}µL_lµßˆMQ'!U“Œd Éêx¤"Y*KÖãåò$u®%ž@¼:w_˜Ø¯m§?aoW9dB¡Kƒµ_Ûç¢ÐÄ];{s’*ª´=p?w GÞo†°å­\”Ú탂ÎÃÓÓ=tªËì||rX®¼ÊõEF|ã‹_³®öMªãçùÃàd—Ï@Ðä8~ö-¬¹x²ÀÞ­œÄ'¸HÍ tîÑA>Äh”~\¤.Á&.ÝvøO'¥Eǘ61 ¾Ó «¬Zõ@33ÔÑc³‰ŒÆh&˃ò "¾)ù¤mÊ¢ðG·ñÆÖ¼ç†coWùeB¡K…»®`a–’³¬§Wì˜í ;ë<¾6-´G¾Ç÷'àèÃìsuªï‡—ÛÌ)ñS”)¥Àµzvîr1Fëówr—›[RÀ’Çhu”—•M©7‘È”&Lcà¶ŸùTŒùGOĤ½“»¯Ò_£ÈàL(tLa]ßm[‹1cÇÂmJì*ÖÇÔ5;з¡½×ìuª+Ë“Sá?'~:öø˜oGl˜þœ<j.Éwpòüd¬ ú+Ûä¥.6•Ða!ZÖ쩇VЖy®¼ˆ9´Ä­.ƒçâ/!"Ó#–ø’@AÄ€úoçð³ôSù`Ò¡kÐ{0(˺»—öŸÌûPŸ®ueÑ£„d |+Ÿ¹™Yör¯§°jR?ù?Ù‰Þ­ñý‰¹8—­¦S=_¼ÔvŽüµo“ÿ8{T;—Ö±üÏo'³uR“×>/ë¥yDdœDïÜò¡ÇrÍ7˜s¸‰´¯ï9 ɰL:t•GQq‰èÿÞf»p'³ìMßVøxlwé… Ç(´”û8y~*Öœ?’­wKœ*elÇÅhY³—žZ­'ii@È¿@àaàß#Àœڲ'©ä®-¾JM Ï ¥ßN"2z… S \åC— ¹¥Bïé?âìµÇƒæßÙ³Gvʱ¥qáÛðÝñ÷q4<6[M»:>Ýn>LaÚáá]mÀúç öòQ³êXZÎ@Ä=d~W5kàzïí8."""0t™ˆ[bÑã ¸|G*DdøøÕî˜äÛ:û†© z«ÏíÏѻ劀ö Ѻv?ý5º4$%ç'öRȺ\ÀÆÒO©Nc MoÀ«3ðT{ Mú¡Lü÷—¸jH?7.""* ]& øVzN߈ÐðGòº™R¯Þè‹1}›eÛ..b7¾;öŽ>ˆÌVÞ®NŒn·À8{·Ä$d7C´ç6üçW),I+%)ÿí¥}lÝhÙMZºnUrl`,Ø"/?)É^?ÌX#ý¥ðO…ˆˆŠ‡ï$FîÔ•0ô™¾ á1ñòº¥¹ë¦=‹!ÞYÎë•…À ïaÍÙ]ˆ2…Þ­˜àÌÚðâaäýü·½S[mÓ{³HAôI_A·µn®œš´aà""¢Áw#öç·1`ÖĨ´=;¶VæØ6Ë}Z=>ÝL\ä~|lþ¼ÿ ÛmÛJAk´¸Œâ”=â[ƒÁÿjÇe‰E„!M§à¨&íÛ^RÈê4ë¨ýæaQ‰àõt›LDD”C—‘:ðï5øÎÙU¢¶ëJÌ.¿gÞtlš~z¢´Gø÷Â{X}f{®Þ­1íç£Mí2>ÆÝ@ °NüªÖ!!.ÿmE¨‡ [u—‚–tYµ–ÞšIDDTX ]FhÛŸÁx~á.$§j§;¨äl‹} üÑ¢~ey=.ê¾?6 †ÝÍv»¶µûbtûEe³w+cάãûµS:„ÝÌ[¥hÐ\;¾egíáC3þ*QÙÆw*#óíþsxeÙÏHSk¯U¯à€C‡G 7 -gBæbÕ©ˆÈrYGky |Û:e¨wKœ¿ðʹôðŸ5NÆœY½YE3ËÒªäÛNDDd ]eœF 8“V§;Nf–µm\ {? kÍøúàÛ8z5ÛmZ×쉀Ž•|ï–¶AWÆú'oJWÔ@ÿQ%–Í kpãÄvŒŸ†À+÷aíZç­ÀŒåÙÛu­+MiijüïÓ}øfßÙ̲-jcÇû>¸zk ¾\•­wËÞÒQ>Au§úƒK§A9WÖòe“€Õs >d¨ËœYDDD&¤BWêƒ0Èw"F~}‡†¶AxÐ/èÜq ¾jí×;¨1ÒoÚÏÝ…}£:ãÞéèÒc07¿ŒAu£tªómê\jû’œ’†>Ü…­<>$7¨CC¬™X Ž>‡Ã¡—³mÿLÍîÓ~\lK©·(¿À•U^+cάV=´ó^YÛ–NûˆˆˆÊ8“ ])'Lÿ|†IËLTiÚÝ›9áÒå븙ˆ@+oì~¹3Ì¥º^ÐoÖoÞ¯v¡:Õù6õ/•ýsoùÍÝŽý×2ËFtóÀ›Ï…`æÏoáa²:³ÜÞÒ!½wkH©´åq£b€_Ö>y;ÑsÕчsfå`R¡Ë¦²FðÈ\=ŽÃç5Xðy „üºU<Á)óh–aݹ`„¸„êT§Jüct\"|fnÁ_A¡™eczØ Uó/ðѱlÛ>S£+ÆtX\z½[jµv üÀ?‰ªü·­^OÌ:Þ\ÊÆDDD9˜TèÊ*öö)øû<ïw¿Á ¦Nر36vÕ³…$[[[ÄÇ©§Ò­.§={öäÙ–üÊsŠŽOÅû?]ǵðDyÝL©Æènç`Qí8Ž„>îݲRZ£•ýPÔNj‡¿꾋Â>"îþ„{ð_°Ž+`ŒVº8çªÐ$&#¢æÓ8_¿;°÷ço‘±3ÉÐuãÄ6<ç? >3¿Å¼ÑÝåÀdog‡x)@eíR©T°s¨ëròñÉ=ù¨\y•çtëA,zNÛ˜¸êWŠDïC7²LtÚ²Fgtø.¶U ùÓ($qÒèC[´Ëå3yoSµ6Ðc¨vÓ kËÜëÃÞÜxª-ÞøµÙÃED”§Â~'Óer¡ëƱèå?3×ÂHï™å==¶ü¢Õ€‹œ 4 :OO?©ÎI§º’:´xùNúLÿ×¢¡Thзåi¸7úá©{·ì,ìð²»Õ`X =*´‡ ÿÜ Ù¦æA­Î½ƒ3ÐÙè>X;wVÆ¥.À×3µ§ßyºðúR$""*€I…®´è‹ð6 “~ø[ \u³Õ¹{û¡#æ`þš#XзoÄêýX9·ÜkFéTWÎ]{€ÞRà ‹R¡–[4út:„hëûˆN}¼MKwomï–]µâ?`štÇgþ~ý8*®„<ÆiYXjO¹ÓC xíúj×s:prÓ·g8[<јTè þuNÜÇ™>M0)Ky—q˱oÙÿðݶµ3v,ܦ„Á®b}L]³}ÚC{êŠëØÅ;èÿîfDÇ% góÿP×󢳜ÐÎÂVûÍÄäv3Ô\ Òˆ?¼ˆË{›&­^Ã¥Ø mOÖ“ô~¾xm"""*GL*ty™ fv¾õî^>Ø2ïñUºÖéêà©ë8{\mÃáûìÄÙÞ“×ãz¯êñJÇ%p±«®ûƒ„ßÕ†,¶n\Ì{ñ­Ã^þ@÷¡Ú1[DDDT*L*t‹…`ø‚ŸÐÖã<šEL¶Þ-k¼Ôf:5®[ïV|ðÇNí€øÓš<Æi‰Ã‚Ý]ý´½[<4HDDTêºJRjr7[‘£ÅM á¿ÌõƒgBñúgGáßë’îHëñ¶ÍªµÅ+>B‡ÚE|ÜT ð¶WK ŒON̽•v|–[mziOÉCDDDzÃÐU‚Ξ߇fæïe®?LÒàç(5,þYŒvnÚoöUÓ¨á;@ûYÎmkn…­ßCW— (l¯“8-˜ÚA ˆ?¼ ˆϽø6aÓ¶ÚqZŸ윊±wDDDT ]%èàewTv·A‡y}û]5nÄk°)TƒfÎ lUcÿ} 4YnÓ¬Zk¼Òa *8Ô)܃ܿ-=´~Ý Ü¾”÷6µi¿y(–JÅFDDD%†¡«%¦˜cÑ¡æødà1¹—ë·pm¼½Z¯ŸMC\–i lÌ-1¢Õ tk4úɽ[q1Àï;¤ µI{Z&÷6.•€îC´KÃ%¸WDDDTºJØÊ¿›`Z·3؇Œ!ì""e \"ªbÅ«?¢‚CݼîB+EJjÿnŽý¢]ÏÉÊè4@; ¾UÀ̬$w…ˆˆˆJCW KJ5ìCMWýX®:K…ÁAqò\ST˜šGà=XþIAëÈ 6"÷6J)X5ïôô×.›âÏFDDD¥¡«œIŒF^}X1qö8ÒLº–c‡»×ƒ›´ƒâÅõ¼Ôiôz^ûíà %|ÞE"""*u ]%ÌÖöj× Î³ÎÚ6µj_BpH}àQ”ö[‡"l‰Þ­¼ˆp%&-½Z"t‘Ñbè*aM<¡Tæ1Ð=]£†Ç1?ø,0øc 5%÷âp¡÷sÚñ-¼µ‡‰ˆˆÈè1t• ÛG¨“O/— Ph`m ×Ú÷kY—™ô4<ÓM´:ÐNdJDDD&…¡«nœØŽ€ñÓxå>¬]kb⼘1¢s®“ô´­Ÿ‚"òïå4ÒÝÍÑE„®͵'îæ8U(µö‘á1t=&þ:FúBû¹»°oTgÜ;½]z Fãæ—áÛÔ9Û¶ííÐ~ClÁwha Ô{ øöK ¦Gé5œˆˆˆÊ†®'¸ùû6Zyc÷Ëa®jx D@¿yX¿yŸºü³oìÙxª=ðßßyßY#/à~(0|Q9ÃÐõ!ÁÁ¨âÑNÊŒ<5ºsÁò¤§Ù1ŠAð ·3çr¯§Úw®o.ÕŽÛ"""¢r…¡ë TªxØØUÏ®lmm§Êû6vÀ‚ÁK®P¸>fà"""*§ºžÀÞÎñRðÊÚ«¥R©`çP#×¶{öìɼnÖi ZGDÂ.úpõþë>÷£b#ý4œˆˆÊ¥RùäȤ1t=AcOO„-?€h5à"ÿ½htžž~¹¾½èã㓽 o_„ÌŸþCЪ]_=µX¿DÐ̵ß&‚ûf¼Lyÿ¸oÆk?t—{ ]Oàî퇎˜ƒùkŽ`Q@Ü>¾«÷G`åÜ>O¾±.·õ…‡‰."""*<†®'PX×ÀwÛÖbÌØ±p›»Šõ1uÍômÈMQá1t‚»—öŸ4Ý.o"""*} ]DDDDzÀÐUÊLyP¨`ÊûÇ}3^¦¼Ü7ãeêûGOÆÐEDDD¤ ]DDDDzÀÐEDDD¤ ]DDDDzÀÐEDDD¤ ]¥E“ŒŸWÍCÀäE¸ð¾˜ÐÕÐ-*žöçÆ‰í? WîÃÚµ&&Î[#:ç:MRYtè¼6e.Î\ ‡…}E ÿ>–Í kõ ´q«‹Ó –PfìŒÂNEÁ·±üé¤Ü~cÞ.lû^/|­,-5Þã>Ç¡Oúßs§ãߘÑ<ùíŸ üžæû¢0‘çŽJL}õ1v,×Ûb[¡m7C7¦ä¿?šøëé7 íçî¾Qqïôté1›_†oSgÃ4·RüƒA¾1òë84´ ƒ~AçŽñUko¼Þ)Ñ •pàÖ]t©l|'ªÕ¤äß~cÞ„&~ï!QZ2hâoÃGzÞ†Ne°6é*öö)øû<ïw¿Á ¦NHºî†ƒº£ÏKñýÚ¦¸~bü=óŠaž C7÷‰R-òo zÞD/×w‹çâé—§Á«¢…\RоÃs—UAcq&ð<šÒïiÎ×;Mû¹£¢cè¢b±·³C¼ôâ‘õ“™J¥‚C C6«ÈnœØ†çü'Ágæ·˜7º»¼/Öu:aý†N™Û4éä·F|ƒ»~6Š7î‚Úß©•i€­kUtî3^õ*Ëu)aÿáÃÍÿáí‰Ïaç°xKÞ™8 7šˆÈˆ1t•CW®C¿7P¿Ç0toÙQ×N`PÛið»KÇõFªº>X¾oJ¡ËR£Fjšºx¨‰Ç6½°èØQÔ3+™} "26 ]DåŒ&ñžŸöbé+ÞÈè¼zmdoø¿³wG÷‚kÖ(”07Sf®¦ÆÝÇÎ-Ûr7u[t_ßv°”îDµv^Ƙž5±qÛ¤ÚUðÏÃÝÉÛ>ŸŽ?Î_Ãò¹ó1îé°¸q»ž@ñ6FõhƒØ]'ñAǪ†­Q†."ʱ5;aXúøø÷q3ð°–fª£±eñLÌýß\¾ùöѰuWÌš7-ª‘ïñ_Oˆ•[öáöðrËtÇWß}ƒ†e ÈËľ¼…IᅦßvCX¼ ê·î‰ï5Iöèj,üì\*޽§ö CE—u+R±!~Ûù'ëÔÄ”±0ðêN¶ŒEŸêé°“ë=Ǹ‹á{÷\KVÁ¨/¾ÃôaíS¶ˆibð^ËrXì…€‹ëᔬðΟߠR—IX°÷Æu,› ]"2 ]D¤j¼xþ W8É­`lþr(úÏ9‚i‹–`c§ˆyzó'¾­»àÔ哨UÈ‹?î‡ÏÖÜÇw¿¬E—zåpŸŒŠ®½cpëôj8+ã1gdO,:Å«»~Üüo>ýt¶ôZšÈ«Ø´û jõ˜…ö©W"¥cY|<ª/ºŒ_ƒÝç‚ðNC'XY*qcÿ˜Ð߯ۇÂÖ‘øqÊ{˜ùÞ`4owíK'[Â#F¼¥C¾Ã¦SÏ0¬qâ.T 6®[Ë2m0¨mÙ\ûë‘y`è"¢}ºc×o ðÃN´ó3JI¡K~ ³¾Þï~Ãô=´‹–*‰¥«¡2­ðêCX6®êw«º— ®Í´-KåËcòèÞèøÉ8óDVV§°òËè<îW¼÷Fsy5î=Ga¢ÿq¼ñÙjyZuÏ·"ž™÷ת+•+ñ üün ë@¡PàaP]ý=Ê$tR›5y ~Þ=ÿ½#…®2)îïÙkê[€•+×bhãåÇÒDøcÝŽóðþp-ŠòÓ”ˆ^D”eš¨ ¨l™6ÚX;Aï¿Áâ9#ä0ì{ ¾¡¸Þ:‰éÓo%[2¶ö œ9{VºÝͺö…Û©ÃøéÛ¯ð4( ñj ]¼"=P4¢¢5ºuã€aõê¥x¼FMšHJºâcc¡’®mll2­»­­|«›Wº~s”N:*Înn°’®£¢£ÓÜ_áP#Þl÷~[kß|€êN øîYKá1çíî™>6‘ÀÐEDYgS k÷­”[³„È€ãè?xº~ö3VMî¡ki †ZºG6Þ½‘bÍz A‘º• ÑÄà»÷º`üŠháÝ=ÊÂÖÊʤ´VGG‡ëpttÒ=–uÑâ(¤îßK;œErHuR DÉâI›ck“²u,Óèï3~€•›ÿÃ×C`ýºõpo5íËÙeúØDDCe™Bé€úÍ›';z±)æ<ˆQ³ÞGßî-ðF 7y®‹kAˆEÚ¿=s{¦»®È«[ðù²Ãè?s ÖLí¥Ë;ýô Ëw^‘oÛ;8Hó5 Oqßà —r¨”Eë¡eu'ìÞ±O羃"éŽÇ¥Æ®»» hݸŒ<.5º£Ó"X÷û*|ù†6콎!Ë×㵇#¢|‰¡‹ˆr@‰³~ÂÚ?ê`ÔÐÐìøj’ˆSõFðpVàÀ¾}ør°§nøê%6¯Ý‡zºÁæÑCDh¨_¿~R“:›¶ìÑ­Ý­J5‘Ò›¼;²MÝü£GþÒu¤‡Â O-o…‘SÆyïÀ:Uº¼û;Ì_w?X†zEm²º °…Ï,ñ;¾Zܱ«‡·{ÔÎÞºˆ(ßaè"¢Q:WĒşóû$|ðUo¬™Ò ‡*˜:¾?úMÿ£«Çý[CˆßLÆü­7ðÇÙÎèèQî6¬_ñ#ºÕ‹{X>ÿs<+X Íœ>u-ºybp§JX¸d:¾ó,ÎuKÁÿøv|¿ý¢Ü÷*1x5<ߟ¹Œ¿†ú§÷`Ä›o R©Âˆ zŒc{·`Ùê(×þ}üúåðŽÚ ÏpÔùøÌœó3ZŒøíÙÌEDYÃÐED9V­ë8Lé¿Óf½ÞÞ-лvaô™ò 6¹ÌļÿMG/| ´uA­Æm°éàt­Z@ºWs¬ùe.ÞŸü-ª—]ˆÂe=0ì“YXý–TwÎcÎÛ-ùÓ^ÌX±a£FaÆðn˜¨¶E½Ö=°xÙtlÞª¸8m¶xÑ4ì´ ßýï7Ìÿl _†ÂÖ± ª×iŒ/–íÆûƒ»ÀNg)R8VƈA-1ú§S6tG¸'¢,cè"¢,ùðç¤K… +L]wS“ÏSÚ¢÷säKwB‹“pIº¤¶õÌÝÓÿÛzÿKµÌKµ&Õ 4èòÖH—W³ÄÚ‹iP´¬ÜQš¤õnóK÷Þaááp«×½êÌÂci1t½†;ÇVbÁÚó»f9ØÌED¯¡‹ˆ( .ìþs–oá½QmÀ,LèSËØU""ÃÐED”Š•AÍz^èöögèÛ½%lÙÊED¯‰¡‹ˆ( ÊÖë„iÒ…ˆ(»ºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈL3tib±gÙ,ø|2=æîÇOcZ§YDìÖµ=¡è2ÿo¬<ïî©­ð= gn>mÁÒ;k1&¿ÕŠ\*#"""Jd‚¡KƒE£:bKh4®î–Á2*,?-ŠÀ=ñ^‘w0¸÷P4¹{‡¶ÄãóÛЪ]T«s=Ë齬Wú{‘)0ÁÐ4öF7¨‡O:¬E\:å~{¾Ç’«å0eˆ;~{¢wï蜱ñÂÎwZÂR”òìŸ.³°fã^x6y ÷²^5ôoBDDDy› †.ê5l ]«Ó-U]ƒÏ¸Ÿ±pÛIÄm£›ïïç‡bUªÂE™´ž*U«bõ%?ø»>Ð{™Fž""""Ò2ÁЕ ß~샊âkõ‚ؾ5©(""v%S!{{{D†G <ÊRÛµkWšy …&»[KDD&¤lÙ²¨Q£†±«AFdV¡ëêî…Xq³*Ž/뚦•ÉÑÁ‘RHJÞ§RpÊ…²Ô¼½½ÓÌA,½ùDDd~Äg>CWþfF¡KƒUK—"àRª»ï–çÄD„ "~Ü/Ñ/j#pÑ~«W¥vy_ß+ððèj.z/ã®E"""JÎŒB—swÜÄÜds¶Í„o6‘‡ŒÐD 9fàËG0ϧN®Ãò}/°df'¸—Ò{Qr¦ºb}Q§`=ø©U\,ðWG¬¯D³ qè‡ÑÞMa[ ¿nY…á#GÂm|  WÄ„Ûй²£×õ^FDDD”œé….k\ÎÒ¢=§®EÏdÓîžÞØw:ý>T¹QFDDD”ÈôB‘ bè""""2†.""""`è""""2†.""""`è""""2†.""""`è""""2†.""""`è""""2†."""=Љ‹ÇŽ×ÓÌ?{#Q_ÓM7¨Re‹º²jdd ]DDDz‹~³·§_øç}ÝM+K%b÷|j˜JQžÀÐEDDFqæúcü}9à•Ë}Ü»¡jcxq*µ±«@ÆÐEDDFñ×Åû˜°ìð+—›¼ò/Dïžh€å.†.""ʳÐö‘2¶¨"bâ-]GÅJ×±âZ¥½ŽQ¥˜~eìêRÅÐEDD&E„0]Èa(:VˆÒ @‰ËDJåâ~‰óÃ"cÓ].ÅtÂúÄ}‰ô¡‹ˆ(«ÿþ/8{#ð•ËÍ÷i‰ý F¯'^­AHD ‚£—p;çn¾z›4 ×z.”P¬Êø­^DÙÅÐEDf!ðe8Œùž‡eº\e÷‚ð_9Ò@µÒ¬®­ªãÓåGr-t©âÕrP –.AaÑòíÄKpªéÄòàpmÐ!K£yõcdF¬Ã˜l¬,`gc ;k«„ë„‹tÛ6ù<éZ!ýûeÿ%£Ö—ò&†."2 b÷Ò«—pýÁKÔ&oJÞ⤠N ×/ÃUêP•p;$":ÇÁI_” …t6ìˆk';kØÚ$Më®–³·±Ò†'1?a91ÏÁÖ*ͺD¸JZÆV–Y®ß‹Ð(†.JCQ¥–RNV;‘û¼¯?úßA]X NÕúd̾I"Øp´…kâÅÉž…âÜÍ'Yºÿ³MêB…R™Ëµ%ʦº4±Ø³l|>™‡s÷ã§1­uE¾‡~ÇûãgâÂíg°r,Œ>£¿À·“ÃVÜ=µ>£'áŒô&·-Xcg-Æä·ZÊGÇäFåMAP©Ôrÿ ˜8•¼ë,VšÖÝ–‚Žhåâv\¼:åí¸LæKÓjµ6,©â#^·Þi:é±Ó_&ñ±EGpK^¾ßv:·þlr PòДüº “ªlR–É·í¤2[ù¾©}½éÔ+C—ø, l…\ìsg£rØîe ãÊÝg™.'Zæ(1ÁÐ¥Á¢Q±%´WwKQ¢zúzö‹ÁK÷ãP¿Fxæû'Z6ïŸzáƒfj î=MgîÀÞ¡-ñøü6´j×ÕêÜ@ÏòAz/ëU£€qþ]faÍÆ½ðlò@ïe½j 0Ú߇(?ziì*ä é \´|½J¥’®¸ñ0s‡·J7T¹8ØÊ-+¢OS^!FšÿlŹ•13mê”1Pˆr— †.ê5l ]§}“Ú­‚¡oUÑMG>8‰ÃW4˜óc]øXbUªÂE™´ž*U«bõ%?ø»>Ð{™à.F¢\&ú*m9æ‡õ]Ñ‹÷²|?Ñ¢c)…kKXY(å–Ñd™p[î7$]‹sãÙZ‰ù ¹#µ,¢ÜZºm-n+•Ò:´!FÌëóÅEwÛ2åm¢¬Öc#­ÛÚR™ì¶4_š¶IX¿x|1OÑaî+·©^¥ârèšÔ¿I¶ÿžÆÃsR>b‚¡+kBÎa€÷ xMY‰ž5\°m{$ìJ¦Böööˆ @x„þˈ(wˆ~NûÎÜÆo.cÏ·²5Zyà†r¡fDD™3ËÐu÷Ô¼1`¼§ý‚YÃÚÊ¡ÈÑÁ‘RHJÞ§RpÊ…²Ôľüôd4Ÿˆ’ˆ£ø®>ŠÄ_~Aø÷f(ÂcÒ­Ä×YaŠï»îuÜ2-/n*_›â¶åfºîžX‡f`ÚªCìUI7¿š‡íG°p•wjàë{½¥2½—¥Þµ˜^çIvª$ÊÜå;O±ê/ÖöÍp ®êe á­¶5ЪVi4ýèw´«[6Óu<×äÞwë*`À—Û_¹Ü?¼UJä~…([ˆÉ¬BW|ð5 è?ã~?.®ò)Êܽz£9fàËG0ϧN®Ãò}/°df'¸—Ò{eÏ­ÇAXä*ÖHAëÚýé.Sº°3¶ñ@ÿ–ÕP·bQÝüŽõËË»3S±„«^ëkb;Å…ˆL›é…®X_Ô)X~*@ üÕ+Ç+ÑlÄB,òzŠSÏp¡SuŒKv—V£aï·ïâ×-«0|äH¸„Cኘ°b:Wv„Žz/#¢¬Gn:懵RÐ:îû ÝÝ„â¼~^U1H [Ík”’G%Omïœþ¹_Y"¢l2½Ðeí áÑk4Ó3,s÷ôƾÓéïVÈ2"ÊXDtvž¼5‡|åÖ©ôÆb§hñn\ƒZWGç†ä#ùˆˆL•é…."2Y"X>«]Áöãוö´4bø„–µJcH»x£ie¸8Ø¡¦DDúÇÐED¹J£Ñàĵ‡r?­ G¯áiƒ˜6¨\oµõ@¯ª(áædàZå>†."ʾ÷žcÝ_¹ŸÖÀt—£¨¿ÙÆCºÔ@Å’¦×Áˆèu0t‘Þˆa6üuk\Źé.#FƒЪ:µ©Ž•K þðDDf‰¡‹ˆr$8<Ûþ½.ñpäÂ=y ÓÔí¬Ñ»y l]]GËBÏ'N&"2 ]DôÚÄ©xvº)÷ÑÚuâ¢Ó98Oa{Ïrܾº5ª;+#Ô”ˆ(ï`è"ÊgÎ\,wlÏŒ…R‰Ñݱz4« ';kcW…ˆÈä1tQâÄÒ±ªx nWÃØU!"2 ]DDDDÀÐE”ÏÜ~lì*åK ]Dù„8=Ï„¥‡±bïEcW…ˆ(_bè"ÊâTñöͬ>t%KË‹þ\mê”ÉåZå/ ]Df.":}fnÅÞ3·uó¼WĆ)=`Ïó! C‘{‰nŸoÂÉktó†v¬…¥u†%ÇÛ""2(†."3uÿi(:M^k÷“F•ŸÐ¯æo͑䉈Œ€¡‹È ]¹û ]¦l@À³0yZd¬¯ßm‹{74r͈ˆò/Ó ]šXìY6 >ŸÌC¹ûñÓ˜Öº¢»§¶Âgô$œ¹ù¶Kcì¬Å˜üVK(ŒPFd '®>„÷´x-O[Y*ñËxo¼ÙÆÃÈ5#"ÊßL0ti°hTGl m€ÆÕÝR–DÞÁàÞCÑtæìÚÏoC«v}P­Î ô,dв^5 çÏCùÚîS7Ñwö6DŨäiqÞÄÍÓz¢Sƒ F®™`èš û £ÔÃ'Ö".Ùü{G·àŒv¾Ó–  ”gøt™…5÷³Éƒ–õª1ÀhÊŸ~?p> ÷ .^-O»9ÛaÏì~hXµ„‘kFDD‚ †.ê5l ]«Ó”øûù¡X•ªpQ&-[¥jU¬¾ä×-ÓÈSD†ñÍæS˜°ì04ít©ÂÎØ;§?ª—)dÜŠ‘Ž †®ŒEDDÂΡdаcooÈð„¸ŒÈ4RÊútù,ØtJ7ÏC ZJK/""Ê;Ì*t9:8 R BÉ[™"""ààT N.Km×®]éÖ9£ùD¯¯Ö`ÑÁ8쬛Wµ˜=¦t,Œ‹§þOöCD”·˜UèªæáÀEû¬\å]~øú^‡Go©ÌÅ e©w-z{{§©¯\éÍ'z1Êü€/·§\]UÀÆ©=9Ê…+bŠmè\Ù 8´Œ(7î‚!ík¹fDD”U ]DyÜþ³·ÑkÆV¹ó¼`oc‰ Sz»qE#׌ˆˆ^CQ¶ö°/ÞY°K7ʼ«£-vÎê‹fîF®½.†.¢<êûm§1nÉAÝ(ó% 9aßÜòà§DDdzºˆò1Êüä_ŽbÞúºyUJÄÞ/û£l±Æ«åCQ¢ŠWcäwbå¾Kºy «”À®Ù}QØÅÞˆ5#"¢œbè"Ê#¢bT0g;vœ¸¡›×©~ylšÖŽvÖF¬éCQîÓ6áߺyƒÚxà—ñ]aÍQ扈ÌC‘‘=|†.S6âÒ§ºyãz5À×#Ûr”y""3ÂÐEdDþ^ Ë丬›7gXK|6 ©ñ*EDD¹‚¡‹ÈHÎ\,·p= ‰”§Å(óK>ì„áj¹fDD”ºˆŒàÀ¹;è5c £´£ÌÛZ[býä7ðFÓÊF®å†."Ûð×U Y° ±qñòty”ùæ5J¹fDD”›ºˆ èÇgñÁ Nf¾„›#þü²?j•/bäšQncè"2‘±¦¯:†™«ÿÑÍ«ì^P \ýP¾¸«kFDD¯CtóïÇ°ÏÆ èk¦(†.¢\¯Vcô¢}Xºç‚n^½JŰçËþ(R€£ÌQÞtÚÿš¼Z×"=Ÿôiˆ¯ßmkÀZŸ&øæÎZŒ®ºˆò”èXÞœ·[ÿñ×ÍkçY[¿è 'Ž2ODyX¬Jiàj]§ ¾Ùü_¾ ]9ÁÐE”KB#bðÆôÍøëâ}ݼþ-«á·‰Ý`cÅQæ‰(SÝܵ¦áóYí°o÷Y>@éæC°zÉçp³Ö`×ñÁœÕ°prCƒn£àpv. ݹƒ=ñ×ê97{‚cx }ú æ¼×J¨3¼ßìOQ¨ö4|1µ>ÿ|36ø=EÑ+K1Fºï“hâÕJt:ßO}¶ M†‘ ]D¹ ðe8:MÙ€‹·’F™¿»'£Ì‘ñT¾~/r¼ž#îÉ׊s³´ü²q]àÓ9iB……”ÑWà98xøh"¡[ݪ˜»¦fµxŽ¡Vañ¿Ñ¿n[1 xŠFXBވO~ûo5*ŽW· ºçptìþ#÷fx?XZBuÿ>„ënÂ1ò8j˜‚‰;NcTë ˆ|rmë5Ã×ÕëcJG‹ Ã+3†."=»õ(&oÀMé:ÑŒ!-ðù[ÍX+"¢žµA± )™Z Ý[ŸàÛ/|à =§wOm…ÏèI8só l –ÆØY‹1ù­–rÂÎ2Ê_Ÿ¿‹ž3¶"42Fž¶µ²ÀºÉ=УG™'¢¼ãÚŠw³´Ü¿¾Ð|ܪ ËEGz±‹Q³ÿ³ìWFó/^ª{m?×—/_ÂÕÕNÎy©r$SãáãH””nþ·uŽ+½ðhß:ù»]q ¾Z$ßßI mÝOK!·x … Fè³›ˆ‘‚”]¼çÏŸ£Ä2{Œœ0«Ðwÿú¼³ß¿„u‹!úùU´®í‰ok7ÔN¶Ü{(šÎܽC[âñùmhÕ®ªÕ¹žåƒô^Ö«Fcÿ9È€¶ó“RŒI8ÒÇÅÁÛ§÷F«ÚeŒ\3"¢ø6ï=‹Ös¾GZ71ã°çê t¯îŠ~[ˆS/h(ÝÅÚÚê¸(DÇk`¯ŽÄ²y?॔fÂÃ#P£Qã ï—Z­öÝQrLüò×-Œn]áÏ`õÎK°âWX?_žác pö7×¼BWèK„*‹¢vµ¢ò´m¡J¨æn'%ç Ü;zgl¼°ó–r³c)Ïðé2 k6î…g“z/ëUc€‘ÿd(ÿÛycï×2_ÔÕ{çôG E\3"¢ìk\µJqÆý§¡é–‹V®~-«åìA,ËÃÃá?4ª;Ÿ¼€çÓ0±_8YÔÄÿ¦îèÖU1±°;ZôûÝk:È{‘<{Fÿµ=Q«b%)R#¦Iý1}Hk”ÛBº_§tï—šU±&ز~>Fí„ob¤ü§±F¯‰¿âÃΕ`‘ñc”þyFö77Û÷̃ì=ºbjߟ0êÍ1ìÝO¯Æie[líÓþ¿¬D±*Uá¢Ûu¬@•ªU±ú’ü]è½L#O‘9kÖšðÅïÇtó*/€}óH×ežˆL›……÷V¿ŸË¢@—Q 0iÜ‚s5±Q¨Ön®}º ΢UCõ í—D£¢E t,Žå{Ï¥\M׿1N¾_.dt¿’µñ<öNŠ»Õîü.ŽwNgw«c™ Cퟭ­5«Ð%öàq/‹ØïãæÍxr;…ŠW’æj ;‡’)‚½½="Ã#ž edúÊ þ wŸ„dyyÏŠEåQæEKå€& 3}º ¸Æ lS W¬ÁÕ"=±ºy±Ü¹Ÿ˜U躻 >\u7ü¡„­ˆB*Lë]ã¿Þ„Qnˆ”BRò¨ˆˆ88•‚“ƒþËRÛµkWºuÎh>ßë®Z¥ñi[7œþ÷H.ÖˆˆÈ|( TÂgÓÒ?•ŽÂ¦6ü{Û6oÃõ‡OP­ë8ßç ±Î|Rvïg(æºnÞ„­{EµMüãZ¢\¹RØu÷ªyy pÑ~«íÑ©øú^‡GoTópÑ{Yê§×ÛÛ;M}EàJo>å‹.gyÑÿ–Œ‚•Y½ˆHÏø#;%¥ke)te|t·µs ôöú»7³{?C0«o qÔBܤiØyñ1zÔ.Ž˜ ÛØòç)45î^•Ð3ðåŠ#˜çÓ '×aù¾X2³ÜK齌ò‡fîòaÕ \DDô*fõMQ¨þ›Øºø& lŽcU¼­úÍÀÜ÷ZCa©À¯[VaøÈ‘p‡Â1aÅ6t®ìõ^FùCùâäÐEDDô*fºDGúvoOÅé’wOoì;þî¼Ü(#"""Jdf¡‹ˆˆˆ(obè""""2†.¢t¨âÕÆ®QÞ¯,r‚=¢TÄé|F|»'KË®:x–ycü"¢\ Lxˆ‹>[ T¨iì™$†.¢dÄ©}>^r¿îO£kt7O,ÓQwfz"¢|%4H \Ý›—´Óã¥Û ¶kçpÅüµzÆÍ^`)ËÅkl0èÓo0ç½.P¾úÎ&‰¡‹(™«ŽáûmgtÓoµ­ÞïÀÀEDùSèKà“nÀí+)çC ^•²¼4áþøêëøä·ñV£âxqu ª{GÇîк„…*Ÿ÷0t%øn똱úÝôM*aåø®P*™¸ˆÈŒ mÜ¿ž³u„ïy½Þ}>ùè2D7©p¬ŠÝgNáêÅsØ÷çÄLJ£ ÕsÜ{”pÎYýò¨\]UžÇ¢h!yúáÕñÇ¡s(Y»ºyÕ4Û&D2-+÷]ÄÇ?ÒM·©Së§ô€•_¡DD¹Aõò*ºµl§Åš£C£*°±GD¼ò ÍT®†.MÔ#¼Ý®9¢ÚÍæý|m77„Š-Záñ¼™8>çÌ»inVè•¶ÿ{#¿Û+÷çU-í3úÀÖš ÁDD¹å¿­Ëp\é…GûÖÁAú}«‰¸„ _-2vµrU®~«\ݽ g ôÀ‰i}åéÍK¿G­‘ßaׂ¡óÛŽÚƒcú¦°ãÞ2’ƒçïbÀœíº!"j”-Œ=³ûÁÉÎÚÈ5#"Ê%¿œ~½åOî¦Ö¹èRp¯(¥”4w `çíjX[[C…èx ìÕ‘X6ï¼”RIxx„TÊÝ‹¯íÆõèÔ{ œ-¢Ù ý‡KÖBd,çÊÍQ1l6ĕؠ@FpòÚCôüb3bââåé Å `ÿ¼(èlgäšå!;I¡k•6x¹Ò®9›s¸Ï^£ÑmOÔªX EŠLjéßcÒ@LÒåöŸF×êNzÚ€¼#W㎥…ÔñÚ/´øggðïý2ø¢®[B© *µì£LÆpéÎSt™²áÑqòtÉBN8øÕ /È“•¥!‚׌5€ÿ9 ï)påü³RéXË÷žK9³ëß—ã5ç]¹ºjyzbüüßñI¯ê8øíwphí* ûÜ…«¡$û)“Ý|„Ž“Ö#(_ý7&. ‡¥­,ö%:»×ÄŠ='Ñ®Q•\}x¢äž…D¢ý¤õ¸ÿ4Tž¶³¶ÄŽ™}Q¿rq#׌ˆˆòƒ\ïÂ®Ž ÁÉ¿ãq8P¿E[T*î ¥k9´o”ÛL”$$"'o€_À yZŒ¿µiZO´¬UÚÈ5#"¢ü"wÇ銸‡¾-šâxHA”sSâÊ»cñýG1´e…Ü|X¢"câÐíóM8{#PžV*øýÓnèÚ¨¢‘kFDDùI®†®s[ÿ‡ë¥ãöÖy°S×vÌC§é 1äÈ0ϳ*Q^«ŠGŸ™Ûpìr€nÞÿ>舭ª±VDD”åjèºsç.ZvxO\BÕá8nÂ4@v §\¯VcÈüøóô-ݼù>­ñn׺F¬åW¹»{Q£†¥¥•nZ!ݶæ™ñi•(Ðh4õý>l8zM7oòÀ¦˜Ø¯±kEDDùY®w¤yŽGi'TO¡ŠÁci:J¡}øÂÅ‹ÀŠ­^¤g—Á²?/è¦GuóÄìw¼ŒW!""JAtóïÇ°ÏÆ h>93M®o抉=¤KÊyî%½:|£|Q]µˆ º…ñ#}°æÀyh¬àíó9–Ì!Ÿßñî©­ð= gn>mÁÒ;k1&¿ÕR>-Qn”‘qÌ^ó/¾Þ|J7ýf,~¿ >+DDY¦ŽB÷J72øì´’¾Ë²?&øæJß™]'0téÅW"lÂï°µÎøa,ôZƒxÌÕ¾®oâVà!Xùb@·ØÞã ðˆÀàÞCÑtæìÚÏoC«v}P­Î ô,¤÷²^5 èsÃ(‹~ÜqÓ~û[7ݽq%ü2¾+”<ßÑkR'Ké(6ãEŠŒ—.ï®J&.WC×_ ÷QøiLëÜ|õÓX¶; [ôb)V»N_‘Ëîþù5ÎØxaç;-!ο]ʳ|ºÌš{áÙäÞËzÕ`m¦$«^ÁØ÷ë¦[×.ƒ S{ÀÊ’Çʽ6¥P ´>m™uy >H*ïžã‡Ù·ìS¼¹x3= A½n£±vÙ ²6ÏÊfÕ xùž•¬Ž«>Ãèå \Y=|&aƘžð÷óC±*Uá¢;×£UªVÅêK~ðw} ÷2 2l¥\ðÇñëöÍnhŽÒhX¥8þ˜Ñ'ÓVV""z…"c€àÍ€F•r¾…àP_ _¥r¶~Õmü÷¸*Î\»M:Ô«‡¯ÖvÅWï˜çê¹þôäîUœ­pr–ï{%3;Á½tÞË(wÝ|„Ÿ­Ç˰hyÚÍÙûç D¹bŒ[1"¢¼®ÒÁ׿OÜcÀßKú¢-+¤º@ɯôSõKüþëv´šÜêà[غ÷,ZÏþN?ë΃r5t-_ Õ\ ˜XX°fFù¼÷ÂãaaëŠ>c–`bïšC4ýºe† ·ñp(\VlCçÊŽPÀQïe”{<C»O×!ð¥¶ïœ“5þü²?<Ê2r͈ˆÌ”Uq à àå Ìr½¬Rµm x:B£:ÓñøÉ x¾1ûÕÑËúó¢\ ]^ƒ§ÂÐc€;—®5ûϤ[æîé}§ÓïC•e¤ÏC"ÑaÒ:Ü{¢Ýç/ŽNÜ1³T)näš™¹¢nïÖeô²:ËŠÝ©râñzj9ËãÌj÷"™·Ðˆtš¼×î¿§­,”Ø4µ'ZÕÖÏeÂÂE{¡lcè"“‡nŸoÂÙò´R¡Ào½áݸ¢‘kFDD”5 ]”çŪâÑgÖ6ü}9@7ïDZ0°µ‡kEDDôzº(O‹W«1dþNìù/i ¿yÃ[á=oO#ÖŠˆˆèõ1tQž¥Ñh0zÑ>l8zM7oÒ€&ø´#ÖŠˆˆ({º(Ïútù,ÝsA7ýžw]ÌÚÒx"""ʆ.Ê“¾\{ 6ÒMjíÇt”Ï.@DDdŠº(ÏùiÇ9Lýõ¨nZ¡øë„®P*¸ˆˆÈt1tQž²úÐŒýq¿nºUíÒØ8¥'¬,-2¹Q>sÇxñðl (ùÞ 2 ]”gì8qC¿Þ µF#O7¨\Ìè;¾L‰tn_>鄾Ú&þd>Á+4Ø¿¨'…Ér&¯¹ýü2¬,¬QÊµŠ±«b²ømFyÂá ÷Ðö6¨âÕò´8âŸsúÃÙÞÆÈ5#ÊC’.áÀz@-½g>]X˜xðk¼7pKÚFgW`Á@ÅÚÆ®•~h¤çhÅL”y"mc—.RHV»F¯M#ý[öïDÜ{y ßô:Œâ.å]%“ÄÐEF÷Ÿß#¼ñÅfDÇÅËÓåŠÀ^)p¹9Û¹fdÒD‹é¦€R•€&]›œK¸Ú¨ÝÖI?›nðJ¸tÓoHÁk;P©Ž1k–s"Ï ܈šbÚ*VÚ¶Å&×:yöÞ~Üyq éß¶‹‹0Úë;½¬÷Ò¾åx¸[·Š˜µxy™oKCÕ•»ÏÐyÊ„GÅÊÓÅ\°î¸v6rÍȤ‰²ð`ÏïÚéaÓ€7Ç·NéQK?4"BèHíuòÛâ:<ˆ ¤ë?WIóCÒ_ÏáMÀ‹@QwéSÝGùZ‹VâÄk‰µmµ¶Ü*áZ,ŸØòb•p;qžB™vž˜N=_w_)HXZ¥¼¶²J¾80ÆRªC¤´­ŸöL \‰Â¤à5¡ðÕv r}þÕ 'YàÒÙ·Vš/½>'þh´àõñ–Öxr3[÷-^ßÜ"_^׻ͿB›ÊuÓq'ÑkÀLÝóÞiRç7Î@ó~ÃÐäî?(gkžN1t‘ÑÜz„“ÖãeX´<íêh‹}ó bIW#׌LZêÀ%¬œ¥ý<1çëa(*"!%\GIÁ("L{-BRdÂí4eaÚòÄ2U\Îë“(àºöbNDðÓ(\°¡Q‘-S…ÁÄ0g!}­YZ¦ôcBàLHÅw¼UTÜß"aÝÉ/r¨´Ny«ÄuZk_wG¶¦Ý®ë´»'þÏt['õàÊÁx\º-6.#O×í3í?†‹y.¡‹Œâáó0´ût¿ —§m­°çË~¨U®ˆ‘kF&M®o¤ÀõçïiË~ýR’¼ÞH„¯Eש[šRL‡H?Ïc ¾YùZ¼ ¼gìZèßÁ Úë|¼ž?GWWX%f,¥5 ºZµN¹¡‹ îyH¤ÜÂu÷‰vW‰•¶MïƒÆÕJ¹fd²Äî·Gw€M‹ÒoYH´þ;í%/­,Ž[{ÀÁ9é:ñ¶SímÑR·n¡6ü%»ùª×—BÉ}à½Ù€£6xŠ`(®cbltµ˜Ö$]ÇÅj×+ˆÛ¢õ%ñZ­N9OL«b3žŸz©—K>?ñ¶Tâ’•©ÛŒœ,jЇ^ØûH–—ýïî,<<2Í|”hQ±F{}›íz*T¡O¯#Zz)ڋॎ‚¿ï-¯ìg3míbè"ƒ ŒAç)qõþsyÚÒB‰õ“ß@;ϲƭåm¢ïÓ³‡Ú`•ÞEì¶3±[ÉÞI 8Ú0$®í¥k{Ç„ùŽ ·Å¼„2ݲ ËØ',cë Ý%•U5›“ûjƒ—\M:×/_K_Þ¥M´óqL0k(pâÏ”ó=÷¯£ç•ëjç%†ÇÄP)®åЧ}$†¸øø¤yòuB0Ìj å‰S·U²õ«Ò®;1dêÖ!M?y ½N­l5í:z2xàzâü·›Î§ª4Pãøí?Ы·(æ\6[ë¯ÑÎ%FwÁ²~ø C\Ùñš½·çöêeè"ƒ‰Š‰C÷Ï7ãÌõÇò´ø®Yñqôhf¢_¤_âKí©ô%õX Qok¿pßMº$~9f—è—#¾$‹¸K?±K¤lY-M"‰P”¼µ)±¥)ù´QÆ:UíæÀœMÚàU²‚¸. DàªlœúèƒðÅïÀÌ·ã{´óÄvÞó׎AÖ´‹që—ŽSú¥lK¼Ö[t|¾0^ݲ 4ú9^D<ʰ\¥ŽÃ‰;;ѳöØl­ßªXSl^7#?èˆ9AÑp(RÿÛø*Ø™g+—ÀÐE§ŠGßÙÛqôÒ}ݼïG·Çö5X+Ò}™^·Õ%;Ä—Øvß_¬ÝNj­zö(ýV¬_ÜÅÊÅËjÇx:°!åº[´F}i¼Ð¤‰Ákß`À8Ó\‰DgôÏf üÎj_ã0ý¡>괾ܨ ^an%á$‚¿èW8üsc×î•\ì cù›—¡T¤?®˜ZzeT–Uu»ŽÂÒ%¿`è¢\¯Öàí»°ûTÒ!ʳßñÂØ7ê±V¤³î[`õé ®ðÙRíý«CUò–ªGÒåe`ö×+Z£D¨*!.å’BVÉò@ÑÒ)ƒTméËîë1Úà•¸Jë=\‰Dðs"‚×RðÚ±Bûœ5hkìé‡^³¥SûÃBìÚì<:ÕØµÊ²ÌBUNW~ÄÐE¹Jô xÿ‡}XwäªnÞ'½bÊ fF¬éˆÀµ|ºöö‘-Ú~&SVhwÅeDô]yªD+•8ºL„*Ñr%Žôˆ\‹jC•®¤0U¬´¶%GìJ»ù²ªÓ›ÚëoÆj·I´˜ˆŽææ¸Ì™n¡÷hc×BÿêzI?vâò¡}h4tбkCFd¶¡KìÖµ=¡è2ÿO»¿ùî©­ð= gn>mÁÒ;k1&¿ÕRž%7Êòƒë^ÂcÄ2Ýé{^ex§ÚXðn›\®eÉꯀ_¾L9ïï?´×bÄl±»ïq®¿Ç÷ôÓ¿J´¢‰>U"T%¶R¹WÔ†ªâe´» õE/±«Qt²oÛ‹Œ«Z}<»•ƒ–^2 fºTX4~Z{ÂMä î=MgîÀÞ¡-ñøü6´j×ÕêÜ@ÏòAz/ëU£€1ÿ#NNÕÀÕ׫*~þ¨“ôÝÇ/?£K/p%Á+1|e‡è^¼œv`òp%æ‰Ósºûòu˜r'ìü úp³;Ägv¦Š|$]>4DôCìÖ¾VWú€LÙêë]Vúß•dÎ-\€Šû+ŽO˜_˜eèòÛó=–\-‡)CÜñÛí¼{G·àŒv¾Ó–Òw~)Ïðé2 k6î…g“z/ëUc€qÿyÐêO»ÃÂOôjv.ïŽK¡«J&»UàV&_„‰ý«D˜»ÿÕZâxkÊ µ¦áËN /ÿ¤:z#ú:9·‚·e¼Œ]mq)x™ëà”³ ]ª kð÷3n;‰¸­ctóýýüP¬JU¸è¾ó¨Rµ*V_òƒ¿ë½—i€|³‹1«¬­LlÔå¨Ë@|çºKdU°)o˜úèƒ<>Ñ `´Ó«—õ“^ÁÿTц*¹ÕªLR –£Kîו’„îbn!ÓO[é¹rleZ×¾àÐD U'Ò/·p•Þƒ/¢3 Z-½(üºD‹qz{Äga¼´Ì{€ÒÖÀ#c2¯Ð¥QáÛ}PqøBt­^Û“ L ;‡’)>²ìííð\(Km×®]éV9£ù¦âqp$¼=^}ŠŽn3¹m­å¶¥N¾r¹sÏÞÆ£ˆ¼}$¦"^…’þÇQáÌn8?¦J¡©`!X¥æ‰— ½‰ûíš&Íã†=ù RgJR½àV”w~õ(â—žÀýpÓ:H¥ M#4-ž~è Št‘~'(püºøñcZŸBBõàîx:m]Mé}¸rH~cV¡ëêî…Xq³*Ž/ëšæ÷ £ƒ"¥”¼*""N¥à” e©y{{§™'BHzóM‰ÀSô¯:ζ™Ÿ¸74Ú Îu>”^q&t2ëp7àn&¡Ë¦ŠôÁùžÍGÃ3¯öɈÑ‚¿})ðüqÒüÑ@‡ôïYвv¨Uyj)2ÙI†[K »G‘~«‰ÄN*{ˆZ-¦ –…cî×G-Ÿ²jé¦&ñ¶¸„kwŠÛš¨„å“Ê51Úû$Þ_,/•Lj¯¢´»]­ïÊ®Þe?Èým2˜„m-4’­\ù…. V-]Š€KA¨î¾[ž#}áDÄï‚û¥s8òEm.Ú`é3ËU©]Þ×÷ <ß#1T•‡­ö)ímá>ð‚R™C})t×ö7±p’¶ÕI{[©Ç£ õMü}®ÖLø»e²(Pi¯´= R-½°.-½ÖzÁ›Ó)Th/nämrÔn¿r"ÂMdBÀ‰L–Ê5Q)§uá(Õ<]°Š|õßW_̬†J­¥RÚ »låÊÇÌ(t)0wÇMÌM6gÛìAøöayÈMtšc¾\qó|Z!àä:,ß÷Kfv‚{é ½—å'óÕÁȦ×`k)~©¦ü”¼ä€ÒÂñþææè®¯ÏÑ/)EȈO !r ILäeÔ _êda&á\kš„s°%ŸÖ$„Ë‚é×AaD_•ÊÝ€'Ü7±>‰uJxñ…•¸>Q/¨’Õ3&íöèKéÒG<´heDõHúrß®½¤a™6ˆÉ·SÍO iÎÉn'ÌWäÒÇŽÂZ ÊͰúY§ƒÔpµR ¢cÂëÒ®Žô’’êb€Ö ÌÈ'”NBñ)oËב ­HQ å‘)ú^ Uã–4»sQ%,-¬¤}Qú•yx²òkÞ˜QàÞTÃBÚ¦kª (ľ\ù•…®Ì)lKá×-«0|äH¸„Cኘ°b:Wv”ÞÛŽz/ËDVøßÎs ³Ç’ãÕð‘ÜÚ•’£µ J ¦´?'%0Ÿ¤V$†Ž ‚‘:1°$¢d­=yøbõ ;dìšèE¼ôdZd©¶JÛ±Y\²KiÿêÀ–"¬%ÞNœ/Ý?£od1¬@Bè SiðÃ-µÜ°÷Cm 8XÙj_O…Ǽ^ðÓµ%!ù:ñvTç¥.|õãfbÓC ®…i`o´-’ðžP§íKšk6 Ï£6<(ì’MÛ¥Ö]ìŇqÒmݲvÚîЮ_±(Ž,¿ æ¾ü¯}„sÁ[äÛgŸ?EýòlåʯÌ:tõœº=“M»{zcßéôûPåF™9ãs}ðãü¸ã¬<-Z»Þkr ¶V)[»Ü´¡©W­»Ò·à]ÃWT®…© }_¶+¬€2·“C…¶õF|ùˆ/9ùZô±²Ô–«•ÚJCõ»bÄŸ^­Ñ6tˆK™jÒ¥fÆ÷·ãàÙ“­˜]7Ü  EaéËϱ¥v|¡xq K¸¦ÖG+JbQ=Éæ ,2hiKlas•[³þx¤F¬”ßE¬ß¨AßòÅ´­A±@à¼W„¢ÄyQȰ•‘ø†ªåÀ%^Û¤mô*¤„UŠ‘X)ÃLš0”8/!ü(2Gé­#10)ráHd›r€C3 ÚWzþ^%f×°ùÞSùZlÍæ@'Ô“Þ׿±eôºÌ:tQûf7V¼¢›×°JXÚ’¾Ÿ 9â²BJ }ã(­SM[%ÝáBɦ¡Lh¬BH²u(’­K\‡ÿ… W¯ÀOÊ7vÖhQ¶5àÔ:á±l’ÝÏ"aý‰™P&/“ø˜–É–S¦*KœŸ‰ éüåÒe™¸Rµ8‰Ó÷´xè7¨\çÕBM¶^:ˆQAØx/MªO‚¥Û Œ——ws%0uµîvXBP Óî “/aÚà’8-Ê䄘Rð‹Ö^2Ø+Z¹ö=Ñ>ŽøÿÞ'jt)v¢ÙëÙ¢>¾¾(´»9uAÇ!YÀqJ6Ï>!Xjoo¾.ÂÈKy»^ÄGU=Ð®Æø”¡ÉT‰–Ê»oI¬¢þ[»69&Nƒvñá_¸üè˜vZºÜ{ygîïGƒ2[92 †.z-±qñxsÞØ|Ì_7oPà÷«a/~͉ßoÒG‹Uq)‰ñu&H¯²B Á&1PX$ñò³LBÒ IŠd¡&y€1¬+wŠIkª¼u[Æ¡i“Y°°.nØJÜõ¶üX'…T»Yœ.C€žïIó´GÏfäYø}¢í+ô\ZåßÏÕhã–Éä@ ]¬Šfc Ý¥,/µ*Ñ1óLzM=“6çbbžH—§ˆU…"N.]G .>QqQˆUÇ!6>qj F¾häÛrK–t‰Jv[\‹Ð—,ÛE¨€áçòb_§ „KöˆM\wõîDY£¨SYu.ƒ’.QÄ©4¬-M°ÏC øçÚ!LHŒ* BnáIè]< ¾‰@éúQÈM<¹È¸°” K?è¶œÿõKwàÙ9ò!†.ʲÈè8ôš¹ûÎÜÖÍû ]8¾ëúñÉû“(´¡ËVúà,äcøŠæ‚MWµ§Å_rÑ¿-*öÎýç.Öœ:€4­D"`õxèúŽ6x½¦­¾‡Z“¸ûLmD^ˆW«¤-Ÿ(é %:áv´üå%ßóD(ŠSÅhËÅmé:*.<ͼy=‘Òýbäû‹õ“~DĆàÿÚóÒ?»Â(Y "Š9—Eqç (æRV¾]Ô© ¬, ÿÃ%Ë ¾iì¤K¼fŸ†àqè-)LÝ‘nßÇC)X=’BÖËȬ·ðk¤÷Û½ k8s”É_]CeQhd ¼§n±+ºys{<Ä$¯½ÚÖ Aì*ý0~ªí7Sf–‘j›3âÃõyøCí/ÕÐ;¸ÿòüŸ$ p(bÏ$_ ¦¢¸¤7Š|¾Ø° Ç¡‘þ= €6™ÖàšY’ÏÄsõT¾ø>>ž¢,1‰&B™a¢e¬¨˜v©kKÞ=©á1!Ò{ÿ†ÜZõ0ø†°äV¬°{ò ½>D¶œÿõKwdkW>ÃÐE¯ô"4 'oÀéëÚÁ5Ò'Æ/ƒ}ñvÝdæ–Å€²+ÛjÚ1hT/;#ÕøÕįÍà¨gº]¡·¥ÖÛ ·ï@­É‹»¢Ì‹hm±‘¾à­-l¥Û¶Ò—½|mgå0ÏFÞE&–±±´×ͳ’–³³rLZF\[j×%Zàæx;Ö4{kg,êû/m vcõà‹Ý½R„ÿÔÜ]+£€]¹F„g&ý’2¿')Ïi˜ÈD+Q ‚ÌÊI·Í/ÅÅÇÊ»Åûý¬niC–¶Âc‚_{}J… 9–”wís)‡ÒßJ¬gÃÙ¯Ò]^5î½dkW~ÄÐE™zô"&­ƒï½çò´8:q»Ïqt¬ä—´mu Ì é[´˜vZŒ´œG„EÉ»žJ¿REÀJü ¿\Åî/JŸ5ζ…äd%ë„pc­ @IÈRi-½.äéÄ d“l¹ÄûÛ&%í}íråþªÿfeºë2*6{®,G¿zãõþعéò£2 \",EÅF`n÷ߤ¿¿µ.T<‘Ø“0íë]¼î_'ù?Mýx )Ôi™h%+!·’•–[ÉDȯœ»¦—ýó)ZWˆš%šçh]¢»ØŽÄV‚¯kû\IŸ™ý 2ãh㪠V Û/·fЇnöÞ¯\ßf¶vå; ]”¡ûOCÑîÓµ¸ñPÛÑ·°c4v¿{ Ü“NFœ`·ÔbÀ"ç¸Ù!ÿR•ûX$¶\…iw DƆfk.v…PÌ©n?¿„8uú»Pâ½ß e¥>Ù¯ü À¦¿¶ñ©Â‚sAà  ÛpÀ­Xö#?#76¤[&¾ÀEKÐ7½KÊtN$‚ÆA¿Õ¨\¤žúÒ#¾ˆ÷ûý†.5|Lªµë¯ëP©°'J¬–á2ןœÁ±›[ÐF ,"x‰–/qIM„ÒÀ„"= ½—ðÃ$ó@†,2@Ü]+én‹Ö2{ë,œ`]²ûÊ2¿³C®Ï¼7öf)Œ$¾ÿw&Lq-ú¾.ñà 1Hw)/]—GQiÄ®X'›¬UCü žL—Z®·èÊPØÉýµëI¦‰¡‹Òåðí'­ÃƒgÚ#oª Áî{QÁ-iTl¹Ãkñéišñk5ƒ~iö¶‹ß£y…°P¾Æ[I|©Ülýpå%+½F¤¿¯­}v6-S¢3ðÑ››2®žôO|½± m«d2|D#Ff¯UÒ §ïíÍt9ñe*ZòLIÓòݱàà0Üxv.ÓåDà|ñZ¯yqIM²gáÒ{ë–ÈÄkEômï³çR Së!•”Y1] ++ïòD+×®+KåÛ÷ƒüðŸô<6*ÛYW¯Ä~–"L=Ò…¬›Ùzÿ‹®öEZ«ÊÉ»NåÖ+éý_Ø©” å«W’Ùú¥ûÏï‘öuhçÛ¥œaè¢4.ÝyŠ“ÖãIöˆD¯ Ø6t? Ú'ÒFщ@á÷ÒÜWô“Z|ôyWâ¾Ç³ü WP©ãäyÑ¿Jt^¿º?ðŸ…=¾.±‹«¸K9ùƒµ¸s9íÑ[ ×â‹ 3∰ò…jK_N•2\æÆÓs8~{ZTìõêÊÄJ¿ëµaë®_ÚòÞïÍ»Ê\|2Ás¹5#ó±²Dð:pȤB—ø"ý¤í2cW#WÔ+Ý뇼zÁ¬˜³öý’Zê@&†CšˆCÏ#e³…,©UY¥ŽE´¼­9=K?DKœèg)>^—øa•Øb%~Tw./ß6§þidZº(…“ע˔ ׬uoá—Iá%¡c¹+Ë}¡ôIÙ%Ýûo¿¸XþpŒˆ ÁŸWW¢wS”'ö³Ôg£íÀ.Z®²ûÁ*:±Š_§‰­Tbw@1'íáñ¢Å-»ý%—ëŠïŽŒ’w1f¦BáÚ™¯(ä°s¥v0Ó—©FaáªE7 Ï zƒlÕóuU/Þë†Ý7Èc‘ùxU K~įøñ”ØJÌÏìÀ”¨çò%9àÄg‚¸¼Jò÷¿øaU<¡ÕJ´ä°/"‡q¢¼‚¡‹tþºxÝ?ß„°(íy'µ½€9]NC—Y, e¤à`ï™îýE+×A¿UòmÑRò§ïr´/*,í¯âÛò¯Öìt`O¾;@tÞ-åZEú`-/·bvp‡2Z†—óÆúr9Øðà&°u °w5•²ÌÎèu}gmCT¬ –J5þ×ç|':ërÚ#mÒþÊM´ýâ)ZªÄ!Ó?ÿ3áµê!y¦Å~QË·]Êåý“»|Øò#ðÏnmÿ­ä ÕŽßõmé[$³¡ß‰Ìƒ¥…u†}ÈTñq¸t_ìê™a+wÿzÑ©úP“ë‡G”†.¦¿ýðæÜ?¯†³m,6½}ª÷˾KñíÄ«5Rð‰Àž¢V‰d'Qvé”ü*ÃóŠ>ZÝØˆÕ§ÅøHé}p*PÖͪÑí» MÎ?;oj)\ý» ˜»E ^N@T¸v÷¡ØøèNÚûy¶’~ªÔkp,¢ä#//Ͱ\ôí öO8’1ý~¤D¦„¡+û~ÛiŒ[rP>½_ݒϱËg/J¸$צÐ(í «3 â´b Ã+ÿÍäQ4òÑrÊuÍÛ­X™‘×)p%ì*¼r ˜Ø¨Õ øów ôeÊå-­€Ö½¥°õ¡¶…‹ˆÒåûè8¢U™.#‚×k¿3t‘Y`èʧæ®?É+ÿ’ow­~ë„£MâàœÒˢ䗀k¿tï+ŽTƒn>¿0KâÅ y÷ú®Dïºé§ò†”:p%»SïF'œ™ŠP.a¸:™¨úe:à·!×3ÌV­éq<{™ †®|F|€}ºü/,ØtRžÕÌ‹z‡¥2aÜ&¥PúGÀ±Eº÷¿ùì<–ý; ÷^^M1¿B¡Zp±+’Ácª±÷Ú/èä1Ì´Z»Žnf MÛ>µ"î@¿€NoiJ$¢,Ë,p bÈŽ©Eæ‚¡+Q«5øà§øqÇY(Ìó>… ­“A%Ÿ´úWÀ¶JšûFÇE`ÃÙØ'…§ä£R—v­*Ÿ¤øÖó ™>¶âÁÎÒDÉã»À™ÃÀ†ï3\6vÚ~]ïÎÔîN$""ÊCW>¡ŠWcè×»±úÐØY©°jÐô®¬ã·­GÂI«Óvr¿ðà–ÿLä0‘¾¡wà]s¤IŸ/]¢3ü…cÀÙ¿´§å¹—Îhñ©‰]‰kkï[¿mn׈ˆÌCW>7çý-ÇüQØ1 ;†íCã²ÉÎWæÔ(õ Lyž¿Ð¨øíÔtü{{{ŠùbDów›}%ù`’Ä‘÷üSû´-Z—q±/¯T¦ìÓ%W…šÚÀµ`;àX ·kLDDf€¡ËÌEFÇ¡×Ì­Øwæ6ª ÂRàªT84i·w€bSE҈΢ß×ѱê¿Yr'øDŽ6ðfƒ)hU©¶O­c4âT<þNìÎI{:žä¬mµçA¬ßhÐN{ÒéO¼§´«¼ÉÀEDD¯ÅìB—ï¡ßñþø™¸pû¬ £Ïè/ðíäÁ°•2ÂÝS[á3zÎÜ|Û‚¥1vÖbL~«¥|f®Ü(3¶ÐÈxOÝ„cWкâClz®v‰-:J ø4mèJFœ7mù¿“àûøxŠùMËwÇFÓ_y’è¨Vçz–Ò{Y¯Œú·x…Γ7àôõÇRÿ:–õûÖ– »ÈvRÈøpn¯[^ ±óòÏØrá[ÄÅÇèær,‰áMæ n©6†Þ„×÷è.pö0pþ¨v·aDhÆËŠðõZ Új¯‹•É|ÝâüˆßìÒ¸}Öœˆˆò³ ]q|öãjô——…Bú­Ñmk»àú;¸§:ƒ36^ØùNKXJe¥<{À§Ë,¬Ù¸žM转WFû;yELtRÀzUxÑ/«‚´Í;hwVk mµz%Ë=Gæ¨ÊDD”™Uè²+ZCßJî òÁI¾¢ÁœëÂÿÀj«R.ÊÄRªT­ŠÕ—üàïú@ïeyÊðî? E»O×â^àsü6ðo ip#©Ð¦’¸VÖîòdâ0b -MŠa ªad‹¨P¨¶¡«Ÿ¹×íïV\ÛŠÕ°öÚ¹ áêJDD”ŠY…®äBÎa€÷ xMY‰ž5\°m{$ìJ¦Böööˆ @x„þËRÛµkWºõÌh~v<ŠÁ´mw ŠÇÞw÷£u¥Çº²çQ•qæ¾Tþ¤© xs §Ã6 Bt  X¡¦CT·lk'p z«[vYE…¡PÀU½}…î_mDp†ËÆ[XáeÉ*xV¶ž–­ð‚%µ§0OÇßÇ3¼‘!˜eèº{j Þ0ÞÓ~Á¬amåPäèà€H)$%oŠˆˆ€ƒS)8åBYjÞÞÞiæ‰À•Þüì¸tû)|>[Gë—òI«« N*,Ð…<梓 ÁQOñëÉé8ùtgŠû×,ÑÛÎ1þ0¯Û¾LU ~kÀ³5,<[¢°µ-DWžñˆò}þÈ&Ódv¡ëî‰uè0`¦­:„Á^•tó«yx pÑ~«Wyw ¾¾WàáÑ[*sÑ{™!w-ž¼ö]¦lDE×ûØé³E¢’ ‹|$]>sË_×7`uša \1¸á4xU죟a ÄíúE{¢g1ìBV$v€C9ˆÝ†bü«Œˆì"dÕkµðDDDy„Y…®øàkÐÆý~\ \åS”¹{õFsÌÀ—+Ž`žO+œ\‡åû^`ÉÌNp/¤÷2C9rñºOÛ„v•®cÍ›G`opÒj…Pr>P §< IJ>ÅÕÀ)îÛ´üx§ñ 8Ûºé§2"pý8 ضD»[ï£ïïwÒ.œÿ[;¼Y×3^§è_©Ш£²Zf¯<Q`Vß^~6àTÀ3\èTã’Ío5jö~û.~ݲ ÃGŽ„Ûø@8®ˆ +¶¡seG(à¨÷2CØ}ê&úÌÚ†‘Ïã›î'a‘xÒj  ô¨lëaç…EØzñ{ÄÅ'u8Ã@ø4‹:î­õW™ä+qú»¤j ëPàÎUàÔ~íQ†Yé/Ž0lÔ^;f;À‘0«ÐåÑwºô]?=ÃrwOoì;~ªÜ(ËMÿ¾†·çÿ¯¼ÿÁؾIVî@Ù_p#$K÷wA@PÒ0 J…:W†~õÆÃÆRÃ@¤\Éç+Åßå3° ŒïŸ8¼ý½¡´ÊTLmÄ{""¢W0«Ð•_ü²ï>úq6 9€î÷“ ìê ²øB¬?·®ý ’: —uóÏ—X¾P­tÖ˜®äÒ \rxÑšÕ¨ÙD¼ˆˆˆÌC—‰ù~ÛiÌ_³‡FíEýRÏ“ œ;⌺ ~Ý9Ï#êfÛXÚ¡OÝÑÅc,”é¬1‡Dÿ¬½«_½œB xuOè/…­b¥õ_""¢<Œ¡Ë„Ì]kölÅñö¢lÁ¤#ü‚í½ñëÍXœ¼û~Šåk•ha¹5 Ä‹RØZÞœùц%+!@ónÀG µá‹ˆˆ(bè2Ÿ.?‚s¶âŸ±P á¤Õ(q8¢Ö\8ŒÈؤó Ša †4œ†ú"ѳGÀÁ À>)lÜxõòî¥W˜5Т;ðá7 \DD”¯1tåqjµcÚÈÀuØóî1X[hO×ó0ÚKïYÃ?ä¿Ë7¯Ð C}®¿a ÄîÃc;µaKy¨V§]¦Pq m?@ì¾\·P;/1pÕlÌÀEDD†®GÜÒ­C€ÀûiËEê«!©–ohG‡O­ßXÀÅMÛBÖ݇C?%`èʃ"£ãàóõ:¼Ws ZVxŒH•ë¨qàiòA €rn50¢Ùüœ ݦ [WN¦¿LÉòÚ Õ¶?P< §Ýé8(ûõ!""2S ]yLhd Æ,\‰Ï›þŒªEBp:H_î©ñ2Ùîb`Ó¾u?FgŸì ¡RgIAk-pâO 6&í2Î@«^RØx4f‹Q1tå!/B£0ýçEøºÝ¯°²ŽÂÂjü¤I±Lí’-1¼é¹×kÃ<»7ÁÏÒ–‹~XbÀÒƒ€¦9`)‘1tÈý‡¾xñôbŠyá~8>±“º箞Äü¶âDpÖø«Ÿ´¬8qH£/ЬB(ð­N/ŸH!k£¶6·.§¿L¹ê@§Á@Û>€k‘×Û0"""ʆ.yþä<<­§é¦£5XR¡J4sÓÝW¤²óoÆÃ?ÕX£-*ôÂ`)p9ÛfñÄÏÑ‘Àñ=ÚaNÔñi—áªm_ ý ¢žO DDDDi0tÈ>ÿR(RÚî.òô–Gj<Œ6>P£~%vÆcÛ# TÉö&ÊÃ@4›š%š¿úÄ9¯þ§%^tŒOw˜; IgmÐ'—¶È…ÓQºº $V¥Ä܃uðcïñ8ZƒžkÓÕÓ`ìEBUIËŠa ºÖ!Ÿ3ñ•Ã@<º£mÑ:°^{;=Õj(lÙprÕÏÑkaè2 å'«â³¶ðÇ‹PÝÐâ:yàzñ¢– ûeÝ<2^Qd8pd³¶ŸÖå kI¥xYíÀ¥¢UK ù@DDDFÅÐe@±ñ˜z¨:bJHS¦Ð(qíZs\º\e'¤¸â¥dvî¨öèCqZžØè´ËØ9j[³D«Vͦ戈(aè20¿ø(›ÎüðHܾYMªNï·}†yؼL{G1N—gK ý@íiylr8*=å †.rt F™27Ò-³·C™²þ¸rµ"ü\»ûpŸ¶n\Heb˜1J|›¾ÚNQžÆÐe@g P¤Óÿ*A¥Š'1Ã÷"ÐïkíîÄÔÄ9 Ûôц­Êus±¦DDD¤o ]ââŠ2e®gX.º_ÙØ…Àµìàv²Àee­æAtŠoÔAzƬ P["""Ò7†®º{j+|FO™›O`[°4ÆÎZŒÉoµL3f|»V8y$óui¤äu¡„%Zߎªx]†hÏèè’kõ'"""Ã`èÊMä î=MgîÀÞ¡-ñøü6´j×ÕêÜ@¯R,[ã¹ëצ3`ir¢UKì6üe1PºrîUœˆˆˆ Ž¡+îÝ‚36^ØùNKX*€Rž=àÓeÖlÜ+…®)”V­øMe•ëÏ=ßcà"""2C ]9àïç‡bUªÂE™8G*U«bõ%?y¸Ò»Å.ÂùÛ€O{¦ ^Õê÷1󵻉ˆˆÈì0tå@DD$ìJ¦Wöööˆ Hÿé/.""¢|¡+)¯ä­Zpp*•fÙ]»vén[¶yƒæÂ6ü%pï|[Áãpk±a*NDD§T*_½™5†®¨æáÀEû¬\å÷’¾¾WàáÑ;ÍÑ‹ÞÞÞ)gt숛³F£¢wÔkÑÝ@562Ól³àv™sÝ6n—éÙÅÖùCW¸{õFsÌÀ—+Ž`žO+œ\‡åû^`ÉÌN¯¾³£ üZ DÅæùáBDDD)1tå€Â¶~ݲ ÃGŽ„Ûø@8®ˆ +¶¡seGcWˆˆˆò†®r÷ôƾÓl­"""¢Ì1tC—™kgQÁ\·ÛezÌuÛ¸]¦Çœ·²†¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºŒA‹=ËfÁç“yè1w?~ÓÚØ5ʾL¶åî©­ð= gn>mÁÒ;k1&¿Õ2Í)’ò"ßC¿ãýñ3qáö3X9FŸÑ_àÛɃa«¾‹Fnåq>ÊÊÄ QXaí¹ ôª–ÇßNñ™×Ý”Ÿ¯«[fÃóÍÙ)æÅ«âà5êGú®“i=gÙ|O™Äó—Ѷ™øk3ÃÏ …é°qÆ®Le´-÷ŽnÁ/ì|§%,¥Ÿm¥<{À§Ë,¬Ù¸Wú@`´úfEœÆŸý¸ý¥P ©îÅjtDÛÚ.¸~ã4µ… \Mï·¨&$8ú›òó•–æC¤×x iRš'7Mê9ËÎ{ʳÉ“xþ2Ú6S~mföyqOuÆäŸ3Ò/†.ƒS ^ÃÒµÚØуŒ·ÅßÏŪT…‹2iÙ*U«bõ%?é+yºùÜ®h }«Šn:òÁI¾¢ÁœëBr¡ÊH|ùNgœúç,œK¢ÿèi˜1¦¬óòFIÔ!AÖÝ”Ÿ¯Ô¢îÅ”þØtaÄæ¨2Ùî¼÷œeï=åïúÀž¿Œ·Í”_›™}^øXmâÏéC劈ˆHØ9”LñÁaooÈð£Õ);BÎa€÷ xMY‰ž5\sÇ =z¶E§·Çâ·U5pçÔ6ôî9–…ÿŬu]ÝL©¬2®{]3y¾D+ׯ_ÍD­w&Á³°•<'³íÎëÏYr™½§ÂMüù3—×fêÏ‹mÛÍ÷9£ìaè¢\áèà€HéC%ù/¶ˆˆ88•2fµ^ËÝS[ðÆ€qðžö f k+o‡m¹X³¶…n™ê-àã·VbñŽ=yþ <³º·h`úÏ—  »Šÿ­¹‚yg{éæ™òs–\fï)'¿™Ãk3½Ï s~Î({º(WTóð@à¢ýV®ró¹¾¾WàáÑÛ$šÍïžX‡f`ÚªCìUI7?ìé=\}‹†µ+é¶#^­†¥UÞ+eVwS¾ù؆»¥Û¡u9;ÝuÈd¸{õFsÌÀ—+Ž`žO+œ\‡åû^`ÉÌNÆ®Ú+Å_Àþã0î÷ãÒhùe!×þD›î_ã÷ƒ‡Ð«A<¼¸‹×À€Ÿ¿3Ne_Cfuw÷r1Ùç+¹“'O šgkØ%ûÖ2åç,¹ÌÞSLúù3å×ffŸæüœQö0tZ¬/ê¬? Š‹þꈕã•h6b!ý0ÚØµ{=¯Ø–_·¬Âð‘#á6>…+bŠmè\ÙÑØµ~%¿p*à.tªŽqÉæ·µ{ú`ü|Ö¯1|Bb`_ÀC¦¯Å§=k­¾Yåî•qÝRH1Õç+¹€û÷Q¬|ñó2Ûî<'›ï)óþó—Ù¶-z×d_›™~^|û®i?g¤w ]†fí áÑÆ®…~¼b[Ü=½±ï´·+¤}§C£™ža¹÷¨/å‹ÉQXfZwS}¾’›¶þrÚ™¯Øî<%ï©<ÿü½bÛLõµùªÏ “~ÎHﺈˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹ˆˆˆÈºˆˆˆˆ €¡‹(ŸŠ z€Ý;wÃÿÞSØ,Ž–ºÁ³BQ¹,.ð2æo¼Œ‰c—ðÕ&_|:v ¬F®4‘ cè"ʇný³zˆŠíú£m½Jº} =OB¯™k°pTG¨¤Ð5{Ñ:|$….kªxuÎP‰¾:`Þ‰PÁB?Û@DdjºˆòMô}ø ïÙ»±p„¯ÞÜ>Ý„GÃ: `ò;(”°´Pê&UáO°}Óø? Aùº­Ð»sXK+AmÁöÞ¾4ÖmÙ•C ôkÜ],±åÇÏð÷•ÛX4óKŒúð3XÝ=‚O!vhض»ü*€hDdò™ÇÿîÄ¿ñ °~˜WŠ S´NÙ×G¾•l~ÜãK˜ýÃ&|ÿ nÇÀÞ­ñ÷/ÓÑ鯻¸´é TªPq GÔkÖq~Cû‘KñùôOPBýÓßl‹{ËÿÁðæîý;CQ>ó à%\ÊWF¡lìæ»²c9N8uÄý¹Ëý»º4,„2ŸàÆço¢¼4­Ž²Ç˜™“PÍ^OÛÁ˜Ùk-Â4 Ô¨[ÖNhÖ¶ ‚×~‹èѳ Û[¢s×^ÐØ¹êC‰ˆò†.¢|ÆÁÁ ‘¡!Pi‹×ܧwëÖM„\9‚uO&̉EHØCÜ|¬FyiJéZEí´+µ¶±Tqòã$çÙç# =6µË•BEÏæè9p8F½Õ)ÇÛED”×1tå3•Õƒãõo±ÿFºUvJ*ˆÁòÅ«ÐÕg4 dp_GGGThÿÎoøx¨÷í$"Êkºˆò ¼¿h;œ¾þõl‚û/£`mï‚¶½†ã讉(h©HÑ‘>9^X?ß#;6„ÂÆ1|<ÿg؈£3yD¥[ töŒB'Z˜õÍ\úa —,Å3ž5uñ§"¢j„¡‹ˆ´®M›6PlÁ¼ÏC°øÁ°3—æÕ9º{¡›{΂²è=¥03•âòþøÞïElýå(Ò¢þƘQðòð{¨åÜŸýxöÑxó¥Qxmòûpj¬as3àߟ>Ɔbíîßá¨LÀʹSðö¨çàÞü2†¶´.ò\Jìø`F|xsW¬Â¶¾íqï –¾ózwïÿGËüˆ$"íá' iÝso}Œ÷þ‹ÆGÓ„4¿&:võCçNÐE¸öïâ 3i©÷•H$ˆMwǪ/g£–™ðñÄÈîK±dÿ |}óWøÕ1Öz “^è‰QKOãFàm.ïˆû©µð÷úÑÔZݯõÅšO±»A¬ß&„®á…žG™r ?ÞŠ€·Öcþ«ƒÔ…îõðíÆ¯p¨A7|±áVOíSI!"ªŽºˆHë$–µðÁ¦Ã˜ôþIìÞó3þüû6¯ Ƈs“`WÛÓ¯Äì±=aRÊý´~5ÍrwJàäì“z5Ѧ¶yÞ:ÎN΀< iJq<¾Š{{?4±Îß‘hVÇ­êš âÒ¥bjIá'ž¬„Óµã˜?ÿZštX qêß…Û ]D¤= ]DTI$¨çÑÅËtaQ)GÔG°xÖ[˜÷Ê@8׿ˆÉÝK7enaQh –Øû%17‡¹¤pYQŽNN…ÇnIl`k#Aª,µXèz””…p‹¨(Y¡º.ƒ^BÍ6ÍT÷)q,‘ºˆHëä™2ȲÌ`o“ß3‰ ¶ôÃ×?|}5»âÀ?„Ð5L«ÏûèÑ£ÂAI)CŠL [;»báÉÁÉYÕÓÖëåÙX<ÆW«í "* Ci•<öš6ñ‡ÛÈe8¼æm˜I;I·£‘,$#—Zî[§ŽáN¦õrºÄ²bÏâü9:½ìS,tÙµèo{ ~9p¡+w”™2;;6@۾ϡqM[­·‘ˆª/†."Ò*“Ú0gb_¼úÉ;螉ñÃû¡‰{M(3Rqæ¾X¾ ÷Žxkl/aíl­>·ƒâ2^{ãC,~g4ìñÙ̸kR¯ŒPl]‰æLáó?À¤µñ¿Ý!•Åâ‡ÞÃÒWðÓ¿ý„Ð¥ÕæQ5ÇÐEDZ&Åøv¢aÛ¯ñÅÚ1ó-¸Ÿsk¸5öÀ³#gbÇŒÿÁ£¦¹Ø­¤Õg®ßk"^j~ú·UÍÓU·Y[|±}7ú5·ƒ8@¾0 †ÎþÛ‚±äëùh=/RK´ìø,¶ÿz<µÚ6""†."Ò>‰9zŒúŸêRözÖøéJÁàeŠÍ犆#`Æ÷ÿ`F‘²^o} å[E×4ÈŸ«.ÅY"42«p‘Ôo~¨ºU6†.""""`è""""Ò†."2¦1y.’êuÕwCˆˆJÅÐEDFÀ#§¼¯ïF•‰¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆtÀ CWø¡ðÆô`œ½~f¶®:i–¿7– êÄNMš‰SWã`é\S®Ä{£ý!TUJQE\èʾ÷™‚1ßÄ¡áp?ügøw„oÚûáÍ. Œ ‡ÎÁ»±œ?îž E·žCáÕú 7NÐzÝG}ÿ9ˆˆˆÈ@\èÊR:`Ö—1B\& ¶Oôhå€ËWnàfö)œ²ðÞ±þ0êÜ}!¨ÿBlÚ¶¾b´^7Äg¤¾ÿDDDd .tYÕòÀ¸ÑyË©1ÇñÛ%>ü² "ÙˆÚžpæÖJàáé‰ç#é£õ:¥j‰ˆˆˆ¨|º JŽ>‘/Àoöwìã€Ð]©°²©W(Y[[#5E†™öëŠ +V&‘H T*ŸtS‰ˆÈÀ5jÔÞÞÞúné‘Á†®¨!x~äTÌý _é¡ E¶66HBRÁ(™L;wØUB]QÅÊÄ VR9U/â÷CWõf¡+êØô¹s7¿fyå^›9vÅA$*'Õî@%ÂÃ/oò@¡ÎAëuܵHDDDep¡Kžx #GLÅÔŽ «q¡:7¿@tÅ|°ö0–uCôñ-Xsà!V÷…[ý­×U”Á…®ˆ_¶âDô}œíÛS ”w›¸û—¿†u!0~¸L…kSÌXŠ~Ím!­Ö눈ˆˆ*ÊàB—÷°ùP*ç—Zïæ€'KCUuDDDDap¡‹ˆˆˆÈ1téC‘0téC‘0téC‘0téC‘0t‘ÎmŽúù‘Á1ÀÐtye&µk‹i½7#«@yVr<’¥µÐÊ«–jÙ²F3x¹Y!>>7ÿ8ŠS~Ø3Ö¦ÀÝw‚ú/ĦmûáÛ)F£º!>#õó "2@Yr¢ï%#*6g¯ÅUø~’Þ‹+±UDºc€¡K‚¶íÛ ×Šb5ÖÞ0gØW˜øâdŒ h‡{ÃIiìڑ߇ڞpæ?އ§'6ž@¤SŒFuJÕ‰’dˆyð×ï&¨®oÜMÄ-!d©ËŸ‚òGq/ ]e‘¢¦[Cd¹…«Ww=5ê4J•ÉRaeS¯PH²¶¶FjŠ )ÖU¦‡ÉiÈ–ÿY¥¹)l,*½-â ÷Ø¢âq[ T±Iªk1L‰¡Jì½JB•ΨBWÔÁUøß†[¸yu-Ř”¹­1ýãí˜èbƒT!@ì’Éd°±s‡fuE………•Ø®ÒʉˆÊò·ñ(]^îzþŽ˜Þ§øgÒãÈÌV ^–ؤLù¬Uƒ:7Ç®£—¿sªZKD•‰¡‹ˆH Äù´nÄ&ª•Øs%^‹½X‘1‘•]ö\[‰G6­ë„ j`çß‘å®okeþ$Í&"bè""z bïÔ­¸d!PÝW…*q·à7î#"ú!Ò+8¾J$îêwý=ÕÐ-ׄW}x AËÓ½FÞ´ <ý ‘qaè""ƒ%žV¦ã›?àæ½¤2×ótwÁ¥µ¯iðø2üuOªÄž+ñrþÆ=ÈҳʿsqUÝvª@å#¬§¹ªz±Ä‹­eÙ½Tâø,qÚ‡²ˆst5©ëXáö‘þ0t‘ÁJÂOyK$öB•åArš¨î«{­®ç÷`%”q¤tIj9٨”OWx5pQõb=Õ¨¦Æ§é‰ÞÿÍxx¸»¨zµˆˆô¡‹ˆ J’,_îþËCþQuXQâQ„DDúd˜¡K™‰}«"hÚ Z|_MîžW••p Ó'aÓ/g 4·C@ÐûXµèUXI€¨;4i&N]ƒ¥s}LY¸ïöWÍ££iéÆƒ¤T|z_ìúÉ©únÑc3ÀХĊ‰}’Ü[¸©“cñÄ¡wz×bÁ$!#Ÿ…]ƒžÇHoÆŽCçàÝØ?ÎwÏ„¢[Ï¡ðj}ƒ'hT7ÄÇQ¢jåîÃ,Ýv«÷AjFáßÅ)âdèâíVæc ©Ì&Uˆ†. Ë+Ë0©][Lë½ç…VÜ;†Õ{açÍ©p°µŸBØÉ ªº¨Ÿ?Æ) ?ìëS àî;AýbÓ¶ýðí£QÝŸ‘úùU×ï&bÉǰþ—ÿ™-/T×´žÞÖ/õz ælá3Qe>gl'¢ªÀC—mÛ·®‹Ÿ@6ö¿Ó¸_¯®l˜…Ik~BŠÔƒ‚fbÁäÁˆŒˆ@mO8HóÇÃÓÏG Ò)F£:%À]ŒDZváÆ},Ýv [_„\¡,TçÓ°fìŒݼòŽ6üué(}4“ˆè±`è*]RB"²nü†£Ycqèä"Ä_üƒú€ucð–¥Âʦ^¡dmmÔR4¬+*,,¬Äv•VNDù®Ä¥bûÉû8~=Ê"uMkZadûšhßÈ’Ôøyß ½´‘ˆèIUè²°´‘f¼5ö–ÙûÀäQ±æçƒèàcƒT!@ì’Éd°±s‡fuE+WIåD¤ö÷…h,Øð7~-aá3O¹cî‹]гM#ÎO?ÀɨBW=OØf$"1]‰¶êOh‰TªšQÚËÛ±+"Q8©öJ(~ÞÞBƒFuü ÒŒR©ÄÁo xãß8zñv¡:1\thŠ™#:¡s9䉈 ‰Q….‹¦=0²­ ‚¿Ç¦Åã~ë8¾Ýz>ûn~ŽèŠø`ía, ê†èã[°æÀC¬ î ·ú ÕÑã‘+ù+K¶Ù«q…êL¤võÀ»BØòmV[O-$"ª<†º2ÃÑÚ¹-"²ì¬Là÷>ønº]^ý‡¾˜„6…bbÐëpsK' ¼ ï>¥úõ¼.dÆO˜—é±°qmŠkCѯ¹-$°Õ¨Žˆ*&+[ŽÍ‡/bÙÖã¸xëA¡:3)^xÖ³Fu‚‡[Ñi`ˆˆŒ‡á….soœMI/µÚ¾þÓØtðT‰un¾8p²äñUšÖQéÒ2³±þày,ÂVT‘N[š›b\Ÿ–˜9¢#ê×tÐS ‰ˆtÇðBUy)i™øfï|¼ýb ékom‰ÏµÁÿ·CgöQõÁÐEDZ“ð(Ÿ‡žÄÊŸþÅÃG…Ï‹èbo…)Ï·ÅÂ¥†½µžZHD¤? ]DôÄÄSõ|ú¾ÞsÒ2 ÕÕv¶ÁÔ!í1ñ9_ØY™ë©…DDúÇÐED»—„OvœÀšŸÏ©ÆoT¿¦=ÞÞãû¶Rß""ªîøIHDírL<–n=†~½€lyáSry¸»à½Q0ª»·êÈD""Rcè"¢ ;w-‹6Åο#¡P>YOÛfµñìê ©”SÅÐEDåOÕ#Nhº÷ĵbu]¼ÝT³ÇèÐDuö""*C•HìÈúåô ,Ûv‡J8/b/ßF˜9²žmÝ@÷#"2@ ]DF.þQî'¥–¹ŽDø¯¹›³ê¶¸ÛpÏñ+Xúãq»Tü¼ˆ;5ÃÜ»ªv'QÅ1t¹ÕûÎbæÚßË]¯e#WÌÙ‹6AøÍ§êÏ‹8ª{ ÕnD•ÔR""ãÆÐEDª±XçoÜǨÅ?*·03ÁØÞOaúÐhZÏYO­#"2†º”™Ø·z!‚¦-Á ÅñÕäîÅVÉNŒD÷V¾ô_‚?¿ž¢*‹:±A“fâÔÕ8X:×Ç”…+ñÞhHž ŽÈ(‹‰hci† ~­ñv`{Õ|[DDôä 0t)±bb„$·CÇ.¥¬“Ó_Åm“šp˽Wê Œ ‡ÎÁ»±œ?îž E·žCáÕú 7NШnˆ£Ž¶™H7œl-1åù§1eP[Ôpà©zˆˆ´ÉCÐå•e˜Ô®-¦õÞŒ¬ê#ö}ŽUaöKnX§.»ùGNYøaÏX˜JwßAê¿›¶í‡o§ê†øŒÔévU¶¨ “`oc¡ïf% ]´mßN¸V”X›p AS¿Á§¡Ç‘µsr^ydDj{xÂ!o‚l <<=±ñ|"b4ªSª–ˆ Ÿ©‰T5³<Qå1ÀÐUe6–¿„¦ã?Å€ÎØµ3¿J&K…•M½B!ÉÚÚ©)2¤hXGDDDTQFº.îýk¯zâèêÅz lml*¨‚½S2™ 6vî°Ó°®¨°°°ÛUZ9QeçÜÚ÷Wt¹ëå¤ç{•ˆ¨òQèRb÷ß"ú|Z¸íU•dÈ’ “‡Áíüiž× ±+"Q8IÕ뇇_€·w ¼¼4ª+ìеJü+©œ¨²¥¤eâÅ%»ñçå¤r×UŸ¾GÉ÷*Q%â2¢Ð%ÁâÝW±¸@I袰üv'Õ”ÊôhtÅ|°ö0–uCôñ-Xsà!V÷…[ýꈪª[÷’0wþ»q¿Bë‹ã¹Ü]95Qe2¼Ð•ŽÖÎm‘-|Qde¿÷ÁwÓ¥èòê§8ôŤRï&±tǺ ?a\¦ÇÂÆµ)f¬ E¿æ¶B\³Õ¨Ž¨*ON¼÷óOý3åù¶øôõžªóDD¤†ºÌ½q6%½B«ž³ƒ ,»ùàÀÉ’wŸhZGT•|wà&~¾™Ùê£{Í„µrJ¼Ö¿µ~FDDºˆ¨¹\wׯ';þÉ+«á`…s‡À¿e}=¶Œˆˆr1t¸$Y^\üöþs-¯Ì»A ü<Mê8é±eDDTC‘»v's·#"úa^YÿöM°å½çao͉N‰ˆª†."õÛÙ›¾(“ÓòÊÄT/{µ;L¤0ODTÕ0t oöžÁ”•‘%W˜773Á·oõÃ˽žÒsˈˆ¨4 ]DD YÓ¾9„/vÊ+«éhÐyèìí¦Ç–Qyºˆ Dü£4Œü`~9•WÖªqM욈†µõÖ.""ª†."p9&çmGdt|^Ù ÎÍñûÏÁÎÊ\-#Ò’˜kÀ¶@ç~@GžñƒŒCQ÷ëé¶(‰)yeïꌅcý •=(‘ºu˜>xx8° ˜û=Ðõ9}·J»þü q7õÝ Ò3†.¢*L»5uÕ¯+”ªeKsS¬y»?^|Ö[Ï-#Ò1pM âãÔËÙYÀÂqƼv®¾|]ÅÛ5¬çƒôÝ"Ò†.¢*(+[ŽÉ+âÛ}góÊê8ÛªÌw𪫿†‘~? ÄEÝfF°[¹hàÊ•¼¾‚×@ý´M[BÕK¤ê—^1 PÈÁôÚ,Ò†.¢*FœwkhðNü~þV^YÛfµ±S\õkÚë±e¤Wçþf™éÀ¡À¼ªNðÊHdÉ@z*’–…°l¦.Ͻ[nf–s3u»C¾¾šY|ÛV¾£¾fð2HŠ„ËX*ü(~eÖdÔzÌe˜¡K™‰}«"hÚ Z|_MîžW~è¼1=g¯ß‡™­+†Nš‡åï¥ð#êÄNMš‰SWã`é\S®Ä{£ýU¿>4­#Ò¦‹7àùy;põNB^Ùp/|?m¬-ÍôØ2Ò«³Bàš˜PŽý ÌÉüÔ_öOB CEƒRÁ`”ôP(OR×Éå\'¸¬î¹©LJ…zÛK hUÂÄ Š³2K_G¼”Bðz]gÍz\'"îÀïíÈÌ.ýõ>´>zíY¶Jÿ”‰W°XÈfT‹Ð¥ÄЉ}’Ü[¸ªÉ¾÷™‚1ßÄ¡áp?ügøw„oÚûáÍ. Œ ‡ÎÁ»±œ?îž E·žCáÕú 7NШnˆ£~þd”öþs£>ü RÕÔb¨Ÿ;º‹ðcÿ˜¯ÎÎþ%®¡ÅÇñýÀû/“>ÂQzá`”šŒRSÔÁ©hSî²x­Tèa£*@ìݪªm+<[¸”³N:Àš` ÇÀ¾jž#U [e®mâã'ª]èzº€.¯,ävm1­÷fd(ÏR:`Ö—1B\&ÂwTmŸ>èÑÊ—¯ÜÀÍìS8eá‡=cýa*Ô¹ûBPÿ…Ø´m?|;ÅhT7Äg¤Þþd<”ÂÝå!ÿ`Æêß PªÌ[Y˜býŒ óóÒsë Ðíëê/4 +}·¤âÄÞ¦„{@r¼: åÞ~ ìüªô“~Q_ôM H6ö€#`i­¾m먾—ÅPaï X Ë6vêrõí°ïÐo ?^û^À•sÀâ@COáu¶zwcV†:Ј·3 ÞNÏ¿-®#®/ÏYG,ër×/9Vü»ªî—™;«Èm±.»ŒurËÅÇÏ]¯<âûÓE¸4m%üc·Öö«a0²¯î†c˹xaOØû/bc¢Q¿ëKظê}¸˜+öå;xóÃ0±sA»ç&ÂæßŨ1n7ñÅï?ÄÔEk‘(¾ J ¼ðî'øðõþBQêý=s5ZÍż9mñþû;°5âj]ø“…ûÆ¥K WHÑgÜ{ø|Î˰”(K}Ž'a€¡K‚¶íÛ ×ÅYÕòÀ¸ÑyË©1ÇñÛ%>ü² "ÙˆÚžpæ?އ§'6ž@¤SŒFuJ€»é‰ddÉ1qÅ~|à|^Y½vøiÁPÕ8.zLÇï¿ |á _è‹C6º}~14‹½Gb`/⮹Üë‚·“Ååøüõ²³ÊìÊ"†S›Ü0äÛZ¸mk¯^¶Î Jâß5÷vÁË“Ü7–ªw‘Šst‰=ºû§Õ«Y+õ:Oº U—Ä]­Y9áoï:àÛ÷ ×ר+.áß¶SMíìÖ€ç+ß 2&¾üËqèL”êZÒ{q…Ö_ûv¼Ò·UÞ²ÄÄÒô ˆÄ‡øõ·O L½ƒçÚxbñ¦¾XøÌŒ›±+œÃˆ65ñ×Ú™è÷Å=¼õª)”)‘Xöñ6L[£;ÔÁË!há;}Æ cêþRï'ŽÃ“¦]Å‘¸p9æ*lSâ©‘³ñÎؽ RãΡGÛ.ø¸ÅÓ˜ÝǤÔçð{‚¿™†®ŠIŽ>‘/Àoöwìã€Ð]©°²©W($Y[[#5E†™fuE………•Ø–ÒÊ©zKLÍÆâ}7qñNj^YóZV˜Pw#O!,R3@5oœÅÓ»?…TüÒ‚MÖÔþ81d&k7Ñø1¥Ù™°%Á<ýÌÒeÂu ,R“„Û)ªe Y"Ì2dêÛB¹EZ ¤rÝ(ñ‡ŸÂÄ iö®È63G–¥ ²,lma ¹ðežii«ºe..›«êÄuÄ:±L¼­0­à`|q/S’x?ûÄË]ímHÝöÂZ?Ô<œ;††¼‹äÈh@¼2›&hä?-þبúI³u‚•¸œkæa@a²²H1jd_ÕßGb]½žÆú¿þÆ’8¤{öÆàÖêŸÏŒ›Žï¢º-±õÄÞS'pñÜiøù,äò8›=ÀÍÛ2Ø„ÿQúý„'‘(óúdÔv1Ç©½aˆkÐc»©?#¬kµÂ¨€6øqÿAÌ œZês †æ›k”¡+êDž9s¿ÇÂWz¨^L[¤ ª`ï”L&¼@vî°Ó°®¨€€€bebà*©œª·s×ïaʼˆŠË\/<ÛBø%8@5=&±‡kÅòBºÍ2RÑu·ða»t'àõ´zžØ«$îºwáåÞN{ï™Òe”8ðÚ¡†z÷œ¸Nìo; eV¶ê^ ä=â—µ¯?$·¯Áä“0ØÖ0‚iDÄÏÉÓ¿«züÑnuq»B¼¯f !×Vε„Àµž+—Ä .Ny»’`oo¤Ø$$&$ÿœUÃzT„pæ^ÏFu3;þ"žóï…{µ»¢wX˜¤@&Wª~…”u¿ÜåZ5Ô%=|ðv5\aQ WÅY¸o|d|™Ïñ$ŒîÓ=êØô¹s7¿fyå^ÞÞˆ]q‰ áóLõú*~ÞÞBƒFuܵHšØuô2Æ,݃”´œóÂ)ø%?Ì~¡³p›ïªÇ–»KQ_S”8˜|J/!Ô˜è?@9¸¨o‹×âE¬spV¯kYθžv=€Ï«ƒWNà‚¸ .Cà‰ïý¶ÝË_ÏNR…ç»Gÿ€ý¼¯õ>ÕGÄw›ªâ¯ Ѫ£K#¤w1*ÎÒ¼1Êx<Œ¾`­MT‹ñññprr‚½ )Âq ‘ê«W‘ˆÛwSQO¸ùÏÎÕ8*õÃ[`#o!;­ËV¨îo'„¶Òî§&AîÇl WW$ß¿Š !HYå”=xð5„ VÖs< £ ]òÄK9b*¦þpT\ Õ¹ù¢+àƒµ‡±$¨¢oÁš±*¸/Üê'hTGô8Äá>m?ŽYkÏ0oki†3âùÎÍõÜ:%cJ\¹Ä#à²5< N 8ª`$†$õmñZn—38\ Tb˜Ê½- ×vxnÚRxóü¤>UŽK-àÎuã \ÕAÿ—pEá ª2·ZU¡HÂúu;Ñ}ö0á;ü vìÿÝ?ü>-¯“§bßŇØÂ ¯ÿ'JÐ^¸‹¹¹9YiH—+a­HÅê%_ ^H3))2øtèXêýŠjÙk êMîᅥIÝ› åö)lÜs#×®ƒùƒ5¥>\5ß\à ]™áhíܪU2ßûà»éRtyõS¬ð»‡Ñ÷q¶o L-p—nW`ÿò×°.dÆO˜—é±°qmŠkCѯ¹­{m5ª#ª¨´Œl¼º|6ýžW&Ntº'xZ6®©Ç–ñ¨³ Ç€“¿ ?w…Ë͈Šß·`TÁ $(G×üÐT°wª2”¦TÁkðÇOêI5]x U®Î^õTŸS·î%—X/örðÂ]Á¦ámó:´ ÆÝ¸‡ð}~.ÞÞv&Oáë90±»'ÞquÃ3Ãÿ‡OÙ¨ö0ù™„›£eÓf¨Y³^ÿ9fŽŠÄü—º£ÑÁcÂýú–x¿¢ÌjwBÈK1qJ_|’!ä?¥9†¼³ÿë× &²ÒŸ£þ7 4ß\ï©/æÞ8›RúyJåüRëÜ|pàdÉã«4­#*Ï݇)² Ç#îä•uñvCÈûCPËɦŒ{’Ê(àÄõåÜ‘ŠOi%ümë7WIöÁV ¦[¥6S'šµV_ˆtÀÄDŠ›ߨäg‘ ÿÄ0sêG…J•™iðê9—Þ] {q€Vö}ôZ=jÕ„Ô¶Öì?]øaü©êlQf>ÂÙÒîW¯dÞ(t·Vý^ÃQáRŒmƒRŸC”œ9B£­5¼ÐEd@Î\SÍ0}?ÿ—â¸Þ-ñõÿúÂÂÌD-«ÂR©Oy#öføˆ½YúºâéV|:O? üwTXÿ º\ \^í€ÄÀǻջ‰Èp(!8¨?}^À¨g½pá—M¸Xs06v-§‡WÓûéCQ% ù+/-ÛƒÔ õx#©KÆwÇ´¡ªÌ«*Aßv3RÝ“uê7õIËš`²NC!dõ:ô~¦vUÏ%&ü" ýY=?—x"Q•%ql†YsK>•ŽÄ¢.¶9ƒÐ¡¸|;^ÞÄ™¡Ï£¦yÙžšÞOWºˆ´L)„ˆ…›Ž`þ†¿3^öÖØ ŸàC'ΟuõE+8§‘xdجoµÓf""-cè"Ò’;S0ðýíø÷Jl^Y·Võ±}Î`Ôp¨†çW{pWX‡Õ±7ëQBé늻Å9šrƒ–k½Ò×%"2P ]DZðOä ž¢ ^¹^Ð_¼Ñæ¦ÕdÀ¼8+â_õd¥§„ uõòö¯%ž Yœ¡}OõEônÂ#"2nü”#zB›‡cü'ûž™?`þã×zà­!íôÜ2¸¥ü.öf‰×i)¥¯+ޝ{±Ä-1h9VãÝ­DT-1tiH¡PâýþÄ›æ•9ÙZâÇكлm#=¶¬‰““ž?¢žšAÞ†ië`t‡:xx1-|Ç£ÏÀt¯kœG}`èRbÅÄ>In‡Ž- Q¦ÞÀ˜Àqè¼ûÇùãî™Ptë9^­¯`pã­× ñqÔÏŸ€´®ûŒÍªqZålg‰ïA÷V@n].9p•¥Q  c_õøíÕç8$"ã0öi úJW§})¡‹Aœwo‚ßã=ïô•@¿1y‹[Oì=uÏÆŸÏB.O³Ùܼ-êÚ?ÞcˆJ]Êì4ÜKÌD­êµ·/ÁO‡N£^«nxÎï)º»¼² “ڵŴޛ‘U üæ!8eá‡=cý!ž\ÜÝw‚ú/ĦmûáÛ)FëuC|FjåoDú÷8Ë«¾‹j†ùfõ ä3â)qÊ \âQ…®uqsÔ§Ûq®©›¶QWyût²ã/â9ÿ^¸W»+zwð€…I dr¥:ç©J ]Ê´;x¹gW¤õ\‚í †#ñÒ^tìøš>Ó w—ãè‡?aéËóQ%hÛ^œÿHQ¬&2"µ=<á Í_×ÃÓÏG Ò)Fëu¥ä2rG?{ ŽU}À|AžmÕÝOþZ¼NôÞ²«ð«÷2ðÁV 1Ç‘nü³s5ŽJýpçÀØß±JÙyl]¶BßͪT•º.î]áØÜaªåß~Ž–>CØGãð(bZ½°ó_ê +-%™,V6õ !kkk¤¦ÈR uT½tjQÇ.Þ6¬À%–_ý~é«Càò`éN.¢êbÝ©Ç[ÿø~`þõ牃3àÖLHB|ø`;„/H›annEVÒåJX+R±zɈ6EõýÊÝ‹íÊå+è8öâ~9e~=ô­Ú¬ 0öÍ»¢é£Eˆ‘Í´Ô [¤ !©`”L&ƒ;ì*¡®¨°°°ÛUZ9Ólõlë†òzZ%ÝCÛ½_À1îz^™ÜÔ&Ù™ÂûY‚{ÚÀá¿ã81ø]<º%üJŠÒ[[‰¨ ÇwÎßÌ‚—cM@jòÄKä;dFlŒ–M›¡fÍ:xuþç˜9*ó_êŽFOb@ ;-m@ÕQ©¡ËÔÄ ¹úH/ùýS8r«æµÉüžl…R-îŸóòöF슃HTNªÝJ„‡_€·w Pç õº¢M(Ö&ñ º¤rªbVüWî*uëÖ.'Æëy8øæ}õœ[¹Ú÷„É»«€AòçO¨•xølü{ë¯DÕˆ¡ü`+‘¼‚7©{Ƈ¾ñÄK$µm€5ûO.ð'¦>ñ#W]•ºZúúbúÒ0mH üºü3Øt€GξÄ;ÇÃpÅÙ õ´8‡›_ ºb>X{K‚º!úø¬9ð«‚û­~‚Ö눪œŒ4àËw½ëóËÄ#_™ S=`þ¥êÝŠ5ÝÔ“™U„x¶ ñB«ÔÐU¿Ç+xaý4ru†]ý¶øqß·ª£ÿÛŽÏÍÀk«~…åãöte†£µs[Dd‹S e¿÷ÁwÓ¥èòê§8ôÅ$¬ Ù€ñ&Àez,l\›bÆÚPôkn lµ^GT¥ˆ‡€-<ŸNíúÀœïÕç@Ì%Žåj×CçÍ#"ªî*wÊ©Þßø'Þù6¦–60ÍÙ—hïöÖî;Žž4ø•mî³)é¥V»ùàÀÉ’wÿTF6¥Rªð—¾›ñälVLÒSóËžy˜þ%ÏHDTETú<]ŠŒ$ÿý7ÜMž~¦šÕ±‡Ô©zu¨ìg&*›RH\SWÂç¡'+´þÖß/¡ÏÓ*¹U)Uø‡õùÛÀ¯[óËÌ,€ ÁÀ  <"QR¹ótÉnbØ3q4É\¤¸ðÚ|þÓçߤ2Ÿ–¨\r…>ÛµûÏå•õômˆÐy°µ2×c˃x´…ãÔslårkª>—bÓ§ôÖ,""*Y¥†®Ó;¿Æåúcp}çXIK»— ïüOñÒá/aœgU"C™%Ç襻±ýψ¼²Á]šcË{ÏÃÂÌÎŒ%î ûøj¦°1ÎÙk$ð¿O+Ž7$"ªŠ*õæÆ(ø÷~]¸Dž½ûÁvêL<¾3¹×ƒô -3Cƒwbß?×òÊ^èîu3ÀÌÔ~ ¤$OþÚ_fa¥>"qÀËúk•«rw/*0-p¢\‰pÛD(3âÓ*Qö(5çíÀïçn啽Ð_Nî©6'Œ«,ÿ DZùíW˜zÎw@C/½5‹ˆˆ*¦Ò÷¥¤$=À;wÔ Ù÷-ÏÀ]a9M¢~z×:5afßwdØ&§¡ÿì­ø'òn^Ù»#:bñ+Ý ©êƒÍÅ݉!_«Oç“]àïcIKÔ=]DDF‘pKWÄ+³&£–ŒìІJß̵ï .…˼Ýêå<{ „§…£E5ùc“~Ä&ÈÐëÝ-¸u?¯lÁKÏàýÑ]õت Jz,›?_fm ¼õÐc˜ÞšEDÕ€<x$~ö”òÃÔLø.·Ñ|*eâ,^¸f0tiÅóï|‡G3~€¥yéOcRMþФ·î%£ç»›qåv‚jYìÔú|R/LyþérîYœûøðUàÁü²æ­ÙkÕG)U&ñ ·g é(«ôujÍ\'é®M®R#ÏOË^Áa—‰øjr÷Ê|¢ED?Dï™[}_}þA©kÞî±½[ê¹eåP(€—ë>~iÊ󎼼¬ž‡‹ˆ¨²I­Ç!@ÂÖâuM€ìxÀá¹'~š«ßÅ‹+wàÎý$´}n6¯^€æU|؇†ØÏDFéüõ{è=ëGÄ%ÈTËæ¦Rlœù<†ùyê¹e刿 œþ#¿ÌÖx÷k s½5‹ˆª)×ÉBè nd.—ÚöO ®îOöøÙ×ñÏ]Oœºt Ê„ônÛË6À²±Æ9ƒz¥‡®¸¨‹8~¼”¾+´lß ÖÆhIOŽ_ºþ³·!!çtQÖ¦y?}Û5ÖsËÊqò°d˜?ö -Ú³×µè¯]Dd\.÷2¯W|}qÊ¢ßÓigÕ—’zÁJSo)à4¼p™Äã'Œ…8µ”‹'†÷{ÿú`èÒ̵óqâÇRv‡˜6ÇoWCsö·‘–üz& ƒçí@Jºz ‚•9 ƒßSõõܲ2È…_„£ n u™8ølÄ[À¸9¿þ!"=ªÌŽ© j¹æÏ‘èè䄤ËI•ø„úUéŸæ/-ܦÃ1]Jü½õc¼½ð[$dJ —› çèiX>/6BŠŽ:±A“fâÔÕ8X:×Ç”…+ñÞhÕûIÓ:ª:ÂŽ_Á°E»ž©îw±³ÂÏG;ºznYîß.Ë/spÞ[<ý¬þÚED¤ Ê$Ä' ?6­ÕÁ+11NBð2VFõ:ëÖ! û–=Qmj#ýÁEtoå‹å­º`v_KŒ ‡ÎÁ»±œ?îž E·žCáÕú 7NШnˆ£¾7™rl9|//Ûƒ,¹º§¨Ž³-. Ÿ†®znYŽî–M%ä—µ~˜ù àZOí""ãÖüÐãß'ë.pÙ0o($_õ®BmPÄã‡u»Ðí½@(¯açþÑ}ÑgÚyì*¨RCW­Æ-á堻Ě•di-´òª¥Z¶¬Ñ ^nVˆOÀÍ?Žâ”…öŒõ‡©p÷„ þ ±iÛ~øvŠÑ¨nˆÏHm•nõ¾³xýóýP(Õç:pwµÇá^@“ºUô×RV&°v°}e~™Tø•7æ`ô õm"¢ªÄ¬à4 ˆß4X«•‡TÊåPXúÀ×î:´ž»qáûü¼3¼µV¿*ªÔÐå7fü*ó Š°ö€9þÂÄ'cL@;Ü»øNJ{`çЈüþ;Ôöð„ƒ4wm <<=±ñ|"b4ª+il!éÖò0í›Cy§–òtwÆÁ%£TÁ«Jº,DžÎ/s©­>•OË.zkQ¹j½-|^ Ÿ_æÚ#kÚt RRªnÿoÊ2­Ž§~Y³Ì´¼²{ [álŸ×‘y+¸Å÷ Ua&Žê įBWÔÁUøß†[¸yu-Ř”¹­1ýãí˜èbƒT!@ì’Éd°±s‡fuE+WIå¤9¥¸¦û[¡ÀÕÉ«ö.';K=¶¬™éÀ—3½ßç—‰'‚75G¼‰Þié÷%"£Áàd\¡ëêUXº5E-ËÜxdŠFÜu^~Þˆ]qâANªï8%ÂÃ/ÀÛ;^ÞÕq×¢îÉ Løl?ÖîÏïËêéÛ¡óake®Ç–•"ú <¸~!¿¬v}õîD¯vzkéžQ….Ÿ‘5s.öœ»‹A­ê #á:B~>NgÀͯºb>X{K‚º!úø¬9ð«‚û­~‚Fu¤[™ÙrŒY²Ûþ¼”W6°c3l3¨Ìó{êÍÍÀŠi@zj~Y—`ÆJÀ®Šò'"¢JS¿©4Wãé±såMÌÕogÙr º _€Å¯w‡ÄT‚u!0~¸L…kSÌXŠ~Ím!­Fu¤;i™Ù±({Ž_Í+Õ½ÖÏ€™i;Ú/-øìmà×35‹çKœ š žø”ˆˆª£ ]â@úž/ÏÁ)áR7ß8Yòø*Më¨ò=JÍÄóóvàð¹›ye´ÁWSú@*­bFÜ(Nvz3"¿Ì­ 0wÐ´ŠŸh›ˆˆ*•‘….26“ÓÐöVüy7¯lú°XÔ’ªÔc$Nö=ðÕ,õÀù\=†S?¬Ø3JDTÝ1tQ•— CïY?âüõü£¼ô ÞÝU­*AJðÉàÏŸòË,¬€7–^Ö_»ˆˆ¨Jaè¢*éÖ½dô|w ®ÜŽW-‹ZŸOì…)ƒžÖsËŠ'9]ø p÷F~Y£ê£zé¯]DDTå0tQ•#­ïlAôýdÕ²‰T‚oÞê‡ñ}[é¹eˆ»C¾V¿dgå—÷ ˜¼LÝÓEDdD®?83 ¸;yè»)‹¡‹ª”ó7î¡ÏÌ› žñßÌTŠMïÄ0ÿ*Ôk”ôX68~ ¿L³%ŽÝÇp¥ðß7¿ƒè„|:ä0j;4Òw“ CUÇ/ÝFÿÙÛ¢ˆneaŠ÷‡ _»&znYŽ©w'>¸“_Ö¬50g-àÖToÍ""ªL§nÄÍøpH ÅÎs+0Éo¹V÷ü5x}ÚbÜLHƒ¥KS,\¹/øoOCU âtçnGJºzW•9vE·V ôÓ }?7Âo5ê ðãgÀºE€\ž¿Þ×ׂÕóp€©!þ¸›t]£û*¡ÀŸWw¨.kB×ѽùˆ¼å¬Øã2r6æìûc;ÕÇ™m Ðuø+èõ7YV¡£Óµˆ¡‹ô.ìÄU [ŠôÌlÕ²‹ö}8í=êê§AûÖŸ¼©¾ýϯÀìµÀêyÀéßó×±u¦¯žyN-$"2x~Ý»õ{`TGõë6CßCt¯·á`aœKÄÐEzµåðE¼¼l²ä Õrg[\2> ]õÓ ‚Ks˜è_xí… ¶¨­§^8""R í¡ÀÖÉ f¹-5‡³S<‡®1t‘Þ|·ÿ^ûìgÈJÕ²»«=-…fõœõÓ ¢«(qÞŠÿÆÍþå˜é®]DDZ´<ð ¯{âÆ^,?üz±rql—_³@L|æSÛQ£F $ß»Œtá+ÀZ ^Š4D†_CæÞ°7ÒÞ.†.Ò‹å;ÿÁ´U‡ ÌYöpwÆ/KF©‚—^ˆc¸Ê \"×zês'2pQ5 T*±ýlɿű]G®íÂàVÿCm{Ízý}z î¤þXýKÞìí »?C—×wãÌ£ÐÓ7A¥cè" Þø7æýðWÞrËÆ5…À55môר˜«e×;Õ„ª‹ýüN ADÕBRÚ}ÄËî”ZŸ­ÈÂñ{0¨Õd߬vgìØ²ÞìƒÒaS³9¾Þ¶M¬Œ³—Kdt¡++á¦O¦_Î@in‡€ ÷±jÑ«_è;4i&N]ƒ¥s}LY¸ïöWí©Ö´Ž*NüÕ4cõa|²ãD^YG¯ºØ·hœì,õÓ¨äxà§5ÀÁ-¥¯#®Úõk[ +O|NDÕƒ£uM¬yñ¤i‰õ ¥¢ÔºŠj3`"þ.Õ…‘….9OŠp§q-öLÂ1ò¹QØ5èyŒô–aLà8tÞýãüq÷L(ºõ ¯ÖW0¸q‚FuC|õ½ÁC®PàõÏ÷cÍÏçòÊz´iˆ]óak¥‡“c_{¾ÒRJ_//pÙ ·p¦y"ªVÊ UO¸ª#£ ]Š{ǰzï#ì¼9›¡öS;yAUõóÇ8eá‡=cýa*Ü}!¨ÿBlÚ¶¾b4ªâ3RÏ[l²²åxiY~üýb^Ùs›aÛœA°4×ñ[0ú*°ísàà@vfá:çZÀ ×€?v×þcà"""­2ªÐûßiܯ×W6̤5?!EêˆAA3±`ò`DFD ¶‡'ò‚¹žžØx>‘N1ÕUÎA´ÆEœ{kø¢Pì9ž?fjT÷X?#f¦&ºkHÄiàÇåÀßa€RQ¸Î­ 0ü@ïQ€™90˜>P}ºkûœÀ¥§ÝŸDÆJ‘!ü[L/=‡Êo ‘ŽUèJJHDÖßp4k,\„ø‹¿`Pÿ°®s Þ²TXÙÔ+’¬­­‘š"CІuE………•Ø®ÒÊ]Z¦‹Ânâ|Lþߪ¯3FúHq`ÿÏ:h5nþ‡&§Âàz+¼Xmb­Æ¸Úî9Ä6}ZXUHÕæÕ™õž —˜ÜkÔ Š_~ÕA[‰ª—šVоÖ7å®wWÖ ÿÞÒA‹ˆ*ŸQ…. KK!ù`Æ[C`/l™½ïLÕk~>ˆ>6HTÁÞ)™L;wØÙhVWT@@ñAÖbà*©Ü؉çOì÷ÞÖBëíÀöøøµg!‘Trÿ <øó'õi{®ž/^ßöY`Ä›plÛOWnKª†¤}@ôå¯ç8pÓ|ΪéÂû¹Œ1‡"3WÀÜ'êUÿ#·Ù‰¥ï2°j:®ÐÁÀ>CÅÞôôâ?ôþúë/á×èùæõÙ“WÍUèªçá ÛŒD$¦+ÑÀVý¯X"•ª¾ä½¼½»â … Nª]…J„‡_€·w Pç Qw-–,.A†>³~Ĺë÷òÊæéŠycž©Ü'ÎHn¶}ܹQ¸Njø=Œ| hÖªø}SŽYwÕ –Ƽ‘êÛhµÉ:aÓQØ.+á­›Vú:—Lkè¬ITA¾·—¿žÛgBh~¾Ò›£URk Æk@ì²’ë-½Ì›Â:ßë¶]Ú ~ŽDs|¡âgÄ3›]û(¿@ü4ýEølÑÓ)ÏHçŒ*tY4í‘meXü=6-‡ô[ÇñíÖ#ðÙGpósDW,ÀkcIP7Dß‚5bUp_¸ÕOÐ¨ŽŠ»u/½Þ݂˷Õ6b„ùôõžxkH»Ê{RY’zÚ‡«€„{…ëÌ->/橼qé‘,|ðů+ÿ¹ê.œ ì SgÀå%á ¼”]9–^—[Ð`µN›E ©²B—E3 û¾¬;è®MšP¤ ¡?£øµ¬¤–%íRÈÛΩ(a áÃIJøµD‡ãB‹DÝsüpm髈?tÄ¿ƒYmµŠôϨB$6øhS(&½7×é0±tÂÐÉ«ðNàSªëB6`ü„ p™ צ˜±6ýšÛ ÿøAŒ-•)¶])S_—zE® õ8•PŽŠýѦ2%îe(ÑÞI¢áQÀEÃO »à4Ú]gQ¸‡§Ø:åjáïyòx|zå:»$ãÍŽoµÞ.û>ªÝE{õJgªëŠ®Wõ5ÙUZP^/— U ­cè"­ÓJàªà´hÛ½â)† 18©BVÎ%+¶øjÂgêöÛ ÜJS¢†¹Ï×}Œ/7UÐÉ 8Ò¢·-K®Ë E9õ¹ãFŠ=†EþúE×Ë[Î-+%€f'@ÙïÊкV-ÔvíÔž¥®½Ë“ó/Š·åI%—å- ÁäI{úÄÝDÙâ%Nã‡ÈV(1ï’–ÂæÒÒ&YÑê/ç+=ðÄ_–•ND&6êžÕ®Ü±<õ¶F^ÁÙ$%Þnn‰öîþÂw¯R˜—Œ4èMÔ¥Ô !±ŽûñøL 1õ‡[ywßãkõ]*ºË¶XH+â„€÷‰ª.Ka 3örUk ]TµˆÓ>Ø,¤ ¦}(Jœÿ'ítž¬seO› "ÁÉ$K!p=R-…Å*ÑÛc¬\sMIaª@(ªê‰˜:á÷ì!Xwk=Ú>ŠÂ ¯×òëTƒ‹…‹Y<(F©îAªPX—“Š,§@¡èÐ}%³Ô¯Â_èæú˜ãÛ‡Ä4' Yç%“Ü T°\¸6)m½‚—²Ïzp5z³¸ÞUmÛŽ˜ ´ë°PÈPu*oûtèdÔ~ÜLTŸµB )B·ÿuo«çV•BõïÜBÝËZQÙ÷póÖ·Bþ¯†ÖfìåªÆº¨jxÒiÄî)±WC W²Ô×—Qî¹–¬|붪1$JázÇžBE„ª:%[‰wmágç áKÖÌÔ& S˜H…Û&Âm¥p[øgd&|Kªzà‚xâq9vF¨gØ?…ë‰wи†6Æ:IÔÁB¼@ƒ9‡rw‰=fª –”ØT׉9Á-I½ŽªW®ÀzBØËz'—Ÿxû™R˜ä½,õ…B]þAªÐY°—)§’.3;·¶ô¤RÕ}Åû©Ë,ï>ªrÕm“¼Ç=~# d¹GSI°ýôǘÐõ£B_ˆ¿@s¿ J¹p¥ mrE6äÊl(„ël᢮ËT}Yª¾\s¯ |é*„²ly–ªNýx Õã)r–UåÂ}²r¾Œ³U÷/ø8™y÷ÛwÕcI…õ­…K2äò¼×-NxêOíéŠÏ¹6qûVü^³ °kÎj|_ñ߉¹l„Ðfaj­ cârîm; §¼Àf•à¬s–-s¯Íl´¶-¿];€øœqÿbnaP>ˆ–*CUHl|ñsM>‘ Mû0^F9ó¡eÇçì&Ì Xª£ Ë;zIªž<ÒæiUÀÊ´hè”4DŇãfL¸p½7ã/!#;Uk›% —Lèòv͈aæLÌoxýÇ*º‡HCbØOL»§ºhJ*ü¸{ÕÔ=köêÛåô¸‰ÎZµž]β­êÇNèÙy»7| ú´§º?U? ]T®ßÎFaØÂ]Úy°¼iö¨w $Nû0â@¯á€\ÜU(¬/­Sê#ÐÊ#î²j¥ÚUøÈ´ n¦YàfâMDÝÖJÜN¼ªê•)]Á3lçjë¦ZC®ÈTõ¼äö¸ˆAKYt "±6wP½kÍMÕc Å^‘¹‰zŒXÞ²©eN¹ENy Ë’ÜeõãHò,)²œS/îÎ3S Ô—¨zh%yíÈYΩ¯MóêÍq6úwl:õA‰Û%®ÓÀÅ>¯!5óÒ²„Kf RU×ârŠª\\NϽ-\²Ú™JBü¬e&©.•0×V‰Û¡,0Ì!%=.®Ã V“µÑL20 ]T¦¯öœÆÿ¾ú¥Âst•8”¢¼i<}€€gßÚ@úÀ•OÕãwÊcVJë§ñ@QQé–¸• ÜŠ½Œw¨ÆƒT”¥3:·@øÝc¥†2ñƒ³‡Ç‹e~P*rv‘Ü&†3…2[µkMÜu¦Þ%—¥m9åêõÕåÙyå™9ëËUãpŠ­—ûÂ/zõ.¾lÕ¯{Õmá"޵QïTß_¼ßCÙRÛ.îz¿´Å/^IÞ¬$·¨IÝ£˜äì5ɹ­ÞÍ©^§è—®: ˆ·Ô£þ¯sוæíbU?‡ºNÜUš»œûÜ’œåÜ]¦â}Åçzow<Ê(¾{Y¬sµsǧ¿«îoˆ–| gc—X'þ=&t]† ûë¸UONÜüÅïSJ¯þ» œ½áîÔ¼Â+þ;LÍLV0±[œ2’„Ûiª2Yfb^@+1Èe©ëäŠ'œB¥Àv^V`oøjôi1–½]ÕC•(+[Ž·¾þUºD¦r\žõ#ê;•³›ÑBøpTÎPÙUÚ´R ±©úÒº`/N°(kRq‰ä渓íŠè KÜH³À¤XáCùw$§?¬àV _À¶õ„p4®ÑJZÞªÛ5íëãÄ}øïÎߥÞSüàT}PzUí2(‰ ÄÞs”}š>ŠÜ„ÕGf–X'ZíðN/<±°`ÿÅïJ \"ñu»Ÿƒ¿®† {ó‘:nÙ“»rït©+WÈ™ÏоA_U3$ÿÝù ·“®¡‘ËS¥®# ³Ÿá­î_UøqŇ¶ŽªË“È oy-K}[æÒò›ê¶X.ÔçÝÎ |âû²¤ð–’ž ¼o¿ÇàV¥‡N2N ]TL|r†° ‡ÎDå•y7¬·š5…4v£ô;Z ž™w€´xà×=êiâ„õë o3!ˆ44š˜©CWž¤*ö¸] 7RÒq=ÝQ29¢/!K~¶BÛ öÆÔqhŒÆ.-…€õ”j7…Ø›%ŽÍ(Ééè_Ë}ÌGéñ8rý'ôô|±Bm¨*Ä^®Æ”%“³1¿áúƒóÂߪ¥[öäÄ^¿]ç¾Ìë+‰*CÏ}gš\o×KëPÓ®jÙ•~ÞÈè„Ëøçæ~ƒëíj"üð{Jo<ü¯ÌõÄ#4õáIÛøÞ|s[għ–0 ðß¾ kÐ×k\©?âÈ8mèÊNŒD÷V¾ô_‚?¿Vÿšˆ:±A“fâÔÕ8X:×Ç”…+ñÞhÕ¸MëŒÍ¥[0ðý¸z'¿ç`HWüðÎs¦×bÞÊ+MWÂ1wæoÕ_CøÒ»êlé8 Ák°ðöjàX”ý—z”-üÓí…€•†è \O5Å”{P*+v”™xD’¨ê —ÂE YnŽÍóưTÄ„®#¨óâ2%S=—¡ù÷Ö/ªÝ&(u¤ôØqæS¼Ókî¦b¸®)’òL‹»g3²Rajá £–iGËz~øûZ(î=*{LcM»ú:j‘öˆGþðò}7£Òü¹¥ÄÀ•+%#û/±·«º1ÒЕÓ_Åm“šy³+So`Là8tÞýãüq÷L(ºõ ¯ÖW0¸q‚FuC|õ¹‘Z·ÿäuŒüp’dù'Æ=ª3‚Çú _nBp²x¸·ȼŽ,…ï_”ÃFx}ü” L”–êÉG*FbȲ*öøâÐ.ñ&Q©RÜJUàFª¶ÒÌ/NˆšwÙGý9XÕ€»“'š¸´DC!\Õn‹=ZÒ'ܵ"Ž/eÑæ!äºÔƽ‡jþ¥ãQ{Ë\op«7uÔ"í_÷à€]únF¥ñk:Tu!Ã"îšÜýßWªyÆrd¥§gÀÒR½,~î goWuc”¡+bßçXu±f¿ä†õ9g¹ùGNYøaÏX˜ ™ÀÝw‚ú/ĦmûáÛ)F£º!>†7F¤4+vÂÛ«~…\¡ôienŠï§`D7¯ü•ÓnÂÕ=%’³Ô½TG*ñLôœ•Ô½ZY ànº°”¸.®e@´§drÕñž¹ä•;ZïÑÈÅ'gVK8Yk2[zõ&Vßðò5}7ƒ¨Ú©ïäUâ°…´3ÔØ[º¨x¡êÃèBWvÂ%MýŸ†GÖÎü#Í"#"PÛóÀp" <<=±ñ|"b4ª+{‚Ñ%Ç_ÀÚýçòÊê8ÛbwðP<ݼÀ)F²2i@ìedutÄOfþ¹­@ ‰¬”ªóŠK¼–Wð¬.â8±÷JìµjTã)4T¿ò6Øž%"ªÞÄ%˜†€€=´ˆª ã ]Â/†åo¡éøO1 …3ví̯’ÉRaeS¯PH²¶¶FjŠ )Öºû‰©ºp'þü/:¯¬G„Î D½v…W63Wï6cƒ J$‹?Î$ê™­b3€—*v²cqPª8ÞJ Wâ@Zq V]‡¦åîÚ#""2tFº.îýk¯zâèêÅz lml*¨‚½S2™ 6vî°Ó°®(ñWLIJ+×§›Ó±hÏMÄ&çO$øLsüïYgœ9þÎXW*É‚‡cº,¥?­XÀ²‘ºÀÙÌN¦np4© '37ؙԒšD5=D¢xÁœƒñ¦%""ÊeD¡K‰ ß~‹èó há¦0œ!K‚L·ó§qx^+Ä®8ˆDà$U¯~ÞÞðòvШ®h°+©Û¸*v'‡¸ŠY«£4uàÇÈ/xÉs^è\üD±©gÛ3„?æUÕ­/¯)Ô½\%Ç^uj<œ}T=YO:O‘1©Š?ÀI·Œ(tI°x÷U,.Pºè,¿ÝI5e„2=]±¬=Œ%AÝ}| ÖxˆUÁ}áV?A£:C#-óÑö㘵öw(rNÁcci†õï °«g‘•³€{+û_"[™=w•Øy[¬RÆi‰cÄi x¿ªš©œˆˆˆ «6ߎKw¬ Ù€ñ&Àez,l\›bÆÚPôkn+Ä[ê IFV6^[þ3~øõB^™8nK0ïÛ´vá•Ó#€˜·…ëK¸š¢Äê(9n–sNhq²¿¸ä[ª9…ü› «„- ""2lFºÏÙŒÁ–Ý|pàdÉ»ú4­3q 2 ^‚cóOÚÚ¹E=„¼?µ „Gñ¼ƒ¾î}†ôìLlQà@œx¦°|â¼H&¥Ìê-öví<·]› foQüf4rg¯ÅaÐüÜŒË?ÝÎK=}ðÍ[ý`i^àåψRÏ6Ÿvg“”ø.JŽ{ùs¤ÂÜÄV®¸Ÿ …¼@Eâ)gÄóY›Û•ºQuÄÐeÄvþ‰1K÷ 5C}ÂUqVùEãü0sD§üóJ¿ˆý2Ó°þ–?,†.s?çíÀ™«qyeÝ[5Àö¹ƒábo<úC5Xþø½;XS„¬üûZ™ÙaäÓï —çKªÙ剈ˆH7º ̉Kw0hþÄ&䂟П¿ÑfHÂÖ,$Äý¨:_âéĽ[mÝ{á•΋àbSW×Í&""ªöº ȦCáÿé^ddÉU˦&R|6±'ÞØ‡"æ]ü…-1 ¤Éóïç`YC5çV§ÆõÔr"""2ºÐ~è¼1=g¯ß‡™­+†Nš‡å賂:±A“fâÔÕ8X:×Ç”…+ñÞhˆs­kZ§ …³×ý%?Ë+s²³ÄÖكЫuàn0nǬÃ77²q9¥pï–ã@Œé4_5»<éQ…®ì{ÿ`ð)óíAÞ÷Æ×Aø¦½Þì¢À˜Àqè¼ûÇùãî™Ptë9^­¯`pãê†ø8Vú6¥¤eâÅ%»±ûØ•¼2Ow„΄§Ë-d_y ?E]Fè² ä­Ú6nxµËRx»ùUz‰ˆˆ¨|Fº²”˜õåFŒ—‰D>}У•._¹›Ù§pÊÂ{ÆúÃT¨s÷„ þ ±iÛ~øvŠÑ¨nˆÏÈJÝž[÷’0wþ»q?¯¬ÏÓðã¬pLý—Ï…Õ7²–©¼x¼Œ¡gÃÂÔªRÛGDDDgT¡Ëª–ÆöÈ[N9Žß.(ñá—mùËFÔöð„CÞ{xxzbãùD:ÅhT§T-UŽ¿/D#0x'î%¦æ•My¾->}¹²nÀº«p N‰‚;Y7Æ¿Oа.'9%""ªjŒ*t”}#^€ßìï0ØÇ¡»RaeS¯PH²¶¶FjŠ )2ÍêŠ +±-¥•—æ—‹ñøê·;ÈV¨#•‰ø&u«I-÷àÌ™}X32ó×·È|Í{¢±Í \8+\ïùˆˆˆ¨òeèŠ:‚çGNEÀÜï±ð•ªÀdkcƒT!@ì’Éd°±s‡†uE+WIå%‘Ëxwía¬øõv^Y +ìxç)´±[ï#ÎàX|áò-Í› ¨÷rÔtkS¡ç ""ýxÜàd|Œ.tEÛ‚Þ#`î†Cã—?Ûº—·7bWD¢pRí*T"<ü¼½…:ê´¹k1I–ÿ„½ÿ\Ë+ónàŒ]o¦ &~¦^L…¬À4¶™À˜¦¯Á¯û,HLŒîe$""2:Fõm-O¼„‘#¦bêG…ÀÕ¸P›_ ºb>X{K‚º!úø¬9ð«‚û­~‚FuÚríNænGDôü²çŸ¶Äòá?cSäi\H.л%Üì¬hˆ±}>ƒ}£¶ZkU.£ ]¿lʼnèû8Û·¦(ï6qö/ ëB6`ü„ p™ צ˜±6ýšÛB[ê´á·³71|Q(&«A”©êÃ!±hÑôÌ;›‚LEþº5dJ¹E뀹€™…VžŸˆˆˆtèB—÷°ùP*ç—Zïæ€'K_¥iÝ“øfïLYyYru²rwJÃÒáá|ÖUl¾•¿žD ôIª}?ƒU‹.ZoU>£ ]†B YÓ¾9„/vÊ)QbTÛ+ðïx{¦¡@çÜ’˜è8M&~Xi§wˆˆˆt¡K‹2²²‘ž)/T&Ë«Éço}º¿œŽR-»Ú¤aJß?ðÀö:çé‚™\‰Á·1°ÿ'0mßGÍ'""¢JÄÐ¥Eo?9ëþ,^ñÍÅ×àsÚý…ð´4!±å—{ÅeãU«~¨;õ#ÀÁ¥’ZKDDDºÄÐ¥Eö–™hì’\æ:r…2Ìðb÷¿á|E\ùuÖ™J¼pÍ =ú}ɳÃ*¹µDDD¤K ]ZÔ½i ¦Ìþ1o9^Q˯*0°ŽíÔ“|áa†«£8›¤²òïûtLÆ+»Àé€k=]7ˆˆˆ*C—ýzÅuÝ-àl­ÞWzG+)Jl‰V¢£¿Æ)ðcŒéFÊ;¥)0î‚íû.’Ê:›#éC—É2ÌðÉï-ñAÿ“ª^®ßî«'5½›L;Ÿ¸ŒÂë?{= /¦>›Ù_nMõÐb"""Ò†.-[ñ—¦w;‡Ð{içL$/^ \uSxí_9<{MF½ ˜˜è¥­DDD¤; ]ZÖ6í.Ò¥à·ÚÒbub´zîJ&¼NÙÂsÕ. ÉS:oéC—µSÜÆÛÊmØœl y-óbõ®© ;™ŽÉ?|ÍÀEDDT­0ti‘ æH³’ã·&V%ÖÇYI°»¡:EÝÑqˈˆˆHߺ* êÄNMš‰SWã`é\S®Ä{£ýQô8Ã&]:cB¸L¤1%>ŽR¸ÇaþHè—*¿ÙDDDT…0t•C™zcÇ¡sðnìç»gBÑ­çPxµ¾‚!>Ž…Ö­WSóæ±yè‹RÚ}{)V.å®E""¢ê†¡«7ÿÁ) ?ìëS!4¹ûBPÿ…Ø´m¿ºFZ7&!R\Ùe>žRøïø=x¦éÊl6U1 ]刌ˆ@mO8äŒ(‡§'6žPMQpc+·nØ2î$¹œþ¸™>†¹kmàã0 FU±RYZW+†®rÈd©°²©W(\Y[[#5EVâú’‚3ÊœŠ£™Öè0p®Uò:DDDT-0t•ÃÖÆ©Bð*Ø«%“É`cç^lݰ°°âà\aGOVj‰ˆ¨ê“J‹ÏßHÕ CW9¼¼½»â €úœÕJ„‡_€·w`±£ŠÝ_ b%• cÞ>n›á2æíã¶®˜SµÂÐU7¿@tÅ|°ö0–uCôñ-Xsà!V÷ÕwÓˆˆˆÈ€0t•CbéŽu!0~¸L…kSÌXŠ~ÍmõÝ4"""2 ]àæ€'·Ë›ˆˆˆ*C‘0tU2c*2æíã¶.cÞ>n›á2öí£ò1téC‘0téC‘0téCWeQfbßê…š¶ƒÄW“»ë»EO¦Œí‰:±A“fâÔÕ8X:×Ç”…+ñÞhÿb§IªªÂý€7¦ãìõû0³uÅÐIó°ü½1°TD¡ƒKcœI3‡4wc$fØ|:C¼ àŸŽ¼ìöúëv1d|_\T¨Lž¿‰_âÐg} ïµÓðߘÁ¼Ž¥mŸ¼OKý ‘ÉkGZSE?} +&öAHr;tlá¢ïÆhAéÛ£L½1ãÐ9x7öóÇÝ3¡èÖs(¼Z_ÁGý4÷1dßûƒ‡LÁ˜oâÐð¸þ3ü»Â7íýðæ3IHL«‰ƒ·î [-Ã;Q­2«ôöúë&j8éÂ%—25Âë6ì…ç„m``¯fÿÆ7N0×±Œí3ð÷i™Ÿ!]FðÚ‘61tU’.¯,ävm1­÷fdé»1ZPÚöÜü#§,ü°g¬?L…ŸgÔ!6mÛ/|pŒÔ[{+*Ké€Y_nÄáÃÒDhmŸ>èÑÊ—¯Ü€²¥Ép€“½aþîT&%–Ú~CÝŠSbë’©Hõ›Ž—:Õƒ2îªÁ½všüóíc0¯ciÛgèïÓ²>CnfŸ2Š×Ž´‡¡«RHж};áZ¡ï†hIéÛÚžpæ¯ëáé‰ç#„¯ATùnr«Z7Ú#o95æ8~» Ä‡_¶"é0’¥©ø`l?œøû,LìëaĤ¹X0yÌ«ú† I ¥¶ßÐ_·¢ÒnýÙk#±ý숛”]ƶWÍ×N³c‘N1ò:–¾}†þ>-ë3$ò—FðÚ‘61tÑ‘ÉRaeS¯Ð„µµ5RSdzk“¦’£OcdÀ ð›ýû8 ㆠ î¾/OÁú >¸q"ƒ_€©ë,ÙFßÍ-W¶YéíocD¯›ØËµnY0ZŽ _W3UIYÛn¯]AeýK1‚×јާE?CBw÷kG¡‹žˆ­ R…‚¿Ìd2lìÜõÙ¬Çu"ÏœŠ€¹ßcá+=TÛbÙèlÚüLÞ:-ž‰·G‡•»÷ÄwYí¦q¼n"壋øzÓ,ùwH^™¡¿v•õoÌÎþýËû´¤Ïcíèñ1tÑñòöF슃HTNªnr%ÂÃ/ÀÛ;Ð`ºÇ£ŽmAï‘ 0wÃ!Œñk–WþèÞM\¼›‰ö­šåm‹\¡€©™aü³)«ýÆðºåŠü%Qõ{¢{#«¼2Cí *ëµòòv0ø×ÑÞ§¥}†ûkGÏð>¨Jqó DW,ÀkcIP7Dß‚5bUp_}7­B䉗0rÄTLýá¨ðaÙ¸P]Ò¥ŸñìÀñï‡0¤]Ü>·+·ÃÈo>ÓOcSYíwós0è×­ ãÇÁË·;¬ |SúkWPYÿÆÜê'üëhèïÓ²>CŒýµ£ÇÇÐU2ÃÑÚ¹-"²ì¬Là÷>ønº]^ý‡¾˜¤ïÖ=¾r¶g]ÈŒŸ0.ÓcaãÚ3Ö†¢_s[}·ºB"~ÙŠÑ÷q¶o L-PÞmâ ìÿ4[—DcÖðŽJÊ€µ£^š¿ïöÑ[{‡›_éí—Å_·‚¢oÝBíÆu ••µíU’†ÿÆ$°5Œ×±¬í[ñšA¿OËü Yþšá¿v¤U ]•ÁÜgSÒõÝ í)g{Ü|pàd€¤=ÞÃæC©œ_j}ÀÄTƒ$1-³ý†üº4÷ÇÿŠ–³íUÎü3ˆ×±œí3ä÷iyŸ!ÿÚ‘V1téC‘0téC‘0téC‘0téC‘0téC‘0téC‘0téC‘0téCQ5•–ƒ½{ö"òæ=X;×ßçàÛ¤–ª.+ö?,ÝöÞ™ò{˶‡ãÝ)£`&Ñs£‰ˆ CQ5tíïèø?4í9=Ú6CÂõÜq&†o§û []‹VlÁ[Bè2W*-W<Ù*S1¬Co,9ö7š˜hgˆˆ CQ5£L¿… &"`Ñ^|úªr;¯ÞÓ#ßÝŽ;¯ô†sÁ;H¤05‘æ-f§Äa×öDÞIBã6ÝدÌ…ƒÚG»®`|¯úØrÙ6u1bô ps0EÈ—³ðç…ëXü&þoÌ¢cϯ' +´ï1P~MÀN4"2v ]DÕÌÝ#{pDÞ?¾âW(èÔj=‡ UÝN+Pžu÷<}±oOÓ´h ÆÖ}¦`XÇ&ØõùDlû{:v~8xïÏBÌÕñØ~?}ÂùíóЬIdIlѶK'd]^¾Åûó§¡®âæ¿Ø7×üñ]Ýtúw "Ò5†.¢j&&:›£†»ù.ì^ƒcv}pkñÛªñ]ýÛ×@ïi¸òþ‹h,,+Ò¬19x&¼¬%ðµƒà!›ñH)O›607±C—Ï"qó,˜ÔõÆ ÁƒàjmŠ~†@iå¤ý %"ªbºˆª3¤&'![ ˜<æ>½k×®"éÂa´ks<§$Inãê] KR§:¨e¥~Ps ;Kõ<ù} ãþš€VÜÑÔ·+‰£û>ñvUu ]DÕL³ma{y9^y„çšÛåWÈ“°få šÇRîkkk‹&½^Ç™­ïƒ•}QøŸåŽÍ’XÕ¼owaÎpúȯ˜?=7Ò6cÅ?·‰ˆÈ0tU3æõŸÅÜqÞ˜0ll7~ƒnO¹##1ŸÎ5õ1äÒcS»½ûáBœºý6ÚճŃK0wýU|¶ø ”¹·R*…4;iYJüöÕvÇì—ýÑîÙAä¿ ?ÇÜÖúvU5 ]DÕŽ ÞX± vÏÃ[ƒ;áV|Ì­ÐcÈxüöœM%…Òäà=?. Ç„>í!±°@†Òo/ýâÑ‹e<£ÔÅý|ÓÐ×»%~òÎ1>ŸdÁ\*‡­ÛÓøjÍàÊØP"¢*…¡‹¨’˜Úãå™ËU—’Xµ~éW_P/øŽFú•ѹ÷D—ßÇiáR”i‹aH¿9¬ðrtî² Öþ}=¯nÜ Žá"¢ê‡¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHºˆˆˆˆt€¡‹ˆˆˆHþh¹ŒA1æLŒIEND®B`‚pgagroal-1.6.0/doc/man/000077500000000000000000000000001456565230200146425ustar00rootroot00000000000000pgagroal-1.6.0/doc/man/pgagroal-admin.1.rst000066400000000000000000000022251456565230200204160ustar00rootroot00000000000000============== pgagroal-admin ============== ----------------------------------- Administration utility for pgagroal ----------------------------------- :Manual section: 1 SYNOPSIS ======== pgagroal-admin [ -f FILE ] [ COMMAND ] DESCRIPTION =========== pgagroal-admin is an administration utility for pgagroal. OPTIONS ======= -f, --file FILE Set the path to a user file -U, --user USER Set the user name -P, --password PASSWORD Set the password for the user -g, --generate Generate a password -l, --length Password length -V, --version Display version information -?, --help Display help COMMANDS ======== master-key Create or update the master key. The master key will be created in the pgagroal user home directory under ~/.pgagroal add-user Add a user update-user Update a user remove-user Remove a user list-users List all users REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal.conf(5), pgagroal_hba.conf(5), pgagroal_databases.conf(5), pgagroal(1), pgagroal-cli(1) pgagroal-1.6.0/doc/man/pgagroal-cli.1.rst000066400000000000000000000033421456565230200200760ustar00rootroot00000000000000============ pgagroal-cli ============ --------------------------------- Command line utility for pgagroal --------------------------------- :Manual section: 1 SYNOPSIS ======== pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] DESCRIPTION =========== pgagroal-cli is a command line utility for pgagroal. OPTIONS ======= -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -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 logfile -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. Optional parameter with the database name, if not specified all will be enabled disable Disable a database. Optional parameter with the database name, if not specified all will be disabled 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 reset Reset the Prometheus statistics reset-server Reset the state of a server. Requires a server name as an argument REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal.conf(5), pgagroal_hba.conf(5), pgagroal_databases.conf(5), pgagroal(1), pgagroal-admin(1) pgagroal-1.6.0/doc/man/pgagroal.1.rst000066400000000000000000000023111456565230200173240ustar00rootroot00000000000000======== pgagroal ======== ----------------------------------------------- High-performance connection pool for PostgreSQL ----------------------------------------------- :Manual section: 1 SYNOPSIS ======== pgagroal [ -c CONFIG_FILE ] [ -a HBA_FILE ] [ -d ] DESCRIPTION =========== pgagroal is a high-performance protocol-native connection pool for PostgreSQL. OPTIONS ======= -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -a, --hba HBA_FILE Set the path to the pgagroal_hba.conf file -l, --limit LIMIT_FILE Set the path to the pgagroal_databases.conf file -u, --users USERS_FILE Set the path to the pgagroal_users.conf file -A, --admins ADMINS_FILE Set the path to the pgagroal_admins.conf file -S, --superuser SUPERUSER_FILE Set the path to the pgagroal_superuser.conf file -d, --daemon Run as a daemon -V, --version Display version information -?, --help Display help REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal.conf(5), pgagroal_hba.conf(5), pgagroal_databases.conf(5), pgagroal-cli(1), pgagroal-admin(1) pgagroal-1.6.0/doc/man/pgagroal.conf.5.rst000066400000000000000000000162131456565230200202620ustar00rootroot00000000000000============= pgagroal.conf ============= ------------------------------------ Main configuration file for pgagroal ------------------------------------ :Manual section: 5 DESCRIPTION =========== pgagroal.conf is the main configuration file for pgagroal. The file is split into different sections specified by the ``[`` and ``]`` characters. The main section is called ``[pgagroal]``. Other sections specifies the PostgreSQL server configuration. All properties are in the format ``key = value``. The characters ``#`` and ``;`` can be used for comments; must be the first character on the line. The ``Bool`` data type supports the following values: ``on``, ``1``, ``true``, ``off``, ``0`` and ``false``. OPTIONS ======= The options for the main section are host The bind address for pgagroal. Mandatory port The bind port for pgagroal. Mandatory unix_socket_dir The Unix Domain Socket location. Mandatory metrics The metrics port. Default is 0 (disabled) metrics_cache_max_age The number of seconds to keep in cache a Prometheus (metrics) response. If set to zero, the caching will be disabled. Can be a string with a suffix, like ``2m`` to indicate 2 minutes. Default is 0 (disabled) metrics_cache_max_size The maximum amount of data to keep in cache when serving Prometheus responses. Changes require restart. This parameter determines the size of memory allocated for the cache even if ``metrics_cache_max_age`` or ``metrics`` are disabled. Its value, however, is taken into account only if ``metrics_cache_max_age`` is set to a non-zero value. Supports suffixes: ``B`` (bytes), the default if omitted, ``K`` or ``KB`` (kilobytes), ``M`` or ``MB`` (megabytes), ``G`` or ``GB`` (gigabytes). Default is 256k management The remote management port. Default is 0 (disabled) log_type The logging type (console, file, syslog). Default is console log_level The logging level, any of the (case insensitive) strings ``FATAL``, ``ERROR``, ``WARN``, ``INFO`` and ``DEBUG`` (that can be more specific as ``DEBUG1`` thru ``DEBUG5``). Debug level greater than 5 will be set to ``DEBUG5``. Not recognized values will make the ``log_level`` be ``INFO``. Default is info log_path The log file location. Default is pgagroal.log. Can be a strftime(3) compatible string log_rotation_age The age that will trigger a log file rotation. If expressed as a positive number, is managed as seconds. Supports suffixes: ``S`` (seconds, the default), ``M`` (minutes), ``H`` (hours), ``D`` (days), ``W`` (weeks). A value of ``0`` disables. Default is 0 (disabled) log_rotation_size The size of the log file that will trigger a log rotation. Supports suffixes: ``B`` (bytes), the default if omitted, ``K`` or ``KB`` (kilobytes), ``M`` or ``MB`` (megabytes), ``G`` or ``GB`` (gigabytes). A value of ``0`` (with or without suffix) disables. Default is 0 log_line_prefix A strftime(3) compatible string to use as prefix for every log line. Must be quoted if contains spaces. Default is ``%Y-%m-%d %H:%M:%S`` log_mode Append to or create the log file (append, create). Default is append log_connections Log connects. Default is off log_disconnections Log disconnects. Default is off blocking_timeout The number of seconds the process will be blocking for a connection (disable = 0). Default is 30 idle_timeout The number of seconds a connection is been kept alive (disable = 0). Default is 0 max_connection_age The maximum number of seconds that a connection will live (disable = 0). Default is 0 validation Should connection validation be performed. Valid options: off, foreground and background. Default is off background_interval The interval between background validation scans in seconds. Default is 300 max_retries The maximum number of iterations to obtain a connection. Default is 5 max_connections The maximum number of connections (max 1000). Default is 1000 allow_unknown_users Allow unknown users to connect. Default is true authentication_timeout The number of seconds the process will wait for valid credentials. Default is 5 pipeline The pipeline type. Valid options are auto, performance, session and transaction. Default is auto auth_query Enable authentication query. Default is false failover Enable failover support. Default is false failover_script The failover script tls Enable Transport Layer Security (TLS). Default is false tls_cert_file Certificate file for TLS tls_key_file Private key file for TLS tls_ca_file Certificate Authority (CA) file for TLS libev The libev backend to use. Valid options: auto, select, poll, epoll, iouring, devpoll and port. Default is auto buffer_size The network buffer size (SO_RCVBUF and SO_SNDBUF). Default is 65535 keep_alive Have SO_KEEPALIVE on sockets. Default is on nodelay Have TCP_NODELAY on sockets. Default is on non_blocking Have O_NONBLOCK on sockets. Default is off backlog The backlog for listen(). Minimum 16. Default is max_connections / 4 hugepage Huge page support. Default is try tracker Track connection lifecycle. Default is off track_prepared_statements Track prepared statements (transaction pooling). Default is off pidfile Path to the PID file. If omitted, automatically set to ``unix_socket_dir/pgagroal.port.pid`` update_process_title The behavior for updating the operating system process title, mainly related to connection processes. Allowed settings are: ``never`` (or ``off``), does not update the process title; ``strict`` to set the process title without overriding the existing initial process title length; ``minimal`` to set the process title to ``username/database``; ``verbose`` (or ``full``) to set the process title to ``user@host:port/database``. Please note that ``strict`` and ``minimal`` are honored only on those systems that do not provide a native way to set the process title (e.g., Linux). On other systems, there is no difference between ``strict`` and ``minimal`` and the assumed behaviour is ``minimal`` even if ``strict`` is used. ``never`` and ``verbose`` are always honored, on every system. On Linux systems the process title is always trimmed to 255 characters, while on system that provide a natve way to set the process title it can be longer Danger zone disconnect_client Disconnect clients that have been idle for more than the specified seconds. This setting DOES NOT take long running transactions into account. Default is 0 disconnect_client_force Disconnect clients that have been active for more than the specified seconds. This setting DOES NOT take long running transactions into account. Default is off The options for the PostgreSQL section are host The address of the PostgreSQL instance. Mandatory port The port of the PostgreSQL instance. Mandatory primary Identify the instance as the primary instance (hint) tls Enable Transport Layer Security (TLS) support (Experimental - no pooling). Default is off REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal_hba.conf(5), pgagroal_databases.conf(5), pgagroal(1), pgagroal-cli(1), pgagroal-admin(1) pgagroal-1.6.0/doc/man/pgagroal_databases.conf.5.rst000066400000000000000000000023671456565230200222760ustar00rootroot00000000000000======================= pgagroal_databases.conf ======================= ------------------------------------------------ Limits for databases, users or both for pgagroal ------------------------------------------------ :Manual section: 5 DESCRIPTION =========== The pgagroal_databases.conf configuration file defines limits for a database or a user or both. FORMAT ====== DATABASE Specifies the database for the rule. Either specific name or all for all databases USER Specifies the user for the rule. Either specific name or all for all users MAX_SIZE Specifies the maximum pool size for the entry. all for all connections INITIAL_SIZE Specifies the initial pool size for the entry. Default is 0. Requires a pgagroal_users.conf configuration MIN_SIZE Specifies the minimum pool size for the entry. Default is 0. Requires a pgagroal_users.conf configuration EXAMPLE ======= :: # # DATABASE USER MAX_SIZE INITIAL_SIZE MIN_SIZE # all all all REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal.conf(5), pgagroal_hba.conf(5), pgagroal(1), pgagroal-cli(1), pgagroal-admin(1) pgagroal-1.6.0/doc/man/pgagroal_hba.conf.5.rst000066400000000000000000000023701456565230200210730ustar00rootroot00000000000000================= pgagroal_hba.conf ================= ------------------------------------------------- Host based access configuration file for pgagroal ------------------------------------------------- :Manual section: 5 DESCRIPTION =========== pgagroal_hba.conf specifies the host based access pattern for pgagroal. FORMAT ====== TYPE Specifies the access method for clients. Only host supported DATABASE Specifies the database for the rule. Either specific name or all for all databases USER Specifies the user for the rule. Either specific name or all for all users ADDRESS Specifies the network for the rule. all for all networks, or IPv4 address with a mask (0.0.0.0/0) or IPv6 address with a mask (::0/0) METHOD Specifies the authentication mode for the user. all for all methods, otherwise trust, reject, password, md5 or scram-sha-256 EXAMPLE ======= :: # # TYPE DATABASE USER ADDRESS METHOD # host all all all all REPORTING BUGS ============== pgagroal is maintained on GitHub at https://github.com/agroal/pgagroal COPYRIGHT ========= pgagroal is licensed under the 3-clause BSD License. SEE ALSO ======== pgagroal.conf(5), pgagroal_databases.conf(5), pgagroal(1), pgagroal-cli(1), pgagroal-admin(1) pgagroal-1.6.0/doc/tutorial/000077500000000000000000000000001456565230200157325ustar00rootroot00000000000000pgagroal-1.6.0/doc/tutorial/01_install.md000066400000000000000000000211071456565230200202230ustar00rootroot00000000000000# Install pgagroal This tutorial will show you how to do a simple installation of `pgagroal`, in order to get a running connection pool. ## Preface This tutorial assumes that you already have an installation of PostgreSQL 10 (or higher). For RPM based distributions such as Fedora and RHEL you can add the [PostgreSQL YUM repository](https://yum.postgresql.org/){:target="_blank"} and do the install via the distribution package manager `dnf`: ``` dnf install -y pgagroal ``` If you don't have PostgreSQL already installed, you can install both PostgreSQL and `pgagroal` in a single pass: ``` dnf install -y postgresql14 postgresql14-server pgagroal ``` (assuming you want to install version 14 of PostgreSQL). ## PostgreSQL setup In the case you don't have yet a PostgreSQL running instance, you need to initialize the cluster the connection pooler will connect to. The followings are simple and quick steps to get a cluster up and running as soon as possible. It is assumed that you run an RPM based distribution, like Fedora or RHEL. Some commands could be in different paths depending on the operating system distribution you are using. ### Initialize cluster You need to define a `PGDATA` data directory where PostgreSQL will store the data in. In the following, it is assumed that the PostgreSQL directory is `/postgres/14/data`, then you can do the following commands in a shell as the operating system user `postgres`: ``` export PATH=/usr/pgsql-14/bin:$PATH mkdir -p /postgres/14/data export PGDATA=/postgres/14/data initdb $PGDATA ``` (`postgres` user) ### Remove default accesses By default, PostgreSQL allows trusted accesses from the local machine to any database within the cluster. It is better to harden your cluster, thus providing accesses only to who and when it is needed. In order to do this, with your text editor of choice, edit the file `$PGDATA/pg_hba.conf` and remote the following lines: ``` host all all 127.0.0.1/32 trust host all all ::1/128 trust host replication all 127.0.0.1/32 trust host replication all ::1/128 trust ``` from `/postgres/14/data/pg_hab.conf` (`postgres` user) ### Add access for a user and a database Assume you will have a database named `mydb` and a user named `myuser` that will be granted access to such database. In order to do so, edit again the `$PGDATA/pg_hba.conf` file and add a couple of lines like the followings: ``` host mydb myuser 127.0.0.1/32 md5 host mydb myuser ::1/128 md5 ``` The first line grants access to the user `myuser` against the database `mydb` on IPv4 `localhost`; the second line does the same but on IPv6 `localhost` connection. Please check the value of the setting `password_encryption` in the configuration file `$PGDATA/postgresql.conf` in order to ensure it matches `md5` as the last column in the previous two lines. ### Start PostgreSQL It is now time to run the PostgreSQL instance, so as the `postgres` operating system user, run: ``` pg_ctl -D $PGDATA start ``` ### Create the database and the user It is now time to create the database and the user of the previous step. As operating system user `postgres`, execute: ``` psql -c "CREATE ROLE myuser WITH LOGIN PASSWORD 'mypassword';" psql -c "CREATE DATABASE mydb WITH OWNER myuser;" ``` It is strongly suggested to choose a strong password to protect the database access! ### Verify access You can check the connectivity of the database user executing, from a shell, as any operating system user, the following command: ``` psql -h localhost -p 5432 -U myuser -c 'SELECT current_timestamp:' mydb ``` Type the `mypassword` password when asked, and if you get back the current date and time, everything is working fine! ## `pgagroal` setup In order to run `pgagroal`, you need at list to configure the main `pgagroal.conf` configuration file, that will tell the pooler how to work, and then `pgagroal_hba.conf` that will instrument the pooler about which users are allowed to connect thru the pooler. `pgagroal` as a daemon cannot be run by `root` operating system user, it is a good idea to create an unprivileged operating system user to run the pooler. ### Add a user to run `pgagroal` As a privileged operating system user (either `root` or via `sudo` or `doas`), run the followings: ``` useradd -ms /bin/bash pgagroal passwd pgagroal ``` The above will create an operating system `pgagroal` that is the one that is going to run the pooler. ### Create basic configuration As the `pgagroal` operating system user, add a master key to protect the `pgagroal` vault and then add the `myuser` to the pooler: ``` pgagroal-admin master-key pgagroal-admin -f /etc/pgagroal/pgagroal_users.conf -U myuser -P mypassword user add ``` **You have to choose a password for the master key - remember it!** It is now time to create the main `/etc/pgagroal/pgagroal.conf` configration file, with your editor of choice of using `cat` from the command line, create the following content: ``` cd /etc/pgagroal cat > pgagroal.conf [pgagroal] host = * port = 2345 log_type = file log_level = info log_path = /tmp/pgagroal.log max_connections = 100 idle_timeout = 600 validation = off unix_socket_dir = /tmp/ [primary] host = localhost port = 5432 ``` and press `Ctrl-D` (if running `cat`) to save the file. Similarly, create the `/etc/pgagroal/pgagroal_hba.conf` file; ``` cd /etc/pgagroal cat > pgagroal_hba.conf host mydb myuser all all ``` and press `Ctrl-D` (if using `cat`) to save the file. Shortly, the above line tells `pgagral` to allow the user `myuser` to *try* to connect to `mydb` using a TCP-IP connection. See [the documentation about `pgagroal_hba.conf` for more details](https://github.com/agroal/pgagroal/blob/master/doc/CONFIGURATION.md#pgagroal_hba-configuration). ### Start pgagroal It is now time to start `pgagroal`, so as the `pgagroal` operating system user run: ``` pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf ``` If the system is running, you will see some output on the log file `/tmp/pgagroal.log`. Since the default configuration files are usually searched into the `/etc/pgagroal/` directory, and have well defined names, you can omit the files from the command line if you named them `pgagroal.conf`, `pgagroal_hba.conf` and `pgagroal_users.conf`: ``` pgagroal ``` You will not need to specify any command line flag for files that have the standard name like: - `/etc/pgagroal/pgagroal.conf` (main configuration file) - `/etc/pgagroal/pgagroal_hba.conf` (host based access configuration file) - `/etc/pgagroal/pgagroal_databases.conf` (limits file) - `/etc/pgagroal/pgagroal_admins.conf` (remote management file) - `/etc/pgagroal/pgagroal_frontend_users.conf` (split security user remapping) **In the case you named the configuration files differently or in a different folder, you need to specify them on the command line!** ## Shell completion There is a minimal shell completion support for `pgagroal-cli` and `pgagroal-admin`. If you are running such commands from a Bash or Zsh, you can take some advantage of command completion. ### Installing command completions in Bash There is a completion script into `contrib/shell_comp/pgagroal_comp.bash` that can be used to help you complete the command line while you are typing. It is required to source the script into your current shell, for instance by doing: ``` shell source contrib/shell_comp/pgagroal_comp.bash ``` At this point, the completions should be active, so you can type the name of one the commands between `pgagroal-cli` and `pgagroal-admin` and hit `` to help the command line completion. ### Installing the command completions on Zsh In order to enable completion into `zsh` you first need to have `compinit` loaded; ensure your `.zshrc` file contains the following lines: ``` shell autoload -U compinit compinit ``` and add the sourcing of the `contrib/shell_comp/pgagroal_comp.zsh` file into your `~/.zshrc` also associating the `_pgagroal_cli` and `_pgagroal_admin` functions to completion by means of `compdef`: ``` shell source contrib/shell_comp/pgagroal_comp.zsh compdef _pgagroal_cli pgagroal-cli compdef _pgagroal_admin pgagroal-admin ``` If you want completions only for one command, e.g., `pgagroal-admin`, remove the `compdef` line that references the command you don't want to have automatic completion. At this point, digit the name of a `pgagroal-cli` or `pgagroal-admin` command and hit `` to trigger the completion system. pgagroal-1.6.0/doc/tutorial/02_prefill.md000066400000000000000000000062761456565230200202250ustar00rootroot00000000000000# Enable prefill for pgagroal This tutorial will show you how to do enable *prefill* for `pgagroal`. The prefill is the capability to activate connections against a specific database even if no one has been actively requested by a user or an application. This allows the pooler to serve a connection faster once it is effectively requested, since the connection is already established. Prefill is done by making `pgagroal` to open the specified amount of connections to a specific database with a specific username, therefore you need to know credentials used on the PostgreSQL side. ## Preface This tutorial assumes that you have already an installation of PostgreSQL 10 (or higher) and `pgagroal`. In particular, this tutorial refers to the configuration done in [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/01_install.md). ## Create prefill configuration Prefill is instrumented by the `pgagroal_databases.conf` configuration file, where you need to list databases, usernames, and limits. Every username/database pair has to be specified on a separated line. The limits are assumed as: - *max number of allowed connections* for that username/database - *initial number of connections*, that is the effective prefill; - *minimum number of connections* to always keep open for the pair username/database. Assuming you want to configure the prefill for the `mydb` database with the `mysuer` username, you have to edit the file `/etc/pgagroal/pgagroal_databases.conf` with your editor of choice or using `cat` from the command line, as follows: ``` cd /etc/pgagroal cat > pgagroal_databases.conf mydb myuser 2 1 0 ``` and press `Ctrl-D` to save the file. This will create a configuration where `mydb` will have a maximum connection size of 2, an initial connection size of 1 and a minimum connection size of 0 for the `myuser` user. The file must be owned by the operating system user `pgagroal`. The `max_size` value is mandatory, while the `initial_size` and `min_size` are optional and if not explicitly set are assumed to be `0`. See [the `pgagroal_databases.conf` file documentation](https://github.com/agroal/pgagroal/blob/master/doc/CONFIGURATION.md#pgagroal_databases-configuration) for more details. ## Restart pgagroal In order to apply changes to the prefill configuration, you need to restart `pgagroal`. You can do so by stopping it and then re-launch the daemon, as `pgagroal` operating system user: ``` pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -l /etc/pgagroal/pgagroal_databases.conf ``` Note that the limit file `pgagroal_databases.conf` has been specified by means of the `l` command line flag. If the file has the standard name `/etc/pgagroal/pgagroal_databases.conf`, you can omit it from the command line. ## Check the prefill You can check the prefill by running, as the `pgagroal` operating system user, the `status` command: ``` pgagroal-cli status Status: Running Active connections: 0 Total connections: 1 Max connections: 100 ``` where the `Total connections` is set by the *initial* connection specified in the limit file. pgagroal-1.6.0/doc/tutorial/03_remote_management.md000066400000000000000000000063031456565230200222470ustar00rootroot00000000000000# Remote administration for pgagroal This tutorial will show you how to do setup remote management for `pgagroal`. `pgagroal` is managed via a command line tool named `pgagroal-cli`. Such tool connects via a local Unix socket if running on the same machine the pooler is running on, but it is possible to use `pgagroal-cli` from a different machine and make it to connect to the pooler machine via *remote management*. ## Preface This tutorial assumes that you have already an installation of PostgreSQL 10 (or higher) and `pgagroal`. In particular, this tutorial refers to the configuration done in [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/01_install.md). ## Enable remote management On the pooler machine, you need to enable the remote management. In order to do so, add the `management` setting to the main `pgagroal.conf` configuration file. The value of setting is the number of a free TCP/IP port to which the remote management will connect to. With your editor of choice, edit the `/etc/pgagroal/pgagroal.conf` file and add the `management` option likely the following: ``` management = 2347 ``` under the `[pgagroal]` section, so that the configuration file looks like: ``` [pgagroal] ... management = 2347 ``` See [the `pgagroal` configuration settings](https://github.com/agroal/pgagroal/blob/master/doc/CONFIGURATION.md#pgagroal) for more details. ## Add remote admin user Remote management is done via a specific admin user, that has to be created within the pooler vault. As the `pgagroal` operating system user, run the following command: ``` cd /etc/pgagroal pgagroal-admin -f pgagroal_admins.conf -U admin -P admin1234 add-user ``` The above will create the `admin` username with the `admin1234` password. **We strongly encourage you to choose non trivial usernames and passwords!** ## Restart pgagroal In order to make the changes available, and therefore activate the remote management, you have to restart `pgagroal`, for example by issuing the following commands from the `pgagroal` operatng system user: ``` pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -A /etc/pgagroal/pgagroal_admins.conf ``` Please note the presence of the `-A` flag that indicates to the pooler which file use to authenticate remote management users. If the file has the standard name `/etc/pgagroal/pgagroal_admins.conf` you can omit it from the command line. ## Connect via remote administration interface In order to connect remotely, you need to specify at least the `-h` and `-p` flags on the `pgagroal-cli` command line. Such flags will tell `pgagroal-cli` to connect to a remote host. You can also specify the username you want to connect with by specifying the `-U` flag. So, to get the status of the pool remotely, you can issue: ``` pgagroal-cli -h localhost -p 2347 -U admin status ``` and type the password `admin1234` when asked for it. If you don't specify the `-U` flag on the command line, you will be asked for a username too. Please note that the above example uses `localhost` as the remote host, but clearly you can specify any *real* remote host you want to manage. pgagroal-1.6.0/doc/tutorial/04_prometheus.md000066400000000000000000000046041456565230200207560ustar00rootroot00000000000000# Prometheus metrics for `pgagroal` This tutorial will show you how to do basic [Prometheus](https://prometheus.io/){:target="_blank"} setup for `pgagroal`. `pgagroal` is able to provide a set of metrics about what it is happening within the pooler, so that a Prometheus instance can collect them and help you monitor the pooler. ## Preface This tutorial assumes that you have already an installation of PostgreSQL 10 (or higher) and `pgagroal`. In particular, this tutorial refers to the configuration done in [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/01_install.md). ## Change the pgagroal configuration In order to enable to export of the metrics, you need to add the `metrics` option in the main `pgagroal.conf` configuration. The value of this setting is the TCP/IP port number that Prometheus will use to grab the exported metrics. Add a line like the following to `/etc/pgagroal/pgagroal.conf` by editing such file with your editor of choice: ``` metrics = 2346 ``` Place it withingr the `[pgagroal]` section, like ``` [pgagroal] ... metrics = 2346 ``` This will bind the TCP/IP port number `2346` to the metrics export. See [the `pgagroal` configuration settings](https://github.com/agroal/pgagroal/blob/master/doc/CONFIGURATION.md#pgagroal) with particular regard to `metrics`, `metrics_cache_max_age` and `metrics_cache_max_size` for more details. ## Restart pgagroal In order to apply changes, you need to restart `pgagroal`, therefore run the following commands as the `pgagroal` operating system user: ``` pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf ``` If you need to specify other configuration files, for example for remote management (see [the related tutorial](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/03_remote_management.md)), add them on the `pgagroal` command line. If the cofiguration files have standard names, you can omit them. ## Get Prometheus metrics Once `pgagroal` is running you can access the metrics with a browser at the pooler address, specifying the `metrics` port number and routing to the `/metrics` page. For example, point your web browser at: ``` http://localhost:2346/metrics ``` It is also possible to get an explaination of what is the meaning of each metric by pointing your web browser at: ``` http://localhost:2346/ ``` pgagroal-1.6.0/doc/tutorial/05_split_security.md000066400000000000000000000062771456565230200216560ustar00rootroot00000000000000# Split security model in pgagroal This tutorial will show you how to split the security model of `pgagroal`. The idea is that the pooler can act as a *user proxy* between your application and the PostgreSQL instance, so that your application does not need to know the exact password to use to connect to PostgreSQL. `pgagroal` will authenticate the connection request with its credentials, and then will authenticate against PostgreSQL with the correct password. This *user mapping* is named *frontend users*. ## Preface This tutorial assumes that you have already an installation of PostgreSQL 10 (or higher) and `pgagroal`. In particular, this tutorial refers to the configuration done in [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/01_install.md). ## Create frontend users Frontend users are stored into the `pgagroal_frontend_users.conf` file, that can be managed via the `pgagroal-admin` command line tool. See [the documentation on frontend users](https://github.com/agroal/pgagroal/blob/master/doc/CONFIGURATION.md#pgagroal_frontend_users-configuration) for more details. As an example, consider the user `myuser` created in the [Installing pgagroal tutorial](https://github.com/pgagroal/pgagroal/blob/master/doc/tutorial/01_install.md)): such user has the `mypassword` password defined on the PostgreSQL side. It is possible to *remap* the user password on the `pgagroal` side, so that an application can connect to the `pgagroal` using a different password, like `application_password`. In turn, `pgagroal` will connect to PostgreSQL using the `mypassword` password. Therefore, the application could not know the *real* password used to connect to PostgreSQL. To achieve this, as `pgagroal` operating system run the following command: ``` pgagroal-admin -f /etc/pgagroal/pgagroal_frontend_users.conf -U myuser -P application_password user add ``` (`pgagroal` user) You will need a password mapping for each user defined in the `pgagroal_users.conf` configuration file. ## Restart pgagroal In order to apply changes, you need to restart `pgagroal`, so as the `pgagroal` operating system user do: ``` pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -F /etc/pgagroal/pgagroal_frontend_users.conf ``` Please note that the frontend users file is specified by means of the `-F` command line flag. If you need to specify other configuration files, add them on the command line. If the files have standard names, you can omit it from the command line. ## Connect to PostgreSQL You can now use the "application password" to access the PostgreSQL instance. As an example, run the following as any operatng system user: ``` psql -h localhost -p 2345 -U myuser mydb ``` using `application_password` as the password. As already explained, `pgagroal` will then use the `mypass` password against PostgreSQL. This **split security model** allows you to avoid sharing password between applications and PostgreSQL, letting the `pgagroal` to be the secret-keeper. This not only improves security, but also allows you to change the PostgreSQL password without having the application to note it. pgagroal-1.6.0/pgagroal.spec000066400000000000000000000174051456565230200160010ustar00rootroot00000000000000Name: pgagroal Version: 1.6.0 Release: 1%{dist} Summary: High-performance connection pool for PostgreSQL License: BSD 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 libatomic cjson cjson-devel Requires: libev openssl systemd libatomic cjson %description pgagroal is a high-performance connection pool for PostgreSQL. %prep %setup -q %build %{__mkdir} build cd build cmake -DCMAKE_BUILD_TYPE=Release .. %{__make} %install %{__mkdir} -p %{buildroot}%{_sysconfdir} %{__mkdir} -p %{buildroot}%{_bindir} %{__mkdir} -p %{buildroot}%{_libdir} %{__mkdir} -p %{buildroot}%{_docdir}/%{name}/grafana %{__mkdir} -p %{buildroot}%{_docdir}/%{name}/etc %{__mkdir} -p %{buildroot}%{_docdir}/%{name}/images %{__mkdir} -p %{buildroot}%{_docdir}/%{name}/shell_comp %{__mkdir} -p %{buildroot}%{_docdir}/%{name}/tutorial %{__mkdir} -p %{buildroot}%{_mandir}/man1 %{__mkdir} -p %{buildroot}%{_mandir}/man5 %{__mkdir} -p %{buildroot}%{_sysconfdir}/pgagroal %{__install} -m 644 %{_builddir}/%{name}-%{version}/LICENSE %{buildroot}%{_docdir}/%{name}/LICENSE %{__install} -m 644 %{_builddir}/%{name}-%{version}/CODE_OF_CONDUCT.md %{buildroot}%{_docdir}/%{name}/CODE_OF_CONDUCT.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/README.md %{buildroot}%{_docdir}/%{name}/README.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/contrib/grafana/dashboard.json %{buildroot}%{_docdir}/%{name}/grafana/dashboard.json %{__install} -m 644 %{_builddir}/%{name}-%{version}/contrib/grafana/README.md %{buildroot}%{_docdir}/%{name}/grafana/README.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/ARCHITECTURE.md %{buildroot}%{_docdir}/%{name}/ARCHITECTURE.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/CLI.md %{buildroot}%{_docdir}/%{name}/CLI.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/CONFIGURATION.md %{buildroot}%{_docdir}/%{name}/CONFIGURATION.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/FAILOVER.md %{buildroot}%{_docdir}/%{name}/FAILOVER.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/GETTING_STARTED.md %{buildroot}%{_docdir}/%{name}/GETTING_STARTED.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/PERFORMANCE.md %{buildroot}%{_docdir}/%{name}/PERFORMANCE.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/PIPELINES.md %{buildroot}%{_docdir}/%{name}/PIPELINES.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/RPM.md %{buildroot}%{_docdir}/%{name}/RPM.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/SECURITY.md %{buildroot}%{_docdir}/%{name}/SECURITY.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/etc/pgagroal.service %{buildroot}%{_docdir}/%{name}/etc/pgagroal.service %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/etc/pgagroal.socket %{buildroot}%{_docdir}/%{name}/etc/pgagroal.socket %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/images/perf-extended.png %{buildroot}%{_docdir}/%{name}/images/perf-extended.png %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/images/perf-prepared.png %{buildroot}%{_docdir}/%{name}/images/perf-prepared.png %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/images/perf-readonly.png %{buildroot}%{_docdir}/%{name}/images/perf-readonly.png %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/images/perf-simple.png %{buildroot}%{_docdir}/%{name}/images/perf-simple.png %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/shell_comp/pgagroal_comp.bash %{buildroot}%{_docdir}/%{name}/shell_comp/pgagroal_comp.bash %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/shell_comp/pgagroal_comp.zsh %{buildroot}%{_docdir}/%{name}/shell_comp/pgagroal_comp.zsh %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/tutorial/01_install.md %{buildroot}%{_docdir}/%{name}/tutorial/01_install.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/tutorial/02_prefill.md %{buildroot}%{_docdir}/%{name}/tutorial/02_prefill.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/tutorial/03_remote_management.md %{buildroot}%{_docdir}/%{name}/tutorial/03_remote_management.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/tutorial/04_prometheus.md %{buildroot}%{_docdir}/%{name}/tutorial/04_prometheus.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/tutorial/05_split_security.md %{buildroot}%{_docdir}/%{name}/tutorial/05_split_security.md %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/etc/pgagroal.conf %{buildroot}%{_sysconfdir}/pgagroal/pgagroal.conf %{__install} -m 644 %{_builddir}/%{name}-%{version}/doc/etc/pgagroal_hba.conf %{buildroot}%{_sysconfdir}/pgagroal/pgagroal_hba.conf %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal.1 %{buildroot}%{_mandir}/man1/pgagroal.1 %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal-admin.1 %{buildroot}%{_mandir}/man1/pgagroal-admin.1 %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal-cli.1 %{buildroot}%{_mandir}/man1/pgagroal-cli.1 %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal.conf.5 %{buildroot}%{_mandir}/man5/pgagroal.conf.5 %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal_databases.conf.5 %{buildroot}%{_mandir}/man5/pgagroal_databases.conf.5 %{__install} -m 644 %{_builddir}/%{name}-%{version}/build/doc/pgagroal_hba.conf.5 %{buildroot}%{_mandir}/man5/pgagroal_hba.conf.5 %{__install} -m 755 %{_builddir}/%{name}-%{version}/build/src/pgagroal %{buildroot}%{_bindir}/pgagroal %{__install} -m 755 %{_builddir}/%{name}-%{version}/build/src/pgagroal-cli %{buildroot}%{_bindir}/pgagroal-cli %{__install} -m 755 %{_builddir}/%{name}-%{version}/build/src/pgagroal-admin %{buildroot}%{_bindir}/pgagroal-admin %{__install} -m 755 %{_builddir}/%{name}-%{version}/build/src/libpgagroal.so.%{version} %{buildroot}%{_libdir}/libpgagroal.so.%{version} chrpath -r %{_libdir} %{buildroot}%{_bindir}/pgagroal chrpath -r %{_libdir} %{buildroot}%{_bindir}/pgagroal-cli chrpath -r %{_libdir} %{buildroot}%{_bindir}/pgagroal-admin cd %{buildroot}%{_libdir}/ %{__ln_s} -f libpgagroal.so.%{version} libpgagroal.so.1 %{__ln_s} -f libpgagroal.so.1 libpgagroal.so %files %license %{_docdir}/%{name}/LICENSE %{_docdir}/%{name}/ARCHITECTURE.md %{_docdir}/%{name}/CLI.md %{_docdir}/%{name}/CODE_OF_CONDUCT.md %{_docdir}/%{name}/CONFIGURATION.md %{_docdir}/%{name}/FAILOVER.md %{_docdir}/%{name}/GETTING_STARTED.md %{_docdir}/%{name}/PERFORMANCE.md %{_docdir}/%{name}/PIPELINES.md %{_docdir}/%{name}/README.md %{_docdir}/%{name}/RPM.md %{_docdir}/%{name}/SECURITY.md %{_docdir}/%{name}/grafana/dashboard.json %{_docdir}/%{name}/grafana/README.md %{_docdir}/%{name}/etc/pgagroal.service %{_docdir}/%{name}/etc/pgagroal.socket %{_docdir}/%{name}/images/perf-extended.png %{_docdir}/%{name}/images/perf-prepared.png %{_docdir}/%{name}/images/perf-readonly.png %{_docdir}/%{name}/images/perf-simple.png %{_docdir}/%{name}/images/perf-simple.png %{_docdir}/%{name}/shell_comp/pgagroal_comp.bash %{_docdir}/%{name}/shell_comp/pgagroal_comp.zsh %{_docdir}/%{name}/tutorial/01_install.md %{_docdir}/%{name}/tutorial/02_prefill.md %{_docdir}/%{name}/tutorial/03_remote_management.md %{_docdir}/%{name}/tutorial/04_prometheus.md %{_docdir}/%{name}/tutorial/05_split_security.md %{_mandir}/man1/pgagroal.1* %{_mandir}/man1/pgagroal-admin.1* %{_mandir}/man1/pgagroal-cli.1* %{_mandir}/man5/pgagroal.conf.5* %{_mandir}/man5/pgagroal_databases.conf.5* %{_mandir}/man5/pgagroal_hba.conf.5* %config %{_sysconfdir}/pgagroal/pgagroal.conf %config %{_sysconfdir}/pgagroal/pgagroal_hba.conf %{_bindir}/pgagroal %{_bindir}/pgagroal-cli %{_bindir}/pgagroal-admin %{_libdir}/libpgagroal.so %{_libdir}/libpgagroal.so.1 %{_libdir}/libpgagroal.so.%{version} %changelog pgagroal-1.6.0/src/000077500000000000000000000000001456565230200141115ustar00rootroot00000000000000pgagroal-1.6.0/src/CMakeLists.txt000066400000000000000000000227411456565230200166570ustar00rootroot00000000000000# # Add files for libpgagroal # FILE(GLOB SOURCE_FILES "libpgagroal/*.c") FILE(GLOB HEADER_FILES "include/*.h") set(SOURCES ${SOURCE_FILES} ${HEADER_FILES}) # # OS # if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") add_compile_options(-DHAVE_LINUX) add_compile_options(-D_POSIX_C_SOURCE=200809L) # # Include directories # include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${SYSTEMD_INCLUDE_DIRS} ${CJSON_INCLUDE_DIRS} ) # # Library directories # link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY} ${SYSTEMD_LIBRARIES} ${LIBATOMIC_LIBRARY} ${CJSON_LIBRARIES} ) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") find_program(HOMEBREW_EXECUTABLE brew) if(EXISTS ${HOMEBREW_EXECUTABLE}) execute_process( COMMAND ${HOMEBREW_EXECUTABLE} --prefix OUTPUT_VARIABLE HOMEBREW_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Homebrew found at ${HOMEBREW_PREFIX}") endif() # # Detecting OpenSSL # execute_process( COMMAND ${HOMEBREW_EXECUTABLE} --prefix openssl OUTPUT_VARIABLE HOMEBREW_OPENSSL OUTPUT_STRIP_TRAILING_WHITESPACE) if(DEFINED HOMEBREW_OPENSSL) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${HOMEBREW_OPENSSL}/include") else() message(FATAL_ERROR "Homebrew's OpenSSL needed") endif() add_compile_options(-D_DARWIN_C_SOURCE) add_compile_options(-DHAVE_OSX) # # Include directories # include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} ${CJSON_INCLUDE_DIRS} ) # # Library directories # link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} ${CJSON_LIBRARIES} ) else() add_compile_options(-D_XOPEN_SOURCE=700) add_compile_options(-D_BSD_SOURCE) add_compile_options(-D_DEFAULT_SOURCE) add_compile_options(-D__BSD_VISIBLE) if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") add_compile_options(-DHAVE_OPENBSD) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") add_compile_options(-DHAVE_FREEBSD) endif() # # Include directories # include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} ${CJSON_INCLUDE_DIRS} ) # # Library directories # link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} ${CJSON_LIBRARIES} ) endif() # # Compile options # add_compile_options(-g) add_compile_options(-Wall) add_compile_options(-Werror) add_compile_options(-std=c17) add_compile_options(-D__USE_ISOC11) add_compile_options(-D_GNU_SOURCE) add_compile_options(-Wno-deprecated) add_compile_options(-Wno-deprecated-declarations) # # version number and string management # add_compile_options(-DPGAGROAL_MAJOR_VERSION=${VERSION_MAJOR}) add_compile_options(-DPGAGROAL_MINOR_VERSION=${VERSION_MINOR}) add_compile_options(-DPGAGROAL_PATCH_VERSION=${VERSION_PATCH}) add_compile_options(-DPGAGROAL_VERSION="${VERSION_STRING}") if(CMAKE_C_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wstrict-prototypes) endif() check_c_compiler_flag(-fPIC HAS_PIC) if (HAS_PIC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") include(CheckPIESupported) check_pie_supported() endif() if (CMAKE_BUILD_TYPE MATCHES Debug) add_compile_options(-O0) add_compile_options(-DDEBUG) check_c_compiler_flag(-Wformat HAS_FORMAT) if (HAS_FORMAT) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wformat") endif() check_c_compiler_flag(-Wformat-security HAS_FORMAT_SECURITY) if (HAS_FORMAT_SECURITY) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wformat-security") endif() check_c_compiler_flag(-fstack-protector-strong HAS_STACK_PROTECTOR_STRONG) if (HAS_STACK_PROTECTOR_STRONG) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fstack-protector-strong") endif() check_c_compiler_flag(-rdynamic HAS_DYNAMIC) if (HAS_DYNAMIC) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -rdynamic") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") endif() check_c_compiler_flag(-fstack-protector-strong HAS_STACKPROTECTOR_STRONG) if (HAS_STACKPROTECTOR_STRONG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong") else() check_c_compiler_flag(-fstack-protector HAS_STACKPROTECTOR) if (HAS_STACKPROTECTOR) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") endif() endif() check_c_compiler_flag(-Wl,arg HAS_LINKER) check_linker_flag(C "-z relro" LINKER_HAS_RELRO) if (HAS_LINKER AND LINKER_HAS_RELRO) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") endif() check_linker_flag(C "-z now" LINKER_HAS_NOW) if (HAS_LINKER AND LINKER_HAS_NOW) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,now") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") endif() if(CMAKE_C_COMPILER_ID STREQUAL "Clang") if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") add_compile_options(-fsanitize=address) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=address") endif() endif() check_c_compiler_flag(-fno-omit-frame-pointer HAS_NO_OMIT_FRAME_POINTER) if (HAS_NO_OMIT_FRAME_POINTER) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer") endif() endif() if (CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) add_compile_options(-O2) add_compile_options(-DNDEBUG) check_c_compiler_flag(-Wformat HAS_FORMAT) if (HAS_FORMAT) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wformat") endif() check_c_compiler_flag(-Wformat-security HAS_FORMAT_SECURITY) if (HAS_FORMAT_SECURITY) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wformat-security") endif() check_c_compiler_flag(-fstack-protector-strong HAS_STACK_PROTECTOR_STRONG) if (HAS_STACK_PROTECTOR_STRONG) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fstack-protector-strong") endif() check_c_compiler_flag(-rdynamic HAS_DYNAMIC) if (HAS_DYNAMIC) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -rdynamic") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") endif() check_c_compiler_flag(-fstack-protector-strong HAS_STACKPROTECTOR_STRONG) if (HAS_STACKPROTECTOR_STRONG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong") else() check_c_compiler_flag(-fstack-protector HAS_STACKPROTECTOR) if (HAS_STACKPROTECTOR) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") endif() endif() check_c_compiler_flag(-Wl,arg HAS_LINKER) check_linker_flag(C "-z relro" LINKER_HAS_RELRO) if (HAS_LINKER AND HAS_RELRO) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") endif() check_linker_flag(C "-z now" LINKER_HAS_NOW) if (HAS_LINKER AND LINKER_HAS_NOW) set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,now") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") endif() endif (CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) if (CMAKE_BUILD_TYPE MATCHES Performance) add_compile_options(-O2) add_compile_options(-DNDEBUG) if(CMAKE_C_COMPILER_ID STREQUAL "GNU") add_compile_options(-march=native) add_compile_options(-mtune=native) add_compile_options(-flto) endif() endif (CMAKE_BUILD_TYPE MATCHES Performance) # # Build libpgagroal # add_library(pgagroal SHARED ${SOURCES}) set_target_properties(pgagroal PROPERTIES LINKER_LANGUAGE C VERSION ${VERSION_STRING} SOVERSION ${VERSION_MAJOR}) target_link_libraries(pgagroal PUBLIC) install(TARGETS pgagroal DESTINATION ${CMAKE_INSTALL_LIBDIR}/) # # Build pgagroal # add_executable(pgagroal-bin main.c ${RESOURCE_OBJECT}) if (CMAKE_C_LINK_PIE_SUPPORTED) set_target_properties(pgagroal-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE TRUE OUTPUT_NAME pgagroal) else() set_target_properties(pgagroal-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE FALSE OUTPUT_NAME pgagroal) endif() target_link_libraries(pgagroal-bin pgagroal) install(TARGETS pgagroal-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) # # Build pgagroal-cli # add_executable(pgagroal-cli-bin cli.c ${RESOURCE_OBJECT}) if (CMAKE_C_LINK_PIE_SUPPORTED) set_target_properties(pgagroal-cli-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE TRUE OUTPUT_NAME pgagroal-cli) else() set_target_properties(pgagroal-cli-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE FALSE OUTPUT_NAME pgagroal-cli) endif() target_link_libraries(pgagroal-cli-bin pgagroal) install(TARGETS pgagroal-cli-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) # # Build pgagroal-admin # add_executable(pgagroal-admin-bin admin.c ${RESOURCE_OBJECT}) if (CMAKE_C_LINK_PIE_SUPPORTED) set_target_properties(pgagroal-admin-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE TRUE OUTPUT_NAME pgagroal-admin) else() set_target_properties(pgagroal-admin-bin PROPERTIES LINKER_LANGUAGE C POSITION_INDEPENDENT_CODE FALSE OUTPUT_NAME pgagroal-admin) endif() target_link_libraries(pgagroal-admin-bin pgagroal) install(TARGETS pgagroal-admin-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) pgagroal-1.6.0/src/admin.c000066400000000000000000000523441456565230200153550ustar00rootroot00000000000000/* * 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 #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_PASSWORD_LENGTH 64 #define MIN_PASSWORD_LENGTH 8 #define ACTION_UNKNOWN 0 #define ACTION_MASTER_KEY 1 #define ACTION_ADD_USER 2 #define ACTION_UPDATE_USER 3 #define ACTION_REMOVE_USER 4 #define ACTION_LIST_USERS 5 static char CHARS[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{', ']', '}', '\\', '|', ';', ':', '\'', '\"', ',', '<', '.', '>', '/', '?'}; static int master_key(char* password, bool generate_pwd, int pwd_length); static int add_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length); static int update_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length); static int remove_user(char* users_path, char* username); static int list_users(char* users_path); static char* generate_password(int pwd_length); static void version(void) { printf("pgagroal-admin %s\n", PGAGROAL_VERSION); exit(1); } static void usage(void) { printf("pgagroal-admin %s\n", PGAGROAL_VERSION); printf(" Administration utility for pgagroal\n"); printf("\n"); printf("Usage:\n"); printf(" pgagroal-admin [ -f FILE ] [ COMMAND ] \n"); printf("\n"); printf("Options:\n"); printf(" -f, --file FILE Set the path to a user file\n"); printf(" Defaults to %s", PGAGROAL_DEFAULT_USERS_FILE); printf(" -U, --user USER Set the user name\n"); printf(" -P, --password PASSWORD Set the password for the user\n"); printf(" -g, --generate Generate a password\n"); printf(" -l, --length Password length\n"); printf(" -V, --version Display version information\n"); printf(" -?, --help Display help\n"); printf("\n"); printf("Commands:\n"); printf(" master-key Create or update the master key\n"); printf(" user Manage a specific user, where can be\n"); printf(" - add to add a new user\n"); printf(" - del to remove an existing user\n"); printf(" - edit to change the password for an existing user\n"); printf(" - ls to list all available users\n"); printf("\n"); printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); printf("Report bugs: %s\n", PGAGROAL_ISSUES); } int main(int argc, char** argv) { int c; char* username = NULL; char* password = NULL; char* file_path = NULL; bool generate_pwd = false; int pwd_length = DEFAULT_PASSWORD_LENGTH; int option_index = 0; int32_t action = ACTION_UNKNOWN; while (1) { static struct option long_options[] = { {"user", required_argument, 0, 'U'}, {"password", required_argument, 0, 'P'}, {"file", required_argument, 0, 'f'}, {"generate", no_argument, 0, 'g'}, {"length", required_argument, 0, 'l'}, {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, '?'} }; c = getopt_long(argc, argv, "gV?f:U:P:l:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'U': username = optarg; break; case 'P': password = optarg; break; case 'f': file_path = optarg; break; case 'g': generate_pwd = true; break; case 'l': pwd_length = atoi(optarg); break; case 'V': version(); break; case '?': usage(); exit(1); break; default: break; } } if (getuid() == 0) { errx(1, "Using the root account is not allowed"); } if (argc > 0) { if (!strcmp("master-key", argv[argc - 1])) { action = ACTION_MASTER_KEY; } else if (parse_command_simple(argc, argv, optind, "user", "add") || parse_deprecated_command(argc, argv, optind, "add-user", NULL, "user add", 1, 6)) { action = ACTION_ADD_USER; } else if (parse_command_simple(argc, argv, optind, "user", "edit") || parse_deprecated_command(argc, argv, optind, "update-user", NULL, "user edit", 1, 6)) { action = ACTION_UPDATE_USER; } else if (parse_command_simple(argc, argv, optind, "user", "del") || parse_deprecated_command(argc, argv, optind, "remove-user", NULL, "user del", 1, 6)) { action = ACTION_REMOVE_USER; } else if (parse_command_simple(argc, argv, optind, "user", "ls") || parse_deprecated_command(argc, argv, optind, "list-users", NULL, "user ls", 1, 6)) { action = ACTION_LIST_USERS; } // exit immediatly if the action is not understood! if (action == ACTION_UNKNOWN) { warnx("unknown command or subcommand <%s>", argv[optind]); usage(); goto error; } // if here, the action is understood, but we need // the file to oeprate onto! // Therefore, if the user did not specify any config file // the default one is used. Note that in the case of ACTION_MASTER_KEY // there is no need for the file_path to be set, so setting to a default // value does nothing. // Setting the file also means we don't have to check against the file_path value. if (file_path == NULL) { file_path = PGAGROAL_DEFAULT_USERS_FILE; } if (action == ACTION_MASTER_KEY) { if (master_key(password, generate_pwd, pwd_length)) { errx(1, "Error for master key"); } } else if (action == ACTION_ADD_USER) { if (add_user(file_path, username, password, generate_pwd, pwd_length)) { errx(1, "Error for "); } } else if (action == ACTION_UPDATE_USER) { if (update_user(file_path, username, password, generate_pwd, pwd_length)) { errx(1, "Error for "); } } else if (action == ACTION_REMOVE_USER) { if (remove_user(file_path, username)) { errx(1, "Error for "); } } else if (action == ACTION_LIST_USERS) { if (list_users(file_path)) { errx(1, "Error for "); } } } exit(0); error: exit(1); } static int master_key(char* password, bool generate_pwd, int pwd_length) { FILE* file = NULL; char buf[MISC_LENGTH]; char* encoded = NULL; struct stat st = {0}; bool do_free = true; if (pgagroal_get_home_directory() == NULL) { char* username = pgagroal_get_user_name(); if (username != NULL) { warnx("No home directory for user \'%s\'", username); } else { warnx("No home directory for user running pgagroal"); } goto error; } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/.pgagroal", pgagroal_get_home_directory()); if (stat(&buf[0], &st) == -1) { mkdir(&buf[0], S_IRWXU); } else { if (S_ISDIR(st.st_mode) && st.st_mode & S_IRWXU && !(st.st_mode & S_IRWXG) && !(st.st_mode & S_IRWXO)) { /* Ok */ } else { warnx("Wrong permissions for ~/.pgagroal (must be 0700)"); goto error; } } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/.pgagroal/master.key", pgagroal_get_home_directory()); if (stat(&buf[0], &st) == -1) { /* Ok */ } else { if (S_ISREG(st.st_mode) && st.st_mode & (S_IRUSR | S_IWUSR) && !(st.st_mode & S_IRWXG) && !(st.st_mode & S_IRWXO)) { /* Ok */ } else { warnx("Wrong permissions for ~/.pgagroal/master.key (must be 0600)"); goto error; } } file = fopen(&buf[0], "w+"); if (file == NULL) { warnx("Could not write to master key file <%s>", &buf[0]); goto error; } if (password == NULL) { if (!generate_pwd) { while (password == NULL) { printf("Master key (will not echo): "); password = pgagroal_get_password(); printf("\n"); if (password != NULL && strlen(password) < MIN_PASSWORD_LENGTH) { printf("Invalid key length, must be at least %d chars.\n", MIN_PASSWORD_LENGTH); free(password); password = NULL; } } } else { password = generate_password(pwd_length); do_free = false; } } else { do_free = false; } pgagroal_base64_encode(password, strlen(password), &encoded); fputs(encoded, file); free(encoded); if (do_free) { free(password); } fclose(file); chmod(&buf[0], S_IRUSR | S_IWUSR); printf("Master Key stored into %s\n", &buf[0]); return 0; error: free(encoded); if (do_free) { free(password); } if (file) { fclose(file); } return 1; } static int add_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length) { FILE* users_file = NULL; char line[MISC_LENGTH]; char* entry = NULL; char* master_key = NULL; char* ptr = NULL; char* encrypted = NULL; int encrypted_length = 0; char* encoded = NULL; char un[MAX_USERNAME_LENGTH]; int number_of_users = 0; bool do_verify = true; char* verify = NULL; bool do_free = true; if (pgagroal_get_master_key(&master_key)) { warnx("Invalid master key"); goto error; } if (password != NULL) { do_verify = false; do_free = false; } users_file = fopen(users_path, "a+"); if (users_file == NULL) { warnx("Could not append to users file <%s>", users_path); goto error; } /* User */ if (username == NULL) { username: printf("User name: "); memset(&un, 0, sizeof(un)); if (fgets(&un[0], sizeof(un), stdin) == NULL) { goto error; } un[strlen(un) - 1] = 0; username = &un[0]; } if (username == NULL || strlen(username) == 0) { goto username; } /* Verify */ while (fgets(line, sizeof(line), users_file)) { ptr = strtok(line, ":"); if (!strcmp(username, ptr)) { warnx("Existing user: %s", username); goto error; } number_of_users++; } if (number_of_users > NUMBER_OF_USERS) { warnx("Too many users"); goto error; } /* Password */ if (password == NULL) { password: if (generate_pwd) { password = generate_password(pwd_length); do_verify = false; printf("Password : %s", password); } else { printf("Password : "); if (password != NULL) { free(password); password = NULL; } password = pgagroal_get_password(); } printf("\n"); } for (int i = 0; i < strlen(password); i++) { if ((unsigned char)(*(password + i)) & 0x80) { goto password; } } if (do_verify) { printf("Verify : "); if (verify != NULL) { free(verify); verify = NULL; } verify = pgagroal_get_password(); printf("\n"); if (strlen(password) != strlen(verify) || memcmp(password, verify, strlen(password)) != 0) { goto password; } } pgagroal_encrypt(password, master_key, &encrypted, &encrypted_length); pgagroal_base64_encode(encrypted, encrypted_length, &encoded); entry = pgagroal_append(entry, username); entry = pgagroal_append(entry, ":"); entry = pgagroal_append(entry, encoded); entry = pgagroal_append(entry, "\n"); fputs(entry, users_file); free(entry); free(master_key); free(encrypted); free(encoded); if (do_free) { free(password); } free(verify); fclose(users_file); return 0; error: free(entry); free(master_key); free(encrypted); free(encoded); if (do_free) { free(password); } free(verify); if (users_file) { fclose(users_file); } return 1; } static int update_user(char* users_path, char* username, char* password, bool generate_pwd, int pwd_length) { FILE* users_file = NULL; FILE* users_file_tmp = NULL; char tmpfilename[MISC_LENGTH]; char line[MISC_LENGTH]; char line_copy[MISC_LENGTH]; char* master_key = NULL; char* ptr = NULL; char* entry = NULL; char* encrypted = NULL; int encrypted_length = 0; char* encoded = NULL; char un[MAX_USERNAME_LENGTH]; bool found = false; bool do_verify = true; char* verify = NULL; bool do_free = true; memset(&tmpfilename, 0, sizeof(tmpfilename)); if (pgagroal_get_master_key(&master_key)) { warnx("Invalid master key"); goto error; } if (password != NULL) { do_verify = false; do_free = false; } users_file = fopen(users_path, "r"); if (!users_file) { warnx("File <%s> not found", users_path); goto error; } snprintf(tmpfilename, sizeof(tmpfilename), "%s.tmp", users_path); users_file_tmp = fopen(tmpfilename, "w+"); if (users_file_tmp == NULL) { warnx("Could not write to temporary user file <%s>", tmpfilename); goto error; } /* User */ if (username == NULL) { username: printf("User name: "); memset(&un, 0, sizeof(un)); if (fgets(&un[0], sizeof(un), stdin) == NULL) { goto error; } un[strlen(un) - 1] = 0; username = &un[0]; } if (username == NULL || strlen(username) == 0) { goto username; } /* Update */ while (fgets(line, sizeof(line), users_file)) { memset(&line_copy, 0, sizeof(line_copy)); memcpy(&line_copy, &line, strlen(line)); ptr = strtok(line, ":"); if (!strcmp(username, ptr)) { /* Password */ if (password == NULL) { password: if (generate_pwd) { password = generate_password(pwd_length); do_verify = false; printf("Password : %s", password); } else { printf("Password : "); if (password != NULL) { free(password); password = NULL; } password = pgagroal_get_password(); } printf("\n"); } for (int i = 0; i < strlen(password); i++) { if ((unsigned char)(*(password + i)) & 0x80) { goto password; } } if (do_verify) { printf("Verify : "); if (verify != NULL) { free(verify); verify = NULL; } verify = pgagroal_get_password(); printf("\n"); if (strlen(password) != strlen(verify) || memcmp(password, verify, strlen(password)) != 0) { goto password; } } pgagroal_encrypt(password, master_key, &encrypted, &encrypted_length); pgagroal_base64_encode(encrypted, encrypted_length, &encoded); entry = NULL; entry = pgagroal_append(entry, username); entry = pgagroal_append(entry, ":"); entry = pgagroal_append(entry, encoded); entry = pgagroal_append(entry, "\n"); fputs(entry, users_file_tmp); free(entry); found = true; } else { fputs(line_copy, users_file_tmp); } } if (!found) { warnx("User '%s' not found", username); goto error; } free(master_key); free(encrypted); free(encoded); if (do_free) { free(password); } free(verify); fclose(users_file); fclose(users_file_tmp); rename(tmpfilename, users_path); return 0; error: free(master_key); free(encrypted); free(encoded); if (do_free) { free(password); } free(verify); if (users_file) { fclose(users_file); } if (users_file_tmp) { fclose(users_file_tmp); } if (strlen(tmpfilename) > 0) { remove(tmpfilename); } return 1; } static int remove_user(char* users_path, char* username) { FILE* users_file = NULL; FILE* users_file_tmp = NULL; char tmpfilename[MISC_LENGTH]; char line[MISC_LENGTH]; char line_copy[MISC_LENGTH]; char* ptr = NULL; char un[MAX_USERNAME_LENGTH]; bool found = false; users_file = fopen(users_path, "r"); if (!users_file) { warnx("File <%s> not found", users_path); goto error; } memset(&tmpfilename, 0, sizeof(tmpfilename)); snprintf(tmpfilename, sizeof(tmpfilename), "%s.tmp", users_path); users_file_tmp = fopen(tmpfilename, "w+"); if (users_file_tmp == NULL) { warnx("Could not write to temporary user file <%s>", tmpfilename); goto error; } /* User */ if (username == NULL) { username: printf("User name: "); memset(&un, 0, sizeof(un)); if (fgets(&un[0], sizeof(un), stdin) == NULL) { goto error; } un[strlen(un) - 1] = 0; username = &un[0]; } if (username == NULL || strlen(username) == 0) { goto username; } /* Remove */ while (fgets(line, sizeof(line), users_file)) { memset(&line_copy, 0, sizeof(line_copy)); memcpy(&line_copy, &line, strlen(line)); ptr = strtok(line, ":"); if (!strcmp(username, ptr)) { found = true; } else { fputs(line_copy, users_file_tmp); } } if (!found) { warnx("User '%s' not found", username); goto error; } fclose(users_file); fclose(users_file_tmp); rename(tmpfilename, users_path); return 0; error: if (users_file) { fclose(users_file); } if (users_file_tmp) { fclose(users_file_tmp); } if (strlen(tmpfilename) > 0) { remove(tmpfilename); } return 1; } static int list_users(char* users_path) { FILE* users_file = NULL; char line[MISC_LENGTH]; char* ptr = NULL; users_file = fopen(users_path, "r"); if (!users_file) { goto error; } /* List */ while (fgets(line, sizeof(line), users_file)) { ptr = strtok(line, ":"); printf("%s\n", ptr); } fclose(users_file); return 0; error: if (users_file) { fclose(users_file); } return 1; } static char* generate_password(int pwd_length) { char* pwd; size_t s; time_t t; s = pwd_length + 1; pwd = calloc(1, s); if (pwd == NULL) { pgagroal_log_fatal("Couldn't allocate memory while generating password"); return NULL; } srand((unsigned)time(&t)); for (int i = 0; i < s; i++) { *((char*)(pwd + i)) = CHARS[rand() % sizeof(CHARS)]; } *((char*)(pwd + pwd_length)) = '\0'; return pwd; } pgagroal-1.6.0/src/cli.c000066400000000000000000000706501456565230200150340ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #define ACTION_UNKNOWN 0 #define ACTION_FLUSH 1 #define ACTION_GRACEFULLY 2 #define ACTION_STOP 3 #define ACTION_STATUS 4 #define ACTION_STATUS_DETAILS 5 #define ACTION_ISALIVE 6 #define ACTION_CANCELSHUTDOWN 7 #define ACTION_ENABLEDB 8 #define ACTION_DISABLEDB 9 #define ACTION_RESET 10 #define ACTION_RESET_SERVER 11 #define ACTION_SWITCH_TO 12 #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); 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, 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_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) { printf("pgagroal-cli %s\n", PGAGROAL_VERSION); exit(1); } static void usage(void) { printf("pgagroal-cli %s\n", PGAGROAL_VERSION); printf(" Command line utility for pgagroal\n"); printf("\n"); printf("Usage:\n"); printf(" pgagroal-cli [ OPTIONS ] [ COMMAND ] \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(" -h, --host HOST Set the host name\n"); printf(" -p, --port PORT Set the port number\n"); 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(" 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(" 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(" - '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"); printf(" status [details] Status of pgagroal, with optional details\n"); printf(" switch-to Switches to the specified primary server\n"); printf(" conf Manages the configuration (e.g., reloads the configuration\n"); 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(" - 'set' to modify a configuration value;\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"); printf(" - a server name on its own\n"); printf(" - 'prometheus' to reset the Prometheus metrics\n"); printf("\n"); printf("pgagroal: <%s>\n", PGAGROAL_HOMEPAGE); printf("Report bugs: <%s>\n", PGAGROAL_ISSUES); } int main(int argc, char** argv) { int socket = -1; SSL* s_ssl = NULL; int ret; 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; int c; int option_index = 0; size_t size; int32_t action = ACTION_UNKNOWN; 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 */ char output_format = COMMAND_OUTPUT_FORMAT_TEXT; 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'}, {"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:F:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': configuration_path = optarg; break; case 'h': host = optarg; break; case 'p': port = optarg; break; case 'U': username = optarg; break; case 'P': password = optarg; break; 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; case 'V': version(); break; case '?': usage(); exit(1); break; default: break; } } if (getuid() == 0) { 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) { errx(1, "you need also -h and -p options to perform a remote connection"); } // 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); if (pgagroal_create_shared_memory(size, HUGEPAGE_OFF, &shmem)) { errx(1, "Error creating shared memory"); } pgagroal_init_configuration(shmem); if (configuration_path != NULL) { ret = pgagroal_read_configuration(shmem, configuration_path, false); if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) { errx(1, "Configuration not found: <%s>", configuration_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { errx(1, "Too many sections in the configuration file <%s>", configuration_path); } if (logfile) { 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))); } if (pgagroal_start_logging()) { errx(1, "Cannot start the logging subsystem"); } config = (struct configuration*)shmem; } else { 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; if (logfile) { 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))); } if (pgagroal_start_logging()) { errx(1, "Cannot start the logging subsystem"); } config = (struct configuration*)shmem; } } if (parse_command(argc, argv, optind, "flush", "idle", &database, "*", NULL, NULL) || parse_deprecated_command(argc, argv, optind, "flush-idle", &database, "flush idle", 1, 6)) { mode = FLUSH_IDLE; action = ACTION_FLUSH; pgagroal_log_trace("Command: [%s]", database); } else if (parse_command(argc, argv, optind, "flush", "all", &database, "*", NULL, NULL) || parse_deprecated_command(argc, argv, optind, "flush-all", &database, "flush all", 1, 6)) { mode = FLUSH_ALL; action = ACTION_FLUSH; pgagroal_log_trace("Command: [%s]", database); } else if (parse_command(argc, argv, optind, "flush", "gracefully", &database, "*", NULL, NULL) || parse_command(argc, argv, optind, "flush", NULL, &database, "*", NULL, NULL) || parse_deprecated_command(argc, argv, optind, "flush-gracefully", &database, "flush", 1, 6)) { mode = FLUSH_GRACEFULLY; action = ACTION_FLUSH; pgagroal_log_trace("Command: [%s]", database); } else if (parse_command(argc, argv, optind, "enable", NULL, &database, "*", NULL, NULL)) { action = ACTION_ENABLEDB; pgagroal_log_trace("Command: [%s]", database); } else if (parse_command(argc, argv, optind, "disable", NULL, &database, "*", NULL, NULL)) { action = ACTION_DISABLEDB; pgagroal_log_trace("Command: [%s]", database); } else if (parse_command_simple(argc, argv, optind, "shutdown", "immediate") || parse_deprecated_command(argc, argv, optind, "stop", NULL, "shutdown immediate", 1, 6)) { action = ACTION_STOP; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "shutdown", "cancel") || parse_deprecated_command(argc, argv, optind, "cancel-shutdown", NULL, "shutdown cancel", 1, 6)) { action = ACTION_CANCELSHUTDOWN; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "shutdown", "gracefully") || parse_command_simple(argc, argv, optind, "shutdown", NULL) || parse_deprecated_command(argc, argv, optind, "gracefully", NULL, "shutdown gracefully", 1, 6)) { action = ACTION_GRACEFULLY; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "status", "details") || parse_deprecated_command(argc, argv, optind, "details", NULL, "status details", 1, 6)) { /* the 'status details' has to be parsed before the normal 'status' command !*/ action = ACTION_STATUS_DETAILS; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "status", NULL)) { action = ACTION_STATUS; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "ping", NULL) || parse_deprecated_command(argc, argv, optind, "is-alive", NULL, "ping", 1, 6)) { action = ACTION_ISALIVE; pgagroal_log_trace("Command: "); } else if (parse_command_simple(argc, argv, optind, "clear", "prometheus") || parse_deprecated_command(argc, argv, optind, "reset", NULL, "clear prometheus", 1, 6)) { action = ACTION_RESET; pgagroal_log_trace("Command: "); } else if (parse_command(argc, argv, optind, "clear", "server", &server, "\0", NULL, NULL) || parse_command(argc, argv, optind, "clear", NULL, &server, "\0", NULL, NULL) || parse_deprecated_command(argc, argv, optind, "reset-server", &server, "clear server", 1, 6)) { action = strlen(server) > 0 ? ACTION_RESET_SERVER : ACTION_UNKNOWN; pgagroal_log_trace("Command: [%s]", server); } else if (parse_command(argc, argv, optind, "switch-to", NULL, &server, "\0", NULL, NULL)) { action = strlen(server) > 0 ? ACTION_SWITCH_TO : ACTION_UNKNOWN; pgagroal_log_trace("Command: [%s]", server); } else if (parse_command_simple(argc, argv, optind, "conf", "reload") || parse_deprecated_command(argc, argv, optind, "reload", NULL, "conf reload", 1, 6)) { /* Local connection only */ if (configuration_path != NULL) { action = ACTION_RELOAD; } 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_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_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) { if (!remote_connection) { /* Local connection */ if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &socket)) { exit_code = 1; goto done; } } else { /* Remote connection */ if (pgagroal_connect(host, atoi(port), &socket)) { /* Remote connection */ l_port = strtol(port, NULL, 10); if ((errno == ERANGE && (l_port == LONG_MAX || l_port == LONG_MIN)) || (errno != 0 && l_port == 0)) { warnx("Specified port %s out of range", port); goto done; } // cannot connect to port less than 1024 because pgagroal // cannot be run as root! if (l_port <= 1024) { warnx("Not allowed port %ld", l_port); goto done; } if (pgagroal_connect(host, (int)l_port, &socket)) { warnx("No route to host: %s:%ld\n", host, l_port); goto done; } } /* User name */ if (username == NULL) { username: printf("User name: "); memset(&un, 0, sizeof(un)); if (fgets(&un[0], sizeof(un), stdin) == NULL) { exit_code = 1; goto done; } un[strlen(un) - 1] = 0; username = &un[0]; } if (username == NULL || strlen(username) == 0) { goto username; } /* Password */ if (password == NULL) { if (password != NULL) { free(password); password = NULL; } printf("Password : "); password = pgagroal_get_password(); printf("\n"); } else { do_free = false; } for (int i = 0; i < strlen(password); i++) { if ((unsigned char)(*(password + i)) & 0x80) { warnx("Bad credentials for %s\n", username); goto done; } } /* Authenticate */ if (pgagroal_remote_management_scram_sha256(username, password, socket, &s_ssl) != AUTH_SUCCESS) { printf("pgagroal-cli: Bad credentials for %s\n", username); goto done; } } } if (action == ACTION_FLUSH) { exit_code = flush(s_ssl, socket, mode, database); } else if (action == ACTION_ENABLEDB) { exit_code = enabledb(s_ssl, socket, database); } else if (action == ACTION_DISABLEDB) { exit_code = disabledb(s_ssl, socket, database); } else if (action == ACTION_GRACEFULLY) { exit_code = gracefully(s_ssl, socket); } else if (action == ACTION_STOP) { exit_code = stop(s_ssl, socket); } else if (action == ACTION_CANCELSHUTDOWN) { exit_code = cancel_shutdown(s_ssl, socket); } else if (action == ACTION_STATUS) { exit_code = status(s_ssl, socket, output_format); } else if (action == ACTION_STATUS_DETAILS) { exit_code = details(s_ssl, socket, output_format); } else if (action == ACTION_ISALIVE) { exit_code = isalive(s_ssl, socket, output_format); } else if (action == ACTION_RESET) { exit_code = reset(s_ssl, socket); } else if (action == ACTION_RESET_SERVER) { exit_code = reset_server(s_ssl, socket, server); } else if (action == ACTION_SWITCH_TO) { exit_code = switch_to(s_ssl, socket, server); } else if (action == ACTION_RELOAD) { exit_code = reload(s_ssl, socket); } else if (action == ACTION_CONFIG_GET) { 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, output_format); } else if (action == ACTION_CONFIG_LS) { exit_code = config_ls(s_ssl, socket, output_format); } done: if (s_ssl != NULL) { int res; SSL_CTX* ctx = SSL_get_SSL_CTX(s_ssl); res = SSL_shutdown(s_ssl); if (res == 0) { SSL_shutdown(s_ssl); } SSL_free(s_ssl); SSL_CTX_free(ctx); } pgagroal_disconnect(socket); if (action == ACTION_UNKNOWN) { printf("pgagroal-cli: unknown command %s\n", argv[optind]); usage(); exit_code = 1; } if (configuration_path != NULL) { if (action != ACTION_UNKNOWN) { switch (exit_code) { case EXIT_STATUS_CONNECTION_ERROR: printf("Connection error on %s\n", config->unix_socket_dir); break; case EXIT_STATUS_DATA_ERROR: case EXIT_STATUS_OK: break; } } } pgagroal_stop_logging(); pgagroal_destroy_shared_memory(shmem, size); if (do_free) { free(password); } if (verbose) { warnx("%s (%d)", exit_code == EXIT_STATUS_OK ? "Success" : "Error", exit_code); } return exit_code; } static int flush(SSL* ssl, int socket, int32_t mode, char* database) { if (pgagroal_management_flush(ssl, socket, mode, database)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int enabledb(SSL* ssl, int socket, char* database) { if (pgagroal_management_enabledb(ssl, socket, database)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int disabledb(SSL* ssl, int socket, char* database) { if (pgagroal_management_disabledb(ssl, socket, database)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int gracefully(SSL* ssl, int socket) { if (pgagroal_management_gracefully(ssl, socket)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int stop(SSL* ssl, int socket) { if (pgagroal_management_stop(ssl, socket)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int cancel_shutdown(SSL* ssl, int socket) { if (pgagroal_management_cancel_shutdown(ssl, socket)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int status(SSL* ssl, int socket, char output_format) { if (pgagroal_management_status(ssl, socket) == 0) { return pgagroal_management_read_status(ssl, socket, output_format); } else { return EXIT_STATUS_CONNECTION_ERROR; } } static int details(SSL* ssl, int socket, char output_format) { if (pgagroal_management_details(ssl, socket) == 0) { return pgagroal_management_read_details(ssl, socket, output_format); } // if here, an error occurred return EXIT_STATUS_CONNECTION_ERROR; } static int 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, output_format)) { return EXIT_STATUS_CONNECTION_ERROR; } if (status != PING_STATUS_RUNNING && status != PING_STATUS_SHUTDOWN_GRACEFULLY) { return EXIT_STATUS_CONNECTION_ERROR; } } else { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int reset(SSL* ssl, int socket) { if (pgagroal_management_reset(ssl, socket)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int reset_server(SSL* ssl, int socket, char* server) { if (pgagroal_management_reset_server(ssl, socket, server)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int switch_to(SSL* ssl, int socket, char* server) { if (pgagroal_management_switch_to(ssl, socket, server)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } static int reload(SSL* ssl, int socket) { if (pgagroal_management_reload(ssl, socket)) { return EXIT_STATUS_CONNECTION_ERROR; } return EXIT_STATUS_OK; } /** * Entry point for a config-get command line action. * * First it sends the message to pgagroal process to execute a config-get, * then reads back the answer. * * @param ssl the SSL mode * @param socket the socket file descriptor * @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, char output_format) { if (!config_key || strlen(config_key) > MISC_LENGTH) { goto error; } if (pgagroal_management_config_get(ssl, socket, config_key)) { goto error; } if (pgagroal_management_read_config_get(socket, config_key, NULL, verbose, output_format)) { goto error; } return EXIT_STATUS_OK; error: return EXIT_STATUS_CONNECTION_ERROR; } /** * Entry point for a config-set command. * * The function requires the configuration parameter to set and its value. * It then sends the command over the socket and reads the answer back. * * @param ssl the SSL connection * @param socket the socket to use * @param config_key the parameter name to set * @param config_value the value to set the parameter to * @param verbose if true the system will print back the new value of the configuration parameter * @return 0 on success */ static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose, char output_format) { int status = EXIT_STATUS_OK; if (!config_key || strlen(config_key) > MISC_LENGTH || !config_value || strlen(config_value) > MISC_LENGTH) { goto error; } if (pgagroal_management_config_set(ssl, socket, config_key, config_value)) { goto error; } status = pgagroal_management_read_config_get(socket, config_key, config_value, verbose, output_format); 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) { if (pgagroal_management_conf_ls(ssl, socket)) { goto error; } if (pgagroal_management_read_conf_ls(ssl, socket, output_format)) { goto error; } return EXIT_STATUS_OK; error: return EXIT_STATUS_CONNECTION_ERROR; } pgagroal-1.6.0/src/include/000077500000000000000000000000001456565230200155345ustar00rootroot00000000000000pgagroal-1.6.0/src/include/configuration.h000066400000000000000000000334131456565230200205600ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_CONFIGURATION_H #define PGAGROAL_CONFIGURATION_H #ifdef __cplusplus extern "C" { #endif #include /* * The main section that must be present in the `pgagroal.conf` * configuration file. */ #define PGAGROAL_MAIN_INI_SECTION "pgagroal" /* * The following constants are used to clearly identify * a section the user wants to get configuration * or change. They are used in the config-get * and config-set operations. */ #define PGAGROAL_CONF_SERVER_PREFIX "server" #define PGAGROAL_CONF_HBA_PREFIX "hba" #define PGAGROAL_CONF_LIMIT_PREFIX "limit" /** * Status that pgagroal_read_configuration() could provide. * Use only negative values for errors, since a positive return * value will indicate the number of problems within sections. */ #define PGAGROAL_CONFIGURATION_STATUS_OK 0 #define PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND -1 #define PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG -2 #define PGAGROAL_CONFIGURATION_STATUS_KO -3 #define PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT -4 /** * Initialize the configuration structure * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_init_configuration(void* shmem); /** * Read the configuration 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_read_configuration(void* shmem, char* filename, bool emitWarnings); /** * Validate the configuration * @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_validate_configuration(void* shmem, bool has_unix_socket, bool has_main_sockets); /** * Read the HBA configuration from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many entries */ int pgagroal_read_hba_configuration(void* shmem, char* filename); /** * Validate the HBA configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_hba_configuration(void* shmem); /** * Read the LIMIT configuration from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many limits */ int pgagroal_read_limit_configuration(void* shmem, char* filename); /** * Validate the LIMIT configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_limit_configuration(void* shmem); /** * Read the USERS configuration from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many users * (i.e., more users than the number defined in the limits) * - PGAGROAL_CONFIGURATION_STATUS_KO if the file has some problem (e.g., cannot be decrypted) * - PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT to indicate a problem reading the content of the file */ int pgagroal_read_users_configuration(void* shmem, char* filename); /** * Validate the USERS configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_users_configuration(void* shmem); /** * Read the FRONTEND USERS configuration from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many users * (i.e., more users than the number defined in the limits) * - PGAGROAL_CONFIGURATION_STATUS_KO if the file has some problem (e.g., cannot be decrypted) * - PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT to indicate a problem reading the content of the file */ int pgagroal_read_frontend_users_configuration(void* shmem, char* filename); /** * Validate the FRONTEND USERS configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_frontend_users_configuration(void* shmem); /** * Read the ADMINS configuration from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file contains too many users * (i.e., more users than the number defined in the limits) * - PGAGROAL_CONFIGURATION_STATUS_KO if the file has some problem (e.g., cannot be decrypted) * - PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT to indicate a problem reading the content of the file */ int pgagroal_read_admins_configuration(void* shmem, char* filename); /** * Validate the ADMINS configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_admins_configuration(void* shmem); /** * Read the superuser from a file * @param shmem The shared memory segment * @param filename The file name * @return 0 (i.e, PGAGROAL_CONFIGURATION_STATUS_OK) upon success, otherwise * - PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND if the file does not exists * - PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG if the file entry is to big * - PGAGROAL_CONFIGURATION_STATUS_KO if the file has some problem (e.g., cannot be decrypted) * - PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT to indicate a problem reading the content of the file */ int pgagroal_read_superuser_configuration(void* shmem, char* filename); /** * Validate the SUPERUSER configuration from a file * @param shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_validate_superuser_configuration(void* shmem); /** * Reload the configuration * @return 0 upon success, otherwise 1 */ int pgagroal_reload_configuration(void); /** * Automatically initialize the 'pidfile' * if none has been specified. * This function is called as last step * from pgagroal_validate_configuration * because it builds the pidfile on the value * of unix_socket_dir. */ void pgagroal_init_pidfile_if_needed(void); /** * Checks if the configuration has a min set of values to * take into account doing a prefill. For example, there must * be users and limits set, otherwise it does not * make any sense to attempt a prefill. * This can be used to wrap the condituion before calling * other prefill functions, e.g., `pgagroal_prefill()`. */ bool pgagroal_can_prefill(void); /** * Gets a configuration parameter and places into the string pointer. * This is used, for example, to get a writable string to send over the * management socket. * * The key can contain words separated by a dot '.' to indicate different search criterias. * A "dotted" key is made by a 'context', a 'section' and a 'search term', so that * it can be written as 'section.context.search'. * If both the section and the context are omitted, the 'search' is performed among the * pgagroal global settings (i.e., those under the [pgagroal] main section). The same * happens if the the section is specified as 'pgagroal', therefore the following two * terms do the same search: * - `update_process_title` * - `pgagroal.update_process_title` * * Other possible sections are: * - server to search for a specific server, the match is performed on the server name; * - hba to search for a specific HBA entry, the match is performed on the username; * - limit to search for a specific database in the limit (database) configuration file. * * When one the above sections is specified, the search is done identifying the entry to snoop * by means of 'context', and within such the 'search' is performed. * * In the case of the `server` section, the `context` has to be the name of a server configured, * while the `search` has to be the keyword to look for. AS an example: `server.venkman.port` provides * the value of the 'port' setting under the server section '[venkman]'. * * In the case of the 'hba` section, the `context` has to be a username as it appears in a line * of the pgagroal_hba.conf file, while the `search` has to be the column keyword to snoop. * For example, `hba.luca.method` will seek for the `method` used to authenticate the user `luca`. * Please note that, since the same user could be listed more than once, only the first matching * entry is reported. * * In the case of the 'limit` section, the `context` has to be a database name as it appears in a line * of the pgagroal_database.conf file, while the `search` has to be the column keyword to snoop. * For example, `limit.pgbench.max_size` will seek for the `max_size` connection limit for the * database 'pgbench'. * Please note that, since the same database could be listed more than once, only the first matching * entry is reported. * * @param buffer where to write the configuration value. The buffer must * be already allocated. In case of failure, the buffer is zero filled. * @param config_key the name of the configuration parameter * @param buffer_size the max length of the buffer where the result will be stored * @return 0 on success, 1 when the key cannot be found */ int pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size); /** * 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_main_configuration( config, NULL, PGAGROAL_MAIN_INI_SECTION, "log_level", "info" ); */ int pgagroal_apply_main_configuration(struct configuration* config, struct server* srv, char* section, char* key, char* value); /** * Function to set a configuration value. * * This function accepts the same prefixes as the configuration get behavior, so * a single parameter like 'max_connections' is managed as the main configuration file, * a 'server' prefix will hit a specific server, a 'limit' prefix will set a limit, and so on. * * The idea behind the function is to "clone" the current configuration in use, and then * apply the changes. In order to be coherent to what a "reload" operation would do, * this function calls 'pgagroal_transfer_configuration' internally. * * @param config_key the string that contains the name of the parameter * @param config_value the value to set * @return 0 on success */ int pgagroal_apply_configuration(char* config_key, char* config_value); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/json.h000066400000000000000000000140231456565230200166560ustar00rootroot00000000000000/* * 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); pgagroal-1.6.0/src/include/logging.h000066400000000000000000000131201456565230200173300ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_LOGGING_H #define PGAGROAL_LOGGING_H #ifdef __cplusplus extern "C" { #endif #include #define PGAGROAL_LOGGING_TYPE_CONSOLE 0 #define PGAGROAL_LOGGING_TYPE_FILE 1 #define PGAGROAL_LOGGING_TYPE_SYSLOG 2 #define PGAGROAL_LOGGING_LEVEL_DEBUG5 1 #define PGAGROAL_LOGGING_LEVEL_DEBUG4 1 #define PGAGROAL_LOGGING_LEVEL_DEBUG3 1 #define PGAGROAL_LOGGING_LEVEL_DEBUG2 1 #define PGAGROAL_LOGGING_LEVEL_DEBUG1 2 #define PGAGROAL_LOGGING_LEVEL_INFO 3 #define PGAGROAL_LOGGING_LEVEL_WARN 4 #define PGAGROAL_LOGGING_LEVEL_ERROR 5 #define PGAGROAL_LOGGING_LEVEL_FATAL 6 #define PGAGROAL_LOGGING_MODE_CREATE 0 #define PGAGROAL_LOGGING_MODE_APPEND 1 #define PGAGROAL_LOGGING_ROTATION_DISABLED 0 #define PGAGROAL_LOGGING_DEFAULT_LOG_LINE_PREFIX "%Y-%m-%d %H:%M:%S" #define pgagroal_log_trace(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_DEBUG5, __FILE__, __LINE__, __VA_ARGS__) #define pgagroal_log_debug(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_DEBUG1, __FILE__, __LINE__, __VA_ARGS__) #define pgagroal_log_info(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) #define pgagroal_log_warn(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__) #define pgagroal_log_error(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) #define pgagroal_log_fatal(...) pgagroal_log_line(PGAGROAL_LOGGING_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__) /** * Initialize the logging system * @return 0 upon success, otherwise 1 */ int pgagroal_init_logging(void); /** * Start the logging system * @return 0 upon success, otherwise 1 */ int pgagroal_start_logging(void); /** * Stop the logging system * @return 0 upon success, otherwise 1 */ int pgagroal_stop_logging(void); /** * Log a line * @param level The level * @param file The file * @param line The line number * @param fmt The formatting code * @return 0 upon success, otherwise 1 */ void pgagroal_log_line(int level, char* file, int line, char* fmt, ...); /** * Log a memory segment * @param data The data * @param size The size * @return 0 upon success, otherwise 1 */ void pgagroal_log_mem(void* data, size_t size); /** * Utility function to understand if log rotation * is enabled or not. * @return true if the rotation is enabled. */ bool log_rotation_enabled(void); /** * Forces a disabling of the log rotation. * Useful when the system cannot determine how to rotate logs. */ void log_rotation_disable(void); /** * Checks if there are the requirements to perform a log rotation. * It returns true in either the case of the size exceeded or * the age exceeded. The age is contained into a global * variable 'next_log_rotation_age' that express the number * of seconds at which the next rotation will be performed. * @return true if the log should be rotated */ bool log_rotation_required(void); /** * Function to compute the next instant at which a log rotation * will happen. It computes only if the logging is to a file * and only if the configuration tells to compute the rotation * age. * @return true on success */ bool log_rotation_set_next_rotation_age(void); /** * Opens the log file defined in the configuration. * Works only for a real log file, i.e., the configuration * must be set up to log to a file, not console. * * The function considers the settings in the configuration * to determine the mode (append, create) and the filename * to open. * * It sets the global variable 'log_file'. * * If it succeed in opening the log file, it calls * the log_rotation_set_next_rotation_age() function to * determine the next instant at which the log file * must be rotated. Calling such function is safe * because if the log rotation is disabled, the function * does nothing. * * @return 0 on success, 1 on error. */ int log_file_open(void); /** * Performs a log file rotation. * It flushes and closes the current log file, * then re-opens it. * * DO NOT LOG WITHIN THIS FUNCTION as long as this * is invoked by log_line */ void log_file_rotate(void); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/management.h000066400000000000000000000321711456565230200200250ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_MANAGEMENT_H #define PGAGROAL_MANAGEMENT_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #define MANAGEMENT_TRANSFER_CONNECTION 1 #define MANAGEMENT_RETURN_CONNECTION 2 #define MANAGEMENT_KILL_CONNECTION 3 #define MANAGEMENT_FLUSH 4 #define MANAGEMENT_GRACEFULLY 5 #define MANAGEMENT_STOP 6 #define MANAGEMENT_STATUS 7 #define MANAGEMENT_DETAILS 8 #define MANAGEMENT_ISALIVE 9 #define MANAGEMENT_CANCEL_SHUTDOWN 10 #define MANAGEMENT_ENABLEDB 11 #define MANAGEMENT_DISABLEDB 12 #define MANAGEMENT_RESET 13 #define MANAGEMENT_RESET_SERVER 14 #define MANAGEMENT_CLIENT_DONE 15 #define MANAGEMENT_CLIENT_FD 16 #define MANAGEMENT_SWITCH_TO 17 #define MANAGEMENT_RELOAD 18 #define MANAGEMENT_REMOVE_FD 19 #define MANAGEMENT_CONFIG_GET 20 #define MANAGEMENT_CONFIG_SET 21 #define MANAGEMENT_CONFIG_LS 22 /** * 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' /** * Read the management header * @param socket The socket descriptor * @param id The resulting management identifier * @param slot The resulting slot * @return 0 upon success, otherwise 1 */ int pgagroal_management_read_header(int socket, signed char* id, int32_t* slot); /** * Read the management payload * @param socket The socket descriptor * @param id The management identifier * @param payload_i The resulting integer payload * @param payload_s The resulting string payload * @return 0 upon success, otherwise 1 */ int pgagroal_management_read_payload(int socket, signed char id, int* payload_i, char** payload_s); /** * Management operation: Transfer a connection * @param slot The slot * @return 0 upon success, otherwise 1 */ int pgagroal_management_transfer_connection(int32_t slot); /** * Management operation: Return a connection * @param slot The slot * @return 0 upon success, otherwise 1 */ int pgagroal_management_return_connection(int32_t slot); /** * Management operation: Kill a connection * @param slot The slot * @param socket The socket * @return 0 upon success, otherwise 1 */ int pgagroal_management_kill_connection(int32_t slot, int socket); /** * Management operation: Flush the pool * @param ssl The SSL connection * @param socket The socket descriptor * @param mode The flush mode * @param database The database * @return 0 upon success, otherwise 1 */ int pgagroal_management_flush(SSL* ssl, int socket, int32_t mode, char* database); /** * Management operation: Enable database * @param ssl The SSL connection * @param socket The socket descriptor * @param database The database name * @return 0 upon success, otherwise 1 */ int pgagroal_management_enabledb(SSL* ssl, int socket, char* database); /** * Management operation: Disable database * @param ssl The SSL connection * @param socket The socket descriptor * @param database The database name * @return 0 upon success, otherwise 1 */ int pgagroal_management_disabledb(SSL* ssl, int socket, char* database); /** * Management operation: Gracefully * @param ssl The SSL connection * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_management_gracefully(SSL* ssl, int socket); /** * Management operation: Stop * @param ssl The SSL connection * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_management_stop(SSL* ssl, int socket); /** * Management operation: Cancel shutdown * @param ssl The SSL connection * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_management_cancel_shutdown(SSL* ssl, int socket); /** * Management operation: Status * @param ssl The SSL connection * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int 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, char output_format); /** * Management: Write status * @param socket The socket * @param graceful Is pgagroal in graceful shutdown * @return 0 upon success, otherwise 1 */ int pgagroal_management_write_status(int socket, bool graceful); /** * Management operation: Details * @param ssl The SSL connection * @param socket The socket * @return 0 upon success, otherwise 1 */ int 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, char output_format); /** * Management: Write details * @param socket The socket * @return 0 upon success, otherwise 1 */ int pgagroal_management_write_details(int socket); /** * Management operation: isalive * @param socket The socket * @return 0 upon success, otherwise 1 */ int pgagroal_management_isalive(SSL* ssl, int socket); /** * Management: Read isalive * @param socket The socket * @param status The resulting status * @return 0 upon success, otherwise 1 */ int pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format); /** * Management: Write isalive * @param socket The socket * @param gracefully Is the server shutting down gracefully * @return 0 upon success, otherwise 1 */ int pgagroal_management_write_isalive(int socket, bool gracefully); /** * Management operation: Reset * @param ssl The SSL connection * @param socket The socket * @return 0 upon success, otherwise 1 */ int pgagroal_management_reset(SSL* ssl, int socket); /** * Management operation: Reset server * @param ssl The SSL connection * @param socket The socket * @param server The server * @return 0 upon success, otherwise 1 */ int pgagroal_management_reset_server(SSL* ssl, int socket, char* server); /** * Management operation: Client done * @param pid The pid * @return 0 upon success, otherwise 1 */ int pgagroal_management_client_done(pid_t pid); /** * Management operation: Client file descriptor * @param slot The slot * @param pid The pid * @return 0 upon success, otherwise 1 */ int pgagroal_management_client_fd(int32_t slot, pid_t pid); /** * Management operation: Switch to * @param ssl The SSL connection * @param socket The socket * @param server The server * @return 0 upon success, otherwise 1 */ int pgagroal_management_switch_to(SSL* ssl, int socket, char* server); /** * Management operation: Reload * @param ssl The SSL connection * @param socket The socket * @return 0 upon success, otherwise 1 */ int pgagroal_management_reload(SSL* ssl, int socket); /** * Management operation: Remove socket descriptor * @param slot The slot * @param socket The socket * @param pid The pid * @return 0 upon success, otherwise 1 */ int pgagroal_management_remove_fd(int32_t slot, int socket, pid_t pid); /** * Management operation: get a configuration setting. * This function sends over the socket the message to get a configuration * value. * In particular, the message block for the action config_get is sent, * then the size of the configuration parameter to get (e.g., `max_connections`) * and last the parameter name itself. * * @param ssl the SSL connection * @param socket the socket file descriptor * @param config_key the name of the configuration parameter to get back * @return 0 on success, 1 on error */ int pgagroal_management_config_get(SSL* ssl, int socket, char* config_key); /** * Management operation result: receives the key to read in the configuration. * * Internally, exploits the function to read the payload from the socket. * @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* config_key, char* expected_value, bool verbose, char output_format); /** * Management operation: write the result of a config_get action on the socket. * * Writes on the socket the result of the request for a specific * configuration parameter. * ° @param socket the socket file descriptor * @param config_key the name of the configuration parameter to get * @return 0 on success */ int pgagroal_management_write_config_get(int socket, char* config_key); /** * Management operation: set a configuration setting. * This function sends over the socket the message to set a configuration * value. * In particular, the message block for the action config_set is sent, * then the size of the configuration parameter to set (e.g., `max_connections`), * then the parameter name. At this point another couple of "size" and "value" is * sent with the size of the value to set and its value. * * @param ssl the SSL connection * @param socket the socket file descriptor * @param config_key the name of the configuration parameter to set * @param config_value the value to set for the new parameter * @return 0 on success, 1 on error */ int pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* config_value); /** * Function to execute the config-set and write over the socket * the result. * * If the parameter is set, the function calls the * pgagroal_management_write_config_get to send back over the * socket the current value of the parameter. Therefore, * this function answers always back the current value * so that it is possible to reason about the new value and * see if it has changed. * * @param socket the socket to use for communication * @param config_key the key to set * @param config_value the value to use * @return 0 on success */ 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 #endif pgagroal-1.6.0/src/include/memory.h000066400000000000000000000042411456565230200172160ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_MEMORY_H #define PGAGROAL_MEMORY_H #ifdef __cplusplus extern "C" { #endif #include #include /** * Initialize a memory segment for the process local message structure */ void pgagroal_memory_init(void); /** * Set the size of the local message structure * @param size The size */ void pgagroal_memory_size(size_t size); /** * Get the message structure * @return The structure */ struct message* pgagroal_memory_message(void); /** * Free the memory segment */ void pgagroal_memory_free(void); /** * Destroy the memory segment */ void pgagroal_memory_destroy(void); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/message.h000066400000000000000000000257751456565230200173510ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_MESSAGE_H #define PGAGROAL_MESSAGE_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #define MESSAGE_STATUS_ZERO 0 #define MESSAGE_STATUS_OK 1 #define MESSAGE_STATUS_ERROR 2 /** @struct * Defines a message */ struct message { signed char kind; /**< The kind of the message */ ssize_t length; /**< The length of the message */ size_t max_length; /**< The maximum size of the message */ void* data; /**< The message data */ } __attribute__ ((aligned (64))); /** * Read a message in blocking mode * @param ssl The SSL struct * @param socket The socket descriptor * @param msg The resulting message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_read_block_message(SSL* ssl, int socket, struct message** msg); /** * Read a message with a timeout * @param ssl The SSL struct * @param socket The socket descriptor * @param timeout The timeout in seconds * @param msg The resulting message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_read_timeout_message(SSL* ssl, int socket, int timeout, struct message** msg); /** * Write a message using a socket * @param ssl The SSL struct * @param socket The socket descriptor * @param msg The message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_write_message(SSL* ssl, int socket, struct message* msg); /** * Create a message * @param data A pointer to the data * @param length The length of the message * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_message(void* data, ssize_t length, struct message** msg); /** * Free a message * @param msg The resulting message */ void pgagroal_free_message(struct message* msg); /** * Copy a message * @param msg The resulting message * @return The copy */ struct message* pgagroal_copy_message(struct message* msg); /** * Free a copy message * @param msg The resulting message */ void pgagroal_free_copy_message(struct message* msg); /** * Write an empty message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_empty(SSL* ssl, int socket); /** * Write a notice message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_notice(SSL* ssl, int socket); /** * Write a pool is full message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_pool_full(SSL* ssl, int socket); /** * Write a connection refused message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_connection_refused(SSL* ssl, int socket); /** * Write a connection refused message (protocol 1 or 2) * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_connection_refused_old(SSL* ssl, int socket); /** * Write a bad password message * @param ssl The SSL struct * @param socket The socket descriptor * @param username The user name * @return 0 upon success, otherwise 1 */ int pgagroal_write_bad_password(SSL* ssl, int socket, char* username); /** * Write an unsupported security model message * @param ssl The SSL struct * @param socket The socket descriptor * @param username The user name * @return 0 upon success, otherwise 1 */ int pgagroal_write_unsupported_security_model(SSL* ssl, int socket, char* username); /** * Write a no HBA entry message * @param ssl The SSL struct * @param socket The socket descriptor * @param username The user name * @param database The database * @param address The client address * @return 0 upon success, otherwise 1 */ int pgagroal_write_no_hba_entry(SSL* ssl, int socket, char* username, char* database, char* address); /** * Write a deallocate all message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_deallocate_all(SSL* ssl, int socket); /** * Write a discard all message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_discard_all(SSL* ssl, int socket); /** * Write TLS response * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_tls(SSL* ssl, int socket); /** * Write a terminate message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_terminate(SSL* ssl, int socket); /** * Write a failover message to the client * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_client_failover(SSL* ssl, int socket); /** * Write an auth password message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_auth_password(SSL* ssl, int socket); /** * Write a rollback message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_rollback(SSL* ssl, int socket); /** * Create an auth password response message * @param password The password * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_password_response(char* password, struct message** msg); /** * Write an auth md5 message * @param ssl The SSL struct * @param socket The socket descriptor * @param salt The salt * @return 0 upon success, otherwise 1 */ int pgagroal_write_auth_md5(SSL* ssl, int socket, char salt[4]); /** * Create an auth MD5 response message * @param md5 The md5 * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_md5_response(char* md5, struct message** msg); /** * Write an auth SCRAM-SHA-256 message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_auth_scram256(SSL* ssl, int socket); /** * Create an auth SCRAM-SHA-256 response message * @param nounce The nounce * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_scram256_response(char* nounce, struct message** msg); /** * Create an auth SCRAM-SHA-256/Continue message * @param cn The client nounce * @param sn The server nounce * @param salt The salt * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_scram256_continue(char* cn, char* sn, char* salt, struct message** msg); /** * Create an auth SCRAM-SHA-256/Continue response message * @param wp The without proff * @param p The proff * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_scram256_continue_response(char* wp, char* p, struct message** msg); /** * Create an auth SCRAM-SHA-256/Final message * @param ss The server signature (BASE64) * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_auth_scram256_final(char* ss, struct message** msg); /** * Write an auth success message * @param ssl The SSL struct * @param socket The socket descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_write_auth_success(SSL* ssl, int socket); /** * Create a SSL message * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_ssl_message(struct message** msg); /** * Create a startup message * @param username The user name * @param database The database * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_startup_message(char* username, char* database, struct message** msg); /** * Create a cancel request message * @param pid The pid * @param secret The secret * @param msg The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_create_cancel_request_message(int pid, int secret, struct message** msg); /** * Is the connection valid * @param socket The socket descriptor * @return true upon success, otherwise false */ bool pgagroal_connection_isvalid(int socket); /** * Log a message * @param msg The message */ void pgagroal_log_message(struct message* msg); /** * Read a message using a socket * @param socket The socket descriptor * @param msg The resulting message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_read_socket_message(int socket, struct message** msg); /** * Write a message using a socket * @param socket The socket descriptor * @param msg The message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_write_socket_message(int socket, struct message* msg); /** * Read a message using SSL * @param ssl The SSL descriptor * @param msg The resulting message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_read_ssl_message(SSL* ssl, struct message** msg); /** * Write a message using SSL * @param ssl The SSL descriptor * @param msg The message * @return One of MESSAGE_STATUS_ZERO, MESSAGE_STATUS_OK or MESSAGE_STATUS_ERROR */ int pgagroal_write_ssl_message(SSL* ssl, struct message* msg); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/network.h000066400000000000000000000106551456565230200174050ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_NETWORK_H #define PGAGROAL_NETWORK_H #ifdef __cplusplus extern "C" { #endif #include #include /** * Bind sockets for a host * @param hostname The host name * @param port The port number * @param fds The resulting descriptors * @param length The resulting length of descriptors * @return 0 upon success, otherwise 1 */ int pgagroal_bind(const char* hostname, int port, int** fds, int* length); /** * Bind a Unix Domain Socket * @param directory The directory * @param file The file * @param fd The resulting descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_bind_unix_socket(const char* directory, const char* file, int* fd); /** * Remove Unix Domain Socket directory * @param directory The directory * @param file The file * @return 0 upon success, otherwise 1 */ int pgagroal_remove_unix_socket(const char* directory, const char* file); /** * Connect to 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_connect(const char* hostname, int port, int* fd); /** * Connect to a Unix Domain Socket * @param directory The directory * @param file The file * @param fd The resulting descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_connect_unix_socket(const char* directory, const char* file, int* fd); /** * Is the socket valid * @param fd The descriptor * @return True upon success, otherwise false */ bool pgagroal_socket_isvalid(int fd); /** * Disconnect from a descriptor * @param fd The descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_disconnect(int fd); /** * Get the sockaddr_in structure * @param sa The sockaddr structure * @return The sockaddr_in / sockaddr_in6 structure */ void* pgagroal_get_sockaddr(struct sockaddr* sa); /** * Get the address of a sockaddr * @param sa The sockaddr structure * @param address The result address * @param length The length */ void pgagroal_get_address(struct sockaddr* sa, char* address, size_t length); /** * Apply TCP/NODELAY to a descriptor * @param fd The descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_tcp_nodelay(int fd); /** * Set the configured socket buffer size to a descriptor * @param fd The descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_socket_buffers(int fd); /** * Apply O_NONBLOCK to a descriptor * @param fd The descriptor * @param value The value * @return 0 upon success, otherwise 1 */ int pgagroal_socket_nonblocking(int fd, bool value); /** * Does the descriptor have O_NONBLOCK * @param fd The descriptor * @return true if non blocking, otherwise false */ bool pgagroal_socket_is_nonblocking(int fd); /** * Does the socket have an error associated * @param fd The descriptor * @return 0 upon success, otherwise 1 */ int pgagroal_socket_has_error(int fd); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/pgagroal.h000066400000000000000000000456141456565230200175130ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_H #define PGAGROAL_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #if HAVE_OPENBSD #include #endif #include #include #define PGAGROAL_HOMEPAGE "https://agroal.github.io/pgagroal/" #define PGAGROAL_ISSUES "https://github.com/agroal/pgagroal/issues" #define MAIN_UDS ".s.pgagroal" #ifdef HAVE_FREEBSD #define PGAGROAL_DEFAULT_CONFIGURATION_PATH "/usr/local/etc/pgagroal/" #else #define PGAGROAL_DEFAULT_CONFIGURATION_PATH "/etc/pgagroal/" #endif #define PGAGROAL_DEFAULT_CONF_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal.conf" #define PGAGROAL_DEFAULT_HBA_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_hba.conf" #define PGAGROAL_DEFAULT_LIMIT_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_databases.conf" #define PGAGROAL_DEFAULT_USERS_FILE PGAGROAL_DEFAULT_CONFIGURATION_PATH "pgagroal_users.conf" #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 MAX_PROCESS_TITLE_LENGTH 256 #define MAX_BUFFER_SIZE 65535 #define DEFAULT_BUFFER_SIZE 65535 #define SECURITY_BUFFER_SIZE 1024 #define MAX_USERNAME_LENGTH 128 #define MAX_DATABASE_LENGTH 256 #define MAX_TYPE_LENGTH 16 #define MAX_ADDRESS_LENGTH 64 #define MAX_PASSWORD_LENGTH 1024 #define MAX_APPLICATION_NAME 64 #define MAX_PATH 1024 #define MISC_LENGTH 128 #define NUMBER_OF_SERVERS 64 #ifdef DEBUG #define MAX_NUMBER_OF_CONNECTIONS 8 #else #define MAX_NUMBER_OF_CONNECTIONS 10000 #endif #define NUMBER_OF_HBAS 64 #define NUMBER_OF_LIMITS 64 #define NUMBER_OF_USERS 64 #define NUMBER_OF_ADMINS 8 #define NUMBER_OF_DISABLED 64 #define NUMBER_OF_SECURITY_MESSAGES 5 #define STATE_NOTINIT -2 #define STATE_INIT -1 #define STATE_FREE 0 #define STATE_IN_USE 1 #define STATE_GRACEFULLY 2 #define STATE_FLUSH 3 #define STATE_IDLE_CHECK 4 #define STATE_MAX_CONNECTION_AGE 5 #define STATE_VALIDATION 6 #define STATE_REMOVE 7 #define SECURITY_INVALID -2 #define SECURITY_REJECT -1 #define SECURITY_TRUST 0 #define SECURITY_PASSWORD 3 #define SECURITY_MD5 5 #define SECURITY_SCRAM256 10 #define SECURITY_ALL 99 #define AUTH_SUCCESS 0 #define AUTH_BAD_PASSWORD 1 #define AUTH_ERROR 2 #define AUTH_TIMEOUT 3 #define SERVER_NOTINIT -2 #define SERVER_NOTINIT_PRIMARY -1 #define SERVER_PRIMARY 0 #define SERVER_REPLICA 1 #define SERVER_FAILOVER 2 #define SERVER_FAILED 3 #define FLUSH_IDLE 0 #define FLUSH_GRACEFULLY 1 #define FLUSH_ALL 2 #define VALIDATION_OFF 0 #define VALIDATION_FOREGROUND 1 #define VALIDATION_BACKGROUND 2 #define HISTOGRAM_BUCKETS 18 #define HUGEPAGE_OFF 0 #define HUGEPAGE_TRY 1 #define HUGEPAGE_ON 2 #define UPDATE_PROCESS_TITLE_NEVER 0 #define UPDATE_PROCESS_TITLE_STRICT 1 #define UPDATE_PROCESS_TITLE_MINIMAL 2 #define UPDATE_PROCESS_TITLE_VERBOSE 3 /** * Constants used to refer to an HBA entry field. */ #define PGAGROAL_HBA_ENTRY_TYPE "type" #define PGAGROAL_HBA_ENTRY_DATABASE "database" #define PGAGROAL_HBA_ENTRY_USERNAME "username" #define PGAGROAL_HBA_ENTRY_ADDRESS "address" #define PGAGROAL_HBA_ENTRY_METHOD "method" /** * Constants used to refer to the limit structure fields */ #define PGAGROAL_LIMIT_ENTRY_DATABASE "database" #define PGAGROAL_LIMIT_ENTRY_USERNAME "username" #define PGAGROAL_LIMIT_ENTRY_MAX_SIZE "max_size" #define PGAGROAL_LIMIT_ENTRY_MIN_SIZE "min_size" #define PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE "initial_size" #define PGAGROAL_LIMIT_ENTRY_LINENO "line_number" /** * Constants used to manage the exit code * of a command sent over the socket in the * management stuff, e.g., `pgagroal-cli`. */ #define EXIT_STATUS_OK 0 #define EXIT_STATUS_CONNECTION_ERROR 1 #define EXIT_STATUS_DATA_ERROR 2 #define likely(x) __builtin_expect (!!(x), 1) #define unlikely(x) __builtin_expect (!!(x), 0) #define MAX(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) #define MIN(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) /* * Common piece of code to perform a sleeping. * * @param zzz the amount of time to * sleep, expressed as nanoseconds. * * Example SLEEP(5000000L) * */ #define SLEEP(zzz) \ do \ { \ struct timespec ts_private; \ ts_private.tv_sec = 0; \ ts_private.tv_nsec = zzz; \ nanosleep(&ts_private, NULL); \ } while (0); /* * Commonly used block of code to sleep * for a specified amount of time and * then jump back to a specified label. * * @param zzz how much time to sleep (as long nanoseconds) * @param goto_to the label to which jump to * * Example: * ... else SLEEP_AND_GOTO(100000L, retry) */ #define SLEEP_AND_GOTO(zzz, goto_to) \ do \ { \ struct timespec ts_private; \ ts_private.tv_sec = 0; \ ts_private.tv_nsec = zzz; \ nanosleep(&ts_private, NULL); \ goto goto_to; \ } while (0); /** * The shared memory segment */ extern void* shmem; /** * The shared memory segment for a pipeline */ extern void* pipeline_shmem; /** * The shared memory segment for the Prometheus data */ extern void* prometheus_shmem; /** * Shared memory used to contain the Prometheus * response cache. */ extern void* prometheus_cache_shmem; /** @struct * Defines a server */ 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 */ 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 * Defines a connection */ struct connection { char username[MAX_USERNAME_LENGTH]; /**< The user name */ char database[MAX_DATABASE_LENGTH]; /**< The database */ char appname[MAX_APPLICATION_NAME]; /**< The application_name */ bool new; /**< Is the connection new */ signed char server; /**< The server identifier */ bool tx_mode; /**< Connection in transaction mode */ signed char has_security; /**< The security identifier */ ssize_t security_lengths[NUMBER_OF_SECURITY_MESSAGES]; /**< The lengths of the security messages */ char security_messages[NUMBER_OF_SECURITY_MESSAGES][SECURITY_BUFFER_SIZE]; /**< The security messages */ int backend_pid; /**< The backend process id */ int backend_secret; /**< The backend secret */ signed char limit_rule; /**< The limit rule used */ time_t start_time; /**< The start timestamp */ time_t timestamp; /**< The last used timestamp */ pid_t pid; /**< The associated process id */ int fd; /**< The descriptor */ } __attribute__ ((aligned (64))); /** @struct * Defines a HBA entry */ struct hba { char type[MAX_TYPE_LENGTH]; /**< The type */ char database[MAX_DATABASE_LENGTH]; /**< The database */ char username[MAX_USERNAME_LENGTH]; /**< The user name */ char address[MAX_ADDRESS_LENGTH]; /**< The address / mask */ char method[MAX_ADDRESS_LENGTH]; /**< The access method */ int lineno; /**< The line number within the configuration file */ } __attribute__ ((aligned (64))); /** @struct * Defines a limit entry */ struct limit { char database[MAX_DATABASE_LENGTH]; /**< The database */ char username[MAX_USERNAME_LENGTH]; /**< The user name */ atomic_ushort active_connections; /**< The active number of connections */ int max_size; /**< The maximum pool size */ int initial_size; /**< The initial pool size */ int min_size; /**< The minimum pool size */ int lineno; /**< The line number within the configuration file */ } __attribute__ ((aligned (64))); /** @struct * Defines a user */ struct user { char username[MAX_USERNAME_LENGTH]; /**< The user name */ char password[MAX_PASSWORD_LENGTH]; /**< The password */ } __attribute__ ((aligned (64))); /** @struct * Defines the Prometheus connection metric */ struct prometheus_connection { atomic_ullong query_count; /**< The number of queries per connection */ } __attribute__ ((aligned (64))); /** * A structure to handle the Prometheus response * so that it is possible to serve the very same * response over and over depending on the cache * settings. * * The `valid_until` field stores the result * of `time(2)`. * * The cache is protected by the `lock` field. * * The `size` field stores the size of the allocated * `data` payload. */ struct prometheus_cache { time_t valid_until; /**< when the cache will become not valid */ atomic_schar lock; /**< lock to protect the cache */ size_t size; /**< size of the cache */ char data[]; /**< the payload */ } __attribute__ ((aligned (64))); /** @struct * Defines the Prometheus metrics */ struct prometheus { atomic_ulong session_time[HISTOGRAM_BUCKETS]; /**< The histogram buckets */ atomic_ulong session_time_sum; /**< Total session time */ atomic_ulong connection_error; /**< The number of error calls */ atomic_ulong connection_kill; /**< The number of kill calls */ atomic_ulong connection_remove; /**< The number of remove calls */ atomic_ulong connection_timeout; /**< The number of timeout calls */ atomic_ulong connection_return; /**< The number of return calls */ atomic_ulong connection_invalid; /**< The number of invalid calls */ atomic_ulong connection_get; /**< The number of get calls */ atomic_ulong connection_idletimeout; /**< The number of idle timeout calls */ atomic_ulong connection_max_connection_age; /**< The number of max connection age calls */ atomic_ulong connection_flush; /**< The number of flush calls */ atomic_ulong connection_success; /**< The number of success calls */ /**< The number of connection awaiting due to `blocking_timeout` */ atomic_ulong connections_awaiting[NUMBER_OF_LIMITS]; atomic_ulong connections_awaiting_total; atomic_ulong auth_user_success; /**< The number of AUTH_SUCCESS calls */ atomic_ulong auth_user_bad_password; /**< The number of AUTH_BAD_PASSWORD calls */ atomic_ulong auth_user_error; /**< The number of AUTH_ERROR calls */ atomic_ulong client_wait; /**< The number of waiting clients */ atomic_ulong client_active; /**< The number of active clients */ atomic_ulong client_wait_time; /**< The time the client waits */ atomic_ullong query_count; /**< The number of queries */ atomic_ullong tx_count; /**< The number of transactions */ atomic_ullong network_sent; /**< The bytes sent by clients */ atomic_ullong network_received; /**< The bytes received from servers */ atomic_int client_sockets; /**< The number of sockets the client used */ atomic_int self_sockets; /**< The number of sockets used by pgagroal itself */ atomic_ulong server_error[NUMBER_OF_SERVERS]; /**< The number of errors for a server */ atomic_ulong failed_servers; /**< The number of failed servers */ struct prometheus_connection prometheus_connections[]; /**< The number of prometheus connections (FMA) */ } __attribute__ ((aligned (64))); /** @struct * Defines the configuration and state of pgagroal */ struct configuration { char configuration_path[MAX_PATH]; /**< The configuration path */ char hba_path[MAX_PATH]; /**< The HBA path */ char limit_path[MAX_PATH]; /**< The limit path */ char users_path[MAX_PATH]; /**< The users path */ char frontend_users_path[MAX_PATH];/**< The frontend users path */ char admins_path[MAX_PATH]; /**< The admins path */ char superuser_path[MAX_PATH]; /**< The superuser path */ char host[MISC_LENGTH]; /**< The host */ int port; /**< The port */ int metrics; /**< The metrics port */ unsigned int metrics_cache_max_age; /**< Number of seconds to cache the Prometheus response */ unsigned int metrics_cache_max_size; /**< Number of bytes max to cache the Prometheus response */ int management; /**< The management port */ bool gracefully; /**< Is pgagroal in gracefully mode */ char disabled[NUMBER_OF_DISABLED][MAX_DATABASE_LENGTH]; /**< Which databases are disabled */ int pipeline; /**< The pipeline type */ bool failover; /**< Is failover enabled */ char failover_script[MISC_LENGTH]; /**< The failover script */ int log_type; /**< The logging type */ int log_level; /**< The logging level */ char log_path[MISC_LENGTH]; /**< The logging path */ bool log_connections; /**< Log successful logins */ bool log_disconnections; /**< Log disconnects */ int log_mode; /**< The logging mode */ unsigned int log_rotation_size; /**< bytes to force log rotation */ unsigned int log_rotation_age; /**< minutes for log rotation */ char log_line_prefix[MISC_LENGTH]; /**< The logging prefix */ atomic_schar log_lock; /**< The logging lock */ unsigned int update_process_title; /**< Behaviour for updating the process title */ bool authquery; /**< Is authentication query enabled */ 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 */ atomic_ushort active_connections; /**< The active number of connections */ int max_connections; /**< The maximum number of connections */ bool allow_unknown_users; /**< Allow unknown users */ int blocking_timeout; /**< The blocking timeout in seconds */ int idle_timeout; /**< The idle 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 */ int max_retries; /**< The maximum number of retries */ int authentication_timeout; /**< The authentication timeout in seconds */ int disconnect_client; /**< Disconnect client if idle for more than the specified seconds */ bool disconnect_client_force; /**< Force a disconnect client if active for more than the specified seconds */ char pidfile[MAX_PATH]; /**< File containing the PID */ char libev[MISC_LENGTH]; /**< Name of libev mode */ int buffer_size; /**< Socket buffer size */ bool keep_alive; /**< Use keep alive */ bool nodelay; /**< Use NODELAY */ bool non_blocking; /**< Use non blocking */ int backlog; /**< The backlog for listen */ unsigned char hugepage; /**< Huge page support */ bool tracker; /**< Tracker support */ bool track_prepared_statements; /**< Track prepared statements (transaction pooling) */ char unix_socket_dir[MISC_LENGTH]; /**< The directory for the Unix Domain Socket */ atomic_schar su_connection; /**< The superuser connection */ int number_of_servers; /**< The number of servers */ int number_of_hbas; /**< The number of HBA entries */ int number_of_limits; /**< The number of limit entries */ int number_of_users; /**< The number of users */ int number_of_frontend_users; /**< The number of users */ int number_of_admins; /**< The number of admins */ atomic_schar states[MAX_NUMBER_OF_CONNECTIONS]; /**< The states */ struct server servers[NUMBER_OF_SERVERS]; /**< The servers */ struct hba hbas[NUMBER_OF_HBAS]; /**< The HBA entries */ struct limit limits[NUMBER_OF_LIMITS]; /**< The limit entries */ struct user users[NUMBER_OF_USERS]; /**< The users */ struct user frontend_users[NUMBER_OF_USERS]; /**< The frontend users */ struct user admins[NUMBER_OF_ADMINS]; /**< The admins */ struct user superuser; /**< The superuser */ struct connection connections[]; /**< The connections (FMA) */ } __attribute__ ((aligned (64))); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/pipeline.h000066400000000000000000000057231456565230200175210ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_PIPELINE_H #define PGAGROAL_PIPELINE_H #ifdef __cplusplus extern "C" { #endif #include #include #include #define PIPELINE_AUTO -1 #define PIPELINE_PERFORMANCE 0 #define PIPELINE_SESSION 1 #define PIPELINE_TRANSACTION 2 typedef int (* initialize)(void*, void**, size_t*); typedef void (* start)(struct ev_loop*, struct worker_io*); typedef void (* callback)(struct ev_loop*, struct ev_io*, int); typedef void (* stop)(struct ev_loop*, struct worker_io*); typedef void (* destroy)(void*, size_t); typedef void (* periodic)(void); /** @struct * Define the structure for a pipeline */ struct pipeline { initialize initialize; /**< The initialize function for the pipeline */ start start; /**< The start function */ callback client; /**< The callback for the client */ callback server; /**< The callback for the server */ stop stop; /**< The stop function */ destroy destroy; /**< The destroy function for the pipeline */ periodic periodic; /**< The periodic function for the pipeline */ }; /** * Get the performance pipeline * @return The structure */ struct pipeline performance_pipeline(void); /** * Get the session pipeline * @return The structure */ struct pipeline session_pipeline(void); /** * Get the transaction pipeline * @return The structure */ struct pipeline transaction_pipeline(void); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/pool.h000066400000000000000000000104721456565230200166620ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_POOL_H #define PGAGROAL_POOL_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include /** * Get a connection * @param username The user name * @param database The database * @param reuse Should a slot be reused * @param transaction_mode Obtain a connection in transaction mode * @param slot The resulting slot * @param ssl The resulting SSL (can be NULL) * @return 0 upon success, 1 if pool is full, otherwise 2 */ int pgagroal_get_connection(char* username, char* database, bool reuse, bool transaction_mode, int* slot, SSL** ssl); /** * Return a connection * @param slot The slot * @param ssl The SSL connection (can be NULL) * @param transaction_mode Is the connection returned in transaction mode * @return 0 upon success, otherwise 1 */ int pgagroal_return_connection(int slot, SSL* ssl, bool transaction_mode); /** * Kill a connection * @param slot The slot * @param ssl The SSL connection (can be NULL) * @return 0 upon success, otherwise 1 */ int pgagroal_kill_connection(int slot, SSL* ssl); /** * Perform idle timeout */ void pgagroal_idle_timeout(void); /** * Perform max connection age check */ void pgagroal_max_connection_age(void); /** * Perform connection validation */ void pgagroal_validation(void); /** * Flush the pool * @param mode The flush mode * @param database The database */ void pgagroal_flush(int mode, char* database); /** * Flush the pool for a specific server * @param server The server */ void pgagroal_flush_server(signed char server); /** * Prefill the pool * @param initial Use initial size */ void pgagroal_prefill(bool initial); /** * Initialize the pool * @return 0 upon success, otherwise 1 */ int pgagroal_pool_init(void); /** * Shutdown the pool * @return 0 upon success, otherwise 1 */ int pgagroal_pool_shutdown(void); /** * Print the status of the pool * @return 0 upon success, otherwise 1 */ int pgagroal_pool_status(void); /** * This function wraps around the logic to call `pgagroal_prefill()`. * In order to avoid code repetition, this function can be used safely * wherever there is the possibility to activate the prefill. The function * does check if the configuration allows for a prefill, and in such case * tries to `fork(2)` and executes the prefill. * Also, the function checks for the presence of a primary with * `pgagroal_get_primary()` and refuses to do a prefill if there * is no primary at all. * * @param do_fork Run the prefill in a separate process * @param initial true if the prefill has to be done with the INITIAL * value of the pgagroal_database.conf file, false if it has * to be done with the MINIMAL value. * */ void pgagroal_prefill_if_can(bool do_fork, bool initial); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/prometheus.h000066400000000000000000000161471456565230200201110ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_PROMETHEUS_H #define PGAGROAL_PROMETHEUS_H #ifdef __cplusplus extern "C" { #endif #include #include /* * Value to disable the Prometheus cache, * it is equivalent to set `metrics_cache` * to 0 (seconds). */ #define PGAGROAL_PROMETHEUS_CACHE_DISABLED 0 /** * Max size of the cache (in bytes). * If the cache request exceeds this size * the caching should be aborted in some way. */ #define PROMETHEUS_MAX_CACHE_SIZE (1024 * 1024) /** * The default cache size in the case * the user did not set any particular * configuration option. */ #define PROMETHEUS_DEFAULT_CACHE_SIZE (256 * 1024) /** * Create a prometheus instance * @param fd The client descriptor */ void pgagroal_prometheus(int fd); /** * Initialize prometheus shmem */ int pgagroal_init_prometheus(size_t* p_size, void** p_shmem); /** * Add session time information * @param time The time */ void pgagroal_prometheus_session_time(double time); /** * Connection error */ void pgagroal_prometheus_connection_error(void); /** * Connection kill */ void pgagroal_prometheus_connection_kill(void); /** * Connection remove */ void pgagroal_prometheus_connection_remove(void); /** * Connection timeout */ void pgagroal_prometheus_connection_timeout(void); /** * Connection return */ void pgagroal_prometheus_connection_return(void); /** * Connection invalid */ void pgagroal_prometheus_connection_invalid(void); /** * Connection get */ void pgagroal_prometheus_connection_get(void); /** * Connection idle timeout */ void pgagroal_prometheus_connection_idletimeout(void); /** * Connection max connection age */ void pgagroal_prometheus_connection_max_connection_age(void); /** * Connection awaiting due to `blocking_timeout`. * Tracks the total awaiting connections and also the * per-limit ones. * * * Every call to this function should be paired * by the same number of calls * to `pgagroal_prometheus_connection_unawaiting()` * * * @param limit_index if greater or equal to zero * tracks the awaiting connection for the limits entry * (i.e., per user and database) */ void pgagroal_prometheus_connection_awaiting(int limit_index); /** * An awaiting conection, i.e., one holded by `blocking_timeout` * that is no more on hold and can restart its workflo. * * * * Every call to this function should be after * the call * to `pgagroal_prometheus_connection_awaiting()` * * * The function decreases the total counter of the awaiting connections as * well as the per-limit ones. * * @param limit_entry if greater or equal to zero * it untracks the corresponding limit entry */ void pgagroal_prometheus_connection_unawaiting(int limit_index); /** * Connection flush */ void pgagroal_prometheus_connection_flush(void); /** * Connection success */ void pgagroal_prometheus_connection_success(void); /** * Increase AUTH_SUCCESS for a user */ void pgagroal_prometheus_auth_user_success(void); /** * Increase AUTH_BAD_PASSWORD for a user */ void pgagroal_prometheus_auth_user_bad_password(void); /** * Increase AUTH_ERROR for a user */ void pgagroal_prometheus_auth_user_error(void); /** * Increase client_wait by 1 */ void pgagroal_prometheus_client_wait_add(void); /** * Decrease client_wait by 1 */ void pgagroal_prometheus_client_wait_sub(void); /** * Increase client_active by 1 */ void pgagroal_prometheus_client_active_add(void); /** * Decrease client_active by 1 */ void pgagroal_prometheus_client_active_sub(void); /** * Increase query_count by 1 */ void pgagroal_prometheus_query_count_add(void); /** * Increase query_count for the specified connection by 1 * @param slot The connection slot */ void pgagroal_prometheus_query_count_specified_add(int slot); /** * Reset query_count for the specified connection * @param slot The connection slot */ void pgagroal_prometheus_query_count_specified_reset(int slot); /** * Increase tx_count by 1 */ void pgagroal_prometheus_tx_count_add(void); /** * Increase network_sent * @param s The size */ void pgagroal_prometheus_network_sent_add(ssize_t s); /** * Increase network_received * @param s The size */ void pgagroal_prometheus_network_received_add(ssize_t s); /** * Increase client_sockets by 1 */ void pgagroal_prometheus_client_sockets_add(void); /** * Decrease client_sockets by 1 */ void pgagroal_prometheus_client_sockets_sub(void); /** * Increase self_sockets by 1 */ void pgagroal_prometheus_self_sockets_add(void); /** * Decrease self_sockets by 1 */ void pgagroal_prometheus_self_sockets_sub(void); /** * Reset the counters and histograms */ void pgagroal_prometheus_reset(void); /** * Increase SERVER_ERROR for a server * @param server The server */ void pgagroal_prometheus_server_error(int server); /** * Count failed servers */ void pgagroal_prometheus_failed_servers(void); /** * Allocates, for the first time, the Prometheus cache. * * The cache structure, as well as its dynamically sized payload, * are created as shared memory chunks. * * Assumes the shared memory for the cofiguration is already set. * * The cache will be allocated as soon as this method is invoked, * even if the cache has not been configured at all! * * If the memory cannot be allocated, the function issues errors * in the logs and disables the caching machinaery. * * @param p_size a pointer to where to store the size of * allocated chunk of memory * @param p_shmem the pointer to the pointer at which the allocated chunk * of shared memory is going to be inserted * * @return 0 on success */ int pgagroal_init_prometheus_cache(size_t* p_size, void** p_shmem); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/remote.h000066400000000000000000000035431456565230200172050ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_REMOTE_H #define PGAGROAL_REMOTE_H #ifdef __cplusplus extern "C" { #endif #include #include /** * Create a remote management instance * @param fd The client descriptor * @param address The client address */ void pgagroal_remote_management(int fd, char* address); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/security.h000066400000000000000000000103741456565230200175610ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_SECURITY_H #define PGAGROAL_SECURITY_H #ifdef __cplusplus extern "C" { #endif #include #include #include /** * Authenticate a user * @param client_fd The descriptor * @param address The client address * @param slot The resulting slot * @param client_ssl The client SSL context * @param server_ssl The server SSL context * @return 0 upon success, otherwise 1 */ int pgagroal_authenticate(int client_fd, char* address, int* slot, SSL** client_ssl, SSL** server_ssl); /** * Authenticate a prefill connection * @param username The user name * @param password The password * @param database The database * @param slot The resulting slot * @param server_ssl The server SSL context * @return 0 upon success, otherwise 1 */ int pgagroal_prefill_auth(char* username, char* password, char* database, int* slot, SSL** server_ssl); /** * Authenticate a remote management user * @param client_fd The descriptor * @param address The client address * @param client_ssl The client SSL context * @return 0 upon success, otherwise 1 */ int pgagroal_remote_management_auth(int client_fd, char* address, SSL** client_ssl); /** * Connect using SCRAM-SHA256 * @param username The user name * @param password The password * @param server_fd The descriptor * @param s_ssl The SSL context * @return 0 upon success, otherwise 1 */ int pgagroal_remote_management_scram_sha256(char* username, char* password, int server_fd, SSL** s_ssl); /** * Get the master key * @param masterkey The master key * @return 0 upon success, otherwise 1 */ int pgagroal_get_master_key(char** masterkey); /** * Encrypt a string * @param plaintext The string * @param password The master password * @param ciphertext The ciphertext output * @param ciphertext_length The length of the ciphertext * @return 0 upon success, otherwise 1 */ int pgagroal_encrypt(char* plaintext, char* password, char** ciphertext, int* ciphertext_length); /** * Decrypt a string * @param ciphertext The string * @param ciphertext_length The length of the ciphertext * @param password The master password * @param plaintext The plaintext output * @return 0 upon success, otherwise 1 */ int pgagroal_decrypt(char* ciphertext, int ciphertext_length, char* password, char** plaintext); /** * MD5 a string * @param str The string * @param md5 The MD5 string * @return 0 upon success, otherwise 1 */ int pgagroal_md5(char* str, int length, char** md5); /** * Is the user known to the system * @param user The user name * @return True if known, otherwise false */ bool pgagroal_user_known(char* user); /** * Is the TLS configuration valid * @return 0 upon success, otherwise 1 */ int pgagroal_tls_valid(void); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/server.h000066400000000000000000000053451456565230200172220ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_SERVER_H #define PGAGROAL_SERVER_H #ifdef __cplusplus extern "C" { #endif #include #include #include /** * Get the primary server * @param server The resulting server identifier * @return 0 upon success, otherwise 1 */ int pgagroal_get_primary(int* server); /** * Update the server state * @param slot The slot * @param socket The descriptor * @param ssl The SSL connection * @return 0 upon success, otherwise 1 */ int pgagroal_update_server_state(int slot, int socket, SSL* ssl); /** * Print the state of the servers * @return 0 upon success, otherwise 1 */ int pgagroal_server_status(void); /** * Failover * @param slot The slot * @return 0 upon success, otherwise 1 */ int pgagroal_server_failover(int slot); /** * Force failover * @param server The server * @return 0 upon success, otherwise 1 */ int pgagroal_server_force_failover(int server); /** * Reset server * @param server The server * @return 0 upon success, otherwise 1 */ int pgagroal_server_reset(char* server); /** * Switch server * @param server The server * @return 0 upon success, otherwise 1 */ int pgagroal_server_switch(char* server); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/shmem.h000066400000000000000000000047531456565230200170270ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_SHMEM_H #define PGAGROAL_SHMEM_H #ifdef __cplusplus extern "C" { #endif #include /** * Create a shared memory segment * @param size The size of the segment * @param hp Huge page value * @parma shmem The shared memory segment * @return 0 upon success, otherwise 1 */ int pgagroal_create_shared_memory(size_t size, unsigned char hp, void** shmem); /** * Resize a shared memory segment * @param size The size of the segment * @param shmem The pointer to the segment * @param new_size The size of the new segment * @param new_shmem The pointer to the new segment * @return 0 upon success, otherwise 1 */ int pgagroal_resize_shared_memory(size_t size, void* shmem, size_t* new_size, void** new_shmem); /** * Destroy a shared memory segment * @param shmem The shared memory segment * @param size The size * @return 0 upon success, otherwise 1 */ int pgagroal_destroy_shared_memory(void* shmem, size_t size); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/tracker.h000066400000000000000000000070461456565230200173470ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_TRACKER_H #define PGAGROAL_TRACKER_H #ifdef __cplusplus extern "C" { #endif #include #include #define TRACKER_CLIENT_START 0 #define TRACKER_CLIENT_STOP 1 #define TRACKER_GET_CONNECTION_SUCCESS 2 #define TRACKER_GET_CONNECTION_TIMEOUT 3 #define TRACKER_GET_CONNECTION_ERROR 4 #define TRACKER_RETURN_CONNECTION_SUCCESS 5 #define TRACKER_RETURN_CONNECTION_KILL 6 #define TRACKER_KILL_CONNECTION 7 #define TRACKER_AUTHENTICATE 8 #define TRACKER_BAD_CONNECTION 9 #define TRACKER_IDLE_TIMEOUT 10 #define TRACKER_MAX_CONNECTION_AGE 11 #define TRACKER_INVALID_CONNECTION 12 #define TRACKER_FLUSH 13 #define TRACKER_REMOVE_CONNECTION 14 #define TRACKER_PREFILL 15 #define TRACKER_PREFILL_RETURN 16 #define TRACKER_PREFILL_KILL 17 #define TRACKER_WORKER_RETURN1 18 #define TRACKER_WORKER_RETURN2 19 #define TRACKER_WORKER_KILL1 20 #define TRACKER_WORKER_KILL2 21 #define TRACKER_TX_RETURN_CONNECTION_START 30 #define TRACKER_TX_RETURN_CONNECTION_STOP 31 #define TRACKER_TX_GET_CONNECTION 32 #define TRACKER_TX_RETURN_CONNECTION 33 #define TRACKER_SOCKET_ASSOCIATE_CLIENT 100 #define TRACKER_SOCKET_ASSOCIATE_SERVER 101 #define TRACKER_SOCKET_DISASSOCIATE_CLIENT 102 #define TRACKER_SOCKET_DISASSOCIATE_SERVER 103 /** * Tracking event: Basic * @param id The event identifier * @param username The user name * @param database The database */ void pgagroal_tracking_event_basic(int id, char* username, char* database); /** * Tracking event: Slot * @param id The event identifier * @param slot The slot */ void pgagroal_tracking_event_slot(int id, int slot); /** * Tracking event: Socket * @param id The event identifier * @param socket The socket */ void pgagroal_tracking_event_socket(int id, int socket); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/include/utils.h000066400000000000000000000337501456565230200170550ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_UTILS_H #define PGAGROAL_UTILS_H #ifdef __cplusplus extern "C" { #endif #include #include #include /** @struct * Defines the signal structure */ struct signal_info { struct ev_signal signal; /**< The libev base type */ int slot; /**< The slot */ }; /** * Get the request identifier * @param msg The message * @return The identifier */ int32_t pgagroal_get_request(struct message* msg); /** * Extract the user name and database from a message * @param msg The message * @param username The resulting user name * @param database The resulting database * @param appname The resulting application_name * @return 0 upon success, otherwise 1 */ int pgagroal_extract_username_database(struct message* msg, char** username, char** database, char** appname); /** * Extract a message from a message * @param type The message type to be extracted * @param msg The message * @param extracted The resulting message * @return 0 upon success, otherwise 1 */ int pgagroal_extract_message(char type, struct message* msg, struct message** extracted); /** * Extract an error message from a message * @param msg The message * @param error The error * @return 0 upon success, otherwise 1 */ int pgagroal_extract_error_message(struct message* msg, char** error); /** * Get a string for the state * @param state * @return The string */ char* pgagroal_get_state_string(signed char state); /** * Read a byte * @param data Pointer to the data * @return The byte */ signed char pgagroal_read_byte(void* data); /** * Read an int16 * @param data Pointer to the data * @return The int16 */ int16_t pgagroal_read_int16(void* data); /** * Read an int32 * @param data Pointer to the data * @return The int32 */ int32_t pgagroal_read_int32(void* data); /** * Read a long * @param data Pointer to the data * @return The long */ long pgagroal_read_long(void* data); /** * Read a string * @param data Pointer to the data * @return The string */ char* pgagroal_read_string(void* data); /** * Write a byte * @param data Pointer to the data * @param b The byte */ void pgagroal_write_byte(void* data, signed char b); /** * Write an int32 * @param data Pointer to the data * @param i The int32 */ void pgagroal_write_int32(void* data, int32_t i); /** * Write a long * @param data Pointer to the data * @param l The long int */ void pgagroal_write_long(void* data, long l); /** * Write a string * @param data Pointer to the data * @param s The string */ void pgagroal_write_string(void* data, char* s); /** * Is the machine big endian ? * @return True if big, otherwise false for little */ bool pgagroal_bigendian(void); /** * Swap * @param i The value * @return The swapped value */ unsigned int pgagroal_swap(unsigned int i); /** * Print the available libev engines */ void pgagroal_libev_engines(void); /** * Get the constant for a libev engine * @param engine The name of the engine * @return The constant */ unsigned int pgagroal_libev(char* engine); /** * Get the name for a libev engine * @param val The constant * @return The name */ char* pgagroal_libev_engine(unsigned int val); /** * Get the home directory * @return The directory */ char* pgagroal_get_home_directory(void); /** * Get the user name * @return The user name */ char* pgagroal_get_user_name(void); /** * Get a password from stdin * @return The password */ char* pgagroal_get_password(void); /** * BASE64 encode a string * @param raw The string * @param raw_length The length of the raw string * @param encoded The encoded string * @return 0 if success, otherwise 1 */ int pgagroal_base64_encode(char* raw, int raw_length, char** encoded); /** * BASE64 decode a string * @param encoded The encoded string * @param encoded_length The length of the encoded string * @param raw The raw string * @param raw_length The length of the raw string * @return 0 if success, otherwise 1 */ int pgagroal_base64_decode(char* encoded, size_t encoded_length, char** raw, int* raw_length); /** * Set process title. * * The function will autonomously check the update policy set * via the configuration option `update_process_title` and * will do nothing if the setting is `never`. * In the case the policy is set to `strict`, the process title * will not overflow the initial command line length (i.e., strlen(argv[*])) * otherwise it will do its best to set the title to the desired string. * * The policies `strict` and `minimal` will be honored only on Linux platforms * where a native call to set the process title is not available. * * * The resulting process title will be set to either `s1` or `s1/s2` if there * both strings and the length is allowed by the policy. * * @param argc The number of arguments * @param argv The argv pointer * @param s1 The first string * @param s2 The second string */ void pgagroal_set_proc_title(int argc, char** argv, char* s1, char* s2); /** * Sets the process title for a given connection. * * Uses `pgagroal_set_proc_title` to build an information string * with the form * user@host:port/database * * This means that all the policies honored by the latter function and * set via the `update_process_title` configuration paramter will be * honored. * * @param argc the number of arguments * @param argv command line arguments * @param connection the struct connection pointer for the established connection. */ void pgagroal_set_connection_proc_title(int argc, char** argv, struct connection* connection); /** * Provide the application version number as a unique value composed of the three * specified parts. For example, when invoked with (1,5,0) it returns 10500. * Every part of the number must be between 0 and 99, and the function * applies a restriction on the values. For example passing 1 or 101 as one of the part * will produce the same result. * * @param major the major version number * @param minor the minor version number * @param patch the patch level * @returns a number made by (patch + minor * 100 + major * 10000 ) */ unsigned int pgagroal_version_as_number(unsigned int major, unsigned int minor, unsigned int patch); /** * Provides the current version number of the application. * It relies on `pgagroal_version_as_number` and invokes it with the * predefined constants. * * @returns the current version number */ unsigned int pgagroal_version_number(void); /** * Checks if the currently running version number is * greater or equal than the specied one. * * @param major the major version number * @param minor the minor version number * @param patch the patch level * @returns true if the current version is greater or equal to the specified one */ bool pgagroal_version_ge(unsigned int major, unsigned int minor, unsigned int patch); /** * Append a string * * @param orig The original string * @param s The string * @returns The new string */ char* pgagroal_append(char* orig, char* s); /** * Append an int * * @param orig The original string * @param i The int * @returns The new string */ char* pgagroal_append_int(char* orig, int i); /** * Append an unsigned long * * @param orig The original string * @param l The long * @returns The new string */ char* pgagroal_append_ulong(char* orig, unsigned long l); /** * Append an unsigned long long * * @param orig The original string * @param l The long * @returns The new string */ char* pgagroal_append_ullong(char* orig, unsigned long long l); #ifdef DEBUG /** * Generate a backtrace in the log * @return 0 if success, otherwise 1 */ int pgagroal_backtrace(void); #endif /** * Utility function to parse the command line * and search for a command. * * The function tries to be smart, in helping to find out * a command with the possible subcommand. * * @param argc the command line counter * @param argv the command line as provided to the application * @param offset the position at which the next token out of `argv` * has to be read. This is usually the `optind` set by getopt_long(). * @param command the string to search for as a main command * @param subcommand if not NULL, a subcommand that should be * matched. If no matches are found with the subcommand, the * function fails. * * @param key if not null, a pointer to a string that will be * filled with the next value on the command line (usually * the name of a database/server or a configuration parameter * name) * @param default_key the default value to be specified for a key * if none is found on the command line. For example, if the key * represents a database name, the "*" could be the default_key * to indicate every possible database. * * @param value if not null, a pointer to a string that will be * filled with the extrac value for the command. For example, in the case * of a configuration subcommand, the value will be the setting to apply. * * @param default_value the default value to set on the `value` pointer * variable if nothing is found on the command line. * * @return true if the parsing of the command line was succesful, false * otherwise * * * Possible command lines: * * flush gracefully pgbench * flush gracefully * flush * flush pgbench * conf get log_level * conf set log_level debug * * that in turn are match by * * parse_command(argv, argc, "flush", "gracefully", &database, "*", NULL, NULL) * parse_command(argv, argc, "flush", "gracefully", NULL, "*", NULL, NULL) * parse_command(argv, argc, "flush", NULL, NULL, "*", NULL, NULL) * parse_command(argv, argc, "flush", NULL, &database, "*", NULL, NULL) * parse_command(argv, argc, "conf", "get", &config_key, NULL, NULL, NULL) * parse_command(argv, argc, "conf", "set", &config_key, NULL, &config_value, NULL) */ bool parse_command(int argc, char** argv, int offset, char* command, char* subcommand, char** key, char* default_key, char** value, char* default_value); /* * A wrapper function to parse a single command (and its subcommand) * without any optional argument. * It calls the parse_command with NULL key, value and defaults. * * Thanks to this wrapper, it is simpler to write the command parsing because * the two following lines are equivalent: * * parse_command( argc, argv, optind, "conf", "reload", NULL, NULL, NULL; NULL ); * * parse_command_simple( argc, argv, optind, "conf", "reload"); * * @see parse_command */ bool parse_command_simple(int argc, char** argv, int offset, char* command, char* subcommand); /** * A function to match against a deprecated command. * It prints out a message to warn the user about * the deprecated usage of the command if there is a specific * "deprecated-by" and "deprecated since" set of information. * * * @param argc the command line counter * @param argv the command line as provided to the application * @param offset the position at which the next token out of `argv` * has to be read. This is usually the `optind` set by getopt_long(). * @param command the string to search for as a main command * @param deprecated_by the name of the command to use * instead of the deprecated one * @param value if not null, a pointer to a string that will be * filled with the value of the database. If no database is found * on the command line, the special value "*" will be placed to * mean "all the database" * @param deprecated_since_major major version since the command has been deprecated * @param deprecated_since_minor minor version since the command has been deprecated * * @return true if the parsing of the command line was succesful, false * otherwise */ bool parse_deprecated_command(int argc, char** argv, int offset, char* command, char** value, 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 #endif pgagroal-1.6.0/src/include/worker.h000066400000000000000000000050701456565230200172200ustar00rootroot00000000000000/* * 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. */ #ifndef PGAGROAL_WORKER_H #define PGAGROAL_WORKER_H #ifdef __cplusplus extern "C" { #endif #include #include #include #define WORKER_SUCCESS 0 #define WORKER_FAILURE 1 #define WORKER_SHUTDOWN 2 #define WORKER_CLIENT_FAILURE 3 #define WORKER_SERVER_FAILURE 4 #define WORKER_SERVER_FATAL 5 #define WORKER_FAILOVER 6 /** @struct * The worker structure for each IO event */ struct worker_io { struct ev_io io; /**< The libev base type */ int client_fd; /**< The client descriptor */ int server_fd; /**< The server descriptor */ int slot; /**< The slot */ SSL* client_ssl; /**< The client SSL context */ SSL* server_ssl; /**< The server SSL context */ }; extern volatile int running; extern volatile int exit_code; /** * Create a worker instance * @param fd The client descriptor * @param address The client address * @param argv The argv */ void pgagroal_worker(int fd, char* address, char** argv); #ifdef __cplusplus } #endif #endif pgagroal-1.6.0/src/libpgagroal/000077500000000000000000000000001456565230200163745ustar00rootroot00000000000000pgagroal-1.6.0/src/libpgagroal/configuration.c000066400000000000000000003567411456565230200214270ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX #include #endif #define LINE_LENGTH 512 static int extract_key_value(char* str, char** key, char** value); static int as_int(char* str, int* i); static int as_bool(char* str, bool* b); static int as_logging_type(char* str); static int as_logging_level(char* str); static int as_logging_mode(char* str); static int as_logging_rotation_size(char* str, unsigned int* size); static int as_logging_rotation_age(char* str, unsigned int* age); static int as_validation(char* str); static int as_pipeline(char* str); static int as_hugepage(char* str); static unsigned int as_update_process_title(char* str, unsigned int* policy, unsigned int default_policy); static int extract_value(char* str, int offset, char** value); static void extract_hba(char* str, char** type, char** database, char** user, char** address, char** method); static void extract_limit(char* str, int server_max, char** database, char** user, int* max_size, int* initial_size, int* min_size); static unsigned int as_seconds(char* str, unsigned int* age, unsigned int default_age); static unsigned int as_bytes(char* str, unsigned int* bytes, unsigned int default_bytes); static int transfer_configuration(struct configuration* config, struct configuration* reload); static void copy_server(struct server* dst, struct server* src); static void copy_hba(struct hba* dst, struct hba* src); static void copy_user(struct user* dst, struct user* src); static int restart_int(char* name, int e, int n); static int restart_string(char* name, char* e, char* n, bool skip_non_existing); static int restart_limit(char* name, struct configuration* config, struct configuration* reload); static int restart_server(struct server* src, struct server* dst); static bool is_empty_string(char* s); static bool is_same_server(struct server* s1, struct server* s2); static bool key_in_section(char* wanted, char* section, char* key, bool global, bool* unknown); static bool is_comment_line(char* line); static bool section_line(char* line, char* section); static int pgagroal_write_server_config_value(char* buffer, char* server_name, char* config_key, size_t buffer_size); static int pgagroal_write_hba_config_value(char* buffer, char* username, char* config_key, size_t buffer_size); static int pgagroal_write_limit_config_value(char* buffer, char* database, char* config_key, size_t buffer_size); static int pgagroal_apply_hba_configuration(struct hba* hba, char* context, char* value); static int pgagroal_apply_limit_configuration_string(struct limit* limit, char* context, char* value); static int pgagroal_apply_limit_configuration_int(struct limit* limit, char* context, int value); static int to_string(char* where, char* value, size_t max_length); static int to_bool(char* where, bool value); static int to_int(char* where, int value); static int to_update_process_title(char* where, int value); static int to_validation(char* where, int value); static int to_pipeline(char* where, int value); static int to_log_mode(char* where, int value); static int to_log_level(char* where, int value); static int to_log_type(char* where, int value); /** * */ int pgagroal_init_configuration(void* shm) { struct configuration* config; config = (struct configuration*)shm; atomic_init(&config->active_connections, 0); for (int i = 0; i < NUMBER_OF_SERVERS; i++) { atomic_init(&config->servers[i].state, SERVER_NOTINIT); } config->failover = false; config->tls = false; config->gracefully = false; config->pipeline = PIPELINE_AUTO; config->authquery = false; config->blocking_timeout = 30; config->idle_timeout = 0; config->max_connection_age = 0; config->validation = VALIDATION_OFF; config->background_interval = 300; config->max_retries = 5; config->authentication_timeout = 5; config->disconnect_client = 0; config->disconnect_client_force = false; config->buffer_size = DEFAULT_BUFFER_SIZE; config->keep_alive = true; config->nodelay = true; config->non_blocking = false; config->backlog = -1; config->hugepage = HUGEPAGE_TRY; config->tracker = false; config->track_prepared_statements = false; config->log_type = PGAGROAL_LOGGING_TYPE_CONSOLE; config->log_level = PGAGROAL_LOGGING_LEVEL_INFO; config->log_connections = false; config->log_disconnections = false; config->log_mode = PGAGROAL_LOGGING_MODE_APPEND; atomic_init(&config->log_lock, STATE_FREE); config->max_connections = 100; config->allow_unknown_users = true; atomic_init(&config->su_connection, STATE_FREE); config->update_process_title = UPDATE_PROCESS_TITLE_VERBOSE; return 0; } /** * This struct is going to store the metadata * about which sections have been parsed during * the configuration read. * This can be used to seek for duplicated sections * at different positions in the configuration file. */ struct config_section { char name[LINE_LENGTH]; /**< The name of the section */ unsigned int lineno; /**< The line number for this section */ bool main; /**< Is this the main configuration section or a server one? */ }; /** * */ int pgagroal_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 configuration* config; int idx_server = 0; struct server srv; bool has_main_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[NUMBER_OF_SERVERS + 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) * NUMBER_OF_SERVERS + 1); config = (struct 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 >= NUMBER_OF_SERVERS + 1) { warnx("Max number of sections (%d) in configuration file <%s> reached!", NUMBER_OF_SERVERS + 1, 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_MAIN_INI_SECTION, LINE_LENGTH); if (sections[idx_sections].main) { has_main_section = true; } idx_sections++; if (strcmp(section, PGAGROAL_MAIN_INI_SECTION)) { if (idx_server > 0 && idx_server <= NUMBER_OF_SERVERS) { memcpy(&(config->servers[idx_server - 1]), &srv, sizeof(struct server)); } else if (idx_server > NUMBER_OF_SERVERS) { printf("Maximum number of servers exceeded\n"); } memset(&srv, 0, sizeof(struct server)); atomic_init(&srv.state, SERVER_NOTINIT); 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_main_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->servers[idx_server - 1]), &srv, sizeof(struct server)); } config->number_of_servers = idx_server; fclose(file); // check there is at least one main section if (!has_main_section) { warnx("No main configuration section [%s] found in file <%s>", PGAGROAL_MAIN_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. for (int i = 0; i < NUMBER_OF_SERVERS + 1; i++) { for (int j = i + 1; j < NUMBER_OF_SERVERS + 1; j++) { // skip uninitialized sections if (!strlen(sections[i].name) || !strlen(sections[j].name)) { continue; } if (!strncmp(sections[i].name, sections[j].name, LINE_LENGTH)) { // cannot log here ... warnx("%s section [%s] duplicated at lines %d and %d of file <%s>", sections[i].main ? "Main" : "Server", sections[i].name, sections[i].lineno, sections[j].lineno, filename); return_value++; // this is an error condition! } } } return return_value; } /** * */ int pgagroal_validate_configuration(void* shm, bool has_unix_socket, bool has_main_sockets) { bool tls; struct stat st; struct configuration* config; tls = false; config = (struct configuration*)shm; if (!has_main_sockets) { if (strlen(config->host) == 0) { pgagroal_log_fatal("pgagroal: No host defined"); return 1; } if (config->port <= 0) { pgagroal_log_fatal("pgagroal: No port defined"); return 1; } } if (!has_unix_socket) { if (strlen(config->unix_socket_dir) == 0) { pgagroal_log_fatal("pgagroal: No unix_socket_dir defined"); return 1; } if (stat(config->unix_socket_dir, &st) == 0 && S_ISDIR(st.st_mode)) { /* Ok */ } else { pgagroal_log_fatal("pgagroal: unix_socket_dir is not a directory (%s)", config->unix_socket_dir); return 1; } } if (config->backlog <= 0) { config->backlog = MAX(config->max_connections / 4, 16); } if (config->authentication_timeout <= 0) { config->authentication_timeout = 5; } if (config->disconnect_client <= 0) { config->disconnect_client = 0; } if (config->authquery) { if (strlen(config->superuser.username) == 0) { pgagroal_log_fatal("pgagroal: Authentication query requires a superuser"); return 1; } else { config->allow_unknown_users = true; if (config->number_of_users > 0) { pgagroal_log_fatal("pgagroal: Users are not supported when using authentication query"); return 1; } if (config->number_of_frontend_users > 0) { pgagroal_log_fatal("pgagroal: Frontend users are not supported when using authentication query"); return 1; } if (config->number_of_limits > 0) { pgagroal_log_fatal("pgagroal: Limits are not supported when using authentication query"); return 1; } } } if (config->max_connections <= 0) { pgagroal_log_fatal("pgagroal: max_connections must be greater than 0"); return 1; } if (config->max_connections > MAX_NUMBER_OF_CONNECTIONS) { pgagroal_log_warn("pgagroal: max_connections (%d) is greater than allowed (%d)", config->max_connections, MAX_NUMBER_OF_CONNECTIONS); config->max_connections = MAX_NUMBER_OF_CONNECTIONS; } if (config->number_of_frontend_users > 0 && config->allow_unknown_users) { pgagroal_log_warn("pgagroal: Frontend users should not be used with allow_unknown_users"); } if (config->failover) { if (strlen(config->failover_script) == 0) { pgagroal_log_fatal("pgagroal: Failover requires a script definition"); return 1; } memset(&st, 0, sizeof(struct stat)); if (stat(config->failover_script, &st) == -1) { pgagroal_log_error("pgagroal: Can't locate failover script: %s", config->failover_script); return 1; } if (!S_ISREG(st.st_mode)) { pgagroal_log_error("pgagroal: Failover script is not a regular file: %s", config->failover_script); return 1; } if (st.st_uid != geteuid()) { pgagroal_log_error("pgagroal: Failover script not owned by user: %s", config->failover_script); return 1; } if (!(st.st_mode & (S_IRUSR | S_IXUSR))) { pgagroal_log_error("pgagroal: Failover script must be executable: %s", config->failover_script); return 1; } if (config->number_of_servers <= 1) { pgagroal_log_fatal("pgagroal: Failover requires at least 2 servers defined"); return 1; } } if (config->number_of_servers <= 0) { pgagroal_log_fatal("pgagroal: No servers defined"); return 1; } for (int i = 0; i < config->number_of_servers; i++) { if (strlen(config->servers[i].host) == 0) { pgagroal_log_fatal("pgagroal: No host defined for server [%s] (%s:%d)", config->servers[i].name, config->configuration_path, config->servers[i].lineno); return 1; } if (config->servers[i].port == 0) { pgagroal_log_fatal("pgagroal: No port defined for server [%s] (%s:%d)", config->servers[i].name, config->configuration_path, config->servers[i].lineno); return 1; } } // check for duplicated servers for (int i = 0; i < config->number_of_servers; i++) { for (int j = i + 1; j < config->number_of_servers; j++) { if (is_same_server(&config->servers[i], &config->servers[j])) { pgagroal_log_fatal("pgagroal: Servers [%s] and [%s] are duplicated! (%s:%d:%d)", config->servers[i].name, config->servers[j].name, config->configuration_path, config->servers[i].lineno, config->servers[j].lineno); return 1; } } } if (config->pipeline == PIPELINE_AUTO) { if (config->tls && (strlen(config->tls_cert_file) > 0 || strlen(config->tls_key_file) > 0)) { tls = true; } if (config->failover || tls || config->disconnect_client > 0) { config->pipeline = PIPELINE_SESSION; } else { config->pipeline = PIPELINE_PERFORMANCE; } } if (config->pipeline == PIPELINE_SESSION) { /* Checks */ } else if (config->pipeline == PIPELINE_TRANSACTION) { if (config->disconnect_client > 0) { pgagroal_log_fatal("pgagroal: Transaction pipeline does not support disconnect_client"); return 1; } if (!config->authquery) { if (config->number_of_users == 0) { pgagroal_log_fatal("pgagroal: Users must be defined for the transaction pipeline"); return 1; } if (config->allow_unknown_users) { pgagroal_log_fatal("pgagroal: Transaction pipeline does not support allow_unknown_users"); return 1; } } for (int i = 0; i < config->number_of_servers; i++) { if (config->servers[i].tls) { pgagroal_log_fatal("pgagroal: Transaction pipeline does not support TLS to a server"); return 1; } } if (config->number_of_limits == 0) { pgagroal_log_fatal("pgagroal: Defining limits for the transaction pipeline is mandatory"); return 1; } for (int i = 0; i < config->number_of_limits; i++) { if (config->limits[i].min_size <= 0) { pgagroal_log_fatal("pgagroal: min_size for transaction pipeline must be greater than 0"); return 1; } if (config->limits[i].initial_size <= 0) { pgagroal_log_fatal("pgagroal: initial_size for transaction pipeline must be greater than 0"); return 1; } if (config->limits[i].max_size <= 0) { pgagroal_log_fatal("pgagroal: max_size for transaction pipeline must be greater than 0"); return 1; } } if (config->blocking_timeout > 0) { pgagroal_log_warn("pgagroal: Using blocking_timeout for the transaction pipeline is not recommended"); } if (config->idle_timeout > 0) { pgagroal_log_warn("pgagroal: Using idle_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"); } if (config->validation == VALIDATION_FOREGROUND) { pgagroal_log_warn("pgagroal: Using foreground validation for the transaction pipeline is not recommended"); } } else if (config->pipeline == PIPELINE_PERFORMANCE) { if (config->tls && (strlen(config->tls_cert_file) > 0 || strlen(config->tls_key_file) > 0)) { tls = true; } if (config->failover) { pgagroal_log_fatal("pgagroal: Performance pipeline does not support failover"); return 1; } if (tls) { pgagroal_log_fatal("pgagroal: Performance pipeline does not support TLS"); return 1; } if (config->disconnect_client > 0) { pgagroal_log_fatal("pgagroal: Performance pipeline does not support disconnect_client"); return 1; } } // do some last initialization here, since the configuration // looks good so far pgagroal_init_pidfile_if_needed(); return 0; } /** * */ int pgagroal_read_hba_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* type = NULL; char* database = NULL; char* username = NULL; char* address = NULL; char* method = NULL; int lineno = 0; struct configuration* config; file = fopen(filename, "r"); if (!file) { return PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; } index = 0; config = (struct configuration*)shm; while (fgets(line, sizeof(line), file)) { lineno++; if (!is_empty_string(line) && !is_comment_line(line)) { extract_hba(line, &type, &database, &username, &address, &method); if (pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_TYPE, type) == 0 && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_DATABASE, database) == 0 && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_USERNAME, username) == 0 && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_ADDRESS, address) == 0 && pgagroal_apply_hba_configuration(&config->hbas[index], PGAGROAL_HBA_ENTRY_METHOD, method) == 0) { // ok, this configuration has been applied index++; if (index >= NUMBER_OF_HBAS) { warnx("Too many HBA entries (max is %d)\n", NUMBER_OF_HBAS); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; } } else { warnx("Invalid HBA entry (%s:%d)", filename, lineno); } free(type); free(database); free(username); free(address); free(method); type = NULL; database = NULL; username = NULL; address = NULL; method = NULL; } } config->number_of_hbas = index; fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; } /** * */ int pgagroal_validate_hba_configuration(void* shm) { struct configuration* config; config = (struct configuration*)shm; if (config->number_of_hbas == 0) { pgagroal_log_fatal("pgagroal: No HBA entry defined"); return 1; } for (int i = 0; i < config->number_of_hbas; i++) { if (!strcasecmp("host", config->hbas[i].type) || !strcasecmp("hostssl", config->hbas[i].type)) { /* Ok */ } else { pgagroal_log_fatal("Unknown HBA type: %s (%s:%d)", config->hbas[i].type, config->hba_path, config->hbas[i].lineno); return 1; } if (!strcasecmp("trust", config->hbas[i].method) || !strcasecmp("reject", config->hbas[i].method) || !strcasecmp("password", config->hbas[i].method) || !strcasecmp("md5", config->hbas[i].method) || !strcasecmp("scram-sha-256", config->hbas[i].method) || !strcasecmp("all", config->hbas[i].method)) { /* Ok */ } else { pgagroal_log_fatal("Unknown HBA method: %s (%s:%d)", config->hbas[i].method, config->hba_path, config->hbas[i].lineno); return 1; } } return 0; } /** * */ int pgagroal_read_limit_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* database = NULL; char* username = NULL; int max_size; int initial_size; int min_size; int server_max; int lineno; struct configuration* config; file = fopen(filename, "r"); if (!file) { return PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; } index = 0; lineno = 0; config = (struct configuration*)shm; server_max = config->max_connections; while (fgets(line, sizeof(line), file)) { lineno++; if (!is_empty_string(line) && !is_comment_line(line)) { initial_size = 0; min_size = 0; extract_limit(line, server_max, &database, &username, &max_size, &initial_size, &min_size); lineno++; if (database && username) { // normalize the sizes initial_size = initial_size > max_size ? max_size : initial_size; min_size = min_size > max_size ? max_size : min_size; if (pgagroal_apply_limit_configuration_string(&config->limits[index], PGAGROAL_LIMIT_ENTRY_DATABASE, database) == 0 && pgagroal_apply_limit_configuration_string(&config->limits[index], PGAGROAL_LIMIT_ENTRY_USERNAME, username) == 0 && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_MAX_SIZE, max_size) == 0 && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_MIN_SIZE, min_size) == 0 && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_LINENO, lineno) == 0 && pgagroal_apply_limit_configuration_int(&config->limits[index], PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, initial_size) == 0) { // configuration applied server_max -= max_size; memcpy(&(config->limits[index].database), database, strlen(database)); memcpy(&(config->limits[index].username), username, strlen(username)); config->limits[index].max_size = max_size; config->limits[index].initial_size = initial_size; config->limits[index].min_size = min_size; config->limits[index].lineno = lineno; atomic_init(&config->limits[index].active_connections, 0); index++; if (index >= NUMBER_OF_LIMITS) { warnx("Too many LIMIT entries (max is %d)\n", NUMBER_OF_LIMITS); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; } } else { warnx("Invalid LIMIT entry /%s:%d)", config->limit_path, lineno); } free(database); free(username); database = NULL; username = NULL; max_size = 0; } } } config->number_of_limits = index; fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; } /** * */ int pgagroal_validate_limit_configuration(void* shm) { int total_connections; struct configuration* config; total_connections = 0; config = (struct configuration*)shm; for (int i = 0; i < config->number_of_limits; i++) { total_connections += config->limits[i].max_size; if (config->limits[i].max_size <= 0) { pgagroal_log_fatal("max_size must be greater than 0 for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); return 1; } if (config->limits[i].initial_size < 0) { pgagroal_log_fatal("initial_size must be greater or equal to 0 for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); return 1; } if (config->limits[i].min_size < 0) { pgagroal_log_fatal("min_size must be greater or equal to 0 for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); return 1; } if (config->limits[i].initial_size > 0 || config->limits[i].min_size > 0) { bool user_found = false; for (int j = 0; j < config->number_of_users; j++) { if (!strcmp(config->limits[i].username, config->users[j].username)) { user_found = true; } } if (!user_found) { pgagroal_log_fatal("Unknown user '%s' for limit entry %d (%s:%d)", config->limits[i].username, i + 1, config->limit_path, config->limits[i].lineno); return 1; } if (config->limits[i].initial_size != 0 && config->limits[i].initial_size < config->limits[i].min_size) { pgagroal_log_warn("initial_size smaller than min_size for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); pgagroal_log_info("Adjusting initial_size from %d to %d (min_size) for limit entry %d (%s:%d)", config->limits[i].initial_size, config->limits[i].min_size, i + 1, config->limit_path, config->limits[i].lineno); config->limits[i].initial_size = config->limits[i].min_size; } if (config->limits[i].initial_size != 0 && config->limits[i].initial_size > config->limits[i].max_size) { pgagroal_log_warn("initial_size greater than max_size for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); pgagroal_log_info("Adjusting initial_size from %d to %d (max_size) for limit entry %d (%s:%d)", config->limits[i].initial_size, config->limits[i].max_size , i + 1, config->limit_path, config->limits[i].lineno); config->limits[i].initial_size = config->limits[i].max_size; } if (config->limits[i].max_size < config->limits[i].min_size) { pgagroal_log_warn("max_size smaller than min_size for limit entry %d (%s:%d)", i + 1, config->limit_path, config->limits[i].lineno); pgagroal_log_info("Adjusting min_size from %d to %d (max_size) for limit entry %d (%s:%d)", config->limits[i].min_size, config->limits[i].max_size , i + 1, config->limit_path, config->limits[i].lineno); config->limits[i].min_size = config->limits[i].max_size; } } } if (total_connections > config->max_connections) { pgagroal_log_fatal("pgagroal: LIMIT: Too many connections defined %d (max_connections = %d)", total_connections, config->max_connections); return 1; } return 0; } /** * */ int pgagroal_read_users_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* master_key = NULL; char* username = NULL; char* password = NULL; char* decoded = NULL; int decoded_length = 0; char* ptr = NULL; struct configuration* config; int status; file = fopen(filename, "r"); if (!file) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; goto error; } if (pgagroal_get_master_key(&master_key)) { status = PGAGROAL_CONFIGURATION_STATUS_KO; goto error; } index = 0; config = (struct configuration*)shm; while (fgets(line, sizeof(line), file)) { if (!is_empty_string(line) && !is_comment_line(line)) { ptr = strtok(line, ":"); username = ptr; ptr = strtok(NULL, ":"); if (ptr == NULL) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_base64_decode(ptr, strlen(ptr), &decoded, &decoded_length)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_decrypt(decoded, decoded_length, master_key, &password)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (strlen(username) < MAX_USERNAME_LENGTH && strlen(password) < MAX_PASSWORD_LENGTH) { memcpy(&config->users[index].username, username, strlen(username)); memcpy(&config->users[index].password, password, strlen(password)); } else { printf("pgagroal: Invalid USER entry\n"); printf("%s\n", line); } free(password); free(decoded); password = NULL; decoded = NULL; index++; } } config->number_of_users = index; if (config->number_of_users > NUMBER_OF_USERS) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; goto error; } free(master_key); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; error: free(master_key); free(password); free(decoded); if (file) { fclose(file); } return status; } /** * */ int pgagroal_validate_users_configuration(void* shm) { return 0; } /** * */ int pgagroal_read_frontend_users_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* master_key = NULL; char* username = NULL; char* password = NULL; char* decoded = NULL; int decoded_length = 0; char* ptr = NULL; struct configuration* config; int status = PGAGROAL_CONFIGURATION_STATUS_OK; file = fopen(filename, "r"); if (!file) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; goto error; } if (pgagroal_get_master_key(&master_key)) { status = PGAGROAL_CONFIGURATION_STATUS_KO; goto error; } index = 0; config = (struct configuration*)shm; while (fgets(line, sizeof(line), file)) { if (!is_empty_string(line) && !is_comment_line(line)) { ptr = strtok(line, ":"); username = ptr; ptr = strtok(NULL, ":"); if (ptr == NULL) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_base64_decode(ptr, strlen(ptr), &decoded, &decoded_length)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_decrypt(decoded, decoded_length, master_key, &password)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (strlen(username) < MAX_USERNAME_LENGTH && strlen(password) < MAX_PASSWORD_LENGTH) { memcpy(&config->frontend_users[index].username, username, strlen(username)); memcpy(&config->frontend_users[index].password, password, strlen(password)); } else { printf("pgagroal: Invalid FRONTEND USER entry\n"); printf("%s\n", line); } free(password); free(decoded); password = NULL; decoded = NULL; index++; } } config->number_of_frontend_users = index; if (config->number_of_frontend_users > NUMBER_OF_USERS) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; goto error; } free(master_key); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; error: free(master_key); free(password); free(decoded); if (file) { fclose(file); } return status; } /** * */ int pgagroal_validate_frontend_users_configuration(void* shm) { struct configuration* config; config = (struct configuration*)shm; for (int i = 0; i < config->number_of_frontend_users; i++) { bool found = false; char* f = &config->frontend_users[i].username[0]; for (int i = 0; !found && i < config->number_of_users; i++) { char* u = &config->users[i].username[0]; if (!strcmp(f, u)) { found = true; } } if (!found) { return 1; } } return 0; } /** * */ int pgagroal_read_admins_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* master_key = NULL; char* username = NULL; char* password = NULL; char* decoded = NULL; int decoded_length = 0; char* ptr = NULL; struct configuration* config; int status = PGAGROAL_CONFIGURATION_STATUS_OK; file = fopen(filename, "r"); if (!file) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; goto error; } if (pgagroal_get_master_key(&master_key)) { status = PGAGROAL_CONFIGURATION_STATUS_KO; goto error; } index = 0; config = (struct configuration*)shm; while (fgets(line, sizeof(line), file)) { if (!is_empty_string(line) && !is_comment_line(line)) { ptr = strtok(line, ":"); username = ptr; ptr = strtok(NULL, ":"); if (ptr == NULL) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_base64_decode(ptr, strlen(ptr), &decoded, &decoded_length)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_decrypt(decoded, decoded_length, master_key, &password)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (strlen(username) < MAX_USERNAME_LENGTH && strlen(password) < MAX_PASSWORD_LENGTH) { memcpy(&config->admins[index].username, username, strlen(username)); memcpy(&config->admins[index].password, password, strlen(password)); } else { printf("pgagroal: Invalid ADMIN entry\n"); printf("%s\n", line); } free(password); free(decoded); password = NULL; decoded = NULL; index++; } } config->number_of_admins = index; if (config->number_of_admins > NUMBER_OF_ADMINS) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; goto error; } free(master_key); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; error: free(master_key); free(password); free(decoded); if (file) { fclose(file); } return status; } /** * */ int pgagroal_validate_admins_configuration(void* shm) { struct configuration* config; config = (struct configuration*)shm; if (config->management > 0 && config->number_of_admins == 0) { pgagroal_log_warn("pgagroal: Remote management enabled, but no admins are defined"); } return 0; } int pgagroal_read_superuser_configuration(void* shm, char* filename) { FILE* file; char line[LINE_LENGTH]; int index; char* master_key = NULL; char* username = NULL; char* password = NULL; char* decoded = NULL; int decoded_length = 0; char* ptr = NULL; struct configuration* config; int status = PGAGROAL_CONFIGURATION_STATUS_OK; file = fopen(filename, "r"); if (!file) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND; goto error; } if (pgagroal_get_master_key(&master_key)) { status = PGAGROAL_CONFIGURATION_STATUS_KO; goto error; } index = 0; config = (struct configuration*)shm; while (fgets(line, sizeof(line), file)) { if (!is_empty_string(line) && !is_comment_line(line)) { if (index > 0) { status = PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG; goto error; } ptr = strtok(line, ":"); username = ptr; ptr = strtok(NULL, ":"); if (ptr == NULL) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_base64_decode(ptr, strlen(ptr), &decoded, &decoded_length)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (pgagroal_decrypt(decoded, decoded_length, master_key, &password)) { status = PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT; goto error; } if (strlen(username) < MAX_USERNAME_LENGTH && strlen(password) < MAX_PASSWORD_LENGTH) { memcpy(&config->superuser.username, username, strlen(username)); memcpy(&config->superuser.password, password, strlen(password)); } else { printf("pgagroal: Invalid SUPERUSER entry\n"); printf("%s\n", line); } free(password); free(decoded); password = NULL; decoded = NULL; index++; } } free(master_key); fclose(file); return PGAGROAL_CONFIGURATION_STATUS_OK; error: free(master_key); free(password); free(decoded); if (file) { fclose(file); } return status; } /** * */ int pgagroal_validate_superuser_configuration(void* shm) { return 0; } int pgagroal_reload_configuration(void) { size_t reload_size; struct configuration* reload = NULL; struct configuration* config; config = (struct configuration*)shmem; pgagroal_log_trace("Configuration: %s", config->configuration_path); pgagroal_log_trace("HBA: %s", config->hba_path); pgagroal_log_trace("Limit: %s", config->limit_path); pgagroal_log_trace("Users: %s", config->users_path); pgagroal_log_trace("Frontend users: %s", config->frontend_users_path); pgagroal_log_trace("Admins: %s", config->admins_path); pgagroal_log_trace("Superuser: %s", config->superuser_path); reload_size = sizeof(struct configuration); if (pgagroal_create_shared_memory(reload_size, HUGEPAGE_OFF, (void**)&reload)) { goto error; } pgagroal_init_configuration((void*)reload); if (pgagroal_read_configuration((void*)reload, config->configuration_path, true)) { goto error; } if (pgagroal_read_hba_configuration((void*)reload, config->hba_path)) { goto error; } if (strcmp("", config->limit_path)) { if (pgagroal_read_limit_configuration((void*)reload, config->limit_path)) { goto error; } } if (strcmp("", config->users_path)) { if (pgagroal_read_users_configuration((void*)reload, config->users_path)) { goto error; } } if (strcmp("", config->frontend_users_path)) { if (pgagroal_read_frontend_users_configuration((void*)reload, config->frontend_users_path)) { goto error; } } if (strcmp("", config->admins_path)) { if (pgagroal_read_admins_configuration((void*)reload, config->admins_path)) { goto error; } } if (strcmp("", config->superuser_path)) { if (pgagroal_read_superuser_configuration((void*)reload, config->superuser_path)) { goto error; } } if (pgagroal_validate_configuration(reload, false, false)) { goto error; } if (pgagroal_validate_hba_configuration(reload)) { goto error; } if (pgagroal_validate_limit_configuration(reload)) { goto error; } if (pgagroal_validate_users_configuration(reload)) { goto error; } if (pgagroal_validate_frontend_users_configuration(reload)) { goto error; } if (pgagroal_validate_admins_configuration(reload)) { goto error; } if (pgagroal_validate_superuser_configuration(reload)) { goto error; } if (transfer_configuration(config, reload) > 0) { goto error; } pgagroal_destroy_shared_memory((void*)reload, reload_size); pgagroal_log_debug("Reload: Success"); return 0; error: if (reload != NULL) { pgagroal_destroy_shared_memory((void*)reload, reload_size); } pgagroal_log_debug("Reload: Failure"); return 1; } /** * Given a line of text extracts the key part and the value. * Valid lines must have the form = . * * The key must be unquoted and cannot have any spaces * in front of it. * * Comments on the right side of a value are allowed. * * The value can be quoted, and this allows for inserting spaces * and comment signs. Quotes are '""' and '\''. * Example of valid lines are: * * foo = bar * foo=bar * foo= bar * foo = "bar" * foo = 'bar' * foo = "#bar" * foo = '#bar' * foo = bar # bar set! * foo = bar# bar set! * * * @param str the line of text incoming from the configuration file * @param key the pointer to where to store the key extracted from the line * @param value the pointer to where to store the value (unquoted) * @returns 1 if unable to parse the line, 0 if everything is ok */ static int extract_key_value(char* str, char** key, char** value) { int c = 0; int offset = 0; int length = strlen(str); char* k; char* v; char quoting_begin = '\0'; char quoting_end = '\0'; // the key does not allow spaces and is whatever is // on the left of the '=' while (str[c] != ' ' && str[c] != '=' && c < length) c++; if (c < length) { k = calloc(1, c + 1); if (k == NULL) { goto error; } memcpy(k, str, c); *key = k; while ((str[c] == ' ' || str[c] == '\t' || str[c] == '=') && c < length) c++; offset = c; // the value of the parameter starts from offset 'offset' while (str[c] != '\r' && str[c] != '\n' && c < length) { if (str[c] == '\'' || str[c] == '"') { if (quoting_begin == '\0') { quoting_begin = str[c]; offset = c + 1; // start at the very first character after the quote } else if (str[c] == quoting_begin && quoting_end == '\0') { quoting_end = str[c]; // end at the last character before the quote break; } } else if (str[c] == '#' || str[c] == ';') { if (quoting_begin == '\0' || (quoting_begin != '\0' && quoting_end != '\0')) { // a comment outside of quoted string, ignore anything else break; } } else if (str[c] == ' ') { if (quoting_begin == '\0' || (quoting_begin != '\0' && quoting_end != '\0')) { // space outside a quoted string, stop here break; } } c++; } // quotes must be the same! if (quoting_begin != '\0' && quoting_begin != quoting_end) { goto error; } if (c <= length) { v = calloc(1, (c - offset) + 1); if (v == NULL) { goto error; } memcpy(v, str + offset, (c - offset)); *value = v; return 0; } } error: return 1; } static int as_int(char* str, int* i) { char* endptr; long val; errno = 0; val = strtol(str, &endptr, 10); if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || (errno != 0 && val == 0)) { goto error; } if (str == endptr) { goto error; } if (*endptr != '\0') { goto error; } *i = (int)val; return 0; error: errno = 0; return 1; } static int as_bool(char* str, bool* b) { if (!strcasecmp(str, "true") || !strcasecmp(str, "on") || !strcasecmp(str, "yes") || !strcasecmp(str, "1")) { *b = true; return 0; } if (!strcasecmp(str, "false") || !strcasecmp(str, "off") || !strcasecmp(str, "no") || !strcasecmp(str, "0")) { *b = false; return 0; } return 1; } static int as_logging_type(char* str) { if (!strcasecmp(str, "console")) { return PGAGROAL_LOGGING_TYPE_CONSOLE; } if (!strcasecmp(str, "file")) { return PGAGROAL_LOGGING_TYPE_FILE; } if (!strcasecmp(str, "syslog")) { return PGAGROAL_LOGGING_TYPE_SYSLOG; } return PGAGROAL_LOGGING_TYPE_CONSOLE; } static int as_logging_level(char* str) { size_t size = 0; int debug_level = 1; char* debug_value = NULL; if (!strncasecmp(str, "debug", strlen("debug"))) { if (strlen(str) > strlen("debug")) { size = strlen(str) - strlen("debug"); debug_value = (char*)calloc(1, size + 1); if (debug_value == NULL) { return PGAGROAL_LOGGING_LEVEL_FATAL; } memcpy(debug_value, str + 5, size); if (as_int(debug_value, &debug_level)) { // cannot parse, set it to 1 debug_level = 1; } free(debug_value); } if (debug_level <= 1) { return PGAGROAL_LOGGING_LEVEL_DEBUG1; } else if (debug_level == 2) { return PGAGROAL_LOGGING_LEVEL_DEBUG2; } else if (debug_level == 3) { return PGAGROAL_LOGGING_LEVEL_DEBUG3; } else if (debug_level == 4) { return PGAGROAL_LOGGING_LEVEL_DEBUG4; } else if (debug_level >= 5) { return PGAGROAL_LOGGING_LEVEL_DEBUG5; } } if (!strcasecmp(str, "info")) { return PGAGROAL_LOGGING_LEVEL_INFO; } if (!strcasecmp(str, "warn")) { return PGAGROAL_LOGGING_LEVEL_WARN; } if (!strcasecmp(str, "error")) { return PGAGROAL_LOGGING_LEVEL_ERROR; } if (!strcasecmp(str, "fatal")) { return PGAGROAL_LOGGING_LEVEL_FATAL; } return PGAGROAL_LOGGING_LEVEL_INFO; } static int as_logging_mode(char* str) { if (!strcasecmp(str, "a") || !strcasecmp(str, "append")) { return PGAGROAL_LOGGING_MODE_APPEND; } if (!strcasecmp(str, "c") || !strcasecmp(str, "create")) { return PGAGROAL_LOGGING_MODE_CREATE; } return PGAGROAL_LOGGING_MODE_APPEND; } static int as_validation(char* str) { if (!strcasecmp(str, "off")) { return VALIDATION_OFF; } if (!strcasecmp(str, "foreground")) { return VALIDATION_FOREGROUND; } if (!strcasecmp(str, "background")) { return VALIDATION_BACKGROUND; } return VALIDATION_OFF; } static int as_pipeline(char* str) { if (!strcasecmp(str, "auto")) { return PIPELINE_AUTO; } if (!strcasecmp(str, "performance")) { return PIPELINE_PERFORMANCE; } if (!strcasecmp(str, "session")) { return PIPELINE_SESSION; } if (!strcasecmp(str, "transaction")) { return PIPELINE_TRANSACTION; } return PIPELINE_AUTO; } static int as_hugepage(char* str) { if (!strcasecmp(str, "off")) { return HUGEPAGE_OFF; } if (!strcasecmp(str, "try")) { return HUGEPAGE_TRY; } if (!strcasecmp(str, "on")) { return HUGEPAGE_ON; } return HUGEPAGE_OFF; } static void extract_hba(char* str, char** type, char** database, char** user, char** address, char** method) { int offset = 0; int length = strlen(str); offset = extract_value(str, offset, type); if (offset == -1 || offset >= length) { return; } offset = extract_value(str, offset, database); if (offset == -1 || offset >= length) { return; } offset = extract_value(str, offset, user); if (offset == -1 || offset >= length) { return; } offset = extract_value(str, offset, address); if (offset == -1 || offset >= length) { return; } extract_value(str, offset, method); } static void extract_limit(char* str, int server_max, char** database, char** user, int* max_size, int* initial_size, int* min_size) { int offset = 0; int length = strlen(str); char* value = NULL; *max_size = 0; *initial_size = 0; *min_size = 0; offset = extract_value(str, offset, database); if (offset == -1 || offset >= length) { return; } offset = extract_value(str, offset, user); if (offset == -1 || offset >= length) { return; } offset = extract_value(str, offset, &value); if (offset == -1) { return; } if (!strcasecmp("all", value)) { *max_size = server_max; } else { if (as_int(value, max_size)) { *max_size = -1; return; } } free(value); value = NULL; offset = extract_value(str, offset, &value); if (offset == -1) { return; } if (value != NULL && strcmp("", value) != 0) { if (!strcasecmp("all", value)) { *initial_size = server_max; } else { if (as_int(value, initial_size)) { *initial_size = 0; return; } } } free(value); value = NULL; offset = extract_value(str, offset, &value); if (offset == -1) { return; } if (value != NULL && strcmp("", value) != 0) { if (!strcasecmp("all", value)) { *min_size = server_max; } else { if (as_int(value, min_size)) { *min_size = 0; return; } } } free(value); } static int extract_value(char* str, int offset, char** value) { int from; int to; int length = strlen(str); char* v = NULL; while ((str[offset] == ' ' || str[offset] == '\t') && offset < length) offset++; if (offset < length) { from = offset; while ((str[offset] != ' ' && str[offset] != '\t' && str[offset] != '\r' && str[offset] != '\n') && offset < length) offset++; if (offset <= length) { to = offset; v = calloc(1, to - from + 1); if (v == NULL) { return -1; } memcpy(v, str + from, to - from); *value = v; return offset; } } return -1; } /** * Utility function to copy all the settings from the source configuration * to the destination one. This is useful for example when a reload * command is issued. * * @param config the new (clean) configuration * @param reload the one loaded from the configuration (i.e., the one to apply) * @return 0 on success, a negative number in the case some parameters cannot be changed * because require a restart (in such case, the value indicates the number of untouched * parameters), a positive value in the case of a dramatic error. */ static int transfer_configuration(struct configuration* config, struct configuration* reload) { #ifdef HAVE_LINUX sd_notify(0, "RELOADING=1"); #endif int unchanged = 0; memcpy(config->host, reload->host, MISC_LENGTH); config->port = reload->port; config->metrics = reload->metrics; config->metrics_cache_max_age = reload->metrics_cache_max_age; unchanged -= restart_int("metrics_cache_max_size", config->metrics_cache_max_size, reload->metrics_cache_max_size); config->management = reload->management; config->update_process_title = reload->update_process_title; /* gracefully */ /* disabled */ /* pipeline */ unchanged -= restart_int("pipeline", config->pipeline, reload->pipeline); config->failover = reload->failover; memcpy(config->failover_script, reload->failover_script, MISC_LENGTH); /* log_type */ restart_int("log_type", config->log_type, reload->log_type); config->log_level = reload->log_level; /* log_path */ // if the log main parameters have changed, we need // to restart the logging system if (strncmp(config->log_path, reload->log_path, MISC_LENGTH) || config->log_rotation_size != reload->log_rotation_size || config->log_rotation_age != reload->log_rotation_age || config->log_mode != reload->log_mode) { pgagroal_log_debug("Log restart triggered!"); pgagroal_stop_logging(); config->log_rotation_size = reload->log_rotation_size; config->log_rotation_age = reload->log_rotation_age; config->log_mode = reload->log_mode; memcpy(config->log_line_prefix, reload->log_line_prefix, MISC_LENGTH); memcpy(config->log_path, reload->log_path, MISC_LENGTH); pgagroal_start_logging(); } config->log_connections = reload->log_connections; config->log_disconnections = reload->log_disconnections; /* log_lock */ config->authquery = reload->authquery; config->tls = reload->tls; memcpy(config->tls_cert_file, reload->tls_cert_file, MISC_LENGTH); memcpy(config->tls_key_file, reload->tls_key_file, MISC_LENGTH); memcpy(config->tls_ca_file, reload->tls_ca_file, MISC_LENGTH); if (config->tls && (config->pipeline == PIPELINE_SESSION || config->pipeline == PIPELINE_TRANSACTION)) { if (pgagroal_tls_valid()) { pgagroal_log_fatal("pgagroal: Invalid TLS configuration"); exit(1); } } /* active_connections */ /* max_connections */ unchanged -= restart_int("max_connections", config->max_connections, reload->max_connections); config->allow_unknown_users = reload->allow_unknown_users; config->blocking_timeout = reload->blocking_timeout; config->idle_timeout = reload->idle_timeout; config->max_connection_age = reload->max_connection_age; config->validation = reload->validation; config->background_interval = reload->background_interval; config->max_retries = reload->max_retries; config->authentication_timeout = reload->authentication_timeout; config->disconnect_client = reload->disconnect_client; config->disconnect_client_force = reload->disconnect_client_force; /* pidfile */ restart_string("pidfile", config->pidfile, reload->pidfile, true); /* libev */ restart_string("libev", config->libev, reload->libev, true); config->buffer_size = reload->buffer_size; config->keep_alive = reload->keep_alive; config->nodelay = reload->nodelay; config->non_blocking = reload->non_blocking; config->backlog = reload->backlog; /* hugepage */ unchanged -= restart_int("hugepage", config->hugepage, reload->hugepage); config->tracker = reload->tracker; config->track_prepared_statements = reload->track_prepared_statements; /* unix_socket_dir */ // does make sense to check for remote connections? Because in the case the Unix socket dir // changes the pgagroal-cli probably will not be able to connect in any case! restart_string("unix_socket_dir", config->unix_socket_dir, reload->unix_socket_dir, false); /* su_connection */ /* states */ // decreasing the number of servers is probably a bad idea if (config->number_of_servers > reload->number_of_servers) { restart_int("decreasing number of servers", config->number_of_servers, reload->number_of_servers); } for (int i = 0; i < reload->number_of_servers; i++) { // check and emit restart warning only for not-added servers if (i < config->number_of_servers) { restart_server(&reload->servers[i], &config->servers[i]); } copy_server(&config->servers[i], &reload->servers[i]); } config->number_of_servers = reload->number_of_servers; // zero fill remaining memory that is unused memset(&config->servers[config->number_of_servers], 0, sizeof(struct server) * (NUMBER_OF_SERVERS - config->number_of_servers)); memset(&config->hbas[0], 0, sizeof(struct hba) * NUMBER_OF_HBAS); for (int i = 0; i < reload->number_of_hbas; i++) { copy_hba(&config->hbas[i], &reload->hbas[i]); } config->number_of_hbas = reload->number_of_hbas; /* number_of_limits */ /* limits */ unchanged -= restart_limit("limits", config, reload); memset(&config->users[0], 0, sizeof(struct user) * NUMBER_OF_USERS); for (int i = 0; i < reload->number_of_users; i++) { copy_user(&config->users[i], &reload->users[i]); } config->number_of_users = reload->number_of_users; memset(&config->frontend_users[0], 0, sizeof(struct user) * NUMBER_OF_USERS); for (int i = 0; i < reload->number_of_frontend_users; i++) { copy_user(&config->frontend_users[i], &reload->frontend_users[i]); } config->number_of_frontend_users = reload->number_of_frontend_users; memset(&config->admins[0], 0, sizeof(struct user) * NUMBER_OF_ADMINS); for (int i = 0; i < reload->number_of_admins; i++) { copy_user(&config->admins[i], &reload->admins[i]); } config->number_of_admins = reload->number_of_admins; memset(&config->superuser, 0, sizeof(struct user)); copy_user(&config->superuser, &reload->superuser); /* prometheus */ /* connections[] */ #ifdef HAVE_LINUX sd_notify(0, "READY=1"); #endif if (unchanged < 0) { pgagroal_log_warn("%d settings cannot be applied", unchanged * -1); } return unchanged; } /** * Checks if the configuration of the first server * is the same as the configuration of the second server. * So far it tests for the same connection string, meaning * that the hostname and the port must be the same (i.e., * pointing to the same endpoint). * It does not resolve the hostname, therefore 'localhost' and '127.0.0.1' * are considered as different hosts. * @return true if the server configurations look the same */ static bool is_same_server(struct server* s1, struct server* s2) { if (!strncmp(s1->host, s2->host, MISC_LENGTH) && s1->port == s2->port) { return true; } else { return false; } } static void copy_server(struct server* dst, struct server* src) { atomic_schar state; // check the server cloned "seems" the same if (is_same_server(dst, src)) { state = atomic_load(&dst->state); } else { state = SERVER_NOTINIT; } memset(dst, 0, sizeof(struct server)); memcpy(&dst->name[0], &src->name[0], MISC_LENGTH); memcpy(&dst->host[0], &src->host[0], MISC_LENGTH); dst->port = src->port; atomic_init(&dst->state, state); } static void copy_hba(struct hba* dst, struct hba* src) { memcpy(&dst->type[0], &src->type[0], MAX_TYPE_LENGTH); memcpy(&dst->database[0], &src->database[0], MAX_DATABASE_LENGTH); memcpy(&dst->username[0], &src->username[0], MAX_USERNAME_LENGTH); memcpy(&dst->address[0], &src->address[0], MAX_ADDRESS_LENGTH); memcpy(&dst->method[0], &src->method[0], MAX_ADDRESS_LENGTH); } static void copy_user(struct user* dst, struct user* src) { memcpy(&dst->username[0], &src->username[0], MAX_USERNAME_LENGTH); memcpy(&dst->password[0], &src->password[0], MAX_PASSWORD_LENGTH); } static int restart_int(char* name, int e, int n) { if (e != n) { pgagroal_log_info("Restart required for %s - Existing %d New %d", name, e, n); return 1; } return 0; } /** * Utility function to notify when a string parameter in the * configuration requires a restart. * Prints a line in the log when a restart is required. * * @param name the name of the parameter * @param e the existing (current) value of the parameter * @param n the new value * @param skip_non_existing if true it will ignore when 'n' is empty, * used when the parameter is automatically set * @return 0 when the parameter values are the same, 1 when it is required * a restart */ static int restart_string(char* name, char* e, char* n, bool skip_non_existing) { if (skip_non_existing && !strlen(n)) { return 0; } if (strcmp(e, n)) { pgagroal_log_info("Restart required for %s - Existing %s New %s", name, e, n); return 1; } return 0; } static int restart_limit(char* name, struct configuration* config, struct configuration* reload) { int ret; ret = restart_int("limits", config->number_of_limits, reload->number_of_limits); if (ret == 1) { goto error; } for (int i = 0; i < reload->number_of_limits; i++) { struct limit* e; struct limit* n; e = &config->limits[i]; n = &reload->limits[i]; if (strcmp(e->database, n->database) || strcmp(e->username, n->username) || e->max_size != n->max_size || e->initial_size != n->initial_size || e->min_size != n->min_size) { pgagroal_log_info("Restart required for limits"); goto error; } } return 0; error: return 1; } static int restart_server(struct server* src, struct server* dst) { char restart_message[2 * MISC_LENGTH]; if (!is_same_server(src, dst)) { snprintf(restart_message, sizeof(restart_message), "Server <%s>, parameter ", src->name); 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; } return 0; } static bool is_empty_string(char* s) { if (s == NULL) { return true; } if (!strcmp(s, "")) { return true; } for (int i = 0; i < strlen(s); i++) { if (s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n') { /* Ok */ } else { return false; } } return true; } /** * Parses a string to see if it contains * a valid value for log rotation size. * Returns 0 if parsing ok, 1 otherwise. * */ static int as_logging_rotation_size(char* str, unsigned int* size) { return as_bytes(str, size, PGAGROAL_LOGGING_ROTATION_DISABLED); } /** * Parses the log_rotation_age string. * The string accepts * - s for seconds * - m for minutes * - h for hours * - d for days * - w for weeks * * The default is expressed in seconds. * The function sets the number of rotationg age as minutes. * Returns 1 for errors, 0 for correct parsing. * */ static int as_logging_rotation_age(char* str, unsigned int* age) { return as_seconds(str, age, PGAGROAL_LOGGING_ROTATION_DISABLED); } void pgagroal_init_pidfile_if_needed(void) { struct configuration* config; config = (struct configuration*)shmem; if (strlen(config->pidfile) == 0) { // no pidfile set, use a default one snprintf(config->pidfile, sizeof(config->pidfile), "%s/pgagroal.%d.pid", config->unix_socket_dir, config->port); pgagroal_log_debug("PID file automatically set to: [%s]", config->pidfile); } } bool pgagroal_can_prefill(void) { struct configuration* config; config = (struct configuration*)shmem; if (config->number_of_users > 0 && config->number_of_limits > 0) { return true; } else { return false; } } /** * Function to check if the specified key belongs to the right section. * The idea is to pass all the values read from the configuration file, * and a boolean parameter to check if the section the parameter belongs is global or not. * A global section is the main `pgagroal` section, while a local section * is a custom user section, i.e., a server section. * * @param wanted the key we want to match against * @param section the section in which the key has been found * @param key the key read from the configuration file * @param global true if the `section` has to be `pgagroal` * @param unknown set to true if the key does match but the section does not. * For instance the key `host` found in a local section while required * to be global will set `unknown` to true. * This parameter can be omitted. * * @returns true if the key matches and the section is of the specified type. * * Example: * key_in_section("host", section, key, true, &unknown); // search for [pgagroal] -> host * key_in_section("port", section, key, false, &unknown); // search for server section -> port */ static bool key_in_section(char* wanted, char* section, char* key, bool global, bool* unknown) { // first of all, look for a key match if (strncmp(wanted, key, MISC_LENGTH)) { // no match at all return false; } // if here there is a match on the key, ensure the section is // appropriate if (global && !strncmp(section, PGAGROAL_MAIN_INI_SECTION, MISC_LENGTH)) { return true; } else if (!global && strlen(section) > 0) { return true; } else { if (unknown) { *unknown = true; } return false; } } /** * Function to see if the specified line is a comment line * and has to be ignored. * A comment line is a line that starts with '#' or ';' or * with spaces (or tabs) and a comment sign. * * @param line the line read from the file * @return true if the line is a full comment line */ static bool is_comment_line(char* line) { int c = 0; int length = strlen(line); while (c < length) { if (line[c] == '#' || line[c] == ';') { return true; } else if (line[c] != ' ' && line[c] != '\t') { break; } c++; } return false; } /** * Function to inspect a configuration line and detect if it handles a section. * If the line handles a section name, like `[pgagroal]` the function does set * the `section` argument, otherwise it does nothing. * * @param line the line to inspect * @param section the pointer to the string that will contain * the section name, only if the line handles a section, otherwise * the pointer will not be changed. * * @returns true if the line handles a section and the `section` pointer * has been changed */ static bool section_line(char* line, char* section) { size_t max; char* ptr = NULL; // if does not appear to be a section line do nothing! if (line[0] != '[') { return false; } ptr = strchr(line, ']'); if (ptr) { memset(section, 0, LINE_LENGTH); max = ptr - line - 1; if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(section, line + 1, max); return true; } return false; } /** * Parses an age string, providing the resulting value as seconds. * An age string is expressed by a number and a suffix that indicates * the multiplier. Accepted suffixes, case insensitive, are: * - s for seconds * - m for minutes * - h for hours * - d for days * - w for weeks * * The default is expressed in seconds. * * @param str the value to parse as retrieved from the configuration * @param age a pointer to the value that is going to store * the resulting number of seconds * @param default_age a value to set when the parsing is unsuccesful */ static unsigned int as_seconds(char* str, unsigned int* age, unsigned int default_age) { int multiplier = 1; int index; char value[MISC_LENGTH]; bool multiplier_set = false; int i_value = default_age; if (is_empty_string(str)) { *age = default_age; return 0; } index = 0; for (int i = 0; i < strlen(str); i++) { if (isdigit(str[i])) { value[index++] = str[i]; } else if (isalpha(str[i]) && multiplier_set) { // another extra char not allowed goto error; } else if (isalpha(str[i]) && !multiplier_set) { if (str[i] == 's' || str[i] == 'S') { multiplier = 1; multiplier_set = true; } else if (str[i] == 'm' || str[i] == 'M') { multiplier = 60; multiplier_set = true; } else if (str[i] == 'h' || str[i] == 'H') { multiplier = 3600; multiplier_set = true; } else if (str[i] == 'd' || str[i] == 'D') { multiplier = 24 * 3600; multiplier_set = true; } else if (str[i] == 'w' || str[i] == 'W') { multiplier = 24 * 3600 * 7; multiplier_set = true; } } else { // do not allow alien chars goto error; } } value[index] = '\0'; if (!as_int(value, &i_value)) { // sanity check: the value // must be a positive number! if (i_value >= 0) { *age = i_value * multiplier; } else { goto error; } return 0; } else { error: *age = default_age; return 1; } } /** * Converts a "size string" into the number of bytes. * * Valid strings have one of the suffixes: * - b for bytes (default) * - k for kilobytes * - m for megabytes * - g for gigabytes * * The default is expressed always as bytes. * Uppercase letters work too. * If no suffix is specified, the value is expressed as bytes. * * @param str the string to parse (e.g., "2M") * @param bytes the value to set as result of the parsing stage * @param default_bytes the default value to set when the parsing cannot proceed * @return 1 if parsing is unable to understand the string, 0 is parsing is * performed correctly (or almost correctly, e.g., empty string) */ static unsigned int as_bytes(char* str, unsigned int* bytes, unsigned int default_bytes) { int multiplier = 1; int index; char value[MISC_LENGTH]; bool multiplier_set = false; int i_value = default_bytes; if (is_empty_string(str)) { *bytes = default_bytes; return 0; } index = 0; for (int i = 0; i < strlen(str); i++) { if (isdigit(str[i])) { value[index++] = str[i]; } else if (isalpha(str[i]) && multiplier_set) { // allow a 'B' suffix on a multiplier // like for instance 'MB', but don't allow it // for bytes themselves ('BB') if (multiplier == 1 || (str[i] != 'b' && str[i] != 'B')) { // another non-digit char not allowed goto error; } } else if (isalpha(str[i]) && !multiplier_set) { if (str[i] == 'M' || str[i] == 'm') { multiplier = 1024 * 1024; multiplier_set = true; } else if (str[i] == 'G' || str[i] == 'g') { multiplier = 1024 * 1024 * 1024; multiplier_set = true; } else if (str[i] == 'K' || str[i] == 'k') { multiplier = 1024; multiplier_set = true; } else if (str[i] == 'B' || str[i] == 'b') { multiplier = 1; multiplier_set = true; } } else { // do not allow alien chars goto error; } } value[index] = '\0'; if (!as_int(value, &i_value)) { // sanity check: the value // must be a positive number! if (i_value >= 0) { *bytes = i_value * multiplier; } else { goto error; } return 0; } else { error: *bytes = default_bytes; return 1; } } /** * Utility function to understand the setting for updating * the process title. * * @param str the value obtained by the configuration parsing * @param policy the pointer to the value where the setting will be stored * @param default_policy a value to set when the configuration cannot be * understood * * @return 0 on success, 1 on error. In any case the `policy` variable is set to * `default_policy`. */ static unsigned int as_update_process_title(char* str, unsigned int* policy, unsigned int default_policy) { if (is_empty_string(str)) { *policy = default_policy; return 1; } if (!strncmp(str, "never", MISC_LENGTH) || !strncmp(str, "off", MISC_LENGTH)) { *policy = UPDATE_PROCESS_TITLE_NEVER; return 0; } else if (!strncmp(str, "strict", MISC_LENGTH)) { *policy = UPDATE_PROCESS_TITLE_STRICT; return 0; } else if (!strncmp(str, "minimal", MISC_LENGTH)) { *policy = UPDATE_PROCESS_TITLE_MINIMAL; return 0; } else if (!strncmp(str, "verbose", MISC_LENGTH) || !strncmp(str, "full", MISC_LENGTH)) { *policy = UPDATE_PROCESS_TITLE_VERBOSE; return 0; } else { // not a valid setting *policy = default_policy; return 1; } } int pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) { struct configuration* config; char section[MISC_LENGTH]; char context[MISC_LENGTH]; char key[MISC_LENGTH]; int begin = -1, end = -1; bool main_section; config = (struct configuration*)shmem; memset(section, 0, MISC_LENGTH); memset(context, 0, MISC_LENGTH); memset(key, 0, MISC_LENGTH); for (int i = 0; i < strlen(config_key); i++) { if (config_key[i] == '.') { if (!strlen(section)) { memcpy(section, &config_key[begin], end - begin + 1); section[end - begin + 1] = '\0'; begin = end = -1; continue; } else if (!strlen(context)) { memcpy(context, &config_key[begin], end - begin + 1); context[end - begin + 1] = '\0'; begin = end = -1; continue; } else if (!strlen(key)) { memcpy(key, &config_key[begin], end - begin + 1); key[end - begin + 1] = '\0'; begin = end = -1; continue; } } if (begin < 0) { begin = i; } end = i; } // if the key has not been found, since there is no ending dot, // try to extract it from the string if (!strlen(key)) { memcpy(key, &config_key[begin], end - begin + 1); key[end - begin + 1] = '\0'; } // force the main section, i.e., global parameters, if and only if // there is no section or section is 'pgagroal' without any subsection main_section = (!strlen(section) || !strncmp(section, "pgagroal", MISC_LENGTH)) && !strlen(context); if (!strncmp(section, "server", MISC_LENGTH)) { return pgagroal_write_server_config_value(buffer, context, key, buffer_size); } else if (!strncmp(section, "hba", MISC_LENGTH)) { return pgagroal_write_hba_config_value(buffer, context, key, buffer_size); } else if (!strncmp(section, "limit", MISC_LENGTH)) { return pgagroal_write_limit_config_value(buffer, context, key, buffer_size); } else if (main_section) { /* global configuration settings */ if (!strncmp(key, "host", MISC_LENGTH)) { return to_string(buffer, config->host, buffer_size); } else if (!strncmp(key, "port", MISC_LENGTH)) { return to_int(buffer, config->port); } else if (!strncmp(key, "log_type", MISC_LENGTH)) { return to_log_type(buffer, config->log_type); } else if (!strncmp(key, "log_mode", MISC_LENGTH)) { return to_log_mode(buffer, config->log_mode); } else if (!strncmp(key, "log_line_prefix", MISC_LENGTH)) { return to_string(buffer, config->log_line_prefix, buffer_size); } else if (!strncmp(key, "log_level", MISC_LENGTH)) { return to_log_level(buffer, config->log_level); } else if (!strncmp(key, "log_rotation_size", MISC_LENGTH)) { return to_int(buffer, config->log_rotation_size); } else if (!strncmp(key, "log_rotation_age", MISC_LENGTH)) { return to_int(buffer, config->log_rotation_age); } else if (!strncmp(key, "log_connections", MISC_LENGTH)) { return to_bool(buffer, config->log_connections); } else if (!strncmp(key, "log_disconnections", MISC_LENGTH)) { return to_bool(buffer, config->log_disconnections); } else if (!strncmp(key, "log_path", MISC_LENGTH)) { return to_string(buffer, config->log_path, buffer_size); } else if (!strncmp(key, "metrics", MISC_LENGTH)) { return to_int(buffer, config->metrics); } else if (!strncmp(key, "metrics_cache_max_age", MISC_LENGTH)) { return to_int(buffer, config->metrics_cache_max_age); } else if (!strncmp(key, "metrics_cache_max_size", MISC_LENGTH)) { return to_int(buffer, config->metrics_cache_max_size); } else if (!strncmp(key, "management", MISC_LENGTH)) { return to_int(buffer, config->management); } else if (!strncmp(key, "pipeline", MISC_LENGTH)) { return to_pipeline(buffer, config->pipeline); } else if (!strncmp(key, "failover_script", MISC_LENGTH)) { return to_string(buffer, config->failover_script, buffer_size); } else if (!strncmp(key, "tls", MISC_LENGTH)) { return to_bool(buffer, config->tls); } else if (!strncmp(key, "auth_query", MISC_LENGTH)) { return to_bool(buffer, config->authquery); } else if (!strncmp(key, "tls_ca_file", MISC_LENGTH)) { return to_string(buffer, config->tls_ca_file, buffer_size); } else if (!strncmp(key, "tls_cert_file", MISC_LENGTH)) { return to_string(buffer, config->tls_cert_file, buffer_size); } else if (!strncmp(key, "tls_key_file", MISC_LENGTH)) { return to_string(buffer, config->tls_key_file, buffer_size); } else if (!strncmp(key, "blocking_timeout", MISC_LENGTH)) { return to_int(buffer, config->blocking_timeout); } else if (!strncmp(key, "idle_timeout", MISC_LENGTH)) { return to_int(buffer, config->idle_timeout); } else if (!strncmp(key, "max_connection_age", MISC_LENGTH)) { return to_int(buffer, config->max_connection_age); } else if (!strncmp(key, "validation", MISC_LENGTH)) { return to_validation(buffer, config->validation); } else if (!strncmp(key, "update_process_title", MISC_LENGTH)) { return to_update_process_title(buffer, config->update_process_title); } else if (!strncmp(key, "background_interval", MISC_LENGTH)) { return to_int(buffer, config->background_interval); } else if (!strncmp(key, "max_retries", MISC_LENGTH)) { return to_int(buffer, config->max_retries); } else if (!strncmp(key, "authentication_timeout", MISC_LENGTH)) { return to_int(buffer, config->authentication_timeout); } else if (!strncmp(key, "disconnect_client", MISC_LENGTH)) { return to_int(buffer, config->disconnect_client); } else if (!strncmp(key, "pidfile", MISC_LENGTH)) { return to_string(buffer, config->pidfile, buffer_size); } else if (!strncmp(key, "allow_unknown_users", MISC_LENGTH)) { return to_bool(buffer, config->allow_unknown_users); } else if (!strncmp(key, "max_connections", MISC_LENGTH)) { return to_int(buffer, config->max_connections); } else if (!strncmp(key, "unix_socket_dir", MISC_LENGTH)) { return to_string(buffer, config->unix_socket_dir, buffer_size); } else if (!strncmp(key, "buffer_size", MISC_LENGTH)) { return to_int(buffer, config->buffer_size); } else if (!strncmp(key, "keep_alive", MISC_LENGTH)) { return to_bool(buffer, config->keep_alive); } else if (!strncmp(key, "nodelay", MISC_LENGTH)) { return to_int(buffer, config->nodelay); } else if (!strncmp(key, "non_blocking", MISC_LENGTH)) { return to_bool(buffer, config->non_blocking); } else if (!strncmp(key, "backlog", MISC_LENGTH)) { return to_int(buffer, config->backlog); } else if (!strncmp(key, "hugepage", MISC_LENGTH)) { return to_bool(buffer, config->hugepage); } else if (!strncmp(key, "track_prepared_statements", MISC_LENGTH)) { return to_bool(buffer, config->track_prepared_statements); } else { goto error; } } // end of global configuration settings else { goto error; } return 0; error: pgagroal_log_debug("Unknown configuration key <%s>", config_key); return 1; } /** * Function to extract a configuration value for a specific server. * @param server_name the name of the server * @param config_key one of the configuration keys allowed in the server section * @param buffer the buffer where to write the stringified version of the value * @param buffer_size the max size of the buffer where the result will be stored * @return 0 on success */ static int pgagroal_write_server_config_value(char* buffer, char* server_name, char* config_key, size_t buffer_size) { int server_index = -1; struct configuration* config; int state; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_SERVERS; i++) { if (!strncmp(config->servers[i].name, server_name, MISC_LENGTH)) { /* this is the right server */ server_index = i; break; } } if (server_index < 0 || server_index > NUMBER_OF_SERVERS) { pgagroal_log_debug("Unable to find a server named <%s> in the current configuration", server_name); goto error; } if (!strncmp(config_key, "host", MISC_LENGTH)) { return to_string(buffer, config->servers[server_index].host, buffer_size); } else if (!strncmp(config_key, "port", MISC_LENGTH)) { return to_int(buffer, config->servers[server_index].port); } else if (!strncmp(config_key, "primary", MISC_LENGTH)) { state = atomic_load(&config->servers[server_index].state); bool primary = false; switch (state) { case SERVER_NOTINIT_PRIMARY: case SERVER_PRIMARY: primary = true; break; default: primary = false; } 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; } error: return 1; } /** * Method to extract a configuration value for an HBA entry. * * Please note that seeking for a username does not provide all the * available configurations, since the same username could have been * listed multiple times. Only the first match is returned. * * @param buffer where to write the stringified value * @param username the username that must match the entry on the HBA entry line * @param config_key the configuration parameter to search for * @param buffer_size the max length of the destination buffer * @return 0 on success */ static int pgagroal_write_hba_config_value(char* buffer, char* username, char* config_key, size_t buffer_size) { int hba_index = -1; struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_HBAS; i++) { if (!strncmp(config->hbas[i].username, username, MISC_LENGTH)) { /* this is the right hba entry */ hba_index = i; break; } } if (hba_index < 0 || hba_index > NUMBER_OF_HBAS) { pgagroal_log_warn("Unable to find a user named <%s> in the current configuration", username); goto error; } if (!strncmp(config_key, "type", MISC_LENGTH)) { return to_string(buffer, config->hbas[hba_index].type, buffer_size); } else if (!strncmp(config_key, "database", MISC_LENGTH)) { return to_string(buffer, config->hbas[hba_index].database, buffer_size); } else if (!strncmp(config_key, "username", MISC_LENGTH)) { return to_string(buffer, config->hbas[hba_index].username, buffer_size); } else if (!strncmp(config_key, "address", MISC_LENGTH)) { return to_string(buffer, config->hbas[hba_index].address, buffer_size); } else if (!strncmp(config_key, "method", MISC_LENGTH)) { return to_string(buffer, config->hbas[hba_index].method, buffer_size); } else { goto error; } error: return 1; } /** * Given a specific username, retrieves the informations about the limit * configuration. The limit configuration is matched against a specific * database. * * @param buffer where to write the information * @param database the username to search for * @param config_key the value to seek into the limits * @param buffer_size the max size of the destination buffer where the result will be written * @return 0 on success */ static int pgagroal_write_limit_config_value(char* buffer, char* database, char* config_key, size_t buffer_size) { int limit_index = -1; struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_LIMITS; i++) { if (!strncmp(config->limits[i].database, database, MISC_LENGTH)) { /* this is the right database entry */ limit_index = i; break; } } if (limit_index < 0 || limit_index > NUMBER_OF_LIMITS) { pgagroal_log_warn("Unable to find a database named <%s> in the current limit configuration", database); goto error; } if (!strncmp(config_key, "username", MISC_LENGTH)) { return to_string(buffer, config->limits[limit_index].username, buffer_size); } else if (!strncmp(config_key, "database", MISC_LENGTH)) { return to_string(buffer, config->limits[limit_index].database, buffer_size); } else if (!strncmp(config_key, "max_size", MISC_LENGTH)) { return to_int(buffer, config->limits[limit_index].max_size); } else if (!strncmp(config_key, "min_size", MISC_LENGTH)) { return to_int(buffer, config->limits[limit_index].min_size); } else if (!strncmp(config_key, "initial_size", MISC_LENGTH)) { return to_int(buffer, config->limits[limit_index].initial_size); } else { goto error; } error: return 1; } /** * An utility function to place an integer value into a string. * @param where the string where to print the value, must be already allocated * @param value the value to convert into a string * @return 0 on success, 1 otherwise */ static int to_int(char* where, int value) { if (!where) { return 1; } snprintf(where, MISC_LENGTH, "%d", value); return 0; } /** * An utility function to place a boolean value into a string. * The value is always converted in either "on" or "off". * * @param where the string where to print the value, must be already allocated * @param value the value to convert into a string * @return 0 on success, 1 otherwise */ static int to_bool(char* where, bool value) { if (!where) { return 1; } snprintf(where, MISC_LENGTH, "%s", value ? "on" : "off"); return 0; } /** * An utility function to place a string into another string. * * In the case the string has inner spaces, such spaces are quoted. The function * tries to be as smart as possible identifying if there is the need for * single or double quotes. * * The function accepts the size of the destination string, and before writing * into such a string the result, it zero fills it. This means it is not mandatory * to zero fill the destination string before calling this function. * Also please note that if the string that is copied into the destination string * has a length bigger than that specified, the function will not copy any data * (and will not zero set the destination string, that will remain untouched!) * * @param where the string where to print the value, must be already allocated * @param value the value to convert into a string * @param max_length the max length of the 'where' destination string * @return 0 on success, 1 otherwise */ static int to_string(char* where, char* value, size_t max_length) { bool needs_quotes = false; bool has_double_quotes = false; bool has_single_quotes = false; char quoting_char = '\0'; int index = 0; if (!where || !value || strlen(value) >= max_length) { return 1; } // assume strings with spaces must be quoted for (int i = 0; i < strlen(value); i++) { if (value[i] == ' ') { needs_quotes = true; } else if (value[i] == '"') { has_double_quotes = true; } else if (value[i] == '\'') { has_single_quotes = true; } } needs_quotes = needs_quotes || has_double_quotes || has_single_quotes; if (needs_quotes) { // there must be space for quotes if (strlen(value) > max_length - 2 - 1) { return 1; } if (!has_single_quotes) { quoting_char = '\''; } else if (!has_double_quotes) { quoting_char = '"'; } } // if here, the size of the string is appropriate, // so do the copy memset(where, 0, max_length); if (needs_quotes) { memcpy(&where[index], "ing_char, sizeof(quoting_char)); index += sizeof(quoting_char); } memcpy(&where[index], value, strlen(value)); index += strlen(value); if (needs_quotes) { memcpy(&where[index], "ing_char, sizeof(quoting_char)); index += sizeof(quoting_char); } where[index] = '\0'; return 0; } /** * An utility function to convert the enumeration of values for the update_process_title * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->update_process_title setting * @return 0 on success, 1 otherwise */ static int to_update_process_title(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case UPDATE_PROCESS_TITLE_VERBOSE: snprintf(where, MISC_LENGTH, "%s", "verbose"); break; case UPDATE_PROCESS_TITLE_MINIMAL: snprintf(where, MISC_LENGTH, "%s", "minimal"); break; case UPDATE_PROCESS_TITLE_STRICT: snprintf(where, MISC_LENGTH, "%s", "strict"); break; case UPDATE_PROCESS_TITLE_NEVER: snprintf(where, MISC_LENGTH, "%s", "never"); break; } return 0; } /** * An utility function to convert the enumeration of values for the validation setting * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->validation setting * @return 0 on success, 1 otherwise */ static int to_validation(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case VALIDATION_OFF: snprintf(where, MISC_LENGTH, "%s", "off"); break; case VALIDATION_FOREGROUND: snprintf(where, MISC_LENGTH, "%s", "foreground"); break; case VALIDATION_BACKGROUND: snprintf(where, MISC_LENGTH, "%s", "background"); break; } return 0; } /** * An utility function to convert the enumeration of values for the pipeline setting * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->pipeline setting * @return 0 on success, 1 otherwise */ static int to_pipeline(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case PIPELINE_AUTO: snprintf(where, MISC_LENGTH, "%s", "auto"); break; case PIPELINE_SESSION: snprintf(where, MISC_LENGTH, "%s", "session"); break; case PIPELINE_TRANSACTION: snprintf(where, MISC_LENGTH, "%s", "transaction"); break; case PIPELINE_PERFORMANCE: snprintf(where, MISC_LENGTH, "%s", "performance"); break; } return 0; } /** * An utility function to convert the enumeration of values for the log_level setting * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->log_level setting * @return 0 on success, 1 otherwise */ static int to_log_level(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case PGAGROAL_LOGGING_LEVEL_DEBUG2: snprintf(where, MISC_LENGTH, "%s", "debug2"); break; case PGAGROAL_LOGGING_LEVEL_DEBUG1: snprintf(where, MISC_LENGTH, "%s", "debug"); break; case PGAGROAL_LOGGING_LEVEL_INFO: snprintf(where, MISC_LENGTH, "%s", "info"); break; case PGAGROAL_LOGGING_LEVEL_WARN: snprintf(where, MISC_LENGTH, "%s", "warn"); break; case PGAGROAL_LOGGING_LEVEL_ERROR: snprintf(where, MISC_LENGTH, "%s", "error"); break; case PGAGROAL_LOGGING_LEVEL_FATAL: snprintf(where, MISC_LENGTH, "%s", "fatal"); break; } return 0; } /** * An utility function to convert the enumeration of values for the log_level setting * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->log_mode setting * @return 0 on success, 1 otherwise */ static int to_log_mode(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case PGAGROAL_LOGGING_MODE_CREATE: snprintf(where, MISC_LENGTH, "%s", "create"); break; case PGAGROAL_LOGGING_MODE_APPEND: snprintf(where, MISC_LENGTH, "%s", "append"); break; } return 0; } /** * An utility function to convert the enumeration of values for the log_type setting * into one of its possible string descriptions. * * @param where the buffer used to store the stringy thing * @param value the config->log_type setting * @return 0 on success, 1 otherwise */ static int to_log_type(char* where, int value) { if (!where || value < 0) { return 1; } switch (value) { case PGAGROAL_LOGGING_TYPE_CONSOLE: snprintf(where, MISC_LENGTH, "%s", "console"); break; case PGAGROAL_LOGGING_TYPE_FILE: snprintf(where, MISC_LENGTH, "%s", "file"); break; case PGAGROAL_LOGGING_TYPE_SYSLOG: snprintf(where, MISC_LENGTH, "%s", "syslog"); break; } return 0; } int pgagroal_apply_main_configuration(struct configuration* config, struct 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); atomic_store(&srv->state, SERVER_NOTINIT); } 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; } atomic_store(&srv->state, SERVER_NOTINIT); } else if (key_in_section("primary", section, key, false, &unknown)) { bool b = false; if (as_bool(value, &b)) { unknown = true; } if (b) { atomic_store(&srv->state, SERVER_NOTINIT_PRIMARY); } else { atomic_store(&srv->state, SERVER_NOTINIT); } } else if (key_in_section("metrics", section, key, true, &unknown)) { if (as_int(value, &config->metrics)) { unknown = true; } } else if (key_in_section("metrics_cache_max_age", section, key, true, &unknown)) { if (as_seconds(value, &config->metrics_cache_max_age, PGAGROAL_PROMETHEUS_CACHE_DISABLED)) { unknown = true; } } else if (key_in_section("metrics_cache_max_size", section, key, true, &unknown)) { if (as_bytes(value, &config->metrics_cache_max_size, PROMETHEUS_DEFAULT_CACHE_SIZE)) { unknown = true; } } else if (key_in_section("management", section, key, true, &unknown)) { if (as_int(value, &config->management)) { unknown = true; } } else if (key_in_section("pipeline", section, key, true, &unknown)) { config->pipeline = as_pipeline(value); } else if (key_in_section("failover", section, key, true, &unknown)) { if (as_bool(value, &config->failover)) { unknown = true; } } else if (key_in_section("failover_script", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->failover_script, value, max); } else if (key_in_section("auth_query", section, key, true, &unknown)) { if (as_bool(value, &config->authquery)) { unknown = true; } } else if (key_in_section("tls", section, key, true, &unknown)) { if (as_bool(value, &config->tls)) { unknown = true; } } 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, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } 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); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } 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); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } 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)) { if (as_int(value, &config->blocking_timeout)) { unknown = true; } } else if (key_in_section("idle_timeout", section, key, true, &unknown)) { if (as_int(value, &config->idle_timeout)) { unknown = true; } } else if (key_in_section("max_connection_age", section, key, true, &unknown)) { if (as_int(value, &config->max_connection_age)) { unknown = true; } } else if (key_in_section("validation", section, key, true, &unknown)) { config->validation = as_validation(value); } else if (key_in_section("background_interval", section, key, true, &unknown)) { if (as_int(value, &config->background_interval)) { unknown = true; } } else if (key_in_section("max_retries", section, key, true, &unknown)) { if (as_int(value, &config->max_retries)) { unknown = true; } } else if (key_in_section("authentication_timeout", section, key, true, &unknown)) { if (as_int(value, &config->authentication_timeout)) { unknown = true; } } else if (key_in_section("disconnect_client", section, key, true, &unknown)) { if (as_int(value, &config->disconnect_client)) { unknown = true; } } else if (key_in_section("disconnect_client_force", section, key, true, &unknown)) { if (as_bool(value, &config->disconnect_client_force)) { unknown = true; } } else if (key_in_section("pidfile", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->pidfile, value, max); } else if (key_in_section("allow_unknown_users", section, key, true, &unknown)) { if (as_bool(value, &config->allow_unknown_users)) { unknown = true; } } else if (key_in_section("log_type", section, key, true, &unknown)) { config->log_type = as_logging_type(value); } else if (key_in_section("log_level", section, key, true, &unknown)) { config->log_level = as_logging_level(value); } else if (key_in_section("log_path", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->log_path, value, max); } else if (key_in_section("log_rotation_size", section, key, true, &unknown)) { if (as_logging_rotation_size(value, &config->log_rotation_size)) { unknown = true; } } else if (key_in_section("log_rotation_age", section, key, true, &unknown)) { if (as_logging_rotation_age(value, &config->log_rotation_age)) { unknown = true; } } else if (key_in_section("log_line_prefix", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->log_line_prefix, value, max); } else if (key_in_section("log_connections", section, key, true, &unknown)) { if (as_bool(value, &config->log_connections)) { unknown = true; } } else if (key_in_section("log_disconnections", section, key, true, &unknown)) { if (as_bool(value, &config->log_disconnections)) { unknown = true; } } else if (key_in_section("log_mode", section, key, true, &unknown)) { config->log_mode = as_logging_mode(value); } else if (key_in_section("max_connections", section, key, true, &unknown)) { if (as_int(value, &config->max_connections)) { unknown = true; } } else if (key_in_section("unix_socket_dir", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->unix_socket_dir, value, max); } else if (key_in_section("libev", section, key, true, &unknown)) { max = strlen(value); if (max > MISC_LENGTH - 1) { max = MISC_LENGTH - 1; } memcpy(config->libev, value, max); } else if (key_in_section("buffer_size", section, key, true, &unknown)) { if (as_int(value, &config->buffer_size)) { unknown = true; } if (config->buffer_size > MAX_BUFFER_SIZE) { config->buffer_size = MAX_BUFFER_SIZE; } } else if (key_in_section("keep_alive", section, key, true, &unknown)) { if (as_bool(value, &config->keep_alive)) { unknown = true; } } else if (key_in_section("nodelay", section, key, true, &unknown)) { if (as_bool(value, &config->nodelay)) { unknown = true; } } else if (key_in_section("non_blocking", section, key, true, &unknown)) { if (as_bool(value, &config->non_blocking)) { unknown = true; } } else if (key_in_section("backlog", section, key, true, &unknown)) { if (as_int(value, &config->backlog)) { unknown = true; } } else if (key_in_section("hugepage", section, key, true, &unknown)) { config->hugepage = as_hugepage(value); } else if (key_in_section("tracker", section, key, true, &unknown)) { if (as_bool(value, &config->tracker)) { unknown = true; } } else if (key_in_section("track_prepared_statements", section, key, true, &unknown)) { if (as_bool(value, &config->track_prepared_statements)) { unknown = true; } } else if (key_in_section("update_process_title", section, key, true, &unknown)) { if (as_update_process_title(value, &config->update_process_title, UPDATE_PROCESS_TITLE_VERBOSE)) { unknown = false; } } else { unknown = true; } if (unknown) { return 1; } else { return 0; } } int pgagroal_apply_configuration(char* config_key, char* config_value) { struct configuration* config; struct configuration* current_config; char section[MISC_LENGTH]; char context[MISC_LENGTH]; char key[MISC_LENGTH]; int begin = -1, end = -1; bool main_section; size_t config_size = 0; struct server* srv_dst; struct server* srv_src; // get the currently running configuration current_config = (struct configuration*)shmem; // create a new configuration that will be the clone of the previous one config_size = sizeof(struct configuration); if (pgagroal_create_shared_memory(config_size, HUGEPAGE_OFF, (void**)&config)) { goto error; } // copy the configuration that is currently running memcpy(config, current_config, config_size); memset(section, 0, MISC_LENGTH); memset(context, 0, MISC_LENGTH); memset(key, 0, MISC_LENGTH); for (int i = 0; i < strlen(config_key); i++) { if (config_key[i] == '.') { if (!strlen(section)) { memcpy(section, &config_key[begin], end - begin + 1); section[end - begin + 1] = '\0'; begin = end = -1; continue; } else if (!strlen(context)) { memcpy(context, &config_key[begin], end - begin + 1); context[end - begin + 1] = '\0'; begin = end = -1; continue; } else if (!strlen(key)) { memcpy(key, &config_key[begin], end - begin + 1); key[end - begin + 1] = '\0'; begin = end = -1; continue; } } if (begin < 0) { begin = i; } end = i; } // if the key has not been found, since there is no ending dot, // try to extract it from the string if (!strlen(key)) { memcpy(key, &config_key[begin], end - begin + 1); key[end - begin + 1] = '\0'; } // force the main section, i.e., global parameters, if and only if // there is no section or section is 'pgagroal' without any subsection main_section = (!strlen(section) || !strncmp(section, PGAGROAL_MAIN_INI_SECTION, MISC_LENGTH)) && !strlen(context); if (!strncmp(section, PGAGROAL_CONF_SERVER_PREFIX, MISC_LENGTH)) { srv_src = srv_dst = NULL; // server.. // here the 'context' is the server name, so let's find it for (int i = 0; i < config->number_of_servers; i++) { if (!strncmp(config->servers[i].name, context, MISC_LENGTH)) { pgagroal_log_debug("Changing configuration of server <%s>: (%s) %s -> %s", config->servers[i].name, config_key, key, config_value); srv_dst = calloc(1, sizeof(struct server)); srv_src = &config->servers[i]; // clone the current server memcpy(srv_dst, srv_src, sizeof(struct server)); if (pgagroal_apply_main_configuration(config, srv_dst, context, key, config_value)) { goto error; } // now that changes have been applied, see if the server // requires a restart: in such case abort the configuration // change if (restart_server(srv_dst, srv_src)) { goto error; } break; // avoid searching for another server section } } memcpy(srv_src, srv_dst, sizeof(struct server)); srv_src = srv_dst = NULL; } else if (!strncmp(section, PGAGROAL_CONF_HBA_PREFIX, MISC_LENGTH)) { // hba.. // here the context is the username // and the section is the 'hba', while the key is what the user wants to change for (int i = 0; i < config->number_of_hbas; i++) { if (!strncmp(config->hbas[i].username, context, MISC_LENGTH)) { // this is the correct HBA entry, apply the changes pgagroal_log_debug("Trying to change HBA configuration setting <%s> to <%s>", key, config_value); if (pgagroal_apply_hba_configuration(&config->hbas[i], key, config_value)) { goto error; } break; // avoid searching for another HBA entry } } } else if (!strncmp(section, PGAGROAL_CONF_LIMIT_PREFIX, MISC_LENGTH)) { // limit.. // the context is the username and the key is what to change for (int i = 0; i < config->number_of_limits; i++) { if (!strncmp(config->limits[i].username, context, MISC_LENGTH)) { // this is the correct limit entry, apply the changes // WARNING: according to restart_limit() every change to a limit entry // requires a restart, so it does not make a lot of sense to apply a configuration change pgagroal_log_debug("Trying to change limit configuration setting <%s> to <%s>", key, config_value); if (pgagroal_apply_limit_configuration_string(&config->limits[i], key, config_value)) { goto error; } break; // avoid searching for another HBA entry } } // return pgagroal_write_limit_config_value(buffer, context, key); } else if (main_section) { pgagroal_log_debug("Trying to change main configuration setting <%s> to <%s>", config_key, config_value); if (pgagroal_apply_main_configuration(config, NULL, PGAGROAL_MAIN_INI_SECTION, config_key, config_value)) { goto error; } } else { // if here, an error happened! goto error; } if (pgagroal_validate_configuration(config, false, false)) { goto error; } if (transfer_configuration(current_config, config)) { goto error; } if (pgagroal_destroy_shared_memory((void*)config, config_size)) { goto error; } // all done return 0; error: if (config != NULL) { memcpy(config, current_config, sizeof(struct configuration)); pgagroal_destroy_shared_memory((void*)config, config_size); } return 1; } /** * Utility function to set an HBA single entry. * The HBA entry must be already allocated. * * Before applying a setting, the field is zeroed. * * @param hba the entry to modify * @param context the entry to modify, e.g., "method" or a constant like PGAGRAOL_HBA_ENTRY_DATABASE * @param value the value to set * * @return 0 on success, 1 on failure */ static int pgagroal_apply_hba_configuration(struct hba* hba, char* context, char* value) { if (!hba || !context || !strlen(context) || !value || !strlen(value)) { goto error; } if (!strncmp(context, PGAGROAL_HBA_ENTRY_TYPE, MAX_TYPE_LENGTH) && strlen(value) < MAX_TYPE_LENGTH) { memset(&(hba->type), 0, strlen(hba->type)); memcpy(&(hba->type), value, strlen(value)); } else if (!strncmp(context, PGAGROAL_HBA_ENTRY_DATABASE, MAX_DATABASE_LENGTH) && strlen(value) < MAX_DATABASE_LENGTH) { memset(&(hba->database), 0, strlen(hba->database)); memcpy(&(hba->database), value, strlen(value)); } else if (!strncmp(context, PGAGROAL_HBA_ENTRY_USERNAME, MAX_USERNAME_LENGTH) && strlen(value) < MAX_USERNAME_LENGTH) { memset(&(hba->username), 0, strlen(hba->username)); memcpy(&(hba->username), value, strlen(value)); } else if (!strncmp(context, PGAGROAL_HBA_ENTRY_ADDRESS, MAX_ADDRESS_LENGTH) && strlen(value) < MAX_ADDRESS_LENGTH) { memset(&(hba->address), 0, strlen(hba->address)); memcpy(&(hba->address), value, strlen(value)); } else if (!strncmp(context, PGAGROAL_HBA_ENTRY_METHOD, MAX_ADDRESS_LENGTH) && strlen(value) < MAX_ADDRESS_LENGTH) { memset(&(hba->method), 0, strlen(hba->method)); memcpy(&(hba->method), value, strlen(value)); } return 0; error: return 1; } /** * An utility function to set a single value for the limit struct. * The structure must already be allocated. * * @param limit the structure to change * @param context the key of the field to change, e.g., 'max_size' or a constant like PGAGROAL_LIMIT_ENTRY_DATABASE * @param value the new value to set * * @return 0 on success. */ static int pgagroal_apply_limit_configuration_string(struct limit* limit, char* context, char* value) { if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_DATABASE, MAX_DATABASE_LENGTH) && strlen(value) < MAX_DATABASE_LENGTH) { memset(&limit->database, 0, strlen(limit->database)); memcpy(&limit->database, value, strlen(value)); } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_USERNAME, MAX_USERNAME_LENGTH) && strlen(value) < MAX_USERNAME_LENGTH) { memset(&limit->username, 0, strlen(limit->username)); memcpy(&limit->username, value, strlen(value)); } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MAX_SIZE, MISC_LENGTH)) { return as_int(value, &limit->max_size); } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MIN_SIZE, MISC_LENGTH)) { return as_int(value, &limit->min_size); } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, MISC_LENGTH)) { return as_int(value, &limit->initial_size); } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_LINENO, MISC_LENGTH)) { return as_int(value, &limit->lineno); } else { goto error; } return 0; error: return 1; } static int pgagroal_apply_limit_configuration_int(struct limit* limit, char* context, int value) { if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MAX_SIZE, MISC_LENGTH)) { limit->max_size = value; } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_MIN_SIZE, MISC_LENGTH)) { limit->min_size = value; } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE, MISC_LENGTH)) { limit->initial_size = value; } else if (!strncmp(context, PGAGROAL_LIMIT_ENTRY_LINENO, MISC_LENGTH)) { limit->lineno = value; } else { goto error; } return 0; error: return 1; } pgagroal-1.6.0/src/libpgagroal/json.c000066400000000000000000000153271456565230200175210ustar00rootroot00000000000000/* * 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; } pgagroal-1.6.0/src/libpgagroal/logging.c000066400000000000000000000271101456565230200201670ustar00rootroot00000000000000/* * 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 /* system */ #include #include #include #include #include #include #include #include #include #define LINE_LENGTH 32 FILE* log_file; time_t next_log_rotation_age; /* number of seconds at which the next location will happen */ char current_log_path[MAX_PATH]; /* the current log file */ static const char* levels[] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; static const char* colors[] = { "\x1b[37m", "\x1b[36m", "\x1b[32m", "\x1b[91m", "\x1b[31m", "\x1b[35m" }; bool log_rotation_enabled(void) { struct configuration* config; config = (struct configuration*)shmem; // disable log rotation in the case // logging is not to a file if (config->log_type != PGAGROAL_LOGGING_TYPE_FILE) { log_rotation_disable(); return false; } // log rotation is enabled if either log_rotation_age or // log_rotation_size is enabled return config->log_rotation_age != PGAGROAL_LOGGING_ROTATION_DISABLED || config->log_rotation_size != PGAGROAL_LOGGING_ROTATION_DISABLED; } void log_rotation_disable(void) { struct configuration* config; config = (struct configuration*)shmem; config->log_rotation_age = PGAGROAL_LOGGING_ROTATION_DISABLED; config->log_rotation_size = PGAGROAL_LOGGING_ROTATION_DISABLED; next_log_rotation_age = 0; } bool log_rotation_required(void) { struct stat log_stat; struct configuration* config; config = (struct configuration*)shmem; if (!log_rotation_enabled()) { return false; } if (stat(current_log_path, &log_stat)) { return false; } if (config->log_rotation_size > 0 && log_stat.st_size >= config->log_rotation_size) { return true; } if (config->log_rotation_age > 0 && next_log_rotation_age > 0 && next_log_rotation_age <= log_stat.st_ctime) { return true; } return false; } bool log_rotation_set_next_rotation_age(void) { struct configuration* config; time_t now; config = (struct configuration*)shmem; if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE && config->log_rotation_age > 0) { now = time(NULL); if (!now) { config->log_rotation_age = PGAGROAL_LOGGING_ROTATION_DISABLED; return false; } next_log_rotation_age = now + config->log_rotation_age; return true; } else { config->log_rotation_age = PGAGROAL_LOGGING_ROTATION_DISABLED; return false; } } /** * */ int pgagroal_init_logging(void) { struct configuration* config; config = (struct configuration*)shmem; if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE) { log_file_open(); if (!log_file) { printf("Failed to open log file %s due to %s\n", strlen(config->log_path) > 0 ? config->log_path : "pgagroal.log", strerror(errno)); errno = 0; log_rotation_disable(); return 1; } } return 0; } /** * */ int pgagroal_start_logging(void) { struct configuration* config; config = (struct configuration*)shmem; if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE && !log_file) { log_file_open(); if (!log_file) { printf("Failed to open log file %s due to %s\n", strlen(config->log_path) > 0 ? config->log_path : "pgagroal.log", strerror(errno)); errno = 0; return 1; } } else if (config->log_type == PGAGROAL_LOGGING_TYPE_SYSLOG) { openlog("pgagroal", LOG_CONS | LOG_PERROR | LOG_PID, LOG_USER); } return 0; } int log_file_open(void) { struct configuration* config; time_t htime; struct tm* tm; config = (struct configuration*)shmem; if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE) { htime = time(NULL); if (!htime) { log_file = NULL; return 1; } tm = localtime(&htime); if (tm == NULL) { log_file = NULL; return 1; } if (strftime(current_log_path, sizeof(current_log_path), config->log_path, tm) <= 0) { // cannot parse the format string, fallback to default logging memcpy(current_log_path, "pgagroal.log", strlen("pgagroal.log")); log_rotation_disable(); } log_file = fopen(current_log_path, config->log_mode == PGAGROAL_LOGGING_MODE_APPEND ? "a" : "w"); if (!log_file) { return 1; } log_rotation_set_next_rotation_age(); return 0; } return 1; } void log_file_rotate(void) { if (log_rotation_enabled()) { fflush(log_file); fclose(log_file); log_file_open(); } } /** * */ int pgagroal_stop_logging(void) { struct configuration* config; config = (struct configuration*)shmem; if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE) { if (log_file != NULL) { return fclose(log_file); } else { return 1; } } else if (config->log_type == PGAGROAL_LOGGING_TYPE_SYSLOG) { closelog(); } return 0; } void pgagroal_log_line(int level, char* file, int line, char* fmt, ...) { signed char isfree; struct configuration* config; config = (struct configuration*)shmem; if (config == NULL) { return; } if (level >= config->log_level) { retry: isfree = STATE_FREE; if (atomic_compare_exchange_strong(&config->log_lock, &isfree, STATE_IN_USE)) { char buf[256]; va_list vl; struct tm* tm; time_t t; char* filename; t = time(NULL); tm = localtime(&t); filename = strrchr(file, '/'); if (filename != NULL) { filename = filename + 1; } else { filename = file; } if (strlen(config->log_line_prefix) == 0) { memcpy(config->log_line_prefix, PGAGROAL_LOGGING_DEFAULT_LOG_LINE_PREFIX, strlen(PGAGROAL_LOGGING_DEFAULT_LOG_LINE_PREFIX)); } va_start(vl, fmt); if (config->log_type == PGAGROAL_LOGGING_TYPE_CONSOLE) { buf[strftime(buf, sizeof(buf), config->log_line_prefix, tm)] = '\0'; fprintf(stdout, "%s %s%-5s\x1b[0m \x1b[90m%s:%d\x1b[0m ", buf, colors[level - 1], levels[level - 1], filename, line); vfprintf(stdout, fmt, vl); fprintf(stdout, "\n"); fflush(stdout); } else if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE) { buf[strftime(buf, sizeof(buf), config->log_line_prefix, tm)] = '\0'; fprintf(log_file, "%s %-5s %s:%d ", buf, levels[level - 1], filename, line); vfprintf(log_file, fmt, vl); fprintf(log_file, "\n"); fflush(log_file); if (log_rotation_required()) { log_file_rotate(); } } else if (config->log_type == PGAGROAL_LOGGING_TYPE_SYSLOG) { switch (level) { case PGAGROAL_LOGGING_LEVEL_DEBUG5: vsyslog(LOG_DEBUG, fmt, vl); break; case PGAGROAL_LOGGING_LEVEL_DEBUG1: vsyslog(LOG_DEBUG, fmt, vl); break; case PGAGROAL_LOGGING_LEVEL_INFO: vsyslog(LOG_INFO, fmt, vl); break; case PGAGROAL_LOGGING_LEVEL_WARN: vsyslog(LOG_WARNING, fmt, vl); break; case PGAGROAL_LOGGING_LEVEL_ERROR: vsyslog(LOG_ERR, fmt, vl); break; case PGAGROAL_LOGGING_LEVEL_FATAL: vsyslog(LOG_CRIT, fmt, vl); break; default: vsyslog(LOG_INFO, fmt, vl); break; } } va_end(vl); atomic_store(&config->log_lock, STATE_FREE); } else { SLEEP_AND_GOTO(1000000L, retry) } } } void pgagroal_log_mem(void* data, size_t size) { signed char isfree; struct configuration* config; config = (struct configuration*)shmem; if (config == NULL) { return; } if (config->log_level == PGAGROAL_LOGGING_LEVEL_DEBUG5 && size > 0 && (config->log_type == PGAGROAL_LOGGING_TYPE_CONSOLE || config->log_type == PGAGROAL_LOGGING_TYPE_FILE)) { retry: isfree = STATE_FREE; if (atomic_compare_exchange_strong(&config->log_lock, &isfree, STATE_IN_USE)) { char buf[(3 * size) + (2 * ((size / LINE_LENGTH) + 1)) + 1 + 1]; int j = 0; int k = 0; memset(&buf, 0, sizeof(buf)); for (int i = 0; i < size; i++) { if (k == LINE_LENGTH) { buf[j] = '\n'; j++; k = 0; } sprintf(&buf[j], "%02X", (signed char) *((char*)data + i)); j += 2; k++; } buf[j] = '\n'; j++; k = 0; for (int i = 0; i < size; i++) { signed char c = (signed char) *((char*)data + i); if (k == LINE_LENGTH) { buf[j] = '\n'; j++; k = 0; } if (c >= 32 && c <= 127) { buf[j] = c; } else { buf[j] = '?'; } j++; k++; } if (config->log_type == PGAGROAL_LOGGING_TYPE_CONSOLE) { fprintf(stdout, "%s", buf); fprintf(stdout, "\n"); fflush(stdout); } else if (config->log_type == PGAGROAL_LOGGING_TYPE_FILE) { fprintf(log_file, "%s", buf); fprintf(log_file, "\n"); fflush(log_file); } atomic_store(&config->log_lock, STATE_FREE); } else { SLEEP_AND_GOTO(1000000L, retry) } } } pgagroal-1.6.0/src/libpgagroal/management.c000066400000000000000000002000311456565230200206500ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #define MANAGEMENT_HEADER_SIZE 5 static int read_complete(SSL* ssl, int socket, void* buf, size_t size); static int write_complete(SSL* ssl, int socket, void* buf, size_t size); 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) { char header[MANAGEMENT_HEADER_SIZE]; if (read_complete(NULL, socket, &header[0], sizeof(header))) { pgagroal_log_warn("pgagroal_management_read_header: %d %s", socket, strerror(errno)); errno = 0; goto error; } *id = pgagroal_read_byte(&(header)); *slot = pgagroal_read_int32(&(header[1])); return 0; error: *id = -1; *slot = -1; return 1; } int pgagroal_management_read_payload(int socket, signed char id, int* payload_i, char** payload_s) { int nr; char* s = NULL; char buf2[2]; char buf4[4]; int size; struct cmsghdr* cmptr = NULL; struct iovec iov[1]; struct msghdr msg; *payload_i = -1; *payload_s = NULL; switch (id) { case MANAGEMENT_TRANSFER_CONNECTION: case MANAGEMENT_CLIENT_FD: memset(&buf2[0], 0, sizeof(buf2)); iov[0].iov_base = &buf2[0]; iov[0].iov_len = sizeof(buf2); cmptr = calloc(1, CMSG_SPACE(sizeof(int))); if (cmptr == NULL) { goto error; } cmptr->cmsg_len = CMSG_LEN(sizeof(int)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = cmptr; msg.msg_controllen = CMSG_SPACE(sizeof(int)); msg.msg_flags = 0; if ((nr = recvmsg(socket, &msg, 0)) < 0) { goto error; } else if (nr == 0) { goto error; } *payload_i = *(int*)CMSG_DATA(cmptr); free(cmptr); break; case MANAGEMENT_FLUSH: if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; } *payload_i = pgagroal_read_int32(&buf4); if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; } size = pgagroal_read_int32(&buf4); s = calloc(1, size + 1); if (s == NULL) { goto error; } if (read_complete(NULL, socket, s, size)) { goto error; } *payload_s = s; break; case MANAGEMENT_KILL_CONNECTION: case MANAGEMENT_CLIENT_DONE: case MANAGEMENT_REMOVE_FD: if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; } *payload_i = pgagroal_read_int32(&buf4); break; case MANAGEMENT_ENABLEDB: case MANAGEMENT_DISABLEDB: case MANAGEMENT_CONFIG_GET: case MANAGEMENT_CONFIG_SET: if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; } *payload_i = pgagroal_read_int32(&buf4); s = calloc(1, *payload_i + 1); if (s == NULL) { goto error; } if (read_complete(NULL, socket, s, *payload_i)) { goto error; } *payload_s = s; break; case MANAGEMENT_RESET_SERVER: case MANAGEMENT_SWITCH_TO: s = calloc(1, MISC_LENGTH); if (s == NULL) { goto error; } if (read_complete(NULL, socket, s, MISC_LENGTH)) { goto error; } *payload_s = s; break; case MANAGEMENT_RETURN_CONNECTION: case MANAGEMENT_GRACEFULLY: case MANAGEMENT_STOP: case MANAGEMENT_CANCEL_SHUTDOWN: case MANAGEMENT_STATUS: case MANAGEMENT_DETAILS: case MANAGEMENT_RESET: case MANAGEMENT_RELOAD: case MANAGEMENT_CONFIG_LS: break; default: goto error; break; } return 0; error: if (cmptr) { free(cmptr); } return 1; } int pgagroal_management_transfer_connection(int32_t slot) { int fd; struct configuration* config; struct cmsghdr* cmptr = NULL; struct iovec iov[1]; struct msghdr msg; char buf2[2]; config = (struct configuration*)shmem; if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &fd)) { pgagroal_log_warn("pgagroal_management_transfer_connection: connect: %d", fd); errno = 0; goto error; } if (write_header(NULL, fd, MANAGEMENT_TRANSFER_CONNECTION, slot)) { pgagroal_log_warn("pgagroal_management_transfer_connection: write: %d", fd); errno = 0; goto error; } /* Write file descriptor */ memset(&buf2[0], 0, sizeof(buf2)); iov[0].iov_base = &buf2[0]; iov[0].iov_len = sizeof(buf2); cmptr = calloc(1, CMSG_SPACE(sizeof(int))); if (cmptr == NULL) { goto error; } cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; cmptr->cmsg_len = CMSG_LEN(sizeof(int)); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = cmptr; msg.msg_controllen = CMSG_SPACE(sizeof(int)); msg.msg_flags = 0; *(int*)CMSG_DATA(cmptr) = config->connections[slot].fd; if (sendmsg(fd, &msg, 0) != 2) { goto error; } free(cmptr); pgagroal_disconnect(fd); return 0; error: if (cmptr) { free(cmptr); } pgagroal_disconnect(fd); pgagroal_kill_connection(slot, NULL); return 1; } int pgagroal_management_return_connection(int32_t slot) { int fd; struct configuration* config; config = (struct configuration*)shmem; if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &fd)) { pgagroal_log_warn("pgagroal_management_return_connection: connect: %d", fd); errno = 0; goto error; } if (write_header(NULL, fd, MANAGEMENT_RETURN_CONNECTION, slot)) { pgagroal_log_warn("pgagroal_management_return_connection: write: %d", fd); errno = 0; goto error; } pgagroal_disconnect(fd); return 0; error: pgagroal_disconnect(fd); return 1; } int pgagroal_management_kill_connection(int32_t slot, int socket) { int fd; char buf[4]; struct configuration* config; config = (struct configuration*)shmem; if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &fd)) { pgagroal_log_warn("pgagroal_management_kill_connection: connect: %d", fd); errno = 0; goto error; } if (write_header(NULL, fd, MANAGEMENT_KILL_CONNECTION, slot)) { pgagroal_log_warn("pgagroal_management_kill_connection: write: %d", fd); errno = 0; goto error; } pgagroal_write_int32(&buf, socket); if (write_complete(NULL, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_kill_connection: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } pgagroal_disconnect(fd); return 0; error: pgagroal_disconnect(fd); return 1; } int pgagroal_management_flush(SSL* ssl, int fd, int32_t mode, char* database) { char buf[4]; if (write_header(ssl, fd, MANAGEMENT_FLUSH, -1)) { pgagroal_log_warn("pgagroal_management_flush: write: %d", fd); errno = 0; goto error; } pgagroal_write_int32(&buf, mode); if (write_complete(ssl, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_flush: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } pgagroal_write_int32(&buf, strlen(database)); if (write_complete(ssl, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_flush: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } if (write_complete(ssl, fd, database, strlen(database))) { pgagroal_log_warn("pgagroal_management_flush: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_enabledb(SSL* ssl, int fd, char* database) { char buf[4]; if (write_header(ssl, fd, MANAGEMENT_ENABLEDB, -1)) { pgagroal_log_warn("pgagroal_management_enabledb: write: %d", fd); errno = 0; goto error; } pgagroal_write_int32(&buf, strlen(database)); if (write_complete(ssl, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_enabledb: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } if (write_complete(ssl, fd, database, strlen(database))) { pgagroal_log_warn("pgagroal_management_enabledb: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_disabledb(SSL* ssl, int fd, char* database) { char buf[4]; if (write_header(ssl, fd, MANAGEMENT_DISABLEDB, -1)) { pgagroal_log_warn("pgagroal_management_disabledb: write: %d", fd); errno = 0; goto error; } pgagroal_write_int32(&buf, strlen(database)); if (write_complete(ssl, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_disabledb: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } if (write_complete(ssl, fd, database, strlen(database))) { pgagroal_log_warn("pgagroal_management_disabledb: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_gracefully(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_GRACEFULLY, -1)) { pgagroal_log_warn("pgagroal_management_gracefully: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_stop(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_STOP, -1)) { pgagroal_log_warn("pgagroal_management_stop: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_cancel_shutdown(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_CANCEL_SHUTDOWN, -1)) { pgagroal_log_warn("pgagroal_management_cancel_shutdown: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_status(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_STATUS, -1)) { pgagroal_log_warn("pgagroal_management_status: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int 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]; int status; 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_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_json_read_status_details: read: %d %s", socket, strerror(errno)); goto error; } status = pgagroal_read_int32(&buf); active = pgagroal_read_int32(&(buf[4])); total = pgagroal_read_int32(&(buf[8])); max = pgagroal_read_int32(&(buf[12])); // 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++) { if (strcmp(disabled[i], "")) { if (!strcmp(disabled[i], "*")) { cJSON_AddItemToArray(databases_array, cJSON_CreateString("ALL")); counter = -1; } else { cJSON_AddItemToArray(databases_array, cJSON_CreateString(disabled[i])); counter++; } } } 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); // the 'status' command ends here if (!include_details) { goto end; } /*********** '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 pgagroal_management_write_status(int socket, bool graceful) { char buf[16]; int active; int total; struct configuration* config; memset(&buf, 0, sizeof(buf)); active = 0; total = 0; config = (struct configuration*)shmem; if (!graceful) { pgagroal_write_int32(&buf, 1); } else { pgagroal_write_int32(&buf, 2); } for (int i = 0; i < config->max_connections; i++) { int state = atomic_load(&config->states[i]); switch (state) { case STATE_IN_USE: case STATE_GRACEFULLY: active++; case STATE_INIT: case STATE_FREE: case STATE_FLUSH: case STATE_IDLE_CHECK: case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: total++; break; default: break; } } pgagroal_write_int32(&(buf[4]), active); pgagroal_write_int32(&(buf[8]), total); pgagroal_write_int32(&(buf[12]), config->max_connections); if (write_complete(NULL, socket, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_write_status: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } if (write_complete(NULL, socket, &config->disabled, sizeof(config->disabled))) { pgagroal_log_warn("pgagroal_management_write_status: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_details(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_DETAILS, -1)) { pgagroal_log_warn("pgagroal_management_details: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_read_details(SSL* ssl, int socket, char output_format) { cJSON* json = pgagroal_management_json_read_status_details(ssl, socket, true); // 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_details: command error [%s]", (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); return 1; } int pgagroal_management_write_details(int socket) { char header[12 + MAX_NUMBER_OF_CONNECTIONS]; struct configuration* config; config = (struct configuration*)shmem; memset(&header, 0, sizeof(header)); pgagroal_write_int32(header, config->max_connections); pgagroal_write_int32(header + 4, config->number_of_limits); pgagroal_write_int32(header + 8, config->number_of_servers); for (int i = 0; i < config->max_connections; i++) { signed char state = atomic_load(&config->states[i]); header[12 + i] = (char)state; } if (write_complete(NULL, socket, header, sizeof(header))) { pgagroal_log_warn("pgagroal_management_write_details: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } for (int i = 0; i < config->number_of_servers; i++) { char server[5 + MISC_LENGTH + MISC_LENGTH]; memset(&server, 0, sizeof(server)); pgagroal_write_string(server, config->servers[i].name); pgagroal_write_string(server + MISC_LENGTH, config->servers[i].host); pgagroal_write_int32(server + MISC_LENGTH + MISC_LENGTH, config->servers[i].port); pgagroal_write_byte(server + MISC_LENGTH + MISC_LENGTH + 4, atomic_load(&config->servers[i].state)); if (write_complete(NULL, socket, server, sizeof(server))) { pgagroal_log_warn("pgagroal_management_write_details: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } } for (int i = 0; i < config->number_of_limits; i++) { char limit[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]; memset(&limit, 0, sizeof(limit)); pgagroal_write_int32(limit, atomic_load(&config->limits[i].active_connections)); pgagroal_write_int32(limit + 4, config->limits[i].max_size); pgagroal_write_int32(limit + 8, config->limits[i].initial_size); pgagroal_write_int32(limit + 12, config->limits[i].min_size); pgagroal_write_string(limit + 16, config->limits[i].database); pgagroal_write_string(limit + 16 + MAX_DATABASE_LENGTH, config->limits[i].username); if (write_complete(NULL, socket, &limit, sizeof(limit))) { pgagroal_log_warn("pgagroal_management_write_details: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } } for (int i = 0; i < config->max_connections; i++) { char details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH + MAX_APPLICATION_NAME]; memset(&details, 0, sizeof(details)); pgagroal_write_long(details, (long)config->connections[i].start_time); pgagroal_write_long(details, (long)config->connections[i].timestamp); pgagroal_write_int32(details + 8, (int)config->connections[i].pid); pgagroal_write_int32(details + 12, (int)config->connections[i].fd); pgagroal_write_string(details + 16, config->connections[i].database); pgagroal_write_string(details + 16 + MAX_DATABASE_LENGTH, config->connections[i].username); pgagroal_write_string(details + 16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH, config->connections[i].appname); if (write_complete(NULL, socket, &details, sizeof(details))) { pgagroal_log_warn("pgagroal_management_write_details: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } } return 0; error: return 1; } int pgagroal_management_isalive(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_ISALIVE, -1)) { pgagroal_log_warn("pgagroal_management_isalive: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format) { char buf[4]; memset(&buf, 0, sizeof(buf)); if (read_complete(ssl, socket, &buf[0], sizeof(buf))) { pgagroal_log_warn("pgagroal_management_read_isalive: read: %d %s", socket, strerror(errno)); errno = 0; goto error; } *status = pgagroal_read_int32(&buf); // 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); cJSON_AddNumberToObject(output, "status", *status); 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, PING_STATUS_RUNNING); } else { pgagroal_write_int32(buf, PING_STATUS_SHUTDOWN_GRACEFULLY); } if (write_complete(NULL, socket, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_write_isalive: write: %d %s", socket, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_reset(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_RESET, -1)) { pgagroal_log_warn("pgagroal_management_reset: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_reset_server(SSL* ssl, int fd, char* server) { char name[MISC_LENGTH]; if (write_header(ssl, fd, MANAGEMENT_RESET_SERVER, -1)) { pgagroal_log_warn("pgagroal_management_reset_server: write: %d", fd); errno = 0; goto error; } memset(&name[0], 0, MISC_LENGTH); memcpy(&name[0], server, strlen(server)); if (write_complete(ssl, fd, &name[0], sizeof(name))) { pgagroal_log_warn("pgagroal_management_reset_server_: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_client_done(pid_t pid) { char buf[4]; int fd; struct configuration* config; config = (struct configuration*)shmem; if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &fd)) { pgagroal_log_warn("pgagroal_management_client_done: connect: %d", fd); errno = 0; goto error; } if (write_header(NULL, fd, MANAGEMENT_CLIENT_DONE, -1)) { pgagroal_log_warn("pgagroal_management_client_done: write: %d", fd); errno = 0; goto error; } memset(&buf, 0, sizeof(buf)); pgagroal_write_int32(buf, pid); if (write_complete(NULL, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_client_done: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } pgagroal_disconnect(fd); return 0; error: pgagroal_disconnect(fd); return 1; } int pgagroal_management_client_fd(int32_t slot, pid_t pid) { char p[MISC_LENGTH]; int fd; struct configuration* config; struct cmsghdr* cmptr = NULL; struct iovec iov[1]; struct msghdr msg; char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */ config = (struct configuration*)shmem; memset(&p, 0, sizeof(p)); snprintf(&p[0], sizeof(p), ".s.%d", pid); if (pgagroal_connect_unix_socket(config->unix_socket_dir, &p[0], &fd)) { pgagroal_log_debug("pgagroal_management_client_fd: connect: %d", fd); errno = 0; goto unavailable; } if (write_header(NULL, fd, MANAGEMENT_CLIENT_FD, slot)) { pgagroal_log_warn("pgagroal_management_client_fd: write: %d", fd); errno = 0; goto error; } /* Write file descriptor */ iov[0].iov_base = buf; iov[0].iov_len = 2; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; cmptr = malloc(CMSG_LEN(sizeof(int))); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; cmptr->cmsg_len = CMSG_LEN(sizeof(int)); msg.msg_control = cmptr; msg.msg_controllen = CMSG_LEN(sizeof(int)); *(int*)CMSG_DATA(cmptr) = config->connections[slot].fd; buf[1] = 0; /* zero status means OK */ buf[0] = 0; /* null byte flag to recv_fd() */ if (sendmsg(fd, &msg, 0) != 2) { goto error; } free(cmptr); pgagroal_disconnect(fd); return 0; unavailable: free(cmptr); pgagroal_disconnect(fd); return 1; error: free(cmptr); pgagroal_disconnect(fd); pgagroal_kill_connection(slot, NULL); return 1; } int pgagroal_management_switch_to(SSL* ssl, int fd, char* server) { char name[MISC_LENGTH]; if (write_header(ssl, fd, MANAGEMENT_SWITCH_TO, -1)) { pgagroal_log_warn("pgagroal_management_switch_to: write: %d", fd); errno = 0; goto error; } memset(&name[0], 0, MISC_LENGTH); memcpy(&name[0], server, strlen(server)); if (write_complete(ssl, fd, &name[0], sizeof(name))) { pgagroal_log_warn("pgagroal_management_switch_to: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_reload(SSL* ssl, int fd) { if (write_header(ssl, fd, MANAGEMENT_RELOAD, -1)) { pgagroal_log_warn("pgagroal_management_reload: write: %d", fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_management_remove_fd(int32_t slot, int socket, pid_t pid) { char p[MISC_LENGTH]; int fd; char buf[4]; struct configuration* config; config = (struct configuration*)shmem; if (atomic_load(&config->states[slot]) == STATE_NOTINIT) { return 0; } memset(&p, 0, sizeof(p)); snprintf(&p[0], sizeof(p), ".s.%d", pid); if (pgagroal_connect_unix_socket(config->unix_socket_dir, &p[0], &fd)) { pgagroal_log_debug("pgagroal_management_remove_fd: slot %d state %d database %s user %s socket %d pid %d connect: %d", slot, atomic_load(&config->states[slot]), config->connections[slot].database, config->connections[slot].username, socket, pid, fd); errno = 0; goto error; } if (write_header(NULL, fd, MANAGEMENT_REMOVE_FD, slot)) { pgagroal_log_warn("pgagroal_management_remove_fd: write: %d", fd); errno = 0; goto error; } pgagroal_write_int32(&buf, socket); if (write_complete(NULL, fd, &buf, sizeof(buf))) { pgagroal_log_warn("pgagroal_management_remove_fd: write: %d %s", fd, strerror(errno)); errno = 0; goto error; } pgagroal_disconnect(fd); return 0; error: pgagroal_disconnect(fd); return 1; } static int read_complete(SSL* ssl, int socket, void* buf, size_t size) { ssize_t r; size_t offset; size_t needs; int retries; offset = 0; needs = size; retries = 0; read: if (ssl == NULL) { r = read(socket, buf + offset, needs); } else { r = SSL_read(ssl, buf + offset, needs); } if (r == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { errno = 0; goto read; } goto error; } else if (r < needs) { SLEEP(10000000L) pgagroal_log_trace("Got: %ld, needs: %ld", r, needs); if (retries < 100) { offset += r; needs -= r; retries++; goto read; } else { errno = EINVAL; goto error; } } return 0; error: return 1; } static int write_complete(SSL* ssl, int socket, void* buf, size_t size) { if (ssl == NULL) { return write_socket(socket, buf, size); } return write_ssl(ssl, buf, size); } static int write_socket(int socket, void* buf, size_t size) { bool keep_write = false; ssize_t numbytes; int offset; ssize_t totalbytes; ssize_t remaining; numbytes = 0; offset = 0; totalbytes = 0; remaining = size; do { numbytes = write(socket, buf + offset, remaining); if (likely(numbytes == size)) { return 0; } else if (numbytes != -1) { offset += numbytes; totalbytes += numbytes; remaining -= numbytes; if (totalbytes == size) { return 0; } pgagroal_log_debug("Write %d - %zd/%zd vs %zd", socket, numbytes, totalbytes, size); keep_write = true; errno = 0; } else { switch (errno) { case EAGAIN: keep_write = true; errno = 0; break; default: keep_write = false; break; } } } while (keep_write); return 1; } static int write_ssl(SSL* ssl, void* buf, size_t size) { bool keep_write = false; ssize_t numbytes; int offset; ssize_t totalbytes; ssize_t remaining; numbytes = 0; offset = 0; totalbytes = 0; remaining = size; do { numbytes = SSL_write(ssl, buf + offset, remaining); if (likely(numbytes == size)) { return 0; } else if (numbytes > 0) { offset += numbytes; totalbytes += numbytes; remaining -= numbytes; if (totalbytes == size) { return 0; } pgagroal_log_debug("SSL/Write %d - %zd/%zd vs %zd", SSL_get_fd(ssl), numbytes, totalbytes, size); keep_write = true; errno = 0; } else { int err = SSL_get_error(ssl, numbytes); switch (err) { case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: #ifndef HAVE_OPENBSD #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) case SSL_ERROR_WANT_ASYNC: case SSL_ERROR_WANT_ASYNC_JOB: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) case SSL_ERROR_WANT_CLIENT_HELLO_CB: #endif #endif #endif errno = 0; keep_write = true; break; case SSL_ERROR_SYSCALL: pgagroal_log_error("SSL_ERROR_SYSCALL: %s (%d)", strerror(errno), SSL_get_fd(ssl)); errno = 0; keep_write = false; break; case SSL_ERROR_SSL: pgagroal_log_error("SSL_ERROR_SSL: %s (%d)", strerror(errno), SSL_get_fd(ssl)); errno = 0; keep_write = false; break; } ERR_clear_error(); if (!keep_write) { return 1; } } } while (keep_write); return 1; } static int write_header(SSL* ssl, int fd, signed char type, int slot) { char header[MANAGEMENT_HEADER_SIZE]; pgagroal_write_byte(&(header), type); pgagroal_write_int32(&(header[1]), slot); return write_complete(ssl, fd, &(header), MANAGEMENT_HEADER_SIZE); } int pgagroal_management_config_get(SSL* ssl, int socket, char* config_key) { char buf[4]; size_t size; // security check: avoid writing something null or with too much stuff! if (!config_key || !strlen(config_key)) { pgagroal_log_debug("pgagroal_management_config_get: no key specified"); goto error; } size = strlen(config_key) + 1; if (size > MISC_LENGTH) { pgagroal_log_debug("pgagroal_management_config_get: key <%s> too big (%d bytes)", config_key, size); goto error; } // send the header for this command if (write_header(ssl, socket, MANAGEMENT_CONFIG_GET, -1)) { pgagroal_log_debug("pgagroal_management_config_get: write error on header for key <%s> on socket %d", config_key, socket); goto error; } // send the size of the payload memset(&buf, 0, sizeof(buf)); pgagroal_write_int32(&buf, size); if (write_complete(ssl, socket, &buf, sizeof(buf))) { pgagroal_log_debug("pgagroal_management_config_get: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", size, config_key, socket, strerror(errno)); goto error; } // send the effective payload, i.e., the configuration parameter name to get memset(&buf, 0, sizeof(buf)); if (write_complete(ssl, socket, config_key, size)) { pgagroal_log_debug("pgagroal_management_config_get: write error sending the configuration name <%s> over socket %d: %s", config_key, socket, strerror(errno)); goto error; } return 0; error: errno = 0; return 1; } int pgagroal_management_write_config_get(int socket, char* config_key) { char data[MISC_LENGTH]; char buf[4]; size_t size; if (!config_key || !strlen(config_key)) { pgagroal_log_debug("pgagroal_management_write_config_get: no key specified"); goto error; } size = strlen(config_key) + 1; if (size > MISC_LENGTH) { pgagroal_log_debug("pgagroal_management_write_config_get: key <%s> too big (%d bytes)", config_key, size); goto error; } memset(&data, 0, sizeof(data)); if (pgagroal_write_config_value(&data[0], config_key, sizeof(data))) { pgagroal_log_debug("pgagroal_management_write_config_get: unknwon configuration key <%s>", config_key); // leave the payload empty, so a zero filled payload will be sent } // send the size of the payload memset(&buf, 0, sizeof(buf)); size = strlen(data) + 1; pgagroal_write_int32(&buf, size); if (write_complete(NULL, socket, &buf, sizeof(buf))) { pgagroal_log_debug("pgagroal_management_write_config_get: write error for the size of the payload <%s> (%d bytes for <%s>, socket %d): %s", data, size, config_key, socket, strerror(errno)); goto error; } if (write_complete(NULL, socket, data, size)) { pgagroal_log_debug("pgagroal_management_write_config_get (%s): write: %d %s", config_key, socket, strerror(errno)); goto error; } return 0; error: errno = 0; return 1; } /** * 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; 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 pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* config_value) { char buf[4]; size_t size; // security check: avoid writing something null or with too much stuff! if (!config_key || !strlen(config_key) || !config_value || !strlen(config_value)) { pgagroal_log_debug("pgagroal_management_config_set: no key or value specified"); goto error; } if (strlen(config_key) > MISC_LENGTH - 1 || strlen(config_value) > MISC_LENGTH - 1) { pgagroal_log_debug("pgagroal_management_config_set: key <%s> or value <%s> too big (max %d bytes)", config_key, config_value, MISC_LENGTH); goto error; } // send the header for this command if (write_header(ssl, socket, MANAGEMENT_CONFIG_SET, -1)) { pgagroal_log_debug("pgagroal_management_config_set: write error on header for key <%s> on socket %d", config_key, socket); goto error; } /* * send a message with the size of the key, the key * then the size of the value and the value */ // send the size of the payload for the config key memset(&buf, 0, sizeof(buf)); size = strlen(config_key) + 1; pgagroal_write_int32(&buf, size); if (write_complete(ssl, socket, &buf, sizeof(buf))) { pgagroal_log_debug("pgagroal_management_config_set: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", size, config_key, socket, strerror(errno)); goto error; } // send the effective payload, i.e., the configuration parameter name to get memset(&buf, 0, sizeof(buf)); if (write_complete(ssl, socket, config_key, size)) { pgagroal_log_debug("pgagroal_management_config_set: write error sending the configuration name <%s> over socket %d: %s", config_key, socket, strerror(errno)); goto error; } // send the size of the payload for the config value memset(&buf, 0, sizeof(buf)); size = strlen(config_value) + 1; pgagroal_write_int32(&buf, size); if (write_complete(ssl, socket, &buf, sizeof(buf))) { pgagroal_log_debug("pgagroal_management_config_set: write error for the size of the payload (%d bytes for <%s>, socket %d): %s", size, config_value, socket, strerror(errno)); goto error; } // send the effective payload, i.e., the configuration value to set memset(&buf, 0, sizeof(buf)); if (write_complete(ssl, socket, config_value, size)) { pgagroal_log_warn("pgagroal_management_config_set: write error sending the configuration value <%s> over socket %d: %s", config_value, socket, strerror(errno)); goto error; } return 0; error: errno = 0; return 1; } int pgagroal_management_write_config_set(int socket, char* config_key, char* config_value) { if (!config_key || !strlen(config_key) || !config_value || !strlen(config_value)) { pgagroal_log_warn("pgagroal_management_write_config_set: no key or value specified"); goto error; } if (strlen(config_key) > MISC_LENGTH - 1 || strlen(config_value) > MISC_LENGTH - 1) { pgagroal_log_warn("pgagroal_management_write_config_set: key <%s> or value <%s> too big (max %d bytes)", config_key, config_value, MISC_LENGTH); goto error; } pgagroal_log_debug("pgagroal_management_write_config_set: trying to set <%s> to <%s>", config_key, config_value); // do set the configuration value if (pgagroal_apply_configuration(config_key, config_value)) { pgagroal_log_debug("pgagroal_management_write_config_set: unable to apply changes to <%s> -> <%s>", config_key, config_value); } // query back the status of the parameter // and send it over the socket return pgagroal_management_write_config_get(socket, config_key); error: errno = 0; 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; } pgagroal-1.6.0/src/libpgagroal/memory.c000066400000000000000000000062371456565230200200600ustar00rootroot00000000000000/* * 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 #include #include #ifdef DEBUG #include #endif /* system */ #ifdef DEBUG #include #endif #include #include #include static struct message* message = NULL; static void* data = NULL; /** * */ void pgagroal_memory_init(void) { struct configuration* config; #ifdef DEBUG assert(shmem != NULL); #endif config = (struct configuration*)shmem; pgagroal_memory_size(config->buffer_size); } /** * */ void pgagroal_memory_size(size_t size) { pgagroal_memory_destroy(); message = (struct message*)calloc(1, sizeof(struct message)); if (message == NULL) { goto error; } data = calloc(1, size); if (data == NULL) { goto error; } message->kind = 0; message->length = 0; message->max_length = size; message->data = data; return; error: pgagroal_log_fatal("Unable to allocate memory"); #ifdef DEBUG pgagroal_backtrace(); #endif errno = 0; } /** * */ struct message* pgagroal_memory_message(void) { #ifdef DEBUG assert(message != NULL); assert(data != NULL); #endif return message; } /** * */ void pgagroal_memory_free(void) { size_t length; #ifdef DEBUG assert(message != NULL); assert(data != NULL); #endif length = message->max_length; memset(message, 0, sizeof(struct message)); memset(data, 0, length); message->kind = 0; message->length = 0; message->max_length = length; message->data = data; } /** * */ void pgagroal_memory_destroy(void) { free(data); free(message); data = NULL; message = NULL; } pgagroal-1.6.0/src/libpgagroal/message.c000066400000000000000000001037651456565230200202000ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include static int read_message(int socket, bool block, int timeout, struct message** msg); static int write_message(int socket, struct message* msg); static int ssl_read_message(SSL* ssl, int timeout, struct message** msg); static int ssl_write_message(SSL* ssl, struct message* msg); int pgagroal_read_block_message(SSL* ssl, int socket, struct message** msg) { if (ssl == NULL) { return read_message(socket, true, 0, msg); } return ssl_read_message(ssl, 0, msg); } int pgagroal_read_timeout_message(SSL* ssl, int socket, int timeout, struct message** msg) { if (ssl == NULL) { return read_message(socket, true, timeout, msg); } return ssl_read_message(ssl, timeout, msg); } int pgagroal_write_message(SSL* ssl, int socket, struct message* msg) { if (ssl == NULL) { return write_message(socket, msg); } return ssl_write_message(ssl, msg); } int pgagroal_read_socket_message(int socket, struct message** msg) { return read_message(socket, false, 0, msg); } int pgagroal_write_socket_message(int socket, struct message* msg) { return write_message(socket, msg); } int pgagroal_read_ssl_message(SSL* ssl, struct message** msg) { return ssl_read_message(ssl, 0, msg); } int pgagroal_write_ssl_message(SSL* ssl, struct message* msg) { return ssl_write_message(ssl, msg); } int pgagroal_create_message(void* data, ssize_t length, struct message** msg) { struct message* copy = NULL; copy = (struct message*)malloc(sizeof(struct message)); if (copy == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating message"); return MESSAGE_STATUS_ERROR; } copy->data = malloc(length); if (copy->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating message"); free(copy); return MESSAGE_STATUS_ERROR; } copy->kind = pgagroal_read_byte(data); copy->length = length; memcpy(copy->data, data, length); *msg = copy; return MESSAGE_STATUS_OK; } void pgagroal_free_message(struct message* msg) { pgagroal_memory_free(); } struct message* pgagroal_copy_message(struct message* msg) { struct message* copy = NULL; #ifdef DEBUG assert(msg != NULL); assert(msg->data != NULL); assert(msg->length > 0); #endif copy = (struct message*)malloc(sizeof(struct message)); if (copy == NULL) { pgagroal_log_fatal("Couldn't allocate memory while copying message"); return NULL; } copy->data = malloc(msg->length); if (copy->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while copying message"); free(copy); return NULL; } copy->kind = msg->kind; copy->length = msg->length; memcpy(copy->data, msg->data, msg->length); return copy; } void pgagroal_free_copy_message(struct message* msg) { if (msg) { if (msg->data) { free(msg->data); msg->data = NULL; } free(msg); msg = NULL; } } int pgagroal_write_empty(SSL* ssl, int socket) { char zero[1]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&zero, 0, sizeof(zero)); msg.kind = 0; msg.length = 1; msg.data = &zero; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_notice(SSL* ssl, int socket) { char notice[1]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(¬ice, 0, sizeof(notice)); notice[0] = 'N'; msg.kind = 'N'; msg.length = 1; msg.data = ¬ice; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_tls(SSL* ssl, int socket) { char tls[1]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&tls, 0, sizeof(tls)); tls[0] = 'S'; msg.kind = 'S'; msg.length = 1; msg.data = &tls; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_pool_full(SSL* ssl, int socket) { int size = 51; char pool_full[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&pool_full, 0, sizeof(pool_full)); pgagroal_write_byte(&pool_full, 'E'); pgagroal_write_int32(&(pool_full[1]), size - 1); pgagroal_write_string(&(pool_full[5]), "SFATAL"); pgagroal_write_string(&(pool_full[12]), "VFATAL"); pgagroal_write_string(&(pool_full[19]), "C53300"); pgagroal_write_string(&(pool_full[26]), "Mconnection pool is full"); msg.kind = 'E'; msg.length = size; msg.data = &pool_full; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_connection_refused(SSL* ssl, int socket) { int size = 46; char connection_refused[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&connection_refused, 0, sizeof(connection_refused)); pgagroal_write_byte(&connection_refused, 'E'); pgagroal_write_int32(&(connection_refused[1]), size - 1); pgagroal_write_string(&(connection_refused[5]), "SFATAL"); pgagroal_write_string(&(connection_refused[12]), "VFATAL"); pgagroal_write_string(&(connection_refused[19]), "C53300"); pgagroal_write_string(&(connection_refused[26]), "Mconnection refused"); msg.kind = 'E'; msg.length = size; msg.data = &connection_refused; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_connection_refused_old(SSL* ssl, int socket) { int size = 20; char connection_refused[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&connection_refused, 0, sizeof(connection_refused)); pgagroal_write_byte(&connection_refused, 'E'); pgagroal_write_string(&(connection_refused[1]), "connection refused"); msg.kind = 'E'; msg.length = size; msg.data = &connection_refused; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_bad_password(SSL* ssl, int socket, char* username) { int size = strlen(username); size += 84; char badpassword[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&badpassword, 0, sizeof(badpassword)); pgagroal_write_byte(&badpassword, 'E'); pgagroal_write_int32(&(badpassword[1]), size - 1); pgagroal_write_string(&(badpassword[5]), "SFATAL"); pgagroal_write_string(&(badpassword[12]), "VFATAL"); pgagroal_write_string(&(badpassword[19]), "C28P01"); pgagroal_write_string(&(badpassword[26]), "Mpassword authentication failed for user \""); pgagroal_write_string(&(badpassword[68]), username); pgagroal_write_string(&(badpassword[68 + strlen(username)]), "\""); pgagroal_write_string(&(badpassword[68 + strlen(username) + 2]), "Rauth_failed"); msg.kind = 'E'; msg.length = size; msg.data = &badpassword; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_unsupported_security_model(SSL* ssl, int socket, char* username) { int size = strlen(username); size += 66; char unsupported[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&unsupported, 0, sizeof(unsupported)); pgagroal_write_byte(&unsupported, 'E'); pgagroal_write_int32(&(unsupported[1]), size - 1); pgagroal_write_string(&(unsupported[5]), "SFATAL"); pgagroal_write_string(&(unsupported[12]), "VFATAL"); pgagroal_write_string(&(unsupported[19]), "C28000"); pgagroal_write_string(&(unsupported[26]), "Munsupported security model for user \""); pgagroal_write_string(&(unsupported[64]), username); pgagroal_write_string(&(unsupported[size - 2]), "\""); msg.kind = 'E'; msg.length = size; msg.data = &unsupported; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_no_hba_entry(SSL* ssl, int socket, char* username, char* database, char* address) { int size = strlen(username); size += strlen(database); size += strlen(address); size += 88; char no_hba[size]; struct message msg; int offset = 64; memset(&msg, 0, sizeof(struct message)); memset(&no_hba, 0, sizeof(no_hba)); pgagroal_write_byte(&no_hba, 'E'); pgagroal_write_int32(&(no_hba[1]), size - 1); pgagroal_write_string(&(no_hba[5]), "SFATAL"); pgagroal_write_string(&(no_hba[12]), "VFATAL"); pgagroal_write_string(&(no_hba[19]), "C28000"); pgagroal_write_string(&(no_hba[26]), "Mno pgagroal_hba.conf entry for host \""); pgagroal_write_string(&(no_hba[64]), address); offset += strlen(address); pgagroal_write_string(&(no_hba[offset]), "\", user \""); offset += 9; pgagroal_write_string(&(no_hba[offset]), username); offset += strlen(username); pgagroal_write_string(&(no_hba[offset]), "\", database \""); offset += 13; pgagroal_write_string(&(no_hba[offset]), database); offset += strlen(database); pgagroal_write_string(&(no_hba[offset]), "\""); msg.kind = 'E'; msg.length = size; msg.data = &no_hba; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_deallocate_all(SSL* ssl, int socket) { int status; int size = 21; char deallocate[size]; struct message msg; struct message* reply = NULL; memset(&msg, 0, sizeof(struct message)); memset(&deallocate, 0, sizeof(deallocate)); pgagroal_write_byte(&deallocate, 'Q'); pgagroal_write_int32(&(deallocate[1]), size - 1); pgagroal_write_string(&(deallocate[5]), "DEALLOCATE ALL;"); msg.kind = 'Q'; msg.length = size; msg.data = &deallocate; if (ssl == NULL) { status = write_message(socket, &msg); } else { status = ssl_write_message(ssl, &msg); } if (status != MESSAGE_STATUS_OK) { goto error; } if (ssl == NULL) { status = read_message(socket, true, 0, &reply); } else { status = ssl_read_message(ssl, 0, &reply); } if (status != MESSAGE_STATUS_OK) { goto error; } if (reply->kind == 'E') { goto error; } pgagroal_free_message(reply); return 0; error: if (reply) { pgagroal_free_message(reply); } return 1; } int pgagroal_write_discard_all(SSL* ssl, int socket) { int status; int size = 18; char discard[size]; struct message msg; struct message* reply = NULL; memset(&msg, 0, sizeof(struct message)); memset(&discard, 0, sizeof(discard)); pgagroal_write_byte(&discard, 'Q'); pgagroal_write_int32(&(discard[1]), size - 1); pgagroal_write_string(&(discard[5]), "DISCARD ALL;"); msg.kind = 'Q'; msg.length = size; msg.data = &discard; if (ssl == NULL) { status = write_message(socket, &msg); } else { status = ssl_write_message(ssl, &msg); } if (status != MESSAGE_STATUS_OK) { goto error; } if (ssl == NULL) { status = read_message(socket, true, 0, &reply); } else { status = ssl_read_message(ssl, 0, &reply); } if (status != MESSAGE_STATUS_OK) { goto error; } if (reply->kind == 'E') { goto error; } pgagroal_free_message(reply); return 0; error: if (reply) { pgagroal_free_message(reply); } return 1; } int pgagroal_write_terminate(SSL* ssl, int socket) { char terminate[5]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&terminate, 0, sizeof(terminate)); terminate[0] = 'X'; terminate[4] = 4; msg.kind = 'X'; msg.length = 5; msg.data = &terminate; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_client_failover(SSL* ssl, int socket) { int size = 57; char failover[size]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&failover, 0, sizeof(failover)); pgagroal_write_byte(&failover, 'E'); pgagroal_write_int32(&(failover[1]), size - 1); pgagroal_write_string(&(failover[5]), "SFATAL"); pgagroal_write_string(&(failover[12]), "VFATAL"); pgagroal_write_string(&(failover[19]), "C53300"); pgagroal_write_string(&(failover[26]), "Mserver failover"); pgagroal_write_string(&(failover[43]), "Rauth_failed"); msg.kind = 'E'; msg.length = size; msg.data = &failover; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_auth_password(SSL* ssl, int socket) { char password[9]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&password, 0, sizeof(password)); password[0] = 'R'; pgagroal_write_int32(&(password[1]), 8); pgagroal_write_int32(&(password[5]), 3); msg.kind = 'R'; msg.length = 9; msg.data = &password; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_write_rollback(SSL* ssl, int socket) { int status; int size = 15; char rollback[size]; struct message msg; struct message* reply = NULL; memset(&msg, 0, sizeof(struct message)); memset(&rollback, 0, sizeof(rollback)); pgagroal_write_byte(&rollback, 'Q'); pgagroal_write_int32(&(rollback[1]), size - 1); pgagroal_write_string(&(rollback[5]), "ROLLBACK;"); msg.kind = 'Q'; msg.length = size; msg.data = &rollback; if (ssl == NULL) { status = write_message(socket, &msg); } else { status = ssl_write_message(ssl, &msg); } if (status != MESSAGE_STATUS_OK) { goto error; } if (ssl == NULL) { status = read_message(socket, true, 0, &reply); } else { status = ssl_read_message(ssl, 0, &reply); } if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(reply); return 0; error: if (reply) { pgagroal_free_message(reply); } return 1; } int pgagroal_create_auth_password_response(char* password, struct message** msg) { struct message* m = NULL; size_t size; size = 6 + strlen(password); m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_password_response"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_password_response"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'p'; m->length = size; pgagroal_write_byte(m->data, 'p'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_string(m->data + 5, password); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_write_auth_md5(SSL* ssl, int socket, char salt[4]) { char md5[13]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&md5, 0, sizeof(md5)); md5[0] = 'R'; pgagroal_write_int32(&(md5[1]), 12); pgagroal_write_int32(&(md5[5]), 5); pgagroal_write_byte(&(md5[9]), salt[0]); pgagroal_write_byte(&(md5[10]), salt[1]); pgagroal_write_byte(&(md5[11]), salt[2]); pgagroal_write_byte(&(md5[12]), salt[3]); msg.kind = 'R'; msg.length = 13; msg.data = &md5; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_create_auth_md5_response(char* md5, struct message** msg) { struct message* m = NULL; size_t size; size = 1 + 4 + strlen(md5) + 1; m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_md5_response"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_md5_response"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'p'; m->length = size; pgagroal_write_byte(m->data, 'p'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_string(m->data + 5, md5); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_write_auth_scram256(SSL* ssl, int socket) { char scram[24]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&scram, 0, sizeof(scram)); scram[0] = 'R'; pgagroal_write_int32(&(scram[1]), 23); pgagroal_write_int32(&(scram[5]), 10); pgagroal_write_string(&(scram[9]), "SCRAM-SHA-256"); msg.kind = 'R'; msg.length = 24; msg.data = &scram; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_create_auth_scram256_response(char* nounce, struct message** msg) { struct message* m = NULL; size_t size; size = 1 + 4 + 13 + 4 + 9 + strlen(nounce); m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_response"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_response"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'p'; m->length = size; pgagroal_write_byte(m->data, 'p'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_string(m->data + 5, "SCRAM-SHA-256"); pgagroal_write_string(m->data + 22, " n,,n=,r="); pgagroal_write_string(m->data + 31, nounce); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_create_auth_scram256_continue(char* cn, char* sn, char* salt, struct message** msg) { struct message* m = NULL; size_t size; size = 1 + 4 + 4 + 2 + strlen(cn) + strlen(sn) + 3 + strlen(salt) + 7; m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_continue"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_continue"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'R'; m->length = size; pgagroal_write_byte(m->data, 'R'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_int32(m->data + 5, 11); pgagroal_write_string(m->data + 9, "r="); pgagroal_write_string(m->data + 11, cn); pgagroal_write_string(m->data + 11 + strlen(cn), sn); pgagroal_write_string(m->data + 11 + strlen(cn) + strlen(sn), ",s="); pgagroal_write_string(m->data + 11 + strlen(cn) + strlen(sn) + 3, salt); pgagroal_write_string(m->data + 11 + strlen(cn) + strlen(sn) + 3 + strlen(salt), ",i=4096"); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_create_auth_scram256_continue_response(char* wp, char* p, struct message** msg) { struct message* m = NULL; size_t size; size = 1 + 4 + strlen(wp) + 3 + strlen(p); m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_continue_response"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_continue_response"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'p'; m->length = size; pgagroal_write_byte(m->data, 'p'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_string(m->data + 5, wp); pgagroal_write_string(m->data + 5 + strlen(wp), ",p="); pgagroal_write_string(m->data + 5 + strlen(wp) + 3, p); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_create_auth_scram256_final(char* ss, struct message** msg) { struct message* m = NULL; size_t size; size = 1 + 4 + 4 + 2 + strlen(ss); m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_final"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating auth_scram256_final"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 'R'; m->length = size; pgagroal_write_byte(m->data, 'R'); pgagroal_write_int32(m->data + 1, size - 1); pgagroal_write_int32(m->data + 5, 12); pgagroal_write_string(m->data + 9, "v="); pgagroal_write_string(m->data + 11, ss); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_write_auth_success(SSL* ssl, int socket) { char success[9]; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&success, 0, sizeof(success)); success[0] = 'R'; pgagroal_write_int32(&(success[1]), 8); pgagroal_write_int32(&(success[5]), 0); msg.kind = 'R'; msg.length = 9; msg.data = &success; if (ssl == NULL) { return write_message(socket, &msg); } return ssl_write_message(ssl, &msg); } int pgagroal_create_ssl_message(struct message** msg) { struct message* m = NULL; size_t size; size = 8; m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating ssl_message"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating ssl_message"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 0; m->length = size; pgagroal_write_int32(m->data, size); pgagroal_write_int32(m->data + 4, 80877103); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_create_startup_message(char* username, char* database, struct message** msg) { struct message* m = NULL; size_t size; size_t us; size_t ds; us = strlen(username); ds = strlen(database); size = 4 + 4 + 4 + 1 + us + 1 + 8 + 1 + ds + 1 + 17 + 9 + 1; m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating startup_message"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m->data == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating startup_message"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 0; m->length = size; pgagroal_write_int32(m->data, size); pgagroal_write_int32(m->data + 4, 196608); pgagroal_write_string(m->data + 8, "user"); pgagroal_write_string(m->data + 13, username); pgagroal_write_string(m->data + 13 + us + 1, "database"); pgagroal_write_string(m->data + 13 + us + 1 + 9, database); pgagroal_write_string(m->data + 13 + us + 1 + 9 + ds + 1, "application_name"); pgagroal_write_string(m->data + 13 + us + 1 + 9 + ds + 1 + 17, "pgagroal"); *msg = m; return MESSAGE_STATUS_OK; } int pgagroal_create_cancel_request_message(int pid, int secret, struct message** msg) { struct message* m = NULL; size_t size; size = 16; m = (struct message*)malloc(sizeof(struct message)); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating cancel_request_message"); return MESSAGE_STATUS_ERROR; } m->data = calloc(1, size); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while creating cancel_request_message"); free(m); return MESSAGE_STATUS_ERROR; } m->kind = 0; m->length = size; pgagroal_write_int32(m->data, size); pgagroal_write_int32(m->data + 4, 80877102); pgagroal_write_int32(m->data + 8, pid); pgagroal_write_int32(m->data + 12, secret); *msg = m; return MESSAGE_STATUS_OK; } bool pgagroal_connection_isvalid(int socket) { int status; int size = 15; char valid[size]; struct message msg; struct message* reply = NULL; memset(&msg, 0, sizeof(struct message)); memset(&valid, 0, sizeof(valid)); pgagroal_write_byte(&valid, 'Q'); pgagroal_write_int32(&(valid[1]), size - 1); pgagroal_write_string(&(valid[5]), "SELECT 1;"); msg.kind = 'Q'; msg.length = size; msg.data = &valid; status = write_message(socket, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = read_message(socket, true, 0, &reply); if (status != MESSAGE_STATUS_OK) { goto error; } if (reply->kind == 'E') { goto error; } pgagroal_free_message(reply); return true; error: if (reply) { pgagroal_free_message(reply); } return false; } void pgagroal_log_message(struct message* msg) { if (msg == NULL) { pgagroal_log_info("Message is NULL"); } else if (msg->data == NULL) { pgagroal_log_info("Message DATA is NULL"); } else { pgagroal_log_mem(msg->data, msg->length); } } static int read_message(int socket, bool block, int timeout, struct message** msg) { bool keep_read; ssize_t numbytes; struct timeval tv; struct message* m = NULL; if (unlikely(timeout > 0)) { tv.tv_sec = timeout; tv.tv_usec = 0; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); } do { keep_read = false; m = pgagroal_memory_message(); numbytes = read(socket, m->data, m->max_length); if (likely(numbytes > 0)) { m->kind = (signed char)(*((char*)m->data)); m->length = numbytes; *msg = m; if (unlikely(timeout > 0)) { tv.tv_sec = 0; tv.tv_usec = 0; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); } return MESSAGE_STATUS_OK; } else if (numbytes == 0) { if ((errno == EAGAIN || errno == EWOULDBLOCK) && block) { keep_read = true; errno = 0; } else { if (unlikely(timeout > 0)) { tv.tv_sec = 0; tv.tv_usec = 0; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); } return MESSAGE_STATUS_ZERO; } } else { if ((errno == EAGAIN || errno == EWOULDBLOCK) && block) { keep_read = true; errno = 0; } } } while (keep_read); if (unlikely(timeout > 0)) { tv.tv_sec = 0; tv.tv_usec = 0; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); } return MESSAGE_STATUS_ERROR; } static int write_message(int socket, struct message* msg) { bool keep_write; ssize_t numbytes; int offset; ssize_t totalbytes; ssize_t remaining; #ifdef DEBUG assert(msg != NULL); #endif numbytes = 0; offset = 0; totalbytes = 0; remaining = msg->length; do { keep_write = false; numbytes = write(socket, msg->data + offset, remaining); if (likely(numbytes == msg->length)) { return MESSAGE_STATUS_OK; } else if (numbytes != -1) { offset += numbytes; totalbytes += numbytes; remaining -= numbytes; if (totalbytes == msg->length) { return MESSAGE_STATUS_OK; } pgagroal_log_debug("Write %d - %zd/%zd vs %zd", socket, numbytes, totalbytes, msg->length); keep_write = true; errno = 0; } else { switch (errno) { case EAGAIN: keep_write = true; errno = 0; break; default: keep_write = false; break; } } } while (keep_write); return MESSAGE_STATUS_ERROR; } static int ssl_read_message(SSL* ssl, int timeout, struct message** msg) { bool keep_read; ssize_t numbytes; time_t start_time; struct message* m = NULL; if (unlikely(timeout > 0)) { start_time = time(NULL); } do { keep_read = false; m = pgagroal_memory_message(); numbytes = SSL_read(ssl, m->data, m->max_length); if (likely(numbytes > 0)) { m->kind = (signed char)(*((char*)m->data)); m->length = numbytes; *msg = m; return MESSAGE_STATUS_OK; } else { int err; err = SSL_get_error(ssl, numbytes); switch (err) { case SSL_ERROR_NONE: break; case SSL_ERROR_ZERO_RETURN: if (timeout > 0) { if (difftime(time(NULL), start_time) >= timeout) { return MESSAGE_STATUS_ZERO; } /* Sleep for 100ms */ SLEEP(100000000L) } case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: #ifndef HAVE_OPENBSD #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) case SSL_ERROR_WANT_ASYNC: case SSL_ERROR_WANT_ASYNC_JOB: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) case SSL_ERROR_WANT_CLIENT_HELLO_CB: #endif #endif #endif keep_read = true; break; case SSL_ERROR_SYSCALL: pgagroal_log_error("SSL_ERROR_SYSCALL: %s (%d)", strerror(errno), SSL_get_fd(ssl)); errno = 0; break; case SSL_ERROR_SSL: pgagroal_log_error("SSL_ERROR_SSL: %s (%d)", strerror(errno), SSL_get_fd(ssl)); break; } ERR_clear_error(); } } while (keep_read); return MESSAGE_STATUS_ERROR; } static int ssl_write_message(SSL* ssl, struct message* msg) { bool keep_write; ssize_t numbytes; int offset; ssize_t totalbytes; ssize_t remaining; #ifdef DEBUG assert(msg != NULL); #endif numbytes = 0; offset = 0; totalbytes = 0; remaining = msg->length; do { keep_write = false; numbytes = SSL_write(ssl, msg->data + offset, remaining); if (likely(numbytes == msg->length)) { return MESSAGE_STATUS_OK; } else if (numbytes > 0) { offset += numbytes; totalbytes += numbytes; remaining -= numbytes; if (totalbytes == msg->length) { return MESSAGE_STATUS_OK; } pgagroal_log_debug("SSL/Write %d - %zd/%zd vs %zd", SSL_get_fd(ssl), numbytes, totalbytes, msg->length); keep_write = true; errno = 0; } else { int err = SSL_get_error(ssl, numbytes); switch (err) { case SSL_ERROR_NONE: break; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: #ifndef HAVE_OPENBSD #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) case SSL_ERROR_WANT_ASYNC: case SSL_ERROR_WANT_ASYNC_JOB: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) case SSL_ERROR_WANT_CLIENT_HELLO_CB: #endif #endif #endif errno = 0; keep_write = true; break; case SSL_ERROR_SYSCALL: pgagroal_log_error("SSL_ERROR_SYSCALL: FD %d", SSL_get_fd(ssl)); pgagroal_log_error("%s", ERR_error_string(err, NULL)); pgagroal_log_error("%s", ERR_lib_error_string(err)); pgagroal_log_error("%s", ERR_reason_error_string(err)); errno = 0; break; case SSL_ERROR_SSL: pgagroal_log_error("SSL_ERROR_SSL: FD %d", SSL_get_fd(ssl)); pgagroal_log_error("%s", ERR_error_string(err, NULL)); pgagroal_log_error("%s", ERR_lib_error_string(err)); pgagroal_log_error("%s", ERR_reason_error_string(err)); errno = 0; break; } ERR_clear_error(); if (!keep_write) { return MESSAGE_STATUS_ERROR; } } } while (keep_write); return MESSAGE_STATUS_ERROR; } pgagroal-1.6.0/src/libpgagroal/network.c000066400000000000000000000360141456565230200202350ustar00rootroot00000000000000/* * 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 #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int bind_host(const char* hostname, int port, int** fds, int* length); /** * */ int pgagroal_bind(const char* hostname, int port, int** fds, int* length) { struct ifaddrs* ifaddr, * ifa; struct sockaddr_in* sa4; struct sockaddr_in6* sa6; char addr[50]; int* star_fds = NULL; int star_length = 0; if (!strcmp("*", hostname)) { if (getifaddrs(&ifaddr) == -1) { pgagroal_log_warn("getifaddrs: %s", strerror(errno)); errno = 0; return 1; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL && (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6) && (ifa->ifa_flags & IFF_UP)) { int* new_fds = NULL; int new_length = 0; memset(addr, 0, sizeof(addr)); if (ifa->ifa_addr->sa_family == AF_INET) { sa4 = (struct sockaddr_in*) ifa->ifa_addr; inet_ntop(AF_INET, &sa4->sin_addr, addr, sizeof(addr)); } else { sa6 = (struct sockaddr_in6*) ifa->ifa_addr; inet_ntop(AF_INET6, &sa6->sin6_addr, addr, sizeof(addr)); } if (bind_host(addr, port, &new_fds, &new_length)) { free(new_fds); continue; } if (star_fds == NULL) { star_fds = malloc(new_length * sizeof(int)); memcpy(star_fds, new_fds, new_length * sizeof(int)); star_length = new_length; } else { star_fds = realloc(star_fds, (star_length + new_length) * sizeof(int)); memcpy(star_fds + star_length, new_fds, new_length * sizeof(int)); star_length += new_length; } free(new_fds); } } *fds = star_fds; *length = star_length; freeifaddrs(ifaddr); return 0; } return bind_host(hostname, port, fds, length); } /** * */ int pgagroal_bind_unix_socket(const char* directory, const char* file, int* fd) { int status; char buf[107]; struct stat st = {0}; struct sockaddr_un addr; struct configuration* config; config = (struct configuration*)shmem; if ((*fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { pgagroal_log_error("pgagroal_bind_unix_socket: socket: %s %s", directory, strerror(errno)); errno = 0; goto error; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (!directory) { directory = "/tmp/"; } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s", directory); if (stat(&buf[0], &st) == -1) { status = mkdir(&buf[0], S_IRWXU); if (status == -1) { pgagroal_log_error("pgagroal_bind_unix_socket: permission defined for %s (%s)", directory, strerror(errno)); errno = 0; goto error; } } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/%s", directory, file); strncpy(addr.sun_path, &buf[0], sizeof(addr.sun_path) - 1); unlink(&buf[0]); if (bind(*fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { pgagroal_log_error("pgagroal_bind_unix_socket: bind: %s/%s %s", directory, file, strerror(errno)); errno = 0; goto error; } if (listen(*fd, config->backlog) == -1) { pgagroal_log_error("pgagroal_bind_unix_socket: listen: %s/%s %s", directory, file, strerror(errno)); errno = 0; goto error; } return 0; error: return 1; } /** * */ int pgagroal_remove_unix_socket(const char* directory, const char* file) { char buf[MISC_LENGTH]; memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/%s", directory, file); unlink(&buf[0]); return 0; } /** * */ int pgagroal_connect(const char* hostname, int port, int* fd) { struct addrinfo hints = {0}; struct addrinfo* servinfo = NULL; struct addrinfo* p = NULL; int yes = 1; socklen_t optlen = sizeof(int); int rv; char sport[5]; int error = 0; struct configuration* config; config = (struct configuration*)shmem; 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 (config != NULL && config->keep_alive) { if (setsockopt(*fd, SOL_SOCKET, SO_KEEPALIVE, &yes, optlen) == -1) { error = errno; pgagroal_disconnect(*fd); errno = 0; *fd = -1; continue; } } if (config != NULL && config->nodelay) { if (setsockopt(*fd, IPPROTO_TCP, TCP_NODELAY, &yes, optlen) == -1) { error = errno; pgagroal_disconnect(*fd); errno = 0; *fd = -1; continue; } } if (config != NULL) { if (setsockopt(*fd, SOL_SOCKET, SO_RCVBUF, &config->buffer_size, optlen) == -1) { error = errno; pgagroal_disconnect(*fd); errno = 0; *fd = -1; continue; } if (setsockopt(*fd, SOL_SOCKET, SO_SNDBUF, &config->buffer_size, optlen) == -1) { error = errno; pgagroal_disconnect(*fd); errno = 0; *fd = -1; continue; } } 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); /* Set O_NONBLOCK on the socket */ if (config != NULL && config->non_blocking) { pgagroal_socket_nonblocking(*fd, true); } return 0; error: pgagroal_log_debug("pgagroal_connect: %s", strerror(error)); if (servinfo != NULL) { freeaddrinfo(servinfo); } return 1; } /** * */ int pgagroal_connect_unix_socket(const char* directory, const char* file, int* fd) { char buf[107]; struct sockaddr_un addr; if ((*fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { pgagroal_log_warn("pgagroal_connect_unix_socket: socket: %s %s", directory, strerror(errno)); errno = 0; return 1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/%s", directory, file); strncpy(addr.sun_path, &buf[0], sizeof(addr.sun_path) - 1); if (connect(*fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { pgagroal_log_trace("pgagroal_connect_unix_socket: connect: %s/%s %s", directory, file, strerror(errno)); errno = 0; return 1; } return 0; } bool pgagroal_socket_isvalid(int fd) { int error = 0; socklen_t length; int r; r = fcntl(fd, F_GETFL); if (r == -1) { errno = 0; return false; } length = sizeof(error); r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length); if (r != 0 || error != 0) { errno = 0; return false; } return true; } /** * */ int pgagroal_disconnect(int fd) { if (fd == -1) { return 1; } return close(fd); } void* pgagroal_get_sockaddr(struct sockaddr* sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } void pgagroal_get_address(struct sockaddr* sa, char* address, size_t length) { if (sa->sa_family == AF_INET) { inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), address, length); } else { inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), address, length); } } int pgagroal_socket_nonblocking(int fd, bool value) { int flags; flags = fcntl(fd, F_GETFL); if (value) { fcntl(fd, F_SETFL, flags | O_NONBLOCK); } else { fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); } return 0; } bool pgagroal_socket_is_nonblocking(int fd) { int flags; flags = fcntl(fd, F_GETFL); return flags & O_NONBLOCK; } int pgagroal_socket_has_error(int fd) { int error = 0; socklen_t length = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) == -1) { pgagroal_log_trace("error getting socket error code: %s (%d)", strerror(errno), fd); errno = 0; goto error; } if (error != 0) { pgagroal_log_trace("socket error: %s (%d)", strerror(error), fd); errno = 0; goto error; } return 0; error: return 1; } int pgagroal_tcp_nodelay(int fd) { struct configuration* config; int yes = 1; socklen_t optlen = sizeof(int); config = (struct configuration*)shmem; if (config->nodelay) { if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, optlen) == -1) { pgagroal_log_warn("tcp_nodelay: %d %s", fd, strerror(errno)); errno = 0; return 1; } } return 0; } int pgagroal_socket_buffers(int fd) { struct configuration* config; socklen_t optlen = sizeof(int); config = (struct configuration*)shmem; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &config->buffer_size, optlen) == -1) { pgagroal_log_warn("socket_buffers: SO_RCVBUF %d %s", fd, strerror(errno)); errno = 0; return 1; } if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &config->buffer_size, optlen) == -1) { pgagroal_log_warn("socket_buffers: SO_SNDBUF %d %s", fd, strerror(errno)); errno = 0; return 1; } return 0; } /** * */ static int bind_host(const char* hostname, int port, int** fds, int* length) { int* result = NULL; int index, size; int sockfd; struct addrinfo hints, * servinfo, * addr; int yes = 1; int rv; char* sport; struct configuration* config; config = (struct configuration*)shmem; index = 0; size = 0; sport = calloc(1, 5); if (sport == NULL) { pgagroal_log_fatal("Couldn't allocate memory while binding host"); 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); pgagroal_log_error("getaddrinfo: %s:%d (%s)", hostname, port, gai_strerror(rv)); return 1; } free(sport); for (addr = servinfo; addr != NULL; addr = addr->ai_next) { size++; } result = calloc(1, size * sizeof(int)); if (sport == NULL) { pgagroal_log_fatal("Couldn't allocate memory while binding host"); 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) { pgagroal_log_debug("server: socket: %s:%d (%s)", hostname, port, strerror(errno)); continue; } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { pgagroal_log_debug("server: so_reuseaddr: %d %s", sockfd, strerror(errno)); pgagroal_disconnect(sockfd); continue; } if (config->non_blocking) { if (pgagroal_socket_nonblocking(sockfd, true)) { pgagroal_disconnect(sockfd); continue; } } if (pgagroal_socket_buffers(sockfd)) { pgagroal_disconnect(sockfd); continue; } if (pgagroal_tcp_nodelay(sockfd)) { pgagroal_disconnect(sockfd); continue; } if (bind(sockfd, addr->ai_addr, addr->ai_addrlen) == -1) { pgagroal_disconnect(sockfd); pgagroal_log_debug("server: bind: %s:%d (%s)", hostname, port, strerror(errno)); continue; } if (listen(sockfd, config->backlog) == -1) { pgagroal_disconnect(sockfd); pgagroal_log_debug("server: listen: %s:%d (%s)", hostname, port, strerror(errno)); continue; } *(result + index) = sockfd; index++; } freeaddrinfo(servinfo); if (index == 0) { free(result); return 1; } *fds = result; *length = index; return 0; } pgagroal-1.6.0/src/libpgagroal/pipeline_perf.c000066400000000000000000000205151456565230200213640ustar00rootroot00000000000000/* * 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 #include #include #include #include #include /* system */ #include #include #include #include #include #include #include static int performance_initialize(void*, void**, size_t*); static void performance_start(struct ev_loop* loop, struct worker_io*); static void performance_client(struct ev_loop* loop, struct ev_io* watcher, int revents); static void performance_server(struct ev_loop* loop, struct ev_io* watcher, int revents); static void performance_stop(struct ev_loop* loop, struct worker_io*); static void performance_destroy(void*, size_t); static void performance_periodic(void); static bool saw_x = false; struct pipeline performance_pipeline(void) { struct pipeline pipeline; pipeline.initialize = &performance_initialize; pipeline.start = &performance_start; pipeline.client = &performance_client; pipeline.server = &performance_server; pipeline.stop = &performance_stop; pipeline.destroy = &performance_destroy; pipeline.periodic = &performance_periodic; return pipeline; } static int performance_initialize(void* shmem, void** pipeline_shmem, size_t* pipeline_shmem_size) { return 0; } static void performance_start(struct ev_loop* loop, struct worker_io* w) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->max_connections; i++) { if (i != w->slot && !config->connections[i].new && config->connections[i].fd > 0) { pgagroal_disconnect(config->connections[i].fd); } } return; } static void performance_stop(struct ev_loop* loop, struct worker_io* w) { } static void performance_destroy(void* pipeline_shmem, size_t pipeline_shmem_size) { } static void performance_periodic(void) { } static void performance_client(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; status = pgagroal_read_socket_message(wi->client_fd, &msg); if (likely(status == MESSAGE_STATUS_OK)) { if (likely(msg->kind != 'X')) { if (wi->server_ssl == NULL) { status = pgagroal_write_socket_message(wi->server_fd, msg); } else { status = pgagroal_write_ssl_message(wi->server_ssl, msg); } if (unlikely(status != MESSAGE_STATUS_OK)) { goto server_error; } } else if (msg->kind == 'X') { saw_x = true; running = 0; } } else if (status == MESSAGE_STATUS_ZERO) { goto client_done; } else { goto client_error; } ev_break (loop, EVBREAK_ONE); return; client_done: config = (struct configuration*)shmem; pgagroal_log_debug("[C] Client done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); errno = 0; if (saw_x) { exit_code = WORKER_SUCCESS; } else { exit_code = WORKER_SERVER_FAILURE; } running = 0; ev_break(loop, EVBREAK_ALL); return; client_error: config = (struct configuration*)shmem; pgagroal_log_warn("[C] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: config = (struct configuration*)shmem; pgagroal_log_warn("[C] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void performance_server(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; bool fatal = false; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; if (wi->server_ssl == NULL) { status = pgagroal_read_socket_message(wi->server_fd, &msg); } else { status = pgagroal_read_ssl_message(wi->server_ssl, &msg); } if (likely(status == MESSAGE_STATUS_OK)) { status = pgagroal_write_socket_message(wi->client_fd, msg); if (unlikely(status != MESSAGE_STATUS_OK)) { goto client_error; } if (unlikely(msg->kind == 'E')) { fatal = false; if (!strncmp(msg->data + 6, "FATAL", 5) || !strncmp(msg->data + 6, "PANIC", 5)) { fatal = true; } if (fatal) { exit_code = WORKER_SERVER_FATAL; running = 0; } } } else if (status == MESSAGE_STATUS_ZERO) { goto server_done; } else { goto server_error; } ev_break(loop, EVBREAK_ONE); return; client_error: config = (struct configuration*)shmem; pgagroal_log_warn("[S] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_done: config = (struct configuration*)shmem; pgagroal_log_debug("[S] Server done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); errno = 0; running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: config = (struct configuration*)shmem; pgagroal_log_warn("[S] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } pgagroal-1.6.0/src/libpgagroal/pipeline_session.c000066400000000000000000000422711456565230200221160ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include static int session_initialize(void*, void**, size_t*); static void session_start(struct ev_loop* loop, struct worker_io*); static void session_client(struct ev_loop* loop, struct ev_io* watcher, int revents); static void session_server(struct ev_loop* loop, struct ev_io* watcher, int revents); static void session_stop(struct ev_loop* loop, struct worker_io*); static void session_destroy(void*, size_t); static void session_periodic(void); static bool in_tx; static int next_client_message; static int next_server_message; static bool saw_x = false; #define CLIENT_INIT 0 #define CLIENT_IDLE 1 #define CLIENT_ACTIVE 2 #define CLIENT_CHECK 3 struct client_session { atomic_schar state; /**< The state */ time_t timestamp; /**< The last used timestamp */ }; static void client_active(int); static void client_inactive(int); struct pipeline session_pipeline(void) { struct pipeline pipeline; pipeline.initialize = &session_initialize; pipeline.start = &session_start; pipeline.client = &session_client; pipeline.server = &session_server; pipeline.stop = &session_stop; pipeline.destroy = &session_destroy; pipeline.periodic = &session_periodic; return pipeline; } static int session_initialize(void* shmem, void** pipeline_shmem, size_t* pipeline_shmem_size) { void* session_shmem = NULL; size_t session_shmem_size; struct client_session* client; struct configuration* config; config = (struct configuration*)shmem; *pipeline_shmem = NULL; *pipeline_shmem_size = 0; if (config->disconnect_client > 0) { session_shmem_size = config->max_connections * sizeof(struct client_session); if (pgagroal_create_shared_memory(session_shmem_size, config->hugepage, &session_shmem)) { return 1; } memset(session_shmem, 0, session_shmem_size); for (int i = 0; i < config->max_connections; i++) { client = session_shmem + (i * sizeof(struct client_session)); atomic_init(&client->state, CLIENT_INIT); client->timestamp = time(NULL); } *pipeline_shmem = session_shmem; *pipeline_shmem_size = session_shmem_size; } return 0; } static void session_start(struct ev_loop* loop, struct worker_io* w) { struct client_session* client; struct configuration* config; config = (struct configuration*)shmem; in_tx = false; next_client_message = 0; next_server_message = 0; for (int i = 0; i < config->max_connections; i++) { if (i != w->slot && !config->connections[i].new && config->connections[i].fd > 0) { pgagroal_disconnect(config->connections[i].fd); } } if (pipeline_shmem != NULL) { client = pipeline_shmem + (w->slot * sizeof(struct client_session)); atomic_store(&client->state, CLIENT_IDLE); client->timestamp = time(NULL); } return; } static void session_stop(struct ev_loop* loop, struct worker_io* w) { struct client_session* client; if (pipeline_shmem != NULL) { client = pipeline_shmem + (w->slot * sizeof(struct client_session)); atomic_store(&client->state, CLIENT_INIT); client->timestamp = time(NULL); } } static void session_destroy(void* pipeline_shmem, size_t pipeline_shmem_size) { if (pipeline_shmem != NULL) { pgagroal_destroy_shared_memory(pipeline_shmem, pipeline_shmem_size); } } static void session_periodic(void) { signed char state; signed char idle; bool do_kill; time_t now; int ret; signed char server; int socket; struct message* cancel_msg = NULL; struct client_session* client; struct configuration* config; config = (struct configuration*)shmem; if (config->disconnect_client > 0 && pipeline_shmem != NULL) { now = time(NULL); for (int i = 0; i < config->max_connections; i++) { client = pipeline_shmem + (i * sizeof(struct client_session)); if (difftime(now, client->timestamp) > config->disconnect_client) { if (config->connections[i].pid != 0) { state = atomic_load(&client->state); do_kill = false; if (config->disconnect_client_force) { do_kill = true; } else { idle = CLIENT_IDLE; if (atomic_compare_exchange_strong(&client->state, &idle, CLIENT_CHECK)) { do_kill = true; } } if (do_kill) { pgagroal_create_cancel_request_message(config->connections[i].backend_pid, config->connections[i].backend_secret, &cancel_msg); server = config->connections[i].server; if (config->servers[server].host[0] == '/') { char pgsql[MISC_LENGTH]; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->servers[server].port); ret = pgagroal_connect_unix_socket(config->servers[server].host, &pgsql[0], &socket); } else { ret = pgagroal_connect(config->servers[server].host, config->servers[server].port, &socket); } if (ret == 0) { pgagroal_log_debug("Cancel request for %s/%s using slot %d (pid %d secret %d)", config->connections[i].database, config->connections[i].username, i, config->connections[i].backend_pid, config->connections[i].backend_secret); pgagroal_write_message(NULL, socket, cancel_msg); } pgagroal_disconnect(socket); atomic_store(&config->states[i], STATE_GRACEFULLY); pgagroal_log_info("Disconnect client %s/%s using slot %d (pid %d socket %d)", config->connections[i].database, config->connections[i].username, i, config->connections[i].pid, config->connections[i].fd); kill(config->connections[i].pid, SIGQUIT); pgagroal_free_copy_message(cancel_msg); cancel_msg = NULL; } else { atomic_store(&client->state, state); } } } } } exit(0); } static void session_client(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; config = (struct configuration*)shmem; client_active(wi->slot); if (wi->client_ssl == NULL) { status = pgagroal_read_socket_message(wi->client_fd, &msg); } else { status = pgagroal_read_ssl_message(wi->client_ssl, &msg); } if (likely(status == MESSAGE_STATUS_OK)) { pgagroal_prometheus_network_sent_add(msg->length); if (likely(msg->kind != 'X')) { int offset = 0; while (offset < msg->length) { if (next_client_message == 0) { char kind = pgagroal_read_byte(msg->data + offset); int length = pgagroal_read_int32(msg->data + offset + 1); /* The Q and E message tell us the execute of the simple query and the prepared statement */ if (kind == 'Q' || kind == 'E') { pgagroal_prometheus_query_count_add(); pgagroal_prometheus_query_count_specified_add(wi->slot); } /* Calculate the offset to the next message */ if (offset + length + 1 <= msg->length) { next_client_message = 0; offset += length + 1; } else { next_client_message = length + 1 - (msg->length - offset); offset = msg->length; } } else { offset = MIN(next_client_message, msg->length); next_client_message -= offset; } } if (wi->server_ssl == NULL) { status = pgagroal_write_socket_message(wi->server_fd, msg); } else { status = pgagroal_write_ssl_message(wi->server_ssl, msg); } if (unlikely(status == MESSAGE_STATUS_ERROR)) { if (config->failover) { pgagroal_server_failover(wi->slot); pgagroal_write_client_failover(wi->client_ssl, wi->client_fd); pgagroal_prometheus_failed_servers(); goto failover; } else { goto server_error; } } } else if (msg->kind == 'X') { saw_x = true; running = 0; } } else if (status == MESSAGE_STATUS_ZERO) { goto client_done; } else { goto client_error; } client_inactive(wi->slot); ev_break(loop, EVBREAK_ONE); return; client_done: pgagroal_log_debug("[C] Client done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); errno = 0; client_inactive(wi->slot); if (saw_x) { exit_code = WORKER_SUCCESS; } else { exit_code = WORKER_SERVER_FAILURE; } running = 0; ev_break(loop, EVBREAK_ALL); return; client_error: pgagroal_log_warn("[C] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; client_inactive(wi->slot); exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: pgagroal_log_warn("[C] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; client_inactive(wi->slot); exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; failover: client_inactive(wi->slot); exit_code = WORKER_FAILOVER; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void session_server(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; bool fatal = false; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; client_active(wi->slot); if (wi->server_ssl == NULL) { status = pgagroal_read_socket_message(wi->server_fd, &msg); } else { status = pgagroal_read_ssl_message(wi->server_ssl, &msg); } if (likely(status == MESSAGE_STATUS_OK)) { pgagroal_prometheus_network_received_add(msg->length); int offset = 0; while (offset < msg->length) { if (next_server_message == 0) { char kind = pgagroal_read_byte(msg->data + offset); int length = pgagroal_read_int32(msg->data + offset + 1); /* The Z message tell us the transaction state */ if (kind == 'Z') { char tx_state = pgagroal_read_byte(msg->data + offset + 5); if (tx_state != 'I' && !in_tx) { pgagroal_prometheus_tx_count_add(); } in_tx = tx_state != 'I'; } /* Calculate the offset to the next message */ if (offset + length + 1 <= msg->length) { next_server_message = 0; offset += length + 1; } else { next_server_message = length + 1 - (msg->length - offset); offset = msg->length; } } else { offset = MIN(next_server_message, msg->length); next_server_message -= offset; } } if (wi->client_ssl == NULL) { status = pgagroal_write_socket_message(wi->client_fd, msg); } else { status = pgagroal_write_ssl_message(wi->client_ssl, msg); } if (unlikely(status != MESSAGE_STATUS_OK)) { goto client_error; } if (unlikely(msg->kind == 'E')) { fatal = false; if (!strncmp(msg->data + 6, "FATAL", 5) || !strncmp(msg->data + 6, "PANIC", 5)) { fatal = true; } if (fatal) { exit_code = WORKER_SERVER_FATAL; running = 0; } } } else if (status == MESSAGE_STATUS_ZERO) { goto server_done; } else { goto server_error; } client_inactive(wi->slot); ev_break(loop, EVBREAK_ONE); return; client_error: config = (struct configuration*)shmem; pgagroal_log_warn("[S] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; client_inactive(wi->slot); exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_done: config = (struct configuration*)shmem; pgagroal_log_debug("[S] Server done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); errno = 0; client_inactive(wi->slot); running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: config = (struct configuration*)shmem; pgagroal_log_warn("[S] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; client_inactive(wi->slot); exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void client_active(int slot) { struct client_session* client; if (pipeline_shmem != NULL) { client = pipeline_shmem + (slot * sizeof(struct client_session)); atomic_store(&client->state, CLIENT_ACTIVE); client->timestamp = time(NULL); } } static void client_inactive(int slot) { struct client_session* client; if (pipeline_shmem != NULL) { client = pipeline_shmem + (slot * sizeof(struct client_session)); atomic_store(&client->state, CLIENT_IDLE); client->timestamp = time(NULL); } } pgagroal-1.6.0/src/libpgagroal/pipeline_transaction.c000066400000000000000000000445231456565230200227620ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include static int transaction_initialize(void*, void**, size_t*); static void transaction_start(struct ev_loop* loop, struct worker_io*); static void transaction_client(struct ev_loop* loop, struct ev_io* watcher, int revents); static void transaction_server(struct ev_loop* loop, struct ev_io* watcher, int revents); static void transaction_stop(struct ev_loop* loop, struct worker_io*); static void transaction_destroy(void*, size_t); static void transaction_periodic(void); static void start_mgt(struct ev_loop* loop); static void shutdown_mgt(struct ev_loop* loop); static void accept_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static int slot; static char username[MAX_USERNAME_LENGTH]; static char database[MAX_DATABASE_LENGTH]; static char appname[MAX_APPLICATION_NAME]; static bool in_tx; static int next_client_message; static int next_server_message; static int unix_socket = -1; static int deallocate; static bool fatal; static int fds[MAX_NUMBER_OF_CONNECTIONS]; static bool saw_x = false; static struct ev_io io_mgt; static struct worker_io server_io; struct pipeline transaction_pipeline(void) { struct pipeline pipeline; pipeline.initialize = &transaction_initialize; pipeline.start = &transaction_start; pipeline.client = &transaction_client; pipeline.server = &transaction_server; pipeline.stop = &transaction_stop; pipeline.destroy = &transaction_destroy; pipeline.periodic = &transaction_periodic; return pipeline; } static int transaction_initialize(void* shmem, void** pipeline_shmem, size_t* pipeline_shmem_size) { return 0; } static void transaction_start(struct ev_loop* loop, struct worker_io* w) { char p[MISC_LENGTH]; bool is_new; struct configuration* config = NULL; config = (struct configuration*)shmem; slot = -1; memcpy(&username[0], config->connections[w->slot].username, MAX_USERNAME_LENGTH); memcpy(&database[0], config->connections[w->slot].database, MAX_DATABASE_LENGTH); memcpy(&appname[0], config->connections[w->slot].appname, MAX_APPLICATION_NAME); in_tx = false; next_client_message = 0; next_server_message = 0; deallocate = false; memset(&p, 0, sizeof(p)); snprintf(&p[0], sizeof(p), ".s.%d", getpid()); if (pgagroal_bind_unix_socket(config->unix_socket_dir, &p[0], &unix_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s/%s", config->unix_socket_dir, &p[0]); goto error; } for (int i = 0; i < config->max_connections; i++) { fds[i] = config->connections[i].fd; } start_mgt(loop); pgagroal_tracking_event_slot(TRACKER_TX_RETURN_CONNECTION_START, w->slot); is_new = config->connections[w->slot].new; pgagroal_return_connection(w->slot, w->server_ssl, true); w->server_fd = -1; w->slot = -1; if (is_new) { /* Sleep for 5ms */ SLEEP(5000000L) } return; error: exit_code = WORKER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void transaction_stop(struct ev_loop* loop, struct worker_io* w) { if (slot != -1) { struct configuration* config = NULL; config = (struct configuration*)shmem; /* We are either in 'X' or the client terminated (consider cancel query) */ if (in_tx) { /* ROLLBACK */ pgagroal_write_rollback(NULL, config->connections[slot].fd); } ev_io_stop(loop, (struct ev_io*)&server_io); pgagroal_tracking_event_slot(TRACKER_TX_RETURN_CONNECTION_STOP, w->slot); pgagroal_return_connection(slot, w->server_ssl, true); slot = -1; } shutdown_mgt(loop); } static void transaction_destroy(void* pipeline_shmem, size_t pipeline_shmem_size) { } static void transaction_periodic(void) { } static void transaction_client(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; SSL* s_ssl = NULL; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; config = (struct configuration*)shmem; /* We can't use the information from wi except from client_fd/client_ssl */ if (slot == -1) { pgagroal_tracking_event_basic(TRACKER_TX_GET_CONNECTION, &username[0], &database[0]); if (pgagroal_get_connection(&username[0], &database[0], true, true, &slot, &s_ssl)) { pgagroal_write_pool_full(wi->client_ssl, wi->client_fd); goto get_error; } wi->server_fd = config->connections[slot].fd; wi->server_ssl = s_ssl; wi->slot = slot; memcpy(&config->connections[slot].appname[0], &appname[0], MAX_APPLICATION_NAME); ev_io_init((struct ev_io*)&server_io, transaction_server, config->connections[slot].fd, EV_READ); server_io.client_fd = wi->client_fd; server_io.server_fd = config->connections[slot].fd; server_io.slot = slot; server_io.client_ssl = wi->client_ssl; server_io.server_ssl = wi->server_ssl; fatal = false; ev_io_start(loop, (struct ev_io*)&server_io); } if (wi->client_ssl == NULL) { status = pgagroal_read_socket_message(wi->client_fd, &msg); } else { status = pgagroal_read_ssl_message(wi->client_ssl, &msg); } if (likely(status == MESSAGE_STATUS_OK)) { pgagroal_prometheus_network_sent_add(msg->length); if (likely(msg->kind != 'X')) { int offset = 0; while (offset < msg->length) { if (next_client_message == 0) { char kind = pgagroal_read_byte(msg->data + offset); int length = pgagroal_read_int32(msg->data + offset + 1); if (config->track_prepared_statements) { /* The P message tell us the prepared statement */ if (kind == 'P') { char* ps = pgagroal_read_string(msg->data + offset + 5); if (strcmp(ps, "")) { deallocate = true; } } } /* The Q and E message tell us the execute of the simple query and the prepared statement */ if (kind == 'Q' || kind == 'E') { pgagroal_prometheus_query_count_add(); pgagroal_prometheus_query_count_specified_add(wi->slot); } /* Calculate the offset to the next message */ if (offset + length + 1 <= msg->length) { next_client_message = 0; offset += length + 1; } else { next_client_message = length + 1 - (msg->length - offset); offset = msg->length; } } else { offset = MIN(next_client_message, msg->length); next_client_message -= offset; } } if (wi->server_ssl == NULL) { status = pgagroal_write_socket_message(wi->server_fd, msg); } else { status = pgagroal_write_ssl_message(wi->server_ssl, msg); } if (unlikely(status == MESSAGE_STATUS_ERROR)) { if (config->failover) { pgagroal_server_failover(slot); pgagroal_write_client_failover(wi->client_ssl, wi->client_fd); pgagroal_prometheus_failed_servers(); goto failover; } else { goto server_error; } } } else if (msg->kind == 'X') { saw_x = true; running = 0; } } else if (status == MESSAGE_STATUS_ZERO) { goto client_done; } else { goto client_error; } ev_break(loop, EVBREAK_ONE); return; client_done: pgagroal_log_debug("[C] Client done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); errno = 0; if (saw_x) { exit_code = WORKER_SUCCESS; } else { exit_code = WORKER_SERVER_FAILURE; } running = 0; ev_break(loop, EVBREAK_ALL); return; client_error: pgagroal_log_warn("[C] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: pgagroal_log_warn("[C] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; failover: exit_code = WORKER_FAILOVER; running = 0; ev_break(loop, EVBREAK_ALL); return; get_error: pgagroal_log_warn("Failure during obtaining connection"); exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void transaction_server(struct ev_loop* loop, struct ev_io* watcher, int revents) { int status = MESSAGE_STATUS_ERROR; bool has_z = false; struct worker_io* wi = NULL; struct message* msg = NULL; struct configuration* config = NULL; wi = (struct worker_io*)watcher; config = (struct configuration*)shmem; /* We can't use the information from wi except from client_fd/client_ssl */ wi->server_fd = config->connections[slot].fd; wi->slot = slot; if (!pgagroal_socket_isvalid(wi->client_fd)) { goto client_error; } if (wi->server_ssl == NULL) { status = pgagroal_read_socket_message(wi->server_fd, &msg); } else { status = pgagroal_read_ssl_message(wi->server_ssl, &msg); } if (likely(status == MESSAGE_STATUS_OK)) { pgagroal_prometheus_network_received_add(msg->length); int offset = 0; while (offset < msg->length) { if (next_server_message == 0) { char kind = pgagroal_read_byte(msg->data + offset); int length = pgagroal_read_int32(msg->data + offset + 1); /* The Z message tell us the transaction state */ if (kind == 'Z') { char tx_state = pgagroal_read_byte(msg->data + offset + 5); has_z = true; if (tx_state != 'I' && !in_tx) { pgagroal_prometheus_tx_count_add(); } in_tx = tx_state != 'I'; } /* Calculate the offset to the next message */ if (offset + length + 1 <= msg->length) { next_server_message = 0; offset += length + 1; } else { next_server_message = length + 1 - (msg->length - offset); offset = msg->length; } } else { offset = MIN(next_server_message, msg->length); next_server_message -= offset; } } if (wi->client_ssl == NULL) { status = pgagroal_write_socket_message(wi->client_fd, msg); } else { status = pgagroal_write_ssl_message(wi->client_ssl, msg); } if (unlikely(status != MESSAGE_STATUS_OK)) { goto client_error; } if (unlikely(msg->kind == 'E')) { if (!strncmp(msg->data + 6, "FATAL", 5) || !strncmp(msg->data + 6, "PANIC", 5)) { fatal = true; } } if (!fatal) { if (has_z && !in_tx && slot != -1) { ev_io_stop(loop, (struct ev_io*)&server_io); if (deallocate) { pgagroal_write_deallocate_all(wi->server_ssl, wi->server_fd); deallocate = false; } pgagroal_tracking_event_slot(TRACKER_TX_RETURN_CONNECTION, slot); if (pgagroal_return_connection(slot, wi->server_ssl, true)) { goto return_error; } slot = -1; } } else { if (has_z && !in_tx && slot != -1) { ev_io_stop(loop, (struct ev_io*)&server_io); exit_code = WORKER_SERVER_FATAL; running = 0; } } } else if (status == MESSAGE_STATUS_ZERO) { goto server_done; } else { goto server_error; } ev_break(loop, EVBREAK_ONE); return; client_error: pgagroal_log_warn("[S] Client error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->client_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_CLIENT_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; server_done: pgagroal_log_debug("[S] Server done (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); errno = 0; running = 0; ev_break(loop, EVBREAK_ALL); return; server_error: pgagroal_log_warn("[S] Server error (slot %d database %s user %s): %s (socket %d status %d)", wi->slot, config->connections[wi->slot].database, config->connections[wi->slot].username, strerror(errno), wi->server_fd, status); pgagroal_log_message(msg); errno = 0; exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; return_error: pgagroal_log_warn("Failure during connection return"); exit_code = WORKER_SERVER_FAILURE; running = 0; ev_break(loop, EVBREAK_ALL); return; } static void start_mgt(struct ev_loop* loop) { memset(&io_mgt, 0, sizeof(struct ev_io)); ev_io_init(&io_mgt, accept_cb, unix_socket, EV_READ); ev_io_start(loop, &io_mgt); } static void shutdown_mgt(struct ev_loop* loop) { char p[MISC_LENGTH]; struct configuration* config = NULL; config = (struct configuration*)shmem; memset(&p, 0, sizeof(p)); snprintf(&p[0], sizeof(p), ".s.%d", getpid()); ev_io_stop(loop, &io_mgt); pgagroal_disconnect(unix_socket); errno = 0; pgagroal_remove_unix_socket(config->unix_socket_dir, &p[0]); errno = 0; } static void accept_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) { struct sockaddr_in client_addr; socklen_t client_addr_length; int client_fd; signed char id; int32_t payload_slot; int payload_i; char* payload_s = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; if (EV_ERROR & revents) { pgagroal_log_debug("accept_cb: invalid event: %s", strerror(errno)); errno = 0; return; } client_addr_length = sizeof(client_addr); client_fd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_addr_length); if (client_fd == -1) { pgagroal_log_debug("accept: %s (%d)", strerror(errno), watcher->fd); errno = 0; return; } /* Process management request */ pgagroal_management_read_header(client_fd, &id, &payload_slot); pgagroal_management_read_payload(client_fd, id, &payload_i, &payload_s); switch (id) { case MANAGEMENT_CLIENT_FD: pgagroal_log_debug("pgagroal: Management client file descriptor: Slot %d FD %d", payload_slot, payload_i); fds[payload_slot] = payload_i; break; case MANAGEMENT_REMOVE_FD: pgagroal_log_debug("pgagroal: Management remove file descriptor: Slot %d FD %d", payload_slot, payload_i); if (fds[payload_slot] == payload_i && !config->connections[payload_slot].new && config->connections[payload_slot].fd > 0) { pgagroal_disconnect(payload_i); fds[payload_slot] = 0; } break; default: pgagroal_log_debug("pgagroal: Unsupported management id: %d", id); break; } pgagroal_disconnect(client_fd); } pgagroal-1.6.0/src/libpgagroal/pool.c000066400000000000000000001415221456565230200175160ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include static int find_best_rule(char* username, char* database); static bool remove_connection(char* username, char* database); static void connection_details(int slot); static bool do_prefill(char* username, char* database, int size); int pgagroal_get_connection(char* username, char* database, bool reuse, bool transaction_mode, int* slot, SSL** ssl) { bool do_init; bool has_lock; int connections; signed char not_init; signed char free; int server; int fd; time_t start_time; int best_rule; int retries; int ret; struct configuration* config; struct prometheus* prometheus; config = (struct configuration*)shmem; prometheus = (struct prometheus*)prometheus_shmem; pgagroal_prometheus_connection_get(); best_rule = find_best_rule(username, database); retries = 0; start_time = time(NULL); pgagroal_prometheus_connection_awaiting(best_rule); start: *slot = -1; *ssl = NULL; do_init = false; has_lock = false; if (best_rule >= 0) { connections = atomic_fetch_add(&config->limits[best_rule].active_connections, 1); if (connections >= config->limits[best_rule].max_size) { goto retry; } } connections = atomic_fetch_add(&config->active_connections, 1); has_lock = true; if (connections >= config->max_connections) { goto retry; } /* Try and find an existing free connection */ if (reuse) { for (int i = 0; *slot == -1 && i < config->max_connections; i++) { free = STATE_FREE; if (atomic_compare_exchange_strong(&config->states[i], &free, STATE_IN_USE)) { if (best_rule == config->connections[i].limit_rule && !strcmp((const char*)(&config->connections[i].username), username) && !strcmp((const char*)(&config->connections[i].database), database)) { *slot = i; } else { atomic_store(&config->states[i], STATE_FREE); } } } } if (*slot == -1 && !transaction_mode) { /* Ok, try and create a new connection */ for (int i = 0; *slot == -1 && i < config->max_connections; i++) { not_init = STATE_NOTINIT; if (atomic_compare_exchange_strong(&config->states[i], ¬_init, STATE_INIT)) { *slot = i; do_init = true; } } } if (*slot != -1) { config->connections[*slot].limit_rule = best_rule; config->connections[*slot].pid = getpid(); if (do_init) { /* We need to find the server for the connection */ if (pgagroal_get_primary(&server)) { config->connections[*slot].limit_rule = -1; config->connections[*slot].pid = -1; atomic_store(&config->states[*slot], STATE_NOTINIT); if (!fork()) { pgagroal_flush(FLUSH_GRACEFULLY, "*"); } goto error; } pgagroal_log_debug("connect: server %d", server); if (config->servers[server].host[0] == '/') { char pgsql[MISC_LENGTH]; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->servers[server].port); ret = pgagroal_connect_unix_socket(config->servers[server].host, &pgsql[0], &fd); } else { ret = pgagroal_connect(config->servers[server].host, config->servers[server].port, &fd); } if (ret) { pgagroal_log_error("pgagroal: No connection to %s:%d", config->servers[server].host, config->servers[server].port); config->connections[*slot].limit_rule = -1; config->connections[*slot].pid = -1; atomic_store(&config->states[*slot], STATE_NOTINIT); pgagroal_prometheus_server_error(server); if (!fork()) { pgagroal_flush_server(server); } if (config->failover) { pgagroal_server_force_failover(server); pgagroal_prometheus_failed_servers(); goto retry; } goto error; } pgagroal_log_debug("connect: %s:%d using slot %d fd %d", config->servers[server].host, config->servers[server].port, *slot, fd); config->connections[*slot].server = server; memset(&config->connections[*slot].username, 0, MAX_USERNAME_LENGTH); memcpy(&config->connections[*slot].username, username, MIN(strlen(username), MAX_USERNAME_LENGTH - 1)); memset(&config->connections[*slot].database, 0, MAX_DATABASE_LENGTH); memcpy(&config->connections[*slot].database, database, MIN(strlen(database), MAX_DATABASE_LENGTH - 1)); config->connections[*slot].has_security = SECURITY_INVALID; config->connections[*slot].fd = fd; atomic_store(&config->states[*slot], STATE_IN_USE); } else { bool kill = false; /* Verify the socket for the slot */ if (!pgagroal_socket_isvalid(config->connections[*slot].fd)) { if (!transaction_mode) { kill = true; } else { atomic_store(&config->states[*slot], STATE_FREE); goto retry; } } if (!kill && config->validation == VALIDATION_FOREGROUND) { kill = !pgagroal_connection_isvalid(config->connections[*slot].fd); } if (kill) { int status; pgagroal_log_debug("pgagroal_get_connection: Slot %d FD %d - Error", *slot, config->connections[*slot].fd); pgagroal_tracking_event_slot(TRACKER_BAD_CONNECTION, *slot); status = pgagroal_kill_connection(*slot, *ssl); pgagroal_prefill_if_can(true, false); if (status == 0) { goto retry2; } else { goto timeout; } } } if (config->connections[*slot].start_time == -1) { config->connections[*slot].start_time = time(NULL); } config->connections[*slot].timestamp = time(NULL); atomic_store(&prometheus->client_wait_time, difftime(time(NULL), start_time)); pgagroal_prometheus_connection_success(); pgagroal_tracking_event_slot(TRACKER_GET_CONNECTION_SUCCESS, *slot); pgagroal_prometheus_connection_unawaiting(best_rule); return 0; } else { retry: if (best_rule >= 0) { atomic_fetch_sub(&config->limits[best_rule].active_connections, 1); } if (has_lock) { atomic_fetch_sub(&config->active_connections, 1); } retry2: if (config->blocking_timeout > 0) { /* Sleep for 500ms */ SLEEP(500000000L) double diff = difftime(time(NULL), start_time); if (diff >= (double)config->blocking_timeout) { goto timeout; } if (best_rule == -1) { remove_connection(username, database); } goto start; } else { if (!transaction_mode) { if (best_rule == -1) { if (remove_connection(username, database)) { if (retries < config->max_retries) { retries++; goto start; } } } else { if (retries < config->max_retries) { retries++; goto start; } } } else /* Sleep for 1000 nanos */ { SLEEP_AND_GOTO(1000L, start) } } } timeout: atomic_store(&prometheus->client_wait_time, difftime(time(NULL), start_time)); pgagroal_prometheus_connection_timeout(); pgagroal_tracking_event_basic(TRACKER_GET_CONNECTION_TIMEOUT, username, database); pgagroal_prometheus_connection_unawaiting(best_rule); return 1; error: if (best_rule >= 0) { atomic_fetch_sub(&config->limits[best_rule].active_connections, 1); } atomic_fetch_sub(&config->active_connections, 1); atomic_store(&prometheus->client_wait_time, difftime(time(NULL), start_time)); pgagroal_prometheus_connection_error(); pgagroal_prometheus_connection_unawaiting(best_rule); pgagroal_tracking_event_basic(TRACKER_GET_CONNECTION_ERROR, username, database); return 2; } int pgagroal_return_connection(int slot, SSL* ssl, bool transaction_mode) { int state; struct configuration* config; time_t now; signed char in_use; signed char age_check; config = (struct configuration*)shmem; /* Kill the connection, if it lives longer than max_connection_age */ if (config->max_connection_age > 0) { now = time(NULL); in_use = STATE_IN_USE; age_check = STATE_MAX_CONNECTION_AGE; if (atomic_compare_exchange_strong(&config->states[slot], &in_use, age_check)) { double age = difftime(now, config->connections[slot].start_time); if ((age >= (double) config->max_connection_age && !config->connections[slot].tx_mode) || !atomic_compare_exchange_strong(&config->states[slot], &age_check, STATE_IN_USE)) { pgagroal_prometheus_connection_max_connection_age(); pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, slot); return pgagroal_kill_connection(slot, ssl); } } } /* Verify the socket for the slot */ if (!transaction_mode && !pgagroal_socket_isvalid(config->connections[slot].fd)) { pgagroal_log_debug("pgagroal_return_connection: Slot %d FD %d - Error", slot, config->connections[slot].fd); config->connections[slot].has_security = SECURITY_INVALID; } /* Can we cache this connection ? */ if (config->connections[slot].has_security != SECURITY_INVALID && (config->connections[slot].has_security != SECURITY_SCRAM256 || (config->connections[slot].has_security == SECURITY_SCRAM256 && (config->authquery || pgagroal_user_known(config->connections[slot].username)))) && ssl == NULL) { state = atomic_load(&config->states[slot]); /* Return the connection, if not GRACEFULLY */ if (state == STATE_IN_USE) { pgagroal_log_debug("pgagroal_return_connection: Slot %d FD %d", slot, config->connections[slot].fd); if (!transaction_mode) { if (pgagroal_write_discard_all(ssl, config->connections[slot].fd)) { goto kill_connection; } } pgagroal_tracking_event_slot(TRACKER_RETURN_CONNECTION_SUCCESS, slot); config->connections[slot].timestamp = time(NULL); if (config->connections[slot].new) { pgagroal_management_transfer_connection(slot); } pgagroal_management_return_connection(slot); if (config->connections[slot].limit_rule >= 0) { atomic_fetch_sub(&config->limits[config->connections[slot].limit_rule].active_connections, 1); } config->connections[slot].new = false; config->connections[slot].pid = -1; config->connections[slot].tx_mode = transaction_mode; memset(&config->connections[slot].appname, 0, sizeof(config->connections[slot].appname)); atomic_store(&config->states[slot], STATE_FREE); atomic_fetch_sub(&config->active_connections, 1); pgagroal_prometheus_connection_return(); return 0; } else if (state == STATE_GRACEFULLY) { pgagroal_write_terminate(ssl, config->connections[slot].fd); } } kill_connection: pgagroal_tracking_event_slot(TRACKER_RETURN_CONNECTION_KILL, slot); return pgagroal_kill_connection(slot, ssl); } int pgagroal_kill_connection(int slot, SSL* ssl) { SSL_CTX* ctx; int ssl_shutdown; int result = 0; int fd; struct configuration* config; config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal_kill_connection: Slot %d FD %d State %d PID %d", slot, config->connections[slot].fd, atomic_load(&config->states[slot]), config->connections[slot].pid); pgagroal_tracking_event_slot(TRACKER_KILL_CONNECTION, slot); fd = config->connections[slot].fd; if (fd != -1) { pgagroal_management_kill_connection(slot, fd); if (ssl != NULL) { ctx = SSL_get_SSL_CTX(ssl); ssl_shutdown = SSL_shutdown(ssl); if (ssl_shutdown == 0) { SSL_shutdown(ssl); } SSL_free(ssl); SSL_CTX_free(ctx); } if (!pgagroal_socket_has_error(fd)) { pgagroal_disconnect(fd); } } else { result = 1; } if (config->connections[slot].pid != -1) { if (config->connections[slot].limit_rule >= 0) { atomic_fetch_sub(&config->limits[config->connections[slot].limit_rule].active_connections, 1); } atomic_fetch_sub(&config->active_connections, 1); } memset(&config->connections[slot].username, 0, sizeof(config->connections[slot].username)); memset(&config->connections[slot].database, 0, sizeof(config->connections[slot].database)); memset(&config->connections[slot].appname, 0, sizeof(config->connections[slot].appname)); config->connections[slot].new = true; config->connections[slot].server = -1; config->connections[slot].tx_mode = false; config->connections[slot].has_security = SECURITY_INVALID; for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { config->connections[slot].security_lengths[i] = 0; memset(&config->connections[slot].security_messages[i], 0, SECURITY_BUFFER_SIZE); } config->connections[slot].backend_pid = 0; config->connections[slot].backend_secret = 0; config->connections[slot].limit_rule = -1; config->connections[slot].start_time = -1; config->connections[slot].timestamp = -1; config->connections[slot].fd = -1; config->connections[slot].pid = -1; atomic_store(&config->states[slot], STATE_NOTINIT); pgagroal_prometheus_connection_kill(); return result; } void pgagroal_idle_timeout(void) { bool prefill; time_t now; signed char free; signed char idle_check; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; now = time(NULL); prefill = false; pgagroal_log_debug("pgagroal_idle_timeout"); /* Here we run backwards in order to keep hot connections in the beginning */ for (int i = config->max_connections - 1; i >= 0; i--) { free = STATE_FREE; idle_check = STATE_IDLE_CHECK; if (atomic_compare_exchange_strong(&config->states[i], &free, idle_check)) { double diff = difftime(now, config->connections[i].timestamp); if (diff >= (double)config->idle_timeout && !config->connections[i].tx_mode) { pgagroal_prometheus_connection_idletimeout(); pgagroal_tracking_event_slot(TRACKER_IDLE_TIMEOUT, i); pgagroal_kill_connection(i, NULL); prefill = true; } else { if (!atomic_compare_exchange_strong(&config->states[i], &idle_check, STATE_FREE)) { pgagroal_prometheus_connection_idletimeout(); pgagroal_tracking_event_slot(TRACKER_IDLE_TIMEOUT, i); pgagroal_kill_connection(i, NULL); prefill = true; } } } } if (prefill) { pgagroal_prefill_if_can(true, false); } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } void pgagroal_max_connection_age(void) { bool prefill; time_t now; signed char free; signed char age_check; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; now = time(NULL); prefill = false; pgagroal_log_debug("pgagroal_max_connection_age"); /* Here we run backwards in order to keep hot connections in the beginning */ for (int i = config->max_connections - 1; i >= 0; i--) { free = STATE_FREE; age_check = STATE_MAX_CONNECTION_AGE; if (atomic_compare_exchange_strong(&config->states[i], &free, age_check)) { double age = difftime(now, config->connections[i].start_time); if (age >= (double)config->max_connection_age && !config->connections[i].tx_mode) { pgagroal_prometheus_connection_max_connection_age(); pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, i); pgagroal_kill_connection(i, NULL); prefill = true; } else { if (!atomic_compare_exchange_strong(&config->states[i], &age_check, STATE_FREE)) { pgagroal_prometheus_connection_max_connection_age(); pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, i); pgagroal_kill_connection(i, NULL); prefill = true; } } } } if (prefill) { pgagroal_prefill_if_can(true, false); } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } void pgagroal_validation(void) { bool prefill = true; time_t now; signed char free; signed char validation; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; now = time(NULL); pgagroal_log_debug("pgagroal_validation"); /* We run backwards */ for (int i = config->max_connections - 1; i >= 0; i--) { free = STATE_FREE; validation = STATE_VALIDATION; if (atomic_compare_exchange_strong(&config->states[i], &free, validation)) { bool kill = false; double diff, age; /* Verify the socket for the slot */ if (!pgagroal_socket_isvalid(config->connections[i].fd)) { kill = true; } /* While we have the connection in validation may as well check for idle_timeout */ if (!kill && config->idle_timeout > 0) { diff = difftime(now, config->connections[i].timestamp); if (diff >= (double)config->idle_timeout) { kill = true; } } /* Also check for max_connection_age */ if (!kill && config->max_connection_age > 0) { age = difftime(now, config->connections[i].start_time); if (age >= (double)config->max_connection_age) { kill = true; } } /* Ok, send SELECT 1 */ if (!kill) { kill = !pgagroal_connection_isvalid(config->connections[i].fd); } if (kill) { pgagroal_prometheus_connection_invalid(); pgagroal_tracking_event_slot(TRACKER_INVALID_CONNECTION, i); pgagroal_kill_connection(i, NULL); prefill = true; } else { if (!atomic_compare_exchange_strong(&config->states[i], &validation, STATE_FREE)) { pgagroal_prometheus_connection_invalid(); pgagroal_tracking_event_slot(TRACKER_INVALID_CONNECTION, i); pgagroal_kill_connection(i, NULL); prefill = true; } } } } if (prefill) { pgagroal_prefill_if_can(true, false); } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } void pgagroal_flush(int mode, char* database) { bool prefill; signed char free; signed char in_use; bool do_kill; signed char server_state; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; prefill = false; pgagroal_log_debug("pgagroal_flush"); for (int i = config->max_connections - 1; i >= 0; i--) { free = STATE_FREE; in_use = STATE_IN_USE; do_kill = false; if (config->connections[i].server != -1) { server_state = atomic_load(&config->servers[config->connections[i].server].state); if (server_state == SERVER_FAILED) { do_kill = true; } } if (!do_kill) { bool consider = false; if (!strcmp(database, "*") || !strcmp(config->connections[i].database, database)) { consider = true; } if (consider) { if (atomic_compare_exchange_strong(&config->states[i], &free, STATE_FLUSH)) { if (pgagroal_socket_isvalid(config->connections[i].fd)) { pgagroal_write_terminate(NULL, config->connections[i].fd); } pgagroal_prometheus_connection_flush(); pgagroal_tracking_event_slot(TRACKER_FLUSH, i); pgagroal_kill_connection(i, NULL); prefill = true; } else if (mode == FLUSH_ALL || mode == FLUSH_GRACEFULLY) { if (atomic_compare_exchange_strong(&config->states[i], &in_use, STATE_FLUSH)) { if (mode == FLUSH_ALL) { kill(config->connections[i].pid, SIGQUIT); pgagroal_prometheus_connection_flush(); pgagroal_tracking_event_slot(TRACKER_FLUSH, i); pgagroal_kill_connection(i, NULL); prefill = true; } else if (mode == FLUSH_GRACEFULLY) { atomic_store(&config->states[i], STATE_GRACEFULLY); } } } } } else { switch (atomic_load(&config->states[i])) { case STATE_NOTINIT: case STATE_INIT: /* Do nothing */ break; case STATE_FREE: atomic_store(&config->states[i], STATE_GRACEFULLY); pgagroal_prometheus_connection_flush(); pgagroal_tracking_event_slot(TRACKER_FLUSH, i); pgagroal_kill_connection(i, NULL); prefill = true; break; case STATE_IN_USE: case STATE_GRACEFULLY: case STATE_FLUSH: atomic_store(&config->states[i], STATE_GRACEFULLY); break; case STATE_IDLE_CHECK: case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: atomic_store(&config->states[i], STATE_GRACEFULLY); break; default: break; } } } if (prefill) { pgagroal_prefill_if_can(true, false); } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } void pgagroal_flush_server(signed char server) { struct configuration* config; int primary = -1; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal_flush_server %s", config->servers[server].name); for (int i = 0; i < config->max_connections; i++) { if (config->connections[i].server == server) { switch (atomic_load(&config->states[i])) { case STATE_NOTINIT: case STATE_INIT: /* Do nothing */ break; case STATE_FREE: atomic_store(&config->states[i], STATE_GRACEFULLY); if (pgagroal_socket_isvalid(config->connections[i].fd)) { pgagroal_write_terminate(NULL, config->connections[i].fd); } pgagroal_prometheus_connection_flush(); pgagroal_tracking_event_slot(TRACKER_FLUSH, i); pgagroal_kill_connection(i, NULL); break; case STATE_IN_USE: case STATE_GRACEFULLY: case STATE_FLUSH: atomic_store(&config->states[i], STATE_GRACEFULLY); break; case STATE_IDLE_CHECK: case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: atomic_store(&config->states[i], STATE_GRACEFULLY); break; default: break; } } } if (pgagroal_get_primary(&primary)) { pgagroal_log_debug("No primary defined"); } else { if (server != (unsigned char)primary && primary != -1) { pgagroal_prefill_if_can(true, true); } } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } void pgagroal_prefill(bool initial) { struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal_prefill"); for (int i = 0; i < config->number_of_limits; i++) { int size; if (initial) { size = config->limits[i].initial_size; } else { size = config->limits[i].min_size; } if (size > 0) { if (strcmp("all", config->limits[i].database) && strcmp("all", config->limits[i].username)) { int user = -1; for (int j = 0; j < config->number_of_users && user == -1; j++) { if (!strcmp(config->limits[i].username, config->users[j].username)) { user = j; } } if (user != -1) { while (do_prefill(config->users[user].username, config->limits[i].database, size)) { int32_t slot = -1; SSL* ssl = NULL; if (pgagroal_prefill_auth(config->users[user].username, config->users[user].password, config->limits[i].database, &slot, &ssl) != AUTH_SUCCESS) { pgagroal_log_warn("Invalid data for user '%s' using limit entry (%d)", config->limits[i].username, i + 1); if (slot != -1) { if (config->connections[slot].fd != -1) { if (pgagroal_socket_isvalid(config->connections[slot].fd)) { pgagroal_write_terminate(NULL, config->connections[slot].fd); } } pgagroal_tracking_event_slot(TRACKER_PREFILL_KILL, slot); pgagroal_kill_connection(slot, ssl); } break; } if (slot != -1) { if (config->connections[slot].has_security != SECURITY_INVALID) { pgagroal_tracking_event_slot(TRACKER_PREFILL_RETURN, slot); pgagroal_return_connection(slot, ssl, false); } else { pgagroal_log_warn("Unsupported security model during prefill for user '%s' using limit entry (%d)", config->limits[i].username, i + 1); if (config->connections[slot].fd != -1) { if (pgagroal_socket_isvalid(config->connections[slot].fd)) { pgagroal_write_terminate(NULL, config->connections[slot].fd); } } pgagroal_tracking_event_slot(TRACKER_PREFILL_KILL, slot); pgagroal_kill_connection(slot, ssl); break; } } } } else { pgagroal_log_warn("Unknown user '%s' for limit entry (%d)", config->limits[i].username, i + 1); } } else { pgagroal_log_warn("Limit entry (%d) with invalid definition", i + 1); } } } pgagroal_pool_status(); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); } int pgagroal_pool_init(void) { struct configuration* config; config = (struct configuration*)shmem; /* States */ for (int i = 0; i < MAX_NUMBER_OF_CONNECTIONS; i++) { atomic_init(&config->states[i], STATE_NOTINIT); } /* Connections */ for (int i = 0; i < config->max_connections; i++) { config->connections[i].new = true; config->connections[i].tx_mode = false; config->connections[i].server = -1; config->connections[i].has_security = SECURITY_INVALID; config->connections[i].limit_rule = -1; config->connections[i].start_time = -1; config->connections[i].timestamp = -1; config->connections[i].fd = -1; config->connections[i].pid = -1; } return 0; } int pgagroal_pool_shutdown(void) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->max_connections; i++) { int state = atomic_load(&config->states[i]); if (state != STATE_NOTINIT) { if (state == STATE_FREE) { if (pgagroal_socket_isvalid(config->connections[i].fd)) { pgagroal_write_terminate(NULL, config->connections[i].fd); } } pgagroal_disconnect(config->connections[i].fd); if (config->connections[i].pid != -1) { kill(config->connections[i].pid, SIGQUIT); } atomic_store(&config->states[i], STATE_NOTINIT); } } return 0; } int pgagroal_pool_status(void) { struct configuration* config; config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal_pool_status: %d/%d", atomic_load(&config->active_connections), config->max_connections); for (int i = 0; i < config->max_connections; i++) { connection_details(i); } #ifdef DEBUG assert(atomic_load(&config->active_connections) <= config->max_connections); #endif return 0; } static int find_best_rule(char* username, char* database) { int best_rule; struct configuration* config; best_rule = -1; config = (struct configuration*)shmem; if (config->number_of_limits > 0) { for (int i = 0; i < config->number_of_limits; i++) { /* There is a match */ if ((!strcmp("all", config->limits[i].username) || !strcmp(username, config->limits[i].username)) && (!strcmp("all", config->limits[i].database) || !strcmp(database, config->limits[i].database))) { if (best_rule == -1) { best_rule = i; } else { if (!strcmp(username, config->limits[best_rule].username) && !strcmp(database, config->limits[best_rule].database)) { /* We have a precise rule already */ } else if (!strcmp("all", config->limits[best_rule].username)) { /* User is better */ if (strcmp("all", config->limits[i].username)) { best_rule = i; } } else if (!strcmp("all", config->limits[best_rule].database)) { /* Database is better */ if (strcmp("all", config->limits[i].database)) { best_rule = i; } } } } } } return best_rule; } static bool remove_connection(char* username, char* database) { signed char free; signed char remove; struct configuration* config; config = (struct configuration*)shmem; pgagroal_log_trace("remove_connection"); for (int i = config->max_connections - 1; i >= 0; i--) { free = STATE_FREE; remove = STATE_REMOVE; if (atomic_compare_exchange_strong(&config->states[i], &free, remove)) { if (!strcmp(username, config->connections[i].username) && !strcmp(database, config->connections[i].database)) { if (!atomic_compare_exchange_strong(&config->states[i], &remove, STATE_FREE)) { pgagroal_prometheus_connection_remove(); pgagroal_tracking_event_slot(TRACKER_REMOVE_CONNECTION, i); pgagroal_kill_connection(i, NULL); } } else { pgagroal_prometheus_connection_remove(); pgagroal_tracking_event_slot(TRACKER_REMOVE_CONNECTION, i); pgagroal_kill_connection(i, NULL); } return true; } } return false; } static void connection_details(int slot) { int state; char time_buf[32]; char start_buf[32]; struct configuration* config; struct connection connection; config = (struct configuration*)shmem; connection = config->connections[slot]; state = atomic_load(&config->states[slot]); memset(&time_buf, 0, sizeof(time_buf)); ctime_r(&(connection.timestamp), &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; memset(&start_buf, 0, sizeof(start_buf)); ctime_r(&(connection.start_time), &start_buf[0]); start_buf[strlen(start_buf) - 1] = 0; switch (state) { case STATE_NOTINIT: pgagroal_log_debug("pgagroal_pool_status: State: NOTINIT"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" FD: %d", connection.fd); break; case STATE_INIT: pgagroal_log_debug("pgagroal_pool_status: State: INIT"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" FD: %d", connection.fd); break; case STATE_FREE: pgagroal_log_debug("pgagroal_pool_status: State: FREE"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_IN_USE: pgagroal_log_debug("pgagroal_pool_status: State: IN_USE"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_GRACEFULLY: pgagroal_log_debug("pgagroal_pool_status: State: GRACEFULLY"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_FLUSH: pgagroal_log_debug("pgagroal_pool_status: State: FLUSH"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_IDLE_CHECK: pgagroal_log_debug("pgagroal_pool_status: State: IDLE CHECK"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_MAX_CONNECTION_AGE: pgagroal_log_debug("pgagroal_pool_status: State: MAX CONNECTION AGE"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_VALIDATION: pgagroal_log_debug("pgagroal_pool_status: State: VALIDATION"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; case STATE_REMOVE: pgagroal_log_debug("pgagroal_pool_status: State: REMOVE"); pgagroal_log_debug(" Slot: %d", slot); pgagroal_log_debug(" Server: %d", connection.server); pgagroal_log_debug(" User: %s", connection.username); pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); pgagroal_log_trace(" Auth: %d", connection.has_security); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); } pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); break; default: pgagroal_log_debug("pgagroal_pool_status: State %d Slot %d FD %d", state, slot, connection.fd); break; } } static bool do_prefill(char* username, char* database, int size) { signed char state; int free = 0; int connections = 0; struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->max_connections; i++) { if (!strcmp((const char*)(&config->connections[i].username), username) && !strcmp((const char*)(&config->connections[i].database), database)) { connections++; } else { state = atomic_load(&config->states[i]); if (state == STATE_NOTINIT) { free++; } } } return connections < size && free > 0; } void pgagroal_prefill_if_can(bool do_fork, bool initial) { int primary; if (pgagroal_can_prefill()) { if (pgagroal_get_primary(&primary)) { pgagroal_log_warn("No primary detected, cannot try to prefill!"); return; } if (do_fork) { if (!fork()) { pgagroal_prefill(initial); } } else { pgagroal_prefill(initial); } } } pgagroal-1.6.0/src/libpgagroal/prometheus.c000066400000000000000000002316511456565230200207430ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include /* system */ #include #include #include #include #include #define CHUNK_SIZE 32768 #define PAGE_UNKNOWN 0 #define PAGE_HOME 1 #define PAGE_METRICS 2 #define BAD_REQUEST 3 #define FIVE_SECONDS 5 #define TEN_SECONDS 10 #define TWENTY_SECONDS 20 #define THIRTY_SECONDS 30 #define FOURTYFIVE_SECONDS 45 #define ONE_MINUTE 60 #define FIVE_MINUTES 300 #define TEN_MINUTES 600 #define TWENTY_MINUTES 1200 #define THIRTY_MINUTES 1800 #define FOURTYFIVE_MINUTES 2700 #define ONE_HOUR 3600 #define TWO_HOURS 7200 #define FOUR_HOURS 14400 #define SIX_HOURS 21600 #define TWELVE_HOURS 43200 #define TWENTYFOUR_HOURS 86400 static int resolve_page(struct message* msg); static int unknown_page(int client_fd); static int home_page(int client_fd); static int metrics_page(int client_fd); static int bad_request(int client_fd); static void general_information(int client_fd); static void connection_information(int client_fd); static void limit_information(int client_fd); static void session_information(int client_fd); static void pool_information(int client_fd); static void auth_information(int client_fd); static void client_information(int client_fd); static void internal_information(int client_fd); static void connection_awaiting_information(int client_fd); static int send_chunk(int client_fd, char* data); static bool is_metrics_cache_configured(void); static bool is_metrics_cache_valid(void); static bool metrics_cache_append(char* data); static bool metrics_cache_finalize(void); static size_t metrics_cache_size_to_alloc(void); static void metrics_cache_invalidate(void); void pgagroal_prometheus(int client_fd) { int status; int page; struct message* msg = NULL; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } page = resolve_page(msg); if (page == PAGE_HOME) { home_page(client_fd); } else if (page == PAGE_METRICS) { metrics_page(client_fd); } else if (page == PAGE_UNKNOWN) { unknown_page(client_fd); } else { bad_request(client_fd); } pgagroal_disconnect(client_fd); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(0); error: pgagroal_log_debug("pgagroal_prometheus: disconnect %d", client_fd); pgagroal_disconnect(client_fd); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(1); } int pgagroal_init_prometheus(size_t* p_size, void** p_shmem) { size_t tmp_p_size = 0; void* tmp_p_shmem = NULL; struct configuration* config; struct prometheus* prometheus; config = (struct configuration*) shmem; *p_size = 0; *p_shmem = NULL; tmp_p_size = sizeof(struct prometheus) + (config->max_connections * sizeof(struct prometheus_connection)); if (pgagroal_create_shared_memory(tmp_p_size, config->hugepage, &tmp_p_shmem)) { goto error; } prometheus = (struct prometheus*)tmp_p_shmem; for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { atomic_init(&prometheus->session_time[i], 0); } atomic_init(&prometheus->session_time_sum, 0); atomic_init(&prometheus->connection_error, 0); atomic_init(&prometheus->connection_kill, 0); atomic_init(&prometheus->connection_remove, 0); atomic_init(&prometheus->connection_timeout, 0); atomic_init(&prometheus->connection_return, 0); atomic_init(&prometheus->connection_invalid, 0); atomic_init(&prometheus->connection_get, 0); atomic_init(&prometheus->connection_idletimeout, 0); atomic_init(&prometheus->connection_max_connection_age, 0); atomic_init(&prometheus->connection_flush, 0); atomic_init(&prometheus->connection_success, 0); // awating connections are those on hold due to // the `blocking_timeout` setting atomic_init(&prometheus->connections_awaiting_total, 0); for (int i = 0; i < NUMBER_OF_LIMITS; i++) { atomic_init(&prometheus->connections_awaiting[i], 0); } atomic_init(&prometheus->auth_user_success, 0); atomic_init(&prometheus->auth_user_bad_password, 0); atomic_init(&prometheus->auth_user_error, 0); atomic_init(&prometheus->client_wait, 0); atomic_init(&prometheus->client_active, 0); atomic_init(&prometheus->client_wait_time, 0); atomic_init(&prometheus->query_count, 0); atomic_init(&prometheus->tx_count, 0); atomic_init(&prometheus->network_sent, 0); atomic_init(&prometheus->network_received, 0); atomic_init(&prometheus->client_sockets, 0); atomic_init(&prometheus->self_sockets, 0); for (int i = 0; i < NUMBER_OF_SERVERS; i++) { atomic_init(&prometheus->server_error[i], 0); } atomic_init(&prometheus->failed_servers, 0); for (int i = 0; i < config->max_connections; i++) { memset(&prometheus->prometheus_connections[i], 0, sizeof(struct prometheus_connection)); atomic_init(&prometheus->prometheus_connections[i].query_count, 0); } *p_size = tmp_p_size; *p_shmem = tmp_p_shmem; return 0; error: return 1; } void pgagroal_prometheus_session_time(double time) { unsigned long t; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; t = (unsigned long)time; atomic_fetch_add(&prometheus->session_time_sum, t); if (t <= FIVE_SECONDS) { atomic_fetch_add(&prometheus->session_time[0], 1); } else if (t > FIVE_SECONDS && t <= TEN_SECONDS) { atomic_fetch_add(&prometheus->session_time[1], 1); } else if (t > TEN_SECONDS && t <= TWENTY_SECONDS) { atomic_fetch_add(&prometheus->session_time[2], 1); } else if (t > TWENTY_SECONDS && t <= THIRTY_SECONDS) { atomic_fetch_add(&prometheus->session_time[3], 1); } else if (t > THIRTY_SECONDS && t <= FOURTYFIVE_SECONDS) { atomic_fetch_add(&prometheus->session_time[4], 1); } else if (t > FOURTYFIVE_SECONDS && t <= ONE_MINUTE) { atomic_fetch_add(&prometheus->session_time[5], 1); } else if (t > ONE_MINUTE && t <= FIVE_MINUTES) { atomic_fetch_add(&prometheus->session_time[6], 1); } else if (t > FIVE_MINUTES && t <= TEN_MINUTES) { atomic_fetch_add(&prometheus->session_time[7], 1); } else if (t > TEN_MINUTES && t <= TWENTY_MINUTES) { atomic_fetch_add(&prometheus->session_time[8], 1); } else if (t > TWENTY_MINUTES && t <= THIRTY_MINUTES) { atomic_fetch_add(&prometheus->session_time[9], 1); } else if (t > THIRTY_MINUTES && t <= FOURTYFIVE_MINUTES) { atomic_fetch_add(&prometheus->session_time[10], 1); } else if (t > FOURTYFIVE_MINUTES && t <= ONE_HOUR) { atomic_fetch_add(&prometheus->session_time[11], 1); } else if (t > ONE_HOUR && t <= TWO_HOURS) { atomic_fetch_add(&prometheus->session_time[12], 1); } else if (t > TWO_HOURS && t <= FOUR_HOURS) { atomic_fetch_add(&prometheus->session_time[13], 1); } else if (t > FOUR_HOURS && t <= SIX_HOURS) { atomic_fetch_add(&prometheus->session_time[14], 1); } else if (t > SIX_HOURS && t <= TWELVE_HOURS) { atomic_fetch_add(&prometheus->session_time[15], 1); } else if (t > TWELVE_HOURS && t <= TWENTYFOUR_HOURS) { atomic_fetch_add(&prometheus->session_time[16], 1); } else { atomic_fetch_add(&prometheus->session_time[17], 1); } } void pgagroal_prometheus_connection_error(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_error, 1); } void pgagroal_prometheus_connection_kill(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_kill, 1); } void pgagroal_prometheus_connection_remove(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_remove, 1); } void pgagroal_prometheus_connection_timeout(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_timeout, 1); } void pgagroal_prometheus_connection_return(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_return, 1); } void pgagroal_prometheus_connection_invalid(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_invalid, 1); } void pgagroal_prometheus_connection_get(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_get, 1); } void pgagroal_prometheus_connection_idletimeout(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_idletimeout, 1); } void pgagroal_prometheus_connection_max_connection_age(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_max_connection_age, 1); } void pgagroal_prometheus_connection_awaiting(int limit_index) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; if (limit_index >= 0) { atomic_fetch_add(&prometheus->connections_awaiting[limit_index], 1); } atomic_fetch_add(&prometheus->connections_awaiting_total, 1); } void pgagroal_prometheus_connection_unawaiting(int limit_index) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; if (limit_index >= 0 && atomic_load(&prometheus->connections_awaiting[limit_index]) > 0) { atomic_fetch_sub(&prometheus->connections_awaiting[limit_index], 1); } if (atomic_load(&prometheus->connections_awaiting_total) > 0) { atomic_fetch_sub(&prometheus->connections_awaiting_total, 1); } } void pgagroal_prometheus_connection_flush(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_flush, 1); } void pgagroal_prometheus_connection_success(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->connection_success, 1); } void pgagroal_prometheus_auth_user_success(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->auth_user_success, 1); } void pgagroal_prometheus_auth_user_bad_password(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->auth_user_bad_password, 1); } void pgagroal_prometheus_auth_user_error(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->auth_user_error, 1); } void pgagroal_prometheus_client_wait_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->client_wait, 1); } void pgagroal_prometheus_client_wait_sub(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_sub(&prometheus->client_wait, 1); } void pgagroal_prometheus_client_active_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->client_active, 1); } void pgagroal_prometheus_client_active_sub(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_sub(&prometheus->client_active, 1); } void pgagroal_prometheus_query_count_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->query_count, 1); } void pgagroal_prometheus_query_count_specified_add(int slot) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->prometheus_connections[slot].query_count, 1); } void pgagroal_prometheus_query_count_specified_reset(int slot) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_store(&prometheus->prometheus_connections[slot].query_count, 0); } void pgagroal_prometheus_tx_count_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->tx_count, 1); } void pgagroal_prometheus_network_sent_add(ssize_t s) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->network_sent, s); } void pgagroal_prometheus_network_received_add(ssize_t s) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->network_received, s); } void pgagroal_prometheus_client_sockets_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->client_sockets, 1); } void pgagroal_prometheus_client_sockets_sub(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_sub(&prometheus->client_sockets, 1); } void pgagroal_prometheus_self_sockets_add(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->self_sockets, 1); } void pgagroal_prometheus_self_sockets_sub(void) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_sub(&prometheus->self_sockets, 1); } void pgagroal_prometheus_reset(void) { signed char cache_is_free; struct configuration* config; struct prometheus* prometheus; struct prometheus_cache* cache; config = (struct configuration*) shmem; prometheus = (struct prometheus*)prometheus_shmem; cache = (struct prometheus_cache*)prometheus_cache_shmem; for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { atomic_store(&prometheus->session_time[i], 0); } atomic_store(&prometheus->session_time_sum, 0); atomic_store(&prometheus->connection_error, 0); atomic_store(&prometheus->connection_kill, 0); atomic_store(&prometheus->connection_remove, 0); atomic_store(&prometheus->connection_timeout, 0); atomic_store(&prometheus->connection_return, 0); atomic_store(&prometheus->connection_invalid, 0); atomic_store(&prometheus->connection_get, 0); atomic_store(&prometheus->connection_idletimeout, 0); atomic_store(&prometheus->connection_max_connection_age, 0); atomic_store(&prometheus->connection_flush, 0); atomic_store(&prometheus->connection_success, 0); // awaiting connections are on hold due to `blocking_timeout` atomic_store(&prometheus->connections_awaiting_total, 0); for (int i = 0; i < NUMBER_OF_LIMITS; i++) { atomic_store(&prometheus->connections_awaiting[i], 0); } atomic_store(&prometheus->auth_user_success, 0); atomic_store(&prometheus->auth_user_bad_password, 0); atomic_store(&prometheus->auth_user_error, 0); atomic_store(&prometheus->client_active, 0); atomic_store(&prometheus->client_wait, 0); atomic_store(&prometheus->client_wait_time, 0); atomic_store(&prometheus->query_count, 0); atomic_store(&prometheus->tx_count, 0); atomic_store(&prometheus->network_sent, 0); atomic_store(&prometheus->network_received, 0); atomic_store(&prometheus->client_sockets, 0); atomic_store(&prometheus->self_sockets, 0); for (int i = 0; i < NUMBER_OF_SERVERS; i++) { atomic_store(&prometheus->server_error[i], 0); } for (int i = 0; i < config->max_connections; i++) { atomic_store(&prometheus->prometheus_connections[i].query_count, 0); } retry_cache_locking: cache_is_free = STATE_FREE; if (atomic_compare_exchange_strong(&cache->lock, &cache_is_free, STATE_IN_USE)) { metrics_cache_invalidate(); atomic_store(&cache->lock, STATE_FREE); } else { /* Sleep for 1ms */ SLEEP_AND_GOTO(1000000L, retry_cache_locking); } } void pgagroal_prometheus_server_error(int server) { struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; atomic_fetch_add(&prometheus->server_error[server], 1); } void pgagroal_prometheus_failed_servers(void) { int count; struct prometheus* prometheus; struct configuration* config; prometheus = (struct prometheus*)prometheus_shmem; config = (struct configuration*) shmem; count = 0; for (int i = 0; i < config->number_of_servers; i++) { signed char state = atomic_load(&config->servers[i].state); if (state == SERVER_FAILED) { count++; } } atomic_store(&prometheus->failed_servers, count); } static int resolve_page(struct message* msg) { char* from = NULL; int index; if (msg->length < 3 || strncmp((char*)msg->data, "GET", 3) != 0) { pgagroal_log_debug("Promethus: Not a GET request"); return BAD_REQUEST; } index = 4; from = (char*)msg->data + index; while (pgagroal_read_byte(msg->data + index) != ' ') { index++; } pgagroal_write_byte(msg->data + index, '\0'); if (strcmp(from, "/") == 0 || strcmp(from, "/index.html") == 0) { return PAGE_HOME; } else if (strcmp(from, "/metrics") == 0) { return PAGE_METRICS; } return PAGE_UNKNOWN; } static int unknown_page(int client_fd) { char* data = NULL; time_t now; char time_buf[32]; int status; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&data, 0, sizeof(data)); now = time(NULL); memset(&time_buf, 0, sizeof(time_buf)); ctime_r(&now, &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; data = pgagroal_append(data, "HTTP/1.1 403 Forbidden\r\n"); data = pgagroal_append(data, "Date: "); data = pgagroal_append(data, &time_buf[0]); data = pgagroal_append(data, "\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; status = pgagroal_write_message(NULL, client_fd, &msg); free(data); return status; } static int home_page(int client_fd) { char* data = NULL; time_t now; char time_buf[32]; int status; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&data, 0, sizeof(data)); now = time(NULL); memset(&time_buf, 0, sizeof(time_buf)); ctime_r(&now, &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; data = pgagroal_append(data, "HTTP/1.1 200 OK\r\n"); data = pgagroal_append(data, "Content-Type: text/html; charset=utf-8\r\n"); data = pgagroal_append(data, "Date: "); data = pgagroal_append(data, &time_buf[0]); data = pgagroal_append(data, "\r\n"); data = pgagroal_append(data, "Transfer-Encoding: chunked\r\n"); data = pgagroal_append(data, "\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; status = pgagroal_write_message(NULL, client_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto done; } free(data); data = NULL; data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, " pgagroal exporter\n"); data = pgagroal_append(data, " "); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "

pgagroal exporter

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Metrics\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_state

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The state of pgagroal\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
valueState\n"); data = pgagroal_append(data, "
    \n"); data = pgagroal_append(data, "
  1. Running
  2. \n"); data = pgagroal_append(data, "
  3. Graceful shutdown
  4. \n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "

pgagroal_pipeline_mode

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The mode of pipeline\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
valueMode\n"); data = pgagroal_append(data, "
    \n"); data = pgagroal_append(data, "
  1. Performance
  2. \n"); data = pgagroal_append(data, "
  3. Session
  4. \n"); data = pgagroal_append(data, "
  5. Transaction
  6. \n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "

pgagroal_server_error

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Errors for servers\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
nameThe name of the server
stateThe server state\n"); data = pgagroal_append(data, "
    \n"); data = pgagroal_append(data, "
  • not_init
  • \n"); data = pgagroal_append(data, "
  • primary
  • \n"); data = pgagroal_append(data, "
  • replica
  • \n"); data = pgagroal_append(data, "
  • failover
  • \n"); data = pgagroal_append(data, "
  • failed
  • \n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "

pgagroal_failed_servers

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of failed servers. Only set if failover is enabled\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_wait_time

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The waiting time of clients\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_query_count

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of queries. Only session and transaction modes are supported\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_query_count

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of queries per connection. Only session and transaction modes are supported\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
idThe connection identifier
userThe user name
databaseThe database
application_nameThe application name
\n"); data = pgagroal_append(data, "

pgagroal_tx_count

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of transactions. Only session and transaction modes are supported\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_active_connections

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of active connections\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_total_connections

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The number of total connections\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_max_connections

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " The maximum number of connections\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Connection information\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
idThe connection identifier
userThe user name
databaseThe database
application_nameThe application name
stateThe connection state\n"); data = pgagroal_append(data, "
    \n"); data = pgagroal_append(data, "
  • not_init
  • \n"); data = pgagroal_append(data, "
  • init
  • \n"); data = pgagroal_append(data, "
  • free
  • \n"); data = pgagroal_append(data, "
  • in_use
  • \n"); data = pgagroal_append(data, "
  • gracefully
  • \n"); data = pgagroal_append(data, "
  • flush
  • \n"); data = pgagroal_append(data, "
  • idle_check
  • \n"); data = pgagroal_append(data, "
  • max_connection_age
  • \n"); data = pgagroal_append(data, "
  • validation
  • \n"); data = pgagroal_append(data, "
  • remove
  • \n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "

pgagroal_limit

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Limit information\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
userThe user name
databaseThe database
typeThe information type\n"); data = pgagroal_append(data, "
    \n"); data = pgagroal_append(data, "
  • min
  • \n"); data = pgagroal_append(data, "
  • initial
  • \n"); data = pgagroal_append(data, "
  • max
  • \n"); data = pgagroal_append(data, "
  • active
  • \n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "
\n"); data = pgagroal_append(data, "

pgagroal_limit_awaiting

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Connections awaiting on hold reported by limit entries\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, " \n"); data = pgagroal_append(data, "
userThe user name
databaseThe database
\n"); data = pgagroal_append(data, "

pgagroal_session_time

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Histogram of session times\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_error

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection errors\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_kill

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection kills\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_remove

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection removes\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_timeout

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection time outs\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_return

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection returns\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_invalid

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection invalids\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_get

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection gets\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_idletimeout

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection idle timeouts\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_max_connection_age

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection max age timeouts\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_flush

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection flushes\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_success

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection successes\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_connection_awaiting

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of connection suspended due to blocking_timeout\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_auth_user_success

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of successful user authentications\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_auth_user_bad_password

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of bad passwords during user authentication\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_auth_user_error

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of errors during user authentication\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_client_wait

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of waiting clients\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_client_active

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of active clients\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_network_sent

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Bytes sent by clients. Only session and transaction modes are supported\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_network_received

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Bytes received from servers. Only session and transaction modes are supported\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_client_sockets

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of sockets the client used\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

pgagroal_self_sockets

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " Number of sockets used by pgagroal itself\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, " agroal.github.io/pgagroal/\n"); data = pgagroal_append(data, "

\n"); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "\n"); send_chunk(client_fd, data); free(data); data = NULL; /* Footer */ data = pgagroal_append(data, "0\r\n\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; status = pgagroal_write_message(NULL, client_fd, &msg); done: if (data != NULL) { free(data); } return status; } static int metrics_page(int client_fd) { char* data = NULL; time_t now; char time_buf[32]; int status; struct message msg; struct prometheus_cache* cache; signed char cache_is_free; cache = (struct prometheus_cache*)prometheus_cache_shmem; memset(&msg, 0, sizeof(struct message)); retry_cache_locking: cache_is_free = STATE_FREE; if (atomic_compare_exchange_strong(&cache->lock, &cache_is_free, STATE_IN_USE)) { // can serve the message out of cache? if (is_metrics_cache_configured() && is_metrics_cache_valid()) { // serve the message directly out of the cache pgagroal_log_debug("Serving metrics out of cache (%d/%d bytes valid until %lld)", strlen(cache->data), cache->size, cache->valid_until); msg.kind = 0; msg.length = strlen(cache->data); msg.data = cache->data; } else { // build the message without the cache metrics_cache_invalidate(); now = time(NULL); memset(&time_buf, 0, sizeof(time_buf)); ctime_r(&now, &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; data = pgagroal_append(data, "HTTP/1.1 200 OK\r\n"); data = pgagroal_append(data, "Content-Type: text/plain; version=0.0.3; charset=utf-8\r\n"); data = pgagroal_append(data, "Date: "); data = pgagroal_append(data, &time_buf[0]); data = pgagroal_append(data, "\r\n"); metrics_cache_append(data); // cache here to avoid the chunking for the cache data = pgagroal_append(data, "Transfer-Encoding: chunked\r\n"); data = pgagroal_append(data, "\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; status = pgagroal_write_message(NULL, client_fd, &msg); if (status != MESSAGE_STATUS_OK) { metrics_cache_invalidate(); atomic_store(&cache->lock, STATE_FREE); goto error; } free(data); data = NULL; general_information(client_fd); connection_information(client_fd); limit_information(client_fd); session_information(client_fd); pool_information(client_fd); auth_information(client_fd); client_information(client_fd); internal_information(client_fd); connection_awaiting_information(client_fd); /* Footer */ data = pgagroal_append(data, "0\r\n\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; metrics_cache_finalize(); } // free the cache atomic_store(&cache->lock, STATE_FREE); } // end of cache locking else { /* Sleep for 1ms */ SLEEP_AND_GOTO(1000000L, retry_cache_locking) } status = pgagroal_write_message(NULL, client_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } free(data); return 0; error: free(data); return 1; } static int bad_request(int client_fd) { char* data = NULL; time_t now; char time_buf[32]; int status; struct message msg; memset(&msg, 0, sizeof(struct message)); memset(&data, 0, sizeof(data)); now = time(NULL); memset(&time_buf, 0, sizeof(time_buf)); ctime_r(&now, &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; data = pgagroal_append(data, "HTTP/1.1 400 Bad Request\r\n"); data = pgagroal_append(data, "Date: "); data = pgagroal_append(data, &time_buf[0]); data = pgagroal_append(data, "\r\n"); msg.kind = 0; msg.length = strlen(data); msg.data = data; status = pgagroal_write_message(NULL, client_fd, &msg); free(data); return status; } static void general_information(int client_fd) { char* data = NULL; struct configuration* config; struct prometheus* prometheus; config = (struct configuration*)shmem; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_state The state of pgagroal\n"); data = pgagroal_append(data, "#TYPE pgagroal_state gauge\n"); data = pgagroal_append(data, "pgagroal_state "); if (config->gracefully) { data = pgagroal_append(data, "2"); } else { data = pgagroal_append(data, "1"); } data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_pipeline_mode The mode of pipeline\n"); data = pgagroal_append(data, "#TYPE pgagroal_pipeline_mode gauge\n"); data = pgagroal_append(data, "pgagroal_pipeline_mode "); data = pgagroal_append_int(data, config->pipeline); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_server_error The number of errors for servers\n"); data = pgagroal_append(data, "#TYPE pgagroal_server_error counter\n"); for (int i = 0; i < config->number_of_servers; i++) { int state = atomic_load(&config->servers[i].state); data = pgagroal_append(data, "pgagroal_server_error{"); data = pgagroal_append(data, "name=\""); data = pgagroal_append(data, config->servers[i].name); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "state=\""); switch (state) { case SERVER_NOTINIT: case SERVER_NOTINIT_PRIMARY: data = pgagroal_append(data, "not_init"); break; case SERVER_PRIMARY: data = pgagroal_append(data, "primary"); break; case SERVER_REPLICA: data = pgagroal_append(data, "replica"); break; case SERVER_FAILOVER: data = pgagroal_append(data, "failover"); break; case SERVER_FAILED: data = pgagroal_append(data, "failed"); break; default: break; } data = pgagroal_append(data, "\"} "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->server_error[i])); data = pgagroal_append(data, "\n"); } data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "#HELP pgagroal_failed_servers The number of failed servers\n"); data = pgagroal_append(data, "#TYPE pgagroal_failed_servers gauge\n"); data = pgagroal_append(data, "pgagroal_failed_servers "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->failed_servers)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_wait_time The waiting time of clients\n"); data = pgagroal_append(data, "#TYPE pgagroal_wait_time gauge\n"); data = pgagroal_append(data, "pgagroal_wait_time "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->client_wait_time)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_query_count The number of queries\n"); data = pgagroal_append(data, "#TYPE pgagroal_query_count counter\n"); data = pgagroal_append(data, "pgagroal_query_count "); data = pgagroal_append_ullong(data, atomic_load(&prometheus->query_count)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_query_count The number of queries per connection\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_query_count counter\n"); for (int i = 0; i < config->max_connections; i++) { data = pgagroal_append(data, "pgagroal_connection_query_count{"); data = pgagroal_append(data, "id=\""); data = pgagroal_append_int(data, i); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->connections[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->connections[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "application_name=\""); data = pgagroal_append(data, config->connections[i].appname); data = pgagroal_append(data, "\"} "); data = pgagroal_append_ullong(data, atomic_load(&prometheus->prometheus_connections[i].query_count)); data = pgagroal_append(data, "\n"); } data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "#HELP pgagroal_tx_count The number of transactions\n"); data = pgagroal_append(data, "#TYPE pgagroal_tx_count counter\n"); data = pgagroal_append(data, "pgagroal_tx_count "); data = pgagroal_append_ullong(data, atomic_load(&prometheus->tx_count)); data = pgagroal_append(data, "\n\n"); if (data != NULL) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } static void connection_information(int client_fd) { char* data = NULL; int active; int total; struct configuration* config; config = (struct configuration*)shmem; active = 0; total = 0; for (int i = 0; i < config->max_connections; i++) { int state = atomic_load(&config->states[i]); switch (state) { case STATE_IN_USE: case STATE_GRACEFULLY: active++; case STATE_INIT: case STATE_FREE: case STATE_FLUSH: case STATE_IDLE_CHECK: case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: total++; break; default: break; } } data = pgagroal_append(data, "#HELP pgagroal_active_connections The number of active connections\n"); data = pgagroal_append(data, "#TYPE pgagroal_active_connections gauge\n"); data = pgagroal_append(data, "pgagroal_active_connections "); data = pgagroal_append_int(data, active); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_total_connections The total number of connections\n"); data = pgagroal_append(data, "#TYPE pgagroal_total_connections gauge\n"); data = pgagroal_append(data, "pgagroal_total_connections "); data = pgagroal_append_int(data, total); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_max_connections The maximum number of connections\n"); data = pgagroal_append(data, "#TYPE pgagroal_max_connections counter\n"); data = pgagroal_append(data, "pgagroal_max_connections "); data = pgagroal_append_int(data, config->max_connections); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection The connection information\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection gauge\n"); for (int i = 0; i < config->max_connections; i++) { int state = atomic_load(&config->states[i]); data = pgagroal_append(data, "pgagroal_connection{"); data = pgagroal_append(data, "id=\""); data = pgagroal_append_int(data, i); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->connections[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->connections[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "application_name=\""); data = pgagroal_append(data, config->connections[i].appname); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "state=\""); switch (state) { case STATE_NOTINIT: data = pgagroal_append(data, "not_init"); break; case STATE_INIT: data = pgagroal_append(data, "init"); break; case STATE_FREE: data = pgagroal_append(data, "free"); break; case STATE_IN_USE: data = pgagroal_append(data, "in_use"); break; case STATE_GRACEFULLY: data = pgagroal_append(data, "gracefully"); break; case STATE_FLUSH: data = pgagroal_append(data, "flush"); break; case STATE_IDLE_CHECK: data = pgagroal_append(data, "idle_check"); break; case STATE_MAX_CONNECTION_AGE: data = pgagroal_append(data, "max_connection_age"); break; case STATE_VALIDATION: data = pgagroal_append(data, "validation"); break; case STATE_REMOVE: data = pgagroal_append(data, "remove"); break; default: break; } data = pgagroal_append(data, "\"} "); switch (state) { case STATE_NOTINIT: data = pgagroal_append(data, "0"); break; case STATE_INIT: case STATE_FREE: case STATE_IN_USE: case STATE_GRACEFULLY: case STATE_FLUSH: case STATE_IDLE_CHECK: case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: data = pgagroal_append(data, "1"); break; default: break; } data = pgagroal_append(data, "\n"); if (strlen(data) > CHUNK_SIZE) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } data = pgagroal_append(data, "\n"); if (data != NULL) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } static void limit_information(int client_fd) { char* data = NULL; struct configuration* config; config = (struct configuration*)shmem; if (config->number_of_limits > 0) { data = pgagroal_append(data, "#HELP pgagroal_limit The limit information\n"); data = pgagroal_append(data, "#TYPE pgagroal_limit gauge\n"); for (int i = 0; i < config->number_of_limits; i++) { data = pgagroal_append(data, "pgagroal_limit{"); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->limits[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->limits[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "type=\"min\"} "); data = pgagroal_append_int(data, config->limits[i].min_size); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_limit{"); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->limits[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->limits[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "type=\"initial\"} "); data = pgagroal_append_int(data, config->limits[i].initial_size); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_limit{"); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->limits[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->limits[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "type=\"max\"} "); data = pgagroal_append_int(data, config->limits[i].max_size); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_limit{"); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->limits[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->limits[i].database); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "type=\"active\"} "); data = pgagroal_append_int(data, config->limits[i].active_connections); data = pgagroal_append(data, "\n"); if (strlen(data) > CHUNK_SIZE) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } data = pgagroal_append(data, "\n"); if (data != NULL) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } } static void session_information(int client_fd) { char* data = NULL; unsigned long counter; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; counter = 0; data = pgagroal_append(data, "#HELP pgagroal_session_time_seconds The session times\n"); data = pgagroal_append(data, "#TYPE pgagroal_session_time_seconds histogram\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"5\"} "); counter += atomic_load(&prometheus->session_time[0]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"10\"} "); counter += atomic_load(&prometheus->session_time[1]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"20\"} "); counter += atomic_load(&prometheus->session_time[2]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"30\"} "); counter += atomic_load(&prometheus->session_time[3]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"45\"} "); counter += atomic_load(&prometheus->session_time[4]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"60\"} "); counter += atomic_load(&prometheus->session_time[5]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"300\"} "); counter += atomic_load(&prometheus->session_time[6]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"600\"} "); counter += atomic_load(&prometheus->session_time[7]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"1200\"} "); counter += atomic_load(&prometheus->session_time[8]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"1800\"} "); counter += atomic_load(&prometheus->session_time[9]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"2700\"} "); counter += atomic_load(&prometheus->session_time[10]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"3600\"} "); counter += atomic_load(&prometheus->session_time[11]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"7200\"} "); counter += atomic_load(&prometheus->session_time[12]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"14400\"} "); counter += atomic_load(&prometheus->session_time[13]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"21600\"} "); counter += atomic_load(&prometheus->session_time[14]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"43200\"} "); counter += atomic_load(&prometheus->session_time[15]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"86400\"} "); counter += atomic_load(&prometheus->session_time[16]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_bucket{le=\"+Inf\"} "); counter += atomic_load(&prometheus->session_time[17]); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_sum "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->session_time_sum)); data = pgagroal_append(data, "\n"); data = pgagroal_append(data, "pgagroal_session_time_seconds_count "); data = pgagroal_append_ulong(data, counter); data = pgagroal_append(data, "\n\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } static void pool_information(int client_fd) { char* data = NULL; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_connection_error Number of connection errors\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_error counter\n"); data = pgagroal_append(data, "pgagroal_connection_error "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_error)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_kill Number of connection kills\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_kill counter\n"); data = pgagroal_append(data, "pgagroal_connection_kill "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_kill)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_remove Number of connection removes\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_remove counter\n"); data = pgagroal_append(data, "pgagroal_connection_remove "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_remove)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_timeout Number of connection time outs\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_timeout counter\n"); data = pgagroal_append(data, "pgagroal_connection_timeout "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_timeout)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_return Number of connection returns\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_return counter\n"); data = pgagroal_append(data, "pgagroal_connection_return "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_return)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_invalid Number of connection invalids\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_invalid counter\n"); data = pgagroal_append(data, "pgagroal_connection_invalid "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_invalid)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_get Number of connection gets\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_get counter\n"); data = pgagroal_append(data, "pgagroal_connection_get "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_get)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_idletimeout Number of connection idle timeouts\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_idletimeout counter\n"); data = pgagroal_append(data, "pgagroal_connection_idletimeout "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_idletimeout)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_max_connection_age Number of connection max age timeouts\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_max_connection_age counter\n"); data = pgagroal_append(data, "pgagroal_connection_max_connection_age "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_max_connection_age)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_flush Number of connection flushes\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_flush counter\n"); data = pgagroal_append(data, "pgagroal_connection_flush "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_flush)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_connection_success Number of connection successes\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_success counter\n"); data = pgagroal_append(data, "pgagroal_connection_success "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_success)); data = pgagroal_append(data, "\n\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } static void auth_information(int client_fd) { char* data = NULL; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_auth_user_success Number of successful user authentications\n"); data = pgagroal_append(data, "#TYPE pgagroal_auth_user_success counter\n"); data = pgagroal_append(data, "pgagroal_auth_user_success "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->auth_user_success)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_auth_user_bad_password Number of bad passwords during user authentication\n"); data = pgagroal_append(data, "#TYPE pgagroal_auth_user_bad_password counter\n"); data = pgagroal_append(data, "pgagroal_auth_user_bad_password "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->auth_user_bad_password)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_auth_user_error Number of errors during user authentication\n"); data = pgagroal_append(data, "#TYPE pgagroal_auth_user_error counter\n"); data = pgagroal_append(data, "pgagroal_auth_user_error "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->auth_user_error)); data = pgagroal_append(data, "\n\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } static void client_information(int client_fd) { char* data = NULL; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_client_wait Number of waiting clients\n"); data = pgagroal_append(data, "#TYPE pgagroal_client_wait gauge\n"); data = pgagroal_append(data, "pgagroal_client_wait "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->client_wait)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_client_active Number of active clients\n"); data = pgagroal_append(data, "#TYPE pgagroal_client_active gauge\n"); data = pgagroal_append(data, "pgagroal_client_active "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->client_active)); data = pgagroal_append(data, "\n\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } static void internal_information(int client_fd) { char* data = NULL; struct prometheus* prometheus; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_network_sent Bytes sent by clients\n"); data = pgagroal_append(data, "#TYPE pgagroal_network_sent gauge\n"); data = pgagroal_append(data, "pgagroal_network_sent "); data = pgagroal_append_ullong(data, atomic_load(&prometheus->network_sent)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_network_received Bytes received from servers\n"); data = pgagroal_append(data, "#TYPE pgagroal_network_received gauge\n"); data = pgagroal_append(data, "pgagroal_network_received "); data = pgagroal_append_ullong(data, atomic_load(&prometheus->network_received)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_client_sockets Number of sockets the client used\n"); data = pgagroal_append(data, "#TYPE pgagroal_client_sockets gauge\n"); data = pgagroal_append(data, "pgagroal_client_sockets "); data = pgagroal_append_int(data, atomic_load(&prometheus->client_sockets)); data = pgagroal_append(data, "\n\n"); data = pgagroal_append(data, "#HELP pgagroal_self_sockets Number of sockets used by pgagroal itself\n"); data = pgagroal_append(data, "#TYPE pgagroal_self_sockets gauge\n"); data = pgagroal_append(data, "pgagroal_self_sockets "); data = pgagroal_append_int(data, atomic_load(&prometheus->self_sockets)); data = pgagroal_append(data, "\n\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } /** * Provides information about the connection awaiting. * * Prints the total connection awaiting counter * and also one line per limit if there are limits. */ static void connection_awaiting_information(int client_fd) { char* data = NULL; struct configuration* config; struct prometheus* prometheus; config = (struct configuration*)shmem; prometheus = (struct prometheus*)prometheus_shmem; data = pgagroal_append(data, "#HELP pgagroal_connection_awaiting Number of connection on-hold (awaiting)\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_awaiting gauge\n"); data = pgagroal_append(data, "pgagroal_connection_awaiting "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connections_awaiting_total)); data = pgagroal_append(data, "\n\n"); if (config->number_of_limits > 0) { data = pgagroal_append(data, "#HELP pgagroal_limit_awaiting The connections on-hold (awaiting) information\n"); data = pgagroal_append(data, "#TYPE pgagroal_limit_awaiting gauge\n"); for (int i = 0; i < config->number_of_limits; i++) { data = pgagroal_append(data, "pgagroal_limit_awaiting{"); data = pgagroal_append(data, "user=\""); data = pgagroal_append(data, config->limits[i].username); data = pgagroal_append(data, "\","); data = pgagroal_append(data, "database=\""); data = pgagroal_append(data, config->limits[i].database); data = pgagroal_append(data, "\"} "); data = pgagroal_append_ulong(data, atomic_load(&prometheus->connections_awaiting[i])); data = pgagroal_append(data, "\n"); if (strlen(data) > CHUNK_SIZE) { send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } } if (data != NULL) { data = pgagroal_append(data, "\n"); send_chunk(client_fd, data); metrics_cache_append(data); free(data); data = NULL; } } static int send_chunk(int client_fd, char* data) { int status; char* m = NULL; struct message msg; memset(&msg, 0, sizeof(struct message)); m = calloc(1, 20); if (m == NULL) { pgagroal_log_fatal("Couldn't allocate memory while binding host"); return MESSAGE_STATUS_ERROR; } sprintf(m, "%lX\r\n", strlen(data)); m = pgagroal_append(m, data); m = pgagroal_append(m, "\r\n"); msg.kind = 0; msg.length = strlen(m); msg.data = m; status = pgagroal_write_message(NULL, client_fd, &msg); free(m); return status; } /** * Checks if the Prometheus cache configuration setting * (`metrics_cache`) has a non-zero value, that means there * are seconds to cache the response. * * @return true if there is a cache configuration, * false if no cache is active */ static bool is_metrics_cache_configured(void) { struct configuration* config; config = (struct configuration*)shmem; // cannot have caching if not set metrics! if (config->metrics == 0) { return false; } return config->metrics_cache_max_age != PGAGROAL_PROMETHEUS_CACHE_DISABLED; } /** * Checks if the cache is still valid, and therefore can be * used to serve as a response. * A cache is considred valid if it has non-empty payload and * a timestamp in the future. * * @return true if the cache is still valid */ static bool is_metrics_cache_valid(void) { time_t now; struct prometheus_cache* cache; cache = (struct prometheus_cache*)prometheus_cache_shmem; if (cache->valid_until == 0 || strlen(cache->data) == 0) { return false; } now = time(NULL); return now <= cache->valid_until; } int pgagroal_init_prometheus_cache(size_t* p_size, void** p_shmem) { struct prometheus_cache* cache; struct configuration* config; size_t cache_size = 0; size_t struct_size = 0; config = (struct configuration*)shmem; // first of all, allocate the overall cache structure cache_size = metrics_cache_size_to_alloc(); struct_size = sizeof(struct prometheus_cache); if (pgagroal_create_shared_memory(struct_size + cache_size, config->hugepage, (void*) &cache)) { goto error; } memset(cache, 0, struct_size + cache_size); cache->valid_until = 0; cache->size = cache_size; atomic_init(&cache->lock, STATE_FREE); // success! do the memory swap *p_shmem = cache; *p_size = cache_size + struct_size; return 0; error: // disable caching config->metrics_cache_max_age = config->metrics_cache_max_size = PGAGROAL_PROMETHEUS_CACHE_DISABLED; pgagroal_log_error("Cannot allocate shared memory for the Prometheus cache!"); *p_size = 0; *p_shmem = NULL; return 1; } /** * Provides the size of the cache to allocate. * * It checks if the metrics cache is configured, and * computers the right minimum value between the * user configured requested size and the default * cache size. * * @return the cache size to allocate */ static size_t metrics_cache_size_to_alloc(void) { struct configuration* config; size_t cache_size = 0; config = (struct configuration*)shmem; // which size to use ? // either the configured (i.e., requested by user) if lower than the max size // or the default value if (is_metrics_cache_configured()) { cache_size = config->metrics_cache_max_size > 0 ? MIN(config->metrics_cache_max_size, PROMETHEUS_MAX_CACHE_SIZE) : PROMETHEUS_DEFAULT_CACHE_SIZE; } return cache_size; } /** * Invalidates the cache. * * Requires the caller to hold the lock on the cache! * * Invalidating the cache means that the payload is zero-filled * and that the valid_until field is set to zero too. */ static void metrics_cache_invalidate(void) { struct prometheus_cache* cache; cache = (struct prometheus_cache*)prometheus_cache_shmem; memset(cache->data, 0, cache->size); cache->valid_until = 0; } /** * Appends data to the cache. * * Requires the caller to hold the lock on the cache! * * If the input data is empty, nothing happens. * The data is appended only if the cache does not overflows, that * means the current size of the cache plus the size of the data * to append does not exceed the current cache size. * If the cache overflows, the cache is flushed and marked * as invalid. * This makes safe to call this method along the workflow of * building the Prometheus response. * * @param data the string to append to the cache * @return true on success */ static bool metrics_cache_append(char* data) { int origin_length = 0; int append_length = 0; struct prometheus_cache* cache; cache = (struct prometheus_cache*)prometheus_cache_shmem; if (!is_metrics_cache_configured()) { return false; } origin_length = strlen(cache->data); append_length = strlen(data); // need to append the data to the cache if (origin_length + append_length >= cache->size) { // cannot append new data, so invalidate cache pgagroal_log_debug("Cannot append %d bytes to the Prometheus cache because it will overflow the size of %d bytes (currently at %d bytes). HINT: try adjusting `metrics_cache_max_size`", append_length, cache->size, origin_length); metrics_cache_invalidate(); return false; } // append the data to the data field memcpy(cache->data + origin_length, data, append_length); cache->data[origin_length + append_length + 1] = '\0'; return true; } /** * Finalizes the cache. * * Requires the caller to hold the lock on the cache! * * This method should be invoked when the cache is complete * and therefore can be served. * * @return true if the cache has a validity */ static bool metrics_cache_finalize(void) { struct configuration* config; struct prometheus_cache* cache; time_t now; cache = (struct prometheus_cache*)prometheus_cache_shmem; config = (struct configuration*)shmem; if (!is_metrics_cache_configured()) { return false; } now = time(NULL); cache->valid_until = now + config->metrics_cache_max_age; return cache->valid_until > now; } pgagroal-1.6.0/src/libpgagroal/remote.c000066400000000000000000000131571456565230200200420ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include /* system */ #include #include #include #include #include void pgagroal_remote_management(int client_fd, char* address) { int server_fd = -1; int status; int exit_code; int auth_status; signed char type; SSL* client_ssl = NULL; struct message* msg = NULL; struct configuration* config; pgagroal_start_logging(); pgagroal_memory_init(); exit_code = 0; config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal_remote_management: connect %d", client_fd); auth_status = pgagroal_remote_management_auth(client_fd, address, &client_ssl); if (auth_status == AUTH_SUCCESS) { status = pgagroal_read_timeout_message(client_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto done; } type = pgagroal_read_byte(msg->data); if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &server_fd)) { goto done; } status = pgagroal_write_message(NULL, server_fd, msg); if (status != MESSAGE_STATUS_OK) { goto done; } switch (type) { case MANAGEMENT_GRACEFULLY: case MANAGEMENT_STOP: case MANAGEMENT_CANCEL_SHUTDOWN: case MANAGEMENT_RESET: case MANAGEMENT_RELOAD: break; case MANAGEMENT_STATUS: case MANAGEMENT_ISALIVE: case MANAGEMENT_DETAILS: do { status = pgagroal_read_timeout_message(NULL, server_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { goto done; } status = pgagroal_write_message(client_ssl, client_fd, msg); } while (status == MESSAGE_STATUS_OK); break; case MANAGEMENT_FLUSH: case MANAGEMENT_RESET_SERVER: case MANAGEMENT_SWITCH_TO: 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; } case MANAGEMENT_ENABLEDB: case MANAGEMENT_DISABLEDB: 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(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; } break; default: pgagroal_log_warn("Unknown management operation: %d", type); pgagroal_log_message(msg); exit_code = 1; goto done; break; } } else { exit_code = 1; } done: if (client_ssl != NULL) { int res; SSL_CTX* ctx = SSL_get_SSL_CTX(client_ssl); res = SSL_shutdown(client_ssl); if (res == 0) { SSL_shutdown(client_ssl); } SSL_free(client_ssl); SSL_CTX_free(ctx); } pgagroal_log_debug("pgagroal_remote_management: disconnect %d", client_fd); pgagroal_disconnect(client_fd); pgagroal_disconnect(server_fd); free(address); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(exit_code); } pgagroal-1.6.0/src/libpgagroal/security.c000066400000000000000000004451131456565230200204170ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int get_auth_type(struct message* msg, int* auth_type); static int compare_auth_response(struct message* orig, struct message* response, int auth_type); static int use_pooled_connection(SSL* c_ssl, int client_fd, int slot, char* username, char* database, int hba_method, SSL** server_ssl); static int use_unpooled_connection(struct message* msg, SSL* c_ssl, int client_fd, int slot, char* username, int hba_method, SSL** server_ssl); static int client_trust(SSL* c_ssl, int client_fd, char* username, char* password, int slot); static int client_password(SSL* c_ssl, int client_fd, char* username, char* password, int slot); static int client_md5(SSL* c_ssl, int client_fd, char* username, char* password, int slot); static int client_scram256(SSL* c_ssl, int client_fd, char* username, char* password, int slot); static int client_ok(SSL* c_ssl, int client_fd, int slot); static int server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd, int slot); static int server_authenticate(struct message* msg, int auth_type, char* username, char* password, int slot, SSL* server_ssl); static int server_trust(int slot, SSL* server_ssl); static int server_password(char* username, char* password, int slot, SSL* server_ssl); static int server_md5(char* username, char* password, int slot, SSL* server_ssl); static int server_scram256(char* username, char* password, int slot, SSL* server_ssl); static bool is_allowed(char* username, char* database, char* address, int* hba_method); static bool is_allowed_username(char* username, char* entry); static bool is_allowed_database(char* database, char* entry); static bool is_allowed_address(char* address, char* entry); static bool is_disabled(char* database); static int get_hba_method(int index); static char* get_password(char* username); static char* get_frontend_password(char* username); static char* get_admin_password(char* username); static int get_salt(void* data, char** salt); static int derive_key_iv(char* password, unsigned char* key, unsigned char* iv); static int aes_encrypt(char* plaintext, unsigned char* key, unsigned char* iv, char** ciphertext, int* ciphertext_length); static int aes_decrypt(char* ciphertext, int ciphertext_length, unsigned char* key, unsigned char* iv, char** plaintext); static int sasl_prep(char* password, char** password_prep); static int generate_nounce(char** nounce); static int get_scram_attribute(char attribute, char* input, size_t size, char** value); static int client_proof(char* password, char* salt, int salt_length, int iterations, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length, unsigned char** result, int* result_length); static int verify_client_proof(char* stored_key, int stored_key_length, char* client_proof, int client_proof_length, char* salt, int salt_length, int iterations, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length); static int salted_password(char* password, char* salt, int salt_length, int iterations, unsigned char** result, int* result_length); static int salted_password_key(unsigned char* salted_password, int salted_password_length, char* key, unsigned char** result, int* result_length); static int stored_key(unsigned char* client_key, int client_key_length, unsigned char** result, int* result_length); static int generate_salt(char** salt, int* size); static int server_signature(char* password, char* salt, int salt_length, int iterations, char* server_key, int server_key_length, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length, unsigned char** result, int* result_length); static bool is_tls_user(char* username, char* database); 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, 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); static int auth_query_server_md5(struct message* startup_response_msg, char* username, char* password, int socket, SSL* server_ssl); static int auth_query_server_scram256(char* username, char* password, int socket, SSL* server_ssl); static int auth_query_get_password(int socket, SSL* server_ssl, char* username, char* database, char** password); static int auth_query_client_md5(SSL* c_ssl, int client_fd, char* username, char* hash, int slot); static int auth_query_client_scram256(SSL* c_ssl, int client_fd, char* username, char* shadow, int slot); int pgagroal_authenticate(int client_fd, char* address, int* slot, SSL** client_ssl, SSL** server_ssl) { int status = MESSAGE_STATUS_ERROR; int ret; int server = 0; int server_fd = -1; int hba_method; struct configuration* config; struct message* msg = NULL; struct message* request_msg = NULL; int32_t request; char* username = NULL; char* database = NULL; char* appname = NULL; SSL* c_ssl = NULL; config = (struct configuration*)shmem; *slot = -1; *client_ssl = NULL; *server_ssl = NULL; /* Receive client calls - at any point if client exits return AUTH_ERROR */ status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); /* Cancel request: 80877102 */ if (request == 80877102) { pgagroal_log_debug("Cancel request from client: %d", client_fd); /* We need to find the server for the connection */ if (pgagroal_get_primary(&server)) { pgagroal_log_error("pgagroal: No valid server available"); pgagroal_write_connection_refused(NULL, client_fd); pgagroal_write_empty(NULL, client_fd); goto error; } if (config->servers[server].host[0] == '/') { char pgsql[MISC_LENGTH]; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->servers[server].port); ret = pgagroal_connect_unix_socket(config->servers[server].host, &pgsql[0], &server_fd); } else { ret = pgagroal_connect(config->servers[server].host, config->servers[server].port, &server_fd); } if (ret) { pgagroal_log_error("pgagroal: No connection to %s:%d", config->servers[server].host, config->servers[server].port); goto error; } status = pgagroal_write_message(NULL, server_fd, msg); if (status != MESSAGE_STATUS_OK) { pgagroal_disconnect(server_fd); goto error; } pgagroal_free_message(msg); pgagroal_disconnect(server_fd); return AUTH_BAD_PASSWORD; } /* GSS request: 80877104 */ if (request == 80877104) { pgagroal_log_debug("GSS request from client: %d", client_fd); status = pgagroal_write_notice(NULL, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); } /* SSL request: 80877103 */ if (request == 80877103) { pgagroal_log_debug("SSL request from client: %d", client_fd); if (config->tls) { SSL_CTX* ctx = NULL; /* We are acting as a server against the client */ if (create_ssl_ctx(false, &ctx)) { goto error; } if (create_ssl_server(ctx, client_fd, &c_ssl)) { pgagroal_log_debug("authenticate: connection error"); pgagroal_write_connection_refused(NULL, client_fd); pgagroal_write_empty(NULL, client_fd); goto error; } *client_ssl = c_ssl; /* Switch to TLS mode */ status = pgagroal_write_tls(NULL, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = SSL_accept(c_ssl); if (status != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("SSL failed: %s", ERR_reason_error_string(err)); goto error; } status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); } else { status = pgagroal_write_notice(NULL, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); } } /* 196608 -> Ok */ if (request == 196608) { request_msg = pgagroal_copy_message(msg); /* Extract parameters: username / database */ pgagroal_log_trace("authenticate: username/database (%d)", client_fd); pgagroal_extract_username_database(request_msg, &username, &database, &appname); /* TLS scenario */ if (is_tls_user(username, database) && c_ssl == NULL) { pgagroal_log_debug("authenticate: tls: %s / %s / %s", username, database, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Verify client against pgagroal_hba.conf */ if (!is_allowed(username, database, address, &hba_method)) { /* User not allowed */ pgagroal_log_debug("authenticate: not allowed: %s / %s / %s", username, database, address); pgagroal_write_no_hba_entry(c_ssl, client_fd, username, database, address); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Reject scenario */ if (hba_method == SECURITY_REJECT) { pgagroal_log_debug("authenticate: reject: %s / %s / %s", username, database, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Gracefully scenario */ if (config->gracefully) { pgagroal_log_debug("authenticate: gracefully: %s / %s / %s", username, database, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Disabled scenario */ if (is_disabled(database)) { pgagroal_log_debug("authenticate: disabled: %s / %s / %s", username, database, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Get connection */ pgagroal_tracking_event_basic(TRACKER_AUTHENTICATE, username, database); ret = pgagroal_get_connection(username, database, true, false, slot, server_ssl); if (ret != 0) { if (ret == 1) { /* Pool full */ pgagroal_log_debug("authenticate: pool is full"); pgagroal_write_pool_full(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } else { /* Other error */ pgagroal_log_debug("authenticate: connection error"); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } } /* Set the application_name on the connection */ if (appname != NULL) { memset(&config->connections[*slot].appname, 0, MAX_APPLICATION_NAME); memcpy(&config->connections[*slot].appname, appname, strlen(appname)); } if (config->connections[*slot].has_security != SECURITY_INVALID) { pgagroal_log_debug("authenticate: getting pooled connection"); pgagroal_free_message(msg); ret = use_pooled_connection(c_ssl, client_fd, *slot, username, database, hba_method, server_ssl); if (ret == AUTH_BAD_PASSWORD) { goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } pgagroal_log_debug("authenticate: got pooled connection (%d)", *slot); } else { pgagroal_log_debug("authenticate: creating pooled connection"); ret = use_unpooled_connection(request_msg, c_ssl, client_fd, *slot, username, hba_method, server_ssl); if (ret == AUTH_BAD_PASSWORD) { goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } pgagroal_log_debug("authenticate: created pooled connection (%d)", *slot); } pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_prometheus_auth_user_success(); pgagroal_log_debug("authenticate: SUCCESS"); return AUTH_SUCCESS; } else if (request == -1) { goto error; } else { pgagroal_log_debug("authenticate: old version: %d (%s)", request, address); pgagroal_write_connection_refused_old(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } bad_password: pgagroal_free_message(msg); pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_prometheus_auth_user_bad_password(); pgagroal_log_debug("authenticate: BAD_PASSWORD"); return AUTH_BAD_PASSWORD; error: pgagroal_free_message(msg); pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_prometheus_auth_user_error(); pgagroal_log_debug("authenticate: ERROR"); return AUTH_ERROR; } int pgagroal_prefill_auth(char* username, char* password, char* database, int* slot, SSL** server_ssl) { int server_fd = -1; int auth_type = -1; signed char server_state; struct configuration* config = NULL; struct message* startup_msg = NULL; struct message* msg = NULL; int ret = -1; int status = -1; config = (struct configuration*)shmem; *slot = -1; *server_ssl = NULL; /* Get connection */ pgagroal_tracking_event_basic(TRACKER_PREFILL, username, database); ret = pgagroal_get_connection(username, database, false, false, slot, server_ssl); if (ret != 0) { goto error; } server_fd = config->connections[*slot].fd; status = pgagroal_create_startup_message(username, database, &startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(*server_ssl, server_fd, startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(*server_ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } get_auth_type(msg, &auth_type); pgagroal_log_trace("prefill_auth: auth type %d", auth_type); /* Supported security models: */ /* trust (0) */ /* password (3) */ /* md5 (5) */ /* scram256 (10) */ if (auth_type == -1) { goto error; } else if (auth_type != SECURITY_TRUST && auth_type != SECURITY_PASSWORD && auth_type != SECURITY_MD5 && auth_type != SECURITY_SCRAM256) { goto error; } if (server_authenticate(msg, auth_type, username, password, *slot, *server_ssl)) { goto error; } server_state = atomic_load(&config->servers[config->connections[*slot].server].state); if (server_state == SERVER_NOTINIT || server_state == SERVER_NOTINIT_PRIMARY) { pgagroal_log_debug("Verify server mode: %d", config->connections[*slot].server); pgagroal_update_server_state(*slot, server_fd, *server_ssl); pgagroal_server_status(); } pgagroal_log_trace("prefill_auth: has_security %d", config->connections[*slot].has_security); pgagroal_log_debug("prefill_auth: SUCCESS"); pgagroal_free_copy_message(startup_msg); pgagroal_free_message(msg); return AUTH_SUCCESS; error: pgagroal_log_debug("prefill_auth: ERROR"); if (*slot != -1) { pgagroal_tracking_event_slot(TRACKER_PREFILL_KILL, *slot); pgagroal_kill_connection(*slot, *server_ssl); } *slot = -1; *server_ssl = NULL; pgagroal_free_copy_message(startup_msg); pgagroal_free_message(msg); return AUTH_ERROR; } int pgagroal_remote_management_auth(int client_fd, char* address, SSL** client_ssl) { int status = MESSAGE_STATUS_ERROR; int hba_method; struct configuration* config; struct message* msg = NULL; struct message* request_msg = NULL; int32_t request; char* username = NULL; char* database = NULL; char* appname = NULL; char* password = NULL; SSL* c_ssl = NULL; config = (struct configuration*)shmem; *client_ssl = NULL; /* Receive client calls - at any point if client exits return AUTH_ERROR */ status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); /* SSL request: 80877103 */ if (request == 80877103) { pgagroal_log_debug("SSL request from client: %d", client_fd); if (config->tls) { SSL_CTX* ctx = NULL; /* We are acting as a server against the client */ if (create_ssl_ctx(false, &ctx)) { goto error; } if (create_ssl_server(ctx, client_fd, &c_ssl)) { goto error; } *client_ssl = c_ssl; /* Switch to TLS mode */ status = pgagroal_write_tls(NULL, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = SSL_accept(c_ssl); if (status != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("SSL failed: %s", ERR_reason_error_string(err)); goto error; } status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); } else { status = pgagroal_write_notice(NULL, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_timeout_message(NULL, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } request = pgagroal_get_request(msg); } } /* 196608 -> Ok */ if (request == 196608) { request_msg = pgagroal_copy_message(msg); /* Extract parameters: username / database */ pgagroal_log_trace("remote_management_auth: username/database (%d)", client_fd); pgagroal_extract_username_database(request_msg, &username, &database, &appname); /* Must be admin database */ if (strcmp("admin", database) != 0) { pgagroal_log_debug("remote_management_auth: admin: %s / %s", username, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* TLS scenario */ if (is_tls_user(username, "admin") && c_ssl == NULL) { pgagroal_log_debug("remote_management_auth: tls: %s / admin / %s", username, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Verify client against pgagroal_hba.conf */ if (!is_allowed(username, "admin", address, &hba_method)) { /* User not allowed */ pgagroal_log_debug("remote_management_auth: not allowed: %s / admin / %s", username, address); pgagroal_write_no_hba_entry(c_ssl, client_fd, username, "admin", address); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } /* Reject scenario */ if (hba_method == SECURITY_REJECT) { pgagroal_log_debug("remote_management_auth: reject: %s / admin / %s", username, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } password = get_admin_password(username); if (password == NULL) { pgagroal_log_debug("remote_management_auth: password: %s / admin / %s", username, address); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } status = client_scram256(c_ssl, client_fd, username, password, -1); if (status == AUTH_BAD_PASSWORD) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } else if (status == AUTH_ERROR) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } status = pgagroal_write_auth_success(c_ssl, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_log_debug("remote_management_auth: SUCCESS"); return AUTH_SUCCESS; } else if (request == -1) { goto error; } else { pgagroal_log_debug("remote_management_auth: old version: %d (%s)", request, address); pgagroal_write_connection_refused_old(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } bad_password: pgagroal_free_message(msg); pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_log_debug("remote_management_auth: BAD_PASSWORD"); return AUTH_BAD_PASSWORD; error: pgagroal_free_message(msg); pgagroal_free_copy_message(request_msg); free(username); free(database); free(appname); pgagroal_log_debug("remote_management_auth: ERROR"); return AUTH_ERROR; } int pgagroal_remote_management_scram_sha256(char* username, char* password, int server_fd, SSL** s_ssl) { int status = MESSAGE_STATUS_ERROR; SSL* ssl = NULL; char key_file[MISC_LENGTH]; char cert_file[MISC_LENGTH]; char root_file[MISC_LENGTH]; struct stat st = {0}; char* salt = NULL; int salt_length = 0; char* password_prep = NULL; char* client_nounce = NULL; char* combined_nounce = NULL; char* base64_salt = NULL; char* iteration_string = NULL; char* err = NULL; int iteration; char* client_first_message_bare = NULL; char* server_first_message = NULL; char wo_proof[58]; unsigned char* proof = NULL; int proof_length; char* proof_base = NULL; char* base64_server_signature = NULL; char* server_signature_received = NULL; int server_signature_received_length; unsigned char* server_signature_calc = NULL; int server_signature_calc_length; struct message* sslrequest_msg = NULL; struct message* startup_msg = NULL; struct message* sasl_response = NULL; struct message* sasl_continue = NULL; struct message* sasl_continue_response = NULL; struct message* sasl_final = NULL; struct message* msg = NULL; pgagroal_memory_size(DEFAULT_BUFFER_SIZE); if (pgagroal_get_home_directory() == NULL) { goto error; } memset(&key_file, 0, sizeof(key_file)); snprintf(&key_file[0], sizeof(key_file), "%s/.pgagroal/pgagroal.key", pgagroal_get_home_directory()); memset(&cert_file, 0, sizeof(cert_file)); snprintf(&cert_file[0], sizeof(cert_file), "%s/.pgagroal/pgagroal.crt", pgagroal_get_home_directory()); memset(&root_file, 0, sizeof(root_file)); snprintf(&root_file[0], sizeof(root_file), "%s/.pgagroal/root.crt", pgagroal_get_home_directory()); if (stat(&key_file[0], &st) == 0) { if (S_ISREG(st.st_mode) && st.st_mode & (S_IRUSR | S_IWUSR) && !(st.st_mode & S_IRWXG) && !(st.st_mode & S_IRWXO)) { if (stat(&cert_file[0], &st) == 0) { if (S_ISREG(st.st_mode)) { SSL_CTX* ctx = NULL; status = pgagroal_create_ssl_message(&sslrequest_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(NULL, server_fd, sslrequest_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(NULL, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->kind == 'S') { if (create_ssl_ctx(true, &ctx)) { goto error; } if (stat(&root_file[0], &st) == -1) { memset(&root_file, 0, sizeof(root_file)); } if (create_ssl_client(ctx, &key_file[0], &cert_file[0], &root_file[0], server_fd, &ssl)) { goto error; } *s_ssl = ssl; do { status = SSL_connect(ssl); if (status != 1) { int err = SSL_get_error(ssl, status); switch (err) { case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: #ifndef HAVE_OPENBSD #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) case SSL_ERROR_WANT_ASYNC: case SSL_ERROR_WANT_ASYNC_JOB: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) case SSL_ERROR_WANT_CLIENT_HELLO_CB: #endif #endif #endif break; case SSL_ERROR_SYSCALL: pgagroal_log_error("SSL_ERROR_SYSCALL: %s (%d)", strerror(errno), server_fd); errno = 0; goto error; break; case SSL_ERROR_SSL: pgagroal_log_error("SSL_ERROR_SSL: %s (%d)", strerror(errno), server_fd); pgagroal_log_error("%s", ERR_error_string(err, NULL)); pgagroal_log_error("%s", ERR_lib_error_string(err)); pgagroal_log_error("%s", ERR_reason_error_string(err)); errno = 0; goto error; break; } ERR_clear_error(); } } while (status != 1); } } } } } status = pgagroal_create_startup_message(username, "admin", &startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(ssl, server_fd, startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->kind != 'R') { goto error; } status = sasl_prep(password, &password_prep); if (status) { goto error; } generate_nounce(&client_nounce); status = pgagroal_create_auth_scram256_response(client_nounce, &sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(ssl, server_fd, sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } sasl_continue = pgagroal_copy_message(msg); get_scram_attribute('r', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &combined_nounce); get_scram_attribute('s', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &base64_salt); get_scram_attribute('i', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &iteration_string); get_scram_attribute('e', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &err); if (err != NULL) { goto error; } pgagroal_base64_decode(base64_salt, strlen(base64_salt), &salt, &salt_length); iteration = atoi(iteration_string); memset(&wo_proof[0], 0, sizeof(wo_proof)); snprintf(&wo_proof[0], sizeof(wo_proof), "c=biws,r=%s", combined_nounce); /* n=,r=... */ client_first_message_bare = sasl_response->data + 26; /* r=...,s=...,i=4096 */ server_first_message = sasl_continue->data + 9; if (client_proof(password_prep, salt, salt_length, iteration, client_first_message_bare, sasl_response->length - 26, server_first_message, sasl_continue->length - 9, &wo_proof[0], strlen(wo_proof), &proof, &proof_length)) { goto error; } pgagroal_base64_encode((char*)proof, proof_length, &proof_base); status = pgagroal_create_auth_scram256_continue_response(&wo_proof[0], (char*)proof_base, &sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(ssl, server_fd, sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (pgagroal_extract_message('R', msg, &sasl_final)) { goto error; } /* Get 'v' attribute */ base64_server_signature = sasl_final->data + 11; pgagroal_base64_decode(base64_server_signature, sasl_final->length - 11, &server_signature_received, &server_signature_received_length); if (server_signature(password_prep, salt, salt_length, iteration, NULL, 0, client_first_message_bare, sasl_response->length - 26, server_first_message, sasl_continue->length - 9, &wo_proof[0], strlen(wo_proof), &server_signature_calc, &server_signature_calc_length)) { goto error; } if (server_signature_calc_length != server_signature_received_length || memcmp(server_signature_received, server_signature_calc, server_signature_calc_length) != 0) { goto bad_password; } if (msg->length == 55) { status = pgagroal_read_block_message(ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } } free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_message(msg); pgagroal_free_copy_message(sslrequest_msg); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); pgagroal_memory_destroy(); return AUTH_SUCCESS; bad_password: free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_message(msg); pgagroal_free_copy_message(sslrequest_msg); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); pgagroal_memory_destroy(); return AUTH_BAD_PASSWORD; error: free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_message(msg); pgagroal_free_copy_message(sslrequest_msg); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); pgagroal_memory_destroy(); return AUTH_ERROR; } static int get_auth_type(struct message* msg, int* auth_type) { int32_t length; int32_t type = -1; int offset; *auth_type = -1; if (msg->kind != 'R') { return 1; } length = pgagroal_read_int32(msg->data + 1); type = pgagroal_read_int32(msg->data + 5); offset = 9; if (type == 0 && msg->length > 8) { if ('E' == pgagroal_read_byte(msg->data + 9)) { return 0; } } switch (type) { case 0: pgagroal_log_trace("Backend: R - Success"); break; case 2: pgagroal_log_trace("Backend: R - KerberosV5"); break; case 3: pgagroal_log_trace("Backend: R - CleartextPassword"); break; case 5: pgagroal_log_trace("Backend: R - MD5Password"); pgagroal_log_trace(" Salt %02hhx%02hhx%02hhx%02hhx", (signed char)(pgagroal_read_byte(msg->data + 9) & 0xFF), (signed char)(pgagroal_read_byte(msg->data + 10) & 0xFF), (signed char)(pgagroal_read_byte(msg->data + 11) & 0xFF), (signed char)(pgagroal_read_byte(msg->data + 12) & 0xFF)); break; case 6: pgagroal_log_trace("Backend: R - SCMCredential"); break; case 7: pgagroal_log_trace("Backend: R - GSS"); break; case 8: pgagroal_log_trace("Backend: R - GSSContinue"); break; case 9: pgagroal_log_trace("Backend: R - SSPI"); break; case 10: pgagroal_log_trace("Backend: R - SASL"); while (offset < length - 8) { char* mechanism = pgagroal_read_string(msg->data + offset); pgagroal_log_trace(" %s", mechanism); offset += strlen(mechanism) + 1; } break; case 11: pgagroal_log_trace("Backend: R - SASLContinue"); break; case 12: pgagroal_log_trace("Backend: R - SASLFinal"); offset += length - 8; if (offset < msg->length) { signed char peek = pgagroal_read_byte(msg->data + offset); switch (peek) { case 'R': type = pgagroal_read_int32(msg->data + offset + 5); break; default: break; } } break; default: break; } *auth_type = type; return 0; } static int compare_auth_response(struct message* orig, struct message* response, int auth_type) { switch (auth_type) { case 3: case 5: return strcmp(pgagroal_read_string(orig->data + 5), pgagroal_read_string(response->data + 5)); break; case 10: return memcmp(orig->data, response->data, orig->length); break; default: break; } return 1; } static int use_pooled_connection(SSL* c_ssl, int client_fd, int slot, char* username, char* database, int hba_method, SSL** server_ssl) { int status = MESSAGE_STATUS_ERROR; struct configuration* config = NULL; struct message* auth_msg = NULL; struct message* msg = NULL; char* password = NULL; config = (struct configuration*)shmem; password = get_frontend_password(username); if (password == NULL) { password = get_password(username); } if (hba_method == SECURITY_ALL) { hba_method = config->connections[slot].has_security; } if (config->authquery) { status = auth_query(c_ssl, client_fd, slot, username, database, hba_method); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else if (password == NULL) { /* We can only deal with SECURITY_TRUST, SECURITY_PASSWORD and SECURITY_MD5 */ pgagroal_create_message(&config->connections[slot].security_messages[0], config->connections[slot].security_lengths[0], &auth_msg); status = pgagroal_write_message(c_ssl, client_fd, auth_msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_copy_message(auth_msg); auth_msg = NULL; /* Password or MD5 */ if (config->connections[slot].has_security != SECURITY_TRUST) { status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_create_message(&config->connections[slot].security_messages[1], config->connections[slot].security_lengths[1], &auth_msg); if (compare_auth_response(auth_msg, msg, config->connections[slot].has_security)) { pgagroal_write_bad_password(c_ssl, client_fd, username); pgagroal_write_empty(c_ssl, client_fd); goto error; } pgagroal_free_copy_message(auth_msg); auth_msg = NULL; pgagroal_create_message(&config->connections[slot].security_messages[2], config->connections[slot].security_lengths[2], &auth_msg); status = pgagroal_write_message(c_ssl, client_fd, auth_msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_copy_message(auth_msg); auth_msg = NULL; } } else { /* We have a password */ if (hba_method == SECURITY_TRUST) { /* R/0 */ client_trust(c_ssl, client_fd, username, password, slot); } else if (hba_method == SECURITY_PASSWORD) { /* R/3 */ status = client_password(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else if (hba_method == SECURITY_MD5) { /* R/5 */ status = client_md5(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else if (hba_method == SECURITY_SCRAM256) { /* R/10 */ status = client_scram256(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else { goto error; } if (client_ok(c_ssl, client_fd, slot)) { goto error; } } return AUTH_SUCCESS; bad_password: pgagroal_log_trace("use_pooled_connection: bad password for slot %d", slot); return AUTH_BAD_PASSWORD; error: pgagroal_log_trace("use_pooled_connection: failed for slot %d", slot); return AUTH_ERROR; } static int use_unpooled_connection(struct message* request_msg, SSL* c_ssl, int client_fd, int slot, char* username, int hba_method, SSL** server_ssl) { int status = MESSAGE_STATUS_ERROR; int server_fd; int auth_type = -1; char* password; signed char server_state; struct message* msg = NULL; struct message* auth_msg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; server_fd = config->connections[slot].fd; password = get_frontend_password(username); if (password == NULL) { password = get_password(username); } /* Disallow unknown users */ if (password == NULL && !config->allow_unknown_users) { pgagroal_log_debug("reject: %s", username); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } /* TLS support */ establish_client_tls_connection(config->connections[slot].server, server_fd, server_ssl); /* Send auth request to PostgreSQL */ pgagroal_log_trace("authenticate: client auth request (%d)", client_fd); status = pgagroal_write_message(*server_ssl, server_fd, request_msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); /* Keep response, and send response to client */ pgagroal_log_trace("authenticate: server auth request (%d)", server_fd); status = pgagroal_read_block_message(*server_ssl, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } get_auth_type(msg, &auth_type); pgagroal_log_trace("authenticate: auth type %d", auth_type); /* Supported security models: */ /* trust (0) */ /* password (3) */ /* md5 (5) */ /* scram-sha-256 (10) */ if (auth_type == -1) { pgagroal_write_message(c_ssl, client_fd, msg); pgagroal_write_empty(c_ssl, client_fd); goto error; } else if (auth_type != SECURITY_TRUST && auth_type != SECURITY_PASSWORD && auth_type != SECURITY_MD5 && auth_type != SECURITY_SCRAM256) { pgagroal_log_info("Unsupported security model: %d", auth_type); pgagroal_write_unsupported_security_model(c_ssl, client_fd, username); pgagroal_write_empty(c_ssl, client_fd); goto error; } if (password == NULL) { if (server_passthrough(msg, auth_type, c_ssl, client_fd, slot)) { goto error; } } else { if (hba_method == SECURITY_ALL) { hba_method = auth_type; } auth_msg = pgagroal_copy_message(msg); if (hba_method == SECURITY_TRUST) { /* R/0 */ client_trust(c_ssl, client_fd, username, password, slot); } else if (hba_method == SECURITY_PASSWORD) { /* R/3 */ status = client_password(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else if (hba_method == SECURITY_MD5) { /* R/5 */ status = client_md5(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else if (hba_method == SECURITY_SCRAM256) { /* R/10 */ status = client_scram256(c_ssl, client_fd, username, password, slot); if (status == AUTH_BAD_PASSWORD) { goto bad_password; } else if (status == AUTH_ERROR) { goto error; } } else { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } if (server_authenticate(auth_msg, auth_type, username, get_password(username), slot, *server_ssl)) { if (pgagroal_socket_isvalid(client_fd)) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); } goto error; } if (client_ok(c_ssl, client_fd, slot)) { goto error; } } server_state = atomic_load(&config->servers[config->connections[slot].server].state); if (server_state == SERVER_NOTINIT || server_state == SERVER_NOTINIT_PRIMARY) { pgagroal_log_debug("Verify server mode: %d", config->connections[slot].server); pgagroal_update_server_state(slot, server_fd, *server_ssl); pgagroal_server_status(); } pgagroal_log_trace("authenticate: has_security %d", config->connections[slot].has_security); pgagroal_free_copy_message(auth_msg); return AUTH_SUCCESS; bad_password: pgagroal_free_copy_message(auth_msg); if (pgagroal_socket_isvalid(client_fd)) { pgagroal_write_bad_password(c_ssl, client_fd, username); if (hba_method == SECURITY_SCRAM256) { pgagroal_write_empty(c_ssl, client_fd); } } return AUTH_BAD_PASSWORD; error: pgagroal_free_copy_message(auth_msg); pgagroal_log_trace("use_unpooled_connection: failed for slot %d", slot); return AUTH_ERROR; } static int client_trust(SSL* c_ssl, int client_fd, char* username, char* password, int slot) { pgagroal_log_debug("client_trust %d %d", client_fd, slot); return AUTH_SUCCESS; } static int client_password(SSL* c_ssl, int client_fd, char* username, char* password, int slot) { int status; time_t start_time; bool non_blocking; struct configuration* config; struct message* msg = NULL; pgagroal_log_debug("client_password %d %d", client_fd, slot); config = (struct configuration*)shmem; status = pgagroal_write_auth_password(c_ssl, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } start_time = time(NULL); non_blocking = pgagroal_socket_is_nonblocking(client_fd); pgagroal_socket_nonblocking(client_fd, true); /* psql may just close the connection without word, so loop */ retry: status = pgagroal_read_timeout_message(c_ssl, client_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { if (difftime(time(NULL), start_time) < config->authentication_timeout) { if (pgagroal_socket_isvalid(client_fd)) /* Sleep for 100ms */ { SLEEP_AND_GOTO(100000000L, retry) } } } if (status != MESSAGE_STATUS_OK) { goto error; } if (!non_blocking) { pgagroal_socket_nonblocking(client_fd, false); } if (strcmp(pgagroal_read_string(msg->data + 5), password)) { pgagroal_write_bad_password(c_ssl, client_fd, username); goto bad_password; } pgagroal_free_message(msg); return AUTH_SUCCESS; bad_password: pgagroal_free_message(msg); return AUTH_BAD_PASSWORD; error: pgagroal_free_message(msg); return AUTH_ERROR; } static int client_md5(SSL* c_ssl, int client_fd, char* username, char* password, int slot) { int status; char salt[4]; time_t start_time; bool non_blocking; size_t size; char* pwdusr = NULL; char* shadow = NULL; char* md5_req = NULL; char* md5 = NULL; struct configuration* config; struct message* msg = NULL; pgagroal_log_debug("client_md5 %d %d", client_fd, slot); config = (struct configuration*)shmem; salt[0] = (char)(random() & 0xFF); salt[1] = (char)(random() & 0xFF); salt[2] = (char)(random() & 0xFF); salt[3] = (char)(random() & 0xFF); status = pgagroal_write_auth_md5(c_ssl, client_fd, salt); if (status != MESSAGE_STATUS_OK) { goto error; } start_time = time(NULL); non_blocking = pgagroal_socket_is_nonblocking(client_fd); pgagroal_socket_nonblocking(client_fd, true); /* psql may just close the connection without word, so loop */ retry: status = pgagroal_read_timeout_message(c_ssl, client_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { if (difftime(time(NULL), start_time) < config->authentication_timeout) { if (pgagroal_socket_isvalid(client_fd)) /* Sleep for 100ms */ { SLEEP_AND_GOTO(100000000L, retry) } } } if (status != MESSAGE_STATUS_OK) { goto error; } if (!non_blocking) { pgagroal_socket_nonblocking(client_fd, false); } size = strlen(username) + strlen(password) + 1; pwdusr = calloc(1, size); snprintf(pwdusr, size, "%s%s", password, username); if (pgagroal_md5(pwdusr, strlen(pwdusr), &shadow)) { goto error; } md5_req = calloc(1, 36); memcpy(md5_req, shadow, 32); memcpy(md5_req + 32, &salt[0], 4); if (pgagroal_md5(md5_req, 36, &md5)) { goto error; } if (strcmp(pgagroal_read_string(msg->data + 8), md5)) { pgagroal_write_bad_password(c_ssl, client_fd, username); goto bad_password; } pgagroal_free_message(msg); free(pwdusr); free(shadow); free(md5_req); free(md5); return AUTH_SUCCESS; bad_password: pgagroal_free_message(msg); free(pwdusr); free(shadow); free(md5_req); free(md5); return AUTH_BAD_PASSWORD; error: pgagroal_free_message(msg); free(pwdusr); free(shadow); free(md5_req); free(md5); return AUTH_ERROR; } static int client_scram256(SSL* c_ssl, int client_fd, char* username, char* password, int slot) { int status; time_t start_time; bool non_blocking; char* password_prep = NULL; char* client_first_message_bare = NULL; char* server_first_message = NULL; char* client_final_message_without_proof = NULL; char* client_nounce = NULL; char* server_nounce = NULL; char* salt = NULL; int salt_length = 0; char* base64_salt = NULL; char* base64_client_proof = NULL; char* client_proof_received = NULL; int client_proof_received_length = 0; unsigned char* client_proof_calc = NULL; int client_proof_calc_length = 0; unsigned char* server_signature_calc = NULL; int server_signature_calc_length = 0; char* base64_server_signature_calc = NULL; struct configuration* config; struct message* msg = NULL; struct message* sasl_continue = NULL; struct message* sasl_final = NULL; pgagroal_log_debug("client_scram256 %d %d", client_fd, slot); config = (struct configuration*)shmem; status = pgagroal_write_auth_scram256(c_ssl, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } start_time = time(NULL); non_blocking = pgagroal_socket_is_nonblocking(client_fd); pgagroal_socket_nonblocking(client_fd, true); /* psql may just close the connection without word, so loop */ retry: status = pgagroal_read_timeout_message(c_ssl, client_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { if (difftime(time(NULL), start_time) < config->authentication_timeout) { if (pgagroal_socket_isvalid(client_fd)) /* Sleep for 100ms */ { SLEEP_AND_GOTO(100000000L, retry) } } } if (status != MESSAGE_STATUS_OK) { goto error; } if (!non_blocking) { pgagroal_socket_nonblocking(client_fd, false); } client_first_message_bare = calloc(1, msg->length - 25); memcpy(client_first_message_bare, msg->data + 26, msg->length - 26); get_scram_attribute('r', (char*)msg->data + 26, msg->length - 26, &client_nounce); generate_nounce(&server_nounce); generate_salt(&salt, &salt_length); pgagroal_base64_encode(salt, salt_length, &base64_salt); server_first_message = calloc(1, 89); snprintf(server_first_message, 89, "r=%s%s,s=%s,i=4096", client_nounce, server_nounce, base64_salt); status = pgagroal_create_auth_scram256_continue(client_nounce, server_nounce, base64_salt, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } sasl_continue = pgagroal_copy_message(msg); pgagroal_free_copy_message(msg); msg = NULL; status = pgagroal_write_message(c_ssl, client_fd, sasl_continue); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } get_scram_attribute('p', (char*)msg->data + 5, msg->length - 5, &base64_client_proof); pgagroal_base64_decode(base64_client_proof, strlen(base64_client_proof), &client_proof_received, &client_proof_received_length); client_final_message_without_proof = calloc(1, 58); memcpy(client_final_message_without_proof, msg->data + 5, 57); sasl_prep(password, &password_prep); if (client_proof(password_prep, salt, salt_length, 4096, client_first_message_bare, strlen(client_first_message_bare), server_first_message, strlen(server_first_message), client_final_message_without_proof, strlen(client_final_message_without_proof), &client_proof_calc, &client_proof_calc_length)) { goto error; } if (client_proof_received_length != client_proof_calc_length || memcmp(client_proof_received, client_proof_calc, client_proof_calc_length) != 0) { goto bad_password; } if (server_signature(password_prep, salt, salt_length, 4096, NULL, 0, client_first_message_bare, strlen(client_first_message_bare), server_first_message, strlen(server_first_message), client_final_message_without_proof, strlen(client_final_message_without_proof), &server_signature_calc, &server_signature_calc_length)) { goto error; } pgagroal_base64_encode((char*)server_signature_calc, server_signature_calc_length, &base64_server_signature_calc); status = pgagroal_create_auth_scram256_final(base64_server_signature_calc, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } sasl_final = pgagroal_copy_message(msg); pgagroal_free_copy_message(msg); msg = NULL; status = pgagroal_write_message(c_ssl, client_fd, sasl_final); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_log_debug("client_scram256 done"); free(password_prep); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(salt); free(base64_salt); free(base64_client_proof); free(client_proof_received); free(client_proof_calc); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_SUCCESS; bad_password: free(password_prep); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(salt); free(base64_salt); free(base64_client_proof); free(client_proof_received); free(client_proof_calc); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_BAD_PASSWORD; error: free(password_prep); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(salt); free(base64_salt); free(base64_client_proof); free(client_proof_received); free(client_proof_calc); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_ERROR; } static int client_ok(SSL* c_ssl, int client_fd, int slot) { int status; size_t size; char* data; struct message msg; struct configuration* config; data = NULL; memset(&msg, 0, sizeof(msg)); config = (struct configuration*)shmem; if (config->connections[slot].has_security == SECURITY_TRUST) { size = config->connections[slot].security_lengths[0]; data = malloc(size); if (data == NULL) { goto error; } memcpy(data, config->connections[slot].security_messages[0], size); } else if (config->connections[slot].has_security == SECURITY_PASSWORD || config->connections[slot].has_security == SECURITY_MD5) { size = config->connections[slot].security_lengths[2]; data = malloc(size); if (data == NULL) { goto error; } memcpy(data, config->connections[slot].security_messages[2], size); } else if (config->connections[slot].has_security == SECURITY_SCRAM256) { size = config->connections[slot].security_lengths[4] - 55; data = malloc(size); if (data == NULL) { goto error; } memcpy(data, config->connections[slot].security_messages[4] + 55, size); } else { goto error; } msg.kind = 'R'; msg.length = size; msg.data = data; status = pgagroal_write_message(c_ssl, client_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } free(data); return 0; error: free(data); return 1; } static int server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd, int slot) { int status = MESSAGE_STATUS_ERROR; int server_fd; int auth_index = 0; int auth_response = -1; struct message* smsg = NULL; struct message* kmsg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; server_fd = config->connections[slot].fd; pgagroal_log_trace("server_passthrough %d %d", auth_type, slot); for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { memset(&config->connections[slot].security_messages[i], 0, SECURITY_BUFFER_SIZE); } if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); auth_index++; status = pgagroal_write_message(c_ssl, client_fd, msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); /* Non-trust clients */ if (auth_type != SECURITY_TRUST) { /* Receive client response, keep it, and send it to PostgreSQL */ status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); auth_index++; status = pgagroal_write_message(NULL, server_fd, msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_block_message(NULL, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (auth_type == SECURITY_SCRAM256) { if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); auth_index++; status = pgagroal_write_message(c_ssl, client_fd, msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); auth_index++; status = pgagroal_write_message(NULL, server_fd, msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); status = pgagroal_read_block_message(NULL, server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } } /* Ok: Keep the response, send it to the client, and exit authenticate() */ get_auth_type(msg, &auth_response); pgagroal_log_trace("authenticate: auth response %d", auth_response); if (auth_response == 0) { if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); config->connections[slot].has_security = auth_type; } status = pgagroal_write_message(c_ssl, client_fd, msg); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_free_message(msg); if (auth_response != 0) { goto error; } } else { /* Trust */ config->connections[slot].has_security = SECURITY_TRUST; } if (config->connections[slot].has_security == SECURITY_TRUST) { pgagroal_create_message(&config->connections[slot].security_messages[0], config->connections[slot].security_lengths[0], &smsg); } else if (config->connections[slot].has_security == SECURITY_PASSWORD || config->connections[slot].has_security == SECURITY_MD5) { pgagroal_create_message(&config->connections[slot].security_messages[2], config->connections[slot].security_lengths[2], &smsg); } else if (config->connections[slot].has_security == SECURITY_SCRAM256) { pgagroal_create_message(&config->connections[slot].security_messages[4], config->connections[slot].security_lengths[4], &smsg); } if (smsg != NULL) { pgagroal_extract_message('K', smsg, &kmsg); if (kmsg != NULL) { config->connections[slot].backend_pid = pgagroal_read_int32(kmsg->data + 5); config->connections[slot].backend_secret = pgagroal_read_int32(kmsg->data + 9); } } pgagroal_free_copy_message(smsg); pgagroal_free_copy_message(kmsg); return 0; error: pgagroal_free_copy_message(smsg); pgagroal_free_copy_message(kmsg); return 1; } static int server_authenticate(struct message* msg, int auth_type, char* username, char* password, int slot, SSL* server_ssl) { int ret = AUTH_ERROR; struct message* smsg = NULL; struct message* kmsg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) { memset(&config->connections[slot].security_messages[i], 0, SECURITY_BUFFER_SIZE); } if (msg->length > SECURITY_BUFFER_SIZE) { pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } config->connections[slot].security_lengths[0] = msg->length; memcpy(&config->connections[slot].security_messages[0], msg->data, msg->length); if (auth_type == SECURITY_TRUST) { ret = server_trust(slot, server_ssl); } else if (auth_type == SECURITY_PASSWORD) { ret = server_password(username, password, slot, server_ssl); } else if (auth_type == SECURITY_MD5) { ret = server_md5(username, password, slot, server_ssl); } else if (auth_type == SECURITY_SCRAM256) { ret = server_scram256(username, password, slot, server_ssl); } if (config->connections[slot].has_security == SECURITY_TRUST) { pgagroal_create_message(&config->connections[slot].security_messages[0], config->connections[slot].security_lengths[0], &smsg); } else if (config->connections[slot].has_security == SECURITY_PASSWORD || config->connections[slot].has_security == SECURITY_MD5) { pgagroal_create_message(&config->connections[slot].security_messages[2], config->connections[slot].security_lengths[2], &smsg); } else if (config->connections[slot].has_security == SECURITY_SCRAM256) { pgagroal_create_message(&config->connections[slot].security_messages[4], config->connections[slot].security_lengths[4], &smsg); } if (smsg != NULL) { pgagroal_extract_message('K', smsg, &kmsg); if (kmsg != NULL) { config->connections[slot].backend_pid = pgagroal_read_int32(kmsg->data + 5); config->connections[slot].backend_secret = pgagroal_read_int32(kmsg->data + 9); } } pgagroal_free_copy_message(smsg); pgagroal_free_copy_message(kmsg); return ret; error: pgagroal_log_error("server_authenticate: %d", auth_type); pgagroal_free_copy_message(smsg); pgagroal_free_copy_message(kmsg); return AUTH_ERROR; } static int server_trust(int slot, SSL* server_ssl) { struct configuration* config = NULL; config = (struct configuration*)shmem; pgagroal_log_trace("server_trust"); config->connections[slot].has_security = SECURITY_TRUST; return AUTH_SUCCESS; } static int server_password(char* username, char* password, int slot, SSL* server_ssl) { int status = MESSAGE_STATUS_ERROR; int auth_index = 1; int auth_response = -1; int server_fd; struct message* auth_msg = NULL; struct message* password_msg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; server_fd = config->connections[slot].fd; pgagroal_log_trace("server_password"); status = pgagroal_create_auth_password_response(password, &password_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(server_ssl, server_fd, password_msg); if (status != MESSAGE_STATUS_OK) { goto error; } config->connections[slot].security_lengths[auth_index] = password_msg->length; memcpy(&config->connections[slot].security_messages[auth_index], password_msg->data, password_msg->length); auth_index++; 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; } get_auth_type(auth_msg, &auth_response); pgagroal_log_trace("authenticate: auth response %d", auth_response); if (auth_response == 0) { 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; } config->connections[slot].security_lengths[auth_index] = auth_msg->length; memcpy(&config->connections[slot].security_messages[auth_index], auth_msg->data, auth_msg->length); config->connections[slot].has_security = SECURITY_PASSWORD; } else { goto bad_password; } pgagroal_free_copy_message(password_msg); pgagroal_free_message(auth_msg); return AUTH_SUCCESS; bad_password: pgagroal_log_warn("Wrong password for user: %s", username); pgagroal_free_copy_message(password_msg); pgagroal_free_message(auth_msg); return AUTH_BAD_PASSWORD; error: pgagroal_free_copy_message(password_msg); pgagroal_free_message(auth_msg); return AUTH_ERROR; } static int server_md5(char* username, char* password, int slot, SSL* server_ssl) { int status = MESSAGE_STATUS_ERROR; int auth_index = 1; int auth_response = -1; int server_fd; size_t size; char* pwdusr = NULL; char* shadow = NULL; char* md5_req = NULL; char* md5 = NULL; char md5str[36]; char* salt = NULL; struct message* auth_msg = NULL; struct message* md5_msg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; server_fd = config->connections[slot].fd; pgagroal_log_trace("server_md5"); if (get_salt(config->connections[slot].security_messages[0], &salt)) { goto error; } size = strlen(username) + strlen(password) + 1; pwdusr = calloc(1, size); snprintf(pwdusr, size, "%s%s", password, username); if (pgagroal_md5(pwdusr, strlen(pwdusr), &shadow)) { goto error; } md5_req = calloc(1, 36); memcpy(md5_req, shadow, 32); memcpy(md5_req + 32, salt, 4); if (pgagroal_md5(md5_req, 36, &md5)) { goto error; } memset(&md5str, 0, sizeof(md5str)); snprintf(&md5str[0], 36, "md5%s", md5); status = pgagroal_create_auth_md5_response(md5str, &md5_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(server_ssl, server_fd, md5_msg); if (status != MESSAGE_STATUS_OK) { goto error; } config->connections[slot].security_lengths[auth_index] = md5_msg->length; memcpy(&config->connections[slot].security_messages[auth_index], md5_msg->data, md5_msg->length); auth_index++; 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; } get_auth_type(auth_msg, &auth_response); pgagroal_log_trace("authenticate: auth response %d", auth_response); if (auth_response == 0) { 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; } config->connections[slot].security_lengths[auth_index] = auth_msg->length; memcpy(&config->connections[slot].security_messages[auth_index], auth_msg->data, auth_msg->length); config->connections[slot].has_security = SECURITY_MD5; } else { goto bad_password; } free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_SUCCESS; bad_password: pgagroal_log_warn("Wrong password for user: %s", username); free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_BAD_PASSWORD; error: free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_ERROR; } static int server_scram256(char* username, char* password, int slot, SSL* server_ssl) { int status = MESSAGE_STATUS_ERROR; int auth_index = 1; int server_fd; char* salt = NULL; int salt_length = 0; char* password_prep = NULL; char* client_nounce = NULL; char* combined_nounce = NULL; char* base64_salt = NULL; char* iteration_string = NULL; char* err = NULL; int iteration; char* client_first_message_bare = NULL; char* server_first_message = NULL; char wo_proof[58]; unsigned char* proof = NULL; int proof_length; char* proof_base = NULL; char* base64_server_signature = NULL; char* server_signature_received = NULL; int server_signature_received_length; unsigned char* server_signature_calc = NULL; int server_signature_calc_length; struct message* sasl_response = NULL; struct message* sasl_continue = NULL; struct message* sasl_continue_response = NULL; struct message* sasl_final = NULL; struct message* msg = NULL; struct configuration* config = NULL; config = (struct configuration*)shmem; server_fd = config->connections[slot].fd; pgagroal_log_trace("server_scram256"); status = sasl_prep(password, &password_prep); if (status) { goto error; } generate_nounce(&client_nounce); status = pgagroal_create_auth_scram256_response(client_nounce, &sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } config->connections[slot].security_lengths[auth_index] = sasl_response->length; memcpy(&config->connections[slot].security_messages[auth_index], sasl_response->data, sasl_response->length); auth_index++; status = pgagroal_write_message(server_ssl, server_fd, sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } 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; } sasl_continue = pgagroal_copy_message(msg); config->connections[slot].security_lengths[auth_index] = sasl_continue->length; memcpy(&config->connections[slot].security_messages[auth_index], sasl_continue->data, sasl_continue->length); auth_index++; get_scram_attribute('r', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &combined_nounce); get_scram_attribute('s', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &base64_salt); get_scram_attribute('i', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &iteration_string); get_scram_attribute('e', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &err); if (err != NULL) { pgagroal_log_error("SCRAM-SHA-256: %s", err); goto error; } pgagroal_base64_decode(base64_salt, strlen(base64_salt), &salt, &salt_length); iteration = atoi(iteration_string); memset(&wo_proof[0], 0, sizeof(wo_proof)); snprintf(&wo_proof[0], sizeof(wo_proof), "c=biws,r=%s", combined_nounce); /* n=,r=... */ client_first_message_bare = config->connections[slot].security_messages[1] + 26; /* r=...,s=...,i=4096 */ server_first_message = config->connections[slot].security_messages[2] + 9; if (client_proof(password_prep, salt, salt_length, iteration, client_first_message_bare, config->connections[slot].security_lengths[1] - 26, server_first_message, config->connections[slot].security_lengths[2] - 9, &wo_proof[0], strlen(wo_proof), &proof, &proof_length)) { goto error; } pgagroal_base64_encode((char*)proof, proof_length, &proof_base); status = pgagroal_create_auth_scram256_continue_response(&wo_proof[0], (char*)proof_base, &sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } config->connections[slot].security_lengths[auth_index] = sasl_continue_response->length; memcpy(&config->connections[slot].security_messages[auth_index], sasl_continue_response->data, sasl_continue_response->length); auth_index++; status = pgagroal_write_message(server_ssl, server_fd, sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } 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; } config->connections[slot].security_lengths[auth_index] = msg->length; memcpy(&config->connections[slot].security_messages[auth_index], msg->data, msg->length); auth_index++; if (pgagroal_extract_message('R', msg, &sasl_final)) { goto error; } /* Get 'v' attribute */ base64_server_signature = sasl_final->data + 11; pgagroal_base64_decode(base64_server_signature, sasl_final->length - 11, &server_signature_received, &server_signature_received_length); if (server_signature(password_prep, salt, salt_length, iteration, NULL, 0, client_first_message_bare, config->connections[slot].security_lengths[1] - 26, server_first_message, config->connections[slot].security_lengths[2] - 9, &wo_proof[0], strlen(wo_proof), &server_signature_calc, &server_signature_calc_length)) { goto error; } if (server_signature_calc_length != server_signature_received_length || memcmp(server_signature_received, server_signature_calc, server_signature_calc_length) != 0) { goto bad_password; } config->connections[slot].has_security = SECURITY_SCRAM256; free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_SUCCESS; bad_password: pgagroal_log_warn("Wrong password for user: %s", username); free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_BAD_PASSWORD; error: free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_ERROR; } static bool is_allowed(char* username, char* database, char* address, int* hba_method) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_hbas; i++) { if (is_allowed_address(address, config->hbas[i].address) && is_allowed_database(database, config->hbas[i].database) && is_allowed_username(username, config->hbas[i].username)) { *hba_method = get_hba_method(i); return true; } } return false; } static bool is_allowed_username(char* username, char* entry) { if (!strcasecmp(entry, "all") || !strcmp(username, entry)) { return true; } return false; } static bool is_allowed_database(char* database, char* entry) { if (!strcasecmp(entry, "all") || !strcmp(database, entry)) { return true; } return false; } static bool is_allowed_address(char* address, char* entry) { struct sockaddr_in address_sa4; struct sockaddr_in6 address_sa6; struct sockaddr_in entry_sa4; struct sockaddr_in6 entry_sa6; char addr[INET6_ADDRSTRLEN]; char s_mask[4]; int mask; char* marker; bool ipv4 = true; memset(&addr, 0, sizeof(addr)); memset(&s_mask, 0, sizeof(s_mask)); if (!strcasecmp(entry, "all")) { return true; } marker = strchr(entry, '/'); if (!marker) { pgagroal_log_warn("Invalid HBA entry: %s", entry); return false; } memcpy(&addr, entry, marker - entry); marker += sizeof(char); memcpy(&s_mask, marker, strlen(marker)); mask = atoi(s_mask); if (strchr(addr, ':') == NULL) { inet_pton(AF_INET, addr, &(entry_sa4.sin_addr)); if (strchr(address, ':') == NULL) { inet_pton(AF_INET, address, &(address_sa4.sin_addr)); } else { return false; } } else { ipv4 = false; inet_pton(AF_INET6, addr, &(entry_sa6.sin6_addr)); if (strchr(address, ':') != NULL) { inet_pton(AF_INET6, address, &(address_sa6.sin6_addr)); } else { return false; } } if (ipv4) { if (!strcmp(entry, "0.0.0.0/0")) { return true; } if (mask < 0 || mask > 32) { pgagroal_log_warn("Invalid HBA entry: %s", entry); return false; } unsigned char a1 = (ntohl(address_sa4.sin_addr.s_addr) >> 24) & 0xff; unsigned char a2 = (ntohl(address_sa4.sin_addr.s_addr) >> 16) & 0xff; unsigned char a3 = (ntohl(address_sa4.sin_addr.s_addr) >> 8) & 0xff; unsigned char a4 = (ntohl(address_sa4.sin_addr.s_addr)) & 0xff; unsigned char e1 = (ntohl(entry_sa4.sin_addr.s_addr) >> 24) & 0xff; unsigned char e2 = (ntohl(entry_sa4.sin_addr.s_addr) >> 16) & 0xff; unsigned char e3 = (ntohl(entry_sa4.sin_addr.s_addr) >> 8) & 0xff; unsigned char e4 = (ntohl(entry_sa4.sin_addr.s_addr)) & 0xff; if (mask <= 8) { return a1 == e1; } else if (mask <= 16) { return a1 == e1 && a2 == e2; } else if (mask <= 24) { return a1 == e1 && a2 == e2 && a3 == e3; } else { return a1 == e1 && a2 == e2 && a3 == e3 && a4 == e4; } } else { if (!strcmp(entry, "::0/0")) { return true; } if (mask < 0 || mask > 128) { pgagroal_log_warn("Invalid HBA entry: %s", entry); return false; } struct sockaddr_in6 netmask; bool result = false; memset(&netmask, 0, sizeof(struct sockaddr_in6)); for (long i = mask, j = 0; i > 0; i -= 8, ++j) { netmask.sin6_addr.s6_addr[j] = i >= 8 ? 0xff : (unsigned long int)((0xffU << (8 - i)) & 0xffU); } for (unsigned i = 0; i < 16; i++) { result |= (0 != (address_sa6.sin6_addr.s6_addr[i] & !netmask.sin6_addr.s6_addr[i])); } return result; } return false; } static bool is_disabled(char* database) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_DISABLED; i++) { if (!strcmp(config->disabled[i], "*") || !strcmp(config->disabled[i], database)) { return true; } } return false; } static int get_hba_method(int index) { struct configuration* config; config = (struct configuration*)shmem; if (!strcasecmp(config->hbas[index].method, "reject")) { return SECURITY_REJECT; } if (!strcasecmp(config->hbas[index].method, "trust")) { return SECURITY_TRUST; } if (!strcasecmp(config->hbas[index].method, "password")) { return SECURITY_PASSWORD; } if (!strcasecmp(config->hbas[index].method, "md5")) { return SECURITY_MD5; } if (!strcasecmp(config->hbas[index].method, "scram-sha-256")) { return SECURITY_SCRAM256; } if (!strcasecmp(config->hbas[index].method, "all")) { return SECURITY_ALL; } return SECURITY_REJECT; } static char* get_password(char* username) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_users; i++) { if (!strcmp(&config->users[i].username[0], username)) { return &config->users[i].password[0]; } } return NULL; } static char* get_frontend_password(char* username) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_frontend_users; i++) { if (!strcmp(&config->frontend_users[i].username[0], username)) { return &config->frontend_users[i].password[0]; } } return NULL; } static char* get_admin_password(char* username) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_admins; i++) { if (!strcmp(&config->admins[i].username[0], username)) { return &config->admins[i].password[0]; } } return NULL; } static int get_salt(void* data, char** salt) { char* result; result = calloc(1, 4); memcpy(result, data + 9, 4); *salt = result; return 0; } int pgagroal_get_master_key(char** masterkey) { FILE* master_key_file = NULL; char buf[MISC_LENGTH]; char line[MISC_LENGTH]; char* mk = NULL; int mk_length = 0; struct stat st = {0}; if (pgagroal_get_home_directory() == NULL) { goto error; } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/.pgagroal", pgagroal_get_home_directory()); if (stat(&buf[0], &st) == -1) { goto error; } else { if (S_ISDIR(st.st_mode) && st.st_mode & S_IRWXU && !(st.st_mode & S_IRWXG) && !(st.st_mode & S_IRWXO)) { /* Ok */ } else { goto error; } } memset(&buf, 0, sizeof(buf)); snprintf(&buf[0], sizeof(buf), "%s/.pgagroal/master.key", pgagroal_get_home_directory()); if (stat(&buf[0], &st) == -1) { goto error; } else { if (S_ISREG(st.st_mode) && st.st_mode & (S_IRUSR | S_IWUSR) && !(st.st_mode & S_IRWXG) && !(st.st_mode & S_IRWXO)) { /* Ok */ } else { goto error; } } master_key_file = fopen(&buf[0], "r"); if (master_key_file == NULL) { goto error; } memset(&line, 0, sizeof(line)); if (fgets(line, sizeof(line), master_key_file) == NULL) { goto error; } pgagroal_base64_decode(&line[0], strlen(&line[0]), &mk, &mk_length); *masterkey = mk; fclose(master_key_file); return 0; error: free(mk); if (master_key_file) { fclose(master_key_file); } return 1; } int pgagroal_encrypt(char* plaintext, char* password, char** ciphertext, int* ciphertext_length) { unsigned char key[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_MAX_IV_LENGTH]; memset(&key, 0, sizeof(key)); memset(&iv, 0, sizeof(iv)); if (derive_key_iv(password, key, iv) != 0) { return 1; } return aes_encrypt(plaintext, key, iv, ciphertext, ciphertext_length); } int pgagroal_decrypt(char* ciphertext, int ciphertext_length, char* password, char** plaintext) { unsigned char key[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_MAX_IV_LENGTH]; memset(&key, 0, sizeof(key)); memset(&iv, 0, sizeof(iv)); if (derive_key_iv(password, key, iv) != 0) { return 1; } return aes_decrypt(ciphertext, ciphertext_length, key, iv, plaintext); } int pgagroal_md5(char* str, int length, char** md5) { int n; MD5_CTX c; unsigned char digest[16]; char* out; out = calloc(1, 33); MD5_Init(&c); MD5_Update(&c, str, length); MD5_Final(digest, &c); for (n = 0; n < 16; ++n) { snprintf(&(out[n * 2]), 32, "%02x", (unsigned int)digest[n]); } *md5 = out; return 0; } bool pgagroal_user_known(char* user) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_users; i++) { if (!strcmp(user, config->users[i].username)) { return true; } } return false; } int pgagroal_tls_valid(void) { struct configuration* config; struct stat st = {0}; config = (struct configuration*)shmem; if (config->tls) { if (strlen(config->tls_cert_file) == 0) { pgagroal_log_error("No TLS certificate defined"); goto error; } if (strlen(config->tls_key_file) == 0) { pgagroal_log_error("No TLS private key defined"); goto error; } if (stat(config->tls_cert_file, &st) == -1) { pgagroal_log_error("Can't locate TLS certificate file: %s", config->tls_cert_file); goto error; } if (!S_ISREG(st.st_mode)) { pgagroal_log_error("TLS certificate file is not a regular file: %s", config->tls_cert_file); goto error; } if (st.st_uid && st.st_uid != geteuid()) { pgagroal_log_error("TLS certificate file not owned by user or root: %s", config->tls_cert_file); goto error; } memset(&st, 0, sizeof(struct stat)); if (stat(config->tls_key_file, &st) == -1) { pgagroal_log_error("Can't locate TLS private key file: %s", config->tls_key_file); goto error; } if (!S_ISREG(st.st_mode)) { pgagroal_log_error("TLS private key file is not a regular file: %s", config->tls_key_file); goto error; } if (st.st_uid == geteuid()) { if (st.st_mode & (S_IRWXG | S_IRWXO)) { pgagroal_log_error("TLS private key file must have 0600 permissions when owned by a non-root user: %s", config->tls_key_file); goto error; } } else if (st.st_uid == 0) { if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { pgagroal_log_error("TLS private key file must have at least 0640 permissions when owned by root: %s", config->tls_key_file); goto error; } } else { pgagroal_log_error("TLS private key file not owned by user or root: %s", config->tls_key_file); goto error; } if (strlen(config->tls_ca_file) > 0) { memset(&st, 0, sizeof(struct stat)); if (stat(config->tls_ca_file, &st) == -1) { pgagroal_log_error("Can't locate TLS CA file: %s", config->tls_ca_file); goto error; } if (!S_ISREG(st.st_mode)) { pgagroal_log_error("TLS CA file is not a regular file: %s", config->tls_ca_file); goto error; } if (st.st_uid && st.st_uid != geteuid()) { pgagroal_log_error("TLS CA file not owned by user or root: %s", config->tls_ca_file); goto error; } } else { pgagroal_log_debug("No TLS CA file"); } } return 0; error: return 1; } static int derive_key_iv(char* password, unsigned char* key, unsigned char* iv) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) OpenSSL_add_all_algorithms(); #endif if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), NULL, (unsigned char*) password, strlen(password), 1, key, iv)) { return 1; } return 0; } static int aes_encrypt(char* plaintext, unsigned char* key, unsigned char* iv, char** ciphertext, int* ciphertext_length) { EVP_CIPHER_CTX* ctx = NULL; int length; size_t size; unsigned char* ct = NULL; int ct_length; if (!(ctx = EVP_CIPHER_CTX_new())) { goto error; } if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) { goto error; } size = strlen(plaintext) + EVP_CIPHER_block_size(EVP_aes_256_cbc()); ct = calloc(1, size); if (EVP_EncryptUpdate(ctx, ct, &length, (unsigned char*)plaintext, strlen((char*)plaintext)) != 1) { goto error; } ct_length = length; if (EVP_EncryptFinal_ex(ctx, ct + length, &length) != 1) { goto error; } ct_length += length; EVP_CIPHER_CTX_free(ctx); *ciphertext = (char*)ct; *ciphertext_length = ct_length; return 0; error: if (ctx) { EVP_CIPHER_CTX_free(ctx); } free(ct); return 1; } static int aes_decrypt(char* ciphertext, int ciphertext_length, unsigned char* key, unsigned char* iv, char** plaintext) { EVP_CIPHER_CTX* ctx = NULL; int plaintext_length; int length; size_t size; char* pt = NULL; if (!(ctx = EVP_CIPHER_CTX_new())) { goto error; } if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv) != 1) { goto error; } size = ciphertext_length + EVP_CIPHER_block_size(EVP_aes_256_cbc()); pt = calloc(1, size); if (EVP_DecryptUpdate(ctx, (unsigned char*)pt, &length, (unsigned char*)ciphertext, ciphertext_length) != 1) { goto error; } plaintext_length = length; if (EVP_DecryptFinal_ex(ctx, (unsigned char*)pt + length, &length) != 1) { goto error; } plaintext_length += length; EVP_CIPHER_CTX_free(ctx); pt[plaintext_length] = 0; *plaintext = pt; return 0; error: if (ctx) { EVP_CIPHER_CTX_free(ctx); } free(pt); return 1; } static int sasl_prep(char* password, char** password_prep) { char* p = NULL; /* Only support ASCII for now */ for (int i = 0; i < strlen(password); i++) { if ((unsigned char)(*(password + i)) & 0x80) { goto error; } } p = strdup(password); *password_prep = p; return 0; error: *password_prep = NULL; return 1; } static int generate_nounce(char** nounce) { size_t s = 18; unsigned char r[s + 1]; char* base = NULL; int result; memset(&r[0], 0, sizeof(r)); result = RAND_bytes(r, sizeof(r)); if (result != 1) { goto error; } r[s] = '\0'; pgagroal_base64_encode((char*)&r[0], s, &base); *nounce = base; #if OPENSSL_API_COMPAT < 0x10100000L RAND_cleanup(); #endif return 0; error: #if OPENSSL_API_COMPAT < 0x10100000L RAND_cleanup(); #endif return 1; } static int get_scram_attribute(char attribute, char* input, size_t size, char** value) { char* dup = NULL; char* result = NULL; char* ptr = NULL; size_t token_size; char match[2]; match[0] = attribute; match[1] = '='; dup = (char*)calloc(1, size + 1); memcpy(dup, input, size); ptr = strtok(dup, ","); while (ptr != NULL) { if (!strncmp(ptr, &match[0], 2)) { token_size = strlen(ptr) - 1; result = calloc(1, token_size); memcpy(result, ptr + 2, token_size); goto done; } ptr = strtok(NULL, ","); } if (result == NULL) { goto error; } done: *value = result; free(dup); return 0; error: *value = NULL; free(dup); return 1; } static int client_proof(char* password, char* salt, int salt_length, int iterations, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length, unsigned char** result, int* result_length) { size_t size = 32; unsigned char* s_p = NULL; int s_p_length; unsigned char* c_k = NULL; int c_k_length; unsigned char* s_k = NULL; int s_k_length; unsigned char* c_s = NULL; unsigned int length; unsigned char* r = NULL; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX* ctx = HMAC_CTX_new(); #else HMAC_CTX hctx; HMAC_CTX* ctx = &hctx; HMAC_CTX_init(ctx); #endif if (salted_password(password, salt, salt_length, iterations, &s_p, &s_p_length)) { goto error; } if (salted_password_key(s_p, s_p_length, "Client Key", &c_k, &c_k_length)) { goto error; } if (stored_key(c_k, c_k_length, &s_k, &s_k_length)) { goto error; } c_s = calloc(1, size); r = calloc(1, size); /* Client signature: HMAC(StoredKey, AuthMessage) */ if (HMAC_Init_ex(ctx, s_k, s_k_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_first_message_bare, client_first_message_bare_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)server_first_message, server_first_message_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_final_message_wo_proof, client_final_message_wo_proof_length) != 1) { goto error; } if (HMAC_Final(ctx, c_s, &length) != 1) { goto error; } /* ClientProof: ClientKey XOR ClientSignature */ for (int i = 0; i < size; i++) { *(r + i) = *(c_k + i) ^ *(c_s + i); } *result = r; *result_length = size; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif free(s_p); free(c_k); free(s_k); free(c_s); return 0; error: *result = NULL; *result_length = 0; if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif } free(s_p); free(c_k); free(s_k); free(c_s); return 1; } static int verify_client_proof(char* s_key, int s_key_length, char* client_proof, int client_proof_length, char* salt, int salt_length, int iterations, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length) { size_t size = 32; unsigned char* c_k = NULL; int c_k_length; unsigned char* s_k = NULL; int s_k_length; unsigned char* c_s = NULL; unsigned int length; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX* ctx = HMAC_CTX_new(); #else HMAC_CTX hctx; HMAC_CTX* ctx = &hctx; HMAC_CTX_init(ctx); #endif /* if (salted_password(password, salt, salt_length, iterations, &s_p, &s_p_length)) */ /* { */ /* goto error; */ /* } */ /* if (salted_password_key(s_p, s_p_length, "Client Key", &c_k, &c_k_length)) */ /* { */ /* goto error; */ /* } */ c_k = calloc(1, size); c_k_length = size; c_s = calloc(1, size); /* Client signature: HMAC(StoredKey, AuthMessage) */ if (HMAC_Init_ex(ctx, s_key, s_key_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_first_message_bare, client_first_message_bare_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)server_first_message, server_first_message_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_final_message_wo_proof, client_final_message_wo_proof_length) != 1) { goto error; } if (HMAC_Final(ctx, c_s, &length) != 1) { goto error; } /* ClientKey: ClientProof XOR ClientSignature */ for (int i = 0; i < size; i++) { *(c_k + i) = *(client_proof + i) ^ *(c_s + i); } if (stored_key(c_k, c_k_length, &s_k, &s_k_length)) { goto error; } if (memcmp(s_key, s_k, size) != 0) { goto error; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif free(c_k); free(s_k); free(c_s); return 0; error: if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif } free(c_k); free(s_k); free(c_s); return 1; } static int salted_password(char* password, char* salt, int salt_length, int iterations, unsigned char** result, int* result_length) { size_t size = 32; int password_length; unsigned int one; unsigned char Ui[size]; unsigned char Ui_prev[size]; unsigned int Ui_length; unsigned char* r = NULL; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX* ctx = HMAC_CTX_new(); #else HMAC_CTX hctx; HMAC_CTX* ctx = &hctx; HMAC_CTX_init(ctx); #endif if (ctx == NULL) { goto error; } password_length = strlen(password); if (!pgagroal_bigendian()) { one = pgagroal_swap(1); } else { one = 1; } r = calloc(1, size); /* SaltedPassword: Hi(Normalize(password), salt, iterations) */ if (HMAC_Init_ex(ctx, password, password_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)salt, salt_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)&one, sizeof(one)) != 1) { goto error; } if (HMAC_Final(ctx, &Ui_prev[0], &Ui_length) != 1) { goto error; } memcpy(r, &Ui_prev[0], size); for (int i = 2; i <= iterations; i++) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (HMAC_CTX_reset(ctx) != 1) { goto error; } #endif if (HMAC_Init_ex(ctx, password, password_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, &Ui_prev[0], size) != 1) { goto error; } if (HMAC_Final(ctx, &Ui[0], &Ui_length) != 1) { goto error; } for (int j = 0; j < size; j++) { *(r + j) ^= *(Ui + j); } memcpy(&Ui_prev[0], &Ui[0], size); } *result = r; *result_length = size; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif return 0; error: if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif } *result = NULL; *result_length = 0; return 1; } static int salted_password_key(unsigned char* salted_password, int salted_password_length, char* key, unsigned char** result, int* result_length) { size_t size = 32; unsigned char* r = NULL; unsigned int length; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX* ctx = HMAC_CTX_new(); #else HMAC_CTX hctx; HMAC_CTX* ctx = &hctx; HMAC_CTX_init(ctx); #endif if (ctx == NULL) { goto error; } r = calloc(1, size); /* HMAC(SaltedPassword, Key) */ if (HMAC_Init_ex(ctx, salted_password, salted_password_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)key, strlen(key)) != 1) { goto error; } if (HMAC_Final(ctx, r, &length) != 1) { goto error; } *result = r; *result_length = size; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif return 0; error: if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif } *result = NULL; *result_length = 0; return 1; } static int stored_key(unsigned char* client_key, int client_key_length, unsigned char** result, int* result_length) { size_t size = 32; unsigned char* r = NULL; unsigned int length; #if OPENSSL_VERSION_NUMBER >= 0x10100000L EVP_MD_CTX* ctx = EVP_MD_CTX_new(); #else EVP_MD_CTX* ctx = EVP_MD_CTX_create(); EVP_MD_CTX_init(ctx); #endif if (ctx == NULL) { goto error; } r = calloc(1, size); /* StoredKey: H(ClientKey) */ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) { goto error; } if (EVP_DigestUpdate(ctx, client_key, client_key_length) != 1) { goto error; } if (EVP_DigestFinal_ex(ctx, r, &length) != 1) { goto error; } *result = r; *result_length = size; #if OPENSSL_VERSION_NUMBER >= 0x10100000L EVP_MD_CTX_free(ctx); #else EVP_MD_CTX_destroy(ctx); #endif return 0; error: if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L EVP_MD_CTX_free(ctx); #else EVP_MD_CTX_destroy(ctx); #endif } *result = NULL; *result_length = 0; return 1; } static int generate_salt(char** salt, int* size) { size_t s = 16; unsigned char* r = NULL; int result; r = calloc(1, s); result = RAND_bytes(r, s); if (result != 1) { goto error; } *salt = (char*)r; *size = s; #if OPENSSL_API_COMPAT < 0x10100000L RAND_cleanup(); #endif return 0; error: #if OPENSSL_API_COMPAT < 0x10100000L RAND_cleanup(); #endif free(r); *salt = NULL; *size = 0; return 1; } static int server_signature(char* password, char* salt, int salt_length, int iterations, char* s_key, int s_key_length, char* client_first_message_bare, size_t client_first_message_bare_length, char* server_first_message, size_t server_first_message_length, char* client_final_message_wo_proof, size_t client_final_message_wo_proof_length, unsigned char** result, int* result_length) { size_t size = 32; unsigned char* r = NULL; unsigned char* s_p = NULL; int s_p_length; unsigned char* s_k = NULL; int s_k_length; unsigned int length; bool do_free = true; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX* ctx = HMAC_CTX_new(); #else HMAC_CTX hctx; HMAC_CTX* ctx = &hctx; HMAC_CTX_init(ctx); #endif if (ctx == NULL) { goto error; } r = calloc(1, size); if (password != NULL) { if (salted_password(password, salt, salt_length, iterations, &s_p, &s_p_length)) { goto error; } if (salted_password_key(s_p, s_p_length, "Server Key", &s_k, &s_k_length)) { goto error; } } else { do_free = false; s_k = (unsigned char*)s_key; s_k_length = s_key_length; } /* Server signature: HMAC(ServerKey, AuthMessage) */ if (HMAC_Init_ex(ctx, s_k, s_k_length, EVP_sha256(), NULL) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_first_message_bare, client_first_message_bare_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)server_first_message, server_first_message_length) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)",", 1) != 1) { goto error; } if (HMAC_Update(ctx, (unsigned char*)client_final_message_wo_proof, client_final_message_wo_proof_length) != 1) { goto error; } if (HMAC_Final(ctx, r, &length) != 1) { goto error; } *result = r; *result_length = length; #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif free(s_p); if (do_free) { free(s_k); } return 0; error: *result = NULL; *result_length = 0; if (ctx != NULL) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L HMAC_CTX_free(ctx); #else HMAC_CTX_cleanup(ctx); #endif } free(s_p); if (do_free) { free(s_k); } return 1; } static bool is_tls_user(char* username, char* database) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_hbas; i++) { if ((!strcmp(database, config->hbas[i].database) || !strcmp("all", config->hbas[i].database)) && (!strcmp(username, config->hbas[i].username) || !strcmp("all", config->hbas[i].username))) { if (!strcmp("hostssl", config->hbas[i].type)) { return true; } } } return false; } static int create_ssl_ctx(bool client, SSL_CTX** ctx) { SSL_CTX* c = NULL; #if (OPENSSL_VERSION_NUMBER < 0x10100000L) OpenSSL_add_all_algorithms(); #endif #if (OPENSSL_VERSION_NUMBER < 0x10100000L) if (client) { c = SSL_CTX_new(TLSv1_2_client_method()); } else { c = SSL_CTX_new(TLSv1_2_server_method()); } #else if (client) { c = SSL_CTX_new(TLS_client_method()); } else { c = SSL_CTX_new(TLS_server_method()); } #endif if (c == NULL) { goto error; } #if (OPENSSL_VERSION_NUMBER < 0x10100000L) SSL_CTX_set_options(c, SSL_OP_NO_SSLv3); SSL_CTX_set_options(c, SSL_OP_NO_TLSv1); SSL_CTX_set_options(c, SSL_OP_NO_TLSv1_1); #else if (SSL_CTX_set_min_proto_version(c, TLS1_2_VERSION) == 0) { goto error; } #endif SSL_CTX_set_mode(c, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_options(c, SSL_OP_NO_TICKET); SSL_CTX_set_session_cache_mode(c, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE); *ctx = c; return 0; error: if (c != NULL) { SSL_CTX_free(c); } return 1; } static int create_ssl_client(SSL_CTX* ctx, char* key, char* cert, char* root, int socket, SSL** ssl) { SSL* s = NULL; bool have_cert = false; bool have_rootcert = false; if (root != NULL && strlen(root) > 0) { if (SSL_CTX_load_verify_locations(ctx, root, NULL) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS CA: %s", root); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } have_rootcert = true; } if (cert != NULL && strlen(cert) > 0) { if (SSL_CTX_use_certificate_chain_file(ctx, cert) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS certificate: %s", cert); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } have_cert = true; } s = SSL_new(ctx); if (s == NULL) { goto error; } if (SSL_set_fd(s, socket) == 0) { goto error; } if (have_cert && key != NULL && strlen(key) > 0) { if (SSL_use_PrivateKey_file(s, key, SSL_FILETYPE_PEM) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS private key: %s", key); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } if (SSL_check_private_key(s) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("TLS private key check failed: %s", key); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } } if (have_rootcert) { SSL_set_verify(s, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL); } *ssl = s; return 0; error: if (s != NULL) { SSL_shutdown(s); SSL_free(s); } SSL_CTX_free(ctx); return 1; } static int create_ssl_server(SSL_CTX* ctx, int socket, SSL** ssl) { SSL* s = NULL; STACK_OF(X509_NAME) * root_cert_list = NULL; struct configuration* config; config = (struct configuration*)shmem; if (strlen(config->tls_cert_file) == 0) { pgagroal_log_error("No TLS certificate defined"); goto error; } if (strlen(config->tls_key_file) == 0) { pgagroal_log_error("No TLS private key defined"); goto error; } if (SSL_CTX_use_certificate_chain_file(ctx, config->tls_cert_file) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS certificate: %s", config->tls_cert_file); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } if (SSL_CTX_use_PrivateKey_file(ctx, config->tls_key_file, SSL_FILETYPE_PEM) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS private key: %s", config->tls_key_file); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } if (SSL_CTX_check_private_key(ctx) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("TLS private key check failed: %s", config->tls_key_file); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } if (strlen(config->tls_ca_file) > 0) { if (SSL_CTX_load_verify_locations(ctx, config->tls_ca_file, NULL) != 1) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS CA: %s", config->tls_ca_file); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } root_cert_list = SSL_load_client_CA_file(config->tls_ca_file); if (root_cert_list == NULL) { unsigned long err; err = ERR_get_error(); pgagroal_log_error("Couldn't load TLS CA: %s", config->tls_ca_file); pgagroal_log_error("Reason: %s", ERR_reason_error_string(err)); goto error; } SSL_CTX_set_verify(ctx, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE), NULL); SSL_CTX_set_client_CA_list(ctx, root_cert_list); } s = SSL_new(ctx); if (s == NULL) { goto error; } if (SSL_set_fd(s, socket) == 0) { goto error; } *ssl = s; return 0; error: if (s != NULL) { SSL_shutdown(s); SSL_free(s); } SSL_CTX_free(ctx); return 1; } static int auth_query(SSL* c_ssl, int client_fd, int slot, char* username, char* database, int hba_method) { int su_socket; SSL* su_ssl = NULL; char* shadow = NULL; int ret; struct configuration* config; config = (struct configuration*)shmem; /* Get connection to server using the superuser */ ret = auth_query_get_connection(config->superuser.username, config->superuser.password, database, &su_socket, &su_ssl); if (ret == AUTH_BAD_PASSWORD) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); atomic_store(&config->su_connection, STATE_FREE); goto bad_password; } else if (ret == AUTH_ERROR) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); atomic_store(&config->su_connection, STATE_FREE); goto error; } else if (ret == AUTH_TIMEOUT) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); atomic_store(&config->su_connection, STATE_FREE); goto error; } /* Call pgagroal_get_password */ if (auth_query_get_password(su_socket, su_ssl, username, database, &shadow)) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); atomic_store(&config->su_connection, STATE_FREE); goto error; } /* Close connection */ pgagroal_disconnect(su_socket); atomic_store(&config->su_connection, STATE_FREE); /* Client security */ if (config->connections[slot].has_security == SECURITY_MD5) { ret = auth_query_client_md5(c_ssl, client_fd, username, shadow, slot); if (ret == AUTH_BAD_PASSWORD) { pgagroal_write_bad_password(c_ssl, client_fd, username); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } } else if (config->connections[slot].has_security == SECURITY_SCRAM256) { ret = auth_query_client_scram256(c_ssl, client_fd, username, shadow, slot); if (ret == AUTH_BAD_PASSWORD) { pgagroal_write_bad_password(c_ssl, client_fd, username); pgagroal_write_empty(c_ssl, client_fd); goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } } else { pgagroal_log_error("Authentication query not supported: %d", config->connections[slot].has_security); pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } /* Client ok */ if (client_ok(c_ssl, client_fd, slot)) { pgagroal_write_connection_refused(c_ssl, client_fd); pgagroal_write_empty(c_ssl, client_fd); goto error; } free(shadow); return AUTH_SUCCESS; bad_password: free(shadow); return AUTH_BAD_PASSWORD; error: free(shadow); return AUTH_ERROR; } static int auth_query_get_connection(char* username, char* password, char* database, int* server_fd, SSL** server_ssl) { int auth_type = -1; int server; signed char isfree; time_t start_time; char* error = NULL; struct configuration* config = NULL; struct message* startup_msg = NULL; struct message* startup_response_msg = NULL; struct message* msg = NULL; int ret = -1; int status = -1; config = (struct configuration*)shmem; *server_fd = -1; pgagroal_prometheus_connection_awaiting(-1); /* We need to find the server for the connection */ if (pgagroal_get_primary(&server)) { pgagroal_log_error("pgagroal: No valid server available"); goto error; } pgagroal_log_debug("connect: server %d", server); start_time = time(NULL); retry: isfree = STATE_FREE; if (atomic_compare_exchange_strong(&config->su_connection, &isfree, STATE_IN_USE)) { if (config->servers[server].host[0] == '/') { char pgsql[MISC_LENGTH]; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->servers[server].port); ret = pgagroal_connect_unix_socket(config->servers[server].host, &pgsql[0], server_fd); } else { ret = pgagroal_connect(config->servers[server].host, config->servers[server].port, server_fd); } if (ret) { pgagroal_log_error("pgagroal: No connection to %s:%d", config->servers[server].host, config->servers[server].port); atomic_store(&config->su_connection, STATE_FREE); goto error; } } else { if (config->blocking_timeout > 0) { /* Sleep for 100ms */ SLEEP(100000000L) double diff = difftime(time(NULL), start_time); if (diff >= (double)config->blocking_timeout) { goto timeout; } goto retry; } else { goto timeout; } } pgagroal_log_debug("connect: %s:%d using fd %d", config->servers[server].host, config->servers[server].port, *server_fd); /* TLS support */ establish_client_tls_connection(server, *server_fd, server_ssl); /* Startup message */ status = pgagroal_create_startup_message(username, database, &startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(*server_ssl, *server_fd, startup_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(*server_ssl, *server_fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } startup_response_msg = pgagroal_copy_message(msg); get_auth_type(msg, &auth_type); pgagroal_log_trace("auth_query_get_connection: auth type %d", auth_type); /* Supported security models: */ /* md5 (5) */ /* scram256 (10) */ if (auth_type == SECURITY_MD5) { ret = auth_query_server_md5(startup_response_msg, username, password, *server_fd, *server_ssl); if (ret == AUTH_BAD_PASSWORD) { goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } } else if (auth_type == SECURITY_SCRAM256) { ret = auth_query_server_scram256(username, password, *server_fd, *server_ssl); if (ret == AUTH_BAD_PASSWORD) { goto bad_password; } else if (ret == AUTH_ERROR) { goto error; } } else { if (msg->kind == 'E') { if (pgagroal_extract_error_message(msg, &error)) { goto error; } pgagroal_log_error("%s", error); } goto error; } free(error); pgagroal_prometheus_connection_unawaiting(-1); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(startup_response_msg); pgagroal_free_message(msg); return AUTH_SUCCESS; bad_password: pgagroal_prometheus_connection_unawaiting(-1); pgagroal_log_debug("auth_query_get_connection: BAD_PASSWORD"); if (*server_fd != -1) { pgagroal_disconnect(*server_fd); } *server_fd = -1; free(error); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(startup_response_msg); pgagroal_free_message(msg); return AUTH_BAD_PASSWORD; error: pgagroal_prometheus_connection_unawaiting(-1); pgagroal_log_debug("auth_query_get_connection: ERROR (%d)", auth_type); if (*server_fd != -1) { pgagroal_disconnect(*server_fd); } *server_fd = -1; free(error); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(startup_response_msg); pgagroal_free_message(msg); return AUTH_ERROR; timeout: pgagroal_prometheus_connection_unawaiting(-1); pgagroal_log_debug("auth_query_get_connection: TIMEOUT"); *server_fd = -1; free(error); pgagroal_free_copy_message(startup_msg); pgagroal_free_copy_message(startup_response_msg); pgagroal_free_message(msg); return AUTH_TIMEOUT; } static int auth_query_server_md5(struct message* startup_response_msg, char* username, char* password, int socket, SSL* server_ssl) { int status = MESSAGE_STATUS_ERROR; int auth_response = -1; size_t size; char* pwdusr = NULL; char* shadow = NULL; char* md5_req = NULL; char* md5 = NULL; char md5str[36]; char* salt = NULL; struct message* auth_msg = NULL; struct message* md5_msg = NULL; pgagroal_log_trace("auth_query_server_md5"); if (get_salt(startup_response_msg->data, &salt)) { goto error; } size = strlen(username) + strlen(password) + 1; pwdusr = calloc(1, size); snprintf(pwdusr, size, "%s%s", password, username); if (pgagroal_md5(pwdusr, strlen(pwdusr), &shadow)) { goto error; } md5_req = calloc(1, 36); memcpy(md5_req, shadow, 32); memcpy(md5_req + 32, salt, 4); if (pgagroal_md5(md5_req, 36, &md5)) { goto error; } memset(&md5str, 0, sizeof(md5str)); snprintf(&md5str[0], 36, "md5%s", md5); status = pgagroal_create_auth_md5_response(md5str, &md5_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(server_ssl, socket, md5_msg); if (status != MESSAGE_STATUS_OK) { goto error; } 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; } get_auth_type(auth_msg, &auth_response); pgagroal_log_trace("authenticate: auth response %d", auth_response); if (auth_response == 0) { 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; } } else { goto bad_password; } free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_SUCCESS; bad_password: pgagroal_log_warn("Wrong password for user: %s", username); free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_BAD_PASSWORD; error: free(pwdusr); free(shadow); free(md5_req); free(md5); free(salt); pgagroal_free_copy_message(md5_msg); pgagroal_free_message(auth_msg); return AUTH_ERROR; } static int auth_query_server_scram256(char* username, char* password, int socket, SSL* server_ssl) { int status = MESSAGE_STATUS_ERROR; char* salt = NULL; int salt_length = 0; char* password_prep = NULL; char* client_nounce = NULL; char* combined_nounce = NULL; char* base64_salt = NULL; char* iteration_string = NULL; char* err = NULL; int iteration; char* client_first_message_bare = NULL; char* server_first_message = NULL; char wo_proof[58]; unsigned char* proof = NULL; int proof_length; char* proof_base = NULL; char* base64_server_signature = NULL; char* server_signature_received = NULL; int server_signature_received_length; unsigned char* server_signature_calc = NULL; int server_signature_calc_length; char* error = NULL; struct message* sasl_response = NULL; struct message* sasl_continue = NULL; struct message* sasl_continue_response = NULL; struct message* sasl_final = NULL; struct message* msg = NULL; pgagroal_log_trace("auth_query_server_scram256"); status = sasl_prep(password, &password_prep); if (status) { goto error; } generate_nounce(&client_nounce); status = pgagroal_create_auth_scram256_response(client_nounce, &sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(server_ssl, socket, sasl_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(server_ssl, socket, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } sasl_continue = pgagroal_copy_message(msg); get_scram_attribute('r', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &combined_nounce); get_scram_attribute('s', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &base64_salt); get_scram_attribute('i', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &iteration_string); get_scram_attribute('e', (char*)(sasl_continue->data + 9), sasl_continue->length - 9, &err); if (err != NULL) { pgagroal_log_error("SCRAM-SHA-256: %s", err); goto error; } pgagroal_base64_decode(base64_salt, strlen(base64_salt), &salt, &salt_length); iteration = atoi(iteration_string); memset(&wo_proof[0], 0, sizeof(wo_proof)); snprintf(&wo_proof[0], sizeof(wo_proof), "c=biws,r=%s", combined_nounce); /* n=,r=... */ client_first_message_bare = sasl_response->data + 26; /* r=...,s=...,i=4096 */ server_first_message = sasl_continue->data + 9; if (client_proof(password_prep, salt, salt_length, iteration, client_first_message_bare, sasl_response->length - 26, server_first_message, sasl_continue->length - 9, &wo_proof[0], strlen(wo_proof), &proof, &proof_length)) { goto error; } pgagroal_base64_encode((char*)proof, proof_length, &proof_base); status = pgagroal_create_auth_scram256_continue_response(&wo_proof[0], (char*)proof_base, &sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(server_ssl, socket, sasl_continue_response); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(server_ssl, socket, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->kind == 'E') { pgagroal_extract_error_message(msg, &error); if (error != NULL) { pgagroal_log_error("%s", error); } goto bad_password; } if (pgagroal_extract_message('R', msg, &sasl_final)) { goto error; } /* Get 'v' attribute */ base64_server_signature = sasl_final->data + 11; pgagroal_base64_decode(base64_server_signature, sasl_final->length - 11, &server_signature_received, &server_signature_received_length); if (server_signature(password_prep, salt, salt_length, iteration, NULL, 0, client_first_message_bare, sasl_response->length - 26, server_first_message, sasl_continue_response->length - 9, &wo_proof[0], strlen(wo_proof), &server_signature_calc, &server_signature_calc_length)) { goto error; } if (server_signature_calc_length != server_signature_received_length || memcmp(server_signature_received, server_signature_calc, server_signature_calc_length) != 0) { goto bad_password; } free(error); free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_SUCCESS; bad_password: pgagroal_log_warn("Wrong password for user: %s", username); free(error); free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_BAD_PASSWORD; error: free(error); free(salt); free(err); free(password_prep); free(client_nounce); free(combined_nounce); free(base64_salt); free(iteration_string); free(proof); free(proof_base); free(server_signature_received); free(server_signature_calc); pgagroal_free_copy_message(sasl_response); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_continue_response); pgagroal_free_copy_message(sasl_final); return AUTH_ERROR; } static int auth_query_get_password(int socket, SSL* server_ssl, char* username, char* database, char** password) { int status; size_t size; char* aq = NULL; size_t result_size; char* result = NULL; struct message qmsg; struct message* tmsg = NULL; struct message* dmsg = NULL; *password = NULL; size = 53 + strlen(username); aq = calloc(1, size); memset(&qmsg, 0, sizeof(struct message)); pgagroal_write_byte(aq, 'Q'); pgagroal_write_int32(aq + 1, size - 1); pgagroal_write_string(aq + 5, "SELECT * FROM public.pgagroal_get_password(\'"); pgagroal_write_string(aq + 49, username); pgagroal_write_string(aq + 49 + strlen(username), "\');"); qmsg.kind = 'Q'; qmsg.length = size; qmsg.data = aq; status = pgagroal_write_message(server_ssl, socket, &qmsg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(server_ssl, socket, &tmsg); if (status != MESSAGE_STATUS_OK) { goto error; } if (pgagroal_extract_message('D', tmsg, &dmsg)) { goto error; } result_size = dmsg->length - 11 + 1; result = (char*)calloc(1, result_size); memcpy(result, dmsg->data + 11, dmsg->length - 11); *password = result; free(aq); pgagroal_free_message(tmsg); pgagroal_free_copy_message(dmsg); return 0; error: pgagroal_log_trace("auth_query_get_password: socket (%d) status (%d)", socket, status); if (tmsg->kind == 'E') { char* error = NULL; if (pgagroal_extract_error_message(tmsg, &error)) { goto error; } pgagroal_log_error("%s in %s", error, database); free(error); } free(aq); pgagroal_free_message(tmsg); pgagroal_free_copy_message(dmsg); return 1; } static int auth_query_client_md5(SSL* c_ssl, int client_fd, char* username, char* hash, int slot) { int status; char salt[4]; time_t start_time; bool non_blocking; char* md5_req = NULL; char* md5 = NULL; struct configuration* config; struct message* msg = NULL; config = (struct configuration*)shmem; salt[0] = (char)(random() & 0xFF); salt[1] = (char)(random() & 0xFF); salt[2] = (char)(random() & 0xFF); salt[3] = (char)(random() & 0xFF); status = pgagroal_write_auth_md5(c_ssl, client_fd, salt); if (status != MESSAGE_STATUS_OK) { goto error; } start_time = time(NULL); non_blocking = pgagroal_socket_is_nonblocking(client_fd); pgagroal_socket_nonblocking(client_fd, true); /* psql may just close the connection without word, so loop */ retry: status = pgagroal_read_timeout_message(c_ssl, client_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { if (difftime(time(NULL), start_time) < config->authentication_timeout) { if (pgagroal_socket_isvalid(client_fd)) /* Sleep for 100ms */ { SLEEP_AND_GOTO(100000000L, retry) } } } if (status != MESSAGE_STATUS_OK) { goto error; } if (!non_blocking) { pgagroal_socket_nonblocking(client_fd, false); } md5_req = calloc(1, 36); memcpy(md5_req, hash + 3, 32); memcpy(md5_req + 32, &salt[0], 4); if (pgagroal_md5(md5_req, 36, &md5)) { goto error; } if (strcmp(pgagroal_read_string(msg->data + 8), md5)) { pgagroal_write_bad_password(c_ssl, client_fd, username); goto bad_password; } pgagroal_free_message(msg); free(md5_req); free(md5); return AUTH_SUCCESS; bad_password: pgagroal_free_message(msg); free(md5_req); free(md5); return AUTH_BAD_PASSWORD; error: pgagroal_free_message(msg); free(md5_req); free(md5); return AUTH_ERROR; } static int auth_query_client_scram256(SSL* c_ssl, int client_fd, char* username, char* shadow, int slot) { int status; time_t start_time; bool non_blocking; char* scram256 = NULL; char* s1 = NULL; char* s2 = NULL; char* s_iterations = NULL; char* base64_stored_key = NULL; char* base64_server_key = NULL; int iterations = 4096; char* stored_key = NULL; int stored_key_length = 0; char* server_key = NULL; int server_key_length = 0; char* client_first_message_bare = NULL; char* server_first_message = NULL; char* client_final_message_without_proof = NULL; char* client_nounce = NULL; char* server_nounce = NULL; char* salt = NULL; int salt_length = 0; char* base64_salt = NULL; char* base64_client_proof = NULL; char* client_proof_received = NULL; int client_proof_received_length = 0; unsigned char* server_signature_calc = NULL; int server_signature_calc_length = 0; char* base64_server_signature_calc = NULL; struct configuration* config; struct message* msg = NULL; struct message* sasl_continue = NULL; struct message* sasl_final = NULL; pgagroal_log_debug("auth_query_client_scram256 %d %d", client_fd, slot); config = (struct configuration*)shmem; status = pgagroal_write_auth_scram256(c_ssl, client_fd); if (status != MESSAGE_STATUS_OK) { goto error; } start_time = time(NULL); non_blocking = pgagroal_socket_is_nonblocking(client_fd); pgagroal_socket_nonblocking(client_fd, true); /* psql may just close the connection without word, so loop */ retry: status = pgagroal_read_timeout_message(c_ssl, client_fd, 1, &msg); if (status != MESSAGE_STATUS_OK) { if (difftime(time(NULL), start_time) < config->authentication_timeout) { if (pgagroal_socket_isvalid(client_fd)) /* Sleep for 100ms */ { SLEEP_AND_GOTO(100000000L, retry) } } } if (status != MESSAGE_STATUS_OK) { goto error; } if (!non_blocking) { pgagroal_socket_nonblocking(client_fd, false); } /* Split shadow */ scram256 = strtok(shadow, "$"); s1 = strtok(NULL, "$"); s2 = strtok(NULL, "$"); s_iterations = strtok(s1, ":"); base64_salt = strtok(NULL, ":"); base64_stored_key = strtok(s2, ":"); base64_server_key = strtok(NULL, ":"); if (strcmp("SCRAM-SHA-256", scram256) != 0) { goto error; } /* Process shadow information */ iterations = atoi(s_iterations); if (pgagroal_base64_decode(base64_salt, strlen(base64_salt), &salt, &salt_length)) { goto error; } if (pgagroal_base64_decode(base64_stored_key, strlen(base64_stored_key), &stored_key, &stored_key_length)) { goto error; } if (pgagroal_base64_decode(base64_server_key, strlen(base64_server_key), &server_key, &server_key_length)) { goto error; } /* Start the flow */ client_first_message_bare = calloc(1, msg->length - 25); memcpy(client_first_message_bare, msg->data + 26, msg->length - 26); get_scram_attribute('r', (char*)msg->data + 26, msg->length - 26, &client_nounce); generate_nounce(&server_nounce); server_first_message = calloc(1, 89); snprintf(server_first_message, 89, "r=%s%s,s=%s,i=%d", client_nounce, server_nounce, base64_salt, iterations); status = pgagroal_create_auth_scram256_continue(client_nounce, server_nounce, base64_salt, &sasl_continue); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(c_ssl, client_fd, sasl_continue); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_timeout_message(c_ssl, client_fd, config->authentication_timeout, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } get_scram_attribute('p', (char*)msg->data + 5, msg->length - 5, &base64_client_proof); pgagroal_base64_decode(base64_client_proof, strlen(base64_client_proof), &client_proof_received, &client_proof_received_length); client_final_message_without_proof = calloc(1, 58); memcpy(client_final_message_without_proof, msg->data + 5, 57); if (verify_client_proof(stored_key, stored_key_length, client_proof_received, client_proof_received_length, salt, salt_length, iterations, client_first_message_bare, strlen(client_first_message_bare), server_first_message, strlen(server_first_message), client_final_message_without_proof, strlen(client_final_message_without_proof))) { goto bad_password; } if (server_signature(NULL, salt, salt_length, iterations, server_key, server_key_length, client_first_message_bare, strlen(client_first_message_bare), server_first_message, strlen(server_first_message), client_final_message_without_proof, strlen(client_final_message_without_proof), &server_signature_calc, &server_signature_calc_length)) { goto error; } pgagroal_base64_encode((char*)server_signature_calc, server_signature_calc_length, &base64_server_signature_calc); status = pgagroal_create_auth_scram256_final(base64_server_signature_calc, &sasl_final); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(c_ssl, client_fd, sasl_final); if (status != MESSAGE_STATUS_OK) { goto error; } pgagroal_log_debug("auth_query_client_scram256 success (%d)", slot); free(salt); free(stored_key); free(server_key); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(base64_client_proof); free(client_proof_received); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_SUCCESS; bad_password: pgagroal_log_debug("auth_query_client_scram256 bad_password (%d)", slot); free(salt); free(stored_key); free(server_key); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(base64_client_proof); free(client_proof_received); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_BAD_PASSWORD; error: pgagroal_log_debug("auth_query_client_scram256 error (%d)", slot); free(salt); free(stored_key); free(server_key); free(client_first_message_bare); free(server_first_message); free(client_final_message_without_proof); free(client_nounce); free(server_nounce); free(base64_client_proof); free(client_proof_received); free(server_signature_calc); free(base64_server_signature_calc); pgagroal_free_copy_message(sasl_continue); pgagroal_free_copy_message(sasl_final); return AUTH_ERROR; } static int establish_client_tls_connection(int server, int fd, SSL** ssl) { struct configuration* config = NULL; struct message* ssl_msg = NULL; struct message* msg = NULL; int status = -1; config = (struct configuration*)shmem; if (config->servers[server].tls) { status = pgagroal_create_ssl_message(&ssl_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_write_message(NULL, fd, ssl_msg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(NULL, fd, &msg); if (status != MESSAGE_STATUS_OK) { goto error; } if (msg->kind == 'S') { create_client_tls_connection(fd, ssl, config->servers[server].tls_key_file, config->servers[server].tls_cert_file, config->servers[server].tls_ca_file); } } pgagroal_free_copy_message(ssl_msg); pgagroal_free_message(msg); return AUTH_SUCCESS; error: pgagroal_free_copy_message(ssl_msg); pgagroal_free_message(msg); return AUTH_ERROR; } static int 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; int status = -1; /* We are acting as a client against the server */ if (create_ssl_ctx(true, &ctx)) { pgagroal_log_error("CTX failed"); goto error; } /* Create SSL structure */ if (create_ssl_client(ctx, tls_key_file, tls_cert_file, tls_ca_file, fd, &s)) { pgagroal_log_error("Client failed"); goto error; } do { status = SSL_connect(s); if (status != 1) { int err = SSL_get_error(s, status); switch (err) { case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: #ifndef HAVE_OPENBSD #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) case SSL_ERROR_WANT_ASYNC: case SSL_ERROR_WANT_ASYNC_JOB: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) case SSL_ERROR_WANT_CLIENT_HELLO_CB: #endif #endif #endif break; case SSL_ERROR_SYSCALL: pgagroal_log_error("SSL_ERROR_SYSCALL: FD %d", fd); pgagroal_log_error("%s", ERR_error_string(err, NULL)); pgagroal_log_error("%s", ERR_lib_error_string(err)); pgagroal_log_error("%s", ERR_reason_error_string(err)); errno = 0; goto error; break; case SSL_ERROR_SSL: pgagroal_log_error("SSL_ERROR_SSL: FD %d", fd); pgagroal_log_error("%s", ERR_error_string(err, NULL)); pgagroal_log_error("%s", ERR_lib_error_string(err)); pgagroal_log_error("%s", ERR_reason_error_string(err)); errno = 0; goto error; break; } ERR_clear_error(); } } while (status != 1); *ssl = s; return AUTH_SUCCESS; error: *ssl = s; return AUTH_ERROR; } pgagroal-1.6.0/src/libpgagroal/server.c000066400000000000000000000264431456565230200200570ustar00rootroot00000000000000/* * 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 #include #include #include #include /* system */ #include #include #include #include #include #include #include static int failover(int old_primary); int pgagroal_get_primary(int* server) { int primary; signed char server_state; struct configuration* config; primary = -1; config = (struct configuration*)shmem; /* Find PRIMARY */ for (int i = 0; primary == -1 && i < config->number_of_servers; i++) { server_state = atomic_load(&config->servers[i].state); if (server_state == SERVER_PRIMARY) { pgagroal_log_trace("pgagroal_get_primary: server (%d) name (%s) primary", i, config->servers[i].name); primary = i; } } /* Find NOTINIT_PRIMARY */ for (int i = 0; primary == -1 && i < config->number_of_servers; i++) { server_state = atomic_load(&config->servers[i].state); if (server_state == SERVER_NOTINIT_PRIMARY) { pgagroal_log_trace("pgagroal_get_primary: server (%d) name (%s) noninit_primary", i, config->servers[i].name); primary = i; } } /* Find the first valid server */ for (int i = 0; primary == -1 && i < config->number_of_servers; i++) { server_state = atomic_load(&config->servers[i].state); if (server_state != SERVER_FAILOVER && server_state != SERVER_FAILED) { pgagroal_log_trace("pgagroal_get_primary: server (%d) name (%s) any (%d)", i, config->servers[i].name, server_state); primary = i; } } if (primary == -1) { goto error; } *server = primary; return 0; error: *server = -1; return 1; } int pgagroal_update_server_state(int slot, int socket, SSL* ssl) { int status; int server; size_t size = 40; signed char state; char is_recovery[size]; struct message qmsg; struct message* tmsg = NULL; struct configuration* config; config = (struct configuration*)shmem; server = config->connections[slot].server; memset(&qmsg, 0, sizeof(struct message)); memset(&is_recovery, 0, size); pgagroal_write_byte(&is_recovery, 'Q'); pgagroal_write_int32(&(is_recovery[1]), size - 1); pgagroal_write_string(&(is_recovery[5]), "SELECT * FROM pg_is_in_recovery();"); qmsg.kind = 'Q'; qmsg.length = size; qmsg.data = &is_recovery; status = pgagroal_write_message(ssl, socket, &qmsg); if (status != MESSAGE_STATUS_OK) { goto error; } status = pgagroal_read_block_message(ssl, socket, &tmsg); if (status != MESSAGE_STATUS_OK) { goto error; } /* Read directly from the D message fragment */ state = pgagroal_read_byte(tmsg->data + 54); pgagroal_free_message(tmsg); if (state == 'f') { atomic_store(&config->servers[server].state, SERVER_PRIMARY); } else { atomic_store(&config->servers[server].state, SERVER_REPLICA); } pgagroal_free_message(tmsg); return 0; error: pgagroal_log_trace("pgagroal_update_server_state: slot (%d) status (%d)", slot, status); pgagroal_free_message(tmsg); return 1; } int pgagroal_server_status(void) { struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < NUMBER_OF_SERVERS; i++) { if (strlen(config->servers[i].name) > 0) { pgagroal_log_debug("pgagroal_server_status: #: %d", i); pgagroal_log_debug(" Name: %s", config->servers[i].name); pgagroal_log_debug(" Host: %s", config->servers[i].host); pgagroal_log_debug(" Port: %d", config->servers[i].port); switch (atomic_load(&config->servers[i].state)) { case SERVER_NOTINIT: pgagroal_log_debug(" State: NOTINIT"); break; case SERVER_NOTINIT_PRIMARY: pgagroal_log_debug(" State: NOTINIT_PRIMARY"); break; case SERVER_PRIMARY: pgagroal_log_debug(" State: PRIMARY"); break; case SERVER_REPLICA: pgagroal_log_debug(" State: REPLICA"); break; case SERVER_FAILOVER: pgagroal_log_debug(" State: FAILOVER"); break; case SERVER_FAILED: pgagroal_log_debug(" State: FAILED"); break; default: pgagroal_log_debug(" State: %d", atomic_load(&config->servers[i].state)); break; } } } return 0; } int pgagroal_server_failover(int slot) { signed char primary; signed char old_primary; int ret = 1; struct configuration* config = NULL; config = (struct configuration*)shmem; primary = SERVER_PRIMARY; old_primary = config->connections[slot].server; if (atomic_compare_exchange_strong(&config->servers[old_primary].state, &primary, SERVER_FAILOVER)) { ret = failover(old_primary); if (!fork()) { pgagroal_flush_server(old_primary); } } return ret; } int pgagroal_server_force_failover(int server) { signed char cur_state; signed char prev_state; struct configuration* config = NULL; config = (struct configuration*)shmem; cur_state = atomic_load(&config->servers[server].state); if (cur_state != SERVER_FAILOVER && cur_state != SERVER_FAILED) { prev_state = atomic_exchange(&config->servers[server].state, SERVER_FAILOVER); if (prev_state == SERVER_NOTINIT || prev_state == SERVER_NOTINIT_PRIMARY || prev_state == SERVER_PRIMARY || prev_state == SERVER_REPLICA) { return failover(server); } else if (prev_state == SERVER_FAILED) { atomic_store(&config->servers[server].state, SERVER_FAILED); } } return 1; } int pgagroal_server_reset(char* server) { signed char state; struct configuration* config = NULL; config = (struct configuration*)shmem; for (int i = 0; i < config->number_of_servers; i++) { if (!strcmp(config->servers[i].name, server)) { state = atomic_load(&config->servers[i].state); if (state == SERVER_FAILED) { atomic_store(&config->servers[i].state, SERVER_NOTINIT); } return 0; } } return 1; } int pgagroal_server_switch(char* server) { int old_primary; int new_primary; signed char state; struct configuration* config = NULL; config = (struct configuration*)shmem; old_primary = -1; new_primary = -1; for (int i = 0; i < config->number_of_servers; i++) { state = atomic_load(&config->servers[i].state); if (state == SERVER_PRIMARY) { old_primary = i; } else if (!strcmp(config->servers[i].name, server)) { new_primary = i; } } if (old_primary != -1 && new_primary != -1) { atomic_store(&config->servers[old_primary].state, SERVER_FAILED); atomic_store(&config->servers[new_primary].state, SERVER_PRIMARY); return 0; } else if (old_primary == -1 && new_primary != -1) { atomic_store(&config->servers[new_primary].state, SERVER_PRIMARY); return 0; } return 1; } static int failover(int old_primary) { signed char state; char old_primary_port[6]; int new_primary; char new_primary_port[6]; int status; pid_t pid; struct configuration* config = NULL; config = (struct configuration*)shmem; new_primary = -1; for (int i = 0; new_primary == -1 && i < config->number_of_servers; i++) { state = atomic_load(&config->servers[i].state); if (state == SERVER_NOTINIT || state == SERVER_NOTINIT_PRIMARY || state == SERVER_REPLICA) { new_primary = i; } } if (new_primary == -1) { pgagroal_log_error("Failover: New primary could not be found"); atomic_store(&config->servers[old_primary].state, SERVER_FAILED); goto error; } pid = fork(); if (pid == -1) { pgagroal_log_error("Failover: Unable to execute failover script"); atomic_store(&config->servers[old_primary].state, SERVER_FAILED); goto error; } else if (pid > 0) { waitpid(pid, &status, 0); if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { pgagroal_log_info("Failover: New primary is %s (%s:%d)", config->servers[new_primary].name, config->servers[new_primary].host, config->servers[new_primary].port); atomic_store(&config->servers[old_primary].state, SERVER_FAILED); atomic_store(&config->servers[new_primary].state, SERVER_PRIMARY); } else { if (WIFEXITED(status)) { pgagroal_log_error("Failover: Error from failover script (exit %d)", WEXITSTATUS(status)); } else { pgagroal_log_error("Failover: Error from failover script (status %d)", status); } atomic_store(&config->servers[old_primary].state, SERVER_FAILED); atomic_store(&config->servers[new_primary].state, SERVER_FAILED); } } else { memset(&old_primary_port, 0, sizeof(old_primary_port)); memset(&new_primary_port, 0, sizeof(new_primary_port)); sprintf(&old_primary_port[0], "%d", config->servers[old_primary].port); sprintf(&new_primary_port[0], "%d", config->servers[new_primary].port); execl(config->failover_script, "pgagroal_failover", config->servers[old_primary].host, old_primary_port, config->servers[new_primary].host, new_primary_port, (char*)NULL); } return 0; error: return 1; } pgagroal-1.6.0/src/libpgagroal/shmem.c000066400000000000000000000061361456565230200176570ustar00rootroot00000000000000/* * 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 /* system */ #include #include #include void* shmem = NULL; void* pipeline_shmem = NULL; void* prometheus_shmem = NULL; void* prometheus_cache_shmem = NULL; int pgagroal_create_shared_memory(size_t size, unsigned char hp, void** shmem) { void* s = NULL; int protection = PROT_READ | PROT_WRITE; int visibility = MAP_ANONYMOUS | MAP_SHARED; *shmem = NULL; #ifdef HAVE_LINUX if (hp == HUGEPAGE_TRY || hp == HUGEPAGE_ON) { visibility = visibility | MAP_HUGETLB; } #endif s = mmap(NULL, size, protection, visibility, -1, 0); if (s == (void*)-1) { errno = 0; s = NULL; if (hp == HUGEPAGE_OFF || hp == HUGEPAGE_ON) { return 1; } } if (s == NULL) { visibility = MAP_ANONYMOUS | MAP_SHARED; s = mmap(NULL, size, protection, visibility, 0, 0); if (s == (void*)-1) { errno = 0; return 1; } } memset(s, 0, size); *shmem = s; return 0; } int pgagroal_resize_shared_memory(size_t size, void* shmem, size_t* new_size, void** new_shmem) { struct configuration* config; config = (struct configuration*)shmem; *new_size = size + (config->max_connections * sizeof(struct connection)); if (pgagroal_create_shared_memory(*new_size, config->hugepage, new_shmem)) { return 1; } memset(*new_shmem, 0, *new_size); memcpy(*new_shmem, shmem, size); return 0; } int pgagroal_destroy_shared_memory(void* shmem, size_t size) { return munmap(shmem, size); } pgagroal-1.6.0/src/libpgagroal/tracker.c000066400000000000000000000125141456565230200201760ustar00rootroot00000000000000/* * 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 #include #include /* system */ #include #include #include #include #include #include static int count_connections(void); void pgagroal_tracking_event_basic(int id, char* username, char* database) { int primary; struct configuration* config; config = (struct configuration*)shmem; if (config->tracker) { struct timeval t; long long milliseconds; gettimeofday(&t, NULL); milliseconds = t.tv_sec * 1000 + t.tv_usec / 1000; if (username == NULL) { username = ""; } if (database == NULL) { database = ""; } pgagroal_get_primary(&primary); pgagroal_log_info("PGAGROAL|%d|%d|%d|%lld|%d|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|", id, -1, -3, milliseconds, getpid(), username, database, "", -1, primary, -1, -3, -1, -1, atomic_load(&config->active_connections), count_connections()); } } void pgagroal_tracking_event_slot(int id, int slot) { struct configuration* config; config = (struct configuration*)shmem; if (config->tracker) { char* username = NULL; char* database = NULL; char* appname = NULL; struct timeval t; long long milliseconds; gettimeofday(&t, NULL); milliseconds = t.tv_sec * 1000 + t.tv_usec / 1000; if (slot != -1) { username = &config->connections[slot].username[0]; database = &config->connections[slot].database[0]; appname = &config->connections[slot].appname[0]; } else { username = ""; database = ""; appname = ""; } pgagroal_log_info("PGAGROAL|%d|%d|%d|%lld|%d|%s|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d|", id, slot, atomic_load(&config->states[slot]), milliseconds, getpid(), username, database, appname, config->connections[slot].new, config->connections[slot].server, config->connections[slot].tx_mode, config->connections[slot].has_security, config->connections[slot].limit_rule, config->connections[slot].fd, atomic_load(&config->active_connections), count_connections()); } } void pgagroal_tracking_event_socket(int id, int socket) { struct configuration* config; config = (struct configuration*)shmem; if (config->tracker) { struct timeval t; long long milliseconds; gettimeofday(&t, NULL); milliseconds = t.tv_sec * 1000 + t.tv_usec / 1000; pgagroal_log_info("PGAGROAL|%d|%lld|%d|%d|", id, milliseconds, getpid(), socket); } } static int count_connections(void) { int active = 0; struct configuration* config; config = (struct configuration*)shmem; for (int i = 0; i < config->max_connections; i++) { int state = atomic_load(&config->states[i]); switch (state) { case STATE_IN_USE: case STATE_GRACEFULLY: active++; default: break; } } return active; } pgagroal-1.6.0/src/libpgagroal/utils.c000066400000000000000000000570771456565230200177200ustar00rootroot00000000000000/* * 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 #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef EVBACKEND_LINUXAIO #define EVBACKEND_LINUXAIO 0x00000040U #endif #ifndef EVBACKEND_IOURING #define EVBACKEND_IOURING 0x00000080U #endif extern char** environ; #if defined(HAVE_LINUX) || defined(HAVE_OSX) static bool env_changed = false; static int max_process_title_size = 0; #endif int32_t pgagroal_get_request(struct message* msg) { if (msg == NULL || msg->data == NULL || msg->length < 8) { return -1; } return pgagroal_read_int32(msg->data + 4); } int pgagroal_extract_username_database(struct message* msg, char** username, char** database, char** appname) { int start, end; int counter = 0; signed char c; char** array = NULL; size_t size; char* un = NULL; char* db = NULL; char* an = NULL; *username = NULL; *database = NULL; *appname = NULL; /* We know where the parameters start, and we know that the message is zero terminated */ for (int i = 8; i < msg->length - 1; i++) { c = pgagroal_read_byte(msg->data + i); if (c == 0) { counter++; } } array = (char**)malloc(sizeof(char*) * counter); counter = 0; start = 8; end = 8; for (int i = 8; i < msg->length - 1; i++) { c = pgagroal_read_byte(msg->data + i); end++; if (c == 0) { array[counter] = (char*)calloc(1, end - start); memcpy(array[counter], msg->data + start, end - start); start = end; counter++; } } for (int i = 0; i < counter; i++) { if (!strcmp(array[i], "user")) { size = strlen(array[i + 1]) + 1; un = calloc(1, size); memcpy(un, array[i + 1], size); *username = un; } else if (!strcmp(array[i], "database")) { size = strlen(array[i + 1]) + 1; db = calloc(1, size); memcpy(db, array[i + 1], size); *database = db; } else if (!strcmp(array[i], "application_name")) { size = strlen(array[i + 1]) + 1; an = calloc(1, size); memcpy(an, array[i + 1], size); *appname = an; } } if (*database == NULL) { *database = *username; } pgagroal_log_trace("Username: %s", *username); pgagroal_log_trace("Database: %s", *database); for (int i = 0; i < counter; i++) { free(array[i]); } free(array); return 0; } int pgagroal_extract_message(char type, struct message* msg, struct message** extracted) { int offset; int m_length; void* data = NULL; struct message* result = NULL; offset = 0; *extracted = NULL; while (result == NULL && offset < msg->length) { char t = (char)pgagroal_read_byte(msg->data + offset); if (type == t) { m_length = pgagroal_read_int32(msg->data + offset + 1); result = (struct message*)malloc(sizeof(struct message)); data = (void*)malloc(1 + m_length); memcpy(data, msg->data + offset, 1 + m_length); result->kind = pgagroal_read_byte(data); result->length = 1 + m_length; result->max_length = 1 + m_length; result->data = data; *extracted = result; return 0; } else { offset += 1; offset += pgagroal_read_int32(msg->data + offset); } } return 1; } int pgagroal_extract_error_message(struct message* msg, char** error) { int max = 0; int offset = 5; signed char type; char* s = NULL; char* result = NULL; *error = NULL; if (msg->kind == 'E') { max = pgagroal_read_int32(msg->data + 1); while (result == NULL && offset < max) { type = pgagroal_read_byte(msg->data + offset); s = pgagroal_read_string(msg->data + offset + 1); if (type == 'M') { result = (char*)calloc(1, strlen(s) + 1); memcpy(result, s, strlen(s)); *error = result; } offset += 1 + strlen(s) + 1; } } else { goto error; } return 0; error: return 1; } char* pgagroal_get_state_string(signed char state) { switch (state) { case STATE_NOTINIT: return "Not initialized"; case STATE_INIT: return "Initializing"; case STATE_FREE: return "Free"; case STATE_IN_USE: return "Active"; case STATE_GRACEFULLY: return "Graceful"; case STATE_FLUSH: return "Flush"; case STATE_IDLE_CHECK: return "Idle check"; case STATE_MAX_CONNECTION_AGE: return "Max connection age check"; case STATE_VALIDATION: return "Validating"; case STATE_REMOVE: return "Removing"; } return "Unknown"; } signed char pgagroal_read_byte(void* data) { return (signed char) *((char*)data); } int16_t pgagroal_read_int16(void* data) { unsigned char bytes[] = {*((unsigned char*)data), *((unsigned char*)(data + 1))}; int16_t res = (int16_t)((bytes[0] << 8)) | ((bytes[1])); return res; } int32_t pgagroal_read_int32(void* data) { unsigned char bytes[] = {*((unsigned char*)data), *((unsigned char*)(data + 1)), *((unsigned char*)(data + 2)), *((unsigned char*)(data + 3))}; int32_t res = (int32_t)((bytes[0] << 24)) | ((bytes[1] << 16)) | ((bytes[2] << 8)) | ((bytes[3])); return res; } long pgagroal_read_long(void* data) { unsigned char bytes[] = {*((unsigned char*)data), *((unsigned char*)(data + 1)), *((unsigned char*)(data + 2)), *((unsigned char*)(data + 3)), *((unsigned char*)(data + 4)), *((unsigned char*)(data + 5)), *((unsigned char*)(data + 6)), *((unsigned char*)(data + 7))}; long res = (long)(((long)bytes[0]) << 56) | (((long)bytes[1]) << 48) | (((long)bytes[2]) << 40) | (((long)bytes[3]) << 32) | (((long)bytes[4]) << 24) | (((long)bytes[5]) << 16) | (((long)bytes[6]) << 8) | (((long)bytes[7])); return res; } char* pgagroal_read_string(void* data) { return (char*)data; } void pgagroal_write_byte(void* data, signed char b) { *((char*)(data)) = b; } void pgagroal_write_int32(void* data, int32_t i) { char* ptr = (char*)&i; *((char*)(data + 3)) = *ptr; ptr++; *((char*)(data + 2)) = *ptr; ptr++; *((char*)(data + 1)) = *ptr; ptr++; *((char*)(data)) = *ptr; } void pgagroal_write_long(void* data, long l) { char* ptr = (char*)&l; *((char*)(data + 7)) = *ptr; ptr++; *((char*)(data + 6)) = *ptr; ptr++; *((char*)(data + 5)) = *ptr; ptr++; *((char*)(data + 4)) = *ptr; ptr++; *((char*)(data + 3)) = *ptr; ptr++; *((char*)(data + 2)) = *ptr; ptr++; *((char*)(data + 1)) = *ptr; ptr++; *((char*)(data)) = *ptr; } void pgagroal_write_string(void* data, char* s) { memcpy(data, s, strlen(s)); } bool pgagroal_bigendian(void) { short int word = 0x0001; char* b = (char*)&word; return (b[0] ? false : true); } unsigned int pgagroal_swap(unsigned int i) { return ((i << 24) & 0xff000000) | ((i << 8) & 0x00ff0000) | ((i >> 8) & 0x0000ff00) | ((i >> 24) & 0x000000ff); } void pgagroal_libev_engines(void) { unsigned int engines = ev_supported_backends(); if (engines & EVBACKEND_SELECT) { pgagroal_log_debug("libev available: select"); } if (engines & EVBACKEND_POLL) { pgagroal_log_debug("libev available: poll"); } if (engines & EVBACKEND_EPOLL) { pgagroal_log_debug("libev available: epoll"); } if (engines & EVBACKEND_LINUXAIO) { pgagroal_log_debug("libev available: linuxaio"); } if (engines & EVBACKEND_IOURING) { pgagroal_log_debug("libev available: iouring"); } if (engines & EVBACKEND_KQUEUE) { pgagroal_log_debug("libev available: kqueue"); } if (engines & EVBACKEND_DEVPOLL) { pgagroal_log_debug("libev available: devpoll"); } if (engines & EVBACKEND_PORT) { pgagroal_log_debug("libev available: port"); } } unsigned int pgagroal_libev(char* engine) { unsigned int engines = ev_supported_backends(); if (engine) { if (!strcmp("select", engine)) { if (engines & EVBACKEND_SELECT) { return EVBACKEND_SELECT; } else { pgagroal_log_warn("libev not available: select"); } } else if (!strcmp("poll", engine)) { if (engines & EVBACKEND_POLL) { return EVBACKEND_POLL; } else { pgagroal_log_warn("libev not available: poll"); } } else if (!strcmp("epoll", engine)) { if (engines & EVBACKEND_EPOLL) { return EVBACKEND_EPOLL; } else { pgagroal_log_warn("libev not available: epoll"); } } else if (!strcmp("linuxaio", engine)) { return EVFLAG_AUTO; } else if (!strcmp("iouring", engine)) { if (engines & EVBACKEND_IOURING) { return EVBACKEND_IOURING; } else { pgagroal_log_warn("libev not available: iouring"); } } else if (!strcmp("devpoll", engine)) { if (engines & EVBACKEND_DEVPOLL) { return EVBACKEND_DEVPOLL; } else { pgagroal_log_warn("libev not available: devpoll"); } } else if (!strcmp("port", engine)) { if (engines & EVBACKEND_PORT) { return EVBACKEND_PORT; } else { pgagroal_log_warn("libev not available: port"); } } else if (!strcmp("auto", engine) || !strcmp("", engine)) { return EVFLAG_AUTO; } else { pgagroal_log_warn("libev unknown option: %s", engine); } } return EVFLAG_AUTO; } char* pgagroal_libev_engine(unsigned int val) { switch (val) { case EVBACKEND_SELECT: return "select"; case EVBACKEND_POLL: return "poll"; case EVBACKEND_EPOLL: return "epoll"; case EVBACKEND_LINUXAIO: return "linuxaio"; case EVBACKEND_IOURING: return "iouring"; case EVBACKEND_KQUEUE: return "kqueue"; case EVBACKEND_DEVPOLL: return "devpoll"; case EVBACKEND_PORT: return "port"; } return "Unknown"; } char* pgagroal_get_home_directory(void) { struct passwd* pw = getpwuid(getuid()); if (pw == NULL) { return NULL; } return pw->pw_dir; } char* pgagroal_get_user_name(void) { struct passwd* pw = getpwuid(getuid()); if (pw == NULL) { return NULL; } return pw->pw_name; } char* pgagroal_get_password(void) { char p[MAX_PASSWORD_LENGTH]; struct termios oldt, newt; int i = 0; int c; char* result = NULL; memset(&p, 0, sizeof(p)); tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); while ((c = getchar()) != '\n' && c != EOF && i < MAX_PASSWORD_LENGTH) { p[i++] = c; } p[i] = '\0'; tcsetattr(STDIN_FILENO, TCSANOW, &oldt); result = calloc(1, strlen(p) + 1); memcpy(result, &p, strlen(p)); return result; } int pgagroal_base64_encode(char* raw, int raw_length, char** encoded) { BIO* b64_bio; BIO* mem_bio; BUF_MEM* mem_bio_mem_ptr; char* r = NULL; if (raw == NULL) { goto error; } b64_bio = BIO_new(BIO_f_base64()); mem_bio = BIO_new(BIO_s_mem()); BIO_push(b64_bio, mem_bio); BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); BIO_write(b64_bio, raw, raw_length); BIO_flush(b64_bio); BIO_get_mem_ptr(mem_bio, &mem_bio_mem_ptr); BIO_set_close(mem_bio, BIO_NOCLOSE); BIO_free_all(b64_bio); BUF_MEM_grow(mem_bio_mem_ptr, (*mem_bio_mem_ptr).length + 1); (*mem_bio_mem_ptr).data[(*mem_bio_mem_ptr).length] = '\0'; r = calloc(1, strlen((*mem_bio_mem_ptr).data) + 1); memcpy(r, (*mem_bio_mem_ptr).data, strlen((*mem_bio_mem_ptr).data)); BUF_MEM_free(mem_bio_mem_ptr); *encoded = r; return 0; error: *encoded = NULL; return 1; } int pgagroal_base64_decode(char* encoded, size_t encoded_length, char** raw, int* raw_length) { BIO* b64_bio; BIO* mem_bio; size_t size; char* decoded; int index; if (encoded == NULL) { goto error; } size = (encoded_length * 3) / 4 + 1; decoded = calloc(1, size); b64_bio = BIO_new(BIO_f_base64()); mem_bio = BIO_new(BIO_s_mem()); BIO_write(mem_bio, encoded, encoded_length); BIO_push(b64_bio, mem_bio); BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); index = 0; while (0 < BIO_read(b64_bio, decoded + index, 1)) { index++; } BIO_free_all(b64_bio); *raw = decoded; *raw_length = index; return 0; error: *raw = NULL; *raw_length = 0; return 1; } void pgagroal_set_proc_title(int argc, char** argv, char* s1, char* s2) { #if defined(HAVE_LINUX) || defined(HAVE_OSX) char title[MAX_PROCESS_TITLE_LENGTH]; size_t size; char** env = environ; int es = 0; struct configuration* config; config = (struct configuration*)shmem; // sanity check: if the user does not want to // update the process title, do nothing if (config->update_process_title == UPDATE_PROCESS_TITLE_NEVER) { return; } if (!env_changed) { for (int i = 0; env[i] != NULL; i++) { es++; } environ = (char**)malloc(sizeof(char*) * (es + 1)); if (environ == NULL) { return; } for (int i = 0; env[i] != NULL; i++) { size = strlen(env[i]); environ[i] = (char*)calloc(1, size + 1); if (environ[i] == NULL) { return; } memcpy(environ[i], env[i], size); } environ[es] = NULL; env_changed = true; } // compute how long was the command line // when the application was started if (max_process_title_size == 0) { for (int i = 0; i < argc; i++) { max_process_title_size += strlen(argv[i]) + 1; } } // compose the new title memset(&title, 0, sizeof(title)); snprintf(title, sizeof(title) - 1, "pgagroal: %s%s%s", s1 != NULL ? s1 : "", s1 != NULL && s2 != NULL ? "/" : "", s2 != NULL ? s2 : ""); // nuke the command line info memset(*argv, 0, max_process_title_size); // copy the new title over argv checking // the update_process_title policy if (config->update_process_title == UPDATE_PROCESS_TITLE_STRICT) { size = max_process_title_size; } else { // here we can set the title to a full description size = strlen(title) + 1; } memcpy(*argv, title, size); memset(*argv + size, 0, 1); // keep track of how long is now the title max_process_title_size = size; #else setproctitle("-pgagroal: %s%s%s", s1 != NULL ? s1 : "", s1 != NULL && s2 != NULL ? "/" : "", s2 != NULL ? s2 : ""); #endif } void pgagroal_set_connection_proc_title(int argc, char** argv, struct connection* connection) { struct configuration* config; int primary; char* info = NULL; config = (struct configuration*)shmem; if (pgagroal_get_primary(&primary)) { // cannot find the primary, this is a problem! pgagroal_set_proc_title(argc, argv, connection->username, connection->database); return; } info = pgagroal_append(info, connection->username); info = pgagroal_append(info, "@"); info = pgagroal_append(info, config->servers[primary].host); info = pgagroal_append(info, ":"); info = pgagroal_append_int(info, config->servers[primary].port); pgagroal_set_proc_title(argc, argv, info, connection->database); free(info); } unsigned int pgagroal_version_as_number(unsigned int major, unsigned int minor, unsigned int patch) { return (patch % 100) + (minor % 100) * 100 + (major % 100) * 10000; } unsigned int pgagroal_version_number(void) { return pgagroal_version_as_number(PGAGROAL_MAJOR_VERSION, PGAGROAL_MINOR_VERSION, PGAGROAL_PATCH_VERSION); } bool pgagroal_version_ge(unsigned int major, unsigned int minor, unsigned int patch) { if (pgagroal_version_number() >= pgagroal_version_as_number(major, minor, patch)) { return true; } else { return false; } } char* pgagroal_append(char* orig, char* s) { size_t orig_length; size_t s_length; char* n = NULL; if (s == NULL) { return orig; } if (orig != NULL) { orig_length = strlen(orig); } else { orig_length = 0; } s_length = strlen(s); n = (char*)realloc(orig, orig_length + s_length + 1); memcpy(n + orig_length, s, s_length); n[orig_length + s_length] = '\0'; return n; } char* pgagroal_append_int(char* orig, int i) { char number[12]; memset(&number[0], 0, sizeof(number)); snprintf(&number[0], 11, "%d", i); orig = pgagroal_append(orig, number); return orig; } char* pgagroal_append_ulong(char* orig, unsigned long l) { char number[21]; memset(&number[0], 0, sizeof(number)); snprintf(&number[0], 20, "%lu", l); orig = pgagroal_append(orig, number); return orig; } char* pgagroal_append_ullong(char* orig, unsigned long long l) { char number[21]; memset(&number[0], 0, sizeof(number)); snprintf(&number[0], 20, "%llu", l); orig = pgagroal_append(orig, number); return orig; } #ifdef DEBUG int pgagroal_backtrace(void) { #ifdef HAVE_LINUX void* array[100]; size_t size; char** strings; size = backtrace(array, 100); strings = backtrace_symbols(array, size); for (size_t i = 0; i < size; i++) { printf("%s\n", strings[i]); } free(strings); #endif return 0; } #endif bool parse_command(int argc, char** argv, int offset, char* command, char* subcommand, char** key, char* default_key, char** value, char* default_value) { // sanity check: if no arguments, nothing to parse! if (argc <= offset) { return false; } // first of all check if the command is the same // as the first argument on the command line if (strncmp(argv[offset], command, MISC_LENGTH)) { return false; } if (subcommand) { // thre must be a subcommand check offset++; if (argc <= offset) { // not enough command args! return false; } if (strncmp(argv[offset], subcommand, MISC_LENGTH)) { return false; } } if (key) { // need to evaluate the database or server or configuration key offset++; *key = argc > offset ? argv[offset] : default_key; if (*key == NULL || strlen(*key) == 0) { goto error; } // do I need also a value? if (value) { offset++; *value = argc > offset ? argv[offset] : default_value; if (*value == NULL || strlen(*value) == 0) { goto error; } } } return true; error: return false; } bool parse_deprecated_command(int argc, char** argv, int offset, char* command, char** value, char* deprecated_by, unsigned int deprecated_since_major, unsigned int deprecated_since_minor) { // sanity check: if no arguments, nothing to parse! if (argc <= offset) { return false; } // first of all check if the command is the same // as the first argument on the command line if (strncmp(argv[offset], command, MISC_LENGTH)) { return false; } if (value) { // need to evaluate the database or server offset++; *value = argc > offset ? argv[offset] : "*"; } // warn the user if there is enough information // about deprecation 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", command, deprecated_by, deprecated_since_major, deprecated_since_minor); } return true; } bool parse_command_simple(int argc, char** argv, int offset, char* command, char* subcommand) { 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; } } pgagroal-1.6.0/src/libpgagroal/worker.c000066400000000000000000000241471456565230200200610ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include volatile int running = 1; volatile int exit_code = WORKER_FAILURE; static void signal_cb(struct ev_loop* loop, ev_signal* w, int revents); void pgagroal_worker(int client_fd, char* address, char** argv) { struct ev_loop* loop = NULL; struct signal_info signal_watcher; struct worker_io client_io; struct worker_io server_io; time_t start_time; bool started = false; int auth_status; struct configuration* config; struct pipeline p; bool tx_pool = false; int32_t slot = -1; SSL* client_ssl = NULL; SSL* server_ssl = NULL; pgagroal_start_logging(); pgagroal_memory_init(); config = (struct configuration*)shmem; memset(&client_io, 0, sizeof(struct worker_io)); memset(&server_io, 0, sizeof(struct worker_io)); client_io.slot = -1; server_io.slot = -1; start_time = time(NULL); pgagroal_tracking_event_basic(TRACKER_CLIENT_START, NULL, NULL); pgagroal_tracking_event_socket(TRACKER_SOCKET_ASSOCIATE_CLIENT, client_fd); pgagroal_set_proc_title(1, argv, "authenticating", NULL); pgagroal_prometheus_client_wait_add(); /* Authentication */ auth_status = pgagroal_authenticate(client_fd, address, &slot, &client_ssl, &server_ssl); if (auth_status == AUTH_SUCCESS) { pgagroal_log_debug("pgagroal_worker: Slot %d (%d -> %d)", slot, client_fd, config->connections[slot].fd); pgagroal_tracking_event_socket(TRACKER_SOCKET_ASSOCIATE_SERVER, config->connections[slot].fd); if (config->log_connections) { pgagroal_log_info("connect: user=%s database=%s address=%s", config->connections[slot].username, config->connections[slot].database, address); } pgagroal_prometheus_client_wait_sub(); pgagroal_prometheus_client_active_add(); pgagroal_pool_status(); // do we have to update the process title? switch (config->update_process_title) { case UPDATE_PROCESS_TITLE_MINIMAL: case UPDATE_PROCESS_TITLE_STRICT: // pgagroal_set_proc_title will check the policy pgagroal_set_proc_title(1, argv, config->connections[slot].username, config->connections[slot].database); break; case UPDATE_PROCESS_TITLE_VERBOSE: pgagroal_set_connection_proc_title(1, argv, &config->connections[slot]); break; } if (config->pipeline == PIPELINE_PERFORMANCE) { p = performance_pipeline(); } else if (config->pipeline == PIPELINE_SESSION) { p = session_pipeline(); } else if (config->pipeline == PIPELINE_TRANSACTION) { p = transaction_pipeline(); tx_pool = true; } else { pgagroal_log_error("pgagroal_worker: Unknown pipeline %d", config->pipeline); p = session_pipeline(); } ev_io_init((struct ev_io*)&client_io, p.client, client_fd, EV_READ); client_io.client_fd = client_fd; client_io.server_fd = config->connections[slot].fd; client_io.slot = slot; client_io.client_ssl = client_ssl; client_io.server_ssl = server_ssl; if (config->pipeline != PIPELINE_TRANSACTION) { ev_io_init((struct ev_io*)&server_io, p.server, config->connections[slot].fd, EV_READ); server_io.client_fd = client_fd; server_io.server_fd = config->connections[slot].fd; server_io.slot = slot; server_io.client_ssl = client_ssl; server_io.server_ssl = server_ssl; } loop = ev_loop_new(pgagroal_libev(config->libev)); ev_signal_init((struct ev_signal*)&signal_watcher, signal_cb, SIGQUIT); signal_watcher.slot = slot; ev_signal_start(loop, (struct ev_signal*)&signal_watcher); p.start(loop, &client_io); started = true; ev_io_start(loop, (struct ev_io*)&client_io); if (config->pipeline != PIPELINE_TRANSACTION) { ev_io_start(loop, (struct ev_io*)&server_io); } while (running) { ev_loop(loop, 0); } if (config->pipeline == PIPELINE_TRANSACTION) { /* The slot may have been updated */ slot = client_io.slot; } pgagroal_prometheus_client_active_sub(); } else { if (config->log_connections) { pgagroal_log_info("connect: address=%s", address); } pgagroal_prometheus_client_wait_sub(); } if (config->log_disconnections) { if (auth_status == AUTH_SUCCESS) { pgagroal_log_info("disconnect: user=%s database=%s address=%s", config->connections[slot].username, config->connections[slot].database, address); } else { pgagroal_log_info("disconnect: address=%s", address); } } /* Return to pool */ if (slot != -1) { if (started) { p.stop(loop, &client_io); pgagroal_prometheus_session_time(difftime(time(NULL), start_time)); } if ((auth_status == AUTH_SUCCESS || auth_status == AUTH_BAD_PASSWORD) && (exit_code == WORKER_SUCCESS || exit_code == WORKER_CLIENT_FAILURE || (exit_code == WORKER_FAILURE && config->connections[slot].has_security != SECURITY_INVALID))) { if (config->pipeline != PIPELINE_TRANSACTION) { pgagroal_tracking_event_socket(TRACKER_SOCKET_DISASSOCIATE_SERVER, config->connections[slot].fd); pgagroal_tracking_event_slot(TRACKER_WORKER_RETURN1, slot); pgagroal_return_connection(slot, server_ssl, tx_pool); } } else if (exit_code == WORKER_SERVER_FAILURE || exit_code == WORKER_SERVER_FATAL || exit_code == WORKER_SHUTDOWN || exit_code == WORKER_FAILOVER || (exit_code == WORKER_FAILURE && config->connections[slot].has_security == SECURITY_INVALID)) { pgagroal_tracking_event_socket(TRACKER_SOCKET_DISASSOCIATE_SERVER, config->connections[slot].fd); pgagroal_tracking_event_slot(TRACKER_WORKER_KILL1, slot); pgagroal_kill_connection(slot, server_ssl); } else { if (pgagroal_socket_isvalid(config->connections[slot].fd) && pgagroal_connection_isvalid(config->connections[slot].fd) && config->connections[slot].has_security != SECURITY_INVALID) { pgagroal_tracking_event_socket(TRACKER_SOCKET_DISASSOCIATE_SERVER, config->connections[slot].fd); pgagroal_tracking_event_slot(TRACKER_WORKER_RETURN2, slot); pgagroal_return_connection(slot, server_ssl, tx_pool); } else { pgagroal_tracking_event_socket(TRACKER_SOCKET_DISASSOCIATE_SERVER, config->connections[slot].fd); pgagroal_tracking_event_slot(TRACKER_WORKER_KILL2, slot); pgagroal_kill_connection(slot, server_ssl); } } } pgagroal_management_client_done(getpid()); if (client_ssl != NULL) { int res; SSL_CTX* ctx = SSL_get_SSL_CTX(client_ssl); res = SSL_shutdown(client_ssl); if (res == 0) { SSL_shutdown(client_ssl); } SSL_free(client_ssl); SSL_CTX_free(ctx); } pgagroal_log_debug("client disconnect: %d", client_fd); pgagroal_tracking_event_socket(TRACKER_SOCKET_DISASSOCIATE_CLIENT, client_fd); pgagroal_disconnect(client_fd); pgagroal_prometheus_client_sockets_sub(); pgagroal_prometheus_query_count_specified_reset(slot); pgagroal_pool_status(); pgagroal_log_debug("After client: PID %d Slot %d (%d)", getpid(), slot, exit_code); if (loop) { ev_io_stop(loop, (struct ev_io*)&client_io); if (config->pipeline != PIPELINE_TRANSACTION) { ev_io_stop(loop, (struct ev_io*)&server_io); } ev_signal_stop(loop, (struct ev_signal*)&signal_watcher); ev_loop_destroy(loop); } free(address); pgagroal_tracking_event_basic(TRACKER_CLIENT_STOP, NULL, NULL); pgagroal_memory_destroy(); pgagroal_stop_logging(); exit(exit_code); } static void signal_cb(struct ev_loop* loop, ev_signal* w, int revents) { struct signal_info* si; si = (struct signal_info*)w; pgagroal_log_debug("pgagroal: signal %d for slot %d", si->signal.signum, si->slot); exit_code = WORKER_SHUTDOWN; running = 0; ev_break(loop, EVBREAK_ALL); } pgagroal-1.6.0/src/main.c000066400000000000000000001670311456565230200152110ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include /* system */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX #include #endif #define MAX_FDS 64 static void accept_main_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static void accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static void accept_metrics_cb(struct ev_loop* loop, struct ev_io* watcher, int revents); static void accept_management_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 void reload_cb(struct ev_loop* loop, ev_signal* w, int revents); static void graceful_cb(struct ev_loop* loop, ev_signal* w, int revents); static void coredump_cb(struct ev_loop* loop, ev_signal* w, int revents); static void idle_timeout_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void max_connection_age_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void validation_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void disconnect_client_cb(struct ev_loop* loop, ev_periodic* w, int revents); static bool accept_fatal(int error); static void add_client(pid_t pid); static void remove_client(pid_t pid); static void reload_configuration(void); static void create_pidfile_or_exit(void); static void remove_pidfile(void); static void shutdown_ports(void); struct accept_io { struct ev_io io; int socket; char** argv; }; struct client { pid_t pid; struct client* next; }; static volatile int keep_running = 1; static char** argv_ptr; static struct ev_loop* main_loop = NULL; static struct accept_io io_main[MAX_FDS]; static struct accept_io io_mgt; static struct accept_io io_uds; static int* main_fds = NULL; static int main_fds_length = -1; static int unix_management_socket = -1; static int unix_pgsql_socket = -1; static struct accept_io io_metrics[MAX_FDS]; static int* metrics_fds = NULL; static int metrics_fds_length = -1; static struct accept_io io_management[MAX_FDS]; static int* management_fds = NULL; static int management_fds_length = -1; static struct pipeline main_pipeline; static int known_fds[MAX_NUMBER_OF_CONNECTIONS]; static struct client* clients = NULL; static void start_mgt(void) { memset(&io_mgt, 0, sizeof(struct accept_io)); ev_io_init((struct ev_io*)&io_mgt, accept_mgt_cb, unix_management_socket, EV_READ); io_mgt.socket = unix_management_socket; io_mgt.argv = argv_ptr; ev_io_start(main_loop, (struct ev_io*)&io_mgt); } static void shutdown_mgt(void) { struct configuration* config; config = (struct configuration*)shmem; ev_io_stop(main_loop, (struct ev_io*)&io_mgt); pgagroal_disconnect(unix_management_socket); errno = 0; pgagroal_remove_unix_socket(config->unix_socket_dir, MAIN_UDS); errno = 0; } static void start_uds(void) { memset(&io_uds, 0, sizeof(struct accept_io)); ev_io_init((struct ev_io*)&io_uds, accept_main_cb, unix_pgsql_socket, EV_READ); io_uds.socket = unix_pgsql_socket; io_uds.argv = argv_ptr; ev_io_start(main_loop, (struct ev_io*)&io_uds); } static void shutdown_uds(void) { char pgsql[MISC_LENGTH]; struct configuration* config; config = (struct configuration*)shmem; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->port); ev_io_stop(main_loop, (struct ev_io*)&io_uds); pgagroal_disconnect(unix_pgsql_socket); errno = 0; pgagroal_remove_unix_socket(config->unix_socket_dir, &pgsql[0]); errno = 0; } static void start_io(void) { for (int i = 0; i < main_fds_length; i++) { int sockfd = *(main_fds + i); memset(&io_main[i], 0, sizeof(struct accept_io)); ev_io_init((struct ev_io*)&io_main[i], accept_main_cb, sockfd, EV_READ); io_main[i].socket = sockfd; io_main[i].argv = argv_ptr; ev_io_start(main_loop, (struct ev_io*)&io_main[i]); } } static void shutdown_io(void) { for (int i = 0; i < main_fds_length; i++) { ev_io_stop(main_loop, (struct ev_io*)&io_main[i]); pgagroal_disconnect(io_main[i].socket); errno = 0; } } static void start_metrics(void) { for (int i = 0; i < metrics_fds_length; i++) { int sockfd = *(metrics_fds + i); memset(&io_metrics[i], 0, sizeof(struct accept_io)); ev_io_init((struct ev_io*)&io_metrics[i], accept_metrics_cb, sockfd, EV_READ); io_metrics[i].socket = sockfd; io_metrics[i].argv = argv_ptr; ev_io_start(main_loop, (struct ev_io*)&io_metrics[i]); } } static void shutdown_metrics(void) { for (int i = 0; i < metrics_fds_length; i++) { ev_io_stop(main_loop, (struct ev_io*)&io_metrics[i]); pgagroal_disconnect(io_metrics[i].socket); errno = 0; } } static void start_management(void) { for (int i = 0; i < management_fds_length; i++) { int sockfd = *(management_fds + i); memset(&io_management[i], 0, sizeof(struct accept_io)); ev_io_init((struct ev_io*)&io_management[i], accept_management_cb, sockfd, EV_READ); io_management[i].socket = sockfd; io_management[i].argv = argv_ptr; ev_io_start(main_loop, (struct ev_io*)&io_management[i]); } } static void shutdown_management(void) { for (int i = 0; i < management_fds_length; i++) { ev_io_stop(main_loop, (struct ev_io*)&io_management[i]); pgagroal_disconnect(io_management[i].socket); errno = 0; } } static void version(void) { printf("pgagroal %s\n", PGAGROAL_VERSION); exit(1); } static void usage(void) { printf("pgagroal %s\n", PGAGROAL_VERSION); printf(" High-performance connection pool for PostgreSQL\n"); printf("\n"); printf("Usage:\n"); printf(" pgagroal [ -c CONFIG_FILE ] [ -a HBA_FILE ] [ -d ]\n"); printf("\n"); printf("Options:\n"); printf(" -c, --config CONFIG_FILE Set the path to the pgagroal.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_CONF_FILE); printf(" -a, --hba HBA_FILE Set the path to the pgagroal_hba.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_HBA_FILE); printf(" -l, --limit LIMIT_FILE Set the path to the pgagroal_databases.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_LIMIT_FILE); printf(" -u, --users USERS_FILE Set the path to the pgagroal_users.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_USERS_FILE); printf(" -F, --frontend FRONTEND_USERS_FILE Set the path to the pgagroal_frontend_users.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_FRONTEND_USERS_FILE); printf(" -A, --admins ADMINS_FILE Set the path to the pgagroal_admins.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_ADMINS_FILE); printf(" -S, --superuser SUPERUSER_FILE Set the path to the pgagroal_superuser.conf file\n"); printf(" Default: %s\n", PGAGROAL_DEFAULT_SUPERUSER_FILE); printf(" -d, --daemon Run as a daemon\n"); printf(" -V, --version Display version information\n"); printf(" -?, --help Display help\n"); printf("\n"); printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); printf("Report bugs: %s\n", PGAGROAL_ISSUES); } int main(int argc, char** argv) { char* configuration_path = NULL; char* hba_path = NULL; char* limit_path = NULL; char* users_path = NULL; char* frontend_users_path = NULL; char* admins_path = NULL; char* superuser_path = NULL; bool daemon = false; pid_t pid, sid; #ifdef HAVE_LINUX int sds; #endif bool has_unix_socket = false; bool has_main_sockets = false; void* tmp_shmem = NULL; struct signal_info signal_watcher[6]; struct ev_periodic idle_timeout; struct ev_periodic max_connection_age; struct ev_periodic validation; struct ev_periodic disconnect_client; struct rlimit flimit; size_t shmem_size; size_t pipeline_shmem_size = 0; size_t prometheus_shmem_size = 0; size_t prometheus_cache_shmem_size = 0; size_t tmp_size; struct configuration* config = NULL; int ret; int c; bool conf_file_mandatory; char message[MISC_LENGTH]; // a generic message used for errors argv_ptr = argv; while (1) { static struct option long_options[] = { {"config", required_argument, 0, 'c'}, {"hba", required_argument, 0, 'a'}, {"limit", required_argument, 0, 'l'}, {"users", required_argument, 0, 'u'}, {"frontend", required_argument, 0, 'F'}, {"admins", required_argument, 0, 'A'}, {"superuser", required_argument, 0, 'S'}, {"daemon", no_argument, 0, 'd'}, {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, '?'} }; int option_index = 0; c = getopt_long (argc, argv, "dV?a:c:l:u:F:A:S:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'a': hba_path = optarg; break; case 'c': configuration_path = optarg; break; case 'l': limit_path = optarg; break; case 'u': users_path = optarg; break; case 'F': frontend_users_path = optarg; break; case 'A': admins_path = optarg; break; case 'S': superuser_path = optarg; break; case 'd': daemon = true; break; case 'V': version(); break; case '?': usage(); exit(1); break; default: break; } } if (getuid() == 0) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Using the root account is not allowed"); #endif errx(1, "Using the root account is not allowed"); } shmem_size = sizeof(struct configuration); if (pgagroal_create_shared_memory(shmem_size, HUGEPAGE_OFF, &shmem)) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Error in creating shared memory"); #endif errx(1, "Error in creating shared memory"); } pgagroal_init_configuration(shmem); config = (struct configuration*)shmem; memset(&known_fds, 0, sizeof(known_fds)); memset(message, 0, MISC_LENGTH); // the main configuration file is mandatory! configuration_path = configuration_path != NULL ? configuration_path : PGAGROAL_DEFAULT_CONF_FILE; if ((ret = pgagroal_read_configuration(shmem, configuration_path, true)) != PGAGROAL_CONFIGURATION_STATUS_OK) { // the configuration has some problem, build up a descriptive message if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) { snprintf(message, MISC_LENGTH, "Configuration file not found"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "Too many sections"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_KO) { snprintf(message, MISC_LENGTH, "Invalid configuration file"); } else if (ret > 0) { snprintf(message, MISC_LENGTH, "%d problematic or duplicated section%c", ret, ret > 1 ? 's' : ' '); } #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, configuration_path); #endif errx(1, "%s (file <%s>)", message, configuration_path); } memcpy(&config->configuration_path[0], configuration_path, MIN(strlen(configuration_path), MAX_PATH - 1)); // the HBA file is mandatory! hba_path = hba_path != NULL ? hba_path : PGAGROAL_DEFAULT_HBA_FILE; memset(message, 0, MISC_LENGTH); ret = pgagroal_read_hba_configuration(shmem, hba_path); if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) { snprintf(message, MISC_LENGTH, "HBA configuration file not found"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, hba_path); #endif errx(1, "%s (file <%s>)", message, hba_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "HBA too many entries (max %d)", NUMBER_OF_HBAS); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, hba_path); #endif errx(1, "%s (file <%s>)", message, hba_path); } memcpy(&config->hba_path[0], hba_path, MIN(strlen(hba_path), MAX_PATH - 1)); conf_file_mandatory = true; read_limit_path: if (limit_path != NULL) { memset(message, 0, MISC_LENGTH); ret = pgagroal_read_limit_configuration(shmem, limit_path); if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) { memcpy(&config->limit_path[0], limit_path, MIN(strlen(limit_path), MAX_PATH - 1)); } else if (conf_file_mandatory && ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND) { snprintf(message, MISC_LENGTH, "LIMIT configuration file not found"); printf("pgagroal: %s (file <%s>)\n", message, limit_path); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, limit_path); #endif exit(1); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "Too many limit entries"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, limit_path); #endif errx(1, "%s (file <%s>)", message, limit_path); } } else { // the user did not specify a file on the command line // so try the default one and allow it to be missing limit_path = PGAGROAL_DEFAULT_LIMIT_FILE; conf_file_mandatory = false; goto read_limit_path; } conf_file_mandatory = true; read_users_path: if (users_path != NULL) { memset(message, 0, MISC_LENGTH); ret = pgagroal_read_users_configuration(shmem, users_path); if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) { memcpy(&config->users_path[0], users_path, MIN(strlen(users_path), MAX_PATH - 1)); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND && conf_file_mandatory) { snprintf(message, MISC_LENGTH, "USERS configuration file not found"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s : %s", message, users_path); #endif errx(1, "%s (file <%s>)", message, users_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_KO || ret == PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT) { snprintf(message, MISC_LENGTH, "Invalid master key file"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, users_path); #endif errx(1, "%s (file <%s>)", message, users_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "USERS: too many users defined (%d, max %d)", config->number_of_users, NUMBER_OF_USERS); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, users_path); #endif errx(1, "%s (file <%s>)", message, users_path); } } else { // the user did not specify a file on the command line // so try the default one and allow it to be missing users_path = PGAGROAL_DEFAULT_USERS_FILE; conf_file_mandatory = false; goto read_users_path; } conf_file_mandatory = true; read_frontend_users_path: if (frontend_users_path != NULL) { ret = pgagroal_read_frontend_users_configuration(shmem, frontend_users_path); if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND && conf_file_mandatory) { memset(message, 0, MISC_LENGTH); snprintf(message, MISC_LENGTH, "FRONTEND USERS configuration file not found"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, frontend_users_path); #endif errx(1, "%s (file <%s>)", message, frontend_users_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT || ret == PGAGROAL_CONFIGURATION_STATUS_KO) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid master key file"); #endif errx(1, "Invalid master key file"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { memset(message, 0, MISC_LENGTH); snprintf(message, MISC_LENGTH, "FRONTEND USERS: Too many users defined %d (max %d)", config->number_of_frontend_users, NUMBER_OF_USERS); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, frontend_users_path); #endif errx(1, "%s (file <%s>)", message, frontend_users_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) { memcpy(&config->frontend_users_path[0], frontend_users_path, MIN(strlen(frontend_users_path), MAX_PATH - 1)); } } else { // the user did not specify a file on the command line // so try the default one and allow it to be missing frontend_users_path = PGAGROAL_DEFAULT_FRONTEND_USERS_FILE; conf_file_mandatory = false; goto read_frontend_users_path; } conf_file_mandatory = true; read_admins_path: if (admins_path != NULL) { memset(message, 0, MISC_LENGTH); ret = pgagroal_read_admins_configuration(shmem, admins_path); if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND && conf_file_mandatory) { snprintf(message, MISC_LENGTH, "ADMINS configuration file not found"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, admins_path); #endif errx(1, "%s (file <%s>)", message, admins_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT || ret == PGAGROAL_CONFIGURATION_STATUS_KO) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid master key file"); #endif errx(1, "Invalid master key file"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "Too many admins defined %d (max %d)", config->number_of_admins, NUMBER_OF_ADMINS); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s %s", message, admins_path); #endif errx(1, "%s (file <%s>)", message, admins_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) { memcpy(&config->admins_path[0], admins_path, MIN(strlen(admins_path), MAX_PATH - 1)); } } else { // the user did not specify a file on the command line // so try the default one and allow it to be missing admins_path = PGAGROAL_DEFAULT_ADMINS_FILE; conf_file_mandatory = false; goto read_admins_path; } conf_file_mandatory = true; read_superuser_path: if (superuser_path != NULL) { ret = pgagroal_read_superuser_configuration(shmem, superuser_path); memset(message, 0, MISC_LENGTH); if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_NOT_FOUND && conf_file_mandatory) { snprintf(message, MISC_LENGTH, "SUPERUSER configuration file not found"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, superuser_path); #endif errx(1, "%s (file <%s>)", message, superuser_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_CANNOT_DECRYPT || ret == PGAGROAL_CONFIGURATION_STATUS_KO) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid master key file"); #endif errx(1, "Invalid master key file"); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_FILE_TOO_BIG) { snprintf(message, MISC_LENGTH, "SUPERUSER: Too many superusers defined (max 1)"); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=%s: %s", message, superuser_path); #endif errx(1, "%s (file <%s>)", message, superuser_path); } else if (ret == PGAGROAL_CONFIGURATION_STATUS_OK) { memcpy(&config->superuser_path[0], superuser_path, MIN(strlen(superuser_path), MAX_PATH - 1)); } } else { // the user did not specify a file on the command line // so try the default one and allow it to be missing superuser_path = PGAGROAL_DEFAULT_SUPERUSER_FILE; conf_file_mandatory = false; goto read_superuser_path; } /* systemd sockets */ #ifdef HAVE_LINUX sds = sd_listen_fds(0); if (sds > 0) { int m = 0; main_fds_length = 0; for (int i = 0; i < sds; i++) { int fd = SD_LISTEN_FDS_START + i; if (sd_is_socket(fd, AF_INET, 0, -1) || sd_is_socket(fd, AF_INET6, 0, -1)) { main_fds_length++; } } if (main_fds_length > 0) { main_fds = malloc(main_fds_length * sizeof(int)); } for (int i = 0; i < sds; i++) { int fd = SD_LISTEN_FDS_START + i; if (sd_is_socket(fd, AF_UNIX, 0, -1)) { unix_pgsql_socket = fd; has_unix_socket = true; } else if (sd_is_socket(fd, AF_INET, 0, -1) || sd_is_socket(fd, AF_INET6, 0, -1)) { *(main_fds + (m * sizeof(int))) = fd; has_main_sockets = true; m++; } } } #endif if (pgagroal_init_logging()) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Failed to init logging"); #endif exit(1); } if (pgagroal_start_logging()) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Failed to start logging"); #endif errx(1, "Failed to start logging"); } if (pgagroal_validate_configuration(shmem, has_unix_socket, has_main_sockets)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid configuration"); #endif errx(1, "Invalid configuration"); } if (pgagroal_validate_hba_configuration(shmem)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid HBA configuration"); #endif errx(1, "Invalid HBA configuration"); } if (pgagroal_validate_limit_configuration(shmem)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid LIMIT configuration"); #endif errx(1, "Invalid LIMIT configuration"); } if (pgagroal_validate_users_configuration(shmem)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid USERS configuration"); #endif errx(1, "Invalid USERS configuration"); } if (pgagroal_validate_frontend_users_configuration(shmem)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid FRONTEND USERS configuration"); #endif errx(1, "Invalid FRONTEND USERS configuration"); } if (pgagroal_validate_admins_configuration(shmem)) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid ADMINS configuration"); #endif errx(1, "Invalid ADMINS configuration"); } if (pgagroal_resize_shared_memory(shmem_size, shmem, &tmp_size, &tmp_shmem)) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Error in creating shared memory"); #endif errx(1, "Error in creating shared memory"); } if (pgagroal_destroy_shared_memory(shmem, shmem_size) == -1) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Error in destroying shared memory"); #endif errx(1, "Error in destroying shared memory"); } shmem_size = tmp_size; shmem = tmp_shmem; config = (struct configuration*)shmem; if (pgagroal_init_prometheus(&prometheus_shmem_size, &prometheus_shmem)) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Error in creating and initializing prometheus shared memory"); #endif errx(1, "Error in creating and initializing prometheus shared memory"); } if (pgagroal_init_prometheus_cache(&prometheus_cache_shmem_size, &prometheus_cache_shmem)) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Error in creating and initializing prometheus cache shared memory"); #endif errx(1, "Error in creating and initializing prometheus cache shared memory"); } if (getrlimit(RLIMIT_NOFILE, &flimit) == -1) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Unable to find limit due to %s", strerror(errno)); #endif err(1, "Unable to find limit"); } /* We are "reserving" 30 file descriptors for pgagroal main */ if (config->max_connections > (flimit.rlim_cur - 30)) { #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=max_connections is larger than the file descriptor limit (%ld available)", (long)(flimit.rlim_cur - 30)); #endif errx(1, "max_connections is larger than the file descriptor limit (%ld available)", (long)(flimit.rlim_cur - 30)); } if (daemon) { if (config->log_type == PGAGROAL_LOGGING_TYPE_CONSOLE) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Daemon mode can't be used with console logging"); #endif errx(1, "Daemon mode can't be used with console logging"); } pid = fork(); if (pid < 0) { #ifdef HAVE_LINUX sd_notify(0, "STATUS=Daemon mode failed"); #endif errx(1, "Daemon mode failed"); } if (pid > 0) { exit(0); } /* We are a daemon now */ umask(0); sid = setsid(); if (sid < 0) { exit(1); } } create_pidfile_or_exit(); pgagroal_pool_init(); pgagroal_set_proc_title(argc, argv, "main", NULL); /* Bind Unix Domain Socket for file descriptor transfers */ if (pgagroal_bind_unix_socket(config->unix_socket_dir, MAIN_UDS, &unix_management_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s/%s", config->unix_socket_dir, MAIN_UDS); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Could not bind to %s/%s", config->unix_socket_dir, MAIN_UDS); #endif goto error; } if (!has_unix_socket) { char pgsql[MISC_LENGTH]; memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->port); if (pgagroal_bind_unix_socket(config->unix_socket_dir, &pgsql[0], &unix_pgsql_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s/%s", config->unix_socket_dir, &pgsql[0]); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Could not bind to %s/%s", config->unix_socket_dir, &pgsql[0]); #endif goto error; } } /* Bind main socket */ if (!has_main_sockets) { if (pgagroal_bind(config->host, config->port, &main_fds, &main_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->port); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Could not bind to %s:%d", config->host, config->port); #endif goto error; } } if (main_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", main_fds_length); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Too many descriptors %d", main_fds_length); #endif goto error; } /* libev */ main_loop = ev_default_loop(pgagroal_libev(config->libev)); if (!main_loop) { pgagroal_log_fatal("pgagroal: No loop implementation (%x) (%x)", pgagroal_libev(config->libev), ev_supported_backends()); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=No loop implementation (%x) (%x)", pgagroal_libev(config->libev), ev_supported_backends()); #endif goto error; } ev_signal_init((struct ev_signal*)&signal_watcher[0], shutdown_cb, SIGTERM); ev_signal_init((struct ev_signal*)&signal_watcher[1], reload_cb, SIGHUP); ev_signal_init((struct ev_signal*)&signal_watcher[2], shutdown_cb, SIGINT); ev_signal_init((struct ev_signal*)&signal_watcher[3], graceful_cb, SIGTRAP); ev_signal_init((struct ev_signal*)&signal_watcher[4], coredump_cb, SIGABRT); ev_signal_init((struct ev_signal*)&signal_watcher[5], shutdown_cb, SIGALRM); for (int i = 0; i < 6; i++) { signal_watcher[i].slot = -1; ev_signal_start(main_loop, (struct ev_signal*)&signal_watcher[i]); } if (config->pipeline == PIPELINE_PERFORMANCE) { main_pipeline = performance_pipeline(); } else if (config->pipeline == PIPELINE_SESSION) { if (pgagroal_tls_valid()) { pgagroal_log_fatal("pgagroal: Invalid TLS configuration"); #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid TLS configuration"); #endif goto error; } main_pipeline = session_pipeline(); } else if (config->pipeline == PIPELINE_TRANSACTION) { if (pgagroal_tls_valid()) { pgagroal_log_fatal("pgagroal: Invalid TLS configuration"); #ifdef HAVE_LINUX sd_notify(0, "STATUS=Invalid TLS configuration"); #endif goto error; } main_pipeline = transaction_pipeline(); } else { pgagroal_log_fatal("pgagroal: Unknown pipeline identifier (%d)", config->pipeline); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Unknown pipeline identifier (%d)", config->pipeline); #endif goto error; } if (main_pipeline.initialize(shmem, &pipeline_shmem, &pipeline_shmem_size)) { pgagroal_log_fatal("pgagroal: Pipeline initialize error (%d)", config->pipeline); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Pipeline initialize error (%d)", config->pipeline); #endif goto error; } start_mgt(); start_uds(); start_io(); if (config->idle_timeout > 0) { ev_periodic_init (&idle_timeout, idle_timeout_cb, 0., MAX(1. * config->idle_timeout / 2., 5.), 0); ev_periodic_start (main_loop, &idle_timeout); } if (config->max_connection_age > 0) { ev_periodic_init (&max_connection_age, max_connection_age_cb, 0., MAX(1. * config->max_connection_age / 2., 5.), 0); ev_periodic_start (main_loop, &max_connection_age); } if (config->validation == VALIDATION_BACKGROUND) { ev_periodic_init (&validation, validation_cb, 0., MAX(1. * config->background_interval, 5.), 0); ev_periodic_start (main_loop, &validation); } if (config->disconnect_client > 0) { ev_periodic_init (&disconnect_client, disconnect_client_cb, 0., MIN(300., MAX(1. * config->disconnect_client / 2., 1.)), 0); ev_periodic_start (main_loop, &disconnect_client); } if (config->metrics > 0) { /* Bind metrics socket */ if (pgagroal_bind(config->host, config->metrics, &metrics_fds, &metrics_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->metrics); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Could not bind to %s:%d", config->host, config->metrics); #endif goto error; } if (metrics_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", metrics_fds_length); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Too many descriptors %d", metrics_fds_length); #endif goto error; } start_metrics(); } if (config->management > 0) { /* Bind management socket */ if (pgagroal_bind(config->host, config->management, &management_fds, &management_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->management); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Could not bind to %s:%d", config->host, config->management); #endif goto error; } if (management_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", management_fds_length); #ifdef HAVE_LINUX sd_notifyf(0, "STATUS=Too many descriptors %d", management_fds_length); #endif goto error; } start_management(); } pgagroal_log_info("pgagroal: %s started on %s:%d", PGAGROAL_VERSION, config->host, config->port); for (int i = 0; i < main_fds_length; i++) { pgagroal_log_debug("Socket: %d", *(main_fds + i)); } pgagroal_log_debug("Unix Domain Socket: %d", unix_pgsql_socket); pgagroal_log_debug("Management: %d", unix_management_socket); for (int i = 0; i < metrics_fds_length; i++) { pgagroal_log_debug("Metrics: %d", *(metrics_fds + i)); } for (int i = 0; i < management_fds_length; i++) { pgagroal_log_debug("Remote management: %d", *(management_fds + i)); } pgagroal_libev_engines(); pgagroal_log_debug("libev engine: %s", pgagroal_libev_engine(ev_backend(main_loop))); pgagroal_log_debug("Pipeline: %d", config->pipeline); pgagroal_log_debug("Pipeline size: %lu", pipeline_shmem_size); #if (OPENSSL_VERSION_NUMBER < 0x10100000L) pgagroal_log_debug("%s", SSLeay_version(SSLEAY_VERSION)); #else pgagroal_log_debug("%s", OpenSSL_version(OPENSSL_VERSION)); #endif pgagroal_log_debug("Configuration size: %lu", shmem_size); pgagroal_log_debug("Max connections: %d", config->max_connections); pgagroal_log_debug("Known users: %d", config->number_of_users); pgagroal_log_debug("Known frontend users: %d", config->number_of_frontend_users); pgagroal_log_debug("Known admins: %d", config->number_of_admins); pgagroal_log_debug("Known superuser: %s", strlen(config->superuser.username) > 0 ? "Yes" : "No"); if (!config->allow_unknown_users && config->number_of_users == 0) { pgagroal_log_warn("No users allowed"); } if (pgagroal_can_prefill()) { if (!fork()) { shutdown_ports(); pgagroal_prefill_if_can(false, true); } } #ifdef HAVE_LINUX sd_notifyf(0, "READY=1\n" "STATUS=Running\n" "MAINPID=%lu", (unsigned long)getpid()); #endif while (keep_running) { ev_loop(main_loop, 0); } pgagroal_log_info("pgagroal: shutdown"); #ifdef HAVE_LINUX sd_notify(0, "STOPPING=1"); #endif pgagroal_pool_shutdown(); if (clients != NULL) { struct client* c = clients; while (c != NULL) { kill(c->pid, SIGQUIT); c = c->next; } } shutdown_management(); shutdown_metrics(); shutdown_mgt(); shutdown_io(); shutdown_uds(); for (int i = 0; i < 6; i++) { ev_signal_stop(main_loop, (struct ev_signal*)&signal_watcher[i]); } ev_loop_destroy(main_loop); free(main_fds); free(metrics_fds); free(management_fds); main_pipeline.destroy(pipeline_shmem, pipeline_shmem_size); remove_pidfile(); pgagroal_stop_logging(); pgagroal_destroy_shared_memory(prometheus_shmem, prometheus_shmem_size); pgagroal_destroy_shared_memory(prometheus_cache_shmem, prometheus_cache_shmem_size); pgagroal_destroy_shared_memory(shmem, shmem_size); return 0; error: remove_pidfile(); exit(1); } static void accept_main_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 accept_io* ai; struct configuration* config; if (EV_ERROR & revents) { pgagroal_log_debug("accept_main_cb: invalid event: %s", strerror(errno)); errno = 0; return; } ai = (struct accept_io*)watcher; config = (struct 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) { char pgsql[MISC_LENGTH]; pgagroal_log_warn("Restarting listening port due to: %s (%d)", strerror(errno), watcher->fd); shutdown_io(); shutdown_uds(); memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->port); if (pgagroal_bind_unix_socket(config->unix_socket_dir, &pgsql[0], &unix_pgsql_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s/%s", config->unix_socket_dir, &pgsql[0]); exit(1); } free(main_fds); main_fds = NULL; main_fds_length = 0; if (pgagroal_bind(config->host, config->port, &main_fds, &main_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->port); exit(1); } if (main_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", main_fds_length); exit(1); } if (!fork()) { shutdown_ports(); pgagroal_flush(FLUSH_GRACEFULLY, "*"); } start_io(); start_uds(); for (int i = 0; i < main_fds_length; i++) { pgagroal_log_debug("Socket: %d", *(main_fds + i)); } pgagroal_log_debug("Unix Domain Socket: %d", unix_pgsql_socket); } else { pgagroal_log_debug("accept: %s (%d)", strerror(errno), watcher->fd); } errno = 0; return; } pgagroal_prometheus_client_sockets_add(); pgagroal_get_address((struct sockaddr*)&client_addr, (char*)&address, sizeof(address)); pgagroal_log_trace("accept_main_cb: client address: %s", address); pid = fork(); if (pid == -1) { /* No process */ pgagroal_log_error("Cannot create process"); } else if (pid > 0) { add_client(pid); } else { char* addr = calloc(1, strlen(address) + 1); if (addr == NULL) { pgagroal_log_fatal("Cannot allocate memory for client address"); return; } memcpy(addr, address, strlen(address)); ev_loop_fork(loop); shutdown_ports(); /* We are leaving the socket descriptor valid such that the client won't reuse it */ pgagroal_worker(client_fd, addr, ai->argv); } pgagroal_disconnect(client_fd); } static void accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) { struct sockaddr_in6 client_addr; socklen_t client_addr_length; int client_fd; signed char id; int32_t slot; int payload_i, secondary_payload_i; char* payload_s = NULL; char* secondary_payload_s = NULL; struct configuration* config; if (EV_ERROR & revents) { pgagroal_log_trace("accept_mgt_cb: got invalid event: %s", strerror(errno)); return; } config = (struct configuration*)shmem; client_addr_length = sizeof(client_addr); client_fd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_addr_length); pgagroal_prometheus_self_sockets_add(); if (client_fd == -1) { if (accept_fatal(errno) && keep_running) { pgagroal_log_warn("Restarting management due to: %s (%d)", strerror(errno), watcher->fd); shutdown_mgt(); if (pgagroal_bind_unix_socket(config->unix_socket_dir, MAIN_UDS, &unix_management_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s", config->unix_socket_dir); exit(1); } start_mgt(); pgagroal_log_debug("Management: %d", unix_management_socket); } else { pgagroal_log_debug("accept: %s (%d)", strerror(errno), watcher->fd); } errno = 0; return; } /* Process internal management request -- f.ex. returning a file descriptor to the pool */ pgagroal_management_read_header(client_fd, &id, &slot); pgagroal_management_read_payload(client_fd, id, &payload_i, &payload_s); switch (id) { case MANAGEMENT_TRANSFER_CONNECTION: pgagroal_log_debug("pgagroal: Management transfer connection: Slot %d FD %d", slot, payload_i); config->connections[slot].fd = payload_i; known_fds[slot] = config->connections[slot].fd; if (config->pipeline == PIPELINE_TRANSACTION) { struct client* c = clients; while (c != NULL) { pgagroal_management_client_fd(slot, c->pid); c = c->next; } } break; case MANAGEMENT_RETURN_CONNECTION: pgagroal_log_debug("pgagroal: Management return connection: Slot %d", slot); break; case MANAGEMENT_KILL_CONNECTION: pgagroal_log_debug("pgagroal: Management kill connection: Slot %d", slot); if (known_fds[slot] == payload_i) { struct client* c = clients; while (c != NULL) { pgagroal_management_remove_fd(slot, payload_i, c->pid); c = c->next; } pgagroal_disconnect(payload_i); known_fds[slot] = 0; } break; case MANAGEMENT_FLUSH: pgagroal_log_debug("pgagroal: Management flush (%d/%s)", payload_i, payload_s); if (!fork()) { shutdown_ports(); pgagroal_flush(payload_i, payload_s); } break; case MANAGEMENT_ENABLEDB: pgagroal_log_debug("pgagroal: Management enabledb: %s", payload_s); pgagroal_pool_status(); for (int i = 0; i < NUMBER_OF_DISABLED; i++) { if (!strcmp("*", payload_s)) { memset(&config->disabled[i], 0, MAX_DATABASE_LENGTH); } else if (!strcmp(config->disabled[i], payload_s)) { memset(&config->disabled[i], 0, MAX_DATABASE_LENGTH); } } free(payload_s); break; case MANAGEMENT_DISABLEDB: pgagroal_log_debug("pgagroal: Management disabledb: %s", payload_s); pgagroal_pool_status(); if (!strcmp("*", payload_s)) { for (int i = 0; i < NUMBER_OF_DISABLED; i++) { memset(&config->disabled[i], 0, MAX_DATABASE_LENGTH); } memcpy(&config->disabled[0], payload_s, 1); } else { for (int i = 0; i < NUMBER_OF_DISABLED; i++) { if (!strcmp(config->disabled[i], "")) { memcpy(&config->disabled[i], payload_s, strlen(payload_s)); break; } } } free(payload_s); break; case MANAGEMENT_GRACEFULLY: pgagroal_log_debug("pgagroal: Management gracefully"); pgagroal_pool_status(); config->gracefully = true; break; case MANAGEMENT_STOP: pgagroal_log_debug("pgagroal: Management stop"); pgagroal_pool_status(); ev_break(loop, EVBREAK_ALL); keep_running = 0; break; case MANAGEMENT_CANCEL_SHUTDOWN: pgagroal_log_debug("pgagroal: Management cancel shutdown"); pgagroal_pool_status(); config->gracefully = false; break; case MANAGEMENT_STATUS: pgagroal_log_debug("pgagroal: Management status"); pgagroal_pool_status(); pgagroal_management_write_status(client_fd, config->gracefully); break; case MANAGEMENT_DETAILS: pgagroal_log_debug("pgagroal: Management details"); pgagroal_pool_status(); pgagroal_management_write_status(client_fd, config->gracefully); pgagroal_management_write_details(client_fd); break; case MANAGEMENT_ISALIVE: 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(); break; case MANAGEMENT_RESET_SERVER: pgagroal_log_debug("pgagroal: Management reset server"); pgagroal_server_reset(payload_s); pgagroal_prometheus_failed_servers(); break; case MANAGEMENT_CLIENT_DONE: pgagroal_log_debug("pgagroal: Management client done"); pid_t p = (pid_t)payload_i; remove_client(p); break; case MANAGEMENT_SWITCH_TO: pgagroal_log_debug("pgagroal: Management switch to"); int old_primary = -1; signed char server_state; for (int i = 0; old_primary == -1 && i < config->number_of_servers; i++) { server_state = atomic_load(&config->servers[i].state); if (server_state == SERVER_PRIMARY) { old_primary = i; } } if (!pgagroal_server_switch(payload_s)) { if (!fork()) { shutdown_ports(); if (old_primary != -1) { pgagroal_flush_server(old_primary); } else { pgagroal_flush(FLUSH_GRACEFULLY, "*"); } } pgagroal_prometheus_failed_servers(); } break; case MANAGEMENT_RELOAD: pgagroal_log_debug("pgagroal: Management reload"); reload_configuration(); break; case MANAGEMENT_CONFIG_GET: pgagroal_log_debug("pgagroal: Management config-get for key <%s>", payload_s); pgagroal_management_write_config_get(client_fd, payload_s); break; case MANAGEMENT_CONFIG_SET: // this command has a secondary payload to extract, that is the configuration value pgagroal_management_read_payload(client_fd, id, &secondary_payload_i, &secondary_payload_s); 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; default: pgagroal_log_debug("pgagroal: Unknown management id: %d", id); break; } if (keep_running && config->gracefully) { if (atomic_load(&config->active_connections) == 0) { pgagroal_pool_status(); keep_running = 0; ev_break(loop, EVBREAK_ALL); } } pgagroal_disconnect(client_fd); pgagroal_prometheus_self_sockets_sub(); } static void accept_metrics_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) { struct sockaddr_in6 client_addr; socklen_t client_addr_length; int client_fd; struct configuration* config; if (EV_ERROR & revents) { pgagroal_log_debug("accept_metrics_cb: invalid event: %s", strerror(errno)); errno = 0; return; } config = (struct configuration*)shmem; client_addr_length = sizeof(client_addr); client_fd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_addr_length); pgagroal_prometheus_self_sockets_add(); if (client_fd == -1) { if (accept_fatal(errno) && keep_running) { pgagroal_log_warn("Restarting listening port due to: %s (%d)", strerror(errno), watcher->fd); shutdown_metrics(); free(metrics_fds); metrics_fds = NULL; metrics_fds_length = 0; if (pgagroal_bind(config->host, config->metrics, &metrics_fds, &metrics_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->metrics); exit(1); } if (metrics_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", metrics_fds_length); exit(1); } start_metrics(); for (int i = 0; i < metrics_fds_length; i++) { pgagroal_log_debug("Metrics: %d", *(metrics_fds + i)); } } else { pgagroal_log_debug("accept: %s (%d)", strerror(errno), watcher->fd); } errno = 0; return; } if (!fork()) { ev_loop_fork(loop); shutdown_ports(); /* We are leaving the socket descriptor valid such that the client won't reuse it */ pgagroal_prometheus(client_fd); } pgagroal_disconnect(client_fd); pgagroal_prometheus_self_sockets_sub(); } static void accept_management_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]; struct configuration* config; if (EV_ERROR & revents) { pgagroal_log_debug("accept_management_cb: invalid event: %s", strerror(errno)); errno = 0; return; } memset(&address, 0, sizeof(address)); config = (struct configuration*)shmem; client_addr_length = sizeof(client_addr); client_fd = accept(watcher->fd, (struct sockaddr*)&client_addr, &client_addr_length); pgagroal_prometheus_self_sockets_add(); if (client_fd == -1) { if (accept_fatal(errno) && keep_running) { pgagroal_log_warn("Restarting listening port due to: %s (%d)", strerror(errno), watcher->fd); shutdown_management(); free(management_fds); management_fds = NULL; management_fds_length = 0; if (pgagroal_bind(config->host, config->management, &management_fds, &management_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->management); exit(1); } if (management_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", management_fds_length); exit(1); } start_management(); for (int i = 0; i < management_fds_length; i++) { pgagroal_log_debug("Remote management: %d", *(management_fds + i)); } } else { pgagroal_log_debug("accept: %s (%d)", strerror(errno), watcher->fd); } errno = 0; return; } pgagroal_get_address((struct sockaddr*)&client_addr, (char*)&address, sizeof(address)); if (!fork()) { char* addr = calloc(1, strlen(address) + 1); if (addr == NULL) { pgagroal_log_fatal("Couldn't allocate address"); return; } memcpy(addr, address, strlen(address)); ev_loop_fork(loop); shutdown_ports(); /* We are leaving the socket descriptor valid such that the client won't reuse it */ pgagroal_remote_management(client_fd, addr); } pgagroal_disconnect(client_fd); pgagroal_prometheus_self_sockets_sub(); } static void shutdown_cb(struct ev_loop* loop, ev_signal* w, int revents) { pgagroal_log_debug("pgagroal: shutdown requested"); pgagroal_pool_status(); ev_break(loop, EVBREAK_ALL); keep_running = 0; } static void reload_cb(struct ev_loop* loop, ev_signal* w, int revents) { pgagroal_log_debug("pgagroal: reload requested"); reload_configuration(); } static void graceful_cb(struct ev_loop* loop, ev_signal* w, int revents) { struct configuration* config; config = (struct configuration*)shmem; pgagroal_log_debug("pgagroal: gracefully requested"); pgagroal_pool_status(); config->gracefully = true; if (atomic_load(&config->active_connections) == 0) { pgagroal_pool_status(); keep_running = 0; ev_break(loop, EVBREAK_ALL); } } static void coredump_cb(struct ev_loop* loop, ev_signal* w, int revents) { pgagroal_log_info("pgagroal: core dump requested"); pgagroal_pool_status(); abort(); } static void idle_timeout_cb(struct ev_loop* loop, ev_periodic* w, int revents) { if (EV_ERROR & revents) { pgagroal_log_trace("idle_timeout_cb: got invalid event: %s", strerror(errno)); return; } /* pgagroal_idle_timeout() is always in a fork() */ if (!fork()) { shutdown_ports(); pgagroal_idle_timeout(); } } static void max_connection_age_cb(struct ev_loop* loop, ev_periodic* w, int revents) { if (EV_ERROR & revents) { pgagroal_log_trace("max_connection_age_cb: got invalid event: %s", strerror(errno)); return; } /* max_connection_age() is always in a fork() */ if (!fork()) { shutdown_ports(); pgagroal_max_connection_age(); } } static void validation_cb(struct ev_loop* loop, ev_periodic* w, int revents) { if (EV_ERROR & revents) { pgagroal_log_trace("validation_cb: got invalid event: %s", strerror(errno)); return; } /* pgagroal_validation() is always in a fork() */ if (!fork()) { shutdown_ports(); pgagroal_validation(); } } static void disconnect_client_cb(struct ev_loop* loop, ev_periodic* w, int revents) { if (EV_ERROR & revents) { pgagroal_log_trace("disconnect_client_cb: got invalid event: %s", strerror(errno)); return; } /* main_pipeline.periodic is always in a fork() */ if (!fork()) { shutdown_ports(); main_pipeline.periodic(); } } 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; } static void add_client(pid_t pid) { struct client* c = NULL; c = (struct client*)malloc(sizeof(struct client)); c->pid = pid; c->next = NULL; if (clients == NULL) { clients = c; } else { struct client* last = NULL; last = clients; while (last->next != NULL) { last = last->next; } last->next = c; } } static void remove_client(pid_t pid) { struct client* c = NULL; struct client* p = NULL; c = clients; p = NULL; if (c != NULL) { while (c->pid != pid) { p = c; c = c->next; if (c == NULL) { return; } } if (c == clients) { clients = c->next; } else { p->next = c->next; } free(c); } } static void reload_configuration(void) { char pgsql[MISC_LENGTH]; struct configuration* config; config = (struct configuration*)shmem; shutdown_io(); shutdown_uds(); shutdown_metrics(); shutdown_management(); pgagroal_reload_configuration(); memset(&pgsql, 0, sizeof(pgsql)); snprintf(&pgsql[0], sizeof(pgsql), ".s.PGSQL.%d", config->port); if (pgagroal_bind_unix_socket(config->unix_socket_dir, &pgsql[0], &unix_pgsql_socket)) { pgagroal_log_fatal("pgagroal: Could not bind to %s/%s", config->unix_socket_dir, &pgsql[0]); goto error; } free(main_fds); main_fds = NULL; main_fds_length = 0; if (pgagroal_bind(config->host, config->port, &main_fds, &main_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->port); goto error; } if (main_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", main_fds_length); goto error; } start_io(); start_uds(); if (config->metrics > 0) { free(metrics_fds); metrics_fds = NULL; metrics_fds_length = 0; /* Bind metrics socket */ if (pgagroal_bind(config->host, config->metrics, &metrics_fds, &metrics_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->metrics); goto error; } if (metrics_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", metrics_fds_length); goto error; } start_metrics(); } if (config->management > 0) { free(management_fds); management_fds = NULL; management_fds_length = 0; /* Bind management socket */ if (pgagroal_bind(config->host, config->management, &management_fds, &management_fds_length)) { pgagroal_log_fatal("pgagroal: Could not bind to %s:%d", config->host, config->management); goto error; } if (management_fds_length > MAX_FDS) { pgagroal_log_fatal("pgagroal: Too many descriptors %d", management_fds_length); goto error; } start_management(); } for (int i = 0; i < main_fds_length; i++) { pgagroal_log_debug("Socket: %d", *(main_fds + i)); } pgagroal_log_debug("Unix Domain Socket: %d", unix_pgsql_socket); for (int i = 0; i < metrics_fds_length; i++) { pgagroal_log_debug("Metrics: %d", *(metrics_fds + i)); } for (int i = 0; i < management_fds_length; i++) { pgagroal_log_debug("Remote management: %d", *(management_fds + i)); } return; error: remove_pidfile(); exit(1); } /** * Creates the pid file for the running pooler. * If a pid file already exists, or if the file cannot be written, * the function kills (exits) the current process. * */ static void create_pidfile_or_exit(void) { char buffer[64]; pid_t pid; int r; int fd; struct configuration* config; config = (struct configuration*)shmem; if (strlen(config->pidfile) > 0) { pid = getpid(); fd = open(config->pidfile, O_WRONLY | O_CREAT | O_EXCL, 0640); if (errno == EEXIST) { errx(1, "PID file <%s> exists, is there another instance running ?", config->pidfile); } else if (errno == EACCES) { errx(1, "PID file <%s> cannot be created due to lack of permissions", config->pidfile); } else if (fd < 0) { err(1, "Could not create PID file <%s>", config->pidfile); } snprintf(&buffer[0], sizeof(buffer), "%u\n", (unsigned)pid); r = write(fd, &buffer[0], strlen(buffer)); if (r < 0) { errx(1, "Could not write into PID file <%s>", config->pidfile); } close(fd); } } static void remove_pidfile(void) { struct configuration* config; config = (struct configuration*)shmem; if (strlen(config->pidfile) > 0) { if (unlink(config->pidfile)) { warn("Cannot remove PID file <%s>", config->pidfile); } } } static void shutdown_ports(void) { struct configuration* config; config = (struct configuration*)shmem; shutdown_io(); if (config->metrics > 0) { shutdown_metrics(); } if (config->management > 0) { shutdown_management(); } } pgagroal-1.6.0/uncrustify.cfg000066400000000000000000004165041456565230200162300ustar00rootroot00000000000000# Uncrustify-0.74.0 # # General options # # The type of line endings. # # Default: auto newlines = auto # lf/crlf/cr/auto # The original size of tabs in the input. # # Default: 8 input_tab_size = 3 # unsigned number # The size of tabs in the output (only used if align_with_tabs=true). # # Default: 8 output_tab_size = 3 # unsigned number # The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^). # # Default: 92 string_escape_char = 92 # unsigned number # Alternate string escape char (usually only used for Pawn). # Only works right before the quote char. string_escape_char2 = 0 # unsigned number # Replace tab characters found in string literals with the escape sequence \t # instead. string_replace_tab_chars = false # true/false # Allow interpreting '>=' and '>>=' as part of a template in code like # 'void f(list>=val);'. If true, 'assert(x<0 && y>=3)' will be broken. # Improvements to template detection may make this option obsolete. tok_split_gte = false # true/false # Disable formatting of NL_CONT ('\\n') ended lines (e.g. multi-line macros). disable_processing_nl_cont = false # true/false # Specify the marker used in comments to disable processing of part of the # file. # # Default: *INDENT-OFF* disable_processing_cmt = " *INDENT-OFF*" # string # Specify the marker used in comments to (re)enable processing in a file. # # Default: *INDENT-ON* enable_processing_cmt = " *INDENT-ON*" # string # Enable parsing of digraphs. enable_digraphs = false # true/false # Option to allow both disable_processing_cmt and enable_processing_cmt # strings, if specified, to be interpreted as ECMAScript regular expressions. # If true, a regex search will be performed within comments according to the # specified patterns in order to disable/enable processing. processing_cmt_as_regex = false # true/false # Add or remove the UTF-8 BOM (recommend 'remove'). utf8_bom = ignore # ignore/add/remove/force/not_defined # If the file contains bytes with values between 128 and 255, but is not # UTF-8, then output as UTF-8. utf8_byte = false # true/false # Force the output encoding to UTF-8. utf8_force = false # true/false # # Spacing options # # Add or remove space around non-assignment symbolic operators ('+', '/', '%', # '<<', and so forth). sp_arith = force # ignore/add/remove/force/not_defined # Add or remove space around arithmetic operators '+' and '-'. # # Overrides sp_arith. sp_arith_additive = force # ignore/add/remove/force/not_defined # Add or remove space around assignment operator '=', '+=', etc. sp_assign = force # ignore/add/remove/force/not_defined # Add or remove space around '=' in C++11 lambda capture specifications. # # Overrides sp_assign. sp_cpp_lambda_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after the capture specification of a C++11 lambda when # an argument list is present, as in '[] (int x){ ... }'. sp_cpp_lambda_square_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space after the capture specification of a C++11 lambda with # no argument list is present, as in '[] { ... }'. sp_cpp_lambda_square_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space after the opening parenthesis and before the closing # parenthesis of a argument list of a C++11 lambda, as in # '[]( int x ){ ... }'. sp_cpp_lambda_argument_list = ignore # ignore/add/remove/force/not_defined # Add or remove space after the argument list of a C++11 lambda, as in # '[](int x) { ... }'. sp_cpp_lambda_paren_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between a lambda body and its call operator of an # immediately invoked lambda, as in '[]( ... ){ ... } ( ... )'. sp_cpp_lambda_fparen = ignore # ignore/add/remove/force/not_defined # Add or remove space around assignment operator '=' in a prototype. # # If set to ignore, use sp_assign. sp_assign_default = ignore # ignore/add/remove/force/not_defined # Add or remove space before assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_before_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_after_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space in 'NS_ENUM ('. sp_enum_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space around assignment '=' in enum. sp_enum_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space before assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_before_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_after_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space around assignment ':' in enum. sp_enum_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space around preprocessor '##' concatenation operator. # # Default: add sp_pp_concat = add # ignore/add/remove/force/not_defined # Add or remove space after preprocessor '#' stringify operator. # Also affects the '#@' charizing operator. sp_pp_stringify = ignore # ignore/add/remove/force/not_defined # Add or remove space before preprocessor '#' stringify operator # as in '#define x(y) L#y'. sp_before_pp_stringify = ignore # ignore/add/remove/force/not_defined # Add or remove space around boolean operators '&&' and '||'. sp_bool = force # ignore/add/remove/force/not_defined # Add or remove space around compare operator '<', '>', '==', etc. sp_compare = force # ignore/add/remove/force/not_defined # Add or remove space inside '(' and ')'. sp_inside_paren = remove # ignore/add/remove/force/not_defined # Add or remove space between nested parentheses, i.e. '((' vs. ') )'. sp_paren_paren = remove # ignore/add/remove/force/not_defined # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('. sp_cparen_oparen = remove # ignore/add/remove/force/not_defined # Whether to balance spaces inside nested parentheses. sp_balance_nested_parens = false # true/false # Add or remove space between ')' and '{'. sp_paren_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between nested braces, i.e. '{{' vs. '{ {'. sp_brace_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space before pointer star '*'. sp_before_ptr_star = remove # ignore/add/remove/force/not_defined # Add or remove space before pointer star '*' that isn't followed by a # variable name. If set to ignore, sp_before_ptr_star is used instead. sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force/not_defined # Add or remove space between pointer stars '*', as in 'int ***a;'. sp_between_ptr_star = ignore # ignore/add/remove/force/not_defined # Add or remove space after pointer star '*', if followed by a word. # # Overrides sp_type_func. sp_after_ptr_star = force # ignore/add/remove/force/not_defined # Add or remove space after pointer caret '^', if followed by a word. sp_after_ptr_block_caret = ignore # ignore/add/remove/force/not_defined # Add or remove space after pointer star '*', if followed by a qualifier. sp_after_ptr_star_qualifier = ignore # ignore/add/remove/force/not_defined # Add or remove space after a pointer star '*', if followed by a function # prototype or function definition. # # Overrides sp_after_ptr_star and sp_type_func. sp_after_ptr_star_func = ignore # ignore/add/remove/force/not_defined # Add or remove space after a pointer star '*' in the trailing return of a # function prototype or function definition. sp_after_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined # Add or remove space between the pointer star '*' and the name of the variable # in a function pointer definition. sp_ptr_star_func_var = ignore # ignore/add/remove/force/not_defined # Add or remove space after a pointer star '*', if followed by an open # parenthesis, as in 'void* (*)()'. sp_ptr_star_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space before a pointer star '*', if followed by a function # prototype or function definition. sp_before_ptr_star_func = ignore # ignore/add/remove/force/not_defined # Add or remove space before a pointer star '*' in the trailing return of a # function prototype or function definition. sp_before_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined # Add or remove space before a reference sign '&'. sp_before_byref = ignore # ignore/add/remove/force/not_defined # Add or remove space before a reference sign '&' that isn't followed by a # variable name. If set to ignore, sp_before_byref is used instead. sp_before_unnamed_byref = ignore # ignore/add/remove/force/not_defined # Add or remove space after reference sign '&', if followed by a word. # # Overrides sp_type_func. sp_after_byref = ignore # ignore/add/remove/force/not_defined # Add or remove space after a reference sign '&', if followed by a function # prototype or function definition. # # Overrides sp_after_byref and sp_type_func. sp_after_byref_func = ignore # ignore/add/remove/force/not_defined # Add or remove space before a reference sign '&', if followed by a function # prototype or function definition. sp_before_byref_func = ignore # ignore/add/remove/force/not_defined # Add or remove space between type and word. In cases where total removal of # whitespace would be a syntax error, a value of 'remove' is treated the same # as 'force'. # # This also affects some other instances of space following a type that are # not covered by other options; for example, between the return type and # parenthesis of a function type template argument, between the type and # parenthesis of an array parameter, or between 'decltype(...)' and the # following word. # # Default: force sp_after_type = force # ignore/add/remove/force/not_defined # Add or remove space between 'decltype(...)' and word, # brace or function call. sp_after_decltype = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space before the parenthesis in the D constructs # 'template Foo(' and 'class Foo('. sp_before_template_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'template' and '<'. # If set to ignore, sp_before_angle is used. sp_template_angle = ignore # ignore/add/remove/force/not_defined # Add or remove space before '<'. sp_before_angle = ignore # ignore/add/remove/force/not_defined # Add or remove space inside '<' and '>'. sp_inside_angle = ignore # ignore/add/remove/force/not_defined # Add or remove space inside '<>'. sp_inside_angle_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space between '>' and ':'. sp_angle_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space after '>'. sp_after_angle = ignore # ignore/add/remove/force/not_defined # Add or remove space between '>' and '(' as found in 'new List(foo);'. sp_angle_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between '>' and '()' as found in 'new List();'. sp_angle_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space between '>' and a word as in 'List m;' or # 'template static ...'. sp_angle_word = ignore # ignore/add/remove/force/not_defined # Add or remove space between '>' and '>' in '>>' (template stuff). # # Default: add sp_angle_shift = add # ignore/add/remove/force/not_defined # (C++11) Permit removal of the space between '>>' in 'foo >'. Note # that sp_angle_shift cannot remove the space without this option. sp_permit_cpp11_shift = false # true/false # Add or remove space before '(' of control statements ('if', 'for', 'switch', # 'while', etc.). sp_before_sparen = force # ignore/add/remove/force/not_defined # Add or remove space inside '(' and ')' of control statements other than # 'for'. sp_inside_sparen = remove # ignore/add/remove/force/not_defined # Add or remove space after '(' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_open = ignore # ignore/add/remove/force/not_defined # Add or remove space before ')' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force/not_defined # Add or remove space inside '(' and ')' of 'for' statements. sp_inside_for = ignore # ignore/add/remove/force/not_defined # Add or remove space after '(' of 'for' statements. # # Overrides sp_inside_for. sp_inside_for_open = remove # ignore/add/remove/force/not_defined # Add or remove space before ')' of 'for' statements. # # Overrides sp_inside_for. sp_inside_for_close = remove # ignore/add/remove/force/not_defined # Add or remove space between '((' or '))' of control statements. sp_sparen_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space after ')' of control statements. sp_after_sparen = ignore # ignore/add/remove/force/not_defined # Add or remove space between ')' and '{' of control statements. sp_sparen_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'do' and '{'. sp_do_brace_open = ignore # ignore/add/remove/force/not_defined # Add or remove space between '}' and 'while'. sp_brace_close_while = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'while' and '('. Overrides sp_before_sparen. sp_while_paren_open = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space between 'invariant' and '('. sp_invariant_paren = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space after the ')' in 'invariant (C) c'. sp_after_invariant_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space before empty statement ';' on 'if', 'for' and 'while'. sp_special_semi = ignore # ignore/add/remove/force/not_defined # Add or remove space before ';'. # # Default: remove sp_before_semi = remove # ignore/add/remove/force/not_defined # Add or remove space before ';' in non-empty 'for' statements. sp_before_semi_for = ignore # ignore/add/remove/force/not_defined # Add or remove space before a semicolon of an empty left part of a for # statement, as in 'for ( ; ; )'. sp_before_semi_for_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space between the semicolons of an empty middle part of a for # statement, as in 'for ( ; ; )'. sp_between_semi_for_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space after ';', except when followed by a comment. # # Default: add sp_after_semi = add # ignore/add/remove/force/not_defined # Add or remove space after ';' in non-empty 'for' statements. # # Default: force sp_after_semi_for = force # ignore/add/remove/force/not_defined # Add or remove space after the final semicolon of an empty part of a for # statement, as in 'for ( ; ; )'. sp_after_semi_for_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space before '[' (except '[]'). sp_before_square = remove # ignore/add/remove/force/not_defined # Add or remove space before '[' for a variable definition. # # Default: remove sp_before_vardef_square = remove # ignore/add/remove/force/not_defined # Add or remove space before '[' for asm block. sp_before_square_asm_block = ignore # ignore/add/remove/force/not_defined # Add or remove space before '[]'. sp_before_squares = remove # ignore/add/remove/force/not_defined # Add or remove space before C++17 structured bindings. sp_cpp_before_struct_binding = ignore # ignore/add/remove/force/not_defined # Add or remove space inside a non-empty '[' and ']'. sp_inside_square = remove # ignore/add/remove/force/not_defined # Add or remove space inside '[]'. sp_inside_square_empty = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and # ']'. If set to ignore, sp_inside_square is used. sp_inside_square_oc_array = ignore # ignore/add/remove/force/not_defined # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'. sp_after_comma = force # ignore/add/remove/force/not_defined # Add or remove space before ',', i.e. 'a,b' vs. 'a ,b'. # # Default: remove sp_before_comma = remove # ignore/add/remove/force/not_defined # (C#) Add or remove space between ',' and ']' in multidimensional array type # like 'int[,,]'. sp_after_mdatype_commas = ignore # ignore/add/remove/force/not_defined # (C#) Add or remove space between '[' and ',' in multidimensional array type # like 'int[,,]'. sp_before_mdatype_commas = remove # ignore/add/remove/force/not_defined # (C#) Add or remove space between ',' in multidimensional array type # like 'int[,,]'. sp_between_mdatype_commas = ignore # ignore/add/remove/force/not_defined # Add or remove space between an open parenthesis and comma, # i.e. '(,' vs. '( ,'. # # Default: force sp_paren_comma = force # ignore/add/remove/force/not_defined # Add or remove space after the variadic '...' when preceded by a # non-punctuator. # The value REMOVE will be overriden with FORCE sp_after_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space before the variadic '...' when preceded by a # non-punctuator. # The value REMOVE will be overriden with FORCE sp_before_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between a type and '...'. sp_type_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between a '*' and '...'. sp_ptr_type_ellipsis = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space between a type and '?'. sp_type_question = ignore # ignore/add/remove/force/not_defined # Add or remove space between ')' and '...'. sp_paren_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between '&&' and '...'. sp_byref_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between ')' and a qualifier such as 'const'. sp_paren_qualifier = ignore # ignore/add/remove/force/not_defined # Add or remove space between ')' and 'noexcept'. sp_paren_noexcept = ignore # ignore/add/remove/force/not_defined # Add or remove space after class ':'. sp_after_class_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space before class ':'. sp_before_class_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space after class constructor ':'. # # Default: add sp_after_constr_colon = add # ignore/add/remove/force/not_defined # Add or remove space before class constructor ':'. # # Default: add sp_before_constr_colon = add # ignore/add/remove/force/not_defined # Add or remove space before case ':'. # # Default: remove sp_before_case_colon = remove # ignore/add/remove/force/not_defined # Add or remove space between 'operator' and operator sign. sp_after_operator = ignore # ignore/add/remove/force/not_defined # Add or remove space between the operator symbol and the open parenthesis, as # in 'operator ++('. sp_after_operator_sym = ignore # ignore/add/remove/force/not_defined # Overrides sp_after_operator_sym when the operator has no arguments, as in # 'operator *()'. sp_after_operator_sym_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or # '(int)a' vs. '(int) a'. sp_after_cast = ignore # ignore/add/remove/force/not_defined # Add or remove spaces inside cast parentheses. sp_inside_paren_cast = ignore # ignore/add/remove/force/not_defined # Add or remove space between the type and open parenthesis in a C++ cast, # i.e. 'int(exp)' vs. 'int (exp)'. sp_cpp_cast_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'sizeof' and '('. sp_sizeof_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'sizeof' and '...'. sp_sizeof_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'sizeof...' and '('. sp_sizeof_ellipsis_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between '...' and a parameter pack. sp_ellipsis_parameter_pack = ignore # ignore/add/remove/force/not_defined # Add or remove space between a parameter pack and '...'. sp_parameter_pack_ellipsis = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'decltype' and '('. sp_decltype_paren = ignore # ignore/add/remove/force/not_defined # (Pawn) Add or remove space after the tag keyword. sp_after_tag = ignore # ignore/add/remove/force/not_defined # Add or remove space inside enum '{' and '}'. sp_inside_braces_enum = ignore # ignore/add/remove/force/not_defined # Add or remove space inside struct/union '{' and '}'. sp_inside_braces_struct = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space inside Objective-C boxed dictionary '{' and '}' sp_inside_braces_oc_dict = ignore # ignore/add/remove/force/not_defined # Add or remove space after open brace in an unnamed temporary # direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore. sp_after_type_brace_init_lst_open = ignore # ignore/add/remove/force/not_defined # Add or remove space before close brace in an unnamed temporary # direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore. sp_before_type_brace_init_lst_close = ignore # ignore/add/remove/force/not_defined # Add or remove space inside an unnamed temporary direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore # works only if sp_before_type_brace_init_lst_close is set to ignore. sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force/not_defined # Add or remove space inside '{' and '}'. sp_inside_braces = ignore # ignore/add/remove/force/not_defined # Add or remove space inside '{}'. sp_inside_braces_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space around trailing return operator '->'. sp_trailing_return = ignore # ignore/add/remove/force/not_defined # Add or remove space between return type and function name. A minimum of 1 # is forced except for pointer return types. sp_type_func = ignore # ignore/add/remove/force/not_defined # Add or remove space between type and open brace of an unnamed temporary # direct-list-initialization. sp_type_brace_init_lst = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '(' on function declaration. sp_func_proto_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '()' on function declaration # without parameters. sp_func_proto_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '(' with a typedef specifier. sp_func_type_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between alias name and '(' of a non-pointer function type typedef. sp_func_def_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '()' on function definition # without parameters. sp_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space inside empty function '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. sp_inside_fparens = remove # ignore/add/remove/force/not_defined # Add or remove space inside function '(' and ')'. sp_inside_fparen = remove # ignore/add/remove/force/not_defined # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. sp_inside_tparen = remove # ignore/add/remove/force/not_defined # Add or remove space between the ')' and '(' in a function type, as in # 'void (*x)(...)'. sp_after_tparen_close = remove # ignore/add/remove/force/not_defined # Add or remove space between ']' and '(' when part of a function call. sp_square_fparen = remove # ignore/add/remove/force/not_defined # Add or remove space between ')' and '{' of function. sp_fparen_brace = remove # ignore/add/remove/force/not_defined # Add or remove space between ')' and '{' of a function call in object # initialization. # # Overrides sp_fparen_brace. sp_fparen_brace_initializer = ignore # ignore/add/remove/force/not_defined # (Java) Add or remove space between ')' and '{{' of double brace initializer. sp_fparen_dbrace = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '(' on function calls. sp_func_call_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between function name and '()' on function calls without # parameters. If set to ignore (the default), sp_func_call_paren is used. sp_func_call_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space between the user function name and '(' on function # calls. You need to set a keyword to be a user function in the config file, # like: # set func_call_user tr _ i18n sp_func_call_user_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space inside user function '(' and ')'. sp_func_call_user_inside_fparen = ignore # ignore/add/remove/force/not_defined # Add or remove space between nested parentheses with user functions, # i.e. '((' vs. '( ('. sp_func_call_user_paren_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between a constructor/destructor and the open # parenthesis. sp_func_class_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between a constructor without parameters or destructor # and '()'. sp_func_class_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove space after 'return'. # # Default: force sp_return = force # ignore/add/remove/force/not_defined # Add or remove space between 'return' and '('. sp_return_paren = force # ignore/add/remove/force/not_defined # Add or remove space between 'return' and '{'. sp_return_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between '__attribute__' and '('. sp_attribute_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'defined' and '(' in '#if defined (FOO)'. sp_defined_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'throw' and '(' in 'throw (something)'. sp_throw_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'throw' and anything other than '(' as in # '@throw [...];'. sp_after_throw = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'catch' and '(' in 'catch (something) { }'. # If set to ignore, sp_before_sparen is used. sp_catch_paren = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between '@catch' and '(' # in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. sp_oc_catch_paren = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before Objective-C protocol list # as in '@protocol Protocol' or '@interface MyClass : NSObject'. sp_before_oc_proto_list = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between class name and '(' # in '@interface className(categoryName):BaseClass' sp_oc_classname_paren = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space between 'version' and '(' # in 'version (something) { }'. If set to ignore, sp_before_sparen is used. sp_version_paren = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space between 'scope' and '(' # in 'scope (something) { }'. If set to ignore, sp_before_sparen is used. sp_scope_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'super' and '(' in 'super (something)'. # # Default: remove sp_super_paren = remove # ignore/add/remove/force/not_defined # Add or remove space between 'this' and '(' in 'this (something)'. # # Default: remove sp_this_paren = remove # ignore/add/remove/force/not_defined # Add or remove space between a macro name and its definition. sp_macro = ignore # ignore/add/remove/force/not_defined # Add or remove space between a macro function ')' and its definition. sp_macro_func = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'else' and '{' if on the same line. sp_else_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between '}' and 'else' if on the same line. sp_brace_else = ignore # ignore/add/remove/force/not_defined # Add or remove space between '}' and the name of a typedef on the same line. sp_brace_typedef = ignore # ignore/add/remove/force/not_defined # Add or remove space before the '{' of a 'catch' statement, if the '{' and # 'catch' are on the same line, as in 'catch (decl) {'. sp_catch_brace = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before the '{' of a '@catch' statement, if the '{' # and '@catch' are on the same line, as in '@catch (decl) {'. # If set to ignore, sp_catch_brace is used. sp_oc_catch_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between '}' and 'catch' if on the same line. sp_brace_catch = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between '}' and '@catch' if on the same line. # If set to ignore, sp_brace_catch is used. sp_oc_brace_catch = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'finally' and '{' if on the same line. sp_finally_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between '}' and 'finally' if on the same line. sp_brace_finally = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'try' and '{' if on the same line. sp_try_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between get/set and '{' if on the same line. sp_getset_brace = ignore # ignore/add/remove/force/not_defined # Add or remove space between a variable and '{' for C++ uniform # initialization. sp_word_brace_init_lst = ignore # ignore/add/remove/force/not_defined # Add or remove space between a variable and '{' for a namespace. # # Default: add sp_word_brace_ns = add # ignore/add/remove/force/not_defined # Add or remove space before the '::' operator. sp_before_dc = ignore # ignore/add/remove/force/not_defined # Add or remove space after the '::' operator. sp_after_dc = ignore # ignore/add/remove/force/not_defined # (D) Add or remove around the D named array initializer ':' operator. sp_d_array_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space after the '!' (not) unary operator. # # Default: remove sp_not = remove # ignore/add/remove/force/not_defined # Add or remove space after the '~' (invert) unary operator. # # Default: remove sp_inv = remove # ignore/add/remove/force/not_defined # Add or remove space after the '&' (address-of) unary operator. This does not # affect the spacing after a '&' that is part of a type. # # Default: remove sp_addr = remove # ignore/add/remove/force/not_defined # Add or remove space around the '.' or '->' operators. # # Default: remove sp_member = remove # ignore/add/remove/force/not_defined # Add or remove space after the '*' (dereference) unary operator. This does # not affect the spacing after a '*' that is part of a type. # # Default: remove sp_deref = remove # ignore/add/remove/force/not_defined # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. # # Default: remove sp_sign = remove # ignore/add/remove/force/not_defined # Add or remove space between '++' and '--' the word to which it is being # applied, as in '(--x)' or 'y++;'. # # Default: remove sp_incdec = remove # ignore/add/remove/force/not_defined # Add or remove space before a backslash-newline at the end of a line. # # Default: add sp_before_nl_cont = add # ignore/add/remove/force/not_defined # (OC) Add or remove space after the scope '+' or '-', as in '-(void) foo;' # or '+(int) bar;'. sp_after_oc_scope = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after the colon in message specs, # i.e. '-(int) f:(int) x;' vs. '-(int) f: (int) x;'. sp_after_oc_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before the colon in message specs, # i.e. '-(int) f: (int) x;' vs. '-(int) f : (int) x;'. sp_before_oc_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_after_oc_dict_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_before_oc_dict_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue: 1];'. sp_after_send_oc_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue :1];'. sp_before_send_oc_colon = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after the (type) in message specs, # i.e. '-(int)f: (int) x;' vs. '-(int)f: (int)x;'. sp_after_oc_type = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after the first (type) in message specs, # i.e. '-(int) f:(int)x;' vs. '-(int)f:(int)x;'. sp_after_oc_return_type = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between '@selector' and '(', # i.e. '@selector(msgName)' vs. '@selector (msgName)'. # Also applies to '@protocol()' constructs. sp_after_oc_at_sel = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between '@selector(x)' and the following word, # i.e. '@selector(foo) a:' vs. '@selector(foo)a:'. sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space inside '@selector' parentheses, # i.e. '@selector(foo)' vs. '@selector( foo )'. # Also applies to '@protocol()' constructs. sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space before a block pointer caret, # i.e. '^int (int arg){...}' vs. ' ^int (int arg){...}'. sp_before_oc_block_caret = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after a block pointer caret, # i.e. '^int (int arg){...}' vs. '^ int (int arg){...}'. sp_after_oc_block_caret = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between the receiver and selector in a message, # as in '[receiver selector ...]'. sp_after_oc_msg_receiver = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space after '@property'. sp_after_oc_property = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove space between '@synchronized' and the open parenthesis, # i.e. '@synchronized(foo)' vs. '@synchronized (foo)'. sp_after_oc_synchronized = ignore # ignore/add/remove/force/not_defined # Add or remove space around the ':' in 'b ? t : f'. sp_cond_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space before the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_before = ignore # ignore/add/remove/force/not_defined # Add or remove space after the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_after = ignore # ignore/add/remove/force/not_defined # Add or remove space around the '?' in 'b ? t : f'. sp_cond_question = ignore # ignore/add/remove/force/not_defined # Add or remove space before the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_before = ignore # ignore/add/remove/force/not_defined # Add or remove space after the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_after = ignore # ignore/add/remove/force/not_defined # In the abbreviated ternary form '(a ?: b)', add or remove space between '?' # and ':'. # # Overrides all other sp_cond_* options. sp_cond_ternary_short = ignore # ignore/add/remove/force/not_defined # Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make # sense here. sp_case_label = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space around the D '..' operator. sp_range = ignore # ignore/add/remove/force/not_defined # Add or remove space after ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_after_for_colon = ignore # ignore/add/remove/force/not_defined # Add or remove space before ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_before_for_colon = ignore # ignore/add/remove/force/not_defined # (D) Add or remove space between 'extern' and '(' as in 'extern (C)'. sp_extern_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space after the opening of a C++ comment, as in '// A'. sp_cmt_cpp_start = ignore # ignore/add/remove/force/not_defined # Add or remove space in a C++ region marker comment, as in '// BEGIN'. # A region marker is defined as a comment which is not preceded by other text # (i.e. the comment is the first non-whitespace on the line), and which starts # with either 'BEGIN' or 'END'. # # Overrides sp_cmt_cpp_start. sp_cmt_cpp_region = ignore # ignore/add/remove/force/not_defined # If true, space added with sp_cmt_cpp_start will be added after Doxygen # sequences like '///', '///<', '//!' and '//!<'. sp_cmt_cpp_doxygen = false # true/false # If true, space added with sp_cmt_cpp_start will be added after Qt translator # or meta-data comments like '//:', '//=', and '//~'. sp_cmt_cpp_qttr = false # true/false # Add or remove space between #else or #endif and a trailing comment. sp_endif_cmt = ignore # ignore/add/remove/force/not_defined # Add or remove space after 'new', 'delete' and 'delete[]'. sp_after_new = ignore # ignore/add/remove/force/not_defined # Add or remove space between 'new' and '(' in 'new()'. sp_between_new_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space between ')' and type in 'new(foo) BAR'. sp_after_newop_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space inside parenthesis of the new operator # as in 'new(foo) BAR'. sp_inside_newop_paren = ignore # ignore/add/remove/force/not_defined # Add or remove space after the open parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_open = ignore # ignore/add/remove/force/not_defined # Add or remove space before the close parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_close = ignore # ignore/add/remove/force/not_defined # Add or remove space before a trailing comment. sp_before_tr_cmt = ignore # ignore/add/remove/force/not_defined # Number of spaces before a trailing comment. sp_num_before_tr_cmt = 0 # unsigned number # Add or remove space before an embedded comment. # # Default: force sp_before_emb_cmt = force # ignore/add/remove/force/not_defined # Number of spaces before an embedded comment. # # Default: 1 sp_num_before_emb_cmt = 1 # unsigned number # Add or remove space after an embedded comment. # # Default: force sp_after_emb_cmt = force # ignore/add/remove/force/not_defined # Number of spaces after an embedded comment. # # Default: 1 sp_num_after_emb_cmt = 1 # unsigned number # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force/not_defined # If true, vbrace tokens are dropped to the previous token and skipped. sp_skip_vbrace_tokens = false # true/false # Add or remove space after 'noexcept'. sp_after_noexcept = ignore # ignore/add/remove/force/not_defined # Add or remove space after '_'. sp_vala_after_translation = ignore # ignore/add/remove/force/not_defined # If true, a is inserted after #define. force_tab_after_define = false # true/false # # Indenting options # # The number of columns to indent per level. Usually 2, 3, 4, or 8. # # Default: 8 indent_columns = 3 # unsigned number # The continuation indent. If non-zero, this overrides the indent of '(', '[' # and '=' continuation indents. Negative values are OK; negative value is # absolute and not increased for each '(' or '[' level. # # For FreeBSD, this is set to 4. indent_continue = 0 # number # The continuation indent, only for class header line(s). If non-zero, this # overrides the indent of 'class' continuation indents. indent_continue_class_head = 0 # unsigned number # Whether to indent empty lines (i.e. lines which contain only spaces before # the newline character). indent_single_newlines = false # true/false # The continuation indent for func_*_param if they are true. If non-zero, this # overrides the indent. indent_param = 0 # unsigned number # How to use tabs when indenting code. # # 0: Spaces only # 1: Indent with tabs to brace level, align with spaces (default) # 2: Indent and align with tabs, using spaces when not on a tabstop # # Default: 1 indent_with_tabs = 0 # unsigned number # Whether to indent comments that are not at a brace level with tabs on a # tabstop. Requires indent_with_tabs=2. If false, will use spaces. indent_cmt_with_tabs = false # true/false # Whether to indent strings broken by '\' so that they line up. indent_align_string = false # true/false # The number of spaces to indent multi-line XML strings. # Requires indent_align_string=true. indent_xml_string = 0 # unsigned number # Spaces to indent '{' from level. indent_brace = 0 # unsigned number # Whether braces are indented to the body level. indent_braces = false # true/false # Whether to disable indenting function braces if indent_braces=true. indent_braces_no_func = false # true/false # Whether to disable indenting class braces if indent_braces=true. indent_braces_no_class = false # true/false # Whether to disable indenting struct braces if indent_braces=true. indent_braces_no_struct = false # true/false # Whether to indent based on the size of the brace parent, # i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. indent_brace_parent = false # true/false # Whether to indent based on the open parenthesis instead of the open brace # in '({\n'. indent_paren_open_brace = false # true/false # (C#) Whether to indent the brace of a C# delegate by another level. indent_cs_delegate_brace = false # true/false # (C#) Whether to indent a C# delegate (to handle delegates with no brace) by # another level. indent_cs_delegate_body = false # true/false # Whether to indent the body of a 'namespace'. indent_namespace = false # true/false # Whether to indent only the first namespace, and not any nested namespaces. # Requires indent_namespace=true. indent_namespace_single_indent = false # true/false # The number of spaces to indent a namespace block. # If set to zero, use the value indent_columns indent_namespace_level = 0 # unsigned number # If the body of the namespace is longer than this number, it won't be # indented. Requires indent_namespace=true. 0 means no limit. indent_namespace_limit = 0 # unsigned number # Whether the 'extern "C"' body is indented. indent_extern = false # true/false # Whether the 'class' body is indented. indent_class = false # true/false # Additional indent before the leading base class colon. # Negative values decrease indent down to the first column. # Requires a newline break before colon (see pos_class_colon # and nl_class_colon) indent_before_class_colon = 0 # number # Whether to indent the stuff after a leading base class colon. indent_class_colon = false # true/false # Whether to indent based on a class colon instead of the stuff after the # colon. Requires indent_class_colon=true. indent_class_on_colon = false # true/false # Whether to indent the stuff after a leading class initializer colon. indent_constr_colon = false # true/false # Virtual indent from the ':' for leading member initializers. # # Default: 2 indent_ctor_init_leading = 2 # unsigned number # Virtual indent from the ':' for following member initializers. # # Default: 2 indent_ctor_init_following = 2 # unsigned number # Additional indent for constructor initializer list. # Negative values decrease indent down to the first column. indent_ctor_init = 0 # number # Whether to indent 'if' following 'else' as a new block under the 'else'. # If false, 'else\nif' is treated as 'else if' for indenting purposes. indent_else_if = false # true/false # Amount to indent variable declarations after a open brace. # # <0: Relative # >=0: Absolute indent_var_def_blk = 0 # number # Whether to indent continued variable declarations instead of aligning. indent_var_def_cont = false # true/false # Whether to indent continued shift expressions ('<<' and '>>') instead of # aligning. Set align_left_shift=false when enabling this. indent_shift = false # true/false # Whether to force indentation of function definitions to start in column 1. indent_func_def_force_col1 = false # true/false # Whether to indent continued function call parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_call_param = false # true/false # Whether to indent continued function definition parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_def_param = false # true/false # for function definitions, only if indent_func_def_param is false # Allows to align params when appropriate and indent them when not # behave as if it was true if paren position is more than this value # if paren position is more than the option value indent_func_def_param_paren_pos_threshold = 0 # unsigned number # Whether to indent continued function call prototype one indent level, # rather than aligning parameters under the open parenthesis. indent_func_proto_param = false # true/false # Whether to indent continued function call declaration one indent level, # rather than aligning parameters under the open parenthesis. indent_func_class_param = false # true/false # Whether to indent continued class variable constructors one indent level, # rather than aligning parameters under the open parenthesis. indent_func_ctor_var_param = false # true/false # Whether to indent continued template parameter list one indent level, # rather than aligning parameters under the open parenthesis. indent_template_param = false # true/false # Double the indent for indent_func_xxx_param options. # Use both values of the options indent_columns and indent_param. indent_func_param_double = false # true/false # Indentation column for standalone 'const' qualifier on a function # prototype. indent_func_const = 0 # unsigned number # Indentation column for standalone 'throw' qualifier on a function # prototype. indent_func_throw = 0 # unsigned number # How to indent within a macro followed by a brace on the same line # This allows reducing the indent in macros that have (for example) # `do { ... } while (0)` blocks bracketing them. # # true: add an indent for the brace on the same line as the macro # false: do not add an indent for the brace on the same line as the macro # # Default: true indent_macro_brace = true # true/false # The number of spaces to indent a continued '->' or '.'. # Usually set to 0, 1, or indent_columns. indent_member = 0 # unsigned number # Whether lines broken at '.' or '->' should be indented by a single indent. # The indent_member option will not be effective if this is set to true. indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. indent_single_line_comments_before = 0 # unsigned number # Spaces to indent single line ('//') comments on lines after code. indent_single_line_comments_after = 0 # unsigned number # When opening a paren for a control statement (if, for, while, etc), increase # the indent level by this value. Negative values decrease the indent level. indent_sparen_extra = 0 # number # Whether to indent trailing single line ('//') comments relative to the code # instead of trying to keep the same absolute column. indent_relative_single_line_comments = false # true/false # Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. # It might be wise to choose the same value for the option indent_case_brace. indent_switch_case = 3 # unsigned number # Spaces to indent the body of a 'switch' before any 'case'. # Usually the same as indent_columns or indent_switch_case. indent_switch_body = 0 # unsigned number # Spaces to indent '{' from 'case'. By default, the brace will appear under # the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. # It might be wise to choose the same value for the option indent_switch_case. indent_case_brace = 0 # number # indent 'break' with 'case' from 'switch'. indent_switch_break_with_case = false # true/false # Whether to indent preprocessor statements inside of switch statements. # # Default: true indent_switch_pp = true # true/false # Spaces to shift the 'case' line, without affecting any other lines. # Usually 0. indent_case_shift = 0 # unsigned number # Whether to align comments before 'case' with the 'case'. # # Default: true indent_case_comment = true # true/false # Whether to indent comments not found in first column. # # Default: true indent_comment = true # true/false # Whether to indent comments found in first column. indent_col1_comment = false # true/false # Whether to indent multi string literal in first column. indent_col1_multi_string_literal = false # true/false # Align comments on adjacent lines that are this many columns apart or less. # # Default: 3 indent_comment_align_thresh = 3 # unsigned number # Whether to ignore indent for goto labels. indent_ignore_label = false # true/false # How to indent goto labels. Requires indent_ignore_label=false. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_label = 1 # number # How to indent access specifiers that are followed by a # colon. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_access_spec = 1 # number # Whether to indent the code after an access specifier by one level. # If true, this option forces 'indent_access_spec=0'. indent_access_spec_body = false # true/false # If an open parenthesis is followed by a newline, whether to indent the next # line so that it lines up after the open parenthesis (not recommended). indent_paren_nl = false # true/false # How to indent a close parenthesis after a newline. # # 0: Indent to body level (default) # 1: Align under the open parenthesis # 2: Indent to the brace level indent_paren_close = 0 # unsigned number # Whether to indent the open parenthesis of a function definition, # if the parenthesis is on its own line. indent_paren_after_func_def = false # true/false # Whether to indent the open parenthesis of a function declaration, # if the parenthesis is on its own line. indent_paren_after_func_decl = false # true/false # Whether to indent the open parenthesis of a function call, # if the parenthesis is on its own line. indent_paren_after_func_call = false # true/false # Whether to indent a comma when inside a brace. # If true, aligns under the open brace. indent_comma_brace = false # true/false # Whether to indent a comma when inside a parenthesis. # If true, aligns under the open parenthesis. indent_comma_paren = false # true/false # Whether to indent a Boolean operator when inside a parenthesis. # If true, aligns under the open parenthesis. indent_bool_paren = false # true/false # Whether to indent a semicolon when inside a for parenthesis. # If true, aligns under the open for parenthesis. indent_semicolon_for_paren = false # true/false # Whether to align the first expression to following ones # if indent_bool_paren=true. indent_first_bool_expr = false # true/false # Whether to align the first expression to following ones # if indent_semicolon_for_paren=true. indent_first_for_expr = false # true/false # If an open square is followed by a newline, whether to indent the next line # so that it lines up after the open square (not recommended). indent_square_nl = false # true/false # (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies. indent_preserve_sql = false # true/false # Whether to align continued statements at the '='. If false or if the '=' is # followed by a newline, the next line is indent one tab. # # Default: true indent_align_assign = true # true/false # If true, the indentation of the chunks after a '=' sequence will be set at # LHS token indentation column before '='. indent_off_after_assign = false # true/false # Whether to align continued statements at the '('. If false or the '(' is # followed by a newline, the next line indent is one tab. # # Default: true indent_align_paren = true # true/false # (OC) Whether to indent Objective-C code inside message selectors. indent_oc_inside_msg_sel = false # true/false # (OC) Whether to indent Objective-C blocks at brace level instead of usual # rules. indent_oc_block = false # true/false # (OC) Indent for Objective-C blocks in a message relative to the parameter # name. # # =0: Use indent_oc_block rules # >0: Use specified number of spaces to indent indent_oc_block_msg = 0 # unsigned number # (OC) Minimum indent for subsequent parameters indent_oc_msg_colon = 0 # unsigned number # (OC) Whether to prioritize aligning with initial colon (and stripping spaces # from lines, if necessary). # # Default: true indent_oc_msg_prioritize_first_colon = true # true/false # (OC) Whether to indent blocks the way that Xcode does by default # (from the keyword if the parameter is on its own line; otherwise, from the # previous indentation level). Requires indent_oc_block_msg=true. indent_oc_block_msg_xcode_style = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a # message keyword. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_keyword = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a message # colon. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_colon = false # true/false # (OC) Whether to indent blocks from where the block caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_caret = false # true/false # (OC) Whether to indent blocks from where the brace caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_brace = false # true/false # When indenting after virtual brace open and newline add further spaces to # reach this minimum indent. indent_min_vbrace_open = 0 # unsigned number # Whether to add further spaces after regular indent to reach next tabstop # when indenting after virtual brace open and newline. indent_vbrace_open_on_tabstop = false # true/false # How to indent after a brace followed by another token (not a newline). # true: indent all contained lines to match the token # false: indent all contained lines to match the brace # # Default: true indent_token_after_brace = true # true/false # Whether to indent the body of a C++11 lambda. indent_cpp_lambda_body = false # true/false # How to indent compound literals that are being returned. # true: add both the indent from return & the compound literal open brace # (i.e. 2 indent levels) # false: only indent 1 level, don't add the indent for the open brace, only # add the indent for the return. # # Default: true indent_compound_literal_return = true # true/false # (C#) Whether to indent a 'using' block if no braces are used. # # Default: true indent_using_block = true # true/false # How to indent the continuation of ternary operator. # # 0: Off (default) # 1: When the `if_false` is a continuation, indent it under `if_false` # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number # Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false # If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. indent_off_after_return = false # true/false # If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. indent_off_after_return_new = false # true/false # If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token. indent_single_after_return = false # true/false # Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they # have their own indentation). indent_ignore_asm_block = false # true/false # Don't indent the close parenthesis of a function definition, # if the parenthesis is on its own line. donot_indent_func_def_close_paren = false # true/false # # Newline adding and removing options # # Whether to collapse empty blocks between '{' and '}'. # If true, overrides nl_inside_empty_func nl_collapse_empty_body = false # true/false # Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'. nl_assign_leave_one_liners = false # true/false # Don't split one-line braced statements inside a 'class xx { }' body. nl_class_leave_one_liners = false # true/false # Don't split one-line enums, as in 'enum foo { BAR = 15 };' nl_enum_leave_one_liners = false # true/false # Don't split one-line get or set functions. nl_getset_leave_one_liners = false # true/false # (C#) Don't split one-line property get or set functions. nl_cs_property_leave_one_liners = false # true/false # Don't split one-line function definitions, as in 'int foo() { return 0; }'. # might modify nl_func_type_name nl_func_leave_one_liners = false # true/false # Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. nl_cpp_lambda_leave_one_liners = false # true/false # Don't split one-line if/else statements, as in 'if(...) b++;'. nl_if_leave_one_liners = false # true/false # Don't split one-line while statements, as in 'while(...) b++;'. nl_while_leave_one_liners = false # true/false # Don't split one-line do statements, as in 'do { b++; } while(...);'. nl_do_leave_one_liners = false # true/false # Don't split one-line for statements, as in 'for(...) b++;'. nl_for_leave_one_liners = false # true/false # (OC) Don't split one-line Objective-C messages. nl_oc_msg_leave_one_liner = false # true/false # (OC) Add or remove newline between method declaration and '{'. nl_oc_mdef_brace = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove newline between Objective-C block signature and '{'. nl_oc_block_brace = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove blank line before '@interface' statement. nl_oc_before_interface = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove blank line before '@implementation' statement. nl_oc_before_implementation = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove blank line before '@end' statement. nl_oc_before_end = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove newline between '@interface' and '{'. nl_oc_interface_brace = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove newline between '@implementation' and '{'. nl_oc_implementation_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newlines at the start of the file. nl_start_of_file = ignore # ignore/add/remove/force/not_defined # The minimum number of newlines at the start of the file (only used if # nl_start_of_file is 'add' or 'force'). nl_start_of_file_min = 0 # unsigned number # Add or remove newline at the end of the file. nl_end_of_file = ignore # ignore/add/remove/force/not_defined # The minimum number of newlines at the end of the file (only used if # nl_end_of_file is 'add' or 'force'). nl_end_of_file_min = 0 # unsigned number # Add or remove newline between '=' and '{'. nl_assign_brace = ignore # ignore/add/remove/force/not_defined # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '[]' and '{'. nl_tsquare_brace = ignore # ignore/add/remove/force/not_defined # (D) Add or remove newline after '= ['. Will also affect the newline before # the ']'. nl_after_square_assign = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function call's ')' and '{', as in # 'list_for_each(item, &list) { }'. nl_fcall_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'enum' and '{'. nl_enum_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'enum' and 'class'. nl_enum_class = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'enum class' and the identifier. nl_enum_class_identifier = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'enum class' type and ':'. nl_enum_identifier_colon = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'enum class identifier :' and type. nl_enum_colon_type = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'struct and '{'. nl_struct_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between 'union' and '{'. nl_union_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between 'if' and '{'. nl_if_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between '}' and 'else'. nl_brace_else = add # ignore/add/remove/force/not_defined # Add or remove newline between 'else if' and '{'. If set to ignore, # nl_if_brace is used instead. nl_elseif_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between 'else' and '{'. nl_else_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between 'else' and 'if'. nl_else_if = remove # ignore/add/remove/force/not_defined # Add or remove newline before '{' opening brace nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force/not_defined # Add or remove newline before 'if'/'else if' closing parenthesis. nl_before_if_closing_paren = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '}' and 'finally'. nl_brace_finally = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'finally' and '{'. nl_finally_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'try' and '{'. nl_try_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between get/set and '{'. nl_getset_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'for' and '{'. nl_for_brace = add # ignore/add/remove/force/not_defined # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) {'. nl_catch_brace = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove newline before the '{' of a '@catch' statement, as in # '@catch (decl) {'. If set to ignore, nl_catch_brace is used. nl_oc_catch_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '}' and 'catch'. nl_brace_catch = ignore # ignore/add/remove/force/not_defined # (OC) Add or remove newline between '}' and '@catch'. If set to ignore, # nl_brace_catch is used. nl_oc_brace_catch = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '}' and ']'. nl_brace_square = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '}' and ')' in a function invocation. nl_brace_fparen = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'while' and '{'. nl_while_brace = add # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'unittest' and '{'. nl_unittest_brace = ignore # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'version (x)' and '{'. nl_version_brace = ignore # ignore/add/remove/force/not_defined # (C#) Add or remove newline between 'using' and '{'. nl_using_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between two open or close braces. Due to general # newline/brace handling, REMOVE may not work. nl_brace_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'do' and '{'. nl_do_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between '}' and 'while' of 'do' statement. nl_brace_while = add # ignore/add/remove/force/not_defined # Add or remove newline between 'switch' and '{'. nl_switch_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = ignore # ignore/add/remove/force/not_defined # Add a newline between ')' and '{' if the ')' is on a different line than the # if/for/etc. # # Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and # nl_catch_brace. nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines nl_multi_line_sparen_open = ignore # ignore/add/remove/force/not_defined # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. nl_multi_line_sparen_close = ignore # ignore/add/remove/force/not_defined # Force a newline in a define after the macro name for multi-line defines. nl_multi_line_define = false # true/false # Whether to add a newline before 'case', and a blank line before a 'case' # statement that follows a ';' or '}'. nl_before_case = false # true/false # Whether to add a newline after a 'case' statement. nl_after_case = false # true/false # Add or remove newline between a case ':' and '{'. # # Overrides nl_after_case. nl_case_colon_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between ')' and 'throw'. nl_before_throw = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'namespace' and '{'. nl_namespace_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template class. nl_template_class = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template class declaration. # # Overrides nl_template_class. nl_template_class_decl = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<>' of a specialized class declaration. # # Overrides nl_template_class_decl. nl_template_class_decl_special = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template class definition. # # Overrides nl_template_class. nl_template_class_def = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<>' of a specialized class definition. # # Overrides nl_template_class_def. nl_template_class_def_special = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template function. nl_template_func = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template function # declaration. # # Overrides nl_template_func. nl_template_func_decl = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<>' of a specialized function # declaration. # # Overrides nl_template_func_decl. nl_template_func_decl_special = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template function # definition. # # Overrides nl_template_func. nl_template_func_def = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<>' of a specialized function # definition. # # Overrides nl_template_func_def. nl_template_func_def_special = ignore # ignore/add/remove/force/not_defined # Add or remove newline after 'template<...>' of a template variable. nl_template_var = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'template<...>' and 'using' of a templated # type alias. nl_template_using = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'class' and '{'. nl_class_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline before or after (depending on pos_class_comma, # may not be IGNORE) each',' in the base class list. nl_class_init_args = ignore # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in the constructor member # initialization. Related to nl_constr_colon, pos_constr_colon and # pos_constr_comma. nl_constr_init_args = ignore # ignore/add/remove/force/not_defined # Add or remove newline before first element, after comma, and after last # element, in 'enum'. nl_enum_own_lines = ignore # ignore/add/remove/force/not_defined # Add or remove newline between return type and function name in a function # definition. # might be modified by nl_func_leave_one_liners nl_func_type_name = force # ignore/add/remove/force/not_defined # Add or remove newline between return type and function name inside a class # definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name # is used instead. nl_func_type_name_class = ignore # ignore/add/remove/force/not_defined # Add or remove newline between class specification and '::' # in 'void A::f() { }'. Only appears in separate member implementation (does # not appear with in-line implementation). nl_func_class_scope = ignore # ignore/add/remove/force/not_defined # Add or remove newline between function scope and name, as in # 'void A :: f() { }'. nl_func_scope_name = ignore # ignore/add/remove/force/not_defined # Add or remove newline between return type and function name in a prototype. nl_func_proto_type_name = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # declaration. nl_func_paren = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_paren for functions with no parameters. nl_func_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # definition. nl_func_def_paren = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # call. nl_func_call_paren = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_call_paren for functions with no parameters. nl_func_call_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline after '(' in a function declaration. nl_func_decl_start = ignore # ignore/add/remove/force/not_defined # Add or remove newline after '(' in a function definition. nl_func_def_start = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_decl_start when there is only one parameter. nl_func_decl_start_single = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_def_start when there is only one parameter. nl_func_def_start_single = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_start is used instead. nl_func_decl_start_multi_line = false # true/false # Whether to add a newline after '(' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_start is used instead. nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. nl_func_decl_args = ignore # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in a function definition. nl_func_def_args = ignore # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in a function call. nl_func_call_args = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after each ',' in a function declaration if '(' # and ')' are in different lines. If false, nl_func_decl_args is used instead. nl_func_decl_args_multi_line = false # true/false # Whether to add a newline after each ',' in a function definition if '(' # and ')' are in different lines. If false, nl_func_def_args is used instead. nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. nl_func_decl_end = ignore # ignore/add/remove/force/not_defined # Add or remove newline before the ')' in a function definition. nl_func_def_end = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_decl_end when there is only one parameter. nl_func_decl_end_single = ignore # ignore/add/remove/force/not_defined # Overrides nl_func_def_end when there is only one parameter. nl_func_def_end_single = ignore # ignore/add/remove/force/not_defined # Whether to add a newline before ')' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_end is used instead. nl_func_decl_end_multi_line = false # true/false # Whether to add a newline before ')' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_end is used instead. nl_func_def_end_multi_line = false # true/false # Add or remove newline between '()' in a function declaration. nl_func_decl_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '()' in a function definition. nl_func_def_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between '()' in a function call. nl_func_call_empty = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function call, # has preference over nl_func_call_start_multi_line. nl_func_call_start = ignore # ignore/add/remove/force/not_defined # Whether to add a newline before ')' in a function call. nl_func_call_end = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. nl_func_call_start_multi_line = false # true/false # Whether to add a newline after each ',' in a function call if '(' and ')' # are in different lines. nl_func_call_args_multi_line = false # true/false # Whether to add a newline before ')' in a function call if '(' and ')' are in # different lines. nl_func_call_end_multi_line = false # true/false # Whether to respect nl_func_call_XXX option in case of closure args. nl_func_call_args_multi_line_ignore_closures = false # true/false # Whether to add a newline after '<' of a template parameter list. nl_template_start = false # true/false # Whether to add a newline after each ',' in a template parameter list. nl_template_args = false # true/false # Whether to add a newline before '>' of a template parameter list. nl_template_end = false # true/false # (OC) Whether to put each Objective-C message parameter on a separate line. # See nl_oc_msg_leave_one_liner. nl_oc_msg_args = false # true/false # Add or remove newline between function signature and '{'. nl_fdef_brace = add # ignore/add/remove/force/not_defined # Add or remove newline between function signature and '{', # if signature ends with ')'. Overrides nl_fdef_brace. nl_fdef_brace_cond = ignore # ignore/add/remove/force/not_defined # Add or remove newline between C++11 lambda signature and '{'. nl_cpp_ldef_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'return' and the return expression. nl_return_expr = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after semicolons, except in 'for' statements. nl_after_semicolon = false # true/false # (Java) Add or remove newline between the ')' and '{{' of the double brace # initializer. nl_paren_dbrace_open = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after the type in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst = ignore # ignore/add/remove/force/not_defined # Whether to add a newline after the open brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_open = ignore # ignore/add/remove/force/not_defined # Whether to add a newline before the close brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_close = ignore # ignore/add/remove/force/not_defined # Whether to add a newline before '{'. nl_before_brace_open = false # true/false # Whether to add a newline after '{'. nl_after_brace_open = false # true/false # Whether to add a newline between the open brace and a trailing single-line # comment. Requires nl_after_brace_open=true. nl_after_brace_open_cmt = false # true/false # Whether to add a newline after a virtual brace open with a non-empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open = false # true/false # Whether to add a newline after a virtual brace open with an empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open_empty = false # true/false # Whether to add a newline after '}'. Does not apply if followed by a # necessary ';'. nl_after_brace_close = false # true/false # Whether to add a newline after a virtual brace close, # as in 'if (foo) a++; return;'. nl_after_vbrace_close = false # true/false # Add or remove newline between the close brace and identifier, # as in 'struct { int a; } b;'. Affects enumerations, unions and # structures. If set to ignore, uses nl_after_brace_close. nl_brace_struct_var = ignore # ignore/add/remove/force/not_defined # Whether to alter newlines in '#define' macros. nl_define_macro = false # true/false # Whether to alter newlines between consecutive parenthesis closes. The number # of closing parentheses in a line will depend on respective open parenthesis # lines. nl_squeeze_paren_close = false # true/false # Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and # '#endif'. Does not affect top-level #ifdefs. nl_squeeze_ifdef = false # true/false # Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. nl_squeeze_ifdef_top_level = false # true/false # Add or remove blank line before 'if'. nl_before_if = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'if' statement. Add/Force work only if the # next token is not a closing brace. nl_after_if = ignore # ignore/add/remove/force/not_defined # Add or remove blank line before 'for'. nl_before_for = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'for' statement. nl_after_for = ignore # ignore/add/remove/force/not_defined # Add or remove blank line before 'while'. nl_before_while = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'while' statement. nl_after_while = ignore # ignore/add/remove/force/not_defined # Add or remove blank line before 'switch'. nl_before_switch = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'switch' statement. nl_after_switch = ignore # ignore/add/remove/force/not_defined # Add or remove blank line before 'synchronized'. nl_before_synchronized = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'synchronized' statement. nl_after_synchronized = ignore # ignore/add/remove/force/not_defined # Add or remove blank line before 'do'. nl_before_do = ignore # ignore/add/remove/force/not_defined # Add or remove blank line after 'do/while' statement. nl_after_do = ignore # ignore/add/remove/force/not_defined # Ignore nl_before_{if,for,switch,do,synchronized} if the control # statement is immediately after a case statement. # if nl_before_{if,for,switch,do} is set to remove, this option # does nothing. nl_before_ignore_after_case = false # true/false # Whether to put a blank line before 'return' statements, unless after an open # brace. nl_before_return = false # true/false # Whether to put a blank line after 'return' statements, unless followed by a # close brace. nl_after_return = false # true/false # Whether to put a blank line before a member '.' or '->' operators. nl_before_member = ignore # ignore/add/remove/force/not_defined # (Java) Whether to put a blank line after a member '.' or '->' operators. nl_after_member = ignore # ignore/add/remove/force/not_defined # Whether to double-space commented-entries in 'struct'/'union'/'enum'. nl_ds_struct_enum_cmt = false # true/false # Whether to force a newline before '}' of a 'struct'/'union'/'enum'. # (Lower priority than eat_blanks_before_close_brace.) nl_ds_struct_enum_close_brace = false # true/false # Add or remove newline before or after (depending on pos_class_colon) a class # colon, as in 'class Foo : public Bar'. nl_class_colon = ignore # ignore/add/remove/force/not_defined # Add or remove newline around a class constructor colon. The exact position # depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma. nl_constr_colon = ignore # ignore/add/remove/force/not_defined # Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }' # into a single line. If true, prevents other brace newline rules from turning # such code into four lines. If true, it also preserves one-liner namespaces. nl_namespace_two_to_one_liner = false # true/false # Whether to remove a newline in simple unbraced if statements, turning them # into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'. nl_create_if_one_liner = false # true/false # Whether to remove a newline in simple unbraced for statements, turning them # into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'. nl_create_for_one_liner = false # true/false # Whether to remove a newline in simple unbraced while statements, turning # them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'. nl_create_while_one_liner = false # true/false # Whether to collapse a function definition whose body (not counting braces) # is only one line so that the entire definition (prototype, braces, body) is # a single line. nl_create_func_def_one_liner = false # true/false # Whether to split one-line simple list definitions into three lines by # adding newlines, as in 'int a[12] = { 0 };'. nl_create_list_one_liner = false # true/false # Whether to split one-line simple unbraced if statements into two lines by # adding a newline, as in 'if(b) i++;'. nl_split_if_one_liner = false # true/false # Whether to split one-line simple unbraced for statements into two lines by # adding a newline, as in 'for (...) stmt;'. nl_split_for_one_liner = false # true/false # Whether to split one-line simple unbraced while statements into two lines by # adding a newline, as in 'while (expr) stmt;'. nl_split_while_one_liner = false # true/false # Don't add a newline before a cpp-comment in a parameter list of a function # call. donot_add_nl_before_cpp_comment = false # true/false # # Blank line options # # The maximum number of consecutive newlines (3 = 2 blank lines). nl_max = 2 # unsigned number # The maximum number of consecutive newlines in a function. nl_max_blank_in_func = 0 # unsigned number # The number of newlines inside an empty function body. # This option overrides eat_blanks_after_open_brace and # eat_blanks_before_close_brace, but is ignored when # nl_collapse_empty_body=true nl_inside_empty_func = 0 # unsigned number # The number of newlines before a function prototype. nl_before_func_body_proto = 0 # unsigned number # The number of newlines before a multi-line function definition. Where # applicable, this option is overridden with eat_blanks_after_open_brace=true nl_before_func_body_def = 0 # unsigned number # The number of newlines before a class constructor/destructor prototype. nl_before_func_class_proto = 0 # unsigned number # The number of newlines before a class constructor/destructor definition. nl_before_func_class_def = 0 # unsigned number # The number of newlines after a function prototype. nl_after_func_proto = 0 # unsigned number # The number of newlines after a function prototype, if not followed by # another function prototype. nl_after_func_proto_group = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype. nl_after_func_class_proto = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype, # if not followed by another constructor/destructor prototype. nl_after_func_class_proto_group = 0 # unsigned number # Whether one-line method definitions inside a class body should be treated # as if they were prototypes for the purposes of adding newlines. # # Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def # and nl_before_func_class_def for one-liners. nl_class_leave_one_liner_groups = false # true/false # The number of newlines after '}' of a multi-line function body. nl_after_func_body = 0 # unsigned number # The number of newlines after '}' of a multi-line function body in a class # declaration. Also affects class constructors/destructors. # # Overrides nl_after_func_body. nl_after_func_body_class = 0 # unsigned number # The number of newlines after '}' of a single line function body. Also # affects class constructors/destructors. # # Overrides nl_after_func_body and nl_after_func_body_class. nl_after_func_body_one_liner = 0 # unsigned number # The number of blank lines after a block of variable definitions at the top # of a function body. # # 0: No change (default). nl_func_var_def_blk = 0 # unsigned number # The number of newlines before a block of typedefs. If nl_after_access_spec # is non-zero, that option takes precedence. # # 0: No change (default). nl_typedef_blk_start = 0 # unsigned number # The number of newlines after a block of typedefs. # # 0: No change (default). nl_typedef_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of typedefs. # # 0: No change (default). nl_typedef_blk_in = 0 # unsigned number # The number of newlines before a block of variable definitions not at the top # of a function body. If nl_after_access_spec is non-zero, that option takes # precedence. # # 0: No change (default). nl_var_def_blk_start = 0 # unsigned number # The number of newlines after a block of variable definitions not at the top # of a function body. # # 0: No change (default). nl_var_def_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of variable # definitions. # # 0: No change (default). nl_var_def_blk_in = 0 # unsigned number # The minimum number of newlines before a multi-line comment. # Doesn't apply if after a brace open or another multi-line comment. nl_before_block_comment = 0 # unsigned number # The minimum number of newlines before a single-line C comment. # Doesn't apply if after a brace open or other single-line C comments. nl_before_c_comment = 0 # unsigned number # The minimum number of newlines before a CPP comment. # Doesn't apply if after a brace open or other CPP comments. nl_before_cpp_comment = 0 # unsigned number # Whether to force a newline after a multi-line comment. nl_after_multiline_comment = false # true/false # Whether to force a newline after a label's colon. nl_after_label_colon = false # true/false # The number of newlines before a struct definition. nl_before_struct = 0 # unsigned number # The number of newlines after '}' or ';' of a struct/enum/union definition. nl_after_struct = 0 # unsigned number # The number of newlines before a class definition. nl_before_class = 0 # unsigned number # The number of newlines after '}' or ';' of a class definition. nl_after_class = 0 # unsigned number # The number of newlines before a namespace. nl_before_namespace = 0 # unsigned number # The number of newlines after '{' of a namespace. This also adds newlines # before the matching '}'. # # 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if # applicable, otherwise no change. # # Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace. nl_inside_namespace = 0 # unsigned number # The number of newlines after '}' of a namespace. nl_after_namespace = 0 # unsigned number # The number of newlines before an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). nl_before_access_spec = 0 # unsigned number # The number of newlines after an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). # # Overrides nl_typedef_blk_start and nl_var_def_blk_start. nl_after_access_spec = 0 # unsigned number # The number of newlines between a function definition and the function # comment, as in '// comment\n void foo() {...}'. # # 0: No change (default). nl_comment_func_def = 0 # unsigned number # The number of newlines after a try-catch-finally block that isn't followed # by a brace close. # # 0: No change (default). nl_after_try_catch_finally = 0 # unsigned number # (C#) The number of newlines before and after a property, indexer or event # declaration. # # 0: No change (default). nl_around_cs_property = 0 # unsigned number # (C#) The number of newlines between the get/set/add/remove handlers. # # 0: No change (default). nl_between_get_set = 0 # unsigned number # (C#) Add or remove newline between property and the '{'. nl_property_brace = ignore # ignore/add/remove/force/not_defined # Whether to remove blank lines after '{'. eat_blanks_after_open_brace = false # true/false # Whether to remove blank lines before '}'. eat_blanks_before_close_brace = false # true/false # How aggressively to remove extra newlines not in preprocessor. # # 0: No change (default) # 1: Remove most newlines not handled by other config # 2: Remove all newlines and reformat completely by config nl_remove_extra_newlines = 0 # unsigned number # (Java) Add or remove newline after an annotation statement. Only affects # annotations that are after a newline. nl_after_annotation = ignore # ignore/add/remove/force/not_defined # (Java) Add or remove newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force/not_defined # The number of newlines before a whole-file #ifdef. # # 0: No change (default). nl_before_whole_file_ifdef = 0 # unsigned number # The number of newlines after a whole-file #ifdef. # # 0: No change (default). nl_after_whole_file_ifdef = 0 # unsigned number # The number of newlines before a whole-file #endif. # # 0: No change (default). nl_before_whole_file_endif = 0 # unsigned number # The number of newlines after a whole-file #endif. # # 0: No change (default). nl_after_whole_file_endif = 0 # unsigned number # # Positioning options # # The position of arithmetic operators in wrapped expressions. pos_arith = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of assignment in wrapped expressions. Do not affect '=' # followed by '{'. pos_assign = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of Boolean operators in wrapped expressions. pos_bool = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of comparison operators in wrapped expressions. pos_compare = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of conditional operators, as in the '?' and ':' of # 'expr ? stmt : stmt', in wrapped expressions. pos_conditional = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in wrapped expressions. pos_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in enum entries. pos_enum_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the base class list if there is more than one # line. Affects nl_class_init_args. pos_class_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the constructor initialization list. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. pos_constr_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of trailing/leading class colon, between class and base class # list. Affects nl_class_colon. pos_class_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of colons between constructor and member initialization. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. pos_constr_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of shift operators in wrapped expressions. pos_shift = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # # Line splitting options # # Try to limit code width to N columns. code_width = 0 # unsigned number # Whether to fully split long 'for' statements at semi-colons. ls_for_split_full = false # true/false # Whether to fully split long function prototypes/calls at commas. # The option ls_code_width has priority over the option ls_func_split_full. ls_func_split_full = false # true/false # Whether to split lines as close to code_width as possible and ignore some # groupings. # The option ls_code_width has priority over the option ls_func_split_full. ls_code_width = false # true/false # # Code alignment options (not left column spaces/tabs) # # Whether to keep non-indenting tabs. align_keep_tabs = false # true/false # Whether to use tabs for aligning. align_with_tabs = false # true/false # Whether to bump out to the next tab when aligning. align_on_tabstop = false # true/false # Whether to right-align numbers. align_number_right = false # true/false # Whether to keep whitespace not required for alignment. align_keep_extra_space = false # true/false # Whether to align variable definitions in prototypes and functions. align_func_params = false # true/false # The span for aligning parameter definitions in function on parameter name. # # 0: Don't align (default). align_func_params_span = 0 # unsigned number # The threshold for aligning function parameter definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_params_thresh = 0 # number # The gap for aligning function parameter definitions. align_func_params_gap = 0 # unsigned number # The span for aligning constructor value. # # 0: Don't align (default). align_constr_value_span = 0 # unsigned number # The threshold for aligning constructor value. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_constr_value_thresh = 0 # number # The gap for aligning constructor value. align_constr_value_gap = 0 # unsigned number # Whether to align parameters in single-line functions that have the same # name. The function names must already be aligned with each other. align_same_func_call_params = false # true/false # The span for aligning function-call parameters for single line functions. # # 0: Don't align (default). align_same_func_call_params_span = 0 # unsigned number # The threshold for aligning function-call parameters for single line # functions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_same_func_call_params_thresh = 0 # number # The span for aligning variable definitions. # # 0: Don't align (default). align_var_def_span = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of variable definitions. # # 0: Part of the type 'void * foo;' (default) # 1: Part of the variable 'void *foo;' # 2: Dangling 'void *foo;' # Dangling: the '*' will not be taken into account when aligning. align_var_def_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of variable definitions. # # 0: Part of the type 'long & foo;' (default) # 1: Part of the variable 'long &foo;' # 2: Dangling 'long &foo;' # Dangling: the '&' will not be taken into account when aligning. align_var_def_amp_style = 0 # unsigned number # The threshold for aligning variable definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_def_thresh = 0 # number # The gap for aligning variable definitions. align_var_def_gap = 0 # unsigned number # Whether to align the colon in struct bit fields. align_var_def_colon = false # true/false # The gap for aligning the colon in struct bit fields. align_var_def_colon_gap = 0 # unsigned number # Whether to align any attribute after the variable name. align_var_def_attribute = false # true/false # Whether to align inline struct/enum/union variable definitions. align_var_def_inline = false # true/false # The span for aligning on '=' in assignments. # # 0: Don't align (default). align_assign_span = 0 # unsigned number # The span for aligning on '=' in function prototype modifier. # # 0: Don't align (default). align_assign_func_proto_span = 0 # unsigned number # The threshold for aligning on '=' in assignments. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_assign_thresh = 0 # number # How to apply align_assign_span to function declaration "assignments", i.e. # 'virtual void foo() = 0' or '~foo() = {default|delete}'. # # 0: Align with other assignments (default) # 1: Align with each other, ignoring regular assignments # 2: Don't align align_assign_decl_func = 0 # unsigned number # The span for aligning on '=' in enums. # # 0: Don't align (default). align_enum_equ_span = 0 # unsigned number # The threshold for aligning on '=' in enums. # Use a negative number for absolute thresholds. # # 0: no limit (default). align_enum_equ_thresh = 0 # number # The span for aligning class member definitions. # # 0: Don't align (default). align_var_class_span = 0 # unsigned number # The threshold for aligning class member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_class_thresh = 0 # number # The gap for aligning class member definitions. align_var_class_gap = 0 # unsigned number # The span for aligning struct/union member definitions. # # 0: Don't align (default). align_var_struct_span = 0 # unsigned number # The threshold for aligning struct/union member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_struct_thresh = 0 # number # The gap for aligning struct/union member definitions. align_var_struct_gap = 0 # unsigned number # The span for aligning struct initializer values. # # 0: Don't align (default). align_struct_init_span = 0 # unsigned number # The span for aligning single-line typedefs. # # 0: Don't align (default). align_typedef_span = 0 # unsigned number # The minimum space between the type and the synonym of a typedef. align_typedef_gap = 0 # unsigned number # How to align typedef'd functions with other typedefs. # # 0: Don't mix them at all (default) # 1: Align the open parenthesis with the types # 2: Align the function type name with the other type names align_typedef_func = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int * pint;' (default) # 1: Part of type name: 'typedef int *pint;' # 2: Dangling: 'typedef int *pint;' # Dangling: the '*' will not be taken into account when aligning. align_typedef_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int & intref;' (default) # 1: Part of type name: 'typedef int &intref;' # 2: Dangling: 'typedef int &intref;' # Dangling: the '&' will not be taken into account when aligning. align_typedef_amp_style = 0 # unsigned number # The span for aligning comments that end lines. # # 0: Don't align (default). align_right_cmt_span = 0 # unsigned number # Minimum number of columns between preceding text and a trailing comment in # order for the comment to qualify for being aligned. Must be non-zero to have # an effect. align_right_cmt_gap = 0 # unsigned number # If aligning comments, whether to mix with comments after '}' and #endif with # less than three spaces before the comment. align_right_cmt_mix = false # true/false # Whether to only align trailing comments that are at the same brace level. align_right_cmt_same_level = false # true/false # Minimum column at which to align trailing comments. Comments which are # aligned beyond this column, but which can be aligned in a lesser column, # may be "pulled in". # # 0: Ignore (default). align_right_cmt_at_col = 0 # unsigned number # The span for aligning function prototypes. # # 0: Don't align (default). align_func_proto_span = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of function prototypes. # # 0: Part of the type 'void * foo();' (default) # 1: Part of the function 'void *foo();' # 2: Dangling 'void *foo();' # Dangling: the '*' will not be taken into account when aligning. align_func_proto_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of function prototypes. # # 0: Part of the type 'long & foo();' (default) # 1: Part of the function 'long &foo();' # 2: Dangling 'long &foo();' # Dangling: the '&' will not be taken into account when aligning. align_func_proto_amp_style = 0 # unsigned number # The threshold for aligning function prototypes. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_proto_thresh = 0 # number # Minimum gap between the return type and the function name. align_func_proto_gap = 0 # unsigned number # Whether to align function prototypes on the 'operator' keyword instead of # what follows. align_on_operator = false # true/false # Whether to mix aligning prototype and variable declarations. If true, # align_var_def_XXX options are used instead of align_func_proto_XXX options. align_mix_var_proto = false # true/false # Whether to align single-line functions with function prototypes. # Uses align_func_proto_span. align_single_line_func = false # true/false # Whether to align the open brace of single-line functions. # Requires align_single_line_func=true. Uses align_func_proto_span. align_single_line_brace = false # true/false # Gap for align_single_line_brace. align_single_line_brace_gap = 0 # unsigned number # (OC) The span for aligning Objective-C message specifications. # # 0: Don't align (default). align_oc_msg_spec_span = 0 # unsigned number # Whether to align macros wrapped with a backslash and a newline. This will # not work right if the macro contains a multi-line comment. align_nl_cont = false # true/false # Whether to align macro functions and variables together. align_pp_define_together = false # true/false # The span for aligning on '#define' bodies. # # =0: Don't align (default) # >0: Number of lines (including comments) between blocks align_pp_define_span = 0 # unsigned number # The minimum space between label and value of a preprocessor define. align_pp_define_gap = 0 # unsigned number # Whether to align lines that start with '<<' with previous '<<'. # # Default: true align_left_shift = true # true/false # Whether to align comma-separated statements following '<<' (as used to # initialize Eigen matrices). align_eigen_comma_init = false # true/false # Whether to align text after 'asm volatile ()' colons. align_asm_colon = false # true/false # (OC) Span for aligning parameters in an Objective-C message call # on the ':'. # # 0: Don't align. align_oc_msg_colon_span = 0 # unsigned number # (OC) Whether to always align with the first parameter, even if it is too # short. align_oc_msg_colon_first = false # true/false # (OC) Whether to align parameters in an Objective-C '+' or '-' declaration # on the ':'. align_oc_decl_colon = false # true/false # (OC) Whether to not align parameters in an Objectve-C message call if first # colon is not on next line of the message call (the same way Xcode does # aligment) align_oc_msg_colon_xcode_like = false # true/false # # Comment modification options # # Try to wrap comments at N columns. cmt_width = 0 # unsigned number # How to reflow comments. # # 0: No reflowing (apart from the line wrapping due to cmt_width) (default) # 1: No touching at all # 2: Full reflow (enable cmt_indent_multi for indent with line wrapping due to cmt_width) cmt_reflow_mode = 0 # unsigned number # Path to a file that contains regular expressions describing patterns for # which the end of one line and the beginning of the next will be folded into # the same sentence or paragraph during full comment reflow. The regular # expressions are described using ECMAScript syntax. The syntax for this # specification is as follows, where "..." indicates the custom regular # expression and "n" indicates the nth end_of_prev_line_regex and # beg_of_next_line_regex regular expression pair: # # end_of_prev_line_regex[1] = "...$" # beg_of_next_line_regex[1] = "^..." # end_of_prev_line_regex[2] = "...$" # beg_of_next_line_regex[2] = "^..." # . # . # . # end_of_prev_line_regex[n] = "...$" # beg_of_next_line_regex[n] = "^..." # # Note that use of this option overrides the default reflow fold regular # expressions, which are internally defined as follows: # # end_of_prev_line_regex[1] = "[\w,\]\)]$" # beg_of_next_line_regex[1] = "^[\w,\[\(]" # end_of_prev_line_regex[2] = "\.$" # beg_of_next_line_regex[2] = "^[A-Z]" cmt_reflow_fold_regex_file = "" # string # Whether to indent wrapped lines to the start of the encompassing paragraph # during full comment reflow (cmt_reflow_mode = 2). Overrides the value # specified by cmt_sp_after_star_cont. # # Note that cmt_align_doxygen_javadoc_tags overrides this option for # paragraphs associated with javadoc tags cmt_reflow_indent_to_paragraph_start = false # true/false # Whether to convert all tabs to spaces in comments. If false, tabs in # comments are left alone, unless used for indenting. cmt_convert_tab_to_spaces = false # true/false # Whether to apply changes to multi-line comments, including cmt_width, # keyword substitution and leading chars. # # Default: true cmt_indent_multi = true # true/false # Whether to align doxygen javadoc-style tags ('@param', '@return', etc.) # and corresponding fields such that groups of consecutive block tags, # parameter names, and descriptions align with one another. Overrides that # which is specified by the cmt_sp_after_star_cont. If cmt_width > 0, it may # be necessary to enable cmt_indent_multi and set cmt_reflow_mode = 2 # in order to achieve the desired alignment for line-wrapping. cmt_align_doxygen_javadoc_tags = false # true/false # The number of spaces to insert after the star and before doxygen # javadoc-style tags (@param, @return, etc). Requires enabling # cmt_align_doxygen_javadoc_tags. Overrides that which is specified by the # cmt_sp_after_star_cont. # # Default: 1 cmt_sp_before_doxygen_javadoc_tags = 1 # unsigned number # Whether to change trailing, single-line c-comments into cpp-comments. cmt_trailing_single_line_c_to_cpp = false # true/false # Whether to group c-comments that look like they are in a block. cmt_c_group = false # true/false # Whether to put an empty '/*' on the first line of the combined c-comment. cmt_c_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined c-comment. cmt_c_nl_end = false # true/false # Whether to change cpp-comments into c-comments. cmt_cpp_to_c = false # true/false # Whether to group cpp-comments that look like they are in a block. Only # meaningful if cmt_cpp_to_c=true. cmt_cpp_group = false # true/false # Whether to put an empty '/*' on the first line of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_end = false # true/false # Whether to put a star on subsequent comment lines. cmt_star_cont = false # true/false # The number of spaces to insert at the start of subsequent comment lines. cmt_sp_before_star_cont = 0 # unsigned number # The number of spaces to insert after the star on subsequent comment lines. cmt_sp_after_star_cont = 0 # unsigned number # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length. # # Default: true cmt_multi_check_last = true # true/false # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length AND if the length is # bigger as the first_len minimum. # # Default: 4 cmt_multi_first_len_minimum = 4 # unsigned number # Path to a file that contains text to insert at the beginning of a file if # the file doesn't start with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_header = "" # string # Path to a file that contains text to insert at the end of a file if the # file doesn't end with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_footer = "" # string # Path to a file that contains text to insert before a function definition if # the function isn't preceded by a C/C++ comment. If the inserted text # contains '$(function)', '$(javaparam)' or '$(fclass)', these will be # replaced with, respectively, the name of the function, the javadoc '@param' # and '@return' stuff, or the name of the class to which the member function # belongs. cmt_insert_func_header = "" # string # Path to a file that contains text to insert before a class if the class # isn't preceded by a C/C++ comment. If the inserted text contains '$(class)', # that will be replaced with the class name. cmt_insert_class_header = "" # string # Path to a file that contains text to insert before an Objective-C message # specification, if the method isn't preceded by a C/C++ comment. If the # inserted text contains '$(message)' or '$(javaparam)', these will be # replaced with, respectively, the name of the function, or the javadoc # '@param' and '@return' stuff. cmt_insert_oc_msg_header = "" # string # Whether a comment should be inserted if a preprocessor is encountered when # stepping backwards from a function name. # # Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and # cmt_insert_class_header. cmt_insert_before_preproc = false # true/false # Whether a comment should be inserted if a function is declared inline to a # class definition. # # Applies to cmt_insert_func_header. # # Default: true cmt_insert_before_inlines = true # true/false # Whether a comment should be inserted if the function is a class constructor # or destructor. # # Applies to cmt_insert_func_header. cmt_insert_before_ctor_dtor = false # true/false # # Code modifying options (non-whitespace) # # Add or remove braces on a single-line 'do' statement. mod_full_brace_do = add # ignore/add/remove/force/not_defined # Add or remove braces on a single-line 'for' statement. mod_full_brace_for = add # ignore/add/remove/force/not_defined # (Pawn) Add or remove braces on a single-line function definition. mod_full_brace_function = add # ignore/add/remove/force/not_defined # Add or remove braces on a single-line 'if' statement. Braces will not be # removed if the braced statement contains an 'else'. mod_full_brace_if = add # ignore/add/remove/force/not_defined # Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either # have, or do not have, braces. If true, braces will be added if any block # needs braces, and will only be removed if they can be removed from all # blocks. # # Overrides mod_full_brace_if. mod_full_brace_if_chain = false # true/false # Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. # If true, mod_full_brace_if_chain will only remove braces from an 'if' that # does not have an 'else if' or 'else'. mod_full_brace_if_chain_only = false # true/false # Add or remove braces on single-line 'while' statement. mod_full_brace_while = ignore # ignore/add/remove/force/not_defined # Add or remove braces on single-line 'using ()' statement. mod_full_brace_using = ignore # ignore/add/remove/force/not_defined # Don't remove braces around statements that span N newlines mod_full_brace_nl = 0 # unsigned number # Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks # which span multiple lines. # # Affects: # mod_full_brace_for # mod_full_brace_if # mod_full_brace_if_chain # mod_full_brace_if_chain_only # mod_full_brace_while # mod_full_brace_using # # Does not affect: # mod_full_brace_do # mod_full_brace_function mod_full_brace_nl_block_rem_mlcond = false # true/false # Add or remove unnecessary parenthesis on 'return' statement. mod_paren_on_return = ignore # ignore/add/remove/force/not_defined # (Pawn) Whether to change optional semicolons to real semicolons. mod_pawn_semicolon = false # true/false # Whether to fully parenthesize Boolean expressions in 'while' and 'if' # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'. mod_full_paren_if_bool = false # true/false # Whether to remove superfluous semicolons. mod_remove_extra_semicolon = false # true/false # Whether to remove duplicate include. mod_remove_duplicate_include = false # true/false # If a function body exceeds the specified number of newlines and doesn't have # a comment after the close brace, a comment will be added. mod_add_long_function_closebrace_comment = 0 # unsigned number # If a namespace body exceeds the specified number of newlines and doesn't # have a comment after the close brace, a comment will be added. mod_add_long_namespace_closebrace_comment = 0 # unsigned number # If a class body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_class_closebrace_comment = 0 # unsigned number # If a switch body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_switch_closebrace_comment = 0 # unsigned number # If an #ifdef body exceeds the specified number of newlines and doesn't have # a comment after the #endif, a comment will be added. mod_add_long_ifdef_endif_comment = 0 # unsigned number # If an #ifdef or #else body exceeds the specified number of newlines and # doesn't have a comment after the #else, a comment will be added. mod_add_long_ifdef_else_comment = 0 # unsigned number # Whether to take care of the case by the mod_sort_xx options. mod_sort_case_sensitive = false # true/false # Whether to sort consecutive single-line 'import' statements. mod_sort_import = false # true/false # (C#) Whether to sort consecutive single-line 'using' statements. mod_sort_using = false # true/false # Whether to sort consecutive single-line '#include' statements (C/C++) and # '#import' statements (Objective-C). Be aware that this has the potential to # break your code if your includes/imports have ordering dependencies. mod_sort_include = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # filename without extension when sorting is enabled. mod_sort_incl_import_prioritize_filename = false # true/false # Whether to prioritize '#include' and '#import' statements that does not # contain extensions when sorting is enabled. mod_sort_incl_import_prioritize_extensionless = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # angle over quotes when sorting is enabled. mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false # Whether to ignore file extension in '#include' and '#import' statements # for sorting comparison. mod_sort_incl_import_ignore_extension = false # true/false # Whether to group '#include' and '#import' statements when sorting is enabled. mod_sort_incl_import_grouping_enabled = false # true/false # Whether to move a 'break' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. mod_move_case_break = false # true/false # Add or remove braces around a fully braced case statement. Will only remove # braces if there are no variable declarations in the block. mod_case_brace = ignore # ignore/add/remove/force/not_defined # Whether to remove a void 'return;' that appears as the last statement in a # function. mod_remove_empty_return = false # true/false # Add or remove the comma after the last value of an enumeration. mod_enum_last_comma = ignore # ignore/add/remove/force/not_defined # (OC) Whether to organize the properties. If true, properties will be # rearranged according to the mod_sort_oc_property_*_weight factors. mod_sort_oc_properties = false # true/false # (OC) Weight of a class property modifier. mod_sort_oc_property_class_weight = 0 # number # (OC) Weight of 'atomic' and 'nonatomic'. mod_sort_oc_property_thread_safe_weight = 0 # number # (OC) Weight of 'readwrite' when organizing properties. mod_sort_oc_property_readwrite_weight = 0 # number # (OC) Weight of a reference type specifier ('retain', 'copy', 'assign', # 'weak', 'strong') when organizing properties. mod_sort_oc_property_reference_weight = 0 # number # (OC) Weight of getter type ('getter=') when organizing properties. mod_sort_oc_property_getter_weight = 0 # number # (OC) Weight of setter type ('setter=') when organizing properties. mod_sort_oc_property_setter_weight = 0 # number # (OC) Weight of nullability type ('nullable', 'nonnull', 'null_unspecified', # 'null_resettable') when organizing properties. mod_sort_oc_property_nullability_weight = 0 # number # # Preprocessor options # # Add or remove indentation of preprocessor directives inside #if blocks # at brace level 0 (file-level). pp_indent = ignore # ignore/add/remove/force/not_defined # Whether to indent #if/#else/#endif at the brace level. If false, these are # indented from column 1. pp_indent_at_level = false # true/false # Specifies the number of columns to indent preprocessors per level # at brace level 0 (file-level). If pp_indent_at_level=false, also specifies # the number of columns to indent preprocessors per level # at brace level > 0 (function-level). # # Default: 1 pp_indent_count = 1 # unsigned number # Add or remove space after # based on pp_level of #if blocks. pp_space = ignore # ignore/add/remove/force/not_defined # Sets the number of spaces per level added with pp_space. pp_space_count = 0 # unsigned number # The indent for '#region' and '#endregion' in C# and '#pragma region' in # C/C++. Negative values decrease indent down to the first column. pp_indent_region = 0 # number # Whether to indent the code between #region and #endregion. pp_region_indent_code = false # true/false # If pp_indent_at_level=true, sets the indent for #if, #else and #endif when # not at file-level. Negative values decrease indent down to the first column. # # =0: Indent preprocessors using output_tab_size # >0: Column at which all preprocessors will be indented pp_indent_if = 0 # number # Whether to indent the code between #if, #else and #endif. pp_if_indent_code = false # true/false # Whether to indent the body of an #if that encompasses all the code in the file. pp_indent_in_guard = false # true/false # Whether to indent '#define' at the brace level. If false, these are # indented from column 1. pp_define_at_level = false # true/false # Whether to indent '#include' at the brace level. pp_include_at_level = false # true/false # Whether to ignore the '#define' body while formatting. pp_ignore_define_body = false # true/false # Whether to indent case statements between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the case statements # directly inside of. # # Default: true pp_indent_case = true # true/false # Whether to indent whole function definitions between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the function definition # is directly inside of. # # Default: true pp_indent_func_def = true # true/false # Whether to indent extern C blocks between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the extern block is # directly inside of. # # Default: true pp_indent_extern = true # true/false # Whether to indent braces directly inside #if, #else, and #endif. # Only applies to the indent of the preprocesser that the braces are directly # inside of. # # Default: true pp_indent_brace = true # true/false # # Sort includes options # # The regex for include category with priority 0. include_category_0 = "" # string # The regex for include category with priority 1. include_category_1 = "" # string # The regex for include category with priority 2. include_category_2 = "" # string # # Use or Do not Use options # # true: indent_func_call_param will be used (default) # false: indent_func_call_param will NOT be used # # Default: true use_indent_func_call_param = true # true/false # The value of the indentation for a continuation line is calculated # differently if the statement is: # - a declaration: your case with QString fileName ... # - an assignment: your case with pSettings = new QSettings( ... # # At the second case the indentation value might be used twice: # - at the assignment # - at the function call (if present) # # To prevent the double use of the indentation value, use this option with the # value 'true'. # # true: indent_continue will be used only once # false: indent_continue will be used every time (default) use_indent_continue_only_once = false # true/false # The value might be used twice: # - at the assignment # - at the opening brace # # To prevent the double use of the indentation value, use this option with the # value 'true'. # # true: indentation will be used only once # false: indentation will be used every time (default) indent_cpp_lambda_only_once = false # true/false # Whether sp_after_angle takes precedence over sp_inside_fparen. This was the # historic behavior, but is probably not the desired behavior, so this is off # by default. use_sp_after_angle_always = false # true/false # Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially, # this tries to format these so that they match Qt's normalized form (i.e. the # result of QMetaObject::normalizedSignature), which can slightly improve the # performance of the QObject::connect call, rather than how they would # otherwise be formatted. # # See options_for_QT.cpp for details. # # Default: true use_options_overriding_for_qt_macros = true # true/false # If true: the form feed character is removed from the list of whitespace # characters. See https://en.cppreference.com/w/cpp/string/byte/isspace. use_form_feed_no_more_as_whitespace_character = false # true/false # # Warn levels - 1: error, 2: warning (default), 3: note # # (C#) Warning is given if doing tab-to-\t replacement and we have found one # in a C# verbatim string literal. # # Default: 2 warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number # Limit the number of loops. # Used by uncrustify.cpp to exit from infinite loop. # 0: no limit. debug_max_number_of_loops = 0 # number # Set the number of the line to protocol; # Used in the function prot_the_line if the 2. parameter is zero. # 0: nothing protocol. debug_line_number_to_protocol = 0 # number # Set the number of second(s) before terminating formatting the current file, # 0: no timeout. # only for linux debug_timeout = 0 # number # Set the number of characters to be printed if the text is too long, # 0: do not truncate. debug_truncate = 0 # unsigned number # Meaning of the settings: # Ignore - do not do any changes # Add - makes sure there is 1 or more space/brace/newline/etc # Force - makes sure there is exactly 1 space/brace/newline/etc, # behaves like Add in some contexts # Remove - removes space/brace/newline/etc # # # - Token(s) can be treated as specific type(s) with the 'set' option: # `set tokenType tokenString [tokenString...]` # # Example: # `set BOOL __AND__ __OR__` # # tokenTypes are defined in src/token_enum.h, use them without the # 'CT_' prefix: 'CT_BOOL' => 'BOOL' # # # - Token(s) can be treated as type(s) with the 'type' option. # `type tokenString [tokenString...]` # # Example: # `type int c_uint_8 Rectangle` # # This can also be achieved with `set TYPE int c_uint_8 Rectangle` # # # To embed whitespace in tokenStrings use the '\' escape character, or quote # the tokenStrings. These quotes are supported: "'` # # # - Support for the auto detection of languages through the file ending can be # added using the 'file_ext' command. # `file_ext langType langString [langString..]` # # Example: # `file_ext CPP .ch .cxx .cpp.in` # # langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use # them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP' # # # - Custom macro-based indentation can be set up using 'macro-open', # 'macro-else' and 'macro-close'. # `(macro-open | macro-else | macro-close) tokenString` # # Example: # `macro-open BEGIN_TEMPLATE_MESSAGE_MAP` # `macro-open BEGIN_MESSAGE_MAP` # `macro-close END_MESSAGE_MAP` # # # option(s) with 'not default' value: 0 # pgagroal-1.6.0/uncrustify.sh000077500000000000000000000003141456565230200160720ustar00rootroot00000000000000#!/bin/bash function indent() { for f in $1 do uncrustify -c uncrustify.cfg -q --replace --no-backup $f done } indent "src/*.c" indent "src/include/*.h" indent "src/libpgagroal/*.c"