diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 6e5353bf..79266d3e 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -23,11 +23,13 @@ services: image: balabit/syslog-ng:latest volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf + - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro - ../Bdd/output:/var/log/syslog-ng - syslog-ng-ctl:/var/lib/syslog-ng ports: - "5514:5514/udp" - "5514:5514/tcp" + - "6514:6514/tcp" working_dir: /workspaces/SolidSyslog entrypoint: ["bash", "-c"] command: diff --git a/Bdd/features/environment.py b/Bdd/features/environment.py index 96400e31..8a0e5a04 100644 --- a/Bdd/features/environment.py +++ b/Bdd/features/environment.py @@ -14,6 +14,7 @@ STORE_PATH_PREFIX = "/tmp/STORE" RECEIVED_UDP_LOG = "Bdd/output/received_udp.log" RECEIVED_TCP_LOG = "Bdd/output/received_tcp.log" +RECEIVED_TLS_LOG = "Bdd/output/received_tls.log" def wait_for_tcp_port_open(host="syslog-ng", port=5514, timeout=5): diff --git a/Bdd/features/steps/syslog_steps.py b/Bdd/features/steps/syslog_steps.py index fb58a932..41745f23 100644 --- a/Bdd/features/steps/syslog_steps.py +++ b/Bdd/features/steps/syslog_steps.py @@ -12,6 +12,7 @@ from behave import given, when, then from environment import ( RECEIVED_TCP_LOG, + RECEIVED_TLS_LOG, RECEIVED_UDP_LOG, STORE_FILE_PATH, STORE_PATH_PREFIX, @@ -20,6 +21,7 @@ PER_TRANSPORT_LOG = { "udp": RECEIVED_UDP_LOG, "tcp": RECEIVED_TCP_LOG, + "tls": RECEIVED_TLS_LOG, } # POSIX-only paths used by the @buffered/threaded scenarios. The cross-platform diff --git a/Bdd/features/tls_transport.feature b/Bdd/features/tls_transport.feature new file mode 100644 index 00000000..7ebf03b9 --- /dev/null +++ b/Bdd/features/tls_transport.feature @@ -0,0 +1,10 @@ +@tls @buffered +Feature: TLS message delivery + The threaded example sends messages via TLS transport (RFC 5425) — + RFC 6587 octet-counting framing over TLS — to syslog-ng. + + Scenario: Message delivered over TLS + Given syslog-ng is running + When the threaded example sends a syslog message with transport tls + Then syslog-ng receives 1 message over tls + And syslog-ng receives a message with priority "134" diff --git a/Bdd/syslog-ng/syslog-ng-full.conf b/Bdd/syslog-ng/syslog-ng-full.conf index d862beed..0dd7b5f0 100644 --- a/Bdd/syslog-ng/syslog-ng-full.conf +++ b/Bdd/syslog-ng/syslog-ng-full.conf @@ -16,6 +16,20 @@ source s_tcp { ); }; +source s_tls { + syslog( + transport("tls") + port(6514) + keep-hostname(yes) + tls( + key-file("/etc/syslog-ng/tls/server.key") + cert-file("/etc/syslog-ng/tls/server.pem") + ca-file("/etc/syslog-ng/tls/ca.pem") + peer-verify(optional-untrusted) + ) + ); +}; + destination d_file { file( "/var/log/syslog-ng/received.log" @@ -46,9 +60,20 @@ destination d_file_tcp { ); }; +destination d_file_tls { + file( + "/var/log/syslog-ng/received_tls.log" + template("PRIORITY=${PRI} TIMESTAMP=${ISODATE} HOSTNAME=${HOST} APP_NAME=${PROGRAM} PROCID=${PID} MSGID=${MSGID} STRUCTURED_DATA=${SDATA} MSG=${MSG}\n") + flush_lines(1) + create-dirs(yes) + perm(0644) + ); +}; + log { source(s_udp); source(s_tcp); + source(s_tls); destination(d_file); }; @@ -61,3 +86,8 @@ log { source(s_tcp); destination(d_file_tcp); }; + +log { + source(s_tls); + destination(d_file_tls); +}; diff --git a/Bdd/syslog-ng/syslog-ng.conf b/Bdd/syslog-ng/syslog-ng.conf index d862beed..0dd7b5f0 100644 --- a/Bdd/syslog-ng/syslog-ng.conf +++ b/Bdd/syslog-ng/syslog-ng.conf @@ -16,6 +16,20 @@ source s_tcp { ); }; +source s_tls { + syslog( + transport("tls") + port(6514) + keep-hostname(yes) + tls( + key-file("/etc/syslog-ng/tls/server.key") + cert-file("/etc/syslog-ng/tls/server.pem") + ca-file("/etc/syslog-ng/tls/ca.pem") + peer-verify(optional-untrusted) + ) + ); +}; + destination d_file { file( "/var/log/syslog-ng/received.log" @@ -46,9 +60,20 @@ destination d_file_tcp { ); }; +destination d_file_tls { + file( + "/var/log/syslog-ng/received_tls.log" + template("PRIORITY=${PRI} TIMESTAMP=${ISODATE} HOSTNAME=${HOST} APP_NAME=${PROGRAM} PROCID=${PID} MSGID=${MSGID} STRUCTURED_DATA=${SDATA} MSG=${MSG}\n") + flush_lines(1) + create-dirs(yes) + perm(0644) + ); +}; + log { source(s_udp); source(s_tcp); + source(s_tls); destination(d_file); }; @@ -61,3 +86,8 @@ log { source(s_tcp); destination(d_file_tcp); }; + +log { + source(s_tls); + destination(d_file_tls); +}; diff --git a/Bdd/syslog-ng/tls/README.md b/Bdd/syslog-ng/tls/README.md new file mode 100644 index 00000000..c1596f0d --- /dev/null +++ b/Bdd/syslog-ng/tls/README.md @@ -0,0 +1,21 @@ +# BDD TLS test material + +**TEST ONLY.** These keys and certificates exist solely to drive the BDD TLS +scenarios (`Bdd/features/tls_transport.feature` et al.) against syslog-ng +running in a BDD compose container. They must never be used for any purpose +that touches real data. + +| File | Role | +|---|---| +| `ca.key` | Test CA private key. Signs the server cert. | +| `ca.pem` | Test CA certificate. Bundled into the example binary via `--tls-ca`. | +| `server.key` | syslog-ng server private key. | +| `server.pem` | syslog-ng server certificate, signed by the test CA. SANs: `syslog-ng`, `localhost`, `127.0.0.1`. | + +Validity is 10 years from generation, so these effectively never expire for +test purposes. + +## Regenerating + +Run `./generate.sh` from this directory (or the repo root). Commit the +regenerated files — the BDD suite pins them. diff --git a/Bdd/syslog-ng/tls/ca.key b/Bdd/syslog-ng/tls/ca.key new file mode 100644 index 00000000..0d773513 --- /dev/null +++ b/Bdd/syslog-ng/tls/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAx7LsZX3e+F+FyFqO2epTfguBgXfS1uPwiQOesW5WSBDLuhsk +FC2THwM/c7vxRQvIZ9hnOpJgi3e8of4lX+rSNg0viYxkrsk/S3SyKp/Qhzu7Eee8 +Cc+NfD+no8uF43gw3XfzX17NWVMTGBJOj3eC4xl/c9u0fApoljYv92Ifsc7TCaud +OHkUBlEBrR9b6bpy+GPDCe26rtG76NwKf+6B0t8IawNjbLDCovFCC9k8ymlTIktt +v9P32J1FgZ18M0FBowA1CrLyCoGNa9loyfUz4Qhje3CG/fUeMLoAOvIWsixBVH3X +MHlGyyJSpK1soBMgs9o+YBnwZatNvl+PQcm3XwIDAQABAoIBAG5Bw3Nlw62uT9cG +6Mg4MFDvWVl6JtaukyeS8BNWsByp6VGfmpPpdckxqd7kbZmJn+R1/si/+L4IJgXq +pAgJRnbBYPW4pf467s9iZtxR0HhZ6jdAkvl3ts5a9tvgitPEkHY2vBBz7zzaOTiZ +IvbjLUjl0xDMxSGXPZdx45nae1VXuLIpvFVGcSs9830TqjK/hGu/rIoO2JC/MfUe +3roal/xYOatCjGtEM86zO3xdxmMnCP8Ei8xsRBY+ygDAzi+9D6L7l3rjp4gPkixC +EL6zueOJpYulYWoIQTV9pKfB00MlZdUs5oMkuZcmP5/Mxx5nzILpHBV2Yj9xY26n +n7lFO4ECgYEA/VqXPAWzREnlHnKxe+gLRNPcRFASvrNFT65JhMRzeUAwvEBCBV15 +1a2ECeBwU2wvGP23usIkYzJD8C4PZ9dne0PTR9lnEFipZ7DyBj9ODBcuP3QRax3c +XPWgTHbU9A++65XBaLb31khKE/4Wzc20+3qjqnQPLekDnoePcQe/cJcCgYEAycjf +L8s7fjh+dhUxUY598cjPF2tiaJmiBlGovSLvca+aDIY/P0jimUixAqESS3LZEqZv +Dy9SwPMT4kM8BIHOWJ/1jgC2NxPEms69hb27EwWKKGf3YfMosWYfzL6w5T7On2tm +8KdFlImjRrOhuLjK2kQ12WE+Cn87nY4C5j5OgHkCgYEAuS8hQgcsnGqi5VmSL25R +5lIxO2GIAgoJTI+pPzlU6jioJsGVQFSt/CijTZDWqbN2zX6OcBz9+d/A1urj6dQB +2JCf+3GGKuWTla45iaV2B9JdxJWzMaeW7f+/1oQ5bdeghpRk1YR/rQoT038y7eVB +N1vSC4JfogAi3BpcF5NBNCsCgYAfSpIpxDdmnHYn86NhRVqMixpqRFHPbLuuhS1I +n0lIdPXLqWnXc4MISDiC8t12a8nz1XF1hl4r48YamViOTl7kaXHX4o5fPiaH5zWG +ruR6z6ocF/tJ6j5OPEsEIjImarX6DNq6yQXGQg3a4fJbRfTXIirDmgvX7uEczq/u +ao0b6QKBgHRDraREe/cLd05y2X30b7HeBK1SCweHYsOU3TdtHP6Nyn12kgy/Il9w +e50GEcRci16Km0wfclFtj8QsOqOYcr2P180INoXqeZgnHi2MmoeXTV7LJUEpYSOC +mPWTDihHP0q0x/RAzd8ynm51qK4Pghs8C5SWOY9Azl9dSKHDBOSe +-----END RSA PRIVATE KEY----- diff --git a/Bdd/syslog-ng/tls/ca.pem b/Bdd/syslog-ng/tls/ca.pem new file mode 100644 index 00000000..6daaf732 --- /dev/null +++ b/Bdd/syslog-ng/tls/ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUa9bOdOquPzUXvg4/X1St4bHQuRAwDQYJKoZIhvcNAQEL +BQAwIjEgMB4GA1UEAwwXU29saWRTeXNsb2cgQkREIFRlc3QgQ0EwHhcNMjYwNDIw +MTcwODQxWhcNMzYwNDE3MTcwODQxWjAiMSAwHgYDVQQDDBdTb2xpZFN5c2xvZyBC +REQgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMey7GV9 +3vhfhchajtnqU34LgYF30tbj8IkDnrFuVkgQy7obJBQtkx8DP3O78UULyGfYZzqS +YIt3vKH+JV/q0jYNL4mMZK7JP0t0siqf0Ic7uxHnvAnPjXw/p6PLheN4MN13819e +zVlTExgSTo93guMZf3PbtHwKaJY2L/diH7HO0wmrnTh5FAZRAa0fW+m6cvhjwwnt +uq7Ru+jcCn/ugdLfCGsDY2ywwqLxQgvZPMppUyJLbb/T99idRYGdfDNBQaMANQqy +8gqBjWvZaMn1M+EIY3twhv31HjC6ADryFrIsQVR91zB5RssiUqStbKATILPaPmAZ +8GWrTb5fj0HJt18CAwEAAaNTMFEwHQYDVR0OBBYEFKvZn0img6757QgOS6J9X79x +79NZMB8GA1UdIwQYMBaAFKvZn0img6757QgOS6J9X79x79NZMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKfFmKPcsrVxmJZNuWwXPM4M5okzcpNP +2PQt+lGC7x60FLlfySAKtnyyYSddHRPgNQELmcKYulpDJj5eYrhWUwdqukra1B2s +bv4bz8UUIYPrc4hvLdLKs5+iLmM9rzioKI9Dq5+r5dlxR5hK2CfOnzyjPb6/rzIy +nxSRsVrAkGUqfi1BH4poWAtwY00A3zxehcYZNtwgNKqVrj9rE0gLl0DSR3W6dABa +q0whWUJQsB9cRSLwxrxiLAU95NzdOmgdcrswYptSZHtSXYdS8mBf31cQNHvjAvn8 +yR1xGaLsJ0E6F+z70kx22KzTkvwavcx4mukm6PVJisqVDulz579+7C4= +-----END CERTIFICATE----- diff --git a/Bdd/syslog-ng/tls/generate.sh b/Bdd/syslog-ng/tls/generate.sh new file mode 100755 index 00000000..59d8b06a --- /dev/null +++ b/Bdd/syslog-ng/tls/generate.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Regenerates the test-only self-signed CA + syslog-ng server cert used by the +# TLS BDD scenarios. Keys in this directory are FOR TESTING ONLY — never use +# them for anything that touches real data. +# +# Run from the repo root: ./Bdd/syslog-ng/tls/generate.sh +# Or from this directory: ./generate.sh +# +# Validity is 10 years so the certs effectively never expire for test purposes. +# If you regenerate, commit the changes — the BDD suite pins these files. + +set -euo pipefail + +cd "$(dirname "$0")" + +DAYS=3650 +SUBJECT_CA="/CN=SolidSyslog BDD Test CA" +SUBJECT_SRV="/CN=syslog-ng" +SAN="subjectAltName = DNS:syslog-ng, DNS:localhost, IP:127.0.0.1" + +# 1. CA key + self-signed CA cert +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -sha256 -days "$DAYS" \ + -subj "$SUBJECT_CA" -out ca.pem + +# 2. Server key + CSR +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -subj "$SUBJECT_SRV" -out server.csr + +# 3. Sign server cert with the CA, carrying the SAN +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \ + -out server.pem -days "$DAYS" -sha256 \ + -extfile <(printf "%s\n" "$SAN") + +# Remove the CSR and CA serial — only the keys + certs need to be committed +rm -f server.csr ca.srl + +chmod 0644 ca.pem server.pem +chmod 0600 ca.key server.key + +echo "Regenerated test certs in $(pwd)" diff --git a/Bdd/syslog-ng/tls/server.key b/Bdd/syslog-ng/tls/server.key new file mode 100644 index 00000000..4024b6b4 --- /dev/null +++ b/Bdd/syslog-ng/tls/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAshK+rIthQABR1DRYVf9E4yV9nGC3S4VPd2jK64/O0dnqAf1m +MozRGecilyA9kWO/lOUlK7FniaLMcBzn4CuaCGjDhN6uCVxgfaGPe0NIB6weBbr6 +RcBaqHu2A3ZKqxdF5vNU//Ty8UFVLksHIEd9xY+hN534YcvGEpm4e9C7TKUzf++4 +an0Ow+7utfh/vXIlz1wir3nwHe9b02gUhnl+q2olM1/jLuH4Sjh3Gwga0Mgtxm2R +ChUIDxBpzTKKA2h0OS+Puz/EiAraHkE6hE1yOtTkMm17LD0t3cMr47m+cLzOZgsK +nfS7H2UeOJVDFAcxoF9K7IM24KC5rSAD+b0iqQIDAQABAoIBAQCkzBov/rGalHEm +TOcvdxVejbSowwz+1DnNzWp2BFmn9d7NxqAMyis9qJ1ndQvlgIWtmz8CJ6UlSEeg +ve+Nk2xyudmwHpC70wTtX1Y1AfgoOY5zSS4S3UhJQmwY2iGSIvCrIqiYVC19PjRB +qmfG5MKt0hWmLgjPINGHJ0IHPA4KiBFnCKtNHn5QakpaplxFblLtAJMv6Mzfsnnm +amzflHA+QvFy0mWYK5GAe6LYSYUm2TikqzJbq4hjeiCDQh1hzZuhdQpQL0mLWYpS +ewj0mIClfWiLdR9Sf3O9RHBP5NczOkJg+QCHwFfOamRWulM9D8GhzJGAxdMRCJI8 +5YpBXx0dAoGBANXiP4KUi2YstRh6gZ5tIDjP2HMqMLDp8UeeOH+NwBA2DQP5sXiX +t9QpO17dhgugz1R2go53BvajJru7yUZPen3rxRrVy2t82DZ0pGrwuKfmURzMm7bp +9HwwvNMDnpCYGGnSK+Lb1FZXTSfEDNb0i+tEVCv/HANurE92p6Awn6L3AoGBANUj +TuAoPCR2A3kXMQEDa/RR51buVLqDgyBujffBvfG/+JpAVplyvOGNiC8DHoRF1JOt +Pfva+Y3rl4q3cvprom+ug55Ch9QKm0WXiHXO/xiebi9Nr5UuUfhz3oZopGlul0Ja +lIlhL7RjZCityC/rSDU0HgzV1AQC9+8XnMgPql9fAoGAWlGnViIaV7RLaSHaeKdG +cxc6JS/MzfiMfhzPaPXYBEB+8I3RpsYSx9H9k0rinyhyRQ2ihjy3QRGWerKLBVjs +th4I5xMfSq22tBA/mjU7FRPKbunsW6qiJouCQW2G2TKRJKavB6ajHS7SkxdUALXW +HCyHiui40K48r3XAxYGi9/MCgYBMB4ZRa8jYcMiYX295nxDZXIYbenT6+4LBBO7b +nF73Z7V0wNKjdkxuYSBdNM2j/YzxCPF9cAVpXMr4DqyTK+YL1MLap3zwZMAyuPVS +oP5ad9lyQlIJ8zYGXL7aT2Wmvm5ymwE1aFeuD25hHGy2u0VMxUSa64Pv0mdDALdK +nlv2HQKBgQC4u3+cyw+cimCc1gMTXB7Lc9ZfxXZvjhnnrHIDjvTSPG5Gipnwv5Yy +q94XuvevNMNWjTUAFubcA+aYa8XMWjcfHqVaatS1gQQkZV11Y2R1JJNjW5dcVtR4 +tfI15/6fnr7LhbsFeRqE6dRKbThaj9jbH4rfqBT/t25os4TfiQtU8A== +-----END RSA PRIVATE KEY----- diff --git a/Bdd/syslog-ng/tls/server.pem b/Bdd/syslog-ng/tls/server.pem new file mode 100644 index 00000000..3ccb1797 --- /dev/null +++ b/Bdd/syslog-ng/tls/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAdWgAwIBAgIUCMEuhoivc1mANtoLz0SkTUUTbKUwDQYJKoZIhvcNAQEL +BQAwIjEgMB4GA1UEAwwXU29saWRTeXNsb2cgQkREIFRlc3QgQ0EwHhcNMjYwNDIw +MTcwODQxWhcNMzYwNDE3MTcwODQxWjAUMRIwEAYDVQQDDAlzeXNsb2ctbmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyEr6si2FAAFHUNFhV/0TjJX2c +YLdLhU93aMrrj87R2eoB/WYyjNEZ5yKXID2RY7+U5SUrsWeJosxwHOfgK5oIaMOE +3q4JXGB9oY97Q0gHrB4FuvpFwFqoe7YDdkqrF0Xm81T/9PLxQVUuSwcgR33Fj6E3 +nfhhy8YSmbh70LtMpTN/77hqfQ7D7u61+H+9ciXPXCKvefAd71vTaBSGeX6raiUz +X+Mu4fhKOHcbCBrQyC3GbZEKFQgPEGnNMooDaHQ5L4+7P8SICtoeQTqETXI61OQy +bXssPS3dwyvjub5wvM5mCwqd9LsfZR44lUMUBzGgX0rsgzbgoLmtIAP5vSKpAgMB +AAGjKTAnMCUGA1UdEQQeMByCCXN5c2xvZy1uZ4IJbG9jYWxob3N0hwR/AAABMA0G +CSqGSIb3DQEBCwUAA4IBAQBKz2+40sPa5jKdzFW0T/k1zHvGDAffv7zeNonw1g6B +D/ORXfE7EQzYGXkZPVo7DD5eqVk+4t0ToCAf8kC6/5StcaW3D5ERoeq5hYOkvGdF +FpHyNrTwwkE3kqdfxTdOH5g2Gzb+XosG9UebpxrGxVbcrqyjBn2LJhM8Kj2zBu6D +ACdtsU5knHvgXy7cnwRrrjFxr8yognWHK6pRxGwDXmMFa2qOrBpIX74OXoei7eYQ +k4hHdFvk/GKqR6lonXfCEaAAAtBqThLbq+bZHVaLWZ+HA1qBU/+KdGYzDl8P022o +VFuAZgp6ffabfyWgFu3a3d+jphjoFKGIfp/0wR7DNq1s +-----END CERTIFICATE----- diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ab1b8e9..6edbc318 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,12 +82,26 @@ if(HAVE_STDATOMIC_H OR HAVE_WINDOWS_INTERLOCKED) set(HAVE_ATOMIC_COUNTER TRUE) endif() +# OpenSSL-based TLS is an optional platform feature. Default ON when OpenSSL is +# discoverable so CI and most workstations get TLS automatically; pass +# -DSOLIDSYSLOG_OPENSSL=OFF to build a UDP/TCP-only library on systems without +# OpenSSL. +find_package(OpenSSL QUIET) +option(SOLIDSYSLOG_OPENSSL "Include OpenSSL-based TLS support" ${OpenSSL_FOUND}) +if(SOLIDSYSLOG_OPENSSL AND NOT OpenSSL_FOUND) + message(FATAL_ERROR "SOLIDSYSLOG_OPENSSL=ON but OpenSSL was not found; install OpenSSL or pass -DSOLIDSYSLOG_OPENSSL=OFF") +endif() + add_subdirectory(Source) if(SOLIDSYSLOG_POSIX) add_subdirectory(Platform/Posix) endif() +if(SOLIDSYSLOG_OPENSSL) + add_subdirectory(Platform/OpenSsl) +endif() + if(HAVE_WINDOWS_INTERLOCKED OR HAVE_WINDOWS_PLATFORM OR SOLIDSYSLOG_WINSOCK) add_subdirectory(Platform/Windows) endif() diff --git a/Example/CMakeLists.txt b/Example/CMakeLists.txt index a01a3a50..a4256f3c 100644 --- a/Example/CMakeLists.txt +++ b/Example/CMakeLists.txt @@ -9,6 +9,10 @@ if(SOLIDSYSLOG_POSIX) Common/ExampleSwitchConfig.c ) + if(SOLIDSYSLOG_OPENSSL) + list(APPEND COMMON_SOURCES Common/ExampleTlsConfig.c) + endif() + # Single-task example: NullBuffer, direct send, bare-metal model add_executable(SolidSyslogExample SingleTask/main.c SingleTask/SolidSyslogExample.c ${COMMON_SOURCES}) target_link_libraries(SolidSyslogExample PRIVATE ${PROJECT_NAME}) @@ -17,12 +21,17 @@ if(SOLIDSYSLOG_POSIX) ${CMAKE_CURRENT_SOURCE_DIR}/Common ) - # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue) + # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue). + # TLS transport (optional via SOLIDSYSLOG_OPENSSL) adds OpenSSL linkage. add_executable(SolidSyslogThreadedExample Threaded/main.c ${COMMON_SOURCES}) target_link_libraries(SolidSyslogThreadedExample PRIVATE ${PROJECT_NAME} Threads::Threads) target_include_directories(SolidSyslogThreadedExample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Common ) + if(SOLIDSYSLOG_OPENSSL) + target_link_libraries(SolidSyslogThreadedExample PRIVATE OpenSSL::SSL OpenSSL::Crypto) + target_compile_definitions(SolidSyslogThreadedExample PRIVATE SOLIDSYSLOG_HAVE_OPENSSL=1) + endif() elseif(SOLIDSYSLOG_WINSOCK AND HAVE_WINDOWS_PLATFORM) # Windows example: NullBuffer + Winsock UDP + Windows clock/hostname/process-id. # Output binary is named SolidSyslogExample.exe so BDD scenarios remain platform-agnostic. diff --git a/Example/Common/ExampleCommandLine.c b/Example/Common/ExampleCommandLine.c index addc444c..716289e4 100644 --- a/Example/Common/ExampleCommandLine.c +++ b/Example/Common/ExampleCommandLine.c @@ -81,7 +81,7 @@ int ExampleCommandLine_Parse(int argc, char* argv[], struct ExampleOptions* opti options->msg = optarg; break; case 't': - if ((strcmp(optarg, "udp") != 0) && (strcmp(optarg, "tcp") != 0)) + if ((strcmp(optarg, "udp") != 0) && (strcmp(optarg, "tcp") != 0) && (strcmp(optarg, "tls") != 0)) { return 1; } diff --git a/Example/Common/ExampleSwitchConfig.c b/Example/Common/ExampleSwitchConfig.c index 9117a2c3..51ec4c6e 100644 --- a/Example/Common/ExampleSwitchConfig.c +++ b/Example/Common/ExampleSwitchConfig.c @@ -14,6 +14,10 @@ void ExampleSwitchConfig_SetByName(const char* name) { selectedIndex = EXAMPLE_SWITCH_TCP; } + else if (strcmp(name, "tls") == 0) + { + selectedIndex = EXAMPLE_SWITCH_TLS; + } } uint8_t ExampleSwitchConfig_Selector(void) diff --git a/Example/Common/ExampleSwitchConfig.h b/Example/Common/ExampleSwitchConfig.h index 36eb2d56..adbf70d3 100644 --- a/Example/Common/ExampleSwitchConfig.h +++ b/Example/Common/ExampleSwitchConfig.h @@ -11,6 +11,7 @@ EXTERN_C_BEGIN { EXAMPLE_SWITCH_UDP, EXAMPLE_SWITCH_TCP, + EXAMPLE_SWITCH_TLS, EXAMPLE_SWITCH_COUNT, }; diff --git a/Example/Common/ExampleTcpConfig.c b/Example/Common/ExampleTcpConfig.c index 916e2b1b..a941bf16 100644 --- a/Example/Common/ExampleTcpConfig.c +++ b/Example/Common/ExampleTcpConfig.c @@ -14,15 +14,15 @@ const char* ExampleTcpConfig_GetHost(void) return "syslog-ng"; } -int ExampleTcpConfig_GetPort(void) +uint16_t ExampleTcpConfig_GetPort(void) { - return EXAMPLE_TCP_PORT; + return (uint16_t) EXAMPLE_TCP_PORT; } void ExampleTcpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleTcpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleTcpConfig_GetPort(); + endpoint->port = ExampleTcpConfig_GetPort(); } /* Static config — host/port never change, so version stays 0 forever and the diff --git a/Example/Common/ExampleTcpConfig.h b/Example/Common/ExampleTcpConfig.h index 0be17712..8e049047 100644 --- a/Example/Common/ExampleTcpConfig.h +++ b/Example/Common/ExampleTcpConfig.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN const char* ExampleTcpConfig_GetHost(void); - int ExampleTcpConfig_GetPort(void); + uint16_t ExampleTcpConfig_GetPort(void); void ExampleTcpConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); uint32_t ExampleTcpConfig_GetEndpointVersion(void); diff --git a/Example/Common/ExampleTlsConfig.c b/Example/Common/ExampleTlsConfig.c new file mode 100644 index 00000000..f8bcf680 --- /dev/null +++ b/Example/Common/ExampleTlsConfig.c @@ -0,0 +1,46 @@ +#include "ExampleTlsConfig.h" +#include "SolidSyslogFormatter.h" + +#include + +enum +{ + EXAMPLE_TLS_PORT = 6514 /* RFC 5425 */ +}; + +/* Test CA for BDD. Paths are relative to the working directory the example is + * launched from (/workspaces/SolidSyslog in the BDD container). */ +static const char* const EXAMPLE_TLS_CA_BUNDLE_PATH = "Bdd/syslog-ng/tls/ca.pem"; + +const char* ExampleTlsConfig_GetHost(void) +{ + return "syslog-ng"; +} + +uint16_t ExampleTlsConfig_GetPort(void) +{ + return (uint16_t) EXAMPLE_TLS_PORT; +} + +const char* ExampleTlsConfig_GetCaBundlePath(void) +{ + return EXAMPLE_TLS_CA_BUNDLE_PATH; +} + +const char* ExampleTlsConfig_GetServerName(void) +{ + return ExampleTlsConfig_GetHost(); +} + +void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) +{ + SolidSyslogFormatter_BoundedString(endpoint->host, ExampleTlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + endpoint->port = ExampleTlsConfig_GetPort(); +} + +/* Static config — host/port never change, so version stays 0 forever and the + sender connects exactly once. */ +uint32_t ExampleTlsConfig_GetEndpointVersion(void) +{ + return 0; +} diff --git a/Example/Common/ExampleTlsConfig.h b/Example/Common/ExampleTlsConfig.h new file mode 100644 index 00000000..0de90802 --- /dev/null +++ b/Example/Common/ExampleTlsConfig.h @@ -0,0 +1,20 @@ +#ifndef EXAMPLETLSCONFIG_H +#define EXAMPLETLSCONFIG_H + +#include "ExternC.h" +#include "SolidSyslogEndpoint.h" + +#include + +EXTERN_C_BEGIN + + const char* ExampleTlsConfig_GetHost(void); + uint16_t ExampleTlsConfig_GetPort(void); + const char* ExampleTlsConfig_GetCaBundlePath(void); + const char* ExampleTlsConfig_GetServerName(void); + void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); + uint32_t ExampleTlsConfig_GetEndpointVersion(void); + +EXTERN_C_END + +#endif /* EXAMPLETLSCONFIG_H */ diff --git a/Example/Common/ExampleUdpConfig.c b/Example/Common/ExampleUdpConfig.c index 29eba802..2ac6408d 100644 --- a/Example/Common/ExampleUdpConfig.c +++ b/Example/Common/ExampleUdpConfig.c @@ -14,15 +14,15 @@ const char* ExampleUdpConfig_GetHost(void) return "syslog-ng"; } -int ExampleUdpConfig_GetPort(void) +uint16_t ExampleUdpConfig_GetPort(void) { - return EXAMPLE_UDP_PORT; + return (uint16_t) EXAMPLE_UDP_PORT; } void ExampleUdpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleUdpConfig_GetPort(); + endpoint->port = ExampleUdpConfig_GetPort(); } /* Static config — host/port never change, so version stays 0 forever and the diff --git a/Example/Common/ExampleUdpConfig.h b/Example/Common/ExampleUdpConfig.h index 6377002b..2ace1b3a 100644 --- a/Example/Common/ExampleUdpConfig.h +++ b/Example/Common/ExampleUdpConfig.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN const char* ExampleUdpConfig_GetHost(void); - int ExampleUdpConfig_GetPort(void); + uint16_t ExampleUdpConfig_GetPort(void); void ExampleUdpConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); uint32_t ExampleUdpConfig_GetEndpointVersion(void); diff --git a/Example/Threaded/main.c b/Example/Threaded/main.c index 5d29e58c..e8c79fea 100644 --- a/Example/Threaded/main.c +++ b/Example/Threaded/main.c @@ -26,6 +26,13 @@ #include "SolidSyslogStreamSender.h" #include "SolidSyslogUdpSender.h" +/* TODO(S3.13, #171): replace these three #ifdef blocks with a per-backend + * ExampleTlsSender factory selected at CMake time. */ +#ifdef SOLIDSYSLOG_HAVE_OPENSSL +#include "ExampleTlsConfig.h" +#include "SolidSyslogTlsStream.h" +#endif + #include #include #include @@ -33,6 +40,7 @@ static const char* const STORE_PATH_PREFIX = "/tmp/STORE"; static struct SolidSyslogFile* storeReadFile; static struct SolidSyslogFile* storeWriteFile; +static bool tlsModeActive; static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { @@ -50,8 +58,16 @@ static void* ServiceThreadEntry(void* arg) return NULL; } +/* SolidSyslogStreamSender and SolidSyslogPosixTcpStream are singletons in this + * walking-skeleton build, so we can't wire plain TCP and TLS simultaneously. + * The launched --transport picks one: "tls" wires UDP + TLS; anything else + * wires UDP + plain TCP (the pre-existing behaviour). Switching to the + * non-enabled reliable transport at runtime falls back to UDP. + */ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* options) { + tlsModeActive = (strcmp(options->transport, "tls") == 0); + struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); static struct SolidSyslogUdpSenderConfig udpConfig = {0}; @@ -61,16 +77,41 @@ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* optio udpConfig.endpointVersion = ExampleUdpConfig_GetEndpointVersion; struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); - static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; - tcpConfig.resolver = resolver; - tcpConfig.stream = SolidSyslogPosixTcpStream_Create(); - tcpConfig.endpoint = ExampleTcpConfig_GetEndpoint; - tcpConfig.endpointVersion = ExampleTcpConfig_GetEndpointVersion; - struct SolidSyslogSender* tcpSender = SolidSyslogStreamSender_Create(&tcpConfig); + struct SolidSyslogSender* reliableSender = NULL; + if (tlsModeActive) + { +#ifdef SOLIDSYSLOG_HAVE_OPENSSL + static struct SolidSyslogTlsStreamConfig tlsStreamConfig = {0}; + tlsStreamConfig.transport = SolidSyslogPosixTcpStream_Create(); + tlsStreamConfig.caBundlePath = ExampleTlsConfig_GetCaBundlePath(); + tlsStreamConfig.serverName = ExampleTlsConfig_GetServerName(); + struct SolidSyslogStream* tlsStream = SolidSyslogTlsStream_Create(&tlsStreamConfig); + + static struct SolidSyslogStreamSenderConfig tlsSenderConfig = {0}; + tlsSenderConfig.resolver = resolver; + tlsSenderConfig.stream = tlsStream; + tlsSenderConfig.endpoint = ExampleTlsConfig_GetEndpoint; + tlsSenderConfig.endpointVersion = ExampleTlsConfig_GetEndpointVersion; + reliableSender = SolidSyslogStreamSender_Create(&tlsSenderConfig); +#else + /* --transport tls requested but the build has no OpenSSL — fall through to TCP. */ + tlsModeActive = false; +#endif + } + if (!tlsModeActive && reliableSender == NULL) + { + static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; + tcpConfig.resolver = resolver; + tcpConfig.stream = SolidSyslogPosixTcpStream_Create(); + tcpConfig.endpoint = ExampleTcpConfig_GetEndpoint; + tcpConfig.endpointVersion = ExampleTcpConfig_GetEndpointVersion; + reliableSender = SolidSyslogStreamSender_Create(&tcpConfig); + } static struct SolidSyslogSender* inners[EXAMPLE_SWITCH_COUNT]; inners[EXAMPLE_SWITCH_UDP] = udpSender; - inners[EXAMPLE_SWITCH_TCP] = tcpSender; + inners[EXAMPLE_SWITCH_TCP] = tlsModeActive ? udpSender : reliableSender; + inners[EXAMPLE_SWITCH_TLS] = tlsModeActive ? reliableSender : udpSender; static struct SolidSyslogSwitchingSenderConfig switchConfig = {0}; switchConfig.senders = inners; @@ -135,6 +176,12 @@ static void DestroySender(void) { SolidSyslogSwitchingSender_Destroy(); SolidSyslogStreamSender_Destroy(); +#ifdef SOLIDSYSLOG_HAVE_OPENSSL + if (tlsModeActive) + { + SolidSyslogTlsStream_Destroy(); + } +#endif SolidSyslogPosixTcpStream_Destroy(); SolidSyslogUdpSender_Destroy(); SolidSyslogPosixDatagram_Destroy(); diff --git a/Interface/SolidSyslogStream.h b/Interface/SolidSyslogStream.h index fd4294e1..507e21e9 100644 --- a/Interface/SolidSyslogStream.h +++ b/Interface/SolidSyslogStream.h @@ -5,14 +5,21 @@ #include "SolidSyslogAddress.h" #include #include +#include EXTERN_C_BEGIN + /* Signed byte count. Models POSIX ssize_t using a standard-C type for portability + to targets that lack 's ssize_t (notably MSVC). Semantics mirror + POSIX recv(2): >=0 = bytes transferred, 0 = EOF on stream reads, <0 = error. */ + typedef intptr_t SolidSyslogSsize; + struct SolidSyslogStream; - bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); - bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); - void SolidSyslogStream_Close(struct SolidSyslogStream * stream); + bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); + bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); + SolidSyslogSsize SolidSyslogStream_Read(struct SolidSyslogStream * stream, void* buffer, size_t size); + void SolidSyslogStream_Close(struct SolidSyslogStream * stream); EXTERN_C_END diff --git a/Interface/SolidSyslogStreamDefinition.h b/Interface/SolidSyslogStreamDefinition.h index 83be381f..5a7860b7 100644 --- a/Interface/SolidSyslogStreamDefinition.h +++ b/Interface/SolidSyslogStreamDefinition.h @@ -9,6 +9,7 @@ EXTERN_C_BEGIN { bool (*Open)(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); bool (*Send)(struct SolidSyslogStream* self, const void* buffer, size_t size); + SolidSyslogSsize (*Read)(struct SolidSyslogStream* self, void* buffer, size_t size); void (*Close)(struct SolidSyslogStream* self); }; diff --git a/Platform/OpenSsl/CMakeLists.txt b/Platform/OpenSsl/CMakeLists.txt new file mode 100644 index 00000000..da9ab417 --- /dev/null +++ b/Platform/OpenSsl/CMakeLists.txt @@ -0,0 +1,19 @@ +# OpenSSL was discovered (or explicitly requested) at the top-level CMakeLists. +# Platform/OpenSsl is only entered when SOLIDSYSLOG_OPENSSL is ON, so we can +# rely on the OPENSSL_INCLUDE_DIR variable being set. + +target_sources(${PROJECT_NAME} PRIVATE + Source/SolidSyslogTlsStream.c +) + +# Headers at compile time; libssl is linked by consumers (real linkage) or +# replaced by OpenSslFake in tests — not declared as a library link here. +target_include_directories(${PROJECT_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Interface + PRIVATE ${OPENSSL_INCLUDE_DIR} +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Interface/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h" +) diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h new file mode 100644 index 00000000..f6855aca --- /dev/null +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -0,0 +1,20 @@ +#ifndef SOLIDSYSLOGTLSSTREAM_H +#define SOLIDSYSLOGTLSSTREAM_H + +#include "SolidSyslogStream.h" + +EXTERN_C_BEGIN + + struct SolidSyslogTlsStreamConfig + { + struct SolidSyslogStream* transport; /* underlying byte stream — caller owns */ + const char* caBundlePath; /* PEM file of trust anchors */ + const char* serverName; /* SNI + cert hostname check; NULL to skip */ + }; + + struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config); + void SolidSyslogTlsStream_Destroy(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGTLSSTREAM_H */ diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c new file mode 100644 index 00000000..ac5e8ce9 --- /dev/null +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -0,0 +1,144 @@ +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsStream.h" + +#include + +struct SolidSyslogTlsStream +{ + struct SolidSyslogStream base; + struct SolidSyslogTlsStreamConfig config; + SSL_CTX* ctx; + SSL* ssl; +}; + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); +static SSL_CTX* CreateSslContext(const char* caBundlePath); +static BIO* CreateTransportBio(void); +static int TransportBioCreate(BIO* bio); +static int TransportBioRead(BIO* bio, char* buffer, int size); +static int TransportBioWrite(BIO* bio, const char* buffer, int size); +static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg); + +static struct SolidSyslogTlsStream instance; + +struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config) +{ + instance.config = *config; + instance.base.Open = Open; + instance.base.Send = Send; + instance.base.Read = Read; + instance.base.Close = Close; + return &instance.base; +} + +void SolidSyslogTlsStream_Destroy(void) +{ + if (instance.ctx != NULL) + { + SSL_CTX_free(instance.ctx); + instance.ctx = NULL; + } +} + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + if (!SolidSyslogStream_Open(stream->config.transport, addr)) + { + return false; + } + stream->ctx = CreateSslContext(stream->config.caBundlePath); + stream->ssl = SSL_new(stream->ctx); + BIO* bio = CreateTransportBio(); + BIO_set_data(bio, stream->config.transport); + SSL_set_bio(stream->ssl, bio, bio); + if (stream->config.serverName != NULL) + { + SSL_set_tlsext_host_name(stream->ssl, stream->config.serverName); + SSL_set1_host(stream->ssl, stream->config.serverName); + } + return SSL_connect(stream->ssl) > 0; +} + +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + return SSL_write(stream->ssl, buffer, (int) size) > 0; +} + +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + return (SolidSyslogSsize) SSL_read(stream->ssl, buffer, (int) size); +} + +static void Close(struct SolidSyslogStream* self) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + SSL_shutdown(stream->ssl); + SSL_free(stream->ssl); + SolidSyslogStream_Close(stream->config.transport); +} + +static BIO* CreateTransportBio(void) +{ + BIO_METHOD* method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "SolidSyslog transport BIO"); + BIO_meth_set_create(method, TransportBioCreate); + BIO_meth_set_read(method, TransportBioRead); + BIO_meth_set_write(method, TransportBioWrite); + BIO_meth_set_ctrl(method, TransportBioCtrl); + return BIO_new(method); +} + +/* Called when BIO_new instantiates a BIO from our method. Marking init=1 tells + * OpenSSL the BIO is ready for I/O; without it SSL_connect bails early. */ +static int TransportBioCreate(BIO* bio) +{ + BIO_set_init(bio, 1); + return 1; +} + +static int TransportBioRead(BIO* bio, char* buffer, int size) +{ + struct SolidSyslogStream* transport = (struct SolidSyslogStream*) BIO_get_data(bio); + return (int) SolidSyslogStream_Read(transport, buffer, (size_t) size); +} + +static int TransportBioWrite(BIO* bio, const char* buffer, int size) +{ + struct SolidSyslogStream* transport = (struct SolidSyslogStream*) BIO_get_data(bio); + return SolidSyslogStream_Send(transport, buffer, (size_t) size) ? size : -1; +} + +/* Minimal ctrl handler. OpenSSL calls this for a variety of control commands + * during normal operation; returning 1 for the common lifecycle commands lets + * SSL_connect / SSL_write / SSL_shutdown proceed. Unknown commands return 0. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL BIO_ctrl_fn contract +static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg) +{ + (void) bio; + (void) larg; + (void) parg; + switch (cmd) + { + case BIO_CTRL_FLUSH: + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + case BIO_CTRL_DUP: + return 1; + default: + return 0; + } +} + +static SSL_CTX* CreateSslContext(const char* caBundlePath) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, caBundlePath, NULL); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + return ctx; +} diff --git a/Platform/Posix/Source/SolidSyslogAddressInternal.h b/Platform/Posix/Source/SolidSyslogAddressInternal.h index 46927eb5..7773e845 100644 --- a/Platform/Posix/Source/SolidSyslogAddressInternal.h +++ b/Platform/Posix/Source/SolidSyslogAddressInternal.h @@ -7,8 +7,7 @@ #include #include -SOLIDSYSLOG_STATIC_ASSERT(sizeof(struct sockaddr_in) <= SOLIDSYSLOG_ADDRESS_SIZE, - SolidSyslogAddressStorage_too_small_for_sockaddr_in); +SOLIDSYSLOG_STATIC_ASSERT(sizeof(struct sockaddr_in) <= SOLIDSYSLOG_ADDRESS_SIZE, SolidSyslogAddressStorage_too_small_for_sockaddr_in); static inline struct sockaddr_in* SolidSyslogAddress_AsSockaddrIn(struct SolidSyslogAddress* address) { diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 3525bc1d..df844d12 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -12,11 +12,12 @@ enum INVALID_FD = -1 }; -static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); -static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); -static void Close(struct SolidSyslogStream* self); -static void EnableTcpNoDelay(int fd); -static inline bool IsFileDescriptorValid(int fd); +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); +static void EnableTcpNoDelay(int fd); +static inline bool IsFileDescriptorValid(int fd); struct SolidSyslogPosixTcpStream { @@ -30,14 +31,17 @@ struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void) { instance.base.Open = Open; instance.base.Send = Send; + instance.base.Read = Read; instance.base.Close = Close; return &instance.base; } void SolidSyslogPosixTcpStream_Destroy(void) { + Close(&instance.base); instance.base.Open = NULL; instance.base.Send = NULL; + instance.base.Read = NULL; instance.base.Close = NULL; } @@ -73,6 +77,12 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size return send(stream->fd, buffer, size, MSG_NOSIGNAL) >= 0; } +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct SolidSyslogPosixTcpStream* stream = (struct SolidSyslogPosixTcpStream*) self; + return (SolidSyslogSsize) recv(stream->fd, buffer, size, 0); +} + static void Close(struct SolidSyslogStream* self) { struct SolidSyslogPosixTcpStream* stream = (struct SolidSyslogPosixTcpStream*) self; diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 72320c3c..5deb21af 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -30,9 +30,14 @@ if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_WINSOCK) ) endif() -if(SOLIDSYSLOG_POSIX) +if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_OPENSSL) list(APPEND SOURCES SolidSyslogStream.c + ) +endif() + +if(SOLIDSYSLOG_POSIX) + list(APPEND SOURCES SolidSyslogStreamSender.c ) endif() diff --git a/Source/SolidSyslogStream.c b/Source/SolidSyslogStream.c index 497ab966..c6623cf3 100644 --- a/Source/SolidSyslogStream.c +++ b/Source/SolidSyslogStream.c @@ -10,6 +10,11 @@ bool SolidSyslogStream_Send(struct SolidSyslogStream* stream, const void* buffer return stream->Send(stream, buffer, size); } +SolidSyslogSsize SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size) +{ + return stream->Read(stream, buffer, size); +} + void SolidSyslogStream_Close(struct SolidSyslogStream* stream) { stream->Close(stream); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 768318a0..f5049df9 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -29,6 +29,8 @@ set(TEST_SOURCES StringFake.c FileFakeTest.cpp FileFake.c + StreamFakeTest.cpp + StreamFake.c TestAssert.cpp SolidSyslogFileStoreTest.cpp main.cpp @@ -58,6 +60,14 @@ if(SOLIDSYSLOG_POSIX) SolidSyslogGetAddrInfoResolverTest.cpp SolidSyslogPosixDatagramTest.cpp SolidSyslogPosixTcpStreamTest.cpp + SocketFakeTest.cpp + ) +endif() + +if(SOLIDSYSLOG_OPENSSL) + list(APPEND TEST_SOURCES + SolidSyslogTlsStreamTest.cpp + OpenSslFakeTest.cpp ) endif() @@ -90,6 +100,10 @@ if(SOLIDSYSLOG_POSIX) target_link_libraries(${PROJECT_NAME}Tests PRIVATE PosixFakes) endif() +if(SOLIDSYSLOG_OPENSSL) + target_link_libraries(${PROJECT_NAME}Tests PRIVATE OpenSslFakes) +endif() + if(HAVE_WINDOWS_PLATFORM OR SOLIDSYSLOG_WINSOCK) target_include_directories(${PROJECT_NAME}Tests PRIVATE ${CMAKE_SOURCE_DIR}/Platform/Windows/Source) endif() diff --git a/Tests/Example/ExampleCommandLineTest.cpp b/Tests/Example/ExampleCommandLineTest.cpp index bb03f3f4..f3088a2d 100644 --- a/Tests/Example/ExampleCommandLineTest.cpp +++ b/Tests/Example/ExampleCommandLineTest.cpp @@ -166,6 +166,16 @@ TEST(ExampleCommandLine, TrailingTextMaxFileSizeReturnsOne) LONGS_EQUAL(1, Parse(3, argv)); } +TEST(ExampleCommandLine, TransportTlsAccepted) +{ + char arg0[] = "test"; + char argFlag[] = "--transport"; + char argVal[] = "tls"; + char* argv[] = {arg0, argFlag, argVal, nullptr}; + LONGS_EQUAL(0, Parse(3, argv)); + STRCMP_EQUAL("tls", options.transport); +} + TEST(ExampleCommandLine, NoSdFlag) { char arg0[] = "test"; diff --git a/Tests/Example/ExampleServiceThreadTest.cpp b/Tests/Example/ExampleServiceThreadTest.cpp index 337b030a..a351ac37 100644 --- a/Tests/Example/ExampleServiceThreadTest.cpp +++ b/Tests/Example/ExampleServiceThreadTest.cpp @@ -16,7 +16,7 @@ static void ExampleEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleUdpConfig_GetPort(); + endpoint->port = ExampleUdpConfig_GetPort(); } static uint32_t ExampleEndpointVersion() // NOLINT(modernize-use-trailing-return-type) diff --git a/Tests/Example/ExampleSwitchConfigTest.cpp b/Tests/Example/ExampleSwitchConfigTest.cpp index 9dda02be..fd349d6f 100644 --- a/Tests/Example/ExampleSwitchConfigTest.cpp +++ b/Tests/Example/ExampleSwitchConfigTest.cpp @@ -24,6 +24,12 @@ TEST(ExampleSwitchConfig, SetByNameTcpSelectsTcpIndex) LONGS_EQUAL(EXAMPLE_SWITCH_TCP, ExampleSwitchConfig_Selector()); } +TEST(ExampleSwitchConfig, SetByNameTlsSelectsTlsIndex) +{ + ExampleSwitchConfig_SetByName("tls"); + LONGS_EQUAL(EXAMPLE_SWITCH_TLS, ExampleSwitchConfig_Selector()); +} + TEST(ExampleSwitchConfig, UnknownNameLeavesPreviousSelection) { ExampleSwitchConfig_SetByName("tcp"); diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp new file mode 100644 index 00000000..8822a368 --- /dev/null +++ b/Tests/OpenSslFakeTest.cpp @@ -0,0 +1,450 @@ +#include "CppUTest/TestHarness.h" +#include "OpenSslFake.h" +#include + +// clang-format off +TEST_GROUP(OpenSslFake) +{ + void setup() override { OpenSslFake_Reset(); } +}; +// clang-format on + +TEST(OpenSslFake, CtxNewCountIsZeroAfterReset) +{ + LONGS_EQUAL(0, OpenSslFake_CtxNewCallCount()); +} + +TEST(OpenSslFake, CtxNewIncrementsCount) +{ + SSL_CTX_new(TLS_client_method()); + LONGS_EQUAL(1, OpenSslFake_CtxNewCallCount()); +} + +TEST(OpenSslFake, CtxNewReturnsNonNull) +{ + CHECK_TRUE(SSL_CTX_new(TLS_client_method()) != nullptr); +} + +TEST(OpenSslFake, LoadVerifyLocationsCapturesCaPath) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, "/my/ca.pem", nullptr); + STRCMP_EQUAL("/my/ca.pem", OpenSslFake_LastCaBundlePath()); +} + +TEST(OpenSslFake, SetVerifyCapturesMode) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr); + LONGS_EQUAL(SSL_VERIFY_PEER, OpenSslFake_LastVerifyMode()); +} + +TEST(OpenSslFake, SetMinProtoVersionCapturesVersion) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + LONGS_EQUAL(TLS1_2_VERSION, OpenSslFake_LastMinProtoVersion()); +} + +TEST(OpenSslFake, CtxNewReturnValueIsSurfaced) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + POINTERS_EQUAL(ctx, OpenSslFake_LastCtxReturned()); +} + +TEST(OpenSslFake, SslNewIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_new(ctx); + LONGS_EQUAL(1, OpenSslFake_SslNewCallCount()); +} + +TEST(OpenSslFake, SslNewReturnsNonNull) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + CHECK_TRUE(SSL_new(ctx) != nullptr); +} + +TEST(OpenSslFake, SslNewCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_new(ctx); + POINTERS_EQUAL(ctx, OpenSslFake_LastSslNewCtxArg()); +} + +TEST(OpenSslFake, BioNewIncrementsCount) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_new(method); + LONGS_EQUAL(1, OpenSslFake_BioNewCallCount()); +} + +TEST(OpenSslFake, BioNewReturnsNonNull) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + CHECK_TRUE(BIO_new(method) != nullptr); +} + +TEST(OpenSslFake, BioNewReturnValueIsSurfaced) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + POINTERS_EQUAL(bio, OpenSslFake_LastBioReturned()); +} + +TEST(OpenSslFake, SetBioIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + LONGS_EQUAL(1, OpenSslFake_SetBioCallCount()); +} + +TEST(OpenSslFake, SetBioCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + POINTERS_EQUAL(ssl, OpenSslFake_LastSetBioSslArg()); +} + +TEST(OpenSslFake, SetBioCapturesReadBioArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + POINTERS_EQUAL(bio, OpenSslFake_LastSetBioReadBioArg()); +} + +TEST(OpenSslFake, ConnectIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_connect(ssl); + LONGS_EQUAL(1, OpenSslFake_ConnectCallCount()); +} + +TEST(OpenSslFake, ConnectCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_connect(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastConnectSslArg()); +} + +TEST(OpenSslFake, ConnectDefaultsToSuccess) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + LONGS_EQUAL(1, SSL_connect(ssl)); +} + +TEST(OpenSslFake, SetTlsExtHostNameCapturesHostname) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set_tlsext_host_name(ssl, "host.example"); + STRCMP_EQUAL("host.example", OpenSslFake_LastSniHostname()); +} + +TEST(OpenSslFake, Set1HostCapturesHostname) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set1_host(ssl, "host.example"); + STRCMP_EQUAL("host.example", OpenSslFake_LastSet1Host()); +} + +TEST(OpenSslFake, BioSetDataCapturesData) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(&sentinel, OpenSslFake_LastSetDataArg()); +} + +TEST(OpenSslFake, BioGetDataReturnsPreviouslySetData) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(&sentinel, BIO_get_data(bio)); +} + +// NOLINTNEXTLINE(readability-non-const-parameter) -- signature fixed by OpenSSL BIO_meth_set_read contract +static int DummyRead(BIO* bio, char* buf, int size) +{ + (void) bio; + (void) buf; + (void) size; + return 0; +} + +TEST(OpenSslFake, BioMethSetReadCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_read(method, DummyRead); + FUNCTIONPOINTERS_EQUAL(DummyRead, OpenSslFake_LastBioReadCallback()); +} + +static int DummyWrite(BIO* bio, const char* buf, int size) +{ + (void) bio; + (void) buf; + (void) size; + return 0; +} + +TEST(OpenSslFake, BioMethSetWriteCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_write(method, DummyWrite); + FUNCTIONPOINTERS_EQUAL(DummyWrite, OpenSslFake_LastBioWriteCallback()); +} + +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL BIO_meth_set_ctrl contract +static long DummyCtrl(BIO* bio, int cmd, long larg, void* parg) +{ + (void) bio; + (void) cmd; + (void) larg; + (void) parg; + return 0; +} + +TEST(OpenSslFake, BioMethSetCtrlCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_ctrl(method, DummyCtrl); + FUNCTIONPOINTERS_EQUAL(DummyCtrl, OpenSslFake_LastBioCtrlCallback()); +} + +static int DummyCreate(BIO* bio) +{ + (void) bio; + return 1; +} + +TEST(OpenSslFake, BioMethSetCreateCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_create(method, DummyCreate); + FUNCTIONPOINTERS_EQUAL(DummyCreate, OpenSslFake_LastBioCreateCallback()); +} + +TEST(OpenSslFake, WriteIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "x", 1); + LONGS_EQUAL(1, OpenSslFake_WriteCallCount()); +} + +TEST(OpenSslFake, WriteCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "x", 1); + POINTERS_EQUAL(ssl, OpenSslFake_LastWriteSslArg()); +} + +TEST(OpenSslFake, WriteCapturesBuffer) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + const char* msg = "payload"; + SSL_write(ssl, msg, 7); + POINTERS_EQUAL(msg, OpenSslFake_LastWriteBuf()); +} + +TEST(OpenSslFake, WriteCapturesSize) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "payload", 7); + LONGS_EQUAL(7, OpenSslFake_LastWriteSize()); +} + +TEST(OpenSslFake, WriteDefaultsToEchoingSize) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + LONGS_EQUAL(7, SSL_write(ssl, "payload", 7)); +} + +TEST(OpenSslFake, ShutdownIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_shutdown(ssl); + LONGS_EQUAL(1, OpenSslFake_ShutdownCallCount()); +} + +TEST(OpenSslFake, FreeIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_free(ssl); + LONGS_EQUAL(1, OpenSslFake_FreeCallCount()); +} + +TEST(OpenSslFake, CtxFreeIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_free(ctx); + LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); +} + +/* ------------------------------------------------------------------------- + * Arg captures — prove each fake function records the args its callers pass. + * ------------------------------------------------------------------------- */ + +TEST(OpenSslFake, CtxNewCapturesMethodArg) +{ + const SSL_METHOD* method = TLS_client_method(); + SSL_CTX_new(method); + POINTERS_EQUAL(method, OpenSslFake_LastCtxNewMethodArg()); +} + +TEST(OpenSslFake, LoadVerifyLocationsCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, "/ca.pem", nullptr); + POINTERS_EQUAL(ctx, OpenSslFake_LastLoadVerifyLocationsCtxArg()); +} + +TEST(OpenSslFake, SetVerifyCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr); + POINTERS_EQUAL(ctx, OpenSslFake_LastSetVerifyCtxArg()); +} + +TEST(OpenSslFake, SetMinProtoVersionCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + POINTERS_EQUAL(ctx, OpenSslFake_LastSslCtxCtrlCtxArg()); +} + +TEST(OpenSslFake, BioMethSetReadCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_read(method, DummyRead); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethSetReadMethodArg()); +} + +TEST(OpenSslFake, BioMethSetWriteCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_write(method, DummyWrite); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethSetWriteMethodArg()); +} + +TEST(OpenSslFake, BioNewCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_new(method); + POINTERS_EQUAL(method, OpenSslFake_LastBioNewMethodArg()); +} + +TEST(OpenSslFake, BioMethNewReturnValueIsSurfaced) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethReturned()); +} + +TEST(OpenSslFake, BioSetDataCapturesBioArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(bio, OpenSslFake_LastSetDataBioArg()); +} + +TEST(OpenSslFake, BioGetDataCapturesBioArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + BIO_get_data(bio); + POINTERS_EQUAL(bio, OpenSslFake_LastGetDataBioArg()); +} + +TEST(OpenSslFake, SetBioCapturesWriteBioArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* rbio = BIO_new(method); + BIO* wbio = BIO_new(method); + SSL_set_bio(ssl, rbio, wbio); + POINTERS_EQUAL(wbio, OpenSslFake_LastSetBioWriteBioArg()); +} + +TEST(OpenSslFake, SetTlsExtHostNameCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set_tlsext_host_name(ssl, "host.example"); + POINTERS_EQUAL(ssl, OpenSslFake_LastSslCtrlSslArg()); +} + +TEST(OpenSslFake, Set1HostCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set1_host(ssl, "host.example"); + POINTERS_EQUAL(ssl, OpenSslFake_LastSet1HostSslArg()); +} + +TEST(OpenSslFake, ShutdownCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_shutdown(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastShutdownSslArg()); +} + +TEST(OpenSslFake, FreeCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_free(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastFreeSslArg()); +} + +TEST(OpenSslFake, CtxFreeCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_free(ctx); + POINTERS_EQUAL(ctx, OpenSslFake_LastCtxFreeCtxArg()); +} + +/* ------------------------------------------------------------------------- + * Failure-mode switches — tests opt into failure returns via SetXxxFails. + * ------------------------------------------------------------------------- */ + +TEST(OpenSslFake, SetConnectFailsMakesConnectReturnNegative) +{ + OpenSslFake_SetConnectFails(true); + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + CHECK_TRUE(SSL_connect(ssl) <= 0); +} + +TEST(OpenSslFake, SetWriteFailsMakesWriteReturnNegative) +{ + OpenSslFake_SetWriteFails(true); + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + CHECK_TRUE(SSL_write(ssl, "x", 1) <= 0); +} diff --git a/Tests/SocketFakeTest.cpp b/Tests/SocketFakeTest.cpp new file mode 100644 index 00000000..7d86d72a --- /dev/null +++ b/Tests/SocketFakeTest.cpp @@ -0,0 +1,46 @@ +#include "CppUTest/TestHarness.h" +#include "SocketFake.h" + +#include + +// clang-format off +TEST_GROUP(SocketFake) +{ + void setup() override { SocketFake_Reset(); } +}; +// clang-format on + +TEST(SocketFake, RecvIncrementsCount) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + LONGS_EQUAL(1, SocketFake_RecvCallCount()); +} + +TEST(SocketFake, RecvCapturesFd) +{ + char buf[16]; + recv(7, buf, sizeof(buf), 0); + LONGS_EQUAL(7, SocketFake_LastRecvFd()); +} + +TEST(SocketFake, RecvCapturesBuf) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + POINTERS_EQUAL(buf, SocketFake_LastRecvBuf()); +} + +TEST(SocketFake, RecvCapturesLen) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + LONGS_EQUAL(sizeof(buf), SocketFake_LastRecvLen()); +} + +TEST(SocketFake, RecvCapturesFlags) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 42); + LONGS_EQUAL(42, SocketFake_LastRecvFlags()); +} diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index d75b3261..d2158855 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -177,3 +177,66 @@ TEST(SolidSyslogPosixTcpStream, CloseIsNoOpWhenNotOpen) SolidSyslogStream_Close(stream); LONGS_EQUAL(0, SocketFake_CloseCallCount()); } + +TEST(SolidSyslogPosixTcpStream, ReadCallsRecvOnce) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(1, SocketFake_RecvCallCount()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesSocketFdToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(SocketFake_SocketFd(), SocketFake_LastRecvFd()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesBufferToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, SocketFake_LastRecvBuf()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesLengthToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), SocketFake_LastRecvLen()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesZeroFlagsToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(0, SocketFake_LastRecvFlags()); +} + +TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) +{ + SocketFake_SetRecvReturn(7); + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogSsize n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(7, n); +} + +TEST(SolidSyslogPosixTcpStream, DestroyClosesOpenSocket) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogPosixTcpStream_Destroy(); + LONGS_EQUAL(1, SocketFake_CloseCallCount()); +} + +TEST(SolidSyslogPosixTcpStream, DestroyClosesWithSocketFd) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogPosixTcpStream_Destroy(); + LONGS_EQUAL(SocketFake_SocketFd(), SocketFake_LastClosedFd()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp new file mode 100644 index 00000000..56fb3445 --- /dev/null +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -0,0 +1,474 @@ +#include "CppUTest/TestHarness.h" +#include "OpenSslFake.h" +#include "SolidSyslogAddress.h" +#include "SolidSyslogStream.h" +#include "SolidSyslogTlsStream.h" +#include "StreamFake.h" +#include + +// clang-format off +TEST_GROUP(SolidSyslogTlsStream) +{ + struct SolidSyslogStream* transport = nullptr; + struct SolidSyslogTlsStreamConfig config = {}; + struct SolidSyslogStream* stream = nullptr; + SolidSyslogAddressStorage addrStorage = {0}; + struct SolidSyslogAddress* addr = nullptr; + + void setup() override + { + OpenSslFake_Reset(); + transport = StreamFake_Create(); + config.transport = transport; + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + stream = SolidSyslogTlsStream_Create(&config); + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + addr = SolidSyslogAddress_FromStorage(&addrStorage); + } + + void teardown() override + { + SolidSyslogTlsStream_Destroy(); + StreamFake_Destroy(transport); + } +}; + +// clang-format on + +TEST(SolidSyslogTlsStream, CreateSucceeds) +{ + CHECK_TRUE(stream != nullptr); +} + +TEST(SolidSyslogTlsStream, OpenOpensTransport) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, StreamFake_OpenCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, OpenPassesAddressToTransport) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(addr, StreamFake_LastOpenAddr(transport)); +} + +TEST(SolidSyslogTlsStream, OpenCreatesSslContext) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_CtxNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenLoadsCaBundleFromConfig) +{ + SolidSyslogTlsStream_Destroy(); + config.caBundlePath = "/some/path/ca.pem"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("/some/path/ca.pem", OpenSslFake_LastCaBundlePath()); +} + +TEST(SolidSyslogTlsStream, OpenRequiresPeerVerification) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(SSL_VERIFY_PEER, OpenSslFake_LastVerifyMode()); +} + +TEST(SolidSyslogTlsStream, OpenSetsTls12Floor) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(TLS1_2_VERSION, OpenSslFake_LastMinProtoVersion()); +} + +TEST(SolidSyslogTlsStream, OpenCreatesSslSession) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_SslNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromCtxNewToSslNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSslNewCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenCreatesBio) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_BioNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenSetsBioOnSsl) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_SetBioCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslFromNewToSetBio) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSetBioSslArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioFromNewToSetBio) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastSetBioReadBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPerformsHandshake) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_ConnectCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslToConnect) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastConnectSslArg()); +} + +TEST(SolidSyslogTlsStream, OpenSetsSniHostnameFromConfig) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("logs.example", OpenSslFake_LastSniHostname()); +} + +TEST(SolidSyslogTlsStream, OpenSetsExpectedCertHostname) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("logs.example", OpenSslFake_LastSet1Host()); +} + +TEST(SolidSyslogTlsStream, OpenSkipsHostnameSetupWhenServerNameIsNull) +{ + /* Default config.serverName is NULL */ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(NULL, OpenSslFake_LastSet1Host()); +} + +TEST(SolidSyslogTlsStream, OpenAttachesTransportAsBioData) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(transport, OpenSslFake_LastSetDataArg()); +} + +TEST(SolidSyslogTlsStream, BioReadCallbackDelegatesToTransportRead) +{ + SolidSyslogStream_Open(stream, addr); + int (*readFn)(BIO*, char*, int) = OpenSslFake_LastBioReadCallback(); + CHECK_TRUE(readFn != nullptr); + if (readFn == nullptr) + { + return; + } + char buf[16]; + readFn(OpenSslFake_LastBioReturned(), buf, sizeof(buf)); + LONGS_EQUAL(1, StreamFake_ReadCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, BioWriteCallbackDelegatesToTransportSend) +{ + SolidSyslogStream_Open(stream, addr); + int (*writeFn)(BIO*, const char*, int) = OpenSslFake_LastBioWriteCallback(); + CHECK_TRUE(writeFn != nullptr); + if (writeFn == nullptr) + { + return; + } + const char msg[] = "hi"; + writeFn(OpenSslFake_LastBioReturned(), msg, (int) sizeof(msg)); + LONGS_EQUAL(1, StreamFake_SendCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, SendWritesToSsl) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + LONGS_EQUAL(1, OpenSslFake_WriteCallCount()); +} + +TEST(SolidSyslogTlsStream, SendPassesBufferToSslWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + POINTERS_EQUAL(msg, OpenSslFake_LastWriteBuf()); +} + +TEST(SolidSyslogTlsStream, SendPassesSizeToSslWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + LONGS_EQUAL(sizeof(msg), OpenSslFake_LastWriteSize()); +} + +TEST(SolidSyslogTlsStream, SendPassesSslFromNewToWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastWriteSslArg()); +} + +TEST(SolidSyslogTlsStream, ReadReadsFromSsl) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(1, OpenSslFake_SslReadCallCount()); +} + +TEST(SolidSyslogTlsStream, ReadPassesSslFromNewToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSslReadSslArg()); +} + +TEST(SolidSyslogTlsStream, ReadPassesBufferToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, OpenSslFake_LastSslReadBuf()); +} + +TEST(SolidSyslogTlsStream, ReadPassesSizeToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), OpenSslFake_LastSslReadSize()); +} + +TEST(SolidSyslogTlsStream, CloseShutsDownSsl) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, OpenSslFake_ShutdownCallCount()); +} + +TEST(SolidSyslogTlsStream, CloseFreesSsl) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, OpenSslFake_FreeCallCount()); +} + +TEST(SolidSyslogTlsStream, CloseClosesTransport) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, DestroyFreesSslContext) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogTlsStream_Destroy(); + LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); + /* teardown re-Destroys safely */ +} + +/* ------------------------------------------------------------------------- + * Pointer-chain assertions: each OpenSSL call must receive the handle + * returned by the preceding call, not some stale or NULL pointer. + * ------------------------------------------------------------------------- */ + +TEST(SolidSyslogTlsStream, OpenPassesClientMethodToCtxNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(TLS_client_method(), OpenSslFake_LastCtxNewMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToLoadVerifyLocations) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastLoadVerifyLocationsCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToSetVerify) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSetVerifyCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToSetMinProtoVersion) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSslCtxCtrlCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToSetRead) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioMethSetReadMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToSetWrite) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioMethSetWriteMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToBioNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioNewMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioFromNewToSetData) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastSetDataBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSameBioForReadAndWrite) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSetBioReadBioArg(), OpenSslFake_LastSetBioWriteBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslToSniCtrl) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSslCtrlSslArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslFromNewToSet1Host) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSet1HostSslArg()); +} + +TEST(SolidSyslogTlsStream, BioReadCallbackLooksUpDataOnTheCorrectBio) +{ + SolidSyslogStream_Open(stream, addr); + int (*readFn)(BIO*, char*, int) = OpenSslFake_LastBioReadCallback(); + char buf[16]; + readFn(OpenSslFake_LastBioReturned(), buf, sizeof(buf)); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastGetDataBioArg()); +} + +TEST(SolidSyslogTlsStream, ClosePassesSslFromNewToShutdown) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastShutdownSslArg()); +} + +TEST(SolidSyslogTlsStream, ClosePassesSslFromNewToFree) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastFreeSslArg()); +} + +TEST(SolidSyslogTlsStream, DestroyPassesCtxFromNewToCtxFree) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogTlsStream_Destroy(); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastCtxFreeCtxArg()); +} + +/* ------------------------------------------------------------------------- + * Error paths. + * ------------------------------------------------------------------------- */ + +TEST(SolidSyslogTlsStream, OpenReturnsTrueOnHappyPath) +{ + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenHandshakeFails) +{ + OpenSslFake_SetConnectFails(true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, SendReturnsTrueOnHappyPath) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hi"; + CHECK_TRUE(SolidSyslogStream_Send(stream, msg, sizeof(msg))); +} + +TEST(SolidSyslogTlsStream, SendReturnsFalseWhenWriteFails) +{ + SolidSyslogStream_Open(stream, addr); + OpenSslFake_SetWriteFails(true); + const char msg[] = "hi"; + CHECK_FALSE(SolidSyslogStream_Send(stream, msg, sizeof(msg))); +} + +TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenTransportOpenFails) +{ + StreamFake_SetOpenFails(transport, true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, OpenSkipsSslSetupWhenTransportOpenFails) +{ + StreamFake_SetOpenFails(transport, true); + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(0, OpenSslFake_CtxNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenWiresBioCtrlCallback) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(OpenSslFake_LastBioCtrlCallback() != nullptr); +} + +TEST(SolidSyslogTlsStream, OpenWiresBioCreateCallback) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(OpenSslFake_LastBioCreateCallback() != nullptr); +} + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsSuccessForFlush) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + LONGS_EQUAL(1, ctrlFn(OpenSslFake_LastBioReturned(), BIO_CTRL_FLUSH, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsSuccessForPushPopDup) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + BIO* bio = OpenSslFake_LastBioReturned(); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_PUSH, 0, nullptr)); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_POP, 0, nullptr)); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_DUP, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsFailureForUnknownCommand) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + LONGS_EQUAL(0, ctrlFn(OpenSslFake_LastBioReturned(), /* arbitrary unsupported cmd */ 9999, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCreateCallbackMarksBioInitialised) +{ + SolidSyslogStream_Open(stream, addr); + auto createFn = OpenSslFake_LastBioCreateCallback(); + createFn(OpenSslFake_LastBioReturned()); + LONGS_EQUAL(1, OpenSslFake_LastSetInitArg()); +} diff --git a/Tests/StreamFake.c b/Tests/StreamFake.c new file mode 100644 index 00000000..bbedc6f8 --- /dev/null +++ b/Tests/StreamFake.c @@ -0,0 +1,122 @@ +#include "StreamFake.h" +#include "SolidSyslogStreamDefinition.h" + +#include + +struct StreamFake +{ + struct SolidSyslogStream base; + int openCallCount; + const struct SolidSyslogAddress* lastOpenAddr; + bool openFails; + int sendCallCount; + const void* lastSendBuf; + size_t lastSendSize; + int readCallCount; + void* lastReadBuf; + size_t lastReadSize; + SolidSyslogSsize readReturn; + int closeCallCount; +}; + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->openCallCount++; + fake->lastOpenAddr = addr; + return !fake->openFails; +} + +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->sendCallCount++; + fake->lastSendBuf = buffer; + fake->lastSendSize = size; + return true; +} + +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->readCallCount++; + fake->lastReadBuf = buffer; + fake->lastReadSize = size; + return fake->readReturn; +} + +static void Close(struct SolidSyslogStream* self) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->closeCallCount++; +} + +struct SolidSyslogStream* StreamFake_Create(void) +{ + struct StreamFake* fake = (struct StreamFake*) calloc(1, sizeof(struct StreamFake)); + fake->base.Open = Open; + fake->base.Send = Send; + fake->base.Read = Read; + fake->base.Close = Close; + return &fake->base; +} + +void StreamFake_Destroy(struct SolidSyslogStream* stream) +{ + free(stream); +} + +int StreamFake_OpenCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->openCallCount; +} + +const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastOpenAddr; +} + +int StreamFake_SendCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->sendCallCount; +} + +const void* StreamFake_LastSendBuf(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastSendBuf; +} + +size_t StreamFake_LastSendSize(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastSendSize; +} + +int StreamFake_ReadCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->readCallCount; +} + +void* StreamFake_LastReadBuf(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastReadBuf; +} + +size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastReadSize; +} + +void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, SolidSyslogSsize value) +{ + ((struct StreamFake*) stream)->readReturn = value; +} + +void StreamFake_SetOpenFails(struct SolidSyslogStream* stream, bool fails) +{ + ((struct StreamFake*) stream)->openFails = fails; +} + +int StreamFake_CloseCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->closeCallCount; +} diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h new file mode 100644 index 00000000..0a53aa60 --- /dev/null +++ b/Tests/StreamFake.h @@ -0,0 +1,31 @@ +#ifndef STREAMFAKE_H +#define STREAMFAKE_H + +#include "ExternC.h" +#include "SolidSyslogStream.h" + +#include +#include + +EXTERN_C_BEGIN + + struct SolidSyslogStream; + struct SolidSyslogAddress; + + struct SolidSyslogStream* StreamFake_Create(void); + void StreamFake_Destroy(struct SolidSyslogStream * stream); + int StreamFake_OpenCallCount(struct SolidSyslogStream * stream); + const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream * stream); + int StreamFake_SendCallCount(struct SolidSyslogStream * stream); + const void* StreamFake_LastSendBuf(struct SolidSyslogStream * stream); + size_t StreamFake_LastSendSize(struct SolidSyslogStream * stream); + int StreamFake_ReadCallCount(struct SolidSyslogStream * stream); + void* StreamFake_LastReadBuf(struct SolidSyslogStream * stream); + size_t StreamFake_LastReadSize(struct SolidSyslogStream * stream); + void StreamFake_SetReadReturn(struct SolidSyslogStream * stream, SolidSyslogSsize value); + void StreamFake_SetOpenFails(struct SolidSyslogStream * stream, bool fails); + int StreamFake_CloseCallCount(struct SolidSyslogStream * stream); + +EXTERN_C_END + +#endif /* STREAMFAKE_H */ diff --git a/Tests/StreamFakeTest.cpp b/Tests/StreamFakeTest.cpp new file mode 100644 index 00000000..0ac32633 --- /dev/null +++ b/Tests/StreamFakeTest.cpp @@ -0,0 +1,109 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogAddress.h" +#include "SolidSyslogStream.h" +#include "StreamFake.h" + +// clang-format off +TEST_GROUP(StreamFake) +{ + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + struct SolidSyslogStream* stream = nullptr; + + // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle + void setup() override { stream = StreamFake_Create(); } + void teardown() override { StreamFake_Destroy(stream); } +}; + +// clang-format on + +TEST(StreamFake, CreateSucceeds) +{ + CHECK_TRUE(stream != nullptr); +} + +TEST(StreamFake, OpenIncrementsCount) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, StreamFake_OpenCallCount(stream)); +} + +TEST(StreamFake, OpenCapturesAddr) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(addr, StreamFake_LastOpenAddr(stream)); +} + +TEST(StreamFake, SendIncrementsCount) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + LONGS_EQUAL(1, StreamFake_SendCallCount(stream)); +} + +TEST(StreamFake, SendCapturesBuffer) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + POINTERS_EQUAL(payload, StreamFake_LastSendBuf(stream)); +} + +TEST(StreamFake, SendCapturesSize) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + LONGS_EQUAL(sizeof(payload), StreamFake_LastSendSize(stream)); +} + +TEST(StreamFake, ReadIncrementsCount) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(1, StreamFake_ReadCallCount(stream)); +} + +TEST(StreamFake, ReadCapturesBuffer) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, StreamFake_LastReadBuf(stream)); +} + +TEST(StreamFake, ReadCapturesSize) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), StreamFake_LastReadSize(stream)); +} + +TEST(StreamFake, ReadReturnsConfiguredValue) +{ + StreamFake_SetReadReturn(stream, 5); + char buf[16]; + SolidSyslogSsize n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(5, n); +} + +TEST(StreamFake, CloseIncrementsCount) +{ + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, StreamFake_CloseCallCount(stream)); +} + +TEST(StreamFake, OpenDefaultsToSuccess) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(StreamFake, SetOpenFailsMakesOpenReturnFalse) +{ + StreamFake_SetOpenFails(stream, true); + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index e37864fa..052303ab 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -1,12 +1,10 @@ if(SOLIDSYSLOG_POSIX) - set(POSIX_FAKES_SOURCES + add_library(PosixFakes STATIC ClockFake.c SafeStringStandard.c SocketFake.c ) - add_library(PosixFakes STATIC ${POSIX_FAKES_SOURCES}) - target_include_directories(PosixFakes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(PosixFakes PUBLIC ${PROJECT_NAME}) @@ -19,3 +17,19 @@ if(SOLIDSYSLOG_WINSOCK) target_link_libraries(WinsockFakes PUBLIC ${PROJECT_NAME}) endif() + +# OpenSSL fake is orthogonal to platform — link-time substitutes libssl symbols +# so the library's TLS code can be test-driven without real OpenSSL. Any target +# linking OpenSslFakes inherits the OpenSSL include path, which is needed +# explicitly on systems where OpenSSL isn't on the default compiler search +# path (e.g. Windows + vcpkg). +if(SOLIDSYSLOG_OPENSSL) + add_library(OpenSslFakes STATIC OpenSslFake.c) + + target_include_directories(OpenSslFakes PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${OPENSSL_INCLUDE_DIR} + ) + + target_link_libraries(OpenSslFakes PUBLIC ${PROJECT_NAME}) +endif() diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c new file mode 100644 index 00000000..b083a9bd --- /dev/null +++ b/Tests/Support/OpenSslFake.c @@ -0,0 +1,612 @@ +#include "OpenSslFake.h" + +#include +#include +#include + +/* ------------------------------------------------------------------------- + * Captured state — one section per OpenSSL API call. Tests read these via + * accessors below; production reaches libssl through the link-interposed + * functions at the bottom of the file. + * ------------------------------------------------------------------------- */ + +/* Sentinel storage for opaque OpenSSL types — our fake returns stable + * pointers to these so tests can assert pointer-chain plumbing with + * POINTERS_EQUAL. */ +static char fakeCtxStorage; +static char fakeMethodStorage; +static char fakeSslStorage; +static char fakeBioMethStorage; +static char fakeBioStorage; + +/* SSL_CTX_new */ +static int ctxNewCallCount; +static const SSL_METHOD* lastCtxNewMethodArg; + +/* SSL_CTX_load_verify_locations */ +static SSL_CTX* lastLoadVerifyLocationsCtxArg; +static const char* lastCaBundlePath; + +/* SSL_CTX_set_verify */ +static SSL_CTX* lastSetVerifyCtxArg; +static int lastVerifyMode; + +/* SSL_CTX_ctrl (SET_MIN_PROTO_VERSION) */ +static SSL_CTX* lastSslCtxCtrlCtxArg; +static long lastMinProtoVersion; + +/* SSL_new */ +static int sslNewCallCount; +static SSL_CTX* lastSslNewCtxArg; + +/* BIO_meth_set_read / BIO_meth_set_write */ +static BIO_METHOD* lastBioMethSetReadMethodArg; +static int (*lastBioReadCallback)(BIO*, char*, int); +static long (*lastBioCtrlCallback)(BIO*, int, long, void*); +static int (*lastBioCreateCallback)(BIO*); +static int lastSetInitArg; +static BIO_METHOD* lastBioMethSetWriteMethodArg; +static int (*lastBioWriteCallback)(BIO*, const char*, int); + +/* BIO_new */ +static int bioNewCallCount; +static const BIO_METHOD* lastBioNewMethodArg; + +/* BIO_set_data / BIO_get_data */ +static BIO* lastSetDataBioArg; +static void* lastSetDataArg; +static BIO* lastGetDataBioArg; + +/* SSL_set_bio */ +static int setBioCallCount; +static SSL* lastSetBioSslArg; +static BIO* lastSetBioReadBioArg; +static BIO* lastSetBioWriteBioArg; + +/* SSL_ctrl (SET_TLSEXT_HOSTNAME) */ +static SSL* lastSslCtrlSslArg; +static const char* lastSniHostname; + +/* SSL_set1_host */ +static SSL* lastSet1HostSslArg; +static const char* lastSet1Host; + +/* SSL_connect */ +static int connectCallCount; +static SSL* lastConnectSslArg; +static bool connectFails; + +/* SSL_write */ +static int writeCallCount; +static SSL* lastWriteSslArg; +static const void* lastWriteBuf; +static int lastWriteSize; +static bool writeFails; + +/* SSL_read */ +static int sslReadCallCount; +static SSL* lastSslReadSslArg; +static void* lastSslReadBuf; +static int lastSslReadSize; + +/* SSL_shutdown */ +static int shutdownCallCount; +static SSL* lastShutdownSslArg; + +/* SSL_free */ +static int freeCallCount; +static SSL* lastFreeSslArg; + +/* SSL_CTX_free */ +static int ctxFreeCallCount; +static SSL_CTX* lastCtxFreeCtxArg; + +/* ------------------------------------------------------------------------- + * Reset — zero every captured value. + * ------------------------------------------------------------------------- */ + +void OpenSslFake_Reset(void) +{ + ctxNewCallCount = 0; + lastCtxNewMethodArg = NULL; + lastLoadVerifyLocationsCtxArg = NULL; + lastCaBundlePath = NULL; + lastSetVerifyCtxArg = NULL; + lastVerifyMode = 0; + lastSslCtxCtrlCtxArg = NULL; + lastMinProtoVersion = 0; + sslNewCallCount = 0; + lastSslNewCtxArg = NULL; + lastBioMethSetReadMethodArg = NULL; + lastBioReadCallback = NULL; + lastBioCtrlCallback = NULL; + lastBioCreateCallback = NULL; + lastSetInitArg = 0; + lastBioMethSetWriteMethodArg = NULL; + lastBioWriteCallback = NULL; + bioNewCallCount = 0; + lastBioNewMethodArg = NULL; + lastSetDataBioArg = NULL; + lastSetDataArg = NULL; + lastGetDataBioArg = NULL; + setBioCallCount = 0; + lastSetBioSslArg = NULL; + lastSetBioReadBioArg = NULL; + lastSetBioWriteBioArg = NULL; + lastSslCtrlSslArg = NULL; + lastSniHostname = NULL; + lastSet1HostSslArg = NULL; + lastSet1Host = NULL; + connectCallCount = 0; + lastConnectSslArg = NULL; + connectFails = false; + writeCallCount = 0; + lastWriteSslArg = NULL; + lastWriteBuf = NULL; + lastWriteSize = 0; + writeFails = false; + sslReadCallCount = 0; + lastSslReadSslArg = NULL; + lastSslReadBuf = NULL; + lastSslReadSize = 0; + shutdownCallCount = 0; + lastShutdownSslArg = NULL; + freeCallCount = 0; + lastFreeSslArg = NULL; + ctxFreeCallCount = 0; + lastCtxFreeCtxArg = NULL; +} + +/* ------------------------------------------------------------------------- + * Accessors — grouped by the OpenSSL function they describe. + * ------------------------------------------------------------------------- */ + +int OpenSslFake_CtxNewCallCount(void) +{ + return ctxNewCallCount; +} + +const SSL_METHOD* OpenSslFake_LastCtxNewMethodArg(void) +{ + return lastCtxNewMethodArg; +} + +SSL_CTX* OpenSslFake_LastCtxReturned(void) +{ + return (SSL_CTX*) &fakeCtxStorage; +} + +SSL_CTX* OpenSslFake_LastLoadVerifyLocationsCtxArg(void) +{ + return lastLoadVerifyLocationsCtxArg; +} + +const char* OpenSslFake_LastCaBundlePath(void) +{ + return lastCaBundlePath; +} + +SSL_CTX* OpenSslFake_LastSetVerifyCtxArg(void) +{ + return lastSetVerifyCtxArg; +} + +int OpenSslFake_LastVerifyMode(void) +{ + return lastVerifyMode; +} + +SSL_CTX* OpenSslFake_LastSslCtxCtrlCtxArg(void) +{ + return lastSslCtxCtrlCtxArg; +} + +long OpenSslFake_LastMinProtoVersion(void) +{ + return lastMinProtoVersion; +} + +int OpenSslFake_SslNewCallCount(void) +{ + return sslNewCallCount; +} + +SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) +{ + return lastSslNewCtxArg; +} + +SSL* OpenSslFake_LastSslReturned(void) +{ + return (SSL*) &fakeSslStorage; +} + +BIO_METHOD* OpenSslFake_LastBioMethReturned(void) +{ + return (BIO_METHOD*) &fakeBioMethStorage; +} + +BIO_METHOD* OpenSslFake_LastBioMethSetReadMethodArg(void) +{ + return lastBioMethSetReadMethodArg; +} + +int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) +{ + return lastBioReadCallback; +} + +long (*OpenSslFake_LastBioCtrlCallback(void))(BIO*, int, long, void*) +{ + return lastBioCtrlCallback; +} + +int (*OpenSslFake_LastBioCreateCallback(void))(BIO*) +{ + return lastBioCreateCallback; +} + +int OpenSslFake_LastSetInitArg(void) +{ + return lastSetInitArg; +} + +BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) +{ + return lastBioMethSetWriteMethodArg; +} + +int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) +{ + return lastBioWriteCallback; +} + +int OpenSslFake_BioNewCallCount(void) +{ + return bioNewCallCount; +} + +const BIO_METHOD* OpenSslFake_LastBioNewMethodArg(void) +{ + return lastBioNewMethodArg; +} + +BIO* OpenSslFake_LastBioReturned(void) +{ + return (BIO*) &fakeBioStorage; +} + +BIO* OpenSslFake_LastSetDataBioArg(void) +{ + return lastSetDataBioArg; +} + +void* OpenSslFake_LastSetDataArg(void) +{ + return lastSetDataArg; +} + +BIO* OpenSslFake_LastGetDataBioArg(void) +{ + return lastGetDataBioArg; +} + +int OpenSslFake_SetBioCallCount(void) +{ + return setBioCallCount; +} + +SSL* OpenSslFake_LastSetBioSslArg(void) +{ + return lastSetBioSslArg; +} + +BIO* OpenSslFake_LastSetBioReadBioArg(void) +{ + return lastSetBioReadBioArg; +} + +BIO* OpenSslFake_LastSetBioWriteBioArg(void) +{ + return lastSetBioWriteBioArg; +} + +SSL* OpenSslFake_LastSslCtrlSslArg(void) +{ + return lastSslCtrlSslArg; +} + +const char* OpenSslFake_LastSniHostname(void) +{ + return lastSniHostname; +} + +SSL* OpenSslFake_LastSet1HostSslArg(void) +{ + return lastSet1HostSslArg; +} + +const char* OpenSslFake_LastSet1Host(void) +{ + return lastSet1Host; +} + +int OpenSslFake_ConnectCallCount(void) +{ + return connectCallCount; +} + +SSL* OpenSslFake_LastConnectSslArg(void) +{ + return lastConnectSslArg; +} + +int OpenSslFake_WriteCallCount(void) +{ + return writeCallCount; +} + +SSL* OpenSslFake_LastWriteSslArg(void) +{ + return lastWriteSslArg; +} + +const void* OpenSslFake_LastWriteBuf(void) +{ + return lastWriteBuf; +} + +int OpenSslFake_LastWriteSize(void) +{ + return lastWriteSize; +} + +int OpenSslFake_SslReadCallCount(void) +{ + return sslReadCallCount; +} + +SSL* OpenSslFake_LastSslReadSslArg(void) +{ + return lastSslReadSslArg; +} + +void* OpenSslFake_LastSslReadBuf(void) +{ + return lastSslReadBuf; +} + +int OpenSslFake_LastSslReadSize(void) +{ + return lastSslReadSize; +} + +int OpenSslFake_ShutdownCallCount(void) +{ + return shutdownCallCount; +} + +SSL* OpenSslFake_LastShutdownSslArg(void) +{ + return lastShutdownSslArg; +} + +int OpenSslFake_FreeCallCount(void) +{ + return freeCallCount; +} + +SSL* OpenSslFake_LastFreeSslArg(void) +{ + return lastFreeSslArg; +} + +int OpenSslFake_CtxFreeCallCount(void) +{ + return ctxFreeCallCount; +} + +SSL_CTX* OpenSslFake_LastCtxFreeCtxArg(void) +{ + return lastCtxFreeCtxArg; +} + +/* ------------------------------------------------------------------------- + * Link-time substitution for OpenSSL — replaces libssl symbols in the test + * binary. Production links real libssl; tests never link -lssl. + * Each function records its args for test assertion. Return values are + * plausible-success stubs; where behaviour needs switching for failure-path + * tests, add a setter (e.g. OpenSslFake_SetConnectFails) in a later cycle. + * ------------------------------------------------------------------------- */ + +const SSL_METHOD* TLS_client_method(void) +{ + return (const SSL_METHOD*) &fakeMethodStorage; +} + +SSL_CTX* SSL_CTX_new(const SSL_METHOD* method) +{ + ctxNewCallCount++; + lastCtxNewMethodArg = method; + return (SSL_CTX*) &fakeCtxStorage; +} + +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API +int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) +{ + (void) CApath; + lastLoadVerifyLocationsCtxArg = ctx; + lastCaBundlePath = CAfile; + return 1; +} + +void SSL_CTX_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb verify_callback) +{ + (void) verify_callback; + lastSetVerifyCtxArg = ctx; + lastVerifyMode = mode; +} + +/* SSL_CTX_set_min_proto_version is a macro forwarding to SSL_CTX_ctrl; fake + * intercepts the ctrl call for the min-proto command only. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API +long SSL_CTX_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) +{ + (void) parg; + lastSslCtxCtrlCtxArg = ctx; + if (cmd == SSL_CTRL_SET_MIN_PROTO_VERSION) + { + lastMinProtoVersion = larg; + } + return 1; +} + +SSL* SSL_new(SSL_CTX* ctx) +{ + sslNewCallCount++; + lastSslNewCtxArg = ctx; + return (SSL*) &fakeSslStorage; +} + +BIO_METHOD* BIO_meth_new(int type, const char* name) +{ + (void) type; + (void) name; + return (BIO_METHOD*) &fakeBioMethStorage; +} + +int BIO_meth_set_read(BIO_METHOD* biom, int (*read)(BIO*, char*, int)) +{ + lastBioMethSetReadMethodArg = biom; + lastBioReadCallback = read; + return 1; +} + +int BIO_meth_set_write(BIO_METHOD* biom, int (*write)(BIO*, const char*, int)) +{ + lastBioMethSetWriteMethodArg = biom; + lastBioWriteCallback = write; + return 1; +} + +int BIO_meth_set_ctrl(BIO_METHOD* biom, long (*ctrl)(BIO*, int, long, void*)) +{ + (void) biom; + lastBioCtrlCallback = ctrl; + return 1; +} + +int BIO_meth_set_create(BIO_METHOD* biom, int (*create)(BIO*)) +{ + (void) biom; + lastBioCreateCallback = create; + return 1; +} + +void BIO_set_init(BIO* a, int init) +{ + (void) a; + lastSetInitArg = init; +} + +BIO* BIO_new(const BIO_METHOD* type) +{ + bioNewCallCount++; + lastBioNewMethodArg = type; + return (BIO*) &fakeBioStorage; +} + +void BIO_set_data(BIO* a, void* ptr) +{ + lastSetDataBioArg = a; + lastSetDataArg = ptr; +} + +void* BIO_get_data(BIO* a) +{ + lastGetDataBioArg = a; + return lastSetDataArg; +} + +void SSL_set_bio(SSL* ssl, BIO* rbio, BIO* wbio) +{ + setBioCallCount++; + lastSetBioSslArg = ssl; + lastSetBioReadBioArg = rbio; + lastSetBioWriteBioArg = wbio; +} + +/* SSL_set_tlsext_host_name is a macro forwarding to SSL_ctrl; fake intercepts + * the SET_TLSEXT_HOSTNAME command and captures the hostname pointer. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API +long SSL_ctrl(SSL* ssl, int cmd, long larg, void* parg) +{ + (void) larg; + lastSslCtrlSslArg = ssl; + if (cmd == SSL_CTRL_SET_TLSEXT_HOSTNAME) + { + lastSniHostname = (const char*) parg; + } + return 1; +} + +int SSL_set1_host(SSL* ssl, const char* hostname) +{ + lastSet1HostSslArg = ssl; + lastSet1Host = hostname; + return 1; +} + +int SSL_connect(SSL* ssl) +{ + connectCallCount++; + lastConnectSslArg = ssl; + return connectFails ? -1 : 1; +} + +void OpenSslFake_SetConnectFails(bool fails) +{ + connectFails = fails; +} + +int SSL_write(SSL* ssl, const void* buf, int num) +{ + writeCallCount++; + lastWriteSslArg = ssl; + lastWriteBuf = buf; + lastWriteSize = num; + return writeFails ? -1 : num; +} + +void OpenSslFake_SetWriteFails(bool fails) +{ + writeFails = fails; +} + +int SSL_read(SSL* ssl, void* buf, int num) +{ + sslReadCallCount++; + lastSslReadSslArg = ssl; + lastSslReadBuf = buf; + lastSslReadSize = num; + return num; +} + +int SSL_shutdown(SSL* ssl) +{ + shutdownCallCount++; + lastShutdownSslArg = ssl; + return 1; +} + +void SSL_free(SSL* ssl) +{ + freeCallCount++; + lastFreeSslArg = ssl; +} + +void SSL_CTX_free(SSL_CTX* ctx) +{ + ctxFreeCallCount++; + lastCtxFreeCtxArg = ctx; +} diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h new file mode 100644 index 00000000..dc1e5b96 --- /dev/null +++ b/Tests/Support/OpenSslFake.h @@ -0,0 +1,115 @@ +#ifndef OPENSSLFAKE_H +#define OPENSSLFAKE_H + +#include "ExternC.h" + +#include + +/* Forward-declared OpenSSL types — full definitions live in . */ +struct ssl_ctx_st; +struct ssl_st; +struct ssl_method_st; +struct bio_st; +struct bio_method_st; + +EXTERN_C_BEGIN + + void OpenSslFake_Reset(void); + + /* Failure-mode switches */ + void OpenSslFake_SetConnectFails(bool fails); + void OpenSslFake_SetWriteFails(bool fails); + + /* SSL_CTX_new */ + int OpenSslFake_CtxNewCallCount(void); + const struct ssl_method_st* OpenSslFake_LastCtxNewMethodArg(void); + struct ssl_ctx_st* OpenSslFake_LastCtxReturned(void); + + /* SSL_CTX_load_verify_locations */ + struct ssl_ctx_st* OpenSslFake_LastLoadVerifyLocationsCtxArg(void); + const char* OpenSslFake_LastCaBundlePath(void); + + /* SSL_CTX_set_verify */ + struct ssl_ctx_st* OpenSslFake_LastSetVerifyCtxArg(void); + int OpenSslFake_LastVerifyMode(void); + + /* SSL_CTX_ctrl (SET_MIN_PROTO_VERSION path) */ + struct ssl_ctx_st* OpenSslFake_LastSslCtxCtrlCtxArg(void); + long OpenSslFake_LastMinProtoVersion(void); + + /* SSL_new */ + int OpenSslFake_SslNewCallCount(void); + struct ssl_ctx_st* OpenSslFake_LastSslNewCtxArg(void); + struct ssl_st* OpenSslFake_LastSslReturned(void); + + /* BIO_meth_new */ + struct bio_method_st* OpenSslFake_LastBioMethReturned(void); + + /* BIO_meth_set_read */ + struct bio_method_st* OpenSslFake_LastBioMethSetReadMethodArg(void); + int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); + long (*OpenSslFake_LastBioCtrlCallback(void))(struct bio_st*, int, long, void*); + int (*OpenSslFake_LastBioCreateCallback(void))(struct bio_st*); + int OpenSslFake_LastSetInitArg(void); + + /* BIO_meth_set_write */ + struct bio_method_st* OpenSslFake_LastBioMethSetWriteMethodArg(void); + int (*OpenSslFake_LastBioWriteCallback(void))(struct bio_st*, const char*, int); + + /* BIO_new */ + int OpenSslFake_BioNewCallCount(void); + const struct bio_method_st* OpenSslFake_LastBioNewMethodArg(void); + struct bio_st* OpenSslFake_LastBioReturned(void); + + /* BIO_set_data */ + struct bio_st* OpenSslFake_LastSetDataBioArg(void); + void* OpenSslFake_LastSetDataArg(void); + + /* BIO_get_data (arg capture for BIO-callback introspection) */ + struct bio_st* OpenSslFake_LastGetDataBioArg(void); + + /* SSL_set_bio */ + int OpenSslFake_SetBioCallCount(void); + struct ssl_st* OpenSslFake_LastSetBioSslArg(void); + struct bio_st* OpenSslFake_LastSetBioReadBioArg(void); + struct bio_st* OpenSslFake_LastSetBioWriteBioArg(void); + + /* SSL_ctrl (SET_TLSEXT_HOSTNAME path — SNI) */ + struct ssl_st* OpenSslFake_LastSslCtrlSslArg(void); + const char* OpenSslFake_LastSniHostname(void); + + /* SSL_set1_host */ + struct ssl_st* OpenSslFake_LastSet1HostSslArg(void); + const char* OpenSslFake_LastSet1Host(void); + + /* SSL_connect */ + int OpenSslFake_ConnectCallCount(void); + struct ssl_st* OpenSslFake_LastConnectSslArg(void); + + /* SSL_write */ + int OpenSslFake_WriteCallCount(void); + struct ssl_st* OpenSslFake_LastWriteSslArg(void); + const void* OpenSslFake_LastWriteBuf(void); + int OpenSslFake_LastWriteSize(void); + + /* SSL_read */ + int OpenSslFake_SslReadCallCount(void); + struct ssl_st* OpenSslFake_LastSslReadSslArg(void); + void* OpenSslFake_LastSslReadBuf(void); + int OpenSslFake_LastSslReadSize(void); + + /* SSL_shutdown */ + int OpenSslFake_ShutdownCallCount(void); + struct ssl_st* OpenSslFake_LastShutdownSslArg(void); + + /* SSL_free */ + int OpenSslFake_FreeCallCount(void); + struct ssl_st* OpenSslFake_LastFreeSslArg(void); + + /* SSL_CTX_free */ + int OpenSslFake_CtxFreeCallCount(void); + struct ssl_ctx_st* OpenSslFake_LastCtxFreeCtxArg(void); + +EXTERN_C_END + +#endif /* OPENSSLFAKE_H */ diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 9f321773..0405770e 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -52,6 +52,13 @@ static int lastSetSockOptOptname; static int closeCallCount; static int lastClosedFd; +static int recvCallCount; +static ssize_t recvReturn; +static int lastRecvFd; +static const void* lastRecvBuf; +static size_t lastRecvLen; +static int lastRecvFlags; + static char lastAddrString[INET_ADDRSTRLEN]; static bool getAddrInfoFails; @@ -97,6 +104,12 @@ void SocketFake_Reset(void) lastSocketType = 0; closeCallCount = 0; lastClosedFd = -1; + recvCallCount = 0; + recvReturn = 0; + lastRecvFd = -1; + lastRecvBuf = NULL; + lastRecvLen = 0; + lastRecvFlags = 0; lastAddrString[0] = '\0'; getAddrInfoFails = false; @@ -309,6 +322,40 @@ int SocketFake_LastClosedFd(void) return lastClosedFd; } +/* recv configuration */ + +void SocketFake_SetRecvReturn(ssize_t value) +{ + recvReturn = value; +} + +/* recv accessors */ + +int SocketFake_RecvCallCount(void) +{ + return recvCallCount; +} + +int SocketFake_LastRecvFd(void) +{ + return lastRecvFd; +} + +const void* SocketFake_LastRecvBuf(void) +{ + return lastRecvBuf; +} + +size_t SocketFake_LastRecvLen(void) +{ + return lastRecvLen; +} + +int SocketFake_LastRecvFlags(void) +{ + return lastRecvFlags; +} + /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails) @@ -429,6 +476,19 @@ int close(int fd) return 0; } +// clang-format off +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; names differ from glibc internal names +ssize_t recv(int sockfd, void* buf, size_t len, int flags) +// clang-format on +{ + recvCallCount++; + lastRecvFd = sockfd; + lastRecvBuf = buf; + lastRecvLen = len; + lastRecvFlags = flags; + return recvReturn; +} + // clang-format off // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; parameter names differ from glibc internal names int getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res) diff --git a/Tests/Support/SocketFake.h b/Tests/Support/SocketFake.h index ea98d288..d57bd0bc 100644 --- a/Tests/Support/SocketFake.h +++ b/Tests/Support/SocketFake.h @@ -63,6 +63,16 @@ EXTERN_C_BEGIN int SocketFake_CloseCallCount(void); int SocketFake_LastClosedFd(void); + /* recv configuration */ + void SocketFake_SetRecvReturn(ssize_t value); + + /* recv accessors */ + int SocketFake_RecvCallCount(void); + int SocketFake_LastRecvFd(void); + const void* SocketFake_LastRecvBuf(void); + size_t SocketFake_LastRecvLen(void); + int SocketFake_LastRecvFlags(void); + /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails); diff --git a/ci/docker-compose.bdd.yml b/ci/docker-compose.bdd.yml index 14ae45a7..08bbdffc 100644 --- a/ci/docker-compose.bdd.yml +++ b/ci/docker-compose.bdd.yml @@ -3,6 +3,7 @@ services: image: balabit/syslog-ng:latest volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf + - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro - bdd-output:/var/log/syslog-ng - syslog-ng-ctl:/var/lib/syslog-ng entrypoint: ["bash", "-c"]