diff --git a/src/core/config.c b/src/core/config.c index f55e57f9..6b53cce5 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -307,17 +307,17 @@ void load_default_config(config_t *config) { } // General settings - snprintf(config->pid_file, MAX_PATH_LENGTH, "/var/run/lightnvr.pid"); - snprintf(config->log_file, MAX_PATH_LENGTH, "/var/log/lightnvr.log"); + safe_strcpy(config->pid_file, "/var/run/lightnvr.pid", MAX_PATH_LENGTH, 0); + safe_strcpy(config->log_file, "/var/log/lightnvr.log", MAX_PATH_LENGTH, 0); config->log_level = LOG_LEVEL_INFO; // Syslog settings config->syslog_enabled = false; - snprintf(config->syslog_ident, sizeof(config->syslog_ident), "lightnvr"); + safe_strcpy(config->syslog_ident, "lightnvr", sizeof(config->syslog_ident), 0); config->syslog_facility = LOG_USER; // Storage settings - snprintf(config->storage_path, MAX_PATH_LENGTH, "/var/lib/lightnvr/recordings"); + safe_strcpy(config->storage_path, "/var/lib/lightnvr/recordings", MAX_PATH_LENGTH, 0); config->storage_path_hls[0] = '\0'; // Empty by default, will use storage_path if not specified config->max_storage_size = 0; // 0 means unlimited config->retention_days = 30; @@ -328,35 +328,35 @@ void load_default_config(config_t *config) { // MP4 recording settings config->record_mp4_directly = false; - snprintf(config->mp4_storage_path, sizeof(config->mp4_storage_path), "/var/lib/lightnvr/recordings/mp4"); + safe_strcpy(config->mp4_storage_path, "/var/lib/lightnvr/recordings/mp4", sizeof(config->mp4_storage_path), 0); config->mp4_segment_duration = 900; // 15 minutes config->mp4_retention_days = 30; // Models settings - snprintf(config->models_path, MAX_PATH_LENGTH, "/var/lib/lightnvr/models"); + safe_strcpy(config->models_path, "/var/lib/lightnvr/models", MAX_PATH_LENGTH, 0); // API detection settings - snprintf(config->api_detection_url, MAX_URL_LENGTH, "http://localhost:8000/detect"); - snprintf(config->api_detection_backend, 32, "onnx"); // Default to ONNX backend + safe_strcpy(config->api_detection_url, "http://localhost:8000/detect", MAX_URL_LENGTH, 0); + safe_strcpy(config->api_detection_backend, "onnx", 32, 0); // Default to ONNX backend // Global detection defaults config->default_detection_threshold = 50; // 50% confidence threshold config->default_pre_detection_buffer = 5; // 5 seconds before detection config->default_post_detection_buffer = 10; // 10 seconds after detection - snprintf(config->default_buffer_strategy, 32, "auto"); // Auto-select buffer strategy + safe_strcpy(config->default_buffer_strategy, "auto", 32, 0); // Auto-select buffer strategy // Database settings - snprintf(config->db_path, MAX_PATH_LENGTH, "/var/lib/lightnvr/lightnvr.db"); + safe_strcpy(config->db_path, "/var/lib/lightnvr/lightnvr.db", MAX_PATH_LENGTH, 0); config->db_backup_interval_minutes = 60; config->db_backup_retention_count = 24; config->db_post_backup_script[0] = '\0'; // Web server settings config->web_port = 8080; - snprintf(config->web_bind_ip, 32, "0.0.0.0"); - snprintf(config->web_root, MAX_PATH_LENGTH, "/var/lib/lightnvr/www"); + safe_strcpy(config->web_bind_ip, "0.0.0.0", 32, 0); + safe_strcpy(config->web_root, "/var/lib/lightnvr/www", MAX_PATH_LENGTH, 0); config->web_auth_enabled = true; - snprintf(config->web_username, 32, "admin"); + safe_strcpy(config->web_username, "admin", 32, 0); // No default password - will be generated randomly on first run config->web_password[0] = '\0'; config->webrtc_disabled = false; // WebRTC is enabled by default @@ -385,7 +385,7 @@ void load_default_config(config_t *config) { // Memory optimization config->buffer_size = 1024; // 1024 KB (1 MB) buffer size config->use_swap = true; - snprintf(config->swap_file, MAX_PATH_LENGTH, "/var/lib/lightnvr/swap"); + safe_strcpy(config->swap_file, "/var/lib/lightnvr/swap", MAX_PATH_LENGTH, 0); config->swap_size = (uint64_t)128 * 1024 * 1024; // 128MB swap // Hardware acceleration @@ -399,14 +399,14 @@ void load_default_config(config_t *config) { // CMake passes these as string literals already (e.g. -DGO2RTC_BINARY_PATH_RAW="/usr/local/bin/go2rtc"), // so they must be used directly — NOT through STRINGIFY, which would double-quote the value. #ifdef GO2RTC_BINARY_PATH_RAW - snprintf(config->go2rtc_binary_path, MAX_PATH_LENGTH, "%s", GO2RTC_BINARY_PATH_RAW); + safe_strcpy(config->go2rtc_binary_path, GO2RTC_BINARY_PATH_RAW, MAX_PATH_LENGTH, 0); #else - snprintf(config->go2rtc_binary_path, MAX_PATH_LENGTH, "/usr/local/bin/go2rtc"); + safe_strcpy(config->go2rtc_binary_path, "/usr/local/bin/go2rtc", MAX_PATH_LENGTH, 0); #endif #ifdef GO2RTC_CONFIG_DIR_RAW - snprintf(config->go2rtc_config_dir, MAX_PATH_LENGTH, "%s", GO2RTC_CONFIG_DIR_RAW); + safe_strcpy(config->go2rtc_config_dir, GO2RTC_CONFIG_DIR_RAW, MAX_PATH_LENGTH, 0); #else - snprintf(config->go2rtc_config_dir, MAX_PATH_LENGTH, "/etc/lightnvr/go2rtc"); + safe_strcpy(config->go2rtc_config_dir, "/etc/lightnvr/go2rtc", MAX_PATH_LENGTH, 0); #endif config->go2rtc_api_port = 1984; config->go2rtc_rtsp_port = 8554; // Default RTSP listen port @@ -417,7 +417,7 @@ void load_default_config(config_t *config) { config->go2rtc_webrtc_enabled = true; // Enable WebRTC by default config->go2rtc_webrtc_listen_port = 8555; // Default WebRTC listen port config->go2rtc_stun_enabled = true; // Enable STUN by default for NAT traversal - snprintf(config->go2rtc_stun_server, sizeof(config->go2rtc_stun_server), "stun.l.google.com:19302"); + safe_strcpy(config->go2rtc_stun_server, "stun.l.google.com:19302", sizeof(config->go2rtc_stun_server), 0); config->go2rtc_external_ip[0] = '\0'; // Empty by default (auto-detect) config->go2rtc_ice_servers[0] = '\0'; // Empty by default (use STUN server) @@ -430,7 +430,7 @@ void load_default_config(config_t *config) { // ONVIF discovery settings config->onvif_discovery_enabled = false; // Disabled by default config->onvif_discovery_interval = 300; // 5 minutes between scans - snprintf(config->onvif_discovery_network, sizeof(config->onvif_discovery_network), "auto"); + safe_strcpy(config->onvif_discovery_network, "auto", sizeof(config->onvif_discovery_network), 0); // Initialize default values for detection-based recording in streams for (int i = 0; i < config->max_streams; i++) { @@ -458,8 +458,8 @@ void load_default_config(config_t *config) { config->mqtt_broker_port = 1883; // Default MQTT port config->mqtt_username[0] = '\0'; // Optional config->mqtt_password[0] = '\0'; // Optional - snprintf(config->mqtt_client_id, sizeof(config->mqtt_client_id), "lightnvr"); - snprintf(config->mqtt_topic_prefix, sizeof(config->mqtt_topic_prefix), "lightnvr"); + safe_strcpy(config->mqtt_client_id, "lightnvr", sizeof(config->mqtt_client_id), 0); + safe_strcpy(config->mqtt_topic_prefix, "lightnvr", sizeof(config->mqtt_topic_prefix), 0); config->mqtt_tls_enabled = false; // No TLS by default config->mqtt_keepalive = 60; // 60 seconds keepalive config->mqtt_qos = 1; // QoS 1 (at least once) @@ -467,7 +467,7 @@ void load_default_config(config_t *config) { // Home Assistant MQTT auto-discovery settings config->mqtt_ha_discovery = false; // Disabled by default - snprintf(config->mqtt_ha_discovery_prefix, sizeof(config->mqtt_ha_discovery_prefix), "homeassistant"); + safe_strcpy(config->mqtt_ha_discovery_prefix, "homeassistant", sizeof(config->mqtt_ha_discovery_prefix), 0); config->mqtt_ha_snapshot_interval = 30; // 30 seconds default } @@ -1288,7 +1288,7 @@ int load_config(config_t *config) { // Set default web root if not specified if (strlen(config->web_root) == 0) { // Set a default web root path - snprintf(config->web_root, sizeof(config->web_root), "%s", "/var/www/lightnvr"); // or another appropriate default + safe_strcpy(config->web_root, "/var/www/lightnvr", sizeof(config->web_root), 0); // or another appropriate default } // Add logging to debug diff --git a/src/database/db_recordings.c b/src/database/db_recordings.c index 125746e4..65c590a7 100644 --- a/src/database/db_recordings.c +++ b/src/database/db_recordings.c @@ -626,7 +626,7 @@ int get_recording_count(time_t start_time, time_t end_time, char sql[8192]; // Use trigger_type and/or detections table to filter detection-based recordings - snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM recordings r WHERE r.is_complete = 1 AND r.end_time IS NOT NULL"); + safe_strcpy(sql, "SELECT COUNT(*) FROM recordings r WHERE r.is_complete = 1 AND r.end_time IS NOT NULL", sizeof(sql), 0); if (has_detection == 1) { // Filter by trigger_type = 'detection' OR existence of linked detections via recording_id (fast index lookup) @@ -969,7 +969,7 @@ int get_recording_metadata_paginated(time_t start_time, time_t end_time, // Add LIMIT and OFFSET for pagination char limit_clause[64]; - snprintf(limit_clause, sizeof(limit_clause), " LIMIT ? OFFSET ?"); + safe_strcpy(limit_clause, " LIMIT ? OFFSET ?", sizeof(limit_clause), 0); safe_strcat(sql, limit_clause, sizeof(sql)); log_debug("SQL query for get_recording_metadata_paginated: %s", sql); diff --git a/src/video/go2rtc/go2rtc_api.c b/src/video/go2rtc/go2rtc_api.c index f16fb15b..2b10cdb6 100644 --- a/src/video/go2rtc/go2rtc_api.c +++ b/src/video/go2rtc/go2rtc_api.c @@ -652,14 +652,14 @@ bool go2rtc_api_get_application_info(int *rtsp_port, if (version && version_size > 0) { cJSON *version_obj = cJSON_GetObjectItem(json, "version"); if (version_obj && cJSON_IsString(version_obj)) { - snprintf(version, version_size, "%s", cJSON_GetStringValue(version_obj)); + safe_strcpy(version, cJSON_GetStringValue(version_obj), version_size, 0); } } if (revision && revision_size > 0) { cJSON *revision_obj = cJSON_GetObjectItem(json, "revision"); if (revision_obj && cJSON_IsString(revision_obj)) { - snprintf(revision, revision_size, "%s", cJSON_GetStringValue(revision_obj)); + safe_strcpy(revision, cJSON_GetStringValue(revision_obj), revision_size, 0); } } diff --git a/src/video/hls/hls_unified_thread.c b/src/video/hls/hls_unified_thread.c index 3fd49e77..1a0fb911 100644 --- a/src/video/hls/hls_unified_thread.c +++ b/src/video/hls/hls_unified_thread.c @@ -2752,7 +2752,7 @@ int stop_hls_unified_stream(const char *stream_name) { // Store a local copy of the stream name for logging char writer_stream_name[MAX_STREAM_NAME]; - snprintf(writer_stream_name, sizeof(writer_stream_name), "%s", stream_name); // Use the stream_name we already have + safe_strcpy(writer_stream_name, stream_name, sizeof(writer_stream_name), 0); // Use the stream_name we already have // Safely get and clear the writer pointer const hls_writer_t *writer_to_cleanup = NULL; diff --git a/src/video/onvif_discovery.c b/src/video/onvif_discovery.c index 354c7385..97059dfa 100644 --- a/src/video/onvif_discovery.c +++ b/src/video/onvif_discovery.c @@ -257,7 +257,7 @@ int discover_onvif_devices(const char *network, onvif_device_info_t *devices, } addr.s_addr = htonl(ip); - snprintf(ip_addr, sizeof(ip_addr), "%s", inet_ntoa(addr)); + safe_strcpy(ip_addr, inet_ntoa(addr), sizeof(ip_addr), 0); // Check if port 3702 (ONVIF) or port 80 (HTTP) is open with a shorter timeout if (is_port_open(ip_addr, 3702, 25) || is_port_open(ip_addr, 80, 25)) { @@ -309,7 +309,7 @@ int discover_onvif_devices(const char *network, onvif_device_info_t *devices, // Send probes to broadcast address addr.s_addr = htonl(broadcast); - snprintf(ip_addr, sizeof(ip_addr), "%s", inet_ntoa(addr)); + safe_strcpy(ip_addr, inet_ntoa(addr), sizeof(ip_addr), 0); log_info("Sending discovery probes to broadcast address: %s", ip_addr); dest_addr.sin_addr.s_addr = htonl(broadcast); diff --git a/src/video/onvif_discovery_thread.c b/src/video/onvif_discovery_thread.c index 6ef64170..4b798676 100644 --- a/src/video/onvif_discovery_thread.c +++ b/src/video/onvif_discovery_thread.c @@ -1,8 +1,3 @@ -#include "video/onvif_discovery_thread.h" -#include "video/onvif_discovery_network.h" -#include "video/onvif_discovery_probe.h" -#include "video/onvif_discovery_response.h" -#include "core/logger.h" #include #include #include @@ -13,6 +8,13 @@ #include #include +#include "video/onvif_discovery_thread.h" +#include "video/onvif_discovery_network.h" +#include "video/onvif_discovery_probe.h" +#include "video/onvif_discovery_response.h" +#include "core/logger.h" +#include "utils/strings.h" + // Maximum number of discovered devices #define MAX_DISCOVERED_DEVICES 32 @@ -53,7 +55,7 @@ void *discovery_thread_func(void *arg) { // Send discovery probes to all addresses in the range for (uint32_t ip = network + 1; ip < broadcast && thread_data->running; ip++) { addr.s_addr = htonl(ip); - snprintf(ip_addr, sizeof(ip_addr), "%s", inet_ntoa(addr)); + safe_strcpy(ip_addr, inet_ntoa(addr), sizeof(ip_addr), 0); // Send discovery probe send_discovery_probe(ip_addr); @@ -64,7 +66,7 @@ void *discovery_thread_func(void *arg) { // Send multiple probes to broadcast address addr.s_addr = htonl(broadcast); - snprintf(ip_addr, sizeof(ip_addr), "%s", inet_ntoa(addr)); + safe_strcpy(ip_addr, inet_ntoa(addr), sizeof(ip_addr), 0); log_info("Sending multiple discovery probes to broadcast address: %s", ip_addr); for (int i = 0; i < 5; i++) { diff --git a/src/web/api_handlers_recordings_batch_download.c b/src/web/api_handlers_recordings_batch_download.c index 4514211a..f30183d8 100644 --- a/src/web/api_handlers_recordings_batch_download.c +++ b/src/web/api_handlers_recordings_batch_download.c @@ -269,7 +269,7 @@ static void *zip_worker(void *arg) { /* Build entry name: stream_YYYY-MM-DDTHH-mm-ss.ext */ const char *base = strrchr(rec.file_path, '/'); base = base ? base+1 : rec.file_path; - snprintf(entries[entry_count].name, sizeof(entries[entry_count].name), "%s", base); + safe_strcpy(entries[entry_count].name, base, sizeof(entries[entry_count].name), 0); uint64_t fsize = 0; uint32_t crc = crc32_of_file(rec.file_path, &fsize); diff --git a/src/web/api_handlers_streams_test.c b/src/web/api_handlers_streams_test.c index a847fcb0..b0728be0 100644 --- a/src/web/api_handlers_streams_test.c +++ b/src/web/api_handlers_streams_test.c @@ -84,7 +84,7 @@ static int test_stream_connection(const char *url, int protocol, } if (video_stream_index == -1) { - snprintf(error_msg, error_msg_size, "No video stream found"); + safe_strcpy(error_msg, "No video stream found", error_msg_size, 0); log_error("%s", error_msg); ret = -1; goto cleanup; diff --git a/src/web/api_handlers_system.c b/src/web/api_handlers_system.c index d64c5d39..d3291892 100644 --- a/src/web/api_handlers_system.c +++ b/src/web/api_handlers_system.c @@ -180,15 +180,15 @@ static void add_versions_to_json(cJSON *info) { char os_version[256] = {0}; if (read_os_release_value("PRETTY_NAME", pretty_name, sizeof(pretty_name))) { - snprintf(os_version, sizeof(os_version), "%s", pretty_name); + safe_strcpy(os_version, pretty_name, sizeof(os_version), 0); } else if (read_os_release_value("NAME", name, sizeof(name))) { if (read_os_release_value("VERSION_ID", version_id, sizeof(version_id))) { snprintf(os_version, sizeof(os_version), "%s %s", name, version_id); } else { - snprintf(os_version, sizeof(os_version), "%s", name); + safe_strcpy(os_version, name, sizeof(os_version), 0); } } else { - snprintf(os_version, sizeof(os_version), "%s", system_info.sysname); + safe_strcpy(os_version, system_info.sysname, sizeof(os_version), 0); } snprintf(details, sizeof(details), "%s %s • %s", @@ -224,7 +224,7 @@ static void add_versions_to_json(cJSON *info) { snprintf(curl_details, sizeof(curl_details), "%s • zlib %s", curl_info->ssl_version, curl_info->libz_version); } else if (curl_info->ssl_version) { - snprintf(curl_details, sizeof(curl_details), "%s", curl_info->ssl_version); + safe_strcpy(curl_details, curl_info->ssl_version, sizeof(curl_details), 0); } else if (curl_info->libz_version) { snprintf(curl_details, sizeof(curl_details), "zlib %s", curl_info->libz_version); } @@ -1254,7 +1254,7 @@ void handle_get_system_info(const http_request_t *req, http_response_t *res) { // Compute recordings directory size using native filesystem traversal. // storage_path is NEVER passed to a shell command (prevents injection). char recordings_dir[512]; - snprintf(recordings_dir, sizeof(recordings_dir), "%s", g_config.storage_path); + safe_strcpy(recordings_dir, g_config.storage_path, sizeof(recordings_dir), 0); /* strip any trailing slash so lstat/opendir work consistently */ size_t rd_len = strlen(recordings_dir); if (rd_len > 1 && recordings_dir[rd_len - 1] == '/') diff --git a/src/web/thumbnail_thread.c b/src/web/thumbnail_thread.c index 8f767e46..649a01c5 100644 --- a/src/web/thumbnail_thread.c +++ b/src/web/thumbnail_thread.c @@ -22,6 +22,7 @@ #define LOG_COMPONENT "Thumbnail" #include "core/logger.h" #include "utils/memory.h" +#include "utils/strings.h" // Maximum concurrent thumbnail generations #define MAX_CONCURRENT_THUMBNAILS 4 @@ -305,8 +306,8 @@ int thumbnail_thread_submit(uint64_t recording_id, int index, work->recording_id = recording_id; work->index = index; - snprintf(work->input_path, sizeof(work->input_path), "%s", input_path); - snprintf(work->output_path, sizeof(work->output_path), "%s", output_path); + safe_strcpy(work->input_path, input_path, sizeof(work->input_path), 0); + safe_strcpy(work->output_path, output_path, sizeof(work->output_path), 0); work->seek_seconds = seek_seconds; work->deferred_action = deferred_action; work->callback = callback;