zmupdate.pl applied every zm_update-*.sql at or above the database's
version, with no upper bound. Migration files from a newer release left
in the update directory, after a downgrade or with a newer build's files
installed over an older one, were applied to a schema they were not
written for. Installing a 1.38 build over a 1.39 one ran the 1.39.0 to
1.39.5 migrations against the 1.38 database before one failed.
Skip, and say so, any migration newer than ZM_VERSION.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The patch loop writes ZM_DYN_DB_VERSION after each file so that a run
that dies partway leaves a record of how far it got. It wrote $version,
the version the database started at, which the loop never changes, so
the write was a no-op: a failed upgrade left the original version in
place and the rerun started again from the beginning, re-running every
patch that had already applied.
Write $v, the version of the patch that was just applied. A run that
completes still ends up at ZM_VERSION from the cascade write below.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLYswrguKc6bBYL8JZbpmZ
* 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>
The scripts run under -T, so they cannot trust the caller's PATH and set
their own. Sixteen of them hardcoded /bin:/usr/bin:/usr/local/bin, which
assumes everything they shell out to lives under /usr or /usr/local.
ZoneMinder::General::findDbCommand looks for a database client on that
PATH, and zmupdate.pl and zmcamtool.pl run what it finds. So an install
whose client sits anywhere else cannot apply schema changes:
sh: mysql: command not found
Command 'mysql -u'zmuser' ... ' exited with status: 127
even with the client on the caller's PATH. Homebrew on Apple Silicon is
the case that surfaced it - the client is in /opt/homebrew/bin - but a
--prefix=/opt install on Linux has the same shape, as does anything that
keeps its database client outside the FHS locations.
Replaced the literal with @ZM_SCRIPT_PATH@, defaulting to the same three
directories plus wherever cmake actually found a client, and overridable
for packagers who want to pin it. Warn at configure time when no client
is found at all, since that failure otherwise appears much later and
says something unrelated.
Nothing changes for an install whose client is already under /usr/bin:
the directory is only appended when it is not in the list, so the
default stays exactly as it was.
Memory.pm is deliberately left alone. It has a narrower PATH of
/bin:/usr/bin, and the only command it runs is uname through an absolute
path from ZM_PATH_UNAME, so it does not need widening.
Verified on macOS across the three cases: with the client in
/opt/homebrew/bin the directory is appended; -DZM_SCRIPT_PATH= is
respected verbatim; and pointing detection at /usr/bin/mariadb leaves
the default untouched. Build clean, suite 146 cases.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5KL9Xbi7K5aGsauLtd8tG
Both loops called $dbh->ping() and discarded the result. ping() only tests
the connection; DBD::mysql does not reconnect on its own since
mysql_auto_reconnect is off. zmupdate's update-check loop sleeps 3600s
between iterations, so on a server with wait_timeout below an hour the
handle is already closed on wake-up and the following zmDbDo fails with
"The client was disconnected by the server because of inactivity".
zmDbConnect() already pings and reconnects when needed, and assigns the
package global $dbh that zmDbDo uses, so call that instead. zmupdate keeps
passing mysql_multi_statements so a reconnect matches the original handle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MariaDB is deprecating mysql-prefixed utility names (mysqldump,
mysql) in favour of its own (mariadb-dump, mariadb). Add
findDbCommand() to ZoneMinder::General that probes for the
MariaDB-native binary first and falls back to the legacy name.
Results are cached per-process. Update the three call sites in
zmupdate.pl.in and zmcamtool.pl.in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* initial implementation of privacy popup
* split the privacy text and run it through translate
* change style of toggle button, validate the form
* fix copy/paste error
* fix typos
* display privacy view inline rather than popup
* display privacy inline if show_privacy flag set
* redirect to console after selection is made
* typo
* css formatting
* update privacy verbiage
* create and load default.php
* fix typos
* fix erroneous copy/paste
* initial implementation of privacy popup
* split the privacy text and run it through translate
* change style of toggle button, validate the form
* fix copy/paste error
* fix typos
* display privacy view inline rather than popup
* display privacy inline if show_privacy flag set
* redirect to console after selection is made
* typo
* css formatting
* update privacy verbiage
* push privacy text to all language files