The encoder templates options page calls mb_strimwidth()
(_options_encoderTemplates.php:65). Only the openSUSE spec required
php-mbstring, so on a Debian, Ubuntu, RedHat or Fedora install without
it the Options > Encoder Templates tab stops with "Call to undefined
function mb_strimwidth()".
Add php-mbstring to distros/ubuntu2004/control (used for the Debian and
Ubuntu builds) and distros/redhat/zoneminder.spec (distros/fedora links
to it).
fixes#5258
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: show "map unavailable" on the map view when Leaflet is missing
Leaflet is an optional install: map.php loads js/leaflet/leaflet.js only
if it exists, and nothing in the build or the packages puts it there.
Without it views/js/map.js still ran new ZMMap(), which calls L.map(),
so the view stayed blank with "L is not defined" in the console.
Check for window.L first, as monitor.js and event.js already do for
their location maps, and show a translated warning in the map area
instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: recommend libjs-leaflet in the Debian/Ubuntu package and link it
The map view and the location maps of the monitor and event views load
Leaflet from js/leaflet/ in the web root, which nothing installs. Debian
and Ubuntu ship it as libjs-leaflet under /usr/share/javascript/leaflet.
Recommend libjs-leaflet and link /usr/share/zoneminder/www/js/leaflet to
it. Without the package the link dangles and the views behave as now
(map.php and output_script_if_exists() check that the file exists).
fixes#5260
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Nothing links or dlopens libvlc any more, so the dev package is no
longer a build requirement anywhere: the debian, beowulf, ubuntu and
split-package control files, the podman Containerfile, the redhat and
opensuse specs, the centos CI image, and the five workflows that
installed libvlc-dev. The redhat spec also required vlc-core at
runtime, which was the dlopen target.
Documentation follows: the libVLC source tab section, the macos note
about libVLC not being found, and the WEB_FILTER_SOURCE source-type
list.
Two stale statements nearby are corrected while the files are open. The
Remote source tab still said its RTSP method "may be removed in the
future"; it was removed earlier on this branch. AGENTS.md still listed
RemoteCameraRTSP and LibVLCCamera among the camera classes, both of
which are now gone.
All workflow yaml parses, the three edited rst files render, and the
debian control field structure is unchanged apart from the removed
entries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y6FieTwEXuLhhR4e2yiax
A blank line after the zoneminder package's Depends ended that stanza
early, so Recommends and Description formed a stanza of their own with
no Package field. dpkg rejects the whole file ("stanza lacks the
'Package' field"), so nothing could read its build depends either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ubuntu2004 control file (the one the deb package CI builds from),
the redhat spec and the opensuse spec already build-depend on
nlohmann-json, so the packages we ship are built with
ZM_HAS_NLOHMANN_JSON and carry AI detection results. distros/debian and
distros/beowulf did not, so a package built from either silently lost
them: CMake treats the library as optional and only warns.
It is header-only and in Debian since buster, which Beowulf is based on,
so it costs nothing at runtime and needs no Depends entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: give each migrated password its own random bcrypt salt
migratePasswords() stored the Bytes::Random::Secure object rather than
bytes from it, so en_base64() encoded the string
"Bytes::Random::Secure=HASH(0x...)". bcrypt only reads the first 22
characters of the salt, all from the constant class name, so every user
on every install got the same salt. With neither Bytes::Random::Secure
nor Data::Entropy installed the salt was empty and bcrypt() died with
"bad bcrypt settings", aborting zmupdate.pl. Even the Data::Entropy path
reused a single salt for every user in the run.
Read 16 bytes per user from /dev/urandom, as generateAuthHashSecret()
in ZoneMinder::Config already does. If it can't be read, leave that
user's legacy hash in place, which auth.php still verifies, instead of
writing a weak one. This drops the need for Bytes::Random::Secure and
the deprecated Data::Entropy. refs #4333
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: drop the Data::Entropy dependency from packaging
zmupdate.pl no longer uses Data::Entropy, which upstream has deprecated
(CVE-2025-1860). refs #4333
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: rehash legacy passwords on every password login
Only the login form called migrateHash(), so accounts that sign in with
user=/pass= or through the API kept their mysql or zmupdate.pl
(mysql+bcrypt) hash, including those with the fixed salt. Call it from
those paths too, and have it check the password type itself.
migrateHash() also regenerated the auth hash from the in-memory user,
which still held the old password, so getAuthUser(), which checks
against the new one in the database, rejected it. Update the in-memory
password first, and update the row by Id rather than by the username
as typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: note passwords still on a zmupdate.pl-migrated hash
Every salt migratePasswords() used before this branch was shared by all
users in a run: from Data::Entropy, which before 0.008 keys its generator
from rand() (CVE-2025-1860), or, since 38c0f743 with Bytes::Random::Secure
installed, a constant. The original hash input is gone, so zmupdate.pl
can't rehash these, and nothing distinguishes them from properly salted
ones. Log a warning listing every user still on a -ZM- hash, noting that
it is upgraded at their next login or password reset, and that unused
accounts can be disabled or deleted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
51e8a4571 dropped libcurl4-gnutls-dev from the runtime Depends, relying on
${shlibs:Depends} for the library. zoneminder-containers/zoneminder-base
builds its runtime dependency package from the control file text with
equivs, where ${shlibs:Depends} is never expanded, so the image lost
libcurl-gnutls.so.4 and zmc/zma exited with status 127.
Add libcurl3t64-gnutls | libcurl3-gnutls, the runtime library the build
links against (libcurl4-gnutls-dev is the first Build-Depends choice).
It is a library package, so it does not conflict with
libcurl4-openssl-dev the way the -dev package did.
Refs zoneminder-containers/zoneminder-base#89
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rc.d script and Apache config hard-coded the /usr/local paths the
FreeBSD/OpenBSD distro target uses, so any build with other paths got
files pointing at the wrong place.
Make the rc.d script a template filled with CMAKE_INSTALL_FULL_BINDIR and
PERL_EXECUTABLE, generated into misc/ when building on OpenBSD. Drop the
OpenBSD Apache config: misc/apache.conf is already generated from the
configured web, cgi and cache paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Point the Apache aliases at the web directory cmake installs to, and drop
the unused pexp line that named python.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 49 control protocol definitions were sitting inline in
zm_create.sql.in, which makes adding a model-specific entry a scroll
through a wall of positional INSERTs. Move them to db/controls.sql and
pull them in with
source @PKGDATADIR@/db/controls.sql
which is how User_Preferences.sql, manufacturers.sql, models.sql,
triggers.sql, Object_Types.sql, AI_Models.sql and coco_dataset.sql are
already handled, and the existing install(FILES ${dbfileslist}) glob puts
the new file where that path resolves to. The 49 rows move across
byte-identical.
The new entry is the Amcrest IP5M-1190EW, verified against firmware
2.810.00AC004.0.R. The generic 'Amcrest HTTP API' entry advertises zoom
and continuous zoom and no presets, which is backwards for this model:
pan, tilt, the diagonals and continuous move all physically move it, arg2
really is the speed, and GotoPreset works, while zoom, focus and iris do
not. ZoomTele, ZoomWide, FocusNear and FocusFar answer OK and do
nothing; autoFocus, getFocusStatus, IrisLarge and AutoPanOn 400.
PositionABS answers OK and is inert, so CanMoveAbs and CanMoveMap stay 0.
The white light exists in Lighting_V2 but is not drivable, so CanLight
stays 0.
Worth recording for anyone rechecking this: ptz.cgi?action=getStatus on
that firmware always reports Postion=0/16.65 MoveStatus=Idle no matter
what the camera is doing, so status cannot tell you whether a move
worked. All of the above was confirmed by diffing snapshots.
zm_update-1.39.20.sql carries the same row to existing installs, guarded
with NOT EXISTS so it can be re-run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzHrvNtF13vyYBAJjktam6
Nothing enforced uniqueness, but every reader already assumed one row per
name: User::Preferences() hashes the rows by Name, montagereview.php and
User_Preference::find_one() take the first match, and TagOrder::load()
reads a single Value. With duplicates present those pick an arbitrary
row, so a write could land on one row while reads returned another --
the tag order would appear to stop updating.
Replace the UserId-only index with UNIQUE (UserId, Name) and make Name
NOT NULL. The unique index still serves UserId-only lookups and the
UserId foreign key as a leftmost prefix, so the old index is redundant
and is dropped. Name has to be NOT NULL for the constraint to mean
anything, since a unique index permits any number of NULLs; a NULL-named
row is unreachable regardless, as a preference is only ever looked up by
name.
zm_update-1.39.19.sql discards NULL-named rows, makes the column NOT
NULL, collapses existing duplicates keeping the highest Id (the most
recently inserted), adds the unique index, then drops the old one. The
index is added before the old one is dropped so the foreign key is never
left without a usable index. Each schema change is guarded against
INFORMATION_SCHEMA so re-running is a no-op. db/User_Preferences.sql
gets the same shape for fresh installs.
With the constraint in place, TagOrder::recordUsage() replaces its
SELECT-then-INSERT-or-UPDATE with a single INSERT ... ON DUPLICATE KEY
UPDATE. That drops a query and closes the race where two concurrent tag
additions by the same user both see no row and both insert.
Verified on MariaDB 11.8.8 against a seeded table: duplicate rows
collapse to the expected survivors, NULL-named rows are removed, a
second run is a clean no-op, NULL/duplicate/bad-UserId inserts are
rejected (1048/1062/1452, so the foreign key survives losing its index),
and mysqldump of a fresh install matches a migrated database exactly.
Bump version.txt and the redhat spec Version to 1.39.19.
Server.php's ReadStats() query (SELECT ... WHERE ServerId=? ORDER BY
TimeStamp DESC LIMIT 1) had no supporting index, forcing a full table
scan on every page load that shows server stats. Add a composite
(ServerId, TimeStamp) index via zm_update-1.39.18.sql (idempotent,
checked against INFORMATION_SCHEMA.STATISTICS) and zm_create.sql.in
for fresh installs. Keep the existing TimeStamp-only index, since
zmstats.pl's prune DELETE filters by TimeStamp alone.
Also query ReadStats() with the server's actual Id() instead of
coercing Id() <= 1 to 0.
Bump version.txt and the redhat spec Version to 1.39.18.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7jeoG8ptXRpXxif2JqkD7
The RedHat unit specified no TimeoutSec, so it inherited systemd's
DefaultTimeoutStopSec. On Fedora that default is short and its global
/usr/lib/systemd/system/service.d/10-timeout-abort.conf sets
TimeoutStopFailureMode=abort, so a stop that overruns the timeout is
killed with SIGABRT and dumps core. ZoneMinder's stop path blocks in
zmpkg.pl while zmdc shuts down every capture/analysis daemon, which can
exceed a short timeout on a busy system, producing zmpkg.pl coredumps.
Set TimeoutSec=60 to match the intent of the Ubuntu unit (which uses
TimeoutSec=600) while keeping shutdown responsive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The postinst built the zmrepo apt source line from lsb_release Codename,
which on Linux Mint returns Mint's own codename (e.g. wilma) that zmrepo
does not publish, producing a broken sources.list.
Resolve to the upstream release the derivative is based on: prefer
/etc/upstream-release/lsb-release (Mint Ubuntu editions), then
UBUNTU_CODENAME / DEBIAN_CODENAME / VERSION_CODENAME from /etc/os-release,
falling back to lsb_release -cs. Ubuntu and Debian proper are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ZM_TMPDIR defaulted to /var/tmp/zm, and the Debian-family packaging put
it in shared system temp: distros/ubuntu2004 used /var/tmp/zm and
distros/beowulf used /tmp/zm. ZoneMinder stages regenerable scratch there
(exports, uploads, the video-concat list, detection scratch), so shared
system temp is the wrong, exposed location. Only the RedHat build did it
right, using a ZoneMinder-owned dir.
Move ZM_TMPDIR to /var/cache/zoneminder/temp - a ZoneMinder-owned,
www-data-owned directory - in the CMake default and in the ubuntu2004 and
beowulf packaging. The Debian packaging already creates and chowns
/var/cache/zoneminder/temp (it was provisioned but unused), so no new dir
plumbing is needed. /var/cache is correct here because everything written
to this dir is regenerable.
Also repoint the CakePHP api/app/tmp symlink (cache, sessions, logs) from
shared /var/tmp to the same /var/cache/zoneminder/temp, matching what the
RedHat build already does.
No migration is required: the contents are transient/regenerable, so on
upgrade the new dir is simply used going forward.
This does not cover the official Debian package on salsa (which sets
ZM_TMPDIR=/tmp/zm and ZM_CONTENTDIR=/var/cache/zoneminder); that is
maintained downstream and needs a separate Debian-side change.
refs #2915
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Catches the redhat spec up to version.txt for the GHSA-vj5r-pc2v-gfwv
security release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The zoneminder runtime Depends listed libcurl4-gnutls-dev, a build-time
-dev package. The curl -dev flavors (gnutls/openssl/nss) are mutually
exclusive, so installing zoneminder forced the gnutls dev flavor and
conflicted with packages requiring libcurl4-openssl-dev.
Remove the explicit curl entries from the runtime Depends: ZoneMinder
links libcurl directly, so ${shlibs:Depends} already generates the
correct runtime library dependency for the flavor it was built against.
Offer libcurl4-gnutls-dev | libcurl4-openssl-dev | libcurl4-nss-dev in
Build-Depends (matching distros/debian/control) so the build env can
satisfy a co-installed package needing libcurl4-openssl-dev.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 428b9d64d0)
Add www-data to video and dialout groups in Debian/Ubuntu postinst
scripts so zmc can access /dev/video* devices on fresh installs.
RedHat packaging already handled this via gpasswd in %post.
Add compile-time ZM_FONTDIR to zm_config_data.h.in and use it as a
fallback in zm_image.cpp when the configured font_file_location is not
found, fixing font loading failures caused by stale DB config values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MakeMaker was only used to copy .pm files — no XS compilation, no binary
linking, no dependency resolution. Its hardcoded "Makefile" output name
conflicts with cmake's generated Makefile for in-source builds, and using
FIRST_MAKEFILE=MakefilePerl causes thousands of "uninitialized value"
warnings because MM.pm stats the wrong file.
Replace with native CMake install(DIRECTORY ... FILES_MATCHING PATTERN
"*.pm") directives. Perl module install path is auto-detected at configure
time via `perl -MConfig` (vendorlib on Linux, sitelib on FreeBSD),
overridable with -DZM_PERL_INSTALL_PATH=<path>.
What's removed:
- ExtUtils::MakeMaker as build dependency
- Three perl+make subprocesses at build time (zmperlmodules,
zmonvifmodules, zmonvifproxy build targets)
- ~6000 auto-generated man3 pages from WSDL stubs
- MakeMaker scaffolding: Makefile.PL, MANIFEST, META.yml, Changes,
README, and t/ZoneMinder.t test stub
What's preserved:
- configure_file() for .pm.in templates (same behavior)
- ZM_PERL_SEARCH_PATH (independent mechanism, unchanged)
- Section 8 man pages for .pl scripts (Pod2Man.cmake, unaffected)
- DESTDIR support (CMake install() handles natively)
- Installed file paths (perl -MConfig returns same paths MakeMaker used)
Verified: 3102 .pm files installed, 0 .pm.in files, 0 .3pm man pages,
no @VERSION@ markers in generated files, DESTDIR and user override work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MakeMaker was only used to copy .pm files — no XS compilation, no binary
linking, no dependency resolution. Its hardcoded "Makefile" output name
conflicts with cmake's generated Makefile for in-source builds, and using
FIRST_MAKEFILE=MakefilePerl causes thousands of "uninitialized value"
warnings because MM.pm stats the wrong file.
Replace with native CMake install(DIRECTORY ... FILES_MATCHING PATTERN
"*.pm") directives. Perl module install path is auto-detected at configure
time via `perl -MConfig` (vendorlib on Linux, sitelib on FreeBSD),
overridable with -DZM_PERL_INSTALL_PATH=<path>.
What's removed:
- ExtUtils::MakeMaker as build dependency
- Three perl+make subprocesses at build time (zmperlmodules,
zmonvifmodules, zmonvifproxy build targets)
- ~6000 auto-generated man3 pages from WSDL stubs
- MakeMaker scaffolding: Makefile.PL, MANIFEST, META.yml, Changes,
README, and t/ZoneMinder.t test stub
What's preserved:
- configure_file() for .pm.in templates (same behavior)
- ZM_PERL_SEARCH_PATH (independent mechanism, unchanged)
- Section 8 man pages for .pl scripts (Pod2Man.cmake, unaffected)
- DESTDIR support (CMake install() handles natively)
- Installed file paths (perl -MConfig returns same paths MakeMaker used)
Verified: 3102 .pm files installed, 0 .pm.in files, 0 .3pm man pages,
no @VERSION@ markers in generated files, DESTDIR and user override work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Fedora 43, net-tools pulls in systemd which conflicts with
systemd-standalone-tmpfiles in container build environments.
Pre-define ZM_PATH_ARP, ZM_PATH_ARP_SCAN, ZM_PATH_IP, and
ZM_PATH_IFCONFIG in the cmake call instead of relying on
find_program() at build time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>