227 Commits
Author SHA1 Message Date
Isaac Connor b7405c58c5 Merge pull request #5288 from nabbi/hard/auth-default
Ask at first run whether to require a login, and refuse the password admin
2026-10-16 19:03:06 -04:00
Isaac Connor 156c880f47 Merge pull request #5290 from nabbi/feat/options-conf-override
feat: make options set in zm.conf or conf.d read-only on the Options page
2026-10-16 11:30:01 -04:00
Nic Boet b631d2efc9 feat: add a minimum length for new passwords refs #5287
Add ZM_PASSWORD_MIN_LENGTH (Options, System; default 8, 0 turns it off).
passwordProblem() refuses a shorter password, so the user page, the API
and both login-page forms apply it alike. Existing passwords keep
working; only a password being set is checked. The length counts
characters, not bytes.

There is no setting for character classes: they push people to
predictable variations (Password1!) and add less than length does.

A database whose Config table doesn't have the option yet (an upgrade
not run with zmupdate.pl -f) gets no minimum. The password inputs carry
minlength so the browser says so before the form is sent; the server
check is the one that counts. The API answers with the English text, as
for the other reasons.

Tests: tests/php/test_default_password.php.
2026-10-16 10:13:53 -05:00
Nic Boet ebbe1e59e2 feat: ask on the first page whether to require a login refs #5287
A new install starts with authentication on and the admin password admin,
which is refused everywhere. Asking for that password just to replace it is
an extra step, and some installs don't want a login at all. Ask instead.

While the first run is pending (authentication on, the privacy page not
answered yet, builtin auth, and admin still has the password admin), the
login page shows "Set up ZoneMinder" instead of the login form:

- Require a login: choose the admin password; the account is then logged
  in and the privacy page follows, as before.
- Don't require a login: ZM_OPT_USE_AUTH is set to 0 in the database.
  Not offered when ZM_OPT_USE_AUTH is set in zm.conf or conf.d: the owner
  of the box has decided then, and only the password is asked for.

Until one is chosen the web interface, API and streams can't be used: the
password admin is still refused. Whoever opens the page first makes the
choice; before this, whoever typed admin/admin did. The form carries a
nonce kept in the session, like the default-password form, and is exempt
from csrf-magic as action=login is (the login regenerates the session id).
Both choices are audited. Upgrades are not affected: ZM_SHOW_PRIVACY is 0
once the privacy page was answered, and zmupdate.pl doesn't reset it.

Docs: a First Run section in the getting-started guide, and the FAQ and
system options updated to match.

Tests: tests/php/test_first_run.php.
2026-10-16 10:10:25 -05:00
Nic Boet cb4c5f5314 feat: ask for a new password when an account logs in with the default one refs #5287
db/zm_create.sql seeds the admin account with the password the user
guide documents (admin), so with authentication on a new install is
still open to anyone who reads the guide.

Refuse that password as a credential and have the web login replace it:

- validateUser() returns false with "This account uses the password
  admin, which ZoneMinder does not accept. Log in to the web interface
  to set a new one." when the password is right but is admin. The API (login.json and
  user=/pass= on any request) answers 401 with that message; the
  user=/pass= web parameters are refused the same way.
- On the login page (action=login) the same login shows a form that
  asks for the current password again and a new one, with a nonce kept
  in the session so another site can't submit it for the user. The new
  password must be set, confirmed and not the default. The account is
  logged in only once it has been replaced; the change is audited.
  That POST skips reCAPTCHA when it carries the form's nonce: the form
  follows a login that passed it, and doesn't render the widget.
  With ZM_AUTH_RELAY=plain the session keeps the new password.
- zmLoadUser() refuses the default password, so zms, zmu and the RTSP
  server don't accept it in user/password parameters either.

The check is on the password typed, so it needs no schema change, covers
existing installs whose admin still has the default password, and
follows the account if it is renamed. A password set to the default
again later is caught the same way. Sessions, tokens and auth hashes
issued before the upgrade keep working until they expire; with
ZM_AUTH_TYPE=remote the web server's login is not affected.

The FAQ now says to force authentication on in conf.d: since 4d7609d36
the daemons apply zm.conf and conf.d values over the database too.

Tests: tests/php/test_default_password.php (the helpers, and that the
seeded hash is the documented default), tests/zm_user_default_password.cpp.
2026-10-16 10:10:25 -05:00
Nic Boet 9ee0e91819 feat: turn authentication on by default for new installs refs #5287
ZM_OPT_USE_AUTH shipped as 0, so a new install accepted every request as
the default user with System=Edit. That includes requests a browser sends
on behalf of any web page a user on the same network opens, so a server
that is never exposed to the internet could still be driven from it.

Default it to 1. zm_create.sql (generated from ConfigData) gives new
installs authentication on. Existing installs keep their value:
zmupdate.pl -f loads Value from the database and writes it back, and only
DefaultValue changes. zm_update-1.39.41.sql also sets DefaultValue to 1
for the row, so that an upgrade run without -f doesn't leave
Value = DefaultValue = 0, which the C++ config loader would skip in favour
of its compiled-in default (on) while the web UI read off.

Update the option help and the user guide (getting started, FAQ, system
options) to say authentication is on for new installs and kept on
upgrade. tests/perl/test_auth_default.pl checks the default, the stored
value and that no update script sets the row's Value; tests/zm_config.cpp
now expects the compiled-in default (opt_use_auth) to be on.
2026-10-16 09:40:37 -05:00
Nic Boet 1adfe62a00 feat: mask secrets on the Options page, never send one set in a file refs #5289
ZM_AUTH_HASH_SECRET and the reCAPTCHA keys are Private string options, so
an admin saw them in a plain text input; only password options were
masked. Private options now use the same masked input and visibility
toggle as password options. The input asks for autocomplete
"new-password", so the browser doesn't fill in a saved login password
that a save would then store in the option.

A Private or password option set in zm.conf or conf.d can't be changed
on this page, so its value is left out of the page altogether: the
masked input holds the placeholder "<set in config file>", is disabled
and has no toggle, and the line naming the file says the value is not
shown (OptionSetInConfigFileHidden). Users who can't edit System
settings saw no Private value before and still don't.

docs/userguide/configfiles.rst says how secrets are shown.
2026-10-16 09:25:16 -05:00
Nic Boet 656a94c9bd feat: make options set in zm.conf or conf.d read-only on the Options page refs #5289
zm.conf and conf.d are set by the system owner and win over the Config
table in PHP, Perl and, since 4d7609d36, the daemons. The Options page
still offered those options for editing and showed the database value,
with a note only when the two differed.

An option a file sets is now shown with the value in effect (a boolean
word read as in configFileValue()), its input disabled, and one line
naming the file ('OptionSetInConfigFile', through translate()). That
line replaces the old "overriden via configuration files" note. A row
whose option a file sets gets no data-requires, so the page script
doesn't enable its input again, and the Requires check of other options
reads the value in effect.

actions/options.php skips options a file sets. A disabled checkbox is
left out of the form, and the action saved a missing boolean as 0, so
without the skip a save of the tab would have reset the Config row; a
hand-made POST can't change it either. The Config row keeps whatever the
admin last saved, which applies again once the file line is removed.

loadConfigFiles() is split out of loadConfig() so the view and the
action can tell which names come from a file and which file. Each entry
carries that file; loadConfig() drops the key before returning, so
configs/viewByName answers as before.

docs/userguide/configfiles.rst describes setting an option in a file.
2026-10-16 09:24:48 -05:00
Nic Boet 8ea28cf909 docs: say how to turn reCAPTCHA off without the web UI
With reCAPTCHA on, a wrong site key, a blocked reCAPTCHA script or a
server that can't reach www.google.com refuses every login on the login
page. Document the two ways back in: a conf.d override, which the web UI
reads before the database, or an UPDATE of the Config row.
2026-10-15 19:01:19 -05:00
Isaac ConnorandClaude Opus 5 5c060bb6e6 chore: drop the libvlc build dependency from packaging, CI and docs
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
2026-10-10 09:32:17 -04:00
Isaac Connor 9f3f6c6770 fix: only trust X-Forwarded-For from configured proxies for auth hash IPs
With ZM_AUTH_HASH_IPS on, the client address bound into the auth hash was
taken from the left-most X-Forwarded-For value whenever the header was
present, both when PHP generated the hash (getRemoteAddr()) and when PHP or
zms validated it (getAuthUser(), zmLoadAuthUser()). The header is client
controlled, so anyone holding a leaked hash could replay it from anywhere by
sending the address it was bound to.

Add ZM_AUTH_TRUSTED_PROXIES, a list of exact reverse proxy addresses.
X-Forwarded-For is now used only when REMOTE_ADDR is one of them, and is read
from the right, skipping hops that are themselves listed proxies, so values a
client prepends are never chosen. With the option empty, the default, the
header is ignored and REMOTE_ADDR is used.

PHP (web/includes/Network.php getRemoteAddr()) and C++ (ClientAddress() in
zm_utils, used by zmLoadAuthUser()) implement the same rule so generation and
validation continue to agree. Every PHP caller already routes through
getRemoteAddr(), so session.php and auth.php need no change.

Reverse proxy users who enable ZM_AUTH_HASH_IPS must list their proxy in the
new option; until they do, hashes bind to the proxy address, which still
validates but no longer distinguishes clients. This is the behaviour change
that the #4921 work avoided by trusting the header.

The option is added through ConfigData only, like other recent options;
zmupdate.pl --freshen inserts it, zms falls back to the compiled-in default and
PHP treats an undefined constant as empty, so no schema migration is needed.

Tests: ClientAddress Catch2 case; tests/php/test_remote_addr.php updated for
the trusted-proxy rule.

refs GHSA-72rf-54rm-798c

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit acea5889dec0826f595cb736147a5fdc9c94a2a9)
2026-09-24 19:51:27 -04:00
singhharsh1708 57a5da2f20 docs: drop the Frame Skip setting, it no longer exists 2026-09-06 11:37:37 +05:30
Jash 3082cdd9f9 docs: minimum alarmed area cannot be 0, the zone form rejects it fixes #4486 2026-08-30 01:39:13 +05:30
АнтонandClaude Opus 4.8 d01fc48848 docs: explain how Event Start/End Command are actually executed
The Recording tab docs said only that 'the parameters to the command
will be the event id and the monitor id', which is true only when the
command contains no '%' - and hides several surprises discovered by
reading zm_monitor.cpp:

- without '%' the whole string is exec'd as ONE executable path with
  event id / monitor id appended; a command with inline arguments
  ('/path/script.sh start') is treated as a single file name and fails;
- with '%' the string runs via /bin/sh -c after %EID%/%MID%/%EC%
  substitution, and nothing is appended automatically (%EC% exists for
  the start command only);
- the child runs in the background as the capture-process user with all
  file descriptors closed, so any output silently disappears;
- the end command fires after the event is finalized, so the video file
  is complete by then.

Document all of that where the two options are described.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 18:29:04 +03: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
copilot-swe-agent[bot]andconnortechnology 04aa0a7781 docs: document all ONVIF_Options key=value pairs
Replaces the uninformative "Any ONVIF options required. This is an
optional field." with a full reference covering:
- key=value,key=value format with an example
- pull_timeout, subscription_timeout, max_retries,
  timestamp_validity, soap_log, renewal_enabled, expire_alarms,
  closes_event — including defaults, valid ranges, and when to use each

Also fixes pre-existing typo: "alam" → "alarm".

Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/149f7873-ca12-424f-85d4-4bdc179d2488

Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-05-03 20:21:49 +00:00
Isaac ConnorandClaude Opus 4.6 863fac4d72 docs: add zma event re-analysis utility documentation
Add dedicated documentation page for the zma binary covering synopsis,
all command-line options, operating modes (update-existing vs create-events),
save-analysis functionality, and usage examples. Add zma entry to the
components page and user guide toctree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:47:26 -05:00
Steve Gilvarry 823504d1d1 Add section on debug logs that we can reference when asking for them. 2026-01-02 14:04:22 +11:00
Isaac Connor b236b48f1b Updates removing zma and fixing some of the perl module names 2024-10-16 11:42:55 -04:00
Isaac Connor 84e1869fb8 Add ELOC substitution to get Latitude and Longitude of event 2024-07-05 17:28:59 -04:00
Isaac Connor 8618900339 If we are not doing an html email, then don't replace with img tags referencing the attachment. Just do the attachment. Introduce %FID% to sub with the id of the filter. 2024-01-15 16:58:54 -05:00
efranzwa 0026e3446b Add images for Viewing Monitors Doc Section 2023-12-20 06:24:37 -08:00
efranzwa bd863d026b Update Viewing Monitors Doc Section 2023-12-20 06:22:43 -08:00
Isaac Connor 112eb6b9ad Add support for EISNAP and EPISNAP for snapshot image 2023-12-19 12:27:02 -05:00
efranzwa b033134596 Add images for Defining Zones Doc Section 2023-12-09 09:49:15 -08:00
efranzwa 6298d48db0 Update Defining Zones Doc Section 2023-12-09 09:47:12 -08:00
efranzwa fec884493c Added images for Defining Monitors section 2023-11-10 12:59:38 -08:00
efranzwa bf08d6c9bc Added MQTT Tab section 2023-11-10 12:54:49 -08:00
efranzwa bea5677a31 Added Misc Tab section 2023-11-10 12:54:33 -08:00
efranzwa ac8ce700bc Added X10 Tab section 2023-11-10 12:54:06 -08:00
efranzwa ad65e34d0f Added Control Tab section 2023-11-10 12:53:48 -08:00
efranzwa bd3b73220f Added Buffers Tab section 2023-11-10 12:53:24 -08:00
efranzwa 703939f7ec Added Timestamp Tab section 2023-11-10 12:53:00 -08:00
efranzwa ca784d9722 Added ONVIF Tab section 2023-11-10 12:52:10 -08:00
efranzwa b347385b21 Added Viewing Tab section 2023-11-10 12:51:46 -08:00
efranzwa 31a1278286 Added Recording Tab section 2023-11-10 12:51:04 -08:00
efranzwa 4861aa7acb Added Analysis Tab section 2023-11-10 12:50:21 -08:00
efranzwa f7cb484fbd Added Source Tab section 2023-11-10 12:49:49 -08:00
efranzwa 15e8ff578b Added General Tab section 2023-11-10 12:49:01 -08:00
efranzwa 8d29f0e468 Add the Adding Monitors section 2023-11-10 12:47:01 -08:00
efranzwa 97b2bd5b9b Update Defining Monitors doc section 2023-11-10 12:42:29 -08:00
efranzwa 7208f746f2 Rework Getting Started doc section 2023-10-28 17:39:10 -07:00
efranzwa 03e59657d3 Add new images for Getting Started doc section 2023-10-28 17:35:18 -07:00
Isaac Connor bdc1660cc1 Update description for FPS Report Interval 2023-09-29 12:26:43 -04:00
Simpler1 18d74ed7ac (feat): Tags
fix(tag): Create tags on mobile

chore(tags): Change TagName to Name

chore(tags): eslint

chore(tags): dbFetchAll to dbQuery for removetag

chore(events): eslint (attempt 2)

feat(tags): Better handling of keyboard

fix(tags): Enter key for creating new tag

fix(tags): Don't allow space as a tag name

feat(tags): Delete tag if last assignment removed

fix(tags): Increase height of dropdown

in progress

fix(Tags): Use T.Id on the events page dropdown

fix(Tags): Remove $availableTags from events.php

chore(sql): Formatting sql statements

feat(Tags): Working OR on filters and events pages

fix(filter): Populate availableTags

chore(Tags): code formatting

fix(tag): Add tag on create tag

Fix(tags): Remove tag from available if last

feat(tags): Add zm_update.sql

fix(chosen): Undo css width

fix(chosen): tags dropdown width

fix(tags): dropdown over timeline

fix(tags): Full width input

fix(events): Refresh table on page show

chore(filter): Clean up availableTags

chore(event): Clean up available & selected Tags

fix(event): Update available tags on remove

fix(event): Remove hack for selected tags

feat(tags): Blur input after adding tag

doc(tags): Initial tags documentation

fix(tags): Dark theme dropdown

fix(tags): Dark theme for tags on input

fix(tags): Dark theme for highlight in dropdown

fix(tags): Populate filter tags droplist

chore(): Bump zm_update to 1.37.42

chore(tags): Move mobile check to skin.js

chore(tags): Comment debug statements

fix(tags): Enter key to create tag on mobile Chome

chore(tags): Space in 'All Tags' for translation

Temporary commit to handle cookie expiration times

chore(tags): Remove unnecessary Tag(s) from en_gb

chore(): Cleanup unnecessary Error and Debug

chore(): Resolve merge conflicts

chore(): Address merge conflicts with master
2023-08-31 15:50:08 -04:00
Alejandro Criado-Pérez a5d83e61b4 Corrections on rst files 2023-08-13 01:20:29 +02:00
Isaac Connor 6485a4da46 Add doc for new substitutions EID and MID 2023-05-29 16:24:45 -04:00
Isaac Connor 28cf2570a9 tabs instead of spaces are messing up ul layout 2023-02-12 16:37:44 -05:00
Alejandro c7a818fc73 Updated zmninja info 2022-12-12 13:41:31 +01:00
Isaac Connor 4907a706a7 New image of monitor add view 2022-09-19 13:40:52 -04:00