Commit Graph

28607 Commits

Author SHA1 Message Date
Isaac Connor
cd3aaff0fd feat: add ZM_LOG_BROWSER_EXTENSIONS config to optionally log extension errors refs #3340
#4914 unconditionally drops Javascript errors and CSP violations sourced from
browser extensions. Some operators want to know when a plugin is touching the
ZoneMinder tab, so gate the suppression behind a config entry.

Add ZM_LOG_BROWSER_EXTENSIONS (boolean, default no) in the logging category.
It is exposed to JS by the existing non-private-config loop in skin.js.php as
the string '0'/'1' (the same convention as ZM_LOG_INJECT), so logger.js only
filters extension sources when it is '0'. Default behaviour is unchanged:
extension noise stays out of the log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 20:33:15 -04:00
Isaac Connor
cb56b810ab Merge pull request #4914 from SteveGilvarry/3340-filter-extension-csp-log
fix: don't log browser-extension errors and CSP violations (fixes #3340)
2026-06-18 20:29:44 -04:00
Isaac Connor
071516240e Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-06-17 19:30:48 -04:00
Isaac Connor
01de3fd7d5 Merge pull request #4938 from SteveGilvarry/4936-zms-sigfpe-buffer-level
fix: guard zms buffer_level against zero buffer count
2026-06-17 19:19:33 -04:00
Isaac Connor
c102f1ec02 fix: pre-create ZMREPO rpm target dirs before rsync deploy
The build-rpm-packages workflows deploy with easingthemes/ssh-deploy using
rsync args -rltgoDzvO, which does not create missing parent directories on
the remote. When the zmrepo directory tree was deleted the deploy step
failed.

--mkpath is not a viable fix: it is parsed by the local rsync in the build
container, and Rocky 8 ships rsync 3.1.3 which predates the flag (3.2.3+).

Add a pre-deploy step that creates rpm/master/<family>/<releasever>/<arch>/
over ssh with mkdir -p, which has no rsync version dependency. Also add
utils/zmrepo_mkdirs.sh to recreate the full tree manually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:18:23 -04:00
SteveGilvarry
629842c938 fix: start zms command processor after buffer state is initialised refs #4936
The command_processor thread was started in runStream() before
temp_image_buffer_count, the read/write indices and temp_image_buffer were
assigned, so a command arriving in that window observed a half-initialised
stream (the root cause behind the divide-by-zero guarded in the previous
commit). Keep the thread object declared up front for the join(), but defer
its launch until after the playback_buffer setup so processCommand can only
run against fully initialised state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:56:11 +10:00
Isaac Connor
fee4aa52bf Merge pull request #4930 from SteveGilvarry/codeql-int-overflow-camera-size
fix: compute camera frame buffer sizes in size_t to avoid int overflow
2026-06-17 18:33:10 -04:00
Isaac Connor
281d87a1f4 Merge pull request #4935 from SteveGilvarry/codeql-ignore-vendored-js
ci: exclude vendored third-party JavaScript from CodeQL analysis
2026-06-17 18:28:22 -04:00
Steve Gilvarry
dc4cf32a63 fix: guard zms buffer_level against zero buffer count refs #4936
MonitorStream::processCommand computed buffer_level by dividing by
temp_image_buffer_count (and via MOD_ADD modulo) while only guarding on
playback_buffer. processCommand runs on the command_processor thread,
started in runStream before temp_image_buffer_count is assigned from
playback_buffer. In that window temp_image_buffer_count is still 0 while
playback_buffer is already > 0, so a command arriving then divided by
zero and raised SIGFPE, crashing nph-zms.

Extract the percentage math into MonitorStreamBufferLevel which returns 0
when the buffer count is not positive, and add Catch2 coverage for the
zero-count and wrap-around cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 08:21:29 +10:00
Isaac Connor
9ba5eafcc1 Merge pull request #4931 from SteveGilvarry/codeql-js-dom-xss
fix: resolve CodeQL DOM-XSS and unsafe-sanitizer findings in skin/monitor JS
2026-06-17 18:21:20 -04:00
Isaac Connor
295673eb97 fix: reload event when DefaultVideo is stale incomplete.mp4
When an event closes the recording file is renamed from incomplete.* to
<Id>-video.* and the DB row is updated. A stale Event model still reports
DefaultVideo=incomplete.mp4, producing spurious 'File does not exist'
warnings. When the incomplete file is missing, clear the object cache and
re-read the event from the database to check the finished file before
warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:56:11 -04:00
SteveGilvarry
45099e53a7 fix: address review feedback on camera buffer overflow fix
- zm_libvnc_camera.cpp resize(): return FALSE and log if av_malloc fails,
  rather than returning TRUE with a null frameBuffer (libVNC would then
  write into it and crash). Matters more now that size_t sizing can
  request large allocations for server-advertised dimensions.
- zm_libvnc_camera.cpp: compute the buffer sizes in the scale Debug() in
  size_t with %zu, so the logged sizes can't overflow int and match the
  values passed to SWScale::Convert.
- zm_libvlc_camera: widen LibvlcPrivateData::bufferSize to size_t and
  compute it in size_t in PrimeCapture, so the allocation itself can't
  overflow. Image::Assign now passes the same stored bufferSize used for
  the allocation, so the read size can't exceed the buffer. Widen the
  compare loop index to size_t to match.

Verified: both translation units compile with -Werror -fsyntax-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 07:54:15 +10:00
SteveGilvarry
23710249a4 fix: tighten control id validation and drop unused catch binding
Address PR #4931 review feedback:
- monitor.js ControlEdit_onClick: parse ControlId as an integer and only
  navigate when it is a positive integer, instead of URL-encoding the raw
  select value.
- skin.js .zmlink handler: drop the unused binding from the catch clause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 07:48:34 +10:00
SteveGilvarry
c47dc991cb ci: exclude vendored third-party JavaScript from CodeQL analysis
CodeQL's open alerts are dominated by findings inside bundled third-party
libraries (jQuery UI, Bootstrap 4, bootstrap-table, the jQuery UI
timepicker addon, hls.js). These flag coding patterns internal to those
libraries -- js/unsafe-jquery-plugin, js/insecure-randomness, etc. -- that
are not ZoneMinder bugs and cannot be fixed without forking the
dependencies. They drown out findings in ZoneMinder-authored code.

Add the vendored library directories/files to paths-ignore in the CodeQL
config. ZoneMinder-authored files in these trees (skin.js,
MonitorStream.js, views/js/*.js, ...) are not listed and remain analysed.

moment.js is intentionally left out: it is scheduled for removal once its
remaining call sites migrate to luxon, so its alert will be resolved by
deletion rather than suppression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 07:42:41 +10:00
Isaac Connor
c490ce6194 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-17 15:39:15 -04:00
Isaac Connor
ff96db6b77 Add mkpath 2026-06-17 10:53:19 -04:00
Isaac Connor
4ea5fee4aa Merge pull request #4934 from connortechnology/4921-authhash-cache-key-collision
fix: stop IP-less auth hash from poisoning the IP-bound cache slot (refs #4921)
2026-06-17 10:20:12 -04:00
Isaac Connor
177b51e04a Merge pull request #4932 from SteveGilvarry/remove-hls-demo
chore: remove unused hls.js demo bundle (and trim other unused dist files)
2026-06-17 08:36:45 -04:00
Isaac Connor
82b098276e Print_r of the event model is not useful 2026-06-17 08:00:01 -04:00
Isaac Connor
3fd9fe543e Merge branch 'master' into 4921-authhash-cache-key-collision 2026-06-17 07:53:04 -04:00
Isaac Connor
19b55adb9c fix: ping camera before reboot in zmwatch and resolve host in Control::ping
zmwatch only gated the camera reboot attempt on CanReboot(), so it called
$control->open() even when the camera was unreachable - the common reason a
monitor has no image since startup - and blocked until the connection timed
out, logging an error each pass.

Add a ping check before open(). Move the host resolution into Control so callers
don't have to dig the ip out of the Path: add Control::host(), which returns the
cached host or derives it from the monitor's ControlAddress/Path via the shared
guess_credentials() (parsing only, no network i/o), and have ping() fall back to
it. ping() still accepts an explicit ip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:52:28 -04:00
Isaac Connor
eb12c73a61 Merge pull request #4929 from IgorA100/patch-938291
Fix: Replace "%" with "%25" in the message line (log.js)
2026-06-17 07:51:13 -04:00
Isaac Connor
bac9b8af01 fix: stop IP-less auth hash from poisoning the IP-bound cache slot refs #4921
generateAuthHash() cached every hash under the IP-keyed slot
'AuthHash'.$remoteAddr, but the value was IP-bound only when $useRemoteAddr was
set. getZmuCommand() calls generateAuthHash(false, true), which wrote an
IP-less hash into the IP-bound slot and reset AuthHashGeneratedAt. Because the
status poll runs getZmuCommand (web/ajax/status.php) right before emitting the
auth hash, the poll then served the IP-less hash to the browser; the next
IP-bound request was rejected by the validator, redirecting the user to login
roughly every poll. This happens with a completely stable client IP, so it is
distinct from the IP-rotation case.

Key the cache slot by the address actually baked into the value: only use the
session address when this caller asked for it (and AUTH_HASH_IPS is on). IP-less
and IP-bound hashes now occupy separate slots and can no longer clobber each
other.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:07:44 -04:00
Isaac Connor
e2ca8ee7bd Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-16 18:40:55 -04:00
Isaac Connor
75fff7a1b4 fix: ping camera before reboot in zmwatch and resolve host in Control::ping
zmwatch only gated the camera reboot attempt on CanReboot(), so it called
$control->open() even when the camera was unreachable - the common reason a
monitor has no image since startup - and blocked until the connection timed
out, logging an error each pass.

Add a ping check before open(). Move the host resolution into Control so callers
don't have to dig the ip out of the Path: add Control::host(), which returns the
cached host or derives it from the monitor's ControlAddress/Path via the shared
guess_credentials() (parsing only, no network i/o), and have ping() fall back to
it. ping() still accepts an explicit ip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 18:30:02 -04:00
SteveGilvarry
54c8f38e85 chore: remove unused hls.js demo bundle (and trim other unused dist files)
Primary change: remove web/js/hls-1.6.13/hls-demo.js (+ its source map),
the upstream demo bundle. It is referenced nowhere in the web interface
and is the source of four CodeQL alerts (js/xss-through-dom,
js/incomplete-sanitization, js/redos: #262-#265).

Optional cleanup (can be dropped from this PR if maintainers prefer to
keep the full dist): the directory also shipped alternate builds that the
web interface never loads. The interface only loads hls.min.js
(watch.php, cycle.php, montage.php). Also removed:

- hls.js, hls.light.*, hls.mjs, hls.light.mjs (+ maps): alternate
  full/light/ESM builds; we use the full minified UMD build
- hls.worker.js (+ map): standalone transmuxer worker. hls.min.js inlines
  the worker via a Blob URL and only loads an external worker when
  config.workerPath is set; ZM never sets it, so it is never fetched
- hls.d.mts, hls.d.ts, hls.js.d.ts: TypeScript declarations, dev-only

Keeps hls.min.js and its source map. The demo removal alone clears
#262-#265; trimming the unused builds additionally clears the
js/insecure-randomness alerts in them (#258-#261).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:00:56 +10:00
SteveGilvarry
38ae51b85b fix: address CodeQL DOM-XSS and unsafe-sanitizer findings in skin/monitor JS
Resolves three CodeQL alerts in ZoneMinder-authored JavaScript:

- skin.js .zmlink click handler (js/xss-through-dom, #135): navigated to
  the element's href/data-url via location.assign() without checking the
  scheme, so a javascript:/data: URL in the attribute would run on click.
  Skip navigation for javascript:/data:/vbscript: URLs.

- skin.js strip_html() (js/incomplete-multi-character-sanitization #266,
  js/polynomial-redos #267): stripped tags with /<[^>]+>/g, which is an
  incomplete sanitizer and can backtrack. Parse the string with DOMParser
  and return textContent instead -- correct and linear. Callers in
  devices/reports/snapshots/controlcaps/events.js use it to extract a
  plain Id from a rendered cell, behaviour preserved.

- monitor.js ControlEdit_onClick (js/xss-through-dom, #246): encode the
  select value with encodeURIComponent() before building the controlcap
  URL.

Verified: eslint clean on both files, node --check passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:32:51 +10:00
SteveGilvarry
286cb90883 fix: compute camera frame buffer sizes in size_t to avoid int overflow
The frame buffer size calculations in the libVNC and libVLC camera
backends multiplied 16/32-bit operands and only widened the result to
size_t afterwards, so the multiplication itself could overflow before
the conversion. Flagged by CodeQL cpp/integer-multiplication-cast-to-long
(alerts 282, 145, 85). The VNC framebuffer dimensions are advertised by
the remote server (uint16_t), making the inputs externally influenced.

Cast the first operand to size_t so each product is computed in size_t:
- zm_libvnc_camera.cpp: SwScale::Convert src size (framebufferWidth *
  framebufferHeight * 4) and dest size (width * height * colours)
- zm_libvnc_camera.cpp: resize() bufferSize, changed from int to size_t
  (not converted to a larger type, so CodeQL did not flag it, but the
  same overflow applied); Debug format updated to %zu
- zm_libvlc_camera.cpp: Image::Assign size (width * height * mBpp)

Verified: both translation units compile with -Werror -fsyntax-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:55:06 +10:00
IgorA100
f51f82671a Update web/skins/classic/views/js/log.js
Co-authored-by: Isaac Connor <iconnor@connortechnology.com>
2026-06-16 09:37:34 +03:00
IgorA100
58a328a204 Of course: "% " => '%25%20' (log.js) 2026-06-15 21:58:06 +03:00
IgorA100
f04119123b Replace "%" with "%25" in the message line (log.js)
Otherwise, decodeURIComponent() will fail, as "%" without subsequent characters is not allowed in a URL.
2026-06-15 21:23:34 +03:00
Isaac Connor
ee29134bb3 fix: parse Grandstream control address with guess_credentials
The hand-rolled regex in open() mis-parsed a stream Path that carried no
credentials, e.g. rtsp://10.0.0.4:554/cam: the greedy [^:@]+ consumed the host
as the username, 554 as the password, and ADDRESS backtracked to the single
digit 4. The control daemon then dialled http://...@4, producing
"Can't connect to 4:80 (Connection timed out)".

Replace the regex with the shared URI-based Control.pm guess_credentials(),
which handles ControlAddress and Path, converts rtsp to http, and falls back to
the Monitor User/Pass. Create the LWP::UserAgent before parsing since
guess_credentials() sets credentials on it. The Grandstream login wire protocol
(challenge/authcode and old-style fallback) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:57:23 -04:00
Isaac Connor
97f2fee109 fix: validate FilterTerm tablename against allowlist to prevent SQL injection
The tablename field of a filter term was copied verbatim into SQL by
sql_attr(), while attr, op, val and collate were all sanitized. An
authenticated user with Events View permission could inject SQL via the
filter[Query][terms][N][tablename] request parameter, enabling blind
read access to the whole database (password hashes, camera credentials).

Restrict tablename to the table aliases actually used by the filter
queries (E, M, S, F, T, ET, Snapshots); reject anything else, log it,
and fall back to 'E'.

Refs GHSA-q2w3-h644-f8xq

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:21:50 -04:00
Isaac Connor
bc15782474 fix: skip final stream sleep when zm_terminate is already set
Avoid sleeping up to MAX_SLEEP (500ms) at the end of runStream when
termination has been requested, so zms shuts down more promptly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:11:51 -04:00
Isaac Connor
a36375585c refactor: make zm_terminate a std::atomic<bool>
zm_terminate is written from signal handlers and read from daemon loops.
A plain bool is undefined to access from a signal handler and may be hoisted
out of a loop by the optimizer. std::atomic<bool> is lock-free on supported
platforms and matches the already-atomic per-object terminate_ flags. Update
the three Debug() vararg sites to call .load() since atomics can't pass
through ...

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:11:51 -04:00
Isaac Connor
8284f1724f feat: include monitor Id and Name in Grandstream control log messages
On systems with many cameras the Grandstream control log entries gave no
indication of which monitor they referred to. Prefix every Debug/Warning/Error
message with the monitor Id and Name. Also fix two message typos
(challengstring, UNable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:49:44 -04:00
Isaac Connor
6678674d7e fix: use utf8mb4 for CakePHP API DB connection for Unicode monitor names refs #4785
CakePHP applies the datasource 'encoding' as SET NAMES, and it was 'utf8',
MySQL's 3-byte utf8mb3 alias. Like the C++ daemon connection, this mangles
4-byte UTF-8 characters in utf8mb4 columns such as Monitors.Name to '?' on
read and truncates them on write, so the API returned and stored corrupted
names. Set it to utf8mb4 to match the schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:24:58 -04:00
Isaac Connor
1e76424db1 Merge branch 'onvif-wsse-created-race' 2026-06-14 09:15:01 -05:00
Isaac Connor
fefc1471e6 Merge pull request #4785 from IgorA100/patch-401263
Feat: Allow national Unicode characters in monitor name
2026-06-14 10:10:55 -04:00
Isaac Connor
a04cd7a432 fix: use utf8mb4 for C++ DB connection so Unicode monitor names log intact refs #4785
The connection charset was "utf8", MySQL's alias for 3-byte utf8mb3, while
Monitors.Name and the DB default are utf8mb4. On a utf8mb3 connection any
4-byte UTF-8 character is read back as '?' and, when embedded in a log message
written to the Logs table, raises ERROR 1366 and truncates the value. Names
with such characters lost their multibyte portion in logs, keeping only the
ASCII tail.

Connect with utf8mb4, falling back to utf8 with a warning if the server
rejects it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:06:37 -04:00
Isaac Connor
142e6ea1b3 Merge pull request #4922 from SteveGilvarry/4360-remove-dead-tablist-css
style: remove dead ul.tabList CSS from classic skin
2026-06-14 09:40:48 -04:00
Isaac Connor
6d939bf0af Merge pull request #4923 from SteveGilvarry/4223-filter-is-operator
fix: convert filter IS/IS NOT to =/!= for non-NULL values
2026-06-14 09:40:02 -04:00
Isaac Connor
55bbec4c55 Merge pull request #4924 from SteveGilvarry/3816-db-ssl-verify-server-cert
feat: add ZM_DB_SSL_VERIFY_SERVER_CERT option (portable across MySQL/MariaDB)
2026-06-14 09:39:36 -04:00
Isaac Connor
3c39cb7f12 Merge pull request #4925 from SteveGilvarry/4225-notrunning-filter-function-none
fix: match Function=None monitors in the NotRunning status filter
2026-06-14 09:11:51 -04:00
IgorA100
3983de5d4b Merge branch 'master' into patch-401263 2026-06-14 10:18:33 +03:00
SteveGilvarry
5bae001994 fix: match Function=None monitors in the NotRunning status filter refs #4225
Monitors with Function=None have no Monitor_Status row, so the LEFT JOIN
to Monitor_Status leaves S.Status NULL and the condition Status='NotRunning'
never matched them - selecting the NotRunning status filter returned no
monitors. Coalesce a missing status to NotRunning in the filter condition,
mirroring the NULL->NotRunning handling already used for display in
console.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 16:06:35 +10:00
SteveGilvarry
8801c42064 fix: address review feedback on DB SSL verify option
- API (database.php.default): only set the PDO verify flag when SSL is
  actually configured (ZM_DB_SSL_CA_CERT set), matching the web/Perl/C++
  layers. Previously a fresh install's default (1) would set the flag on a
  non-SSL connection, since the CakePHP datasource merges 'flags' uncondi-
  tionally.
- Both PHP layers: cast to string and trim before parsing the value, and use
  strict in_array, to avoid type-juggling and stray-whitespace edge cases.
- zm_db.cpp: use my_bool (not char) for the MYSQL_OPT_SSL_VERIFY_SERVER_CERT
  fallback argument, the type libmysqlclient expects. That branch only
  compiles on older clients without MYSQL_OPT_SSL_MODE, where my_bool exists.

refs #3816
2026-06-14 15:57:09 +10:00
SteveGilvarry
19ac9c97ae fix: negate modulo for IS NOT Odd/Even; clarify IS comment
Addresses review feedback on #4223:
- The IS NOT Odd/Even branch emitted '% 2 = ' (identical to IS), so
  'MaxScore IS NOT Odd' matched odd values instead of even. Emit '% 2 != '
  so the negation is correct.
- Reword the comment to reflect that IS is only preserved for NULL here;
  any other value (including TRUE/FALSE) compares for equality.

refs #4223
2026-06-14 15:43:30 +10:00
SteveGilvarry
fa79d193c8 fix: convert filter IS/IS NOT to =/!= for non-NULL values refs #4223
A filter term such as 'Score IS 2' or 'Score IS NOT 2' emitted literal
SQL 'Score IS 2', which is invalid - IS / IS NOT only accept
NULL/TRUE/FALSE in MySQL. Keep IS/IS NOT only when the value is NULL
(and the existing Odd/Even modulo handling); for any other value emit
= / != so the term is valid and matches as the user intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:05:39 +10:00
SteveGilvarry
e60bdc67b2 feat: add ZM_DB_SSL_VERIFY_SERVER_CERT option (portable across MySQL/MariaDB)
Add a ZM_DB_SSL_VERIFY_SERVER_CERT setting so a database connection that uses
ZM_DB_SSL_CA_CERT can talk to a server with a self-signed or otherwise
non-matching certificate. When enabled, verification is by identity (the cert
must chain to the CA and its CN/SAN must match ZM_DB_HOST), consistent across
the C++ daemons, the PHP web interface, the CakePHP API and the Perl scripts.

This re-does the reverted #3817. That PR broke the build because it called
mysql_options(MYSQL_OPT_SSL_VERIFY_SERVER_CERT, ...), and that enum was removed
from the MySQL 8.0 C client in favour of MYSQL_OPT_SSL_MODE; it also passed a
c_str() where a my_bool* was expected, and referenced the PHP constant
unconditionally (fatal on PHP 8 for an upgraded install whose zm.conf predates
the option).

The option that controls server-cert verification differs by client library and
the symbols are enum values, not macros, so CMake feature-detects them by
compiling:
  - HAVE_MYSQL_OPT_SSL_MODE  (MySQL 5.7.11+/8.0, MariaDB Connector/C 3.1+)
  - HAVE_MYSQL_OPT_SSL_VERIFY_SERVER_CERT  (older MariaDB/MySQL)
zm_db.cpp uses SSL_MODE_VERIFY_IDENTITY / SSL_MODE_REQUIRED when the former is
available, else falls back to the latter with a proper my_bool.

Value handling is three-way in every layer: a truthy value verifies, a false-y
value (0/false/no/off) skips verification, and an empty/unset value leaves the
client default in place so existing installs are unchanged on upgrade. PHP, the
API datasource (via PDO flags) and the Perl DSN are all guarded with defined()
checks. Fresh installs default to 1.

Documents the full ZM_DB_* connection and SSL settings, including the hostname
verification gotcha when connecting by IP, in docs/userguide/configfiles.rst.

refs #3816
2026-06-14 13:20:00 +10:00