diff --git a/include/fluent-bit/flb_pipe.h b/include/fluent-bit/flb_pipe.h index 9a428f4e062..a8f0246d38c 100644 --- a/include/fluent-bit/flb_pipe.h +++ b/include/fluent-bit/flb_pipe.h @@ -23,11 +23,10 @@ #include #ifdef _WIN32 -#include -#define flb_pipefd_t evutil_socket_t -#define flb_sockfd_t evutil_socket_t -#define flb_pipe_w(fd, buf, len) send(fd, buf, len, 0) -#define flb_pipe_r(fd, buf, len) recv(fd, buf, len, 0) +#define flb_pipefd_t intptr_t +#define flb_sockfd_t intptr_t +#define flb_pipe_w(fd, buf, len) send((SOCKET) (fd), buf, len, 0) +#define flb_pipe_r(fd, buf, len) recv((SOCKET) (fd), buf, len, 0) #define flb_pipe_error() flb_wsa_get_last_error() #define FLB_PIPE_WOULDBLOCK() (WSAGetLastError() == WSAEWOULDBLOCK) #else @@ -43,8 +42,8 @@ int flb_pipe_create(flb_pipefd_t pipefd[2]); void flb_pipe_destroy(flb_pipefd_t pipefd[2]); int flb_pipe_close(flb_pipefd_t fd); int flb_pipe_set_nonblocking(flb_pipefd_t fd); -ssize_t flb_pipe_read_all(int fd, void *buf, size_t count); -ssize_t flb_pipe_write_all(int fd, const void *buf, size_t count); +ssize_t flb_pipe_read_all(flb_pipefd_t fd, void *buf, size_t count); +ssize_t flb_pipe_write_all(flb_pipefd_t fd, const void *buf, size_t count); void flb_pipe_log_last_error(); #endif diff --git a/include/fluent-bit/flb_socket.h b/include/fluent-bit/flb_socket.h index 6f7b6ddd2a0..e09f5f8b35c 100644 --- a/include/fluent-bit/flb_socket.h +++ b/include/fluent-bit/flb_socket.h @@ -25,7 +25,7 @@ #ifdef _WIN32 #include -#define flb_sockfd_t evutil_socket_t +#define flb_sockfd_t intptr_t #define flb_socket_close(fd) evutil_closesocket(fd) #define flb_socket_error(fd) evutil_socket_geterror(fd) diff --git a/lib/monkey/.github/workflows/build-pr.yaml b/lib/monkey/.github/workflows/build-pr.yaml index 2898d7eecae..121aff7fa35 100644 --- a/lib/monkey/.github/workflows/build-pr.yaml +++ b/lib/monkey/.github/workflows/build-pr.yaml @@ -20,10 +20,12 @@ jobs: arch: x64 cmake_additional_opt: "" os: windows-latest + cmake_version: "3.31.6" - name: "Windows 64bit (ARM)" arch: amd64_arm64 - cmake_additional_opt: "-G \"NMake Makefiles\" -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_SYSTEM_PROCESSOR=ARM64" + cmake_additional_opt: "-G \"NMake Makefiles\" -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_SYSTEM_PROCESSOR=ARM64 -DMK_TLS_BACKEND=mbedtls" os: windows-latest + cmake_version: "3.31.6" steps: - name: Checkout repository uses: actions/checkout@v3 @@ -35,6 +37,83 @@ jobs: with: arch: ${{ matrix.config.arch }} + - name: Set up CMake + run: | + $cmakeVersion = "${{ matrix.config.cmake_version }}" + $cmakeArch = "x86_64" + $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v${cmakeVersion}/cmake-${cmakeVersion}-windows-${cmakeArch}.zip" + $cmakeZip = "$env:RUNNER_TEMP\cmake-${cmakeVersion}.zip" + $cmakeDir = "C:\Program Files\CMake" + + Write-Host "Downloading CMake ${cmakeVersion} from official GitHub releases: ${cmakeUrl}" + + # Download with retry logic + $maxRetries = 3 + $retryCount = 0 + $downloaded = $false + + while ($retryCount -lt $maxRetries -and -not $downloaded) { + try { + $retryCount++ + Write-Host "Attempt $retryCount of $maxRetries" + Invoke-WebRequest -Uri $cmakeUrl -OutFile $cmakeZip -UseBasicParsing -ErrorAction Stop + + # Verify file was downloaded and has content + if (Test-Path $cmakeZip) { + $fileInfo = Get-Item $cmakeZip + if ($fileInfo.Length -gt 0) { + Write-Host "Download successful. File size: $($fileInfo.Length) bytes" + $downloaded = $true + } else { + Write-Host "Downloaded file is empty, retrying..." + Remove-Item $cmakeZip -Force -ErrorAction SilentlyContinue + } + } + } catch { + Write-Host "Download attempt $retryCount failed: $_" + if ($retryCount -lt $maxRetries) { + Start-Sleep -Seconds 2 + Remove-Item $cmakeZip -Force -ErrorAction SilentlyContinue + } else { + throw "Failed to download CMake after $maxRetries attempts: $_" + } + } + } + + # Verify it's a valid ZIP file by checking the header (ZIP files start with PK) + $header = [System.IO.File]::ReadAllBytes($cmakeZip)[0..1] + if (-not ($header[0] -eq 0x50 -and $header[1] -eq 0x4B)) { + Write-Host "Warning: File may not be a valid ZIP. First bytes: $($header[0]), $($header[1])" + Write-Host "Expected: 80, 75 (PK in hex)" + throw "Downloaded file is not a valid ZIP archive" + } + Write-Host "ZIP file validation passed (PK header found)" + + Write-Host "Extracting CMake to C:\Program Files" + # Remove existing CMake directory if it exists + if (Test-Path $cmakeDir) { + Remove-Item $cmakeDir -Recurse -Force + } + + # Extract ZIP file + Expand-Archive -Path $cmakeZip -DestinationPath "C:\Program Files" -Force + + # Rename extracted folder to standard CMake directory name + $extractedDir = "C:\Program Files\cmake-${cmakeVersion}-windows-${cmakeArch}" + if (Test-Path $extractedDir) { + Rename-Item -Path $extractedDir -NewName "CMake" -Force + } + + # Verify installation + $cmakeExe = "$cmakeDir\bin\cmake.exe" + if (Test-Path $cmakeExe) { + Write-Host "CMake installed successfully" + & $cmakeExe --version + } else { + throw "CMake installation verification failed: $cmakeExe not found" + } + shell: pwsh + - name: Build on ${{ matrix.os }} with vs-2019 run: | mkdir build diff --git a/lib/monkey/.gitignore b/lib/monkey/.gitignore index 6708c67e177..931b68e5f4b 100644 --- a/lib/monkey/.gitignore +++ b/lib/monkey/.gitignore @@ -24,6 +24,7 @@ lib/python/conf/* lib/python/jemalloc.config src/include/ build/ +integration_tests/.venv/ include/monkey/mk_static_plugins.h include/monkey/mk_core/mk_core_info.h .vscode diff --git a/lib/monkey/AGENTS.md b/lib/monkey/AGENTS.md new file mode 100644 index 00000000000..110352cf173 --- /dev/null +++ b/lib/monkey/AGENTS.md @@ -0,0 +1,184 @@ +# AGENTS + +This file is the local operating guide for agents working in this repository. +It focuses on two things: + +- how the Monkey source tree is organized +- how commits are written in the existing Git history + +## Repository map + +Monkey is a small HTTP server written in C. The tree is split by subsystem. + +- `mk_core/` + Core utilities used by the server and plugins. + Includes memory helpers, strings, files, thread helpers, event loops, + I/O vectors, config parsing, and generic utilities. + +- `mk_server/` + The HTTP server implementation. + This is where connection handling, request parsing, header generation, + virtual hosts, MIME resolution, scheduler integration, streams, plugins, + and server lifecycle live. + +- `mk_bin/` + The standalone `monkey` executable entrypoints and signal handling. + +- `include/monkey/` + Public and internal headers for the core, server, parser, plugins, + config, events, streams, and API types. + +- `plugins/` + Optional server features. + Examples in this tree include `liana`, `mandril`, `dirlisting`, `cgi`, + `fastcgi`, `auth`, `logger`, `tls`, and `cheetah`. + +- `api/` + Small API-focused programs and tests. + +- `test/` + Native unit/integration-style test targets used by CMake. + +- `fuzz/` + Fuzzing entrypoints and helpers for parser and request handling. + +- `conf/` + Config templates installed or copied at build/install time. + +- `htdocs/` + Default static site content used by the standalone server. + +- `cmake/` + CMake helper modules and build logic. + +- `deps/` + Bundled third-party dependencies such as regex, rbtree, libco, + and mbedtls sources. + +- `qa/` + Extra request fixtures and local QA artifacts. + +## Main runtime flow + +When debugging behavior, the usual path is: + +1. `mk_bin/monkey.c` + Starts the binary. +2. `mk_server/monkey.c`, `mk_server/mk_server.c` + Initializes the server and worker threads. +3. `mk_server/mk_scheduler.c` + Drives socket events into protocol handlers. +4. `mk_server/mk_http.c` + Owns HTTP session lifecycle, request preparation, response handling, + range parsing, file serving, keepalive, and teardown. +5. `mk_server/mk_http_parser.c` + Parses the request line, headers, body state, and chunked transfer coding. +6. `mk_server/mk_header.c`, `mk_server/mk_stream.c` + Build and send responses. + +Useful supporting code: + +- `mk_server/mk_vhost.c` + Virtual host lookup, per-vhost file descriptor table. +- `mk_server/mk_mimetype.c` + File extension to MIME mapping. +- `mk_server/mk_user.c` + `~user` URI handling. +- `mk_server/mk_plugin.c` + Plugin registration and API exposure. +- `mk_core/mk_event_*.c` + Backend-specific event loop implementations. + +## Build and verification + +Typical local build entrypoint: + +```bash +cmake --build build +``` + +If a fresh build tree is needed, inspect `CMakeLists.txt` and the generated +`build/` layout before changing build flags. The project currently requires +CMake 3.20 and produces `build/bin/monkey`. + +## Commit style used in this repository + +Follow the existing Git history, not the older wording in `CONTRIBUTING.md`. + +### Subject format + +Use a short, lowercase, scope-prefixed subject. The common patterns are: + +- `build: bump to v1.8.7` +- `server: clean thread destroy on worker loop exit` +- `server: http: move initialization of request headers to request init` +- `core: event: Plug descriptor leaks in an error case.` +- `parser: fixed header loss issue caused by duplicated headers` +- `logger: set log file permissions to 0600, closes CVE-2013-1771 (#413)` + +### Prefix rules + +Pick the narrowest stable prefix that matches the area being changed. + +- `build:` for version bumps, CMake, workflows, packaging +- `core:` for `mk_core/` functionality +- `server:` for `mk_server/` functionality +- `server: http:` for `mk_server/mk_http.c` and closely related request flow +- `server: parser:` or `server: http_parser:` for parser-specific work +- `plugin:` or a plugin-specific prefix when the change is isolated there +- `test:` for tests +- `logger:`, `scheduler:`, `mimetype:`, `config:` when the change is clearly + isolated to that subsystem and history already uses that style +- backend-specific prefixes like `mk_event_kqueue:` are acceptable when the + change is narrow and entirely local to that backend + +### Subject style rules + +- keep it concise +- prefer lowercase after the prefix +- use colon-separated scopes, not bracket tags +- do not invent long marketing titles +- keep the subject under 80 characters +- match existing nouns already used in history where possible + +Good examples for this tree: + +- `server: http: reject malformed range delimiters` +- `server: http: avoid reusing invalid request state` +- `server: parser: validate chunk length tokens strictly` +- `core: memory: handle null mk_ptr_to_buf input` + +Bad examples for this tree: + +- `Fix CVEs` +- `Monkey: important security fixes` +- `HTTP: Add Various Improvements` +- `misc: cleanup` + +## Commit body rules + +The older contribution guide still applies well here. + +- include a body for non-trivial changes +- wrap body lines at about 80 columns +- explain the bug, the fix, and any verification done +- if the change is security-related, describe the faulty path precisely +- if multiple root causes exist, prefer separate commits + +When I am asked to commit in this repository, default behavior should be: + +1. split unrelated changes into separate commits +2. choose the narrowest prefix from the existing history +3. write a short lowercase subject +4. add a body for anything beyond trivial cleanup +5. use `git commit -s` unless the user explicitly asks otherwise + +## Working rules for this repository + +- Do not touch unrelated untracked files in the worktree. +- Be careful around parser and request lifecycle code. Many bugs surface later + in teardown, not at the first invalid input. +- Prefer minimal targeted fixes over broad refactors unless requested. +- When a bug crosses files, still group the commit by root cause, not by file. +- For security fixes, verify behavior on the built binary, not only by code + inspection. diff --git a/lib/monkey/CMakeLists.txt b/lib/monkey/CMakeLists.txt index 3dc1ef28a01..599f8b43c98 100644 --- a/lib/monkey/CMakeLists.txt +++ b/lib/monkey/CMakeLists.txt @@ -23,7 +23,7 @@ endif() # Monkey Version set(MK_VERSION_MAJOR 1) set(MK_VERSION_MINOR 8) -set(MK_VERSION_PATCH 7) +set(MK_VERSION_PATCH 8) set(MK_VERSION_STR "${MK_VERSION_MAJOR}.${MK_VERSION_MINOR}.${MK_VERSION_PATCH}") # Output paths @@ -59,10 +59,15 @@ option(MK_PLUGIN_CGI "CGI support" No) option(MK_PLUGIN_CHEETAH "Cheetah Shell Interface" No) option(MK_PLUGIN_DIRLISTING "Directory Listing" Yes) option(MK_PLUGIN_FASTCGI "FastCGI" No) -option(MK_PLUGIN_LIANA "Basic network layer" Yes) option(MK_PLUGIN_LOGGER "Log Writer" No) option(MK_PLUGIN_MANDRIL "Security" Yes) -option(MK_PLUGIN_TLS "TLS/SSL support" No) +option(MK_TLS "TLS/SSL support" Yes) +set(MK_TLS_BACKEND "auto" CACHE STRING "TLS backend: auto, openssl, or mbedtls") +set_property(CACHE MK_TLS_BACKEND PROPERTY STRINGS auto openssl mbedtls) + +if(DEFINED MK_PLUGIN_TLS) + set(MK_TLS ${MK_PLUGIN_TLS}) +endif() # Options to build Monkey with/without binary and # static/dynamic library modes (default is always just @@ -101,12 +106,6 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") set(MK_SYSTEM_MALLOC 1) endif() -if(MK_STATIC_PLUGINS) - set(MK_STATIC_PLUGINS "${MK_STATIC_PLUGINS},liana") -else() - set(MK_STATIC_PLUGINS "liana") -endif() - # Variable to be populated by plugins/CMakeLists.txt. It will contain the # code required to initialize any static plugin. set(STATIC_PLUGINS_INIT "") @@ -133,6 +132,10 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") MK_DEFINITION(_CRT_SECURE_NO_WARNINGS) endif() +if (MK_TLS) + MK_DEFINITION(MK_HAVE_TLS) +endif() + # Enable experimental (dev) HTTP/2 support if (MK_HTTP2) MK_DEFINITION(MK_HAVE_HTTP2) @@ -233,6 +236,23 @@ endif() # =========== CONFIGURATION FILES============= # ============================================ +# Build/runtime paths +# +# These defaults make an in-tree build immediately runnable via ./bin/monkey +# while still allowing packagers to override them with cache entries. +set(MK_PATH_CONF "${PROJECT_BINARY_DIR}/conf" CACHE PATH + "Default Monkey configuration directory") +set(MK_PATH_LOG "${PROJECT_BINARY_DIR}/logs" CACHE PATH + "Default Monkey log directory") +set(MK_PATH_PIDPATH "${PROJECT_BINARY_DIR}/run" CACHE PATH + "Default Monkey PID directory") +set(MK_PATH_WWW "${PROJECT_SOURCE_DIR}/htdocs" CACHE PATH + "Default Monkey document root") + +file(MAKE_DIRECTORY "${MK_PATH_CONF}") +file(MAKE_DIRECTORY "${MK_PATH_LOG}") +file(MAKE_DIRECTORY "${MK_PATH_PIDPATH}") + # Default values for conf/monkey.conf set(MK_CONF_LISTEN "2001") set(MK_CONF_WORKERS "0") @@ -251,6 +271,7 @@ set(MK_CONF_SYMLINK "Off") set(MK_CONF_DEFAULT_MIME "text/plain") set(MK_CONF_FDT "On") set(MK_CONF_OVERCAPACITY "Resist") +set(MK_PIDFILE "${MK_CONF_PIDFILE}") # Default values for conf/sites/default set(MK_VH_SERVERNAME "127.0.0.1") @@ -318,6 +339,7 @@ add_subdirectory(deps/rbtree) add_subdirectory(deps/regex) add_subdirectory(deps/flb_libco) add_subdirectory(mk_core) +add_subdirectory(tls) add_subdirectory(plugins) add_subdirectory(mk_server) diff --git a/lib/monkey/CONTRIBUTING.md b/lib/monkey/CONTRIBUTING.md index e82a8af7378..6f128a82e62 100644 --- a/lib/monkey/CONTRIBUTING.md +++ b/lib/monkey/CONTRIBUTING.md @@ -21,8 +21,10 @@ You have to pay attention to the code indentation, tabs are 4 spaces, spaces on When you commit your local changes in your repository (before to push to Github), we need you take care of the following: - - Your principal commit message (one line subject) must be prefixed with the core section name, e.g: If you are adding a new but missing protocol feature it could be __HTTP: add new XYZ method__. + - Your principal commit message (one line subject) must be prefixed with the affected area name. Follow the style used in the existing history, e.g: `build: ...`, `core: ...`, `server: ...`, `server: http: ...`, `server: parser: ...`. - The Subject of the commit must not be longer than 80 characters. + - Keep the subject short and prefer lowercase wording after the prefix. + - Use the narrowest practical scope prefix for the change. - On the commit body, each line should not be longer than 80 characters. - On most of cases we want full description about what your patch is doing, the patch description should be self descriptive.. like for dummies. Do not assume everybody knows what you are doing and on each like do not exceed 80 characters. - When running the __git commit__ command, make sure you are using the __-s__ flag, that will add a Signed-off comment in the patch description. @@ -31,19 +33,27 @@ Expanding a bit the example feature message we could use the following command: > $ git commit -a -s > -> HTTP: add new XYZ method +> server: http: add new xyz method > -> This patch adds the missing XYZ method described in RCF2616 in the +> This patch adds the missing XYZ method described in RFC2616 in the > section 12.4.x.a, it do not alter the core behavior but if the new > method is requested it will take care of the proper handling. > -> the patch have been tested using tools A & B. +> The patch has been tested using tools A & B. > > Signed-off-by: Your Name +Some recent examples from this repository are: + + - `server: clean thread destroy on worker loop exit` + - `server: http: move initialization of request headers to request init` + - `server: parser: remove unnecessary index updater` + - `build: bump to v1.8.7` + - `core: event: Plug descriptor leaks in an error case.` + If you want to see a real example, run the following command: -> $ git log 4efbc11bafeb56fbe2b4f0f6925671630ce84125 +> $ git log --oneline --no-merges -20 Your path/patches should be fully documented, that will make the review process faster for us, and a faster merge for you. diff --git a/lib/monkey/README.md b/lib/monkey/README.md index 26040a45ea6..c0e9c487259 100644 --- a/lib/monkey/README.md +++ b/lib/monkey/README.md @@ -1,59 +1,143 @@ -# Monkey Server +# Monkey HTTP Server -[Monkey](http://monkey-project.com) is a fast and lightweight Web Server for Linux. It has been designed to be very scalable with low memory and CPU consumption, the perfect solution for Embedded Linux and high end production environments. +Monkey is a lightweight HTTP server for Linux written in C. It keeps the core +small, favors predictable resource usage, and exposes a plugin-oriented +architecture that works both as a standalone web server and as a reusable +runtime foundation for HTTP-facing components. -Besides the common features as HTTP server, it expose a flexible C API which aims to behave as a fully HTTP development framework, so it can be extended as desired through the plugins interface. +The repository ships the `monkey` executable, the shared runtime in +`mk_core/`, the HTTP server implementation in `mk_server/`, optional plugins, +test targets, fuzzing entrypoints, and the default configuration and site +assets used by local builds. -For more details please refer to the [official documentation](http://monkey-project.com/documentation/). +## Why Monkey -## Features +- Small C codebase with a focused HTTP/1.1 server implementation +- Event-driven scheduler with worker threads +- Virtual hosts, keepalive, range requests, and static file serving +- Optional plugins for TLS, CGI, FastCGI, logging, auth, shell access, and + directory listing +- Build-time feature switches through CMake instead of a large external stack +- Suitable for direct server use or embedding-oriented integration work -- HTTP/1.1 Compliant -- Hybrid Networking Model: Asynchronous mode + fixed Threads -- Indented configuration style -- Versatile plugin subsystem / API -- x86, x86_64 & ARM compatible -- More features: - - SSL - - IPv6 - - Basic Auth - - Log writer - - Security - - Directory Listing - - CGI - - FastCGI - - Much more! -- Embeddable as a shared library +## Quick Start -## Requirements +Requirements: -When building Monkey it needs: +- Linux +- CMake 3.20 or newer +- A C compiler with GNU99 support +- POSIX threads -- CMake >= 2.8 -- Glibc >= 2.5 -- GNU C Compiler >= 3.2 +Build: -Monkey requires the following components on runtime: +```bash +cmake -S . -B build +cmake --build build +``` -- Linux Kernel >= 2.6.32 -- Pthreads support +Run from the source tree with the bundled configuration: -## Writing Scalable Web Services +```bash +./build/bin/monkey -c conf +``` -If you are interested into use [Monkey](http://monkey-project.com) as a base platform build scalable web services, we recommend you check our [Duda I/O](http://duda.io) project made for that purpose. +Serve a directory without editing config files: -## Join us! +```bash +./build/bin/monkey -o htdocs +``` -Monkey is an open organization so we want to hear about you, we continue growing and you can be part of it!, you can reach us at: +The generated binary is: -- Mailing list: http://lists.monkey-project.com -- IRC: irc.freenode.net #monkey -- Twitter: http://www.twitter.com/monkeywebserver -- Linkedin: http://www.linkedin.com/groups/Monkey-HTTP-Daemon-3211216 -- Freecode: http://freecode.com/projects/monkey (R.I.P) +```text +build/bin/monkey +``` -If you want to get involved, please also refer to our [Contributing](https://github.com/monkey/monkey/blob/master/CONTRIBUTING.md) guidelines. +## Build Options -## Author +Useful CMake toggles from the current build system: -Eduardo Silva +- `MK_TLS=On|Off` enable or disable TLS support +- `MK_TLS_BACKEND=auto|openssl|mbedtls` choose the TLS backend +- `MK_PLUGIN_CGI=On` +- `MK_PLUGIN_FASTCGI=On` +- `MK_PLUGIN_LOGGER=On` +- `MK_PLUGIN_CHEETAH=On` +- `MK_TESTS=On` build native test targets +- `MK_HTTP2=On` enable in-development HTTP/2 code paths +- `MK_DEBUG=On` build with debug symbols + +Example: + +```bash +cmake -S . -B build \ + -DMK_TESTS=On \ + -DMK_PLUGIN_LOGGER=On \ + -DMK_TLS=On +cmake --build build +``` + +## Runtime Notes + +Common command-line options: + +- `-c, --configdir` set the configuration directory +- `-s, --serverconf` choose the main server config file +- `-p, --port` override the listen port +- `-w, --workers` override the worker count +- `-D, --daemon` run in the background +- `--https` enable HTTPS on configured listeners +- `-b, --build` print build information +- `-h, --help` show the full command reference + +Configuration templates live under `conf/`. The default static site content for +local runs lives under `htdocs/`. + +## Repository Layout + +- `mk_core/` core utilities, memory, events, files, strings, and threading +- `mk_server/` HTTP server, scheduler, request lifecycle, headers, streams, + virtual hosts, and plugin integration +- `mk_bin/` executable entrypoints and signal handling +- `include/monkey/` public and internal headers +- `plugins/` optional runtime features +- `test/` native tests used by CMake +- `fuzz/` fuzzing entrypoints and helpers +- `qa/` request fixtures and QA material +- `conf/` configuration templates +- `htdocs/` default site assets + +## Development + +Build test targets: + +```bash +cmake -S . -B build -DMK_TESTS=On +cmake --build build +``` + +Additional notes: + +- Fuzzing notes live in [FUZZ.md](FUZZ.md) +- Arduino YUN cross-build notes live in [ARDUINO_YUN.md](ARDUINO_YUN.md) +- Contribution rules live in [CONTRIBUTING.md](CONTRIBUTING.md) +- Local repository conventions for agents live in [AGENTS.md](AGENTS.md) + +## Project Links + +- Repository: https://github.com/monkey/monkey +- Issues: https://github.com/monkey/monkey/issues +- Project site: https://monkeywebserver.com +- Documentation: https://monkeywebserver.com/documentation/ + +## Contributing + +Send changes through GitHub pull requests. Before opening one, read +[CONTRIBUTING.md](CONTRIBUTING.md) and follow the commit style already used in +the repository history: narrow scope prefixes, short lowercase subjects, and a +proper body for non-trivial changes. + +## License + +Monkey source files are licensed under the Apache License, Version 2.0. diff --git a/lib/monkey/htdocs/index.html b/lib/monkey/htdocs/index.html index a5b6e16c748..b939a29985f 100644 --- a/lib/monkey/htdocs/index.html +++ b/lib/monkey/htdocs/index.html @@ -9,7 +9,7 @@ - Monkey Server v1.6 + Monkey Server @@ -55,12 +55,12 @@
  • - + Documentation
  • - Blog + Website
  • Source Code @@ -78,11 +78,11 @@
    -

    Monkey Server v1.6

    +

    Monkey Server

    - Monkey is a high performance Open Source Web Server and + Monkey is a high performance Open Source Web Server and development stack. It has been designed to be scalable by nature through low memory and CPU consumption, the right choice for High-End production servers and Embedded devices.

    @@ -98,15 +98,15 @@

    Monkey Server v1.6

    -

    Release Notes

    -

    Monkey v1.6.0

    +

    Documentation

    +

    Get started and go deeper

    - This version is a major improvement since the previous series: improved TLS, add additional support for OSX and BSD, the scheduler is aware about protocol handlers (HTTP2 is coming!), a new HTTP parser have been implemented, the plugins can be static or dynamic, the build system is now based on CMake, general performance have been improved and much more. We invite you to read our official release notes: + Monkey ships as a compact event-driven web server focused on low resource usage, extensibility and predictable behavior. For installation, configuration and operational guidance, use the official documentation:

    @@ -115,7 +115,7 @@

    Release Notes

    Design, goals and Vision

    Performance & Innovation

    - Monkey is a complete event-driven HTTP framework. It have been designed as a + Monkey is a complete event-driven HTTP framework. It have been designed as a small core capable to extend it behavior through the plugin interface.

    diff --git a/lib/monkey/include/monkey/mk_config.h b/lib/monkey/include/monkey/mk_config.h index f6552815347..c65414fadd7 100644 --- a/lib/monkey/include/monkey/mk_config.h +++ b/lib/monkey/include/monkey/mk_config.h @@ -142,6 +142,11 @@ struct mk_server * is used when overriding the configuration from some caller */ char *transport_layer; + int tls_mode; + char *tls_cert_file; + char *tls_cert_chain_file; + char *tls_key_file; + char *tls_dh_param_file; /* Define the default mime type when is not possible to find the proper one */ struct mk_list mimetype_list; diff --git a/lib/monkey/include/monkey/mk_core/mk_string.h b/lib/monkey/include/monkey/mk_core/mk_string.h index dddf9c6ce54..18281e7214c 100644 --- a/lib/monkey/include/monkey/mk_core/mk_string.h +++ b/lib/monkey/include/monkey/mk_core/mk_string.h @@ -76,6 +76,7 @@ void mk_string_split_free(struct mk_list *list); int mk_string_trim(char **str); char *mk_string_build(char **buffer, unsigned long *len, const char *format, ...) PRINTF_WARNINGS(3,4); +char *mk_string_html_escape(const char *str); #if defined (__GNUC__) || defined (_WIN32) int mk_string_itop(uint64_t value, mk_ptr_t *p); diff --git a/lib/monkey/include/monkey/mk_core/mk_win32_socketpair.h b/lib/monkey/include/monkey/mk_core/mk_win32_socketpair.h new file mode 100644 index 00000000000..2fd2190ca51 --- /dev/null +++ b/lib/monkey/include/monkey/mk_core/mk_win32_socketpair.h @@ -0,0 +1,30 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2026 Eduardo Silva + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MK_WIN32_SOCKETPAIR_H +#define MK_WIN32_SOCKETPAIR_H + +#ifdef _WIN32 +#include +#include + +int mk_win32_socketpair(SOCKET pair[2]); +#endif + +#endif diff --git a/lib/monkey/include/monkey/mk_http.h b/lib/monkey/include/monkey/mk_http.h index 42204f9c6fe..b28db86a250 100644 --- a/lib/monkey/include/monkey/mk_http.h +++ b/lib/monkey/include/monkey/mk_http.h @@ -135,7 +135,7 @@ struct mk_http_session char *body; /* Initial fixed size buffer for small requests */ - char body_fixed[MK_REQUEST_CHUNK]; + char body_fixed[MK_REQUEST_CHUNK + 1]; /* * FIXME: in previous versions of Monkey we used to parse the complete request diff --git a/lib/monkey/include/monkey/mk_http_parser.h b/lib/monkey/include/monkey/mk_http_parser.h index 9e3b365eef7..465ea0e4962 100644 --- a/lib/monkey/include/monkey/mk_http_parser.h +++ b/lib/monkey/include/monkey/mk_http_parser.h @@ -389,7 +389,11 @@ int mk_http_parser_chunked_decode_buf(struct mk_http_parser *p, static inline int mk_http_parser_more(struct mk_http_parser *p, int len) { - if (abs(len - p->i) - 1 > 0) { + if (len <= 0 || p->i < 0) { + return MK_FALSE; + } + + if ((p->i + 1) < len) { return MK_TRUE; } diff --git a/lib/monkey/include/monkey/mk_net.h b/lib/monkey/include/monkey/mk_net.h index 1bcfac88f71..6e5c245afe4 100644 --- a/lib/monkey/include/monkey/mk_net.h +++ b/lib/monkey/include/monkey/mk_net.h @@ -22,6 +22,7 @@ #include #include +#include struct mk_net_connection { struct mk_event event; @@ -32,6 +33,9 @@ struct mk_net_connection { }; int mk_net_init(); +struct mk_plugin_network *mk_net_transport_default(); +int mk_net_transport_event_interest(struct mk_plugin_network *transport, + int fd, int fallback); struct mk_net_connection *mk_net_conn_create(char *addr, int port); int mk_net_conn_write(struct mk_channel *channel, diff --git a/lib/monkey/include/monkey/mk_plugin_net.h b/lib/monkey/include/monkey/mk_plugin_net.h index e60c74cf73b..9a7782044af 100644 --- a/lib/monkey/include/monkey/mk_plugin_net.h +++ b/lib/monkey/include/monkey/mk_plugin_net.h @@ -33,6 +33,7 @@ struct mk_plugin_network { int (*writev) (struct mk_plugin *, int, struct mk_iov *); int (*close) (struct mk_plugin *, int); int (*send_file) (struct mk_plugin *, int, int, off_t *, size_t); + int (*event_interest) (struct mk_plugin *, int, int); int buffer_size; struct mk_plugin *plugin; }; diff --git a/lib/monkey/include/monkey/mk_server.h b/lib/monkey/include/monkey/mk_server.h index f84a7d632e2..cc0500f1aa7 100644 --- a/lib/monkey/include/monkey/mk_server.h +++ b/lib/monkey/include/monkey/mk_server.h @@ -24,6 +24,7 @@ #include #include #include +#include #define MK_SERVER_SIGNAL_START 0xEEEEEEEE #define MK_SERVER_SIGNAL_STOP 0xDDDDDDDD @@ -33,7 +34,7 @@ struct mk_server_listen struct mk_event event; int server_fd; - struct mk_plugin *network; + struct mk_plugin_network *network; struct mk_sched_handler *protocol; struct mk_config_listener *listen; struct mk_list _head; diff --git a/lib/monkey/include/monkey/mk_tls_transport.h b/lib/monkey/include/monkey/mk_tls_transport.h new file mode 100644 index 00000000000..b18bd1cda80 --- /dev/null +++ b/lib/monkey/include/monkey/mk_tls_transport.h @@ -0,0 +1,15 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef MK_TLS_TRANSPORT_H +#define MK_TLS_TRANSPORT_H + +#include +#include + +int mk_tls_enabled(void); +int mk_tls_init(struct mk_server *server); +void mk_tls_thread_init(struct mk_server *server); +void mk_tls_exit(struct mk_server *server); +struct mk_plugin_network *mk_tls_transport(void); + +#endif diff --git a/lib/monkey/integration_tests/conftest.py b/lib/monkey/integration_tests/conftest.py new file mode 100644 index 00000000000..c2c78053b71 --- /dev/null +++ b/lib/monkey/integration_tests/conftest.py @@ -0,0 +1,18 @@ +import logging + + +def configure_logging(): + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + if not logger.handlers: + handler = logging.StreamHandler() + handler.setLevel(logging.INFO) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + +logger = configure_logging() diff --git a/lib/monkey/integration_tests/pytest.ini b/lib/monkey/integration_tests/pytest.ini new file mode 100644 index 00000000000..7b6de672b5f --- /dev/null +++ b/lib/monkey/integration_tests/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +testpaths = tests +pythonpath = . src +log_cli = 1 +log_cli_level = INFO +log_cli_format = %(asctime)s - %(levelname)s - %(message)s +log_cli_date_format = %Y-%m-%d %H:%M:%S diff --git a/lib/monkey/integration_tests/requirements.txt b/lib/monkey/integration_tests/requirements.txt new file mode 100644 index 00000000000..75b4ea88895 --- /dev/null +++ b/lib/monkey/integration_tests/requirements.txt @@ -0,0 +1 @@ +pytest>=8,<9 diff --git a/lib/monkey/integration_tests/run_tests.py b/lib/monkey/integration_tests/run_tests.py new file mode 100644 index 00000000000..f0858cd0bfc --- /dev/null +++ b/lib/monkey/integration_tests/run_tests.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + + +SUITE_ROOT = Path(__file__).resolve().parent +VENV_PYTHON = SUITE_ROOT / ".venv" / "bin" / "python3" +REEXEC_ENV = "MONKEY_INTEGRATION_REEXEC" + + +def _maybe_reexec_in_venv() -> None: + if os.environ.get(REEXEC_ENV) == "1": + return + + if not VENV_PYTHON.is_file(): + return + + current = Path(sys.executable).resolve() + target = VENV_PYTHON.resolve() + + if current == target: + return + + env = os.environ.copy() + env[REEXEC_ENV] = "1" + os.execve(str(target), [str(target), str(Path(__file__).resolve()), *sys.argv[1:]], env) + + +_maybe_reexec_in_venv() + +try: + import pytest # noqa: E402 +except ModuleNotFoundError: + if VENV_PYTHON.is_file() and os.environ.get(REEXEC_ENV) != "1": + env = os.environ.copy() + env[REEXEC_ENV] = "1" + os.execve(str(VENV_PYTHON), [str(VENV_PYTHON), str(Path(__file__).resolve()), *sys.argv[1:]], env) + raise + + +def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]: + parser = argparse.ArgumentParser(description="Run Monkey integration tests.") + parser.add_argument("--plain-binary", help="Set MONKEY_BIN_PLAIN for this run.") + parser.add_argument("--openssl-binary", help="Set MONKEY_BIN_OPENSSL for this run.") + parser.add_argument("--mbedtls-binary", help="Set MONKEY_BIN_MBEDTLS for this run.") + parser.add_argument( + "--binaries", + choices=["all", "plain", "openssl", "mbedtls"], + default="all", + help="Limit the suite to a specific Monkey binary family.", + ) + parser.add_argument("--valgrind", action="store_true", help="Run Monkey under valgrind.") + parser.add_argument( + "--valgrind-strict", + action="store_true", + help="Run Monkey under valgrind and fail on reported leaks/errors.", + ) + parser.add_argument("--quiet", action="store_true", help="Run pytest with -q.") + return parser.parse_known_args(argv) + + +def build_pytest_args(args: argparse.Namespace, passthrough: list[str]) -> list[str]: + pytest_args = ["--rootdir", str(SUITE_ROOT)] + + if args.quiet: + pytest_args.append("-q") + else: + pytest_args.append("-vv") + + pytest_args.extend(passthrough) + if not passthrough: + pytest_args.append(str(SUITE_ROOT / "tests")) + + return pytest_args + + +def main(argv: list[str]) -> int: + args, passthrough = parse_args(argv) + + if args.plain_binary: + os.environ["MONKEY_BIN_PLAIN"] = args.plain_binary + if args.openssl_binary: + os.environ["MONKEY_BIN_OPENSSL"] = args.openssl_binary + if args.mbedtls_binary: + os.environ["MONKEY_BIN_MBEDTLS"] = args.mbedtls_binary + if args.binaries != "all": + os.environ["MONKEY_TEST_BINARIES"] = args.binaries + if args.valgrind: + os.environ["MONKEY_VALGRIND"] = "1" + if args.valgrind_strict: + os.environ["MONKEY_VALGRIND"] = "1" + os.environ["MONKEY_VALGRIND_STRICT"] = "1" + + return pytest.main(build_pytest_args(args, passthrough)) + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/lib/monkey/integration_tests/src/__init__.py b/lib/monkey/integration_tests/src/__init__.py new file mode 100644 index 00000000000..f7049ed1224 --- /dev/null +++ b/lib/monkey/integration_tests/src/__init__.py @@ -0,0 +1 @@ +# Monkey integration test helpers package. diff --git a/lib/monkey/integration_tests/src/monkey_manager.py b/lib/monkey/integration_tests/src/monkey_manager.py new file mode 100644 index 00000000000..259b2b5b070 --- /dev/null +++ b/lib/monkey/integration_tests/src/monkey_manager.py @@ -0,0 +1,338 @@ +from __future__ import annotations + +import http.client +import os +import re +import shutil +import socket +import ssl +import subprocess +import tempfile +import time +from dataclasses import dataclass +from pathlib import Path +ENV_MONKEY_BIN_PLAIN = "MONKEY_BIN_PLAIN" +ENV_MONKEY_BIN_OPENSSL = "MONKEY_BIN_OPENSSL" +ENV_MONKEY_BIN_MBEDTLS = "MONKEY_BIN_MBEDTLS" +ENV_MONKEY_VALGRIND = "MONKEY_VALGRIND" +ENV_MONKEY_VALGRIND_STRICT = "MONKEY_VALGRIND_STRICT" + + +def generate_tls_assets(directory: Path) -> None: + subprocess.run( + [ + "openssl", + "req", + "-x509", + "-newkey", + "rsa:2048", + "-keyout", + str(directory / "rsa_key.pem"), + "-out", + str(directory / "srv_cert.pem"), + "-sha256", + "-days", + "1", + "-nodes", + "-subj", + "/CN=127.0.0.1", + ], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + subprocess.run( + [ + "openssl", + "dhparam", + "-out", + str(directory / "dhparam.pem"), + "512", + ], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + shutil.copy2(directory / "rsa_key.pem", directory / "rsa.pem") + + +def find_available_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("127.0.0.1", 0)) + return sock.getsockname()[1] + + +def wait_for_port(host: str, port: int, timeout: float = 10.0) -> None: + deadline = time.time() + timeout + + while time.time() < deadline: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(0.2) + if sock.connect_ex((host, port)) == 0: + return + time.sleep(0.1) + + raise TimeoutError(f"Timed out waiting for {host}:{port}") + + +def replace_config_value(config_text: str, key: str, value: str) -> str: + pattern = rf"^(\s*{re.escape(key)}\s+).*$" + updated, count = re.subn(pattern, rf"\g<1>{value}", config_text, count=1, flags=re.MULTILINE) + if count != 1: + raise AssertionError(f"Could not replace config key: {key}") + return updated + + +@dataclass +class MonkeyBinary: + label: str + env_var: str + tls: bool + + +@dataclass +class HttpResponse: + status: int + reason: str + headers: dict[str, str] + body: bytes + + +class MonkeyManager: + def __init__(self, binary_path: str, tls: bool = False) -> None: + self.binary_path = Path(binary_path).resolve() + self.tls = tls + self.host = "127.0.0.1" + self.port = find_available_port() + self.workdir_obj = tempfile.TemporaryDirectory(prefix="monkey-it-") + self.workdir = Path(self.workdir_obj.name) + self.confdir = self.workdir / "conf" + self.htdocs = self.workdir / "htdocs" + self.log_file = self.workdir / "monkey.log" + self.valgrind_log_file = self.workdir / "valgrind.log" + self.process: subprocess.Popen[str] | None = None + self.valgrind_enabled = os.environ.get(ENV_MONKEY_VALGRIND) == "1" + self.valgrind_strict = os.environ.get(ENV_MONKEY_VALGRIND_STRICT) == "1" + + def _read_log_file(self) -> str: + if not self.log_file.exists(): + return "" + return self.log_file.read_text(encoding="utf-8", errors="replace") + + @property + def build_root(self) -> Path: + return self.binary_path.parent.parent + + @property + def url(self) -> str: + scheme = "https" if self.tls else "http" + return f"{scheme}://{self.host}:{self.port}/" + + def _copy_build_conf(self, filename: str) -> None: + src = self.build_root / "conf" / filename + if not src.is_file(): + raise FileNotFoundError(f"Missing config asset: {src}") + shutil.copy2(src, self.confdir / filename) + + def _prepare_tls_assets(self) -> None: + self._copy_build_conf("tls.conf") + generate_tls_assets(self.confdir) + + def prepare(self) -> None: + self.confdir.mkdir(parents=True, exist_ok=True) + self.htdocs.mkdir(parents=True, exist_ok=True) + (self.htdocs / "index.html").write_text( + "Monkey integration test payload\n", + encoding="utf-8", + ) + + self._copy_build_conf("monkey.mime") + if self.tls: + self._prepare_tls_assets() + + listen_flags = " tls" if self.tls else "" + monkey_conf = ( + "[SERVER]\n" + f" Listen {self.host}:{self.port}{listen_flags}\n" + " Workers 1\n" + " Timeout 15\n" + f" PidFile {self.workdir / 'monkey.pid'}\n" + " User root\n" + " UserDir disabled\n" + " Indexfile index.html\n" + " HideVersion Off\n" + " Resume On\n" + " KeepAlive On\n" + " KeepAliveTimeout 5\n" + " MaxKeepAliveRequest 50\n" + " MaxRequestSize 32\n" + " SymLink Off\n" + " DefaultMimeType text/plain\n" + " FDT On\n" + " OverCapacity Resist\n" + ) + (self.confdir / "monkey.conf").write_text(monkey_conf, encoding="utf-8") + + def _build_command(self) -> list[str]: + command = [ + str(self.binary_path), + "-c", + str(self.confdir), + "-s", + "monkey.conf", + "-o", + str(self.htdocs), + ] + + if not self.valgrind_enabled: + return command + + valgrind_command = [ + "valgrind", + f"--log-file={self.valgrind_log_file}", + "--leak-check=full", + "--show-leak-kinds=all", + "--track-origins=yes", + "--error-exitcode=99", + ] + + if self.valgrind_strict: + valgrind_command.append("--errors-for-leak-kinds=definite,possible,indirect") + + valgrind_command.extend(command) + return valgrind_command + + def start(self) -> None: + self.prepare() + log_handle = self.log_file.open("w", encoding="utf-8") + self.process = subprocess.Popen( + self._build_command(), + stdout=log_handle, + stderr=subprocess.STDOUT, + text=True, + ) + try: + wait_for_port(self.host, self.port) + except TimeoutError as exc: + log_handle.flush() + if self.process.poll() is not None: + raise RuntimeError( + f"Monkey exited before listening.\n{self._read_log_file()}" + ) from exc + raise RuntimeError( + f"Monkey did not start listening in time.\n{self._read_log_file()}" + ) from exc + + def _validate_valgrind(self, return_code: int) -> None: + if not self.valgrind_enabled: + return + + if return_code == 99: + raise AssertionError(self.valgrind_log_file.read_text(encoding="utf-8")) + + def stop(self) -> None: + if self.process is not None: + self.process.terminate() + try: + return_code = self.process.wait(timeout=5) + except subprocess.TimeoutExpired: + self.process.kill() + return_code = self.process.wait(timeout=5) + + self._validate_valgrind(return_code) + self.process = None + + self.workdir_obj.cleanup() + + def fetch(self) -> tuple[int, bytes, dict[str, str]]: + response = self.request("GET", "/", headers={"Connection": "close"}) + return response.status, response.body, response.headers + + def open_http_connection(self) -> http.client.HTTPConnection: + if self.tls: + context = ssl._create_unverified_context() + return http.client.HTTPSConnection(self.host, self.port, timeout=5, context=context) + return http.client.HTTPConnection(self.host, self.port, timeout=5) + + def request( + self, + method: str, + path: str, + body: bytes | None = None, + headers: dict[str, str] | None = None, + ) -> HttpResponse: + connection = self.open_http_connection() + try: + connection.request(method, path, body=body, headers=headers or {}) + response = connection.getresponse() + payload = response.read() + response_headers = {k.lower(): v for k, v in response.getheaders()} + return HttpResponse(response.status, response.reason, response_headers, payload) + finally: + connection.close() + + def request_keepalive_sequence( + self, + requests_spec: list[tuple[str, str, dict[str, str] | None]], + ) -> list[HttpResponse]: + connection = self.open_http_connection() + responses = [] + + try: + for method, path, headers in requests_spec: + connection.request(method, path, headers=headers or {}) + response = connection.getresponse() + payload = response.read() + response_headers = {k.lower(): v for k, v in response.getheaders()} + responses.append(HttpResponse(response.status, response.reason, response_headers, payload)) + finally: + connection.close() + + return responses + + def raw_request(self, payload: bytes) -> bytes: + return self.raw_request_parts([payload]) + + def raw_request_parts( + self, + payloads: list[bytes], + pause_between_parts: float = 0.0, + shutdown_write: bool = False, + ) -> bytes: + sock = socket.create_connection((self.host, self.port), timeout=5) + sock.settimeout(5) + + if self.tls: + context = ssl._create_unverified_context() + sock = context.wrap_socket(sock, server_hostname=self.host) + + try: + for index, payload in enumerate(payloads): + sock.sendall(payload) + if pause_between_parts > 0 and index + 1 < len(payloads): + time.sleep(pause_between_parts) + + if shutdown_write: + try: + sock.shutdown(socket.SHUT_WR) + except OSError: + pass + + chunks = [] + while True: + try: + data = sock.recv(65535) + except ConnectionResetError: + break + except socket.timeout: + break + + if not data: + break + chunks.append(data) + + return b"".join(chunks) + finally: + sock.close() diff --git a/lib/monkey/integration_tests/tests/test_network_layer.py b/lib/monkey/integration_tests/tests/test_network_layer.py new file mode 100644 index 00000000000..12f1ba71d97 --- /dev/null +++ b/lib/monkey/integration_tests/tests/test_network_layer.py @@ -0,0 +1,491 @@ +from __future__ import annotations + +import shutil +import subprocess +import os +from pathlib import Path + +import pytest + +from src.monkey_manager import ENV_MONKEY_BIN_MBEDTLS +from src.monkey_manager import ENV_MONKEY_BIN_OPENSSL +from src.monkey_manager import ENV_MONKEY_BIN_PLAIN +from src.monkey_manager import find_available_port +from src.monkey_manager import generate_tls_assets +from src.monkey_manager import MonkeyBinary +from src.monkey_manager import MonkeyManager +from src.monkey_manager import replace_config_value +from src.monkey_manager import wait_for_port + + +BINARIES = [ + MonkeyBinary("plain", ENV_MONKEY_BIN_PLAIN, False), + MonkeyBinary("openssl", ENV_MONKEY_BIN_OPENSSL, True), + MonkeyBinary("mbedtls", ENV_MONKEY_BIN_MBEDTLS, True), +] + + +def resolve_binary(candidate: MonkeyBinary) -> str: + path = os.environ.get(candidate.env_var) + if not path: + defaults = { + ENV_MONKEY_BIN_PLAIN: "/tmp/monkey-build-default/bin/monkey", + ENV_MONKEY_BIN_OPENSSL: "/tmp/monkey-build-tls/bin/monkey", + ENV_MONKEY_BIN_MBEDTLS: "/tmp/monkey-build-mbedtls/bin/monkey", + } + path = defaults[candidate.env_var] + + if not Path(path).is_file(): + pytest.skip(f"{candidate.label} binary not available: {path}") + + return path + + +def selected_binaries() -> list[MonkeyBinary]: + selection = os.environ.get("MONKEY_TEST_BINARIES", "all") + if selection == "all": + return BINARIES + + return [candidate for candidate in BINARIES if candidate.label == selection] + + +@pytest.fixture(params=selected_binaries(), ids=lambda item: item.label) +def monkey_instance(request): + candidate = request.param + manager = MonkeyManager(resolve_binary(candidate), tls=candidate.tls) + manager.start() + try: + yield manager + finally: + manager.stop() + + +def test_serves_index_body(monkey_instance: MonkeyManager): + status, body, headers = monkey_instance.fetch() + + assert status == 200 + assert body == b"Monkey integration test payload\n" + assert headers["content-type"].startswith("text/html") + assert int(headers["content-length"]) == len(body) + + +def test_server_header_present(monkey_instance: MonkeyManager): + status, _, headers = monkey_instance.fetch() + + assert status == 200 + assert headers["server"].startswith("Monkey/") + + +def test_forbidden_error_page_escapes_reflected_uri(monkey_instance: MonkeyManager): + payload = b"GET /../\"> HTTP/1.1\r\n" + payload += f"Host: {monkey_instance.host}:{monkey_instance.port}\r\n".encode() + payload += b"Connection: close\r\n\r\n" + + response = monkey_instance.raw_request(payload) + + assert b"HTTP/1.1 403 Forbidden" in response + assert b"" not in response + assert b"/../"><script>alert(73541);</script>" in response + + +def test_server_restarts_cleanly(): + for candidate in selected_binaries(): + manager = MonkeyManager(resolve_binary(candidate), tls=candidate.tls) + manager.start() + try: + status, body, _ = manager.fetch() + assert status == 200 + assert body == b"Monkey integration test payload\n" + finally: + manager.stop() + + + manager = MonkeyManager(resolve_binary(candidate), tls=candidate.tls) + manager.start() + try: + status, body, _ = manager.fetch() + assert status == 200 + assert body == b"Monkey integration test payload\n" + finally: + manager.stop() + + +def test_binary_starts_with_no_arguments_from_build_tree(tmp_path: Path): + plain_binary = resolve_binary(MonkeyBinary("plain", ENV_MONKEY_BIN_PLAIN, False)) + build_root = Path(plain_binary).resolve().parent.parent + runtime_root = tmp_path / "runtime" + runtime_bin = runtime_root / "bin" + runtime_conf = runtime_root / "conf" + runtime_sites = runtime_conf / "sites" + runtime_htdocs = runtime_root / "htdocs" + runtime_run = runtime_root / "run" + runtime_logs = runtime_root / "logs" + port = find_available_port() + log_file = runtime_root / "monkey.log" + + runtime_bin.mkdir(parents=True) + runtime_sites.mkdir(parents=True) + runtime_htdocs.mkdir(parents=True) + runtime_run.mkdir(parents=True) + runtime_logs.mkdir(parents=True) + + shutil.copy2(build_root / "bin" / "monkey", runtime_bin / "monkey") + shutil.copy2(build_root / "conf" / "monkey.mime", runtime_conf / "monkey.mime") + shutil.copy2(build_root / "conf" / "plugins.load", runtime_conf / "plugins.load") + + monkey_conf = (build_root / "conf" / "monkey.conf").read_text(encoding="utf-8") + monkey_conf = replace_config_value(monkey_conf, "Listen", str(port)) + monkey_conf = replace_config_value( + monkey_conf, "PidFile", str((runtime_run / "monkey.pid").resolve()) + ) + (runtime_conf / "monkey.conf").write_text(monkey_conf, encoding="utf-8") + + site_conf = (build_root / "conf" / "sites" / "default").read_text(encoding="utf-8") + site_conf = replace_config_value( + site_conf, "DocumentRoot", str(runtime_htdocs.resolve()) + ) + site_conf = replace_config_value( + site_conf, "AccessLog", str((runtime_logs / "access.log").resolve()) + ) + site_conf = replace_config_value( + site_conf, "ErrorLog", str((runtime_logs / "error.log").resolve()) + ) + (runtime_sites / "default").write_text(site_conf, encoding="utf-8") + + (runtime_htdocs / "index.html").write_text( + "Monkey direct startup regression\n", + encoding="utf-8", + ) + + process = None + log_handle = log_file.open("w", encoding="utf-8") + try: + process = subprocess.Popen( + ["./bin/monkey"], + cwd=runtime_root, + stdout=log_handle, + stderr=subprocess.STDOUT, + text=True, + ) + wait_for_port("127.0.0.1", port) + + manager = MonkeyManager(plain_binary, tls=False) + manager.host = "127.0.0.1" + manager.port = port + response = manager.request("GET", "/") + assert response.status == 200 + assert response.body == b"Monkey direct startup regression\n" + finally: + log_handle.flush() + log_handle.close() + if process is not None: + process.terminate() + process.wait(timeout=5) + + +def test_binary_https_can_be_enabled_from_cli_without_tls_conf(tmp_path: Path): + plain_binary = resolve_binary(MonkeyBinary("plain", ENV_MONKEY_BIN_PLAIN, False)) + build_root = Path(plain_binary).resolve().parent.parent + runtime_root = tmp_path / "runtime-https" + runtime_bin = runtime_root / "bin" + runtime_conf = runtime_root / "conf" + runtime_sites = runtime_conf / "sites" + runtime_htdocs = runtime_root / "htdocs" + runtime_run = runtime_root / "run" + runtime_logs = runtime_root / "logs" + port = find_available_port() + log_file = runtime_root / "monkey.log" + + runtime_bin.mkdir(parents=True) + runtime_sites.mkdir(parents=True) + runtime_htdocs.mkdir(parents=True) + runtime_run.mkdir(parents=True) + runtime_logs.mkdir(parents=True) + + shutil.copy2(build_root / "bin" / "monkey", runtime_bin / "monkey") + shutil.copy2(build_root / "conf" / "monkey.mime", runtime_conf / "monkey.mime") + shutil.copy2(build_root / "conf" / "plugins.load", runtime_conf / "plugins.load") + + monkey_conf = (build_root / "conf" / "monkey.conf").read_text(encoding="utf-8") + monkey_conf = replace_config_value( + monkey_conf, "PidFile", str((runtime_run / "monkey.pid").resolve()) + ) + (runtime_conf / "monkey.conf").write_text(monkey_conf, encoding="utf-8") + + site_conf = (build_root / "conf" / "sites" / "default").read_text(encoding="utf-8") + site_conf = replace_config_value( + site_conf, "DocumentRoot", str(runtime_htdocs.resolve()) + ) + site_conf = replace_config_value( + site_conf, "AccessLog", str((runtime_logs / "access.log").resolve()) + ) + site_conf = replace_config_value( + site_conf, "ErrorLog", str((runtime_logs / "error.log").resolve()) + ) + (runtime_sites / "default").write_text(site_conf, encoding="utf-8") + + (runtime_htdocs / "index.html").write_text( + "Monkey CLI HTTPS regression\n", + encoding="utf-8", + ) + generate_tls_assets(runtime_root) + + process = None + log_handle = log_file.open("w", encoding="utf-8") + try: + process = subprocess.Popen( + [ + "./bin/monkey", + "--https", + "-p", + str(port), + "--tls-cert", + str((runtime_root / "srv_cert.pem").resolve()), + "--tls-key", + str((runtime_root / "rsa_key.pem").resolve()), + "--tls-dh", + str((runtime_root / "dhparam.pem").resolve()), + ], + cwd=runtime_root, + stdout=log_handle, + stderr=subprocess.STDOUT, + text=True, + ) + wait_for_port("127.0.0.1", port) + + manager = MonkeyManager(plain_binary, tls=True) + manager.host = "127.0.0.1" + manager.port = port + response = manager.request("GET", "/") + assert response.status == 200 + assert response.body == b"Monkey CLI HTTPS regression\n" + finally: + log_handle.flush() + log_handle.close() + if process is not None: + process.terminate() + process.wait(timeout=5) + + +def test_binary_https_without_explicit_certs_uses_builtin_fallback(tmp_path: Path): + plain_binary = resolve_binary(MonkeyBinary("plain", ENV_MONKEY_BIN_PLAIN, False)) + build_root = Path(plain_binary).resolve().parent.parent + runtime_root = tmp_path / "runtime-https-fallback" + runtime_bin = runtime_root / "bin" + runtime_conf = runtime_root / "conf" + runtime_sites = runtime_conf / "sites" + runtime_htdocs = runtime_root / "htdocs" + runtime_run = runtime_root / "run" + runtime_logs = runtime_root / "logs" + port = find_available_port() + log_file = runtime_root / "monkey.log" + + runtime_bin.mkdir(parents=True) + runtime_sites.mkdir(parents=True) + runtime_htdocs.mkdir(parents=True) + runtime_run.mkdir(parents=True) + runtime_logs.mkdir(parents=True) + + shutil.copy2(build_root / "bin" / "monkey", runtime_bin / "monkey") + shutil.copy2(build_root / "conf" / "monkey.mime", runtime_conf / "monkey.mime") + shutil.copy2(build_root / "conf" / "plugins.load", runtime_conf / "plugins.load") + shutil.copy2(build_root / "conf" / "tls.conf", runtime_conf / "tls.conf") + + monkey_conf = (build_root / "conf" / "monkey.conf").read_text(encoding="utf-8") + monkey_conf = replace_config_value( + monkey_conf, "PidFile", str((runtime_run / "monkey.pid").resolve()) + ) + (runtime_conf / "monkey.conf").write_text(monkey_conf, encoding="utf-8") + + site_conf = (build_root / "conf" / "sites" / "default").read_text(encoding="utf-8") + site_conf = replace_config_value( + site_conf, "DocumentRoot", str(runtime_htdocs.resolve()) + ) + site_conf = replace_config_value( + site_conf, "AccessLog", str((runtime_logs / "access.log").resolve()) + ) + site_conf = replace_config_value( + site_conf, "ErrorLog", str((runtime_logs / "error.log").resolve()) + ) + (runtime_sites / "default").write_text(site_conf, encoding="utf-8") + + (runtime_htdocs / "index.html").write_text( + "Monkey CLI HTTPS builtin fallback\n", + encoding="utf-8", + ) + + process = None + log_handle = log_file.open("w", encoding="utf-8") + try: + process = subprocess.Popen( + [ + "./bin/monkey", + "--https", + "-p", + str(port), + ], + cwd=runtime_root, + stdout=log_handle, + stderr=subprocess.STDOUT, + text=True, + ) + wait_for_port("127.0.0.1", port) + + manager = MonkeyManager(plain_binary, tls=True) + manager.host = "127.0.0.1" + manager.port = port + response = manager.request("GET", "/") + assert response.status == 200 + assert response.body == b"Monkey CLI HTTPS builtin fallback\n" + finally: + log_handle.flush() + log_handle.close() + if process is not None: + process.terminate() + process.wait(timeout=5) + + +def test_head_returns_headers_without_body(monkey_instance: MonkeyManager): + response = monkey_instance.request("HEAD", "/") + + assert response.status == 200 + assert response.body == b"" + assert int(response.headers["content-length"]) > 0 + assert response.headers["content-type"].startswith("text/html") + + +def test_missing_resource_returns_404(monkey_instance: MonkeyManager): + response = monkey_instance.request("GET", "/missing-file") + + assert response.status == 404 + assert b"Not Found" in response.body + + +def test_path_traversal_returns_403(monkey_instance: MonkeyManager): + response = monkey_instance.request("GET", "/../etc/passwd") + + assert response.status == 403 + + +def test_encoded_path_traversal_returns_403(monkey_instance: MonkeyManager): + response = monkey_instance.request("GET", "/%2e%2e/etc/passwd") + + assert response.status == 403 + + +def test_unknown_method_returns_501(monkey_instance: MonkeyManager): + payload = ( + b"BREW / HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + response = monkey_instance.raw_request(payload) + + assert b"HTTP/1.1 501" in response + + +def test_put_returns_405(monkey_instance: MonkeyManager): + response = monkey_instance.request("PUT", "/") + + assert response.status == 405 + + +def test_http11_without_host_returns_400(monkey_instance: MonkeyManager): + payload = ( + b"GET / HTTP/1.1\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + response = monkey_instance.raw_request(payload) + + assert b"HTTP/1.1 400" in response + + +def test_http10_without_host_is_accepted(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request( + b"GET / HTTP/1.0\r\n" + b"\r\n" + ) + + assert b"HTTP/1.1 200 OK" in response + + +def test_absolute_form_request_target_returns_400(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request( + b"GET http://localhost/ HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + + assert b"HTTP/1.1 400" in response + + +def test_keepalive_supports_multiple_requests(monkey_instance: MonkeyManager): + responses = monkey_instance.request_keepalive_sequence( + [ + ("GET", "/", {"Connection": "keep-alive"}), + ("GET", "/", {"Connection": "close"}), + ] + ) + + assert len(responses) == 2 + assert responses[0].status == 200 + assert responses[1].status == 200 + assert responses[0].body == b"Monkey integration test payload\n" + assert responses[1].body == b"Monkey integration test payload\n" + + +def test_pipelined_requests_return_both_responses(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request( + b"GET / HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Connection: keep-alive\r\n" + b"\r\n" + b"GET /missing-file HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + + assert response.count(b"HTTP/1.1 ") == 2 + assert b"HTTP/1.1 200 OK" in response + assert b"HTTP/1.1 404 Not Found" in response + + +def test_oversized_header_returns_413(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request( + b"GET / HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"X-Fill: " + (b"a" * 40000) + b"\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + + assert b"HTTP/1.1 413" in response + + +def test_split_request_across_tcp_chunks_is_parsed(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request_parts( + [ + b"GET / HTTP/1.1\r\n" + b"Host: localhost\r\n", + b"Connection: close\r\n" + b"\r\n", + ], + pause_between_parts=0.01, + ) + + assert b"HTTP/1.1 200 OK" in response + + +def test_bad_request_line_returns_400(monkey_instance: MonkeyManager): + response = monkey_instance.raw_request( + b"GET noslash HTTP/1.1\r\n" + b"Host: localhost\r\n" + b"Connection: close\r\n" + b"\r\n" + ) + + assert b"HTTP/1.1 400" in response diff --git a/lib/monkey/mk_bin/monkey.c b/lib/monkey/mk_bin/monkey.c index 15d695ba6ce..46f52444e20 100644 --- a/lib/monkey/mk_bin/monkey.c +++ b/lib/monkey/mk_bin/monkey.c @@ -24,6 +24,9 @@ #include #include +#include +#include +#include #if defined(__DATE__) && defined(__TIME__) static const char MONKEY_BUILT[] = __DATE__ " " __TIME__; @@ -37,7 +40,7 @@ static void mk_version(void) printf("Monkey HTTP Server v%s\n", MK_VERSION_STR); printf("Built : %s (%s %i.%i.%i)\n", MONKEY_BUILT, CC, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); - printf("Home : http://monkey-project.com\n"); + printf("Home : https://monkeywebserver.com\n"); fflush(stdout); } @@ -83,6 +86,11 @@ static void mk_help(int rc) printf(" -I, --pid-file\t\t\tset full path for the PID file (override config)\n"); printf(" -p, --port=PORT\t\t\tset listener TCP port (override config)\n"); printf(" -o, --one-shot=DIR\t\t\tone-shot, serve a single directory\n"); + printf(" --https\t\t\t\tenable HTTPS on configured listeners\n"); + printf(" --tls-cert=FILE\t\t\tset TLS certificate file\n"); + printf(" --tls-key=FILE\t\t\tset TLS private key file\n"); + printf(" --tls-chain=FILE\t\t\tset TLS certificate chain file\n"); + printf(" --tls-dh=FILE\t\t\tset TLS DH parameters file\n"); printf(" -t, --transport=TRANSPORT\t\tspecify transport layer (override config)\n"); printf(" -w, --workers=N\t\t\tset number of workers (override config)\n"); printf(" -m, --mimes-conf-file=FILE\t\tspecify mimes configuration file\n"); @@ -98,15 +106,115 @@ static void mk_help(int rc) printf(" -h, --help\t\t\t\tprint this help\n\n"); printf("%sDocumentation%s\n", ANSI_BOLD, ANSI_RESET); - printf(" http://monkey-project.com/documentation\n\n"); + printf(" https://monkeywebserver.com/documentation\n\n"); exit(rc); } +static int mk_dir_exists(const char *path) +{ + struct stat st; + + if (path == NULL || path[0] == '\0') { + return MK_FALSE; + } + + if (stat(path, &st) == -1) { + return MK_FALSE; + } + + if (S_ISDIR(st.st_mode) == 0) { + return MK_FALSE; + } + + return MK_TRUE; +} + +static const char *mk_default_config_dir(char *buf, size_t size, const char *argv0) +{ + char exe_path[PATH_MAX]; + char candidate[PATH_MAX]; + char *last_slash; + int written; + ssize_t len; + + if (mk_dir_exists("conf") == MK_TRUE) { + if (realpath("conf", buf) != NULL) { + return buf; + } + + written = snprintf(buf, size, "%s", "conf"); + if (written < 0 || (size_t) written >= size) { + return NULL; + } + return buf; + } + + if (MK_PATH_CONF[0] != '\0' && mk_dir_exists(MK_PATH_CONF) == MK_TRUE) { + written = snprintf(buf, size, "%s", MK_PATH_CONF); + if (written < 0 || (size_t) written >= size) { + return NULL; + } + return buf; + } + + len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len > 0) { + exe_path[len] = '\0'; + } + else if (argv0 != NULL && realpath(argv0, exe_path) != NULL) { + len = strlen(exe_path); + } + else { + return NULL; + } + + last_slash = strrchr(exe_path, '/'); + if (last_slash == NULL) { + return NULL; + } + *last_slash = '\0'; + + written = snprintf(candidate, sizeof(candidate), "%s/../conf", exe_path); + if (written < 0 || (size_t) written >= sizeof(candidate)) { + return NULL; + } + if (mk_dir_exists(candidate) == MK_TRUE) { + if (realpath(candidate, buf) != NULL) { + return buf; + } + + written = snprintf(buf, size, "%s", candidate); + if (written < 0 || (size_t) written >= size) { + return NULL; + } + return buf; + } + + written = snprintf(candidate, sizeof(candidate), "%s/conf", exe_path); + if (written < 0 || (size_t) written >= sizeof(candidate)) { + return NULL; + } + if (mk_dir_exists(candidate) == MK_TRUE) { + if (realpath(candidate, buf) != NULL) { + return buf; + } + + written = snprintf(buf, size, "%s", candidate); + if (written < 0 || (size_t) written >= size) { + return NULL; + } + return buf; + } + + return NULL; +} + /* MAIN */ int main(int argc, char **argv) { int opt; + int enable_https = MK_FALSE; char *port_override = NULL; int workers_override = -1; int run_daemon = 0; @@ -121,16 +229,35 @@ int main(int argc, char **argv) char *sites_conf_dir = NULL; char *plugins_conf_dir = NULL; char *mimes_conf_file = NULL; + char *tls_cert_file = NULL; + char *tls_key_file = NULL; + char *tls_chain_file = NULL; + char *tls_dh_file = NULL; + char default_config_dir[PATH_MAX]; + const char *resolved_config_dir; struct mk_server *server; + enum { + MK_OPT_HTTPS = 1000, + MK_OPT_TLS_CERT, + MK_OPT_TLS_KEY, + MK_OPT_TLS_CHAIN, + MK_OPT_TLS_DH + }; + static const struct option long_opts[] = { { "configdir", required_argument, NULL, 'c' }, { "serverconf", required_argument, NULL, 's' }, { "build", no_argument, NULL, 'b' }, { "daemon", no_argument, NULL, 'D' }, + { "https", no_argument, NULL, MK_OPT_HTTPS }, { "pid-file", required_argument, NULL, 'I' }, { "port", required_argument, NULL, 'p' }, { "one-shot", required_argument, NULL, 'o' }, + { "tls-cert", required_argument, NULL, MK_OPT_TLS_CERT }, + { "tls-key", required_argument, NULL, MK_OPT_TLS_KEY }, + { "tls-chain", required_argument, NULL, MK_OPT_TLS_CHAIN }, + { "tls-dh", required_argument, NULL, MK_OPT_TLS_DH }, { "transport", required_argument, NULL, 't' }, { "workers", required_argument, NULL, 'w' }, { "version", no_argument, NULL, 'v' }, @@ -159,6 +286,9 @@ int main(int argc, char **argv) case 'D': run_daemon = 1; break; + case MK_OPT_HTTPS: + enable_https = MK_TRUE; + break; case 'I': pid_file = optarg; break; @@ -168,6 +298,18 @@ int main(int argc, char **argv) case 'o': one_shot = optarg; break; + case MK_OPT_TLS_CERT: + tls_cert_file = mk_string_dup(optarg); + break; + case MK_OPT_TLS_KEY: + tls_key_file = mk_string_dup(optarg); + break; + case MK_OPT_TLS_CHAIN: + tls_chain_file = mk_string_dup(optarg); + break; + case MK_OPT_TLS_DH: + tls_dh_file = mk_string_dup(optarg); + break; case 't': transport_layer = mk_string_dup(optarg); break; @@ -211,11 +353,14 @@ int main(int argc, char **argv) server = mk_server_create(); /* set configuration path */ - if (!path_config) { - server->path_conf_root = MK_PATH_CONF; + if (path_config != NULL) { + server->path_conf_root = path_config; } else { - server->path_conf_root = path_config; + resolved_config_dir = mk_default_config_dir(default_config_dir, + sizeof(default_config_dir), + argv[0]); + server->path_conf_root = (char *) resolved_config_dir; } /* set target configuration file for the server */ @@ -272,6 +417,11 @@ int main(int argc, char **argv) server->one_shot = one_shot; server->port_override = port_override; server->transport_layer = transport_layer; + server->tls_mode = enable_https; + server->tls_cert_file = tls_cert_file; + server->tls_cert_chain_file = tls_chain_file; + server->tls_key_file = tls_key_file; + server->tls_dh_param_file = tls_dh_file; mk_version(); mk_signal_init(server); @@ -305,7 +455,10 @@ int main(int argc, char **argv) * Once the all configuration is set, let mk_server configure the * internals. Not accepting connections yet. */ - mk_server_setup(server); + if (mk_server_setup(server) != 0) { + mk_err("Startup aborted due to configuration/setup failure."); + return EXIT_FAILURE; + } /* Register PID of Monkey */ mk_utils_register_pid(server->path_conf_pidfile); diff --git a/lib/monkey/mk_core/CMakeLists.txt b/lib/monkey/mk_core/CMakeLists.txt index a1a32101783..1772830685c 100644 --- a/lib/monkey/mk_core/CMakeLists.txt +++ b/lib/monkey/mk_core/CMakeLists.txt @@ -24,6 +24,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") set(src ${src} "external/winpthreads.c" + "mk_win32_socketpair.c" ) add_subdirectory(deps/) else() @@ -123,6 +124,21 @@ check_c_source_compiles(" return epoll_create(1); }" HAVE_EPOLL) +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + check_c_source_compiles(" + #include + #include + int main() { + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + return 0; + }" MK_HAVE_AFUNIX_H) + + if (MK_HAVE_AFUNIX_H) + MK_DEFINITION(MK_HAVE_AFUNIX_H) + endif() +endif() + if (MK_EVENT_LOOP_SELECT) if (NOT HAVE_SELECT) message(FATAL_ERROR "Event loop backend > select(2) not available") @@ -210,6 +226,10 @@ configure_file( add_library(mk_core STATIC ${src}) target_link_libraries(mk_core ${CMAKE_THREAD_LIBS_INIT}) +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + target_link_libraries(mk_core ws2_32) +endif() + if (MK_EVENT_LOOP_LIBEVENT) target_link_libraries(mk_core event) endif() diff --git a/lib/monkey/mk_core/mk_event_libevent.c b/lib/monkey/mk_core/mk_event_libevent.c index 4f1403f4135..0f02fa0f8fa 100644 --- a/lib/monkey/mk_core/mk_event_libevent.c +++ b/lib/monkey/mk_core/mk_event_libevent.c @@ -19,12 +19,16 @@ #include +#ifdef _WIN32 +#include +#endif /* Libevent */ #include #ifdef _WIN32 #define ERR(e) (WSA##e) +static LONG mk_event_libevent_wsa_initialized = 0; #else #define ERR(e) (e) #endif @@ -37,8 +41,44 @@ struct ev_map { struct mk_event_ctx *ctx; }; +static int mk_event_libevent_socketpair(evutil_socket_t fd[2]) +{ +#ifdef _WIN32 + int ret; + SOCKET pair[2]; + + ret = mk_win32_socketpair(pair); + if (ret != 0) { + return ret; + } + + fd[0] = (evutil_socket_t) pair[0]; + fd[1] = (evutil_socket_t) pair[1]; + return 0; +#else + return evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fd); +#endif +} + static inline int _mk_event_init() { +#ifdef _WIN32 + int ret; + WSADATA wsa_data; + + if (InterlockedCompareExchange(&mk_event_libevent_wsa_initialized, + 1, 0) != 0) { + return 0; + } + + ret = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (ret != 0) { + InterlockedExchange(&mk_event_libevent_wsa_initialized, 0); + WSASetLastError(ret); + return -1; + } +#endif + return 0; } @@ -46,6 +86,10 @@ static inline void *_mk_event_loop_create(int size) { struct mk_event_ctx *ctx; + if (_mk_event_init() != 0) { + return NULL; + } + /* Main event context */ ctx = mk_mem_alloc_z(sizeof(struct mk_event_ctx)); if (!ctx) { @@ -300,7 +344,7 @@ static inline int _mk_event_timeout_create(struct mk_event_ctx *ctx, mk_bug(data == NULL); - if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) { + if (mk_event_libevent_socketpair(fd) == -1) { perror("socketpair"); return -1; } @@ -364,7 +408,7 @@ static inline int _mk_event_channel_create(struct mk_event_ctx *ctx, mk_bug(data == NULL); - ret = evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + ret = mk_event_libevent_socketpair(fd); if (ret == -1) { perror("socketpair"); diff --git a/lib/monkey/mk_core/mk_memory.c b/lib/monkey/mk_core/mk_memory.c index c4073e23161..008f7ac6b0e 100644 --- a/lib/monkey/mk_core/mk_memory.c +++ b/lib/monkey/mk_core/mk_memory.c @@ -52,6 +52,16 @@ char *mk_ptr_to_buf(mk_ptr_t p) { char *buf; + if (!p.data || p.len == 0) { + buf = mk_mem_alloc(1); + if (!buf) { + return NULL; + } + + buf[0] = '\0'; + return buf; + } + buf = mk_mem_alloc(p.len + 1); if (!buf) return NULL; diff --git a/lib/monkey/mk_core/mk_string.c b/lib/monkey/mk_core/mk_string.c index b2fdc5714b1..f5742c2ea4b 100644 --- a/lib/monkey/mk_core/mk_string.c +++ b/lib/monkey/mk_core/mk_string.c @@ -450,6 +450,76 @@ char *mk_string_build(char **buffer, unsigned long *len, return *buffer; } +char *mk_string_html_escape(const char *str) +{ + size_t i; + size_t escaped_len = 0; + char *buf; + char *p; + const char *escape; + + if (!str) { + return mk_string_dup(""); + } + + for (i = 0; str[i] != '\0'; i++) { + switch (str[i]) { + case '<': + case '>': + escaped_len += 4; + break; + case '&': + escaped_len += 5; + break; + case '"': + case '\'': + escaped_len += 6; + break; + default: + escaped_len++; + } + } + + buf = mk_mem_alloc(escaped_len + 1); + if (!buf) { + return NULL; + } + + p = buf; + for (i = 0; str[i] != '\0'; i++) { + escape = NULL; + + switch (str[i]) { + case '<': + escape = "<"; + break; + case '>': + escape = ">"; + break; + case '&': + escape = "&"; + break; + case '"': + escape = """; + break; + case '\'': + escape = "'"; + break; + } + + if (escape) { + strcpy(p, escape); + p += strlen(escape); + } + else { + *p++ = str[i]; + } + } + + *p = '\0'; + return buf; +} + int mk_string_trim(char **str) { unsigned int i; diff --git a/lib/monkey/mk_core/mk_win32_socketpair.c b/lib/monkey/mk_core/mk_win32_socketpair.c new file mode 100644 index 00000000000..ac87a48546e --- /dev/null +++ b/lib/monkey/mk_core/mk_win32_socketpair.c @@ -0,0 +1,276 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2026 Eduardo Silva + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef _WIN32 + +#include +#include +#ifdef MK_HAVE_AFUNIX_H +#include +#endif +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN + +#include + +#include +#include + +static LONG mk_win32_wsa_initialized = 0; + +static int mk_win32_socketpair_init(void) +{ + int ret; + WSADATA wsa_data; + + if (InterlockedCompareExchange(&mk_win32_wsa_initialized, 1, 0) != 0) { + return 0; + } + + ret = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (ret != 0) { + InterlockedExchange(&mk_win32_wsa_initialized, 0); + WSASetLastError(ret); + return -1; + } + + return 0; +} + +#ifdef MK_HAVE_AFUNIX_H +static int mk_win32_afunix_supported = -1; + +static int mk_win32_create_temp_path(char path[MAX_PATH]) +{ + char short_path[MAX_PATH] = {0}; + char long_path[MAX_PATH] = {0}; + + if (GetTempPathA(MAX_PATH, short_path) == 0) { + return -1; + } + + if (GetLongPathNameA(short_path, long_path, MAX_PATH) == 0) { + strncpy(long_path, short_path, sizeof(long_path) - 1); + } + + if (GetTempFileNameA(long_path, "mk", 0, path) == 0) { + return -1; + } + + DeleteFileA(path); + return 0; +} + +static int mk_win32_check_afunix(void) +{ + SOCKET fd; + + if (mk_win32_afunix_supported >= 0) { + return mk_win32_afunix_supported; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == INVALID_SOCKET) { + mk_win32_afunix_supported = 0; + return 0; + } + + closesocket(fd); + mk_win32_afunix_supported = 1; + return 1; +} + +static int mk_win32_socketpair_afunix(SOCKET pair[2]) +{ + int ret; + int path_len; + int addr_len; + SOCKET listener = INVALID_SOCKET; + SOCKET client = INVALID_SOCKET; + SOCKET server = INVALID_SOCKET; + struct sockaddr_un addr; + char path[MAX_PATH] = {0}; + + if (mk_win32_create_temp_path(path) != 0) { + return -1; + } + + path_len = (int) strlen(path); + if (path_len >= (int) sizeof(addr.sun_path)) { + DeleteFileA(path); + WSASetLastError(WSAEINVAL); + return -1; + } + + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) { + DeleteFileA(path); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, path, path_len + 1); + + ret = bind(listener, (struct sockaddr *) &addr, sizeof(addr)); + if (ret == SOCKET_ERROR) { + goto error; + } + + ret = listen(listener, 1); + if (ret == SOCKET_ERROR) { + goto error; + } + + client = socket(AF_UNIX, SOCK_STREAM, 0); + if (client == INVALID_SOCKET) { + goto error; + } + + addr_len = sizeof(addr); + ret = getsockname(listener, (struct sockaddr *) &addr, &addr_len); + if (ret == SOCKET_ERROR) { + goto error; + } + + ret = connect(client, (struct sockaddr *) &addr, addr_len); + if (ret == SOCKET_ERROR) { + goto error; + } + + server = accept(listener, NULL, NULL); + if (server == INVALID_SOCKET) { + goto error; + } + + closesocket(listener); + DeleteFileA(path); + + pair[0] = server; + pair[1] = client; + return 0; + +error: + if (listener != INVALID_SOCKET) { + closesocket(listener); + } + if (client != INVALID_SOCKET) { + closesocket(client); + } + if (server != INVALID_SOCKET) { + closesocket(server); + } + DeleteFileA(path); + return -1; +} +#endif + +static int mk_win32_socketpair_loopback(SOCKET pair[2]) +{ + int ret; + int one = 1; + int addr_len; + SOCKET listener = INVALID_SOCKET; + SOCKET client = INVALID_SOCKET; + SOCKET server = INVALID_SOCKET; + struct sockaddr_in addr; + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == INVALID_SOCKET) { + return -1; + } + + ret = setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + (const char *) &one, sizeof(one)); + if (ret == SOCKET_ERROR) { + closesocket(listener); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + ret = bind(listener, (struct sockaddr *) &addr, sizeof(addr)); + if (ret == SOCKET_ERROR) { + closesocket(listener); + return -1; + } + + ret = listen(listener, 1); + if (ret == SOCKET_ERROR) { + closesocket(listener); + return -1; + } + + addr_len = sizeof(addr); + ret = getsockname(listener, (struct sockaddr *) &addr, &addr_len); + if (ret == SOCKET_ERROR) { + closesocket(listener); + return -1; + } + + client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (client == INVALID_SOCKET) { + closesocket(listener); + return -1; + } + + ret = connect(client, (struct sockaddr *) &addr, sizeof(addr)); + if (ret == SOCKET_ERROR) { + closesocket(client); + closesocket(listener); + return -1; + } + + server = accept(listener, NULL, NULL); + closesocket(listener); + if (server == INVALID_SOCKET) { + closesocket(client); + return -1; + } + + pair[0] = server; + pair[1] = client; + return 0; +} + +int mk_win32_socketpair(SOCKET pair[2]) +{ + if (pair == NULL) { + WSASetLastError(WSAEINVAL); + return -1; + } + + if (mk_win32_socketpair_init() != 0) { + return -1; + } + +#ifdef MK_HAVE_AFUNIX_H + if (mk_win32_check_afunix() && mk_win32_socketpair_afunix(pair) == 0) { + return 0; + } +#endif + + return mk_win32_socketpair_loopback(pair); +} + +#endif diff --git a/lib/monkey/mk_server/CMakeLists.txt b/lib/monkey/mk_server/CMakeLists.txt index 457525e6253..08bec36b7b8 100644 --- a/lib/monkey/mk_server/CMakeLists.txt +++ b/lib/monkey/mk_server/CMakeLists.txt @@ -31,10 +31,18 @@ if(MK_HTTP2) ) endif() +set(src ${src} ${MK_TLS_SOURCES}) + # Always build a static library, thats our core :) add_library(monkey-core-static STATIC ${src}) set_target_properties(monkey-core-static PROPERTIES OUTPUT_NAME monkey) +if(MK_TLS_INCLUDE_DIRS) + target_include_directories(monkey-core-static PRIVATE ${MK_TLS_INCLUDE_DIRS}) +endif() target_link_libraries(monkey-core-static mk_core ${CMAKE_THREAD_LIBS_INIT} ${STATIC_PLUGINS_LIBS} ${CMAKE_DL_LIBS} rbtree co) +if(MK_TLS_LIBS) + target_link_libraries(monkey-core-static ${MK_TLS_LIBS}) +endif() message(STATUS "LINKING ${STATIC_PLUGINS_LIBS}") diff --git a/lib/monkey/mk_server/mk_config.c b/lib/monkey/mk_server/mk_config.c index f8dc9c00f08..79f5f6c1081 100644 --- a/lib/monkey/mk_server/mk_config.c +++ b/lib/monkey/mk_server/mk_config.c @@ -100,8 +100,28 @@ void mk_config_free_all(struct mk_server *server) mk_mem_free(server->transport_layer); } + if (server->tls_cert_file) { + mk_mem_free(server->tls_cert_file); + } + + if (server->tls_cert_chain_file) { + mk_mem_free(server->tls_cert_chain_file); + } + + if (server->tls_key_file) { + mk_mem_free(server->tls_key_file); + } + + if (server->tls_dh_param_file) { + mk_mem_free(server->tls_dh_param_file); + } + mk_config_listeners_free(server); + if (server->mimetype_default_str) { + mk_mem_free(server->mimetype_default_str); + } + mk_ptr_free(&server->server_software); mk_mem_free(server); } @@ -123,15 +143,8 @@ int mk_config_listen_check_busy(struct mk_server *server) { int fd; struct mk_list *head; - struct mk_plugin *p; struct mk_config_listener *listen; - p = mk_plugin_cap(MK_CAP_SOCK_PLAIN, server); - if (!p) { - mk_warn("Listen check: consider build monkey with basic socket handling!"); - return MK_FALSE; - } - mk_list_foreach(head, &server->listeners) { listen = mk_list_entry(head, struct mk_config_listener, _head); @@ -275,8 +288,12 @@ static int mk_config_listen_read(struct mk_rconf_section *section, static int mk_config_read_files(char *path_conf, char *file_conf, struct mk_server *server) { + int flags; unsigned long len; char *tmp = NULL; + char *default_mimetype = NULL; + struct mk_list *cur; + struct mk_config_listener *listen; struct stat checkdir; struct mk_rconf *cnf; struct mk_rconf_section *section; @@ -303,8 +320,10 @@ static int mk_config_read_files(char *path_conf, char *file_conf, mk_err("Cannot read '%s'", server->conf_main); return -1; } + section = mk_rconf_section_get(cnf, "SERVER"); if (!section) { + mk_mem_free(tmp); mk_err("ERROR: No 'SERVER' section defined"); return -1; } @@ -324,8 +343,19 @@ static int mk_config_read_files(char *path_conf, char *file_conf, } } else { - mk_config_listener_add(NULL, server->port_override, - MK_CAP_HTTP, server); + flags = MK_CAP_HTTP; + if (server->tls_mode == MK_TRUE) { + flags |= MK_CAP_SOCK_TLS; + } + + mk_config_listener_add(NULL, server->port_override, flags, server); + } + + if (server->tls_mode == MK_TRUE) { + mk_list_foreach(cur, &server->listeners) { + listen = mk_list_entry(cur, struct mk_config_listener, _head); + listen->flags |= MK_CAP_SOCK_TLS; + } } /* Number of thread workers */ @@ -436,10 +466,10 @@ static int mk_config_read_files(char *path_conf, char *file_conf, } /* Default Mimetype */ - mk_mem_free(tmp); - tmp = mk_rconf_section_get_key(section, "DefaultMimeType", MK_RCONF_STR); - if (tmp) { - mk_string_build(&server->mimetype_default_str, &len, "%s\r\n", tmp); + default_mimetype = mk_rconf_section_get_key(section, "DefaultMimeType", MK_RCONF_STR); + if (default_mimetype) { + mk_string_build(&server->mimetype_default_str, &len, "%s\r\n", default_mimetype); + mk_mem_free(default_mimetype); } /* File Descriptor Table (FDT) */ @@ -605,6 +635,11 @@ void mk_config_set_init_values(struct mk_server *server) /* Internals */ server->safe_event_write = MK_FALSE; + server->tls_mode = MK_FALSE; + server->tls_cert_file = NULL; + server->tls_cert_chain_file = NULL; + server->tls_key_file = NULL; + server->tls_dh_param_file = NULL; /* Init plugin list */ mk_list_init(&server->plugins); diff --git a/lib/monkey/mk_server/mk_fifo.c b/lib/monkey/mk_server/mk_fifo.c index fd148db7b4f..ca4c8ac6c95 100644 --- a/lib/monkey/mk_server/mk_fifo.c +++ b/lib/monkey/mk_server/mk_fifo.c @@ -21,7 +21,7 @@ #include #ifdef _WIN32 -#include +#include #endif static struct mk_fifo_worker *mk_fifo_worker_create(struct mk_fifo *ctx, @@ -30,6 +30,9 @@ static struct mk_fifo_worker *mk_fifo_worker_create(struct mk_fifo *ctx, int id; int ret; struct mk_fifo_worker *fw; +#ifdef _WIN32 + SOCKET channel[2]; +#endif /* Get an ID */ id = mk_list_size(&ctx->workers); @@ -55,9 +58,14 @@ static struct mk_fifo_worker *mk_fifo_worker_create(struct mk_fifo *ctx, fw->buf_size = MK_FIFO_BUF_SIZE; #ifdef _WIN32 - ret = evutil_socketpair(AF_INET, SOCK_STREAM, 0, fw->channel); + ret = mk_win32_socketpair(channel); + if (ret == 0) { + fw->channel[0] = (mk_fifo_channel_fd) channel[0]; + fw->channel[1] = (mk_fifo_channel_fd) channel[1]; + } if (ret == -1) { perror("socketpair"); + mk_mem_free(fw->buf_data); mk_mem_free(fw); return NULL; } @@ -65,6 +73,7 @@ static struct mk_fifo_worker *mk_fifo_worker_create(struct mk_fifo *ctx, ret = pipe(fw->channel); if (ret == -1) { perror("pipe"); + mk_mem_free(fw->buf_data); mk_mem_free(fw); return NULL; } @@ -249,8 +258,8 @@ static int mk_fifo_worker_destroy_all(struct mk_fifo *ctx) fw = mk_list_entry(head, struct mk_fifo_worker, _head); #ifdef _WIN32 - evutil_closesocket(fw->channel[0]); - evutil_closesocket(fw->channel[1]); + closesocket((SOCKET) fw->channel[0]); + closesocket((SOCKET) fw->channel[1]); #else close(fw->channel[0]); close(fw->channel[1]); @@ -264,14 +273,14 @@ static int mk_fifo_worker_destroy_all(struct mk_fifo *ctx) return c; } -static int msg_write(int fd, void *buf, size_t count) +static int msg_write(mk_fifo_channel_fd fd, void *buf, size_t count) { ssize_t bytes; size_t total = 0; do { #ifdef _WIN32 - bytes = send(fd, (uint8_t *)buf + total, count - total, 0); + bytes = send((SOCKET) fd, (uint8_t *)buf + total, count - total, 0); #else bytes = write(fd, (uint8_t *)buf + total, count - total); #endif @@ -410,7 +419,8 @@ int mk_fifo_worker_read(void *event) /* Read data from pipe */ #ifdef _WIN32 - bytes = recv(fw->channel[0], fw->buf_data + fw->buf_len, available, 0); + bytes = recv((SOCKET) fw->channel[0], + fw->buf_data + fw->buf_len, available, 0); #else bytes = read(fw->channel[0], fw->buf_data + fw->buf_len, available); #endif diff --git a/lib/monkey/mk_server/mk_http.c b/lib/monkey/mk_server/mk_http.c index ad12a74a045..91f6fc004ee 100644 --- a/lib/monkey/mk_server/mk_http.c +++ b/lib/monkey/mk_server/mk_http.c @@ -384,6 +384,7 @@ static int mk_http_error_page(char *title, mk_ptr_t *message, char *signature, char **out_buf, unsigned long *out_size) { char *temp; + char *escaped; *out_buf = NULL; @@ -394,9 +395,19 @@ static int mk_http_error_page(char *title, mk_ptr_t *message, char *signature, temp = mk_string_dup(""); } - mk_string_build(out_buf, out_size, - MK_REQUEST_DEFAULT_PAGE, title, temp, signature); + if (!temp) { + return -1; + } + + escaped = mk_string_html_escape(temp); mk_mem_free(temp); + if (!escaped) { + return -1; + } + + mk_string_build(out_buf, out_size, + MK_REQUEST_DEFAULT_PAGE, title, escaped, signature); + mk_mem_free(escaped); return 0; } @@ -457,6 +468,10 @@ static int mk_http_range_parse(struct mk_http_request *sr) if ((sep_pos = mk_string_char_search(sr->range.data, '-', sr->range.len)) < 0) return -1; + if (sep_pos < eq_pos) { + return -1; + } + len = sr->range.len; sh = &sr->headers; @@ -476,10 +491,16 @@ static int mk_http_range_parse(struct mk_http_request *sr) /* =yyy-xxx */ if ((eq_pos + 1 != sep_pos) && (len > sep_pos + 1)) { buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, sep_pos); + if (!buffer) { + return -1; + } sh->ranges[0] = (unsigned long) atol(buffer); mk_mem_free(buffer); buffer = mk_string_copy_substr(sr->range.data, sep_pos + 1, len); + if (!buffer) { + return -1; + } sh->ranges[1] = (unsigned long) atol(buffer); mk_mem_free(buffer); @@ -493,6 +514,9 @@ static int mk_http_range_parse(struct mk_http_request *sr) /* =yyy- */ if ((eq_pos + 1 != sep_pos) && (len == sep_pos + 1)) { buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, len); + if (!buffer) { + return -1; + } sr->headers.ranges[0] = (unsigned long) atol(buffer); mk_mem_free(buffer); @@ -522,7 +546,16 @@ static int mk_http_directory_redirect_check(struct mk_http_session *cs, return 0; } + if (!sr->host.data || sr->host.len <= 0) { + mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr, server); + return -1; + } + host = mk_ptr_to_buf(sr->host); + if (!host) { + mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr, server); + return -1; + } /* * Add ending slash to the location string @@ -588,6 +621,9 @@ static inline char *mk_http_index_lookup(mk_ptr_t *path_base, } off = path_base->len; + if ((size_t) off >= buf_size) { + return NULL; + } memcpy(buf, path_base->data, off); mk_list_foreach(head, server->index_files) { @@ -999,7 +1035,6 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr, sr->in_file.fd = sr->file_fd; sr->in_file.bytes_offset = 0; sr->in_file.bytes_total = sr->file_info.size; - sr->in_file.stream = &sr->stream; } /* Process methods */ @@ -1042,8 +1077,10 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr, /* Send file content */ if (sr->method == MK_METHOD_GET || sr->method == MK_METHOD_POST) { /* Note: bytes and offsets are set after the Range check */ - sr->in_file.type = MK_STREAM_FILE; - mk_stream_append(&sr->in_file, &sr->stream); + mk_stream_in_file(&sr->stream, &sr->in_file, sr->file_fd, + sr->in_file.bytes_total, + sr->in_file.bytes_offset, + NULL, NULL); } /* @@ -1138,15 +1175,27 @@ int mk_http_request_end(struct mk_http_session *cs, struct mk_server *server) ret = mk_http_parser_more(&cs->parser, cs->body_length); if (ret == MK_TRUE) { /* Our pipeline request limit is the same that our keepalive limit */ + if (cs->parser.i < 0 || + (unsigned int) (cs->parser.i + 1) >= cs->body_length) { + goto shutdown; + } + cs->counter_connections++; len = (cs->body_length - cs->parser.i) -1; + if (len <= 0) { + goto shutdown; + } memmove(cs->body, cs->body + cs->parser.i + 1, len); cs->body_length = len; /* Prepare for next one */ - sr = mk_list_entry_first(&cs->request_list, struct mk_http_request, _head); + if (mk_list_is_empty(&cs->request_list) == 0) { + cs->close_now = MK_TRUE; + goto shutdown; + } + sr = &cs->sr_fixed; mk_http_request_free(sr, server); mk_http_request_init(cs, sr, server); mk_http_parser_init(&cs->parser); @@ -1429,7 +1478,7 @@ int mk_http_session_init(struct mk_http_session *cs, struct mk_sched_conn *conn, /* alloc space for body content */ if (conn->net->buffer_size > MK_REQUEST_CHUNK) { - cs->body = mk_mem_alloc(conn->net->buffer_size); + cs->body = mk_mem_alloc(conn->net->buffer_size + 1); cs->body_size = conn->net->buffer_size; } else { @@ -1626,9 +1675,10 @@ int mk_http_sched_done(struct mk_sched_conn *conn, struct mk_http_request *sr; session = mk_http_session_get(conn); - sr = mk_list_entry_first(&session->request_list, - struct mk_http_request, _head); - mk_plugin_stage_run_40(session, sr, server); + if (mk_list_is_empty(&session->request_list) != 0) { + sr = &session->sr_fixed; + mk_plugin_stage_run_40(session, sr, server); + } return mk_http_request_end(session, server); } diff --git a/lib/monkey/mk_server/mk_http_parser.c b/lib/monkey/mk_server/mk_http_parser.c index 9413528ab6e..3c831f293c1 100644 --- a/lib/monkey/mk_server/mk_http_parser.c +++ b/lib/monkey/mk_server/mk_http_parser.c @@ -173,6 +173,16 @@ static inline void request_set(mk_ptr_t *ptr, struct mk_http_parser *p, char *bu static inline int header_cmp(const char *expected, char *value, int len) { int i = 0; + size_t expected_len; + + if (len < 0) { + return -1; + } + + expected_len = strlen(expected); + if ((size_t) len != expected_len) { + return -1; + } if (len >= 8) { if (expected[0] != tolower(value[0])) return -1; @@ -535,6 +545,9 @@ static int http_parser_transfer_encoding_chunked(struct mk_http_parser *p, (errno != 0)) { return MK_HTTP_PARSER_ERROR; } + if (ptr == tmp || *ptr != '\0') { + return MK_HTTP_PARSER_ERROR; + } if (chunk_len < 0) { return MK_HTTP_PARSER_ERROR; diff --git a/lib/monkey/mk_server/mk_lib.c b/lib/monkey/mk_server/mk_lib.c index c2940086483..c070603044b 100644 --- a/lib/monkey/mk_server/mk_lib.c +++ b/lib/monkey/mk_server/mk_lib.c @@ -171,6 +171,7 @@ static void mk_lib_worker(void *data) } mk_event_loop_destroy(server->lib_evl); + server->lib_evl = NULL; mk_exit_all(server); pthread_kill(pthread_self(), 0); @@ -226,6 +227,7 @@ int mk_start(mk_ctx_t *ctx) } mk_event_loop_destroy(server->lib_evl_start); + server->lib_evl_start = NULL; if (ret == -1) { mk_stop(ctx); } diff --git a/lib/monkey/mk_server/mk_mimetype.c b/lib/monkey/mk_server/mk_mimetype.c index b86b4ef1a05..5462ea5c776 100644 --- a/lib/monkey/mk_server/mk_mimetype.c +++ b/lib/monkey/mk_server/mk_mimetype.c @@ -197,7 +197,12 @@ struct mk_mimetype *mk_mimetype_find(struct mk_server *server, mk_ptr_t *filenam { int j, len; - j = len = filename->len; + if (!filename->data || filename->len <= 0) { + return NULL; + } + + len = filename->len; + j = len - 1; /* looking for extension */ while (j >= 0 && filename->data[j] != '.') { diff --git a/lib/monkey/mk_server/mk_net.c b/lib/monkey/mk_server/mk_net.c index de183de4870..1dc9be03f09 100644 --- a/lib/monkey/mk_server/mk_net.c +++ b/lib/monkey/mk_server/mk_net.c @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -30,8 +31,139 @@ #else #include #include +#include +#include +#include #endif +#if defined (__linux__) +#include +#endif + +static int net_plain_read(struct mk_plugin *plugin, int socket_fd, + void *buf, int count) +{ + (void) plugin; + + return recv(socket_fd, buf, count, 0); +} + +static int net_plain_write(struct mk_plugin *plugin, int socket_fd, + const void *buf, size_t count) +{ + (void) plugin; + + return send(socket_fd, buf, count, 0); +} + +static int net_plain_writev(struct mk_plugin *plugin, int socket_fd, + struct mk_iov *mk_io) +{ + (void) plugin; + + return mk_iov_send(socket_fd, mk_io); +} + +static int net_plain_close(struct mk_plugin *plugin, int socket_fd) +{ + (void) plugin; + +#ifdef _WIN32 + return closesocket(socket_fd); +#else + return close(socket_fd); +#endif +} + +static int net_plain_send_file(struct mk_plugin *plugin, int socket_fd, + int file_fd, off_t *file_offset, + size_t file_count) +{ + ssize_t ret = -1; + + (void) plugin; + +#if defined (__linux__) + ret = sendfile(socket_fd, file_fd, file_offset, file_count); + if (ret == -1 && errno != EAGAIN) { + MK_TRACE("[net] sendfile(%i) failed: %s", socket_fd, strerror(errno)); + } + return ret; +#elif defined (__APPLE__) + off_t offset = *file_offset; + off_t len = (off_t) file_count; + + ret = sendfile(file_fd, socket_fd, offset, &len, NULL, 0); + if (ret == -1 && errno != EAGAIN) { + MK_TRACE("[net] sendfile(%i) failed: %s", socket_fd, strerror(errno)); + } + else if (len > 0) { + *file_offset += len; + return len; + } + return ret; +#elif defined (__FreeBSD__) + off_t offset = *file_offset; + off_t len = (off_t) file_count; + + ret = sendfile(file_fd, socket_fd, offset, len, NULL, 0, 0); + if (ret == -1 && errno != EAGAIN) { + MK_TRACE("[net] sendfile(%i) failed: %s", socket_fd, strerror(errno)); + } + else if (len > 0) { + *file_offset += len; + return len; + } + return ret; +#else + ssize_t bytes_written = 0; + ssize_t send_ret; + ssize_t to_be_sent; + uint8_t temporary_buffer[1024]; + + if (file_offset != NULL) { + lseek(file_fd, *file_offset, SEEK_SET); + } + + while (1) { + ret = read(file_fd, temporary_buffer, sizeof(temporary_buffer)); + if (ret == 0) { + return bytes_written; + } + else if (ret < 0) { + return -1; + } + + to_be_sent = ret; + while (to_be_sent > 0) { + send_ret = send(socket_fd, + &temporary_buffer[ret - to_be_sent], + to_be_sent, 0); + if (send_ret == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + return -1; + } + } + else { + bytes_written += send_ret; + to_be_sent -= send_ret; + } + } + } +#endif +} + +static struct mk_plugin_network mk_net_io_plain = { + .read = net_plain_read, + .write = net_plain_write, + .writev = net_plain_writev, + .close = net_plain_close, + .send_file = net_plain_send_file, + .event_interest = NULL, + .buffer_size = MK_REQUEST_CHUNK, + .plugin = NULL +}; + /* Initialize the network stack*/ int mk_net_init() { @@ -63,6 +195,21 @@ int mk_net_init() return 0; } +struct mk_plugin_network *mk_net_transport_default() +{ + return &mk_net_io_plain; +} + +int mk_net_transport_event_interest(struct mk_plugin_network *transport, + int fd, int fallback) +{ + if (transport != NULL && transport->event_interest != NULL) { + return transport->event_interest(transport->plugin, fd, fallback); + } + + return fallback; +} + /* Connect to a TCP socket server */ static int mk_net_fd_connect(int fd, char *host, unsigned long port) { @@ -209,7 +356,10 @@ int mk_net_conn_write(struct mk_channel *channel, ret = mk_event_add(sched->loop, channel->fd, MK_EVENT_THREAD, - MK_EVENT_WRITE, channel->event); + mk_net_transport_event_interest(channel->io, + channel->fd, + MK_EVENT_WRITE), + channel->event); if (ret == -1) { /* * If we failed here there no much that we can do, just @@ -262,7 +412,10 @@ int mk_net_conn_write(struct mk_channel *channel, ret = mk_event_add(sched->loop, channel->fd, MK_EVENT_THREAD, - MK_EVENT_WRITE, channel->event); + mk_net_transport_event_interest(channel->io, + channel->fd, + MK_EVENT_WRITE), + channel->event); if (ret == -1) { /* * If we failed here there no much that we can do, just diff --git a/lib/monkey/mk_server/mk_scheduler.c b/lib/monkey/mk_server/mk_scheduler.c index a680d3cdfe2..f359b6fea3c 100644 --- a/lib/monkey/mk_server/mk_scheduler.c +++ b/lib/monkey/mk_server/mk_scheduler.c @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -188,7 +189,7 @@ struct mk_sched_conn *mk_sched_add_connection(int remote_fd, /* Close connection, otherwise continue */ if (ret == MK_PLUGIN_RET_CLOSE_CONX) { - listener->network->network->close(listener->network, remote_fd); + listener->network->close(listener->network->plugin, remote_fd); MK_LT_SCHED(remote_fd, "PLUGIN_CLOSE"); return NULL; } @@ -216,7 +217,7 @@ struct mk_sched_conn *mk_sched_add_connection(int remote_fd, event->status = MK_EVENT_NONE; conn->arrive_time = server->clock_context->log_current_utime; conn->protocol = handler; - conn->net = listener->network->network; + conn->net = listener->network; conn->is_timeout_on = MK_FALSE; conn->server_listen = listener; @@ -390,6 +391,7 @@ void *mk_sched_launch_worker_loop(void *data) /* Export known scheduler node to context thread */ MK_TLS_SET(mk_tls_sched_worker_node, sched); + mk_tls_thread_init(server); mk_plugin_core_thread(server); if (server->scheduler_mode == MK_SCHEDULER_REUSEPORT) { @@ -598,8 +600,10 @@ int mk_sched_check_timeouts(struct mk_sched_worker *sched, MK_TRACE("Scheduler, closing fd %i due TIMEOUT", conn->event.fd); MK_LT_SCHED(conn->event.fd, "TIMEOUT_CONN_PENDING"); - conn->protocol->cb_close(conn, sched, MK_SCHED_CONN_TIMEOUT, - server); + if (conn->protocol->cb_close) { + conn->protocol->cb_close(conn, sched, MK_SCHED_CONN_TIMEOUT, + server); + } mk_sched_drop_connection(conn, sched, server); } } @@ -673,6 +677,8 @@ int mk_sched_threads_destroy_conn(struct mk_sched_worker *sched, * Scheduler events handler: lookup for event handler and invoke * proper callbacks. */ +static int mk_sched_conn_pending_output(struct mk_channel *channel); + int mk_sched_event_read(struct mk_sched_conn *conn, struct mk_sched_worker *sched, struct mk_server *server) @@ -696,14 +702,54 @@ int mk_sched_event_read(struct mk_sched_conn *conn, if (ret == -1) { if (errno == EAGAIN) { MK_TRACE("[FD %i] EAGAIN: need to read more data", conn->event.fd); + mk_event_add(sched->loop, conn->event.fd, + MK_EVENT_CONNECTION, + mk_net_transport_event_interest(conn->net, + conn->event.fd, + MK_EVENT_READ), + conn); return 1; } return -1; } + if (mk_sched_conn_pending_output(&conn->channel) == MK_TRUE) { + return mk_sched_event_write(conn, sched, server); + } + return ret; } +static int mk_sched_conn_pending_output(struct mk_channel *channel) +{ + struct mk_list *head; + struct mk_stream *stream; + struct mk_stream_input *input; + + if (mk_channel_is_empty(channel) == 0) { + return MK_FALSE; + } + + /* + * The request parser installs placeholder input streams with zero bytes + * before a response exists. Only treat the channel as writable work when + * at least one input actually has bytes pending. + */ + mk_list_foreach(head, &channel->streams) { + stream = mk_list_entry(head, struct mk_stream, _head); + if (mk_list_is_empty(&stream->inputs) == 0) { + continue; + } + + input = mk_list_entry_first(&stream->inputs, struct mk_stream_input, _head); + if (input->bytes_total > 0) { + return MK_TRUE; + } + } + + return MK_FALSE; +} + int mk_sched_event_write(struct mk_sched_conn *conn, struct mk_sched_worker *sched, struct mk_server *server) @@ -716,6 +762,14 @@ int mk_sched_event_write(struct mk_sched_conn *conn, ret = mk_channel_write(&conn->channel, &count); if (ret == MK_CHANNEL_FLUSH || ret == MK_CHANNEL_BUSY) { + if ((conn->event.mask & MK_EVENT_WRITE) == 0) { + mk_event_add(sched->loop, conn->event.fd, + MK_EVENT_CONNECTION, + mk_net_transport_event_interest(conn->net, + conn->event.fd, + MK_EVENT_WRITE), + conn); + } return 0; } else if (ret == MK_CHANNEL_DONE || ret == MK_CHANNEL_EMPTY) { @@ -729,7 +783,9 @@ int mk_sched_event_write(struct mk_sched_conn *conn, event = &conn->event; mk_event_add(sched->loop, event->fd, MK_EVENT_CONNECTION, - MK_EVENT_READ, + mk_net_transport_event_interest(conn->net, + event->fd, + MK_EVENT_READ), conn); } return 0; @@ -749,7 +805,7 @@ int mk_sched_event_close(struct mk_sched_conn *conn, MK_TRACE("[FD %i] Connection Handler, closed", conn->event.fd); mk_event_del(sched->loop, &conn->event); - if (type != MK_EP_SOCKET_DONE) { + if (type != MK_EP_SOCKET_DONE && conn->protocol->cb_close) { conn->protocol->cb_close(conn, sched, type, server); } /* diff --git a/lib/monkey/mk_server/mk_server.c b/lib/monkey/mk_server/mk_server.c index 82c7a403b95..86b66bc16d6 100644 --- a/lib/monkey/mk_server/mk_server.c +++ b/lib/monkey/mk_server/mk_server.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -124,7 +125,7 @@ struct mk_sched_conn *mk_server_listen_handler(struct mk_sched_worker *sched, error: if (client_fd != -1) { - listener->network->network->close(listener->network, client_fd); + listener->network->close(listener->network->plugin, client_fd); } return NULL; @@ -174,7 +175,6 @@ struct mk_list *mk_server_listen_init(struct mk_server *server) struct mk_event *event; struct mk_server_listen *listener; struct mk_sched_handler *protocol; - struct mk_plugin *plugin; struct mk_config_listener *listen; if (!server) { @@ -234,15 +234,14 @@ struct mk_list *mk_server_listen_init(struct mk_server *server) listener->protocol = protocol; } #endif - listener->network = mk_plugin_cap(MK_CAP_SOCK_PLAIN, server); + listener->network = mk_net_transport_default(); if (listen->flags & MK_CAP_SOCK_TLS) { - plugin = mk_plugin_cap(MK_CAP_SOCK_TLS, server); - if (!plugin) { + if (!mk_tls_enabled()) { mk_err("SSL/TLS not supported"); exit(EXIT_FAILURE); } - listener->network = plugin; + listener->network = mk_tls_transport(); } mk_list_add(&listener->_head, listeners); @@ -515,7 +514,15 @@ void mk_server_worker_loop(struct mk_server *server) if (event->mask & MK_EVENT_WRITE) { MK_TRACE("[FD %i] Event WRITE", event->fd); - ret = mk_sched_event_write(conn, sched, server); + if (mk_channel_is_empty(&conn->channel) == 0 && + mk_net_transport_event_interest(conn->net, + event->fd, + MK_EVENT_READ) == MK_EVENT_WRITE) { + ret = mk_sched_event_read(conn, sched, server); + } + else { + ret = mk_sched_event_write(conn, sched, server); + } } if (event->mask & MK_EVENT_READ) { diff --git a/lib/monkey/mk_server/mk_stream.c b/lib/monkey/mk_server/mk_stream.c index df0f1b0b81b..37db09e82d3 100644 --- a/lib/monkey/mk_server/mk_stream.c +++ b/lib/monkey/mk_server/mk_stream.c @@ -19,6 +19,7 @@ #include #include +#include #include /* Create a new channel */ @@ -108,7 +109,9 @@ int mk_channel_flush(struct mk_channel *channel) mk_event_add(mk_sched_loop(), channel->fd, MK_EVENT_CONNECTION, - MK_EVENT_WRITE, + mk_net_transport_event_interest(channel->io, + channel->fd, + MK_EVENT_WRITE), channel->event); } } diff --git a/lib/monkey/mk_server/mk_user.c b/lib/monkey/mk_server/mk_user.c index 7200ff08cb6..716331acdd7 100644 --- a/lib/monkey/mk_server/mk_user.c +++ b/lib/monkey/mk_server/mk_user.c @@ -46,7 +46,7 @@ int mk_user_init(struct mk_http_session *cs, struct mk_http_request *sr, } limit = mk_string_char_search(sr->uri_processed.data + offset, '/', - sr->uri_processed.len); + sr->uri_processed.len - offset); if (limit == -1) { limit = (sr->uri_processed.len) - offset; diff --git a/lib/monkey/mk_server/monkey.c b/lib/monkey/mk_server/monkey.c index 68513d21b40..af2bbda1c43 100644 --- a/lib/monkey/mk_server/monkey.c +++ b/lib/monkey/mk_server/monkey.c @@ -28,6 +28,7 @@ #include #include #include +#include pthread_once_t mk_server_tls_setup_once = PTHREAD_ONCE_INIT; @@ -191,7 +192,15 @@ int mk_server_setup(struct mk_server *server) /* Core and Scheduler setup */ mk_config_start_configure(server); + if (server->path_conf_root != NULL && server->config == NULL) { + return -1; + } + mk_config_signature(server); + ret = mk_tls_init(server); + if (ret != 0) { + return -1; + } mk_sched_init(server); @@ -234,8 +243,18 @@ void mk_exit_all(struct mk_server *server) /* Continue exiting */ mk_plugin_exit_all(server); + mk_tls_exit(server); mk_clock_exit(server); mk_sched_exit(server); + if (server->lib_evl != NULL) { + mk_event_loop_destroy(server->lib_evl); + server->lib_evl = NULL; + } + if (server->lib_evl_start != NULL) { + mk_event_loop_destroy(server->lib_evl_start); + server->lib_evl_start = NULL; + } + pthread_mutex_destroy(&server->vhost_fdt_mutex); mk_config_free_all(server); } diff --git a/lib/monkey/plugins/CMakeLists.txt b/lib/monkey/plugins/CMakeLists.txt index a78d836953f..9c77f1e2616 100644 --- a/lib/monkey/plugins/CMakeLists.txt +++ b/lib/monkey/plugins/CMakeLists.txt @@ -3,10 +3,14 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # CHECK_STATIC_PLUGIN: Check if a plugin will be linked statically macro(CHECK_STATIC_PLUGIN name) - string(REPLACE "," ";" plugins ${MK_STATIC_PLUGINS}) - list(FIND plugins ${name} found) - if(NOT found EQUAL -1) - set(IS_STATIC TRUE) + if(MK_STATIC_PLUGINS) + string(REPLACE "," ";" plugins "${MK_STATIC_PLUGINS}") + list(FIND plugins ${name} found) + if(NOT found EQUAL -1) + set(IS_STATIC TRUE) + else() + set(IS_STATIC FALSE) + endif() else() set(IS_STATIC FALSE) endif() @@ -88,10 +92,8 @@ MK_BUILD_PLUGIN("cgi") MK_BUILD_PLUGIN("cheetah") MK_BUILD_PLUGIN("dirlisting") MK_BUILD_PLUGIN("fastcgi") -MK_BUILD_PLUGIN("liana") MK_BUILD_PLUGIN("logger") MK_BUILD_PLUGIN("mandril") -MK_BUILD_PLUGIN("tls") MK_BUILD_PLUGIN("duda") # Generate include/monkey/mk_static_plugins.h diff --git a/lib/monkey/plugins/dirlisting/dirlisting.c b/lib/monkey/plugins/dirlisting/dirlisting.c index 1ffd3fac465..3e8aaf8b73f 100644 --- a/lib/monkey/plugins/dirlisting/dirlisting.c +++ b/lib/monkey/plugins/dirlisting/dirlisting.c @@ -476,6 +476,7 @@ struct dirhtml_value *mk_dirhtml_tag_assign(struct mk_list *list, aux->value = value; aux->sep = sep; aux->tags = tags; + aux->owned = MK_FALSE; if (value) { aux->len = strlen(value); @@ -497,6 +498,9 @@ static void mk_dirhtml_tag_free_list(struct mk_list *list) mk_list_foreach_safe(head, tmp, list) { target = mk_list_entry(head, struct dirhtml_value, _head); mk_list_del(&target->_head); + if (target->owned == MK_TRUE && target->value) { + mk_api->mem_free(target->value); + } mk_api->mem_free(target); } } @@ -741,11 +745,13 @@ static int mk_dirhtml_init(struct mk_plugin *plugin, int len; char tmp[16]; unsigned int i = 0; + char *escaped_uri; struct mk_list *head; struct mk_list list; struct mk_f_list *entry; struct mk_dirhtml_request *request; struct mk_stream *stream; + struct dirhtml_value *title; if (!(dir = opendir(sr->real_path.data))) { return -1; @@ -802,9 +808,24 @@ static int mk_dirhtml_init(struct mk_plugin *plugin, mk_list_init(&list); /* Set %_html_title_% */ - mk_dirhtml_tag_assign(&list, 0, mk_dir_iov_none, - sr->uri_processed.data, - (char **) _tags_global); + escaped_uri = mk_string_html_escape(sr->uri_processed.data); + if (!escaped_uri) { + mk_stream_release(stream); + closedir(dir); + mk_api->mem_free(request); + return -1; + } + title = mk_dirhtml_tag_assign(&list, 0, mk_dir_iov_none, + escaped_uri, + (char **) _tags_global); + if (!title) { + mk_api->mem_free(escaped_uri); + mk_stream_release(stream); + closedir(dir); + mk_api->mem_free(request); + return -1; + } + title->owned = MK_TRUE; /* Set %_theme_path_% */ mk_dirhtml_tag_assign(&list, 1, mk_dir_iov_none, diff --git a/lib/monkey/plugins/dirlisting/dirlisting.h b/lib/monkey/plugins/dirlisting/dirlisting.h index 678a4887b00..88077c315ee 100644 --- a/lib/monkey/plugins/dirlisting/dirlisting.h +++ b/lib/monkey/plugins/dirlisting/dirlisting.h @@ -142,6 +142,7 @@ struct dirhtml_value /* string data */ int len; + int owned; char *value; /* next node */ diff --git a/lib/monkey/plugins/tls/CMakeLists.txt b/lib/monkey/plugins/tls/CMakeLists.txt index 2bde86cacf2..c05081dce21 100644 --- a/lib/monkey/plugins/tls/CMakeLists.txt +++ b/lib/monkey/plugins/tls/CMakeLists.txt @@ -1,17 +1,10 @@ -set(src - tls.c -) - -if(NOT WITH_MBEDTLS_SHARED) - option(ENABLE_TESTING OFF) - option(ENABLE_PROGRAMS OFF) - option(INSTALL_MBEDTLS_HEADERS OFF) - set(MK_MBEDTLS_SRC ../../deps/mbedtls-2.4.2) - add_subdirectory(${MK_MBEDTLS_SRC} ${CMAKE_BINARY_DIR}/mbedtls-2.4.2) - include_directories(${MK_MBEDTLS_SRC}/include) +if(NOT DEFINED MK_TLS_PLUGIN_SRC) + message(FATAL_ERROR "TLS backend was not configured. Enable MK_PLUGIN_TLS and configure tls/CMakeLists.txt") endif() -MONKEY_PLUGIN(tls "${src}") +MONKEY_PLUGIN(tls "${MK_TLS_PLUGIN_SRC}") -MONKEY_PLUGIN_LINK_LIB(tls mbedtls) +foreach(lib ${MK_TLS_PLUGIN_LIBS}) + MONKEY_PLUGIN_LINK_LIB(tls ${lib}) +endforeach() add_subdirectory(conf) diff --git a/lib/monkey/plugins/tls/tls_openssl.c b/lib/monkey/plugins/tls/tls_openssl.c new file mode 100644 index 00000000000..948823847f8 --- /dev/null +++ b/lib/monkey/plugins/tls/tls_openssl.c @@ -0,0 +1,605 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef SENDFILE_BUF_SIZE +#define SENDFILE_BUF_SIZE 16384 +#endif + +struct tls_config { + char *cert_file; + char *cert_chain_file; + char *key_file; + char *dh_param_file; + int8_t check_client_cert; +}; + +struct tls_context_head { + SSL *ssl; + int fd; + struct tls_context_head *_next; +}; + +struct tls_thread_context { + struct tls_context_head *contexts; + struct mk_list _head; +}; + +struct tls_server_context { + struct tls_config config; + SSL_CTX *ctx; + pthread_mutex_t mutex; + struct mk_list threads; +}; + +static pthread_key_t local_context; +static struct tls_server_context *server_context; + +static struct tls_thread_context *local_thread_context(void) +{ + return pthread_getspecific(local_context); +} + +static int tls_handle_return(SSL *ssl, int ret) +{ + int error; + + if (ret > 0) { + return ret; + } + + error = SSL_get_error(ssl, ret); + switch (error) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + errno = 0; + return -1; + } +} + +static int config_parse(const char *confdir, struct tls_config *conf) +{ + long unsigned int len; + char *conf_path = NULL; + char *cert_file = NULL; + char *cert_chain_file = NULL; + char *key_file = NULL; + char *dh_param_file = NULL; + int8_t check_client_cert = MK_FALSE; + struct mk_rconf_section *section; + struct mk_rconf *conf_head; + + mk_api->str_build(&conf_path, &len, "%stls.conf", confdir); + conf_head = mk_api->config_open(conf_path); + mk_api->mem_free(conf_path); + + if (conf_head == NULL) { + goto fallback; + } + + section = mk_api->config_section_get(conf_head, "TLS"); + if (!section) { + goto fallback; + } + + cert_file = mk_api->config_section_get_key(section, "CertificateFile", + MK_RCONF_STR); + cert_chain_file = mk_api->config_section_get_key(section, + "CertificateChainFile", + MK_RCONF_STR); + key_file = mk_api->config_section_get_key(section, "RSAKeyFile", + MK_RCONF_STR); + dh_param_file = mk_api->config_section_get_key(section, "DHParameterFile", + MK_RCONF_STR); + check_client_cert = (size_t) mk_api->config_section_get_key(section, + "CheckClientCert", + MK_RCONF_BOOL); + +fallback: + if (!cert_file) { + mk_api->str_build(&conf->cert_file, &len, "%ssrv_cert.pem", confdir); + } + else if (*cert_file == '/') { + conf->cert_file = cert_file; + } + else { + mk_api->str_build(&conf->cert_file, &len, "%s/%s", confdir, cert_file); + } + + if (cert_chain_file == NULL) { + conf->cert_chain_file = NULL; + } + else if (*cert_chain_file == '/') { + conf->cert_chain_file = cert_chain_file; + } + else { + mk_api->str_build(&conf->cert_chain_file, &len, "%s/%s", + confdir, cert_chain_file); + } + + if (!key_file) { + mk_api->str_build(&conf->key_file, &len, "%srsa.pem", confdir); + } + else if (*key_file == '/') { + conf->key_file = key_file; + } + else { + mk_api->str_build(&conf->key_file, &len, "%s/%s", confdir, key_file); + } + + if (!dh_param_file) { + mk_api->str_build(&conf->dh_param_file, &len, "%sdhparam.pem", confdir); + } + else if (*dh_param_file == '/') { + conf->dh_param_file = dh_param_file; + } + else { + mk_api->str_build(&conf->dh_param_file, &len, "%s/%s", + confdir, dh_param_file); + } + + conf->check_client_cert = check_client_cert; + + if (conf_head) { + mk_api->config_free(conf_head); + } + + return 0; +} + +static void config_free(struct tls_config *conf) +{ + if (conf->cert_file) mk_api->mem_free(conf->cert_file); + if (conf->cert_chain_file) mk_api->mem_free(conf->cert_chain_file); + if (conf->key_file) mk_api->mem_free(conf->key_file); + if (conf->dh_param_file) mk_api->mem_free(conf->dh_param_file); +} + +static int tls_load_credentials(struct tls_server_context *ctx) +{ + BIO *bio; + DH *dh; + + if (SSL_CTX_use_certificate_chain_file(ctx->ctx, ctx->config.cert_file) != 1) { + mk_api->_error(MK_WARN, "[tls] failed to load certificate chain from %s", + ctx->config.cert_file); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(ctx->ctx, ctx->config.key_file, + SSL_FILETYPE_PEM) != 1) { + mk_api->_error(MK_WARN, "[tls] failed to load private key from %s", + ctx->config.key_file); + return -1; + } + + if (SSL_CTX_check_private_key(ctx->ctx) != 1) { + mk_api->_error(MK_WARN, "[tls] certificate/private key mismatch"); + return -1; + } + + if (ctx->config.cert_chain_file != NULL) { + if (SSL_CTX_load_verify_locations(ctx->ctx, + ctx->config.cert_chain_file, + NULL) != 1) { + mk_api->_error(MK_WARN, "[tls] failed to load CA chain from %s", + ctx->config.cert_chain_file); + return -1; + } + } + + bio = BIO_new_file(ctx->config.dh_param_file, "r"); + if (bio != NULL) { + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + BIO_free(bio); + if (dh != NULL) { + SSL_CTX_set_tmp_dh(ctx->ctx, dh); + DH_free(dh); + } + } + + return 0; +} + +static struct tls_context_head *context_get_head(int fd) +{ + struct tls_thread_context *thctx; + struct tls_context_head *cur; + + thctx = local_thread_context(); + if (thctx == NULL) { + return NULL; + } + + cur = thctx->contexts; + while (cur) { + if (cur->fd == fd) { + return cur; + } + cur = cur->_next; + } + + return NULL; +} + +static SSL *context_get(int fd) +{ + struct tls_context_head *head; + + head = context_get_head(fd); + if (head == NULL) { + return NULL; + } + + return head->ssl; +} + +static SSL *context_new(int fd) +{ + SSL *ssl; + struct tls_context_head *ctx_head; + struct tls_thread_context *thctx; + + thctx = local_thread_context(); + if (thctx == NULL) { + return NULL; + } + + ctx_head = context_get_head(-1); + if (ctx_head == NULL) { + ctx_head = mk_api->mem_alloc_z(sizeof(struct tls_context_head)); + if (ctx_head == NULL) { + return NULL; + } + ctx_head->fd = -1; + ctx_head->_next = thctx->contexts; + thctx->contexts = ctx_head; + } + + ssl = SSL_new(server_context->ctx); + if (ssl == NULL) { + return NULL; + } + + SSL_set_fd(ssl, fd); + SSL_set_accept_state(ssl); + ctx_head->ssl = ssl; + ctx_head->fd = fd; + + return ssl; +} + +static void context_unset(int fd) +{ + struct tls_context_head *ctx_head; + + ctx_head = context_get_head(fd); + if (ctx_head == NULL) { + return; + } + + if (ctx_head->ssl != NULL) { + SSL_shutdown(ctx_head->ssl); + SSL_free(ctx_head->ssl); + ctx_head->ssl = NULL; + } + ctx_head->fd = -1; +} + +static void contexts_free(struct tls_context_head *ctx) +{ + struct tls_context_head *cur; + struct tls_context_head *tmp; + + cur = ctx; + while (cur) { + tmp = cur->_next; + if (cur->ssl != NULL) { + SSL_free(cur->ssl); + } + mk_api->mem_free(cur); + cur = tmp; + } +} + +static SSL *context_get_or_create(int fd) +{ + SSL *ssl; + + ssl = context_get(fd); + if (ssl != NULL) { + return ssl; + } + + return context_new(fd); +} + +static int mk_tls_read(struct mk_plugin *plugin, int fd, void *buf, int count) +{ + SSL *ssl; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + return tls_handle_return(ssl, SSL_read(ssl, buf, count)); +} + +static int mk_tls_write(struct mk_plugin *plugin, int fd, + const void *buf, size_t count) +{ + SSL *ssl; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + return tls_handle_return(ssl, SSL_write(ssl, buf, count)); +} + +static int mk_tls_writev(struct mk_plugin *plugin, int fd, struct mk_iov *mk_io) +{ + int i; + int ret; + SSL *ssl; + size_t used; + size_t len; + unsigned char *buf; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + len = mk_io->total_len; + buf = mk_api->mem_alloc(len); + if (buf == NULL) { + return -1; + } + + used = 0; + for (i = 0; i < mk_io->iov_idx; i++) { + memcpy(buf + used, mk_io->io[i].iov_base, mk_io->io[i].iov_len); + used += mk_io->io[i].iov_len; + } + + ret = SSL_write(ssl, buf, len); + mk_api->mem_free(buf); + + return tls_handle_return(ssl, ret); +} + +static int mk_tls_send_file(struct mk_plugin *plugin, int fd, int file_fd, + off_t *file_offset, size_t file_count) +{ + int ret; + SSL *ssl; + ssize_t used; + ssize_t remain; + ssize_t sent; + unsigned char *buf; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + buf = mk_api->mem_alloc(SENDFILE_BUF_SIZE); + if (buf == NULL) { + return -1; + } + + sent = 0; + remain = file_count; + + do { + used = pread(file_fd, buf, SENDFILE_BUF_SIZE, *file_offset); + if (used == 0) { + ret = 0; + } + else if (used < 0) { + ret = -1; + } + else if (remain > 0) { + ret = SSL_write(ssl, buf, used < remain ? used : remain); + } + else { + ret = SSL_write(ssl, buf, used); + } + + if (ret > 0) { + if (remain > 0) { + remain -= ret; + } + sent += ret; + *file_offset += ret; + } + } while (ret > 0); + + mk_api->mem_free(buf); + + if (sent > 0) { + return sent; + } + + return tls_handle_return(ssl, ret); +} + +static int mk_tls_close(struct mk_plugin *plugin, int fd) +{ + (void) plugin; + + context_unset(fd); + close(fd); + return 0; +} + +static int mk_tls_plugin_init(struct mk_plugin *plugin, char *confdir) +{ + int used; + struct mk_list *head; + struct mk_config_listener *listen; + + mk_api = plugin->api; + + used = MK_FALSE; + mk_list_foreach(head, &plugin->server_ctx->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + if (listen->flags & MK_CAP_SOCK_TLS) { + used = MK_TRUE; + break; + } + } + + if (!used) { + return -2; + } + + server_context = mk_api->mem_alloc_z(sizeof(struct tls_server_context)); + if (server_context == NULL) { + return -1; + } + + config_parse(confdir, &server_context->config); + pthread_mutex_init(&server_context->mutex, NULL); + mk_list_init(&server_context->threads); + pthread_key_create(&local_context, NULL); + + OPENSSL_init_ssl(0, NULL); + SSL_load_error_strings(); + + server_context->ctx = SSL_CTX_new(TLS_server_method()); + if (server_context->ctx == NULL) { + return -1; + } + + SSL_CTX_set_mode(server_context->ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_session_cache_mode(server_context->ctx, SSL_SESS_CACHE_SERVER); + + if (server_context->config.check_client_cert == MK_TRUE) { + SSL_CTX_set_verify(server_context->ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + } + else { + SSL_CTX_set_verify(server_context->ctx, SSL_VERIFY_NONE, NULL); + } + + return tls_load_credentials(server_context); +} + +static void mk_tls_worker_init(struct mk_server *server) +{ + struct tls_thread_context *thctx; + + (void) server; + + thctx = mk_api->mem_alloc_z(sizeof(struct tls_thread_context)); + if (thctx == NULL) { + exit(EXIT_FAILURE); + } + + pthread_mutex_lock(&server_context->mutex); + mk_list_add(&thctx->_head, &server_context->threads); + pthread_mutex_unlock(&server_context->mutex); + + pthread_setspecific(local_context, thctx); +} + +static int mk_tls_plugin_exit(struct mk_plugin *plugin) +{ + struct mk_list *cur; + struct mk_list *tmp; + struct tls_thread_context *thctx; + + (void) plugin; + + if (server_context == NULL) { + return 0; + } + + mk_list_foreach_safe(cur, tmp, &server_context->threads) { + thctx = mk_list_entry(cur, struct tls_thread_context, _head); + contexts_free(thctx->contexts); + mk_mem_free(thctx); + } + + if (server_context->ctx != NULL) { + SSL_CTX_free(server_context->ctx); + } + + config_free(&server_context->config); + pthread_mutex_destroy(&server_context->mutex); + mk_api->mem_free(server_context); + server_context = NULL; + + return 0; +} + +struct mk_plugin_network mk_plugin_network_tls = { + .read = mk_tls_read, + .write = mk_tls_write, + .writev = mk_tls_writev, + .close = mk_tls_close, + .send_file = mk_tls_send_file, + .buffer_size = 16384 +}; + +struct mk_plugin mk_plugin_tls = { + .shortname = "tls", + .name = "SSL/TLS Network Layer", + .version = MK_VERSION_STR, + .hooks = MK_PLUGIN_NETWORK_LAYER, + .init_plugin = mk_tls_plugin_init, + .exit_plugin = mk_tls_plugin_exit, + .master_init = NULL, + .worker_init = mk_tls_worker_init, + .network = &mk_plugin_network_tls, + .capabilities = MK_CAP_SOCK_TLS +}; diff --git a/lib/monkey/test/CMakeLists.txt b/lib/monkey/test/CMakeLists.txt index 75c3464dbbf..9bc8c36d62c 100644 --- a/lib/monkey/test/CMakeLists.txt +++ b/lib/monkey/test/CMakeLists.txt @@ -12,6 +12,13 @@ set(UNIT_TESTS_FILES event_timeout.c ) +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + set(UNIT_TESTS_FILES + ${UNIT_TESTS_FILES} + win32_socketpair.c + ) +endif() + # Prepare list of unit tests foreach(source_file ${UNIT_TESTS_FILES}) get_filename_component(source_file_we ${source_file} NAME_WE) diff --git a/lib/monkey/test/event_timeout.c b/lib/monkey/test/event_timeout.c index 8ed4183fd68..2e175c3755a 100644 --- a/lib/monkey/test/event_timeout.c +++ b/lib/monkey/test/event_timeout.c @@ -15,56 +15,60 @@ * limitations under the License. */ -#ifndef _WIN32 -#include -#include -#endif - -#include -#include +#include #include "mk_tests.h" -static void consume_timer_tick(int fd) +static int consume_timer_tick(int fd, uint64_t *val) { - int ret; - uint64_t val; #ifdef _WIN32 - ret = recv(fd, &val, sizeof(val), 0); + return recv(fd, (char *) val, sizeof(*val), MSG_WAITALL); #else - ret = read(fd, &val, sizeof(val)); + return read(fd, val, sizeof(*val)); #endif - TEST_ASSERT(ret >= 0); } -#ifdef _WIN32 -static void check_timer_invalidated(int fd) -{ -} -#else -static void check_timer_invalidated(int fd) -{ - struct itimerspec timer_spec; - TEST_ASSERT(timerfd_gettime(fd, &timer_spec) == -1); -} -#endif - void test_timeout_tick_destroy(void) { + int ret; + int tries; struct mk_event_loop *evl; - struct mk_event *ev; + struct mk_event *fired; + struct mk_event ev = {0}; + uint64_t tick = 0; int fd; int timeout_interval = 1; - evl = mk_event_loop_create(1); - ev = mk_mem_alloc_z(sizeof(struct mk_event*)); - fd = mk_event_timeout_create(evl, timeout_interval, 0, ev); - TEST_ASSERT(ev->fd == fd); + TEST_CHECK(mk_event_init() == 0); + + evl = mk_event_loop_create(4); + TEST_ASSERT(evl != NULL); + + fd = mk_event_timeout_create(evl, timeout_interval, 0, &ev); + TEST_ASSERT(fd >= 0); + TEST_ASSERT(ev.fd == fd); + + ret = 0; + for (tries = 0; tries < 2 && ret == 0; tries++) { + ret = mk_event_wait_2(evl, 1500); + } + TEST_ASSERT(ret == 1); + + fired = NULL; + mk_event_foreach(fired, evl) { + TEST_ASSERT(fired == &ev); + TEST_ASSERT((fired->mask & MK_EVENT_READ) != 0); + break; + } + + ret = consume_timer_tick(ev.fd, &tick); + TEST_ASSERT(ret == sizeof(tick)); + TEST_ASSERT(tick == 1); - consume_timer_tick(ev->fd); - mk_event_timeout_destroy(evl, ev); + ret = mk_event_timeout_destroy(evl, &ev); + TEST_ASSERT(ret == 0); - check_timer_invalidated(fd); + mk_event_loop_destroy(evl); } TEST_LIST = { diff --git a/lib/monkey/test/lib_server.c b/lib/monkey/test/lib_server.c index 67295997961..f511baf01fa 100644 --- a/lib/monkey/test/lib_server.c +++ b/lib/monkey/test/lib_server.c @@ -17,6 +17,7 @@ #include #include +#include #include "mk_tests.h" @@ -240,6 +241,20 @@ void test_server_start_stop_single_worker(void) TEST_CHECK(result == TEST_SUCCESS); } +void test_core_plain_transport_available(void) +{ + struct mk_plugin_network *transport; + + transport = mk_net_transport_default(); + TEST_CHECK(transport != NULL); + TEST_CHECK(transport->read != NULL); + TEST_CHECK(transport->write != NULL); + TEST_CHECK(transport->writev != NULL); + TEST_CHECK(transport->close != NULL); + TEST_CHECK(transport->send_file != NULL); + TEST_CHECK(transport->plugin == NULL); +} + void test_server_start_stop_more_workers(void) { mk_ctx_t *srv = mk_create(); @@ -270,6 +285,10 @@ void test_server_start_stop_force_fair_balancing(void) } TEST_LIST = { + { + "core_plain_transport_available", + test_core_plain_transport_available + }, { "server_start_stop", test_server_start_stop_single_worker diff --git a/lib/monkey/test/win32_socketpair.c b/lib/monkey/test/win32_socketpair.c new file mode 100644 index 00000000000..32de3b8c18e --- /dev/null +++ b/lib/monkey/test/win32_socketpair.c @@ -0,0 +1,49 @@ +/* Monkey HTTP Server + * ================== + * Copyright 2001-2026 Monkey Software LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "mk_tests.h" + +void test_win32_socketpair_stream(void) +{ + int ret; + uint64_t in = 0x12345678; + uint64_t out = 0; + SOCKET pair[2]; + + ret = mk_win32_socketpair(pair); + TEST_ASSERT(ret == 0); + + ret = send(pair[0], (const char *) &in, sizeof(in), 0); + TEST_ASSERT(ret == sizeof(in)); + + ret = recv(pair[1], (char *) &out, sizeof(out), MSG_WAITALL); + TEST_ASSERT(ret == sizeof(out)); + TEST_ASSERT(out == in); + + closesocket(pair[0]); + closesocket(pair[1]); +} + +TEST_LIST = { + {"win32_socketpair_stream", test_win32_socketpair_stream}, + {NULL, NULL} +}; diff --git a/lib/monkey/tls/CMakeLists.txt b/lib/monkey/tls/CMakeLists.txt new file mode 100644 index 00000000000..ac983007d2e --- /dev/null +++ b/lib/monkey/tls/CMakeLists.txt @@ -0,0 +1,106 @@ +set(MK_TLS_PROVIDER "none") +set(MK_TLS_SOURCES "${PROJECT_SOURCE_DIR}/tls/tls_stub.c") +set(MK_TLS_LIBS "") +set(MK_TLS_INCLUDE_DIRS "") + +function(mk_tls_openssl_matches_target out_var) + set(match TRUE) + + if(WIN32 AND OPENSSL_FOUND) + set(target_platform "") + + if(CMAKE_VS_PLATFORM_NAME) + string(TOLOWER "${CMAKE_VS_PLATFORM_NAME}" target_platform) + elseif(CMAKE_GENERATOR_PLATFORM) + string(TOLOWER "${CMAKE_GENERATOR_PLATFORM}" target_platform) + elseif(CMAKE_SYSTEM_PROCESSOR) + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" target_platform) + endif() + + if(target_platform STREQUAL "arm64" OR target_platform STREQUAL "aarch64") + foreach(openssl_lib ${OPENSSL_LIBRARIES}) + string(TOLOWER "${openssl_lib}" openssl_lib_lower) + if(openssl_lib_lower MATCHES "([/\\\\]|^)x64([/\\\\]|$)" OR + openssl_lib_lower MATCHES "([/\\\\]|^)amd64([/\\\\]|$)") + set(match FALSE) + endif() + endforeach() + elseif(target_platform STREQUAL "x64") + foreach(openssl_lib ${OPENSSL_LIBRARIES}) + string(TOLOWER "${openssl_lib}" openssl_lib_lower) + if(openssl_lib_lower MATCHES "([/\\\\]|^)arm64([/\\\\]|$)") + set(match FALSE) + endif() + endforeach() + endif() + endif() + + set(${out_var} ${match} PARENT_SCOPE) +endfunction() + +if(MK_TLS AND (MK_TLS_BACKEND STREQUAL "auto" OR MK_TLS_BACKEND STREQUAL "openssl")) + find_package(OpenSSL QUIET) + if(OPENSSL_FOUND) + mk_tls_openssl_matches_target(MK_TLS_OPENSSL_MATCHES_TARGET) + if(MK_TLS_OPENSSL_MATCHES_TARGET) + set(MK_TLS_PROVIDER "openssl") + set(MK_TLS_SOURCES "${PROJECT_SOURCE_DIR}/tls/tls_openssl.c") + set(MK_TLS_LIBS ${OPENSSL_LIBRARIES}) + if(OPENSSL_INCLUDE_DIR) + set(MK_TLS_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + endif() + elseif(MK_TLS_BACKEND STREQUAL "openssl") + message(FATAL_ERROR "MK_TLS_BACKEND=openssl was requested but the detected OpenSSL libraries do not match the target platform") + else() + message(STATUS "Skipping OpenSSL backend due to target/library architecture mismatch") + endif() + elseif(MK_TLS_BACKEND STREQUAL "openssl") + message(FATAL_ERROR "MK_TLS_BACKEND=openssl was requested but OpenSSL was not found") + endif() +endif() + +if(MK_TLS AND MK_TLS_PROVIDER STREQUAL "none") + set(MK_TLS_PROVIDER "mbedtls") + set(MK_TLS_SOURCES "${PROJECT_SOURCE_DIR}/tls/tls_mbedtls.c") + set(MK_TLS_LIBS mbedtls mbedx509 mbedcrypto) + + if(NOT MK_MBEDTLS_SHARED) + option(ENABLE_TESTING OFF) + option(ENABLE_PROGRAMS OFF) + option(INSTALL_MBEDTLS_HEADERS OFF) + set(MK_MBEDTLS_SRC ../deps/mbedtls-2.4.2) + add_subdirectory(${MK_MBEDTLS_SRC} ${CMAKE_BINARY_DIR}/mbedtls-2.4.2) + set(MK_TLS_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/deps/mbedtls-2.4.2/include) + else() + find_path(MBEDTLS_INCLUDE_DIR NAMES mbedtls/version.h) + find_library(MBEDTLS_LIBRARY NAMES mbedtls) + find_library(MBEDX509_LIBRARY NAMES mbedx509) + find_library(MBEDCRYPTO_LIBRARY NAMES mbedcrypto) + + if(NOT MBEDTLS_INCLUDE_DIR OR NOT MBEDTLS_LIBRARY OR + NOT MBEDX509_LIBRARY OR NOT MBEDCRYPTO_LIBRARY) + message(FATAL_ERROR "MK_TLS_BACKEND=mbedtls was selected but the mbedTLS headers or libraries were not found") + endif() + + set(MK_TLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) + set(MK_TLS_LIBS ${MBEDTLS_LIBRARY} ${MBEDX509_LIBRARY} ${MBEDCRYPTO_LIBRARY}) + endif() +endif() + +message(STATUS "TLS backend selected: ${MK_TLS_PROVIDER}") + +configure_file( + "${PROJECT_SOURCE_DIR}/tls/tls.conf" + "${PROJECT_BINARY_DIR}/conf/tls.conf" + COPYONLY + ) + +if(NOT MK_WITHOUT_CONF AND DEFINED MK_PATH_CONF) + install(FILES "${PROJECT_SOURCE_DIR}/tls/tls.conf" + DESTINATION ${MK_PATH_CONF}) +endif() + +set(MK_TLS_PROVIDER "${MK_TLS_PROVIDER}" PARENT_SCOPE) +set(MK_TLS_SOURCES "${MK_TLS_SOURCES}" PARENT_SCOPE) +set(MK_TLS_LIBS "${MK_TLS_LIBS}" PARENT_SCOPE) +set(MK_TLS_INCLUDE_DIRS "${MK_TLS_INCLUDE_DIRS}" PARENT_SCOPE) diff --git a/lib/monkey/tls/tls.conf b/lib/monkey/tls/tls.conf new file mode 100644 index 00000000000..fcf00517a38 --- /dev/null +++ b/lib/monkey/tls/tls.conf @@ -0,0 +1,25 @@ +[TLS] + # If no absolute path is given, files are assumed to be located + # in plugin configuration directory. All files are assumed to be + # stored in PEM format. + + # Server certificate + # + CertificateFile srv_cert.pem + + # Certificate chain + # + # Not required, but can speed-up handshakes. + # + # CertificateChainFile srv_cert_chain.pem + + # Server RSA key + # + RSAKeyFile rsa_key.pem + + # Diffie-Hellman parameters + # + # Generate using openssl: + # $ openssl dhparam -out dhparam.pem 1024 + # + DHParameterFile dhparam.pem diff --git a/lib/monkey/tls/tls_mbedtls.c b/lib/monkey/tls/tls_mbedtls.c new file mode 100644 index 00000000000..22a014579a3 --- /dev/null +++ b/lib/monkey/tls/tls_mbedtls.c @@ -0,0 +1,991 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SENDFILE_BUF_SIZE +#define SENDFILE_BUF_SIZE MBEDTLS_SSL_MAX_CONTENT_LEN +#endif + +#ifndef POLAR_DEBUG_LEVEL +#define POLAR_DEBUG_LEVEL 0 +#endif + +#if (!defined(MBEDTLS_BIGNUM_C) || !defined(MBEDTLS_ENTROPY_C) || \ + !defined(MBEDTLS_SSL_TLS_C) || !defined(MBEDTLS_SSL_SRV_C) || \ + !defined(MBEDTLS_NET_C) || !defined(MBEDTLS_RSA_C) || \ + !defined(MBEDTLS_CTR_DRBG_C)) +#error "One or more required POLARSSL modules not built." +#endif + +struct polar_config { + char *cert_file; + char *cert_chain_file; + char *key_file; + char *dh_param_file; + int8_t check_client_cert; +}; + +#if defined(MBEDTLS_SSL_CACHE_C) +struct polar_sessions { + pthread_mutex_t _mutex; + mbedtls_ssl_cache_context cache; +}; + +static struct polar_sessions global_sessions = { + ._mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +#endif + +struct polar_context_head { + mbedtls_ssl_context context; + int fd; + int want_event; + struct polar_context_head *_next; +}; + +struct polar_thread_context { + + struct polar_context_head *contexts; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_pk_context pkey; + mbedtls_ssl_config conf; + + struct mk_list _head; +}; + +struct polar_server_context { + struct mk_server *server; + struct polar_config config; + mbedtls_x509_crt cert; + mbedtls_x509_crt ca_cert; + pthread_mutex_t mutex; + mbedtls_dhm_context dhm; + mbedtls_entropy_context entropy; + struct polar_thread_context threads; +}; + +struct polar_server_context *server_context; +static const char *my_dhm_P = MBEDTLS_DHM_RFC5114_MODP_2048_P; +static const char *my_dhm_G = MBEDTLS_DHM_RFC5114_MODP_2048_G; + +static pthread_key_t local_context; +static int local_context_created = MK_FALSE; +static struct polar_context_head *context_get_head(int fd); + +#ifdef _WIN32 +static ssize_t tls_pread(int fd, void *buf, size_t count, off_t offset) +{ + __int64 original; + int ret; + + original = _lseeki64(fd, 0, SEEK_CUR); + if (original < 0) { + return -1; + } + + if (_lseeki64(fd, offset, SEEK_SET) < 0) { + return -1; + } + + ret = _read(fd, buf, (unsigned int) count); + _lseeki64(fd, original, SEEK_SET); + + return ret; +} +#else +static ssize_t tls_pread(int fd, void *buf, size_t count, off_t offset) +{ + return pread(fd, buf, count, offset); +} +#endif + +/* + * The following function is taken from PolarSSL sources to get + * the number of available bytes to read from a buffer. + * + * We copy this to make it inline and avoid extra context switches + * on each read routine. + */ +static inline size_t polar_get_bytes_avail(const mbedtls_ssl_context *ssl) +{ + return (ssl->in_offt == NULL ? 0 : ssl->in_msglen); +} + +static struct polar_thread_context *local_thread_context(void) +{ + return pthread_getspecific(local_context); +} + +#if (POLAR_DEBUG_LEVEL > 0) +static void polar_debug(void *ctx, int level, const char *str) +{ + (void)ctx; + + if (level < POLAR_DEBUG_LEVEL) { + mk_warn("%.*s", (int)strlen(str) - 1, str); + } +} +#endif + +static int handle_return(mbedtls_ssl_context *ssl, int ret) +{ + struct polar_context_head *head; + + head = context_get_head(*(int *) ssl->p_bio); + if (head != NULL) { + head->want_event = MK_EVENT_EMPTY; + } + +#if defined(TRACE) + char err_buf[72]; + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + MK_TRACE("[tls] SSL error: %s", err_buf); + } +#endif + if (ret < 0) { + switch( ret ) + { + case MBEDTLS_ERR_SSL_WANT_READ: + if (head != NULL) { + head->want_event = MK_EVENT_READ; + } + if (errno != EAGAIN) + errno = EAGAIN; + return -1; + case MBEDTLS_ERR_SSL_WANT_WRITE: + if (head != NULL) { + head->want_event = MK_EVENT_WRITE; + } + if (errno != EAGAIN) + errno = EAGAIN; + return -1; + case MBEDTLS_ERR_SSL_CONN_EOF: + return 0; + default: + if (errno == EAGAIN) + errno = 0; + return -1; + } + } + else { + return ret; + } +} + +static int tls_cache_get(void *p, mbedtls_ssl_session *session) +{ + struct polar_sessions *session_cache; + int ret; + + session_cache = p; + pthread_mutex_lock(&session_cache->_mutex); + ret = mbedtls_ssl_cache_get(&session_cache->cache, session); + pthread_mutex_unlock(&session_cache->_mutex); + + return ret; +} + +static int tls_cache_set(void *p, const mbedtls_ssl_session *session) +{ + struct polar_sessions *session_cache; + int ret; + + session_cache = p; + pthread_mutex_lock(&session_cache->_mutex); + ret = mbedtls_ssl_cache_set(&session_cache->cache, session); + pthread_mutex_unlock(&session_cache->_mutex); + + return ret; +} + +static int config_parse(const char *confdir, + const struct mk_server *server, + struct polar_config *conf) +{ + long unsigned int len; + char *conf_path = NULL; + char *cert_file = NULL; + char *cert_chain_file = NULL; + char *key_file = NULL; + char *dh_param_file = NULL; + int8_t check_client_cert = MK_FALSE; + struct mk_rconf_section *section; + struct mk_rconf *conf_head; + + mk_string_build(&conf_path, &len, "%s/tls.conf", confdir); + conf_head = mk_rconf_open(conf_path); + mk_mem_free(conf_path); + + if (conf_head == NULL) { + goto fallback; + } + + section = mk_rconf_section_get(conf_head, "TLS"); + if (!section) { + goto fallback; + } + + cert_file = mk_rconf_section_get_key(section, "CertificateFile", MK_RCONF_STR); + cert_chain_file = mk_rconf_section_get_key(section, "CertificateChainFile", MK_RCONF_STR); + key_file = mk_rconf_section_get_key(section, "RSAKeyFile", MK_RCONF_STR); + dh_param_file = mk_rconf_section_get_key(section, "DHParameterFile", MK_RCONF_STR); + + check_client_cert = (size_t) mk_rconf_section_get_key(section, + "CheckClientCert", + MK_RCONF_BOOL); +fallback: + if (server->tls_cert_file != NULL) { + if (cert_file != NULL) { + mk_mem_free(cert_file); + } + cert_file = mk_string_dup(server->tls_cert_file); + } + + if (server->tls_cert_chain_file != NULL) { + if (cert_chain_file != NULL) { + mk_mem_free(cert_chain_file); + } + cert_chain_file = mk_string_dup(server->tls_cert_chain_file); + } + + if (server->tls_key_file != NULL) { + if (key_file != NULL) { + mk_mem_free(key_file); + } + key_file = mk_string_dup(server->tls_key_file); + } + + if (server->tls_dh_param_file != NULL) { + if (dh_param_file != NULL) { + mk_mem_free(dh_param_file); + } + dh_param_file = mk_string_dup(server->tls_dh_param_file); + } + + /* Set default name if not specified */ + if (!cert_file) { + mk_string_build(&conf->cert_file, &len, + "%s/srv_cert.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*cert_file == '/') { + conf->cert_file = cert_file; + } + else { + mk_string_build(&conf->cert_file, &len, + "%s/%s", confdir, cert_file); + mk_mem_free(cert_file); + } + } + + /* Set default name if not specified */ + if (cert_chain_file) { + /* Set absolute path or compose a new one based on the relative */ + if (*cert_chain_file == '/') { + conf->cert_chain_file = cert_chain_file; + } + else { + mk_string_build(&conf->cert_chain_file, &len, + "%s/%s", confdir, cert_chain_file); + mk_mem_free(cert_chain_file); + } + } + else { + conf->cert_chain_file = NULL; + } + + /* Set default name if not specified */ + if (!key_file) { + mk_string_build(&conf->key_file, &len, + "%s/rsa.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*key_file == '/') { + conf->key_file = key_file; + } + else { + mk_string_build(&conf->key_file, &len, + "%s/%s", confdir, key_file); + mk_mem_free(key_file); + } + } + + /* Set default name if not specified */ + if (!dh_param_file) { + mk_string_build(&conf->dh_param_file, &len, + "%s/dhparam.pem", confdir); + } + else { + /* Set absolute path or compose a new one based on the relative */ + if (*dh_param_file == '/') { + conf->dh_param_file = dh_param_file; + } + else { + mk_string_build(&conf->dh_param_file, &len, + "%s/%s", confdir, dh_param_file); + mk_mem_free(dh_param_file); + } + } + + /* Set client cert check */ + conf->check_client_cert = check_client_cert; + + if (conf_head) { + mk_rconf_free(conf_head); + } + + return 0; +} + +static int polar_load_certs(const struct polar_config *conf) +{ + char err_buf[72]; + int ret = -1; + + ret = mbedtls_x509_crt_parse_file(&server_context->cert, conf->cert_file); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load cert '%s' failed: %s", + conf->cert_file, + err_buf); + +#if defined(MBEDTLS_CERTS_C) + mk_warn("[tls] Using test certificates, " + "please set 'CertificateFile' in tls.conf"); + + ret = mbedtls_x509_crt_parse(&server_context->cert, + (unsigned char *)mbedtls_test_srv_crt, strlen(mbedtls_test_srv_crt)); + + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load built-in cert failed: %s", err_buf); + return -1; + } + + return 0; +#else + return -1; +#endif // defined(MBEDTLS_CERTS_C) + } + else if (conf->cert_chain_file != NULL) { + ret = mbedtls_x509_crt_parse_file(&server_context->ca_cert, + conf->cert_chain_file); + + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_warn("[tls] Load cert chain '%s' failed: %s", + conf->cert_chain_file, + err_buf); + } + } + + return 0; +} + +static int polar_load_key(struct polar_thread_context *thread_context, + const struct polar_config *conf) +{ + char err_buf[72]; + int ret; + + assert(conf->key_file); + + ret = mbedtls_pk_parse_keyfile(&thread_context->pkey, conf->key_file, NULL); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + MK_TRACE("[tls] Load key '%s' failed: %s", + conf->key_file, + err_buf); + +#if defined(MBEDTLS_CERTS_C) + + ret = mbedtls_pk_parse_key(&thread_context->pkey, + (unsigned char *)mbedtls_test_srv_key, + strlen(mbedtls_test_srv_key), NULL, 0); + if (ret) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Failed to load built-in RSA key: %s", err_buf); + return -1; + } +#else + return -1; +#endif // defined(MBEDTLS_CERTS_C) + } + return 0; +} + +static int polar_load_dh_param(const struct polar_config *conf) +{ + char err_buf[72]; + int ret; + + assert(conf->dh_param_file); + + ret = mbedtls_dhm_parse_dhmfile(&server_context->dhm, conf->dh_param_file); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + + ret = mbedtls_mpi_read_string(&server_context->dhm.P, 16, my_dhm_P); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Load DH parameter failed: %s", err_buf); + return -1; + } + ret = mbedtls_mpi_read_string(&server_context->dhm.G, 16, my_dhm_G); + if (ret < 0) { + mbedtls_strerror(ret, err_buf, sizeof(err_buf)); + mk_err("[tls] Load DH parameter failed: %s", err_buf); + return -1; + } + } + + return 0; +} + +static int tls_global_init() +{ + if (local_context_created == MK_FALSE) { + pthread_key_create(&local_context, NULL); + local_context_created = MK_TRUE; + } + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_init(&global_sessions.cache); +#endif + + pthread_mutex_lock(&server_context->mutex); + mk_list_init(&server_context->threads._head); + mbedtls_entropy_init(&server_context->entropy); + pthread_mutex_unlock(&server_context->mutex); + + MK_TRACE("[tls] Load certificates."); + if (polar_load_certs(&server_context->config)) { + return -1; + } + MK_TRACE("[tls] Load DH parameters."); + if (polar_load_dh_param(&server_context->config)) { + return -1; + } + + return 0; +} + + +static int entropy_func_safe(void *data, unsigned char *output, size_t len) +{ + int ret; + + pthread_mutex_lock(&server_context->mutex); + ret = mbedtls_entropy_func(data, output, len); + pthread_mutex_unlock(&server_context->mutex); + + return ret; +} + +static void contexts_free(struct polar_context_head *ctx) +{ + struct polar_context_head *cur, *next; + + if (ctx != NULL) { + cur = ctx; + next = cur->_next; + + for (; next; cur = next, next = next->_next) { + mbedtls_ssl_free(&cur->context); + memset(cur, 0, sizeof(*cur)); + mk_mem_free(cur); + } + + mbedtls_ssl_free(&cur->context); + memset(cur, 0, sizeof(*cur)); + mk_mem_free(cur); + } +} + +static void config_free(struct polar_config *conf) +{ + if (conf->cert_file) mk_mem_free(conf->cert_file); + if (conf->cert_chain_file) mk_mem_free(conf->cert_chain_file); + if (conf->key_file) mk_mem_free(conf->key_file); + if (conf->dh_param_file) mk_mem_free(conf->dh_param_file); +} + +/* Contexts may be requested from outside workers on exit so we should + * be prepared for an empty context. + */ +static mbedtls_ssl_context *context_get(int fd) +{ + struct polar_thread_context *thctx = local_thread_context(); + struct polar_context_head **cur; + + if (thctx == NULL) { + return NULL; + } + + cur = &thctx->contexts; + + for (; *cur; cur = &(*cur)->_next) { + if ((*cur)->fd == fd) { + return &(*cur)->context; + } + } + + return NULL; +} + +static struct polar_context_head *context_get_head(int fd) +{ + struct polar_thread_context *thctx = local_thread_context(); + struct polar_context_head *cur; + + if (thctx == NULL) { + return NULL; + } + + cur = thctx->contexts; + while (cur) { + if (cur->fd == fd) { + return cur; + } + cur = cur->_next; + } + + return NULL; +} + +static mbedtls_ssl_context *context_new(int fd) +{ + struct polar_thread_context *thctx = local_thread_context(); + struct polar_context_head **cur = &thctx->contexts; + mbedtls_ssl_context *ssl = NULL; + mbedtls_ssl_cache_context cache; + + mbedtls_ssl_cache_init(&cache); + + assert(cur != NULL); + + for (; *cur; cur = &(*cur)->_next) { + if ((*cur)->fd == -1) { + break; + } + } + + if (*cur == NULL) { + MK_TRACE("[polarssl %d] New ssl context.", fd); + + *cur = mk_mem_alloc(sizeof(**cur)); + if (*cur == NULL) { + return NULL; + } + (*cur)->_next = NULL; + (*cur)->want_event = MK_EVENT_READ; + + ssl = &(*cur)->context; + + mbedtls_ssl_init(ssl); + mbedtls_ssl_setup(ssl, &thctx->conf); + + mbedtls_ssl_conf_session_cache(&thctx->conf, + &global_sessions, + tls_cache_get, + tls_cache_set); + + mbedtls_ssl_set_bio(ssl, &(*cur)->fd, + mbedtls_net_send, mbedtls_net_recv, NULL); + + mbedtls_ssl_conf_rng(&thctx->conf, mbedtls_ctr_drbg_random, + &thctx->ctr_drbg); + +#if (POLAR_DEBUG_LEVEL > 0) + mbedtls_ssl_conf_dbg(ssl, polar_debug, 0); +#endif + + mbedtls_ssl_conf_own_cert(&thctx->conf, &server_context->cert, &thctx->pkey); + mbedtls_ssl_conf_ca_chain(&thctx->conf, &server_context->ca_cert, NULL); + mbedtls_ssl_conf_dh_param_ctx(&thctx->conf, &server_context->dhm); + + if (server_context->config.check_client_cert == MK_TRUE) { + mbedtls_ssl_conf_authmode(&thctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + } + } + else { + ssl = &(*cur)->context; + } + + (*cur)->fd = fd; + + return ssl; +} + +static int context_unset(int fd, mbedtls_ssl_context *ssl) +{ + struct polar_context_head *head; + + head = container_of(ssl, struct polar_context_head, context); + + if (head->fd == fd) { + head->fd = -1; + mbedtls_ssl_session_reset(ssl); + } + else { + mk_err("[polarssl %d] Context already unset.", fd); + } + + return 0; +} + +static int mk_tls_read(struct mk_plugin *plugin, int fd, void *buf, int count) +{ + size_t avail; + mbedtls_ssl_context *ssl = context_get(fd); + + (void) plugin; + + if (!ssl) { + ssl = context_new(fd); + } + + int ret = handle_return(ssl, mbedtls_ssl_read(ssl, buf, count)); + MK_TRACE("IN: %i SSL READ: %i ; CORE COUNT: %i", + ssl->in_msglen, + ret, count); + + /* Check if the caller read less than the available data */ + if (ret > 0) { + avail = polar_get_bytes_avail(ssl); + if (avail > 0) { + /* + * A read callback would never read in buffer more than + * the size specified in 'count', but it aims to return + * as value the total information read in the buffer plugin + */ + ret += avail; + } + } + return ret; +} + +static int mk_tls_write(struct mk_plugin *plugin, int fd, const void *buf, size_t count) +{ + mbedtls_ssl_context *ssl = context_get(fd); + (void) plugin; + if (!ssl) { + ssl = context_new(fd); + } + + return handle_return(ssl, mbedtls_ssl_write(ssl, buf, count)); +} + +static int mk_tls_writev(struct mk_plugin *plugin, int fd, struct mk_iov *mk_io) +{ + mbedtls_ssl_context *ssl = context_get(fd); + const int iov_len = mk_io->iov_idx; + const struct mk_iovec *io = mk_io->io; + const size_t len = mk_io->total_len; + unsigned char *buf; + size_t used = 0; + int ret = 0, i; + + (void) plugin; + + if (!ssl) { + ssl = context_new(fd); + } + + buf = mk_mem_alloc(len); + if (buf == NULL) { + mk_err("malloc failed: %s", strerror(errno)); + return -1; + } + + for (i = 0; i < iov_len; i++) { + memcpy(buf + used, io[i].iov_base, io[i].iov_len); + used += io[i].iov_len; + } + + assert(used == len); + ret = mbedtls_ssl_write(ssl, buf, len); + mk_mem_free(buf); + + return handle_return(ssl, ret); +} + +static int mk_tls_send_file(struct mk_plugin *plugin, int fd, int file_fd, off_t *file_offset, + size_t file_count) +{ + mbedtls_ssl_context *ssl = context_get(fd); + unsigned char *buf; + ssize_t used, remain = file_count, sent = 0; + int ret; + + (void) plugin; + + if (!ssl) { + ssl = context_new(fd); + } + + buf = mk_mem_alloc(SENDFILE_BUF_SIZE); + if (buf == NULL) { + return -1; + } + + do { + used = tls_pread(file_fd, buf, SENDFILE_BUF_SIZE, *file_offset); + if (used == 0) { + ret = 0; + } + else if (used < 0) { + mk_err("[tls] Read from file failed: %s", strerror(errno)); + ret = -1; + } + else if (remain > 0) { + ret = mbedtls_ssl_write(ssl, buf, used < remain ? used : remain); + } + else { + ret = mbedtls_ssl_write(ssl, buf, used); + } + + if (ret > 0) { + if (remain > 0) { + remain -= ret; + } + sent += ret; + *file_offset += ret; + } + } while (ret > 0); + + mk_mem_free(buf); + + if (sent > 0) { + return sent; + } + else { + return handle_return(ssl, ret); + } +} + +static int tls_event_interest(struct mk_plugin *plugin, int fd, int fallback) +{ + struct polar_context_head *head; + + (void) plugin; + + head = context_get_head(fd); + if (head == NULL) { + return fallback; + } + + if (head->want_event == MK_EVENT_EMPTY) { + return fallback; + } + + return head->want_event; +} + +static int mk_tls_close(struct mk_plugin *plugin, int fd) +{ + mbedtls_ssl_context *ssl = context_get(fd); + + (void) plugin; + + MK_TRACE("[fd %d] Closing connection", fd); + + if (ssl) { + mbedtls_ssl_close_notify(ssl); + context_unset(fd, ssl); + } + + mk_event_closesocket(fd); + return 0; +} + +int mk_tls_enabled(void) +{ + return MK_TRUE; +} + +int mk_tls_init(struct mk_server *server) +{ + int used; + struct mk_list *head; + struct mk_config_listener *listen; + + /* Check if the plugin will be used by some listener */ + used = MK_FALSE; + mk_list_foreach(head, &server->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + if (listen->flags & MK_CAP_SOCK_TLS) { + used = MK_TRUE; + break; + } + } + + if (used) { + /* If it's used, load certificates.. mandatory */ + server_context = mk_mem_alloc_z(sizeof(struct polar_server_context)); + server_context->server = server; + config_parse(server->path_conf_root, server, &server_context->config); + return tls_global_init(); + } + else { + return 0; + } +} + +void mk_tls_thread_init(struct mk_server *server) +{ + int ret; + struct polar_thread_context *thctx; + const char *pers = "monkey"; + (void) server; + + if (server_context == NULL) { + return; + } + + MK_TRACE("[tls] Init thread context."); + + thctx = mk_mem_alloc(sizeof(*thctx)); + if (thctx == NULL) { + goto error; + } + thctx->contexts = NULL; + mk_list_init(&thctx->_head); + + + /* SSL confniguration */ + mbedtls_ssl_config_init(&thctx->conf); + mbedtls_ssl_config_defaults(&thctx->conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + + pthread_mutex_lock(&server_context->mutex); + mk_list_add(&thctx->_head, &server_context->threads._head); + pthread_mutex_unlock(&server_context->mutex); + + mbedtls_ctr_drbg_init(&thctx->ctr_drbg); + ret = mbedtls_ctr_drbg_seed(&thctx->ctr_drbg, + entropy_func_safe, &server_context->entropy, + (const unsigned char *) pers, + strlen(pers)); + if (ret != 0) { + goto error; + } + + mbedtls_pk_init(&thctx->pkey); + + MK_TRACE("[tls] Load RSA key."); + if (polar_load_key(thctx, &server_context->config)) { + goto error; + } + + MK_TRACE("[tls] Set local thread context."); + pthread_setspecific(local_context, thctx); + + return; + + error: + exit(EXIT_FAILURE); +} + +void mk_tls_exit(struct mk_server *server) +{ + struct mk_list *cur, *tmp; + struct polar_thread_context *thctx; + (void) server; + + if (server_context == NULL) { + return; + } + + mbedtls_x509_crt_free(&server_context->cert); + mbedtls_x509_crt_free(&server_context->ca_cert); + mbedtls_dhm_free(&server_context->dhm); + + mk_list_foreach_safe(cur, tmp, &server_context->threads._head) { + thctx = mk_list_entry(cur, struct polar_thread_context, _head); + contexts_free(thctx->contexts); + mbedtls_pk_free(&thctx->pkey); + mbedtls_ctr_drbg_free(&thctx->ctr_drbg); + mbedtls_ssl_config_free(&thctx->conf); + mk_mem_free(thctx); + } + mbedtls_entropy_free(&server_context->entropy); + pthread_mutex_destroy(&server_context->mutex); + +#if defined(MBEDTLS_SSL_CACHE_C) + mbedtls_ssl_cache_free(&global_sessions.cache); +#endif + + config_free(&server_context->config); + mk_mem_free(server_context); + server_context = NULL; + + if (local_context_created == MK_TRUE) { + pthread_key_delete(local_context); + local_context_created = MK_FALSE; + } +} + +static struct mk_plugin_network mk_tls_io = { + .read = mk_tls_read, + .write = mk_tls_write, + .writev = mk_tls_writev, + .close = mk_tls_close, + .send_file = mk_tls_send_file, + .event_interest = tls_event_interest, + .buffer_size = MBEDTLS_SSL_MAX_CONTENT_LEN +}; + +struct mk_plugin_network *mk_tls_transport(void) +{ + return &mk_tls_io; +} diff --git a/lib/monkey/tls/tls_openssl.c b/lib/monkey/tls/tls_openssl.c new file mode 100644 index 00000000000..d7865889598 --- /dev/null +++ b/lib/monkey/tls/tls_openssl.c @@ -0,0 +1,872 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#include + +#include +#include +#include +#include + +#include +#include + +#ifndef SENDFILE_BUF_SIZE +#define SENDFILE_BUF_SIZE 16384 +#endif + +/* + * OPENSSL_VERSION_NUMBER has the following semantics: + * + * 0x010100000L M = major F = fix S = status + * MMNNFFPPS N = minor P = patch + */ +#define OPENSSL_1_1_0 0x010100000L + +struct tls_config { + char *cert_file; + char *cert_chain_file; + char *key_file; + char *dh_param_file; + int8_t check_client_cert; +}; + +struct tls_context_head { + SSL *ssl; + int fd; + int want_event; + struct tls_context_head *_next; +}; + +struct tls_thread_context { + struct tls_context_head *contexts; + struct mk_list _head; +}; + +struct tls_server_context { + struct mk_server *server; + struct tls_config config; + SSL_CTX *ctx; + pthread_mutex_t mutex; + struct mk_list threads; +}; + +static pthread_key_t local_context; +static int local_context_created = MK_FALSE; +static struct tls_server_context *server_context; + +static const char tls_builtin_cert[] = +"-----BEGIN CERTIFICATE-----\n" +"MIIDGzCCAgOgAwIBAgIUPmm+Zw0d+wr1qtakXqWvrVEKHkYwDQYJKoZIhvcNAQEL\n" +"BQAwHTEbMBkGA1UEAwwSTW9ua2V5IERldmVsb3BtZW50MB4XDTI2MDQxMDAwMDQz\n" +"MFoXDTM2MDQwNzAwMDQzMFowHTEbMBkGA1UEAwwSTW9ua2V5IERldmVsb3BtZW50\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZBfSrPWiomWqMl/tgoD\n" +"nAJF/us5EqJ0b1n2La0t5tuM7HPnKNsRsdvZ1Ft7EUoOrjXtSaYv/DymcLnGTV50\n" +"Iim2m9qci21g33IWpBnomKzthJyHpnRGEwfTFFDNS7Q//C3ry8ylfDgQGr0hwHb7\n" +"9ezriCub4MRA5kRfZ5Vza77zaDMVEbDks9EbaHvx9boWi3DZAVI7njOWsqSlBbol\n" +"G4IE1h7RMwWzzefOFs9XsDf3/oVxzraC3OXAvs4a9iFVdGfCfL9E+GvudQyfBXmf\n" +"nxtR/jf2cMr/xHI2o4WrzHJxPG/qioDZFqmgK0c+nGoEEogOiNq6EFWyrAVLF31o\n" +"KQIDAQABo1MwUTAdBgNVHQ4EFgQUKIDMozCfk+4qUxE78vOy+l4643gwHwYDVR0j\n" +"BBgwFoAUKIDMozCfk+4qUxE78vOy+l4643gwDwYDVR0TAQH/BAUwAwEB/zANBgkq\n" +"hkiG9w0BAQsFAAOCAQEAQisVixhpmiNkMVNFpOsFqsPEHu8s9PbYC+doVCSUBA6Q\n" +"B3Xd9mAcogQb1aCOF6i+jTspFzpoIR2TiqDlh5U/1KldPRHYWW5n1kginFtc8R1n\n" +"AZTE9Ri/YOIJOkx+bHOY3EY/UntvG03VWbpXRjssyoe+e5bhxSkTAsAk8wivj3Gx\n" +"knCb1lbE6ydTpuyjGKygpHA51cVreGle71STi7F4XgklWO//eLNlunRRTrpMErAF\n" +"RQ6I96CkfEER0JdHaVJLqYy+UksWdzoj9Fc8s492qzqi6GFAetQ6P2y7aYWEYVa4\n" +"7vs9eGnRpAn+8A6B1dSDxMPogQRBAusQYtIRoaP9Ig==\n" +"-----END CERTIFICATE-----\n"; + +static const char tls_builtin_key[] = +"-----BEGIN PRIVATE KEY-----\n" +"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpkF9Ks9aKiZao\n" +"yX+2CgOcAkX+6zkSonRvWfYtrS3m24zsc+co2xGx29nUW3sRSg6uNe1Jpi/8PKZw\n" +"ucZNXnQiKbab2pyLbWDfchakGeiYrO2EnIemdEYTB9MUUM1LtD/8LevLzKV8OBAa\n" +"vSHAdvv17OuIK5vgxEDmRF9nlXNrvvNoMxURsOSz0Rtoe/H1uhaLcNkBUjueM5ay\n" +"pKUFuiUbggTWHtEzBbPN584Wz1ewN/f+hXHOtoLc5cC+zhr2IVV0Z8J8v0T4a+51\n" +"DJ8FeZ+fG1H+N/Zwyv/EcjajhavMcnE8b+qKgNkWqaArRz6cagQSiA6I2roQVbKs\n" +"BUsXfWgpAgMBAAECggEADWuazyvKqC5ZmURRclP6kydu6M0vODVZZ9LD9DuHrYTk\n" +"83X87rPgA6a15+PRqr2kyc8E19ZqZ9lZBwT9F/SI1odcp5s21qYyi5zZA+X1Ddhp\n" +"+Bv3dIoxXaI555q5lOtQQSJVTk0FL/6z75nWiQghywYUYjOpY7HEvTTeJDGk7/sN\n" +"Bozc5Bczn5W6z7asKaXt7nC0WwauNMJ18WwKRjJlwOgBhb0/Qj5fqy1IxPI/kPwY\n" +"eAoIi91ARg/MCkUb4Yh6LTCfkwkElbpEIPr1T+2hYYl/x3wlvOUuEb+eIoZLNZ5x\n" +"K7kHPBcDEtGyl4yKPV4ltlJ0/ie0lp8pmNmXDNfV8QKBgQDUDx5dn833A1k1JMpm\n" +"XIjKOtIeaTRT58a2yM/T7de9oKRgC4hgEQ4cXG+R6r8n9/zLm3/m+l8+rH/tdBMl\n" +"C1Ekn8dL1be5zPpxDJlUSyQcYEJyDo2RHCW6Jgd6hrh73RmeFF5Tr4dFJcVVybZN\n" +"qAJPX3UcSrgmwrvqsXKkVXjrVQKBgQDMsw8eJzWDGErFLN43iE8UVD2oymgr8pq9\n" +"RcdoTX/MDHjwqUj0vGoY+IFE/sFXr100kqxH9ao5UNHn6yxed425m3wNP7O1rRqi\n" +"Nsu4WVsfsJ1J5n1Gbs6ujJ2TSiMG+a3fVr93T+6c+ysxMsWcz3gWZcbs4duRQ3Iw\n" +"NtM2KOCRhQKBgQDCTgAS5WSB222YBlf2pv8n3fG9r8QkxZEM1r+nfp1ZwaIb5zVU\n" +"YQw+7GvGlgQFiXL21UrCx9MRyFmHp/4KyW3WUxj34aHw+2LWxyaPWDKEVadMfw00\n" +"U0g2YrYjjOHpjNP2Rs+PepxFvbAtRSBn03QaamsSO1y1F2W8TE+xSCf96QKBgFlQ\n" +"a3E5pGSdzcn4iMDsLazuELU8E3XRdejNsHL3FaK/cml3Q4jdSOG6VBT5nvyWXHGa\n" +"6abALtSxSdUKTKKvQVxR1i+lstC7RdqvU/YMrvDFy+s5sUFxCacpXXutpljdyhqf\n" +"rAzwCGngQXlG8Og5sej74W7sITRhnEojMcb40PtNAoGAGawnWAi0AkIOVtRMBema\n" +"QZmW3tdVj798XHCI/8cl0CvsgctDdFmku759j4AUIlrAcn/R+umQwSnPAwwNXGV3\n" +"spDlSVoSDk7lYS4lVCWYUH+BCxqF+Ytb3IlJlv/FtKxCP4eiD1aCIbm9D/WCwBtf\n" +"36AgOGpW2UA1O65QO4j7HeU=\n" +"-----END PRIVATE KEY-----\n"; + +static struct tls_context_head *context_get_head(int fd); + +#ifdef _WIN32 +static ssize_t tls_pread(int fd, void *buf, size_t count, off_t offset) +{ + __int64 original; + int ret; + + original = _lseeki64(fd, 0, SEEK_CUR); + if (original < 0) { + return -1; + } + + if (_lseeki64(fd, offset, SEEK_SET) < 0) { + return -1; + } + + ret = _read(fd, buf, (unsigned int) count); + _lseeki64(fd, original, SEEK_SET); + + return ret; +} +#else +static ssize_t tls_pread(int fd, void *buf, size_t count, off_t offset) +{ + return pread(fd, buf, count, offset); +} +#endif + +static int tls_load_builtin_credentials(struct tls_server_context *ctx) +{ + BIO *cert_bio; + BIO *key_bio; + X509 *cert; + EVP_PKEY *key; + + cert_bio = BIO_new_mem_buf((void *) tls_builtin_cert, -1); + if (cert_bio == NULL) { + return -1; + } + + cert = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL); + BIO_free(cert_bio); + if (cert == NULL) { + return -1; + } + + key_bio = BIO_new_mem_buf((void *) tls_builtin_key, -1); + if (key_bio == NULL) { + X509_free(cert); + return -1; + } + + key = PEM_read_bio_PrivateKey(key_bio, NULL, NULL, NULL); + BIO_free(key_bio); + if (key == NULL) { + X509_free(cert); + return -1; + } + + if (SSL_CTX_use_certificate(ctx->ctx, cert) != 1) { + EVP_PKEY_free(key); + X509_free(cert); + return -1; + } + + if (SSL_CTX_use_PrivateKey(ctx->ctx, key) != 1) { + EVP_PKEY_free(key); + X509_free(cert); + return -1; + } + + EVP_PKEY_free(key); + X509_free(cert); + + return 0; +} + +static struct tls_thread_context *local_thread_context(void) +{ + return pthread_getspecific(local_context); +} + +static int tls_handle_return(SSL *ssl, int ret) +{ + int error; + struct tls_context_head *head; + + head = context_get_head(SSL_get_fd(ssl)); + if (head != NULL) { + head->want_event = MK_EVENT_EMPTY; + } + + if (ret > 0) { + return ret; + } + + error = SSL_get_error(ssl, ret); + switch (error) { + case SSL_ERROR_WANT_READ: + if (head != NULL) { + head->want_event = MK_EVENT_READ; + } + errno = EAGAIN; + return -1; + case SSL_ERROR_WANT_WRITE: + if (head != NULL) { + head->want_event = MK_EVENT_WRITE; + } + errno = EAGAIN; + return -1; + case SSL_ERROR_ZERO_RETURN: + return 0; + default: + errno = 0; + return -1; + } +} + +static int config_parse(const char *confdir, + const struct mk_server *server, + struct tls_config *conf) +{ + long unsigned int len; + char *conf_path = NULL; + char *cert_file = NULL; + char *cert_chain_file = NULL; + char *key_file = NULL; + char *dh_param_file = NULL; + int8_t check_client_cert = MK_FALSE; + struct mk_rconf_section *section; + struct mk_rconf *conf_head; + + mk_string_build(&conf_path, &len, "%s/tls.conf", confdir); + conf_head = mk_rconf_open(conf_path); + mk_mem_free(conf_path); + + if (conf_head == NULL) { + goto fallback; + } + + section = mk_rconf_section_get(conf_head, "TLS"); + if (!section) { + goto fallback; + } + + cert_file = mk_rconf_section_get_key(section, "CertificateFile", MK_RCONF_STR); + cert_chain_file = mk_rconf_section_get_key(section, "CertificateChainFile", MK_RCONF_STR); + key_file = mk_rconf_section_get_key(section, "RSAKeyFile", MK_RCONF_STR); + dh_param_file = mk_rconf_section_get_key(section, "DHParameterFile", MK_RCONF_STR); + check_client_cert = (size_t) mk_rconf_section_get_key(section, "CheckClientCert", + MK_RCONF_BOOL); + +fallback: + if (server->tls_cert_file != NULL) { + if (cert_file != NULL) { + mk_mem_free(cert_file); + } + cert_file = mk_string_dup(server->tls_cert_file); + } + + if (server->tls_cert_chain_file != NULL) { + if (cert_chain_file != NULL) { + mk_mem_free(cert_chain_file); + } + cert_chain_file = mk_string_dup(server->tls_cert_chain_file); + } + + if (server->tls_key_file != NULL) { + if (key_file != NULL) { + mk_mem_free(key_file); + } + key_file = mk_string_dup(server->tls_key_file); + } + + if (server->tls_dh_param_file != NULL) { + if (dh_param_file != NULL) { + mk_mem_free(dh_param_file); + } + dh_param_file = mk_string_dup(server->tls_dh_param_file); + } + + if (!cert_file) { + mk_string_build(&conf->cert_file, &len, "%s/srv_cert.pem", confdir); + } + else if (*cert_file == '/') { + conf->cert_file = cert_file; + } + else { + mk_string_build(&conf->cert_file, &len, "%s/%s", confdir, cert_file); + mk_mem_free(cert_file); + } + + if (cert_chain_file == NULL) { + conf->cert_chain_file = NULL; + } + else if (*cert_chain_file == '/') { + conf->cert_chain_file = cert_chain_file; + } + else { + mk_string_build(&conf->cert_chain_file, &len, "%s/%s", confdir, cert_chain_file); + mk_mem_free(cert_chain_file); + } + + if (!key_file) { + mk_string_build(&conf->key_file, &len, "%s/rsa.pem", confdir); + } + else if (*key_file == '/') { + conf->key_file = key_file; + } + else { + mk_string_build(&conf->key_file, &len, "%s/%s", confdir, key_file); + mk_mem_free(key_file); + } + + if (!dh_param_file) { + mk_string_build(&conf->dh_param_file, &len, "%s/dhparam.pem", confdir); + } + else if (*dh_param_file == '/') { + conf->dh_param_file = dh_param_file; + } + else { + mk_string_build(&conf->dh_param_file, &len, "%s/%s", confdir, dh_param_file); + mk_mem_free(dh_param_file); + } + + conf->check_client_cert = check_client_cert; + + if (conf_head) { + mk_rconf_free(conf_head); + } + + return 0; +} + +static void config_free(struct tls_config *conf) +{ + if (conf->cert_file) mk_mem_free(conf->cert_file); + if (conf->cert_chain_file) mk_mem_free(conf->cert_chain_file); + if (conf->key_file) mk_mem_free(conf->key_file); + if (conf->dh_param_file) mk_mem_free(conf->dh_param_file); +} + +static void tls_init_library(void) +{ +#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 + OPENSSL_add_all_algorithms_noconf(); + SSL_load_error_strings(); + SSL_library_init(); +#else + OPENSSL_init_ssl(0, NULL); + SSL_load_error_strings(); +#endif +} + +static SSL_CTX *tls_create_server_context(void) +{ +#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 + return SSL_CTX_new(SSLv23_server_method()); +#else + return SSL_CTX_new(TLS_server_method()); +#endif +} + +static int tls_configure_dh(struct tls_server_context *ctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIO *bio; + DH *dh; + + bio = BIO_new_file(ctx->config.dh_param_file, "r"); + if (bio == NULL) { + return 0; + } + + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + BIO_free(bio); + if (dh == NULL) { + return 0; + } + + if (SSL_CTX_set_tmp_dh(ctx->ctx, dh) != 1) { + DH_free(dh); + return -1; + } + + DH_free(dh); + return 0; +#else +#ifdef SSL_CTX_set_dh_auto + if (SSL_CTX_set_dh_auto(ctx->ctx, 1) != 1) { + return -1; + } +#endif + return 0; +#endif +} + +static int tls_load_credentials(struct tls_server_context *ctx) +{ + if (SSL_CTX_use_certificate_chain_file(ctx->ctx, ctx->config.cert_file) != 1) { + mk_warn("[tls] failed to load certificate chain from %s", + ctx->config.cert_file); + mk_warn("[tls] using built-in development certificate, please configure CertificateFile/RSAKeyFile for production"); + if (tls_load_builtin_credentials(ctx) != 0) { + return -1; + } + return 0; + } + + if (SSL_CTX_use_PrivateKey_file(ctx->ctx, ctx->config.key_file, + SSL_FILETYPE_PEM) != 1) { + mk_warn("[tls] failed to load private key from %s", + ctx->config.key_file); + return -1; + } + + if (SSL_CTX_check_private_key(ctx->ctx) != 1) { + mk_warn("[tls] certificate/private key mismatch"); + return -1; + } + + if (ctx->config.cert_chain_file != NULL) { + if (SSL_CTX_load_verify_locations(ctx->ctx, + ctx->config.cert_chain_file, + NULL) != 1) { + mk_warn("[tls] failed to load CA chain from %s", + ctx->config.cert_chain_file); + return -1; + } + } + + if (tls_configure_dh(ctx) != 0) { + mk_warn("[tls] failed to configure DH parameters"); + return -1; + } + + return 0; +} + +static struct tls_context_head *context_get_head(int fd) +{ + struct tls_thread_context *thctx; + struct tls_context_head *cur; + + thctx = local_thread_context(); + if (thctx == NULL) { + return NULL; + } + + cur = thctx->contexts; + while (cur) { + if (cur->fd == fd) { + return cur; + } + cur = cur->_next; + } + + return NULL; +} + +static SSL *context_get(int fd) +{ + struct tls_context_head *head; + + head = context_get_head(fd); + if (head == NULL) { + return NULL; + } + + return head->ssl; +} + +static SSL *context_new(int fd) +{ + SSL *ssl; + struct tls_context_head *ctx_head; + struct tls_thread_context *thctx; + + thctx = local_thread_context(); + if (thctx == NULL) { + return NULL; + } + + ctx_head = context_get_head(-1); + if (ctx_head == NULL) { + ctx_head = mk_mem_alloc_z(sizeof(struct tls_context_head)); + if (ctx_head == NULL) { + return NULL; + } + ctx_head->fd = -1; + ctx_head->want_event = MK_EVENT_READ; + ctx_head->_next = thctx->contexts; + thctx->contexts = ctx_head; + } + + ssl = SSL_new(server_context->ctx); + if (ssl == NULL) { + return NULL; + } + + SSL_set_fd(ssl, fd); + SSL_set_accept_state(ssl); + ctx_head->ssl = ssl; + ctx_head->fd = fd; + + return ssl; +} + +static void context_unset(int fd) +{ + struct tls_context_head *ctx_head; + + ctx_head = context_get_head(fd); + if (ctx_head == NULL) { + return; + } + + if (ctx_head->ssl != NULL) { + SSL_shutdown(ctx_head->ssl); + SSL_free(ctx_head->ssl); + ctx_head->ssl = NULL; + } + ctx_head->fd = -1; +} + +static void contexts_free(struct tls_context_head *ctx) +{ + struct tls_context_head *cur; + struct tls_context_head *tmp; + + cur = ctx; + while (cur) { + tmp = cur->_next; + if (cur->ssl != NULL) { + SSL_free(cur->ssl); + } + mk_mem_free(cur); + cur = tmp; + } +} + +static SSL *context_get_or_create(int fd) +{ + SSL *ssl; + + ssl = context_get(fd); + if (ssl != NULL) { + return ssl; + } + + return context_new(fd); +} + +static int mk_tls_read(struct mk_plugin *plugin, int fd, void *buf, int count) +{ + SSL *ssl; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + return tls_handle_return(ssl, SSL_read(ssl, buf, count)); +} + +static int mk_tls_write(struct mk_plugin *plugin, int fd, + const void *buf, size_t count) +{ + SSL *ssl; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + return tls_handle_return(ssl, SSL_write(ssl, buf, count)); +} + +static int mk_tls_writev(struct mk_plugin *plugin, int fd, struct mk_iov *mk_io) +{ + int i; + int ret; + SSL *ssl; + size_t used; + size_t len; + unsigned char *buf; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + len = mk_io->total_len; + buf = mk_mem_alloc(len); + if (buf == NULL) { + return -1; + } + + used = 0; + for (i = 0; i < mk_io->iov_idx; i++) { + memcpy(buf + used, mk_io->io[i].iov_base, mk_io->io[i].iov_len); + used += mk_io->io[i].iov_len; + } + + ret = SSL_write(ssl, buf, len); + mk_mem_free(buf); + + return tls_handle_return(ssl, ret); +} + +static int mk_tls_send_file(struct mk_plugin *plugin, int fd, int file_fd, + off_t *file_offset, size_t file_count) +{ + int ret; + SSL *ssl; + ssize_t used; + ssize_t remain; + ssize_t sent; + unsigned char *buf; + + (void) plugin; + + ssl = context_get_or_create(fd); + if (ssl == NULL) { + return -1; + } + + buf = mk_mem_alloc(SENDFILE_BUF_SIZE); + if (buf == NULL) { + return -1; + } + + sent = 0; + remain = file_count; + + do { + used = tls_pread(file_fd, buf, SENDFILE_BUF_SIZE, *file_offset); + if (used == 0) { + ret = 0; + } + else if (used < 0) { + ret = -1; + } + else if (remain > 0) { + ret = SSL_write(ssl, buf, used < remain ? used : remain); + } + else { + ret = SSL_write(ssl, buf, used); + } + + if (ret > 0) { + if (remain > 0) { + remain -= ret; + } + sent += ret; + *file_offset += ret; + } + } while (ret > 0); + + mk_mem_free(buf); + + if (sent > 0) { + return sent; + } + + return tls_handle_return(ssl, ret); +} + +static int mk_tls_close(struct mk_plugin *plugin, int fd) +{ + (void) plugin; + + context_unset(fd); + mk_event_closesocket(fd); + return 0; +} + +static int tls_event_interest(struct mk_plugin *plugin, int fd, int fallback) +{ + struct tls_context_head *ctx_head; + + (void) plugin; + + ctx_head = context_get_head(fd); + if (ctx_head == NULL) { + return fallback; + } + + if (ctx_head->want_event == MK_EVENT_EMPTY) { + return fallback; + } + + return ctx_head->want_event; +} + +int mk_tls_enabled(void) +{ + return MK_TRUE; +} + +int mk_tls_init(struct mk_server *server) +{ + int used; + struct mk_list *head; + struct mk_config_listener *listen; + + used = MK_FALSE; + mk_list_foreach(head, &server->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + if (listen->flags & MK_CAP_SOCK_TLS) { + used = MK_TRUE; + break; + } + } + + if (!used) { + return 0; + } + + server_context = mk_mem_alloc_z(sizeof(struct tls_server_context)); + if (server_context == NULL) { + return -1; + } + + server_context->server = server; + config_parse(server->path_conf_root, server, &server_context->config); + pthread_mutex_init(&server_context->mutex, NULL); + mk_list_init(&server_context->threads); + if (local_context_created == MK_FALSE) { + pthread_key_create(&local_context, NULL); + local_context_created = MK_TRUE; + } + + tls_init_library(); + + server_context->ctx = tls_create_server_context(); + if (server_context->ctx == NULL) { + return -1; + } + +#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 +#ifdef SSL_CTX_set_ecdh_auto + SSL_CTX_set_ecdh_auto(server_context->ctx, 1); +#endif +#endif + + SSL_CTX_set_mode(server_context->ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_session_cache_mode(server_context->ctx, SSL_SESS_CACHE_SERVER); + + if (server_context->config.check_client_cert == MK_TRUE) { + SSL_CTX_set_verify(server_context->ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + } + else { + SSL_CTX_set_verify(server_context->ctx, SSL_VERIFY_NONE, NULL); + } + + return tls_load_credentials(server_context); +} + +void mk_tls_thread_init(struct mk_server *server) +{ + struct tls_thread_context *thctx; + + (void) server; + + if (server_context == NULL) { + return; + } + + thctx = mk_mem_alloc_z(sizeof(struct tls_thread_context)); + if (thctx == NULL) { + exit(EXIT_FAILURE); + } + + pthread_mutex_lock(&server_context->mutex); + mk_list_add(&thctx->_head, &server_context->threads); + pthread_mutex_unlock(&server_context->mutex); + + pthread_setspecific(local_context, thctx); +} + +void mk_tls_exit(struct mk_server *server) +{ + struct mk_list *cur; + struct mk_list *tmp; + struct tls_thread_context *thctx; + (void) server; + + if (server_context == NULL) { + return; + } + + mk_list_foreach_safe(cur, tmp, &server_context->threads) { + thctx = mk_list_entry(cur, struct tls_thread_context, _head); + contexts_free(thctx->contexts); + mk_mem_free(thctx); + } + + if (server_context->ctx != NULL) { + SSL_CTX_free(server_context->ctx); + } + + config_free(&server_context->config); + pthread_mutex_destroy(&server_context->mutex); + mk_mem_free(server_context); + server_context = NULL; + + if (local_context_created == MK_TRUE) { + pthread_key_delete(local_context); + local_context_created = MK_FALSE; + } +} + +static struct mk_plugin_network mk_tls_io = { + .read = mk_tls_read, + .write = mk_tls_write, + .writev = mk_tls_writev, + .close = mk_tls_close, + .send_file = mk_tls_send_file, + .event_interest = tls_event_interest, + .buffer_size = 16384 +}; + +struct mk_plugin_network *mk_tls_transport(void) +{ + return &mk_tls_io; +} diff --git a/lib/monkey/tls/tls_stub.c b/lib/monkey/tls/tls_stub.c new file mode 100644 index 00000000000..f1d510e8c79 --- /dev/null +++ b/lib/monkey/tls/tls_stub.c @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include + +int mk_tls_enabled(void) +{ + return 0; +} + +int mk_tls_init(struct mk_server *server) +{ + (void) server; + return 0; +} + +void mk_tls_thread_init(struct mk_server *server) +{ + (void) server; +} + +void mk_tls_exit(struct mk_server *server) +{ + (void) server; +} + +struct mk_plugin_network *mk_tls_transport(void) +{ + return NULL; +} diff --git a/src/flb_pipe.c b/src/flb_pipe.c index 37dbde5151d..3534dda1484 100644 --- a/src/flb_pipe.c +++ b/src/flb_pipe.c @@ -43,31 +43,38 @@ #include #ifdef _WIN32 +#include /* - * Building on Windows means that Monkey library (lib/monkey) and it - * core runtime have been build with 'libevent' backend support, that - * library provide an abstraction to create a socketpairs. + * Building on Windows means that Monkey library (lib/monkey) provides + * a socketpair abstraction backed by AF_UNIX stream sockets when the + * platform supports them. * * Creating a pipe on Fluent Bit @Windows, means create a socket pair. */ int flb_pipe_create(flb_pipefd_t pipefd[2]) { + SOCKET pair[2]; struct linger sl = {1, 0}; /* l_onoff = 1, l_linger = 0 */ - if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) == -1) { + if (mk_win32_socketpair(pair) == -1) { perror("socketpair"); return -1; } - if (setsockopt(pipefd[0], SOL_SOCKET, SO_LINGER, (const char*)&sl, sizeof(sl)) == -1) { + pipefd[0] = (flb_pipefd_t) pair[0]; + pipefd[1] = (flb_pipefd_t) pair[1]; + + if (setsockopt((SOCKET) pipefd[0], + SOL_SOCKET, SO_LINGER, (const char *) &sl, sizeof(sl)) == -1) { perror("setsockopt linger failed on pipefd[0]"); flb_pipe_destroy(pipefd); return -1; } - if (setsockopt(pipefd[1], SOL_SOCKET, SO_LINGER, (const char*)&sl, sizeof(sl)) == -1) { + if (setsockopt((SOCKET) pipefd[1], + SOL_SOCKET, SO_LINGER, (const char *) &sl, sizeof(sl)) == -1) { perror("setsockopt linger failed on pipefd[1]"); flb_pipe_destroy(pipefd); return -1; @@ -78,18 +85,20 @@ int flb_pipe_create(flb_pipefd_t pipefd[2]) void flb_pipe_destroy(flb_pipefd_t pipefd[2]) { - evutil_closesocket(pipefd[0]); - evutil_closesocket(pipefd[1]); + closesocket((SOCKET) pipefd[0]); + closesocket((SOCKET) pipefd[1]); } int flb_pipe_close(flb_pipefd_t fd) { - return evutil_closesocket(fd); + return closesocket((SOCKET) fd); } int flb_pipe_set_nonblocking(flb_pipefd_t fd) { - return evutil_make_socket_nonblocking(fd); + u_long mode = 1; + + return ioctlsocket((SOCKET) fd, FIONBIO, &mode); } #else /* All other flavors of Unix/BSD are OK */ @@ -133,7 +142,7 @@ int flb_pipe_set_nonblocking(flb_pipefd_t fd) #endif /* Blocking read until receive 'count' bytes */ -ssize_t flb_pipe_read_all(int fd, void *buf, size_t count) +ssize_t flb_pipe_read_all(flb_pipefd_t fd, void *buf, size_t count) { ssize_t bytes; size_t total = 0; @@ -165,7 +174,7 @@ ssize_t flb_pipe_read_all(int fd, void *buf, size_t count) } /* Blocking write until send 'count bytes */ -ssize_t flb_pipe_write_all(int fd, const void *buf, size_t count) +ssize_t flb_pipe_write_all(flb_pipefd_t fd, const void *buf, size_t count) { ssize_t bytes; size_t total = 0; @@ -194,4 +203,4 @@ ssize_t flb_pipe_write_all(int fd, const void *buf, size_t count) } while (total < count); return total; -} \ No newline at end of file +}