Add a new AUDIT logging level (-5) between PANIC (-4) and NOLOG (shifted
to -6) across C++, PHP, and Perl loggers. AUDIT entries use code 'AUD'
and syslog priority LOG_NOTICE. They record who changed what, from where,
for monitors, filters, users, config, roles, groups, zones, states,
servers, storage, events, snapshots, control caps, and login/logout.
AUDIT entries have their own retention period (ZM_LOG_AUDIT_DATABASE_LIMIT,
default 1 year) separate from regular log pruning. The log pruning in
zmstats.pl and zmaudit.pl now excludes AUDIT rows from regular pruning
and prunes them independently.
Critical safety: the C++ termination logic is changed from
'if (level <= FATAL)' to 'if (level == FATAL || level == PANIC)' to
prevent AUDIT-level log calls from killing the process.
Includes db migration zm_update-1.39.1.sql to shift any stored NOLOG
config values from -5 to -6.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
document.getElementById('evtStream') can return null if the element
was removed from the DOM during event navigation or deletion, causing
a TypeError when accessing .src on the result.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
this.closest() returns null when the <video-stream> element isn't inside
a monitor div, causing TypeError on the subsequent querySelector call.
Add null checks in both divMode and divError setters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In loadEventData, date/time filter values from form inputs were passed
directly into the CakePHP REST URL path. Locale-formatted dates using /
as separator (e.g. 01/15/2024) break URL routing because Apache decodes
%2F back to / in path segments.
Use moment() to normalize date/time values to YYYY-MM-DD HH:mm:ss before
encoding them into the URL. Also replace hardcoded fallback dates in
clicknav that used MM/DD/YYYY format with ISO format equivalents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When zones have Units='Percent' in the database but their Coords contain
pixel values (>100), ParsePercentagePolygon treats them as percentages,
causing wild scaling (e.g., 639% * 1920 / 100 = 12269) followed by
clamping to monitor bounds, producing degenerate full-frame zones.
Add a pre-check in Zone::Load that scans coordinate values before calling
ParsePercentagePolygon. If any value exceeds 100, log a warning and use
ParsePolygonString (pixel path) instead. Also add unit tests for both
ParsePolygonString and ParsePercentagePolygon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MonitorStream.js initialized analyse_frames = true, causing zone
overlays to always appear on stream start regardless of view settings.
Change default to false so watch view starts clean. The zone editor
view already explicitly enables analysis after monitor creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lo_x/lo_y/hi_x/hi_y variables were unsigned int despite being assigned
from signed int32 Vector2 members and compared against signed int dimensions.
This caused scattered (int) and (unsigned int) casts throughout CheckAlarms,
std_alarmedpixels, and Setup. Switching to int eliminates the casts, fixes
the signed/unsigned comparison warnings, and makes the -1 sentinel check in
std_alarmedpixels explicit rather than relying on unsigned wraparound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add config type entries for Hostname, DNS, NTP, NetworkInterfaces,
Capabilities, Scopes, Services, VideoSources, VideoSourceConfigurations,
AudioSources, AudioSourceConfigurations, AudioEncoderConfigurations,
StreamUri, SnapshotUri, ImagingOptions, PTZConfigurations, PTZNodes,
PTZPresets, and PTZStatus.
- Support __PROFILE_TOKEN__ substitution in request bodies
- Add writable flag to config types and enforce it in set_config
- Add NTP set handler for SetNTP ONVIF command
- Organize config_types by ONVIF service (device, media, imaging, PTZ)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a camera reconnects at a different resolution than the zone polygons
were configured for, the polygon bounding box can exceed the frame
dimensions. This caused a heap buffer overflow in the memset that zeros
the bbox rows of the diff image, confirmed by Valgrind (invalid write
at 0 bytes past a 921600-byte / 1280x720 block).
- Validate delta_image buffer and dimensions at entry
- Clamp hi_y/hi_x to image height/width before memset and pixel loops
- Guard filter, blob, and HighlightEdges loops against empty rows where
ranges[y].lo_x is -1 (would wrap to UINT_MAX as unsigned)
- Clamp extents in std_alarmedpixels independently for defense in depth
Warning() logs identify which zones need polygon reconfiguration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The post-save redirect included filter query params in the URL, which
caused the view to populate from request data instead of loading from
the database. Since the querystring omitted Name and UserId, both
fields appeared empty/wrong after reload. Remove the querystring from
the redirect so the view loads the saved filter from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add new 'ONVIF' Controls entry with corrected brightness/contrast
ranges (0-100). Update legacy entries (ONVIF Camera, Netcat ONVIF,
Reolink RLC-423/411/420) to use Protocol 'ONVIF' instead of the
old per-vendor module names (onvif, Netcat, Reolink).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Created some slightly rounded corners for the streams, tables, and navigation block: .imageFeed, .nav, .fixed-table-body, id='videoFeedStream*' (skin.css)
avformat_close_input() can block for 75-90s on TCP retransmit timeout
when an RTSP camera becomes unresponsive, and the connect() retry loop
also lacks heartbeat updates. This causes zmwatch to kill zmc with a
stale heartbeat even though the process is actively reconnecting.
Add SetHeartbeatTime() calls before/after Close() and in the connect()
retry loop so zmwatch knows zmc is still alive during reconnection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Before limitPoints() silently clamps coordinates, check if any zone
point exceeds the monitor's image dimensions and show a warning icon
with tooltip in the zones table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AutoSelectFormat() opens the video device to enumerate formats, but
non-capture processes like zms (QUERY mode) should never need to access
the device. Guard the call with the existing capture flag so zms no
longer fails with "No such file or directory" errors when running under
Apache with systemd PrivateDevices=yes.
Also downgrade the fallback message from Error to Warning since YUYV
fallback is a recoverable condition, and add a hint about PrivateDevices
to the ENOENT error in AutoSelectFormat for cases where capture mode
legitimately can't open the device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Debian Forky's stock apache2 systemd unit sets PrivateDevices=yes,
giving Apache its own /dev namespace without video devices. This
caused file_exists('/dev/video0') to return false even though the
device exists and www-data is in the video group.
Remove the file_exists() pre-check and just attempt v4l2-ctl directly.
When it fails, provide targeted diagnostics: missing v4l2-ctl, systemd
PrivateDevices detected, or generic permission error with the running
username.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The event thread was sleeping 33ms (ZM_SAMPLE_RATE) between checks for
packet decoded/analyzed status. Replace with a condition variable wait
on the packet itself, using a 2ms timeout as safety net for the race
between flag check and wait entry.
Add packet->notify_all() at every site where decoded or analyzed is set
to true, so the event thread wakes up near-instantly. Add wait_for()
to ZMPacketLock to support timed waits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove per-packet ZMPacketLock trylock() from clearPackets() scan loops
and queuePacket() GOP deletion — the iterator check is sufficient since
threads only access packets through their own shared_ptr.
Add a monotonic uint64_t queue_index to ZMPacket, assigned on enqueue.
clearPackets() now finds the earliest iterator position with a single
min() over the 2-3 iterators, then uses one integer comparison per
scanned packet instead of walking all iterators per packet.
Defer packet destruction outside the mutex in both clearPackets() and
queuePacket() by collecting removed shared_ptrs into a local vector
and releasing the lock before they are destroyed.
Raise per-packet deletion Debug(1) to Debug(4) in both paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
App::uses('AppModel', 'CameraModel') tells CakePHP to look for AppModel
in a non-existent 'CameraModel' package. The correct second argument is
'Model', which points to app/Model/AppModel.php where the base class
actually lives.
This was likely a copy-paste error — every other model in the codebase
correctly uses App::uses('AppModel', 'Model'). The bug may go unnoticed
when another model loads AppModel first via CakePHP's autoloader, but
causes a fatal error if CameraModel is the first model resolved in a
request (e.g. hitting the camera models API endpoint directly).
Add --protocol, --address, and --device CLI options that bypass the
database and socket machinery to load a control module directly and
execute a single command. This enables standalone testing of control
modules (e.g. ONVIF.pm get_config) without a running ZoneMinder
instance. Also update POD with full documentation and examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If EventStartCommand/EventEndCommand contains a % character, use the
new token substitution (%EID%, %MID%, %EC%) with sh -c execution.
Otherwise, fall back to the original execlp() behavior that passes
event_id and monitor_id as $1 and $2, so existing installs are not
broken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The post-save redirect included filter query params in the URL, which
caused the view to populate from request data instead of loading from
the database. Since the querystring omitted Name and UserId, both
fields appeared empty/wrong after reload. Remove the querystring from
the redirect so the view loads the saved filter from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add new 'ONVIF' Controls entry with corrected brightness/contrast
ranges (0-100). Update legacy entries (ONVIF Camera, Netcat ONVIF,
Reolink RLC-423/411/420) to use Protocol 'ONVIF' instead of the
old per-vendor module names (onvif, Netcat, Reolink).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>