mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-16 03:32:31 -04:00
zms checked access against the monitor id in the query string and then streamed the event id from the same query, loading the event's real monitor later without checking it. A viewer allowed monitor 1 and denied monitor 2 could fetch a monitor-2 event by sending monitor=1, or by leaving monitor out, which checked only the global Events permission. For event requests, look up the event's MonitorId and require Events view plus access to that monitor. When the stream is chosen by monitor and time instead of by event id, the named monitor is the one streamed and is the one checked. Moving to another event during playback stays within the same monitor, so checking the first one is enough. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
431 lines
16 KiB
C++
431 lines
16 KiB
C++
//
|
|
// ZoneMinder Streaming Server, $Date$, $Revision$
|
|
// Copyright (C) 2001-2008 Philip Coombes
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
#include "zm.h"
|
|
#include "zm_db.h"
|
|
#include "zm_user.h"
|
|
#include "zm_signal.h"
|
|
#include "zm_monitorstream.h"
|
|
#include "zm_eventstream.h"
|
|
#include "zm_fifo_stream.h"
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
|
|
bool ValidateAccess(User *user, int mon_id) {
|
|
bool allowed = true;
|
|
|
|
if (mon_id > 0) {
|
|
if (user->getStream() < User::PERM_VIEW) {
|
|
allowed = false;
|
|
Warning("Insufficient privileges for request user %d %s for monitor %d, user does not have permission to stream live view.",
|
|
user->Id(), user->getUsername(), mon_id);
|
|
} else if (!user->canAccess(mon_id)) {
|
|
allowed = false;
|
|
Warning("Insufficient privileges for request user %d %s for monitor %d, user does not have permission view this monitor.",
|
|
user->Id(), user->getUsername(), mon_id);
|
|
}
|
|
} else if (user->getEvents() < User::PERM_VIEW) {
|
|
allowed = false;
|
|
Warning("Insufficient privileges for request user %d %s, user does not have permission view events.",
|
|
user->Id(), user->getUsername());
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
// Event access is decided by the monitor the event belongs to, never by a monitor id the
|
|
// client supplies alongside it: an allowed monitor paired with a denied monitor's event id
|
|
// must not stream that event. mon_id is used only when the stream is chosen by monitor and
|
|
// time, in which case it is the monitor whose events are streamed.
|
|
bool ValidateEventAccess(User *user, int mon_id, uint64_t event_id, bool by_monitor_and_time) {
|
|
if (user->getEvents() < User::PERM_VIEW) {
|
|
Warning("Insufficient privileges for request user %d %s, user does not have permission view events.",
|
|
user->Id(), user->getUsername());
|
|
return false;
|
|
}
|
|
if (!by_monitor_and_time) {
|
|
zmDbRow row;
|
|
if (!event_id || !row.fetch(stringtf("SELECT `MonitorId` FROM `Events` WHERE `Id` = %" PRIu64, event_id))) {
|
|
Warning("Unknown event %" PRIu64 " requested by user %d %s", event_id, user->Id(), user->getUsername());
|
|
return false;
|
|
}
|
|
mon_id = row[0] ? atoi(row[0]) : 0;
|
|
}
|
|
if (!user->canAccess(mon_id)) {
|
|
Warning("Insufficient privileges for request user %d %s for event %" PRIu64 " of monitor %d, user does not have permission view this monitor.",
|
|
user->Id(), user->getUsername(), event_id, mon_id);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, const char *argv[], char **envp) {
|
|
self = argv[0];
|
|
|
|
srand(getpid() * time(nullptr));
|
|
|
|
enum { ZMS_UNKNOWN, ZMS_MONITOR, ZMS_EVENT, ZMS_FIFO } source = ZMS_UNKNOWN;
|
|
enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG;
|
|
char format[32] = "";
|
|
int monitor_id = 0;
|
|
SystemTimePoint event_time = std::chrono::system_clock::time_point::min();
|
|
uint64_t event_id = 0;
|
|
unsigned int frame_id = 1;
|
|
int frames_to_send = -1;
|
|
unsigned int scale = 100;
|
|
unsigned int rate = 100;
|
|
double maxfps = 0.0;
|
|
unsigned int bitrate = 100000;
|
|
unsigned int ttl = 0;
|
|
bool analysis_frames = false;
|
|
EventStream::StreamMode replay = EventStream::MODE_NONE;
|
|
std::string username;
|
|
std::string password;
|
|
char auth[64] = "";
|
|
std::string jwt_token_str = "";
|
|
unsigned int connkey = 0;
|
|
unsigned int playback_buffer = 0;
|
|
|
|
bool nph = false;
|
|
const char *basename = strrchr(argv[0], '/');
|
|
if ( basename ) // if we found a / lets skip past it
|
|
basename++;
|
|
else // argv[0] might not contain the full path, but just the script name
|
|
basename = argv[0];
|
|
const char *nph_prefix = "nph-";
|
|
if ( basename && !strncmp(basename, nph_prefix, strlen(nph_prefix)) ) {
|
|
nph = true;
|
|
}
|
|
|
|
char log_id_string[32] = "zms";
|
|
logInit(log_id_string);
|
|
zmLoadStaticConfig();
|
|
zmDbConnect();
|
|
zmLoadDBConfig();
|
|
logInit(log_id_string);
|
|
|
|
for (char **env = envp; *env != 0; env++) {
|
|
char *thisEnv = *env;
|
|
Debug(1, "env: %s", thisEnv);
|
|
}
|
|
|
|
const char *query = getenv("QUERY_STRING");
|
|
if ( query == nullptr ) {
|
|
Fatal("No query string.");
|
|
return exit_zm(0);
|
|
} // end if query
|
|
|
|
Debug(1, "Query: %s", query);
|
|
|
|
char *q_ptr = (char *)query;
|
|
char *parms[16]; // Shouldn't be more than this
|
|
int parm_no = 0;
|
|
while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) {
|
|
parm_no++;
|
|
q_ptr = nullptr;
|
|
}
|
|
|
|
for ( int p = 0; p < parm_no; p++ ) {
|
|
char *name = strtok(parms[p], "=");
|
|
char const *value = strtok(nullptr, "=");
|
|
if ( !value )
|
|
value = "";
|
|
if ( !strcmp(name, "analysis") ) {
|
|
if ( !strcmp(value, "true") ) {
|
|
analysis_frames = true;
|
|
} else {
|
|
analysis_frames = (atoi(value) == 1);
|
|
}
|
|
Debug(1, "Viewing analysis frames");
|
|
} else if ( !strcmp(name, "source") ) {
|
|
if ( !strcmp(value, "event") ) {
|
|
source = ZMS_EVENT;
|
|
} else if ( !strcmp(value, "fifo") ) {
|
|
source = ZMS_FIFO;
|
|
} else {
|
|
source = ZMS_MONITOR;
|
|
}
|
|
} else if ( !strcmp(name, "mode") ) {
|
|
if (!strcmp(value, "jpeg")) {
|
|
mode = ZMS_JPEG;
|
|
} else if (!strcmp(value, "single")) {
|
|
mode = ZMS_SINGLE;
|
|
} else if (!strcmp(value, "raw")) {
|
|
mode = ZMS_RAW;
|
|
} else if (!strcmp(value, "zip")) {
|
|
mode = ZMS_ZIP;
|
|
} else if (!strcmp(value, "mpeg")) {
|
|
mode = ZMS_MPEG;
|
|
} else if (!strcmp(value, "paused")) {
|
|
mode = ZMS_JPEG;
|
|
} else {
|
|
Error("Unsupported value for mode");
|
|
}
|
|
} else if ( !strcmp(name, "format") ) {
|
|
strncpy(format, value, sizeof(format)-1);
|
|
} else if ( !strcmp(name, "monitor") ) {
|
|
monitor_id = atoi(value);
|
|
if ( source == ZMS_UNKNOWN )
|
|
source = ZMS_MONITOR;
|
|
} else if ( !strcmp(name, "datetime") ) {
|
|
std::tm tm = {};
|
|
std::stringstream ss(value);
|
|
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
|
|
event_time = std::chrono::system_clock::from_time_t(std::mktime(&tm));
|
|
//Debug(1, "Setting event_time to %s", SystemTimePointToString(event_time).c_str());
|
|
} else if ( !strcmp(name, "time") ) {
|
|
double double_time = atof(value);
|
|
std::chrono::duration<double> event_time_seconds(double_time);
|
|
auto system_duration = std::chrono::duration_cast<std::chrono::system_clock::duration>(event_time_seconds);
|
|
event_time = SystemTimePoint{system_duration};
|
|
//Debug(1, "Setting event_time to %s from %s %.2f %lf",
|
|
//SystemTimePointToString(event_time).c_str(), value, double_time, event_time_seconds.count());
|
|
} else if ( !strcmp(name, "event") ) {
|
|
event_id = strtoull(value, nullptr, 10);
|
|
source = ZMS_EVENT;
|
|
} else if ( !strcmp(name, "frame") ) {
|
|
frame_id = strtoull(value, nullptr, 10);
|
|
source = ZMS_EVENT;
|
|
} else if ( !strcmp(name, "frames") ) {
|
|
frames_to_send = strtoll(value, nullptr, 10);
|
|
} else if ( !strcmp(name, "scale") ) {
|
|
scale = atoi(value);
|
|
if (scale > 1600) {
|
|
Warning("Limiting scale to 16x");
|
|
scale = 1600;
|
|
}
|
|
} else if ( !strcmp(name, "rate") ) {
|
|
rate = atoi(value);
|
|
} else if ( !strcmp(name, "maxfps") ) {
|
|
maxfps = atof(value);
|
|
} else if ( !strcmp(name, "bitrate") ) {
|
|
bitrate = atoi(value);
|
|
} else if ( !strcmp(name, "ttl") ) {
|
|
ttl = atoi(value);
|
|
} else if ( !strcmp(name, "replay") ) {
|
|
if ( !strcmp(value, "gapless") ) {
|
|
replay = EventStream::MODE_ALL_GAPLESS;
|
|
} else if ( !strcmp(value, "all") ) {
|
|
replay = EventStream::MODE_ALL;
|
|
} else if ( !strcmp(value, "none") ) {
|
|
replay = EventStream::MODE_NONE;
|
|
} else if ( !strcmp(value, "single") ) {
|
|
replay = EventStream::MODE_SINGLE;
|
|
} else {
|
|
Error("Unsupported value %s for replay, defaulting to none", value);
|
|
}
|
|
} else if ( !strcmp(name, "connkey") ) {
|
|
connkey = atoi(value);
|
|
} else if ( !strcmp(name, "buffer") ) {
|
|
playback_buffer = atoi(value);
|
|
} else if ( !strcmp(name, "auth") ) {
|
|
strncpy(auth, value, sizeof(auth)-1);
|
|
} else if ( !strcmp(name, "token") ) {
|
|
jwt_token_str = value;
|
|
Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str());
|
|
} else if ( !strcmp(name, "user") ) {
|
|
username = UriDecode(value);
|
|
} else if ( !strcmp(name, "username") ) {
|
|
username = UriDecode(value);
|
|
} else if ( !strcmp(name, "pass") ) {
|
|
password = UriDecode(value);
|
|
Debug(1, "Have %s for password", password.c_str());
|
|
} else if ( !strcmp(name, "password") ) {
|
|
password = UriDecode(value);
|
|
Debug(1, "Have %s for password", password.c_str());
|
|
} else {
|
|
Debug(1, "Unknown parameter passed to zms %s=%s", name, value);
|
|
} // end if possible parameter names
|
|
} // end foreach parm
|
|
|
|
if ( event_id ) {
|
|
snprintf(log_id_string, sizeof(log_id_string), "zms_e%" PRIu64, event_id);
|
|
} else if ( monitor_id ) {
|
|
snprintf(log_id_string, sizeof(log_id_string), "zms_m%d", monitor_id);
|
|
}
|
|
logInit(log_id_string);
|
|
|
|
// Summarise the decoded request so a runaway nph-zms can be tied to its
|
|
// stream type/params. Debug level because this fires once per process and
|
|
// there can be many (one per montage tile). refs #5006 (nph-zms
|
|
// occasionally consuming excessive memory).
|
|
{
|
|
static const char *source_names[] = {"unknown", "monitor", "event", "fifo"};
|
|
static const char *mode_names[] = {"jpeg", "mpeg", "raw", "zip", "single"};
|
|
Debug(1, "zms request: source=%s mode=%s monitor=%d event=%" PRIu64
|
|
" frame=%u frames=%d connkey=%u scale=%u rate=%u maxfps=%.2f"
|
|
" buffer=%u ttl=%u rss=%zuKB",
|
|
source_names[source], mode_names[mode], monitor_id, event_id,
|
|
frame_id, frames_to_send, connkey, scale, rate, maxfps,
|
|
playback_buffer, ttl, zm_get_rss_kb());
|
|
}
|
|
|
|
if ( config.opt_use_auth ) {
|
|
User *user = nullptr;
|
|
|
|
if ( jwt_token_str != "" ) {
|
|
// user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips);
|
|
user = zmLoadTokenUser(jwt_token_str, false);
|
|
} else if ( strcmp(config.auth_relay, "none") == 0 ) {
|
|
if ( checkUser(username.c_str()) ) {
|
|
user = zmLoadUser(username.c_str());
|
|
} else {
|
|
Error("Bad username");
|
|
}
|
|
} else {
|
|
if ( *auth ) {
|
|
user = zmLoadAuthUser(auth, username, config.auth_hash_ips);
|
|
} else if ( username.length() && password.length() ) {
|
|
user = zmLoadUser(username.c_str(), password.c_str());
|
|
}
|
|
}
|
|
if ( !user ) {
|
|
fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout);
|
|
|
|
const char *referer = getenv("HTTP_REFERER");
|
|
const char *request_uri = getenv("REQUEST_URI");
|
|
const char *xff = getenv("HTTP_X_FORWARDED_FOR");
|
|
const char *remote = getenv("REMOTE_ADDR");
|
|
// Most failures here are stale auth hashes on long-lived <img src=nph-zms?...>
|
|
// streams whose hash TTL expired; the browser keeps reconnecting with the
|
|
// baked-in URL. Including user/auth-prefix/uri/xff makes the noise diagnosable
|
|
// without flipping on Debug.
|
|
// Log only the first 8 chars of the hash: enough to correlate, not enough to replay.
|
|
Warning("Unable to authenticate user (user='%s' auth='%.8s%s' uri='%s' referer='%s' xff='%s' remote='%s')",
|
|
username.c_str(),
|
|
auth,
|
|
(*auth && strlen(auth) > 8) ? "..." : "",
|
|
request_uri ? request_uri : "",
|
|
referer ? referer : "",
|
|
xff ? xff : "",
|
|
remote ? remote : "");
|
|
return exit_zm(0);
|
|
}
|
|
bool allowed;
|
|
if ( source == ZMS_EVENT ) {
|
|
// Mirrors the setStreamStart() choice below.
|
|
bool by_monitor_and_time = monitor_id && (event_time != std::chrono::system_clock::time_point::min());
|
|
allowed = ValidateEventAccess(user, monitor_id, event_id, by_monitor_and_time);
|
|
} else {
|
|
allowed = ValidateAccess(user, monitor_id);
|
|
}
|
|
if ( !allowed ) {
|
|
delete user;
|
|
user = nullptr;
|
|
fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout);
|
|
return exit_zm(0);
|
|
}
|
|
delete user;
|
|
user = nullptr;
|
|
} // end if config.opt_use_auth
|
|
|
|
HwCapsDetect();
|
|
Image::Initialise();
|
|
zmSetDefaultTermHandler();
|
|
zmSetDefaultDieHandler();
|
|
zmSetDefaultPipeHandler();
|
|
|
|
setbuf(stdout, nullptr);
|
|
if ( nph ) {
|
|
fputs("HTTP/1.0 200 OK\r\n", stdout);
|
|
}
|
|
fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION);
|
|
|
|
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
|
char date_string[64];
|
|
tm now_tm = {};
|
|
strftime(date_string, sizeof(date_string)-1,
|
|
"%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&now, &now_tm));
|
|
|
|
fputs("Last-Modified: ", stdout);
|
|
fputs(date_string, stdout);
|
|
fputs(
|
|
"\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
|
|
"Cache-Control: no-store, no-cache, must-revalidate\r\n"
|
|
"Cache-Control: post-check=0, pre-check=0\r\n"
|
|
"Pragma: no-cache\r\n",
|
|
stdout);
|
|
|
|
if ( source == ZMS_MONITOR ) {
|
|
MonitorStream stream;
|
|
stream.setStreamScale(scale);
|
|
stream.setStreamReplayRate(rate);
|
|
stream.setStreamMaxFPS(maxfps);
|
|
stream.setStreamTTL(ttl);
|
|
stream.setStreamQueue(connkey);
|
|
stream.setStreamBuffer(playback_buffer);
|
|
stream.setFramesToSend(frames_to_send);
|
|
stream.setStreamStart(monitor_id);
|
|
stream.setStreamFrameType(analysis_frames ? StreamBase::FRAME_ANALYSIS: StreamBase::FRAME_NORMAL);
|
|
|
|
if ( mode == ZMS_JPEG ) {
|
|
stream.setStreamType(MonitorStream::STREAM_JPEG);
|
|
} else if ( mode == ZMS_RAW ) {
|
|
stream.setStreamType(MonitorStream::STREAM_RAW);
|
|
} else if ( mode == ZMS_ZIP ) {
|
|
stream.setStreamType(MonitorStream::STREAM_ZIP);
|
|
} else if ( mode == ZMS_SINGLE ) {
|
|
stream.setStreamType(MonitorStream::STREAM_SINGLE);
|
|
} else {
|
|
stream.setStreamFormat(format);
|
|
stream.setStreamBitrate(bitrate);
|
|
stream.setStreamType(MonitorStream::STREAM_MPEG);
|
|
}
|
|
stream.runStream();
|
|
} else if ( source == ZMS_FIFO ) {
|
|
FifoStream stream;
|
|
stream.setStreamMaxFPS(maxfps);
|
|
stream.setStreamStart(monitor_id, format);
|
|
stream.runStream();
|
|
} else if ( source == ZMS_EVENT ) {
|
|
EventStream stream;
|
|
stream.setStreamScale(scale);
|
|
stream.setStreamReplayRate(rate);
|
|
stream.setStreamMaxFPS(maxfps);
|
|
stream.setStreamMode(replay);
|
|
stream.setStreamQueue(connkey);
|
|
stream.setFramesToSend(frames_to_send);
|
|
if ( monitor_id && (event_time != std::chrono::system_clock::time_point::min())) {
|
|
stream.setStreamStart(monitor_id, event_time);
|
|
} else if ( event_id && (event_time != std::chrono::system_clock::time_point::min())) {
|
|
stream.setStreamStart(event_id, event_time);
|
|
} else {
|
|
Debug(3, "Setting stream start to frame (%d)", frame_id);
|
|
stream.setStreamStart(event_id, frame_id);
|
|
}
|
|
stream.setStreamFrameType(analysis_frames ? StreamBase::FRAME_ANALYSIS: StreamBase::FRAME_NORMAL);
|
|
if ( mode == ZMS_JPEG ) {
|
|
stream.setStreamType(EventStream::STREAM_JPEG);
|
|
} else if ( mode == ZMS_SINGLE ) {
|
|
stream.setStreamType(MonitorStream::STREAM_SINGLE);
|
|
} else {
|
|
stream.setStreamFormat(format);
|
|
stream.setStreamBitrate(bitrate);
|
|
stream.setStreamType(EventStream::STREAM_MPEG);
|
|
} // end if jpeg or mpeg
|
|
stream.runStream();
|
|
} else {
|
|
Error("Neither a monitor or event was specified.");
|
|
} // end if monitor or event
|
|
|
|
return exit_zm(0);
|
|
}
|
|
|