Compare commits

...

42 Commits

Author SHA1 Message Date
Brendan LE GLAUNEC c0d890acad Merge pull request #20 from EtixLabs/bugfix/version-1.1.1
v1.1.1 : Fixed functional tests & Multiple bugfixes
2016-11-12 13:13:14 +01:00
Brendan LE GLAUNEC 553524ae43 v1.1.1 : Fixed functional tests & Multiple bugfixes 2016-11-12 12:55:32 +01:00
Brendan LE GLAUNEC f80af6bd58 Merge pull request #18 from EtixLabs/bugfix/remove-unnecessary-null-pointer-checks
v1.1.0 : Removed unnecessary null pointer checks
2016-11-03 16:36:46 +01:00
Brendan LE GLAUNEC 6c127e4cbe v1.1.0 : Removed unnecessary null pointer checks 2016-11-03 16:34:54 +01:00
Brendan LE GLAUNEC c16c8c0aaa v1.1.0 : Docker Hub Readme changes 2016-11-03 08:45:20 +01:00
Brendan LE GLAUNEC be74c3c814 v1.1.0 : Deployment updated & Docker Hub 2016-11-02 14:42:33 +01:00
Brendan LE GLAUNEC 2f93ddd7e5 v1.1.0 : Updated deployment 2016-11-02 14:42:22 +01:00
Brendan LE GLAUNEC 108f869a43 v1.1.0 : Updated package 2016-11-02 08:42:45 +01:00
Brendan LE GLAUNEC 6685f74a90 v1.1.0 : Fixed multithreading & added timeout to ffmpeg 2016-11-02 07:56:03 +01:00
Brendan LE GLAUNEC 37176292d0 Merge branch 'master' of github.com:EtixLabs/cameradar 2016-10-31 10:13:52 +01:00
Brendan LE GLAUNEC a49b8ef481 v1.1.0 : Updated package 2016-10-31 10:02:53 +01:00
Brendan LE GLAUNEC 8e26751247 v1.1.0 : Added GST RTSP standard in cmd line 2016-10-31 10:01:13 +01:00
Brendan LE GLAUNEC 2564943ae7 Merge pull request #15 from EtixLabs/feature/improve-cameradar-ux
Feature/improve cameradar ux last details
2016-10-29 08:59:48 +02:00
Brendan LE GLAUNEC 74b4590758 v1.1.0 : Added package generation to README 2016-10-29 08:57:51 +02:00
Brendan LE GLAUNEC 9e9c1ba5b6 v1.1.0 : Updated package name & added packge generation script 2016-10-29 08:55:14 +02:00
Brendan LE GLAUNEC 9d78e84dc0 Merge pull request #14 from EtixLabs/feature/improve-cameradar-ux
v1.1.0 : Multithreading & UX update
2016-10-28 11:15:36 +02:00
Brendan LE GLAUNEC ecd318d0c2 v1.1.0 : Update Readme & Removed debug logs 2016-10-28 10:53:41 +02:00
Brendan LE GLAUNEC 58b101ed60 v1.1.0 : Multithreading & UX update 2016-10-28 09:50:37 +02:00
Brendan LE GLAUNEC 7e6c501582 Merge branch 'master' of github.com:EtixLabs/cameradar 2016-10-24 14:02:59 +02:00
Brendan LE GLAUNEC d9a221f9c6 Added standard Comelit RTSP URL to dictionary 2016-10-24 14:02:51 +02:00
Brendan LE GLAUNEC c10525b50e Added Cameradar logo to README.md 2016-10-13 15:51:30 +02:00
Brendan LE GLAUNEC 67b118a82e Added Cameradar logo 2016-10-13 15:43:48 +02:00
Brendan LE GLAUNEC 1f5db9baa0 Updated testing binary 2016-09-12 14:28:28 +02:00
Brendan LE GLAUNEC 4ef463d8a9 Merge branch 'master' of github.com:EtixLabs/cameradar 2016-08-31 13:34:01 +02:00
Brendan LE GLAUNEC ae3329bd25 v1.0.5 : Fixed a potential failure in MySQL CM and fixed code 2016-08-31 12:46:21 +02:00
Brendan LE GLAUNEC 46e17bb0ee Create CHANGELOG.md 2016-08-31 10:36:39 +02:00
Brendan LE GLAUNEC 27b296c9d2 v1.0.4 : Fixed nmap package detection 2016-08-31 09:39:08 +02:00
Brendan LE GLAUNEC 006c0139be Merge branch 'master' of github.com:EtixLabs/cameradar 2016-08-30 16:59:08 +02:00
Brendan LE GLAUNEC 63119d3ff3 v1.0.3 : Corrected GStreamer check 2016-08-30 16:58:46 +02:00
Brendan LE GLAUNEC 5859e9c595 Removed forgotten logs 2016-08-26 12:59:46 +02:00
Brendan LE GLAUNEC d0220ceb7f v1.0.2 - Fix issues with MySQL CM 2016-08-24 12:11:54 +02:00
Brendan LE GLAUNEC 064a6ff588 v1.0.1 : Removed useless text from the Readme 2016-07-08 15:05:17 +02:00
Brendan LE GLAUNEC 9a269bfe0e v1.0.1 : Updated to 16.04 & removed boost dependency 2016-07-07 17:47:08 +02:00
Brendan LE GLAUNEC c44b933a83 v 1.0.0 - Changed tag - Updated deployment version 2016-06-21 11:00:35 +02:00
Brendan LE GLAUNEC 1f5e9fc502 v 1.0.0 - Added functionnal testing - Needs Travis integration 2016-06-21 10:53:24 +02:00
Brendan LE GLAUNEC 08231074b9 Update README.md 2016-06-07 12:38:17 +02:00
Brendan LE GLAUNEC c6d801750e Cameradar now waits for MySQL before being deployed 2016-06-03 09:07:07 +02:00
Brendan LE GLAUNEC 2cf49a8db4 Update CMakeLists.txt 2016-06-03 08:49:55 +02:00
Brendan LE GLAUNEC 4fba8a8594 Update README.md 2016-06-02 10:13:48 +02:00
Brendan LE GLAUNEC 76365e3a07 v0.2.2 : Cameradar now supports badly configured cameras 2016-05-27 14:36:52 +02:00
Brendan LE GLAUNEC e6a38af241 Update README.md 2016-05-26 10:52:29 +02:00
Brendan LE GLAUNEC a4ad49c1a7 Quick MySQL docker deployment & code cleaning 2016-05-24 08:56:11 +02:00
82 changed files with 2377 additions and 603 deletions
+15
View File
@@ -26,3 +26,18 @@
*.exe *.exe
*.out *.out
*.app *.app
# Results
result.json
test-results.xml
# Build
build/
# JetBrains
.idea/
# Deps
deps/boost/
deps/jsoncpp/
mysql-connector/
+160
View File
@@ -0,0 +1,160 @@
# Cameradar Changelog
This file lists all versions of the repository and precises all changes.
## v1.1.1
#### Minor changes :
* Removed unnecessary null pointer checks (thanks to https://github.com/elfring)
* Updated package description
* Removed debug message in CMake build
* Added `/ch01.264` to the URL dictionary in the deployment (Comelit default RTSP URL)
* Updated tests partially (still needs work to make the code cleaner)
* Variable names are now compliant with Golang best practices
* JSON variable names are back to normal
* Functions have been moved in more appropriate source files
* Structure definitions have been moved in more appropriate source files
* Source files have been renamed to be more relevant
* JUnit output now considers each camera as a test case
* JUnit output now contains errors which makes debugging much easier
* Added header files where it was forgotten
#### Bugfixes :
* Fixed an issue where if you loose your internet connection during thumbnail generation, FFMpeg would get stuck forever and thus Cameradar would never finish
* Fixed an issue where multithreading could cause crashes
* Fixed an issue where the routes dictionary was mistaken for the credentials dictionary
* Fixed issues with the golang testing tool
* Fixed automated camera generation
* Fixed docker IP address resolution
#### Known issues :
* There is an issue with Camera Emulation Server that makes it impossible for Cameradar to generate thumbnails, which is why right now the verification of the thumbnails presence is commented and it is assumed correct. It is probably an issue with GST-RTSP-Server but requires investigation.
## v1.1.0
#### Major changes :
* There are more command line options
* Port can now be overridden in the command line
* Subnet can now be overridden in the command line
* Bruteforce is now multithreaded and will use as many threads as there are discovered cameras
* Thumbnail generation is now multithreaded and will use as many threads as there are discovered cameras
* There are now default configuration values in order to make cameradar easier to use
#### Minor changes :
* The algorithms take external input into account (so that a 3rd party can change the DB to help Cameradar in real-time) and thus check the persistent data at each iteration
* The default log level is now DEBUG instead of INFO
* The bruteforce logs are now INFO instead of DEBUG
* The thumbnail generation logs are now INFO instead of DEBUG
#### Bugs fixed
* Fixed a bug in which the MySQL cache manager would consider a camera with known ids as having a valid path even if it weren't
* Fixed a bug in which TCP RTSP streams would not generate thumbnails
## v1.0.5
* Fixed error in MySQL Cache Manager in which thumbnail generation on valid streams could not be done
* Fixed potential crash in the case the machine running cameradar has no memory left to allocate space for the dynamic cache manager
## v1.0.4
#### Bugs fixed :
* Fixed nmap package detection
## v1.0.3
#### Bugs fixed :
* Corrected GStreamer check
## v1.0.2
#### Bugs fixed :
* Fixed issues in MySQL Cache Manager
#### Minor changes :
* Added useful debug logs
## v1.0.1
### Ubuntu 16.04 Release
#### Major changes :
* The Docker deployment is now done using Ubuntu 16.04 instead of Ubuntu 15.10, so that it uses more recent packages.
#### Minor changes :
* Removed useless dependencies
## v1.0.0
### First production-ready release
#### Major changes :
* Added functional testing
## v0.2.2
After doing some testing on a weirdly configured camera network in a far away Datacenter, I discovered that some Cameras needed a few tweaks to the Cameradar bruteforcing method in order to be accessed.
#### Major changes :
* Cameradar can access Cameras that are configured to always send 400 Bad Requests responses
#### Minor changes :
* Changed iterator name from `it` to `stream` in dumb cache manager to improve code readability
#### Bugfixes :
* Cameradar no longer considers a timing out Camera as an accessible stream
## v0.2.1
This package adds fixes the Docker deployment package.
#### Minor changes
* Fixed the Docker deployment package
* Updated README
## v0.2.0
### MySQL Cache Manager Release
This package adds a new cache manager using a MySQL database, that can store the results between mutiple uses.
#### Major changes
* Added a MySQL Cache Manager
#### Minor changes
* Removed legacy code
* Removed boost dependency
* Improved debugging logs
## v0.1.1
### Docker release
This package adds a way to deploy Cameradar using Docker.
#### Major changes
* Added a quick Docker deployment process
* Added automatic dependencies downloading through CMake for the manual installation
* Added CPack packaging for the Docker deployment
#### Minor changes
* Changed recommended cloning method to HTTPS
* Added lots of informations to README.md
## v0.1.0
This package was the first OpenSource version of Cameradar. It contained only a simple cache manager and had some bugs.
+12 -12
View File
@@ -19,10 +19,9 @@ set (PROJECT_NAME cameradar)
project (${PROJECT_NAME}) project (${PROJECT_NAME})
set (${PROJECT_NAME}_VERSION_MAJOR 0) set (${PROJECT_NAME}_VERSION_MAJOR 1)
set (${PROJECT_NAME}_VERSION_MINOR 1) set (${PROJECT_NAME}_VERSION_MINOR 1)
set (${PROJECT_NAME}_VERSION_PATCH 1) set (${PROJECT_NAME}_VERSION_PATCH 1)
set (${PROJECT_NAME}_SUFFIX "-beta")
set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}") set (${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}${${PROJECT_NAME}_SUFFIX}")
find_package(Git REQUIRED) find_package(Git REQUIRED)
@@ -33,7 +32,7 @@ set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Wextra -Wno-unused-function")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color") #enable error coloration on gcc set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color") #enable error coloration on gcc
# release specific flags # release specific flags
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") #enable error coloration on gcc set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
#debug specific flags #debug specific flags
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
@@ -92,17 +91,18 @@ link_directories (
) )
include_directories ( include_directories (
"cameradar_standalone/include" "cameradar_standalone/include"
"deps/jsoncpp/src/deps.jsoncpp/include" "deps/jsoncpp/src/deps.jsoncpp/include"
"deps/boost/src/deps.boost/include" "deps/boost/src/deps.boost/include"
"deps/mysql-connector/include" "deps/mysql-connector/include"
) )
set (${CAMERADAR_BINARIES} "") set (${CAMERADAR_BINARIES} "")
set (${CAMERADAR_LIBRARIES} "") set (${CAMERADAR_LIBRARIES} "")
#build cache managers # Build cache managers
add_subdirectory (deps) add_subdirectory (deps)
message ("Debug")
add_subdirectory (cameradar_standalone) add_subdirectory (cameradar_standalone)
add_subdirectory (cache_managers) add_subdirectory (cache_managers)
@@ -113,17 +113,17 @@ install (FILES ${CAMERADAR_CACHE_MANAGERS} DESTINATION cache_managers)
install (FILES ${CAMERADAR_LIBRARIES} DESTINATION libraries) install (FILES ${CAMERADAR_LIBRARIES} DESTINATION libraries)
install (DIRECTORY ${CMAKE_SOURCE_DIR}/deps/licenses DESTINATION libraries) install (DIRECTORY ${CMAKE_SOURCE_DIR}/deps/licenses DESTINATION libraries)
# cpack configuration # CPack configuration
include (InstallRequiredSystemLibraries) include (InstallRequiredSystemLibraries)
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar") set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar")
set (CPACK_PACKAGE_VENDOR "Etix Labs") set (CPACK_PACKAGE_VENDOR "Etix Labs")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "cameradar tool") set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cameradar hacks its way into RTSP CCTV cameras")
set (CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}_${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}") set (CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}_${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}")
set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set (CPACK_PACKAGE_VERSION_MAJOR "0") set (CPACK_PACKAGE_VERSION_MAJOR "0")
set (CPACK_PACKAGE_VERSION_MINOR "1") set (CPACK_PACKAGE_VERSION_MINOR "2")
set (CPACK_PACKAGE_VERSION_PATCH "0") set (CPACK_PACKAGE_VERSION_PATCH "2")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}") set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}_${${PROJECT_NAME}_VERSION}")
set (CPACK_GENERATOR "TGZ") set (CPACK_GENERATOR "TGZ")
set (CPACK_SOURCE_GENERATOR "TGZ") set (CPACK_SOURCE_GENERATOR "TGZ")
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

+123 -24
View File
@@ -2,6 +2,10 @@
## An RTSP surveillance camera access multitool ## An RTSP surveillance camera access multitool
[![cameradar License](https://img.shields.io/badge/license-Apache-blue.svg)](#license)
[![Latest release](https://img.shields.io/badge/release-1.1.1-green.svg)](https://github.com/EtixLabs/cameradar/releases/latest)
#### Cameradar allows you to: #### Cameradar allows you to:
* **Detect open RTSP hosts** on any accessible subnetwork * **Detect open RTSP hosts** on any accessible subnetwork
@@ -14,10 +18,13 @@
#### And all of this in a _single command-line_. #### And all of this in a _single command-line_.
Of course, you can also call for individual tasks if you plug in a Database to Cameradar, but for now this repo only contains a basic cache manager. You can however create your own by following the simple example of the **dumb cache manager**. Of course, you can also call for individual tasks if you plug in a Database to Cameradar using the MySQL cache manager for example. You can create your own cache manager by following the simple example of the **dumb cache manager**.
<p align="center"><img src="https://raw.githubusercontent.com/EtixLabs/cameradar/master/Cameradar.png" width="350"/></p>
## Table of content ## Table of content
- [Docker Image](#docker-image)
- [Quick install](#quick-install) - [Quick install](#quick-install)
- [Dependencies](#quick-install###dependencies) - [Dependencies](#quick-install###dependencies)
- [Five steps guide](#quick-install###five-steps-guide) - [Five steps guide](#quick-install###five-steps-guide)
@@ -34,23 +41,53 @@ Of course, you can also call for individual tasks if you plug in a Database to C
- [Under the hood](#under-the-hood) - [Under the hood](#under-the-hood)
- [Contribution](#contribution) - [Contribution](#contribution)
- [Next improvements](#next-improvements) - [Next improvements](#next-improvements)
- [Frequently Asked Questions](#frequently-asked-questions)
- [License](#license) - [License](#license)
## Docker Image
This is the fastest and simplest way to use Cameradar. To do this you will just need `docker` on your machine.
Run
```
docker run \
-v /tmp/thumbs/:/tmp/thumbs \
-e CACHE_MANAGER=your_manager \
-e CAMERAS_PORTS=your_ports \
-e CAMERAS_SUBNETWORKS=your_subnetwork \
ullaakut/cameradar:tag
```
* `your_subnetwork` can be a subnet (e.g.: `172.16.100.0/24`) or even an IP (e.g.: `172.16.100.10`).
* `your_ports` can be one port, multiple ports and even port ranges (e.g.: `554,8554,9000-9554`)
* `your_manager` can be either `dumb` or `mysql` but you probably want to use `dumb`. Check [Cameradar's readme on the Docker Hub](https://hub.docker.com/r/ullaakut/cameradar/) for more information.
* `tag` allows you to specify a specific version for camerada. If you don't specify any tag, you will use the latest version by default (recommended)
The generated thumbnails will be in `/tmp/thumbs` on both your machine and the `cameradar` container.
For more complex use of the Docker image, see the `Environment variables` part of [Cameradar's readme on the Docker Hub](https://hub.docker.com/r/ullaakut/cameradar/).
## Quick install ## Quick install
The quick install uses docker to build Cameradar without polluting your machine with dependencies and makes it easy to deploy Cameradar in a few commands. **However, it may require networking knowledge, as your docker containers will need access to the cameras subnetwork.** The quick install uses docker to build Cameradar without polluting your machine with dependencies and makes it easy to deploy Cameradar in a few commands. **However, it may require networking knowledge, as your docker containers will need access to the cameras subnetwork.**
### Dependencies ### Dependencies
The only dependencies are `docker` and `docker-compose`. The only dependencies are `docker`, `docker-tools`, `git` and `make`.
### Five steps guide ### Five steps guide
1. `git clone https://github.com/EtixLabs/cameradar.git` 1. `git clone https://github.com/EtixLabs/cameradar.git`
2. Go into the Cameradar repository, then to the `deployment` directory 2. Go into the Cameradar repository, then to the `deployment` directory
3. Tweak the `conf/cameradar.conf.json` as you need (see [the onfiguration guide here](#configuration) for more information) 3. Tweak the `conf/cameradar.conf.json` as you need (see [the onfiguration guide here](#configuration) for more information)
4. Run `docker-compose build cameradar` to build the cameradar container 4. Run `docker-compose build & docker-compose up`
5. Run `docker-compose up cameradar` to launch Cameradar
By default, the version of the package in the deployment should be the last stable release.
If you want to scan a different subnetwork or different ports, change the values `CAMERAS_SUBNETWORKS` and `CAMERAS_PORTS` in the `docker-compose.yml` file.
The generated thumbnails will be in the `cameradar_thumbnails` folder after cameradar has finished executing.
If you want to deploy your custom version of Cameradar using the same method, you should check the [advanced docker deployment](#advanced-docker-deployment) tutorial here. If you want to deploy your custom version of Cameradar using the same method, you should check the [advanced docker deployment](#advanced-docker-deployment) tutorial here.
@@ -63,8 +100,10 @@ The manual installation is recommended if you want to tweak Cameradar and quickl
To install Cameradar you will need these packages To install Cameradar you will need these packages
* cmake (`cmake`) * cmake (`cmake`)
* git (`git`)
* gstreamer1.x (`libgstreamer1.0-dev`) * gstreamer1.x (`libgstreamer1.0-dev`)
* ffmpeg (`ffmpeg`) * ffmpeg (`ffmpeg`)
* boost (`libboost-all-dev`)
* libcurl (`libcurl4-openssl-dev`) * libcurl (`libcurl4-openssl-dev`)
### Steps ### Steps
@@ -72,34 +111,52 @@ To install Cameradar you will need these packages
The simplest way would be to follow these steps : The simplest way would be to follow these steps :
1. `git clone https://github.com/EtixLabs/cameradar.git` 1. `git clone https://github.com/EtixLabs/cameradar.git`
2. Go into the Cameradar repository, create a directory named `build` and go in it 2. `mkdir build`
3. In the build directory, run `cmake ..` This will generate the Makefiles you need to build Cameradar 3. `cd build`
4. Run the command `make` 3. `cmake ..`
5. This should compile Cameradar. Go into the `cameradar_standalone` directory 4. `make`
6. You can now customize the `conf/cameradar.conf.json` file to set the subnetworks and specific ports you want to scan, as well as the thumbnail generation path. More information will be given about the configuration file in another part of this document. 5. `cd cameradar_standalone`
7. You are now ready to launch Cameradar by launching `./cameradar` in the cameradar_standalone directory. 6. `./cameradar -s the_subnet_you_want_to_scan`
## Advanced Docker deployment ## Advanced Docker deployment
In case you want to use Docker to deploy your custom version of Cameradar.
### Dependencies ### Dependencies
The only dependencies are `docker` and `docker-compose`. The only dependencies are `docker` and `docker-compose`.
### Deploy a custom version of Cameradar ### Using the package generation script
1. `git clone https://github.com/EtixLabs/cameradar.git`
2. `cd deployment`
3. `rm *.tar.gz`
4. `./build_last_package.sh`
5. `docker-compose build cameradar`
6. `docker-compose up cameradar`
2. Go into the Cameradar repository, create a directory named `build` and go in it ### Deploy a custom version of Cameradar by hand
3. In the build directory, run `cmake .. -DCMAKE_BUILD_TYPE=Release` This will generate the Makefiles you need to build Cameradar
4. Run the command `make package` to compile it into a package
5. Copy your package into the `deployment` directory
6. Run `docker-compose build cameradar` to build the cameradar container using your custom package
5. Run `docker-compose up cameradar` to launch Cameradar
1. `git clone https://github.com/EtixLabs/cameradar.git`
2. `cd build`
3. `cmake .. -DCMAKE_BUILD_TYPE=Release`
4. `make package`
5. `cp cameradar_*_Release_Linux.tar.gz ../deployment`
6. `cd ../deployment`
7. `docker-compose build cameradar`
8. `docker-compose up cameradar`
### Configuration ### Configuration
Here is the basic content of the configuration file with simple placeholders : Here is the basic content of the configuration file with simple placeholders :
```json ```json
{ {
"mysql_db" : {
"host" : "MYSQL_SERVER_IP_ADDRESS",
"port" : MYSQL_SERVER_PORT,
"user": "root",
"password": "root",
"db_name": "cmrdr"
},
"subnets" : "SUBNET1,SUBNET2,SUBNET3,[...]", "subnets" : "SUBNET1,SUBNET2,SUBNET3,[...]",
"ports" : "PORT1,PORT2,[...]", "ports" : "PORT1,PORT2,[...]",
"rtsp_url_file" : "conf/url.json", "rtsp_url_file" : "conf/url.json",
@@ -110,20 +167,39 @@ Here is the basic content of the configuration file with simple placeholders :
} }
``` ```
This **configuration is needed only if you want to overwrite the default values**, which are :
```json
{
"subnets" : "localhost",
"ports" : "554,8554",
"rtsp_url_file" : "conf/url.json",
"rtsp_ids_file" : "conf/ids.json",
"thumbnail_storage_path" : "/tmp",
"cache_manager_path" : "../cache_managers/dumb_cache_manager",
"cache_manager_name" : "dumb"
}
```
This means that **by default Cameradar will not use a database**, will scan localhost and the ports 554 (default RTSP port) and 8554 (default emulated RTSP port), use the default constructor dictionaries and store the thumbnails in `/tmp`. If you need to override simply the subnets or ports, you can use the [command line options](#command-line-options).
The subnetworks should be passed separated by commas only, and their subnet format should be the same as used in nmap. The subnetworks should be passed separated by commas only, and their subnet format should be the same as used in nmap.
```json ```json
"subnets" : "172.100.16.0/24,172.100.17.0/24,localhost,192.168.1.13" "subnets" : "172.100.16.0/24,172.100.17.0/24,localhost,192.168.1.13"
``` ```
The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks..How is formatted Cameradar's result The **RTSP ports for most cameras are 554**, so you should probably specify 554 as one of the ports you scan. Not giving any ports in the configuration will scan every port of every host found on the subnetworks.
You **can use your own files for the ids and routes dictionaries** used to bruteforce the cameras, but the Cameradar repo already gives you a good base that works with most cameras.
You **can use your own files for the ids and routes dictionaries** used to bruteforce the cameras, but the Cameradar repository already gives you a good base that works with most cameras.
The thumbnail storage path should be a **valid and accessible directory** in which the thumbnails will be stored. The thumbnail storage path should be a **valid and accessible directory** in which the thumbnails will be stored.
The cache manager path and name variables are used to change the cache manager you want to load into Cameradar. If you want to, you can code your own cache manager using a database, a file, a remote server, [...]. Feel free to share it by creating a merge request on this repo if you developed a generic manager (It must not be specific to your company's infrastructure). The cache manager path and name variables are used to change the cache manager you want to load into Cameradar. If you want to, you can code your own cache manager using a database, a file, a remote server, [...]. Feel free to share it by creating a merge request on this repository if you developed a generic manager (It must not be specific to your company's infrastructure).
## Output ## Output
For each camera, Cameradar will output these JSON objects :
```json ```json
{ {
"address" : "173.16.100.45", "address" : "173.16.100.45",
@@ -143,7 +219,7 @@ The cache manager path and name variables are used to change the cache manager y
## Check camera access ## Check camera access
If you have vlc, you should be able to use the GUI to connect to the RTSP stream using this format : `username:password@address:port/route` If you have [VLC Media Player](http://www.videolan.org/vlc/), you should be able to use the GUI to connect to the RTSP stream using this format : `username:password@address:port/route`
With the above result, the RTSP URL would be `admin:123456@173.16.100.45:554/live.sdp` With the above result, the RTSP URL would be `admin:123456@173.16.100.45:554/live.sdp`
@@ -152,6 +228,9 @@ If you're still in your console however, you can go even faster by using **vlc i
## Command line options ## Command line options
* **"-c"** : Set a custom path to the configuration file (-c /path/to/conf) * **"-c"** : Set a custom path to the configuration file (-c /path/to/conf)
* **"-s"** : Set custom subnets (overrides configuration)
* **"-p"** : Set custom ports (overrides configuration)
* **"-m"** : Set number of threads (*Default value : 1*)
* **"-l"** : Set log level * **"-l"** : Set log level
* **"-l 1"** : Log level DEBUG * **"-l 1"** : Log level DEBUG
* _Will print everything including debugging logs_ * _Will print everything including debugging logs_
@@ -172,6 +251,7 @@ If you're still in your console however, you can go even faster by using **vlc i
* Needs either to be launched with the -d option or to use an advanced cache manager (DB, file, ...) with data already present * Needs either to be launched with the -d option or to use an advanced cache manager (DB, file, ...) with data already present
* **"-v"** : Display Cameradar's version * **"-v"** : Display Cameradar's version
* **"-h"** : Display this help * **"-h"** : Display this help
* **"--gst-rtsp-server"** : Use this option if the bruteforce does not seem to work (only detects the username but not the path, or the opposite). This option will switch the order of the bruteforce to prioritize path over credentials, which is the way priority is handled for cameras that use GStreamer's RTSP server.
## Under the hood ## Under the hood
@@ -197,18 +277,37 @@ The output of Cameradar will be printed on the standard output and will also be
## Contribution ## Contribution
Well there are many things we could code in order to add features to Cameradar. Adding other protocols than RTSP would be really cool, as well as making generic cache managers. Creating an HTTP server with an API that would launch cameradar upon recieving requests ans answer with Cameradar's result would also be potentially really useful. Well there are many things we could code in order to add features to Cameradar. Adding other protocols than RTSP would be really cool, as well as making more generic cache managers. Improving Cameradar's performance or even the deployment could also be a great help!
If you're not into software development or not into C++, even updating the dictionaries would be a really cool contribution! Just make sure the ids and routes you add are **default constructor credentials** and not custom credentials. If you're not into software development or not into C++, even updating the dictionaries would be a really cool contribution! Just make sure the ids and routes you add are **default constructor credentials** and not custom credentials.
If you have other cool ideas, feel free to share them with me at brendan.leglaunec@etixgroup.com ! If you have other cool ideas, feel free to share them with me at [brendan.leglaunec@etixgroup.com](mailto:brendan.leglaunec@etixgroup.com) !
## Next improvements ## Next improvements
- [x] Add a docker deployment to avoid the current deps hell - [x] Add a docker deployment to avoid the current deps hell
- [x] Development of a MySQL cache manager - [x] Development of a MySQL cache manager
- [ ] Development of a JSON file cache manager - [ ] Development of a JSON file cache manager
- [ ] Development of an XML file cache manager - [ ] Development of an XML file cache manager
- [ ] Make a standalone docker image
- [ ] Push to DockerHub
## Frequently Asked Questions
> My camera's credentials are guessed by Cameradar but the RTSP url is not!
Your camera probably uses GST RTSP Server internally. Try the `--gst-rtsp-server` command-line option, and if it does not work, send me the cameradar output in DEBUG mode (`-l 1`) and I will help you.
> Cameradar does not detect any camera!
That means that either your cameras are not streaming in RTSP or that they are not on the subnetwork you are scanning. In most cases, CCTV cameras will be on a private subnetwork. Use the `-s` option to specify your camera's subnetwork.
> Cameradar detects my cameras, but does not manage to access them at all!
Maybe your cameras have been configured and the credentials / URL have been changed. Cameradar only guesses using default constructor values. However, you can use your own dictionary in which you just have to add your passwords. To do that, see how the [configuration](#configuration) works.
> It does not compile
You probably missed the part with the dependencies! Use the quick docker deployment, it will be easier and will not pollute your machine with useless dependencies! `;)`
## License ## License
-1
View File
@@ -21,5 +21,4 @@ set (LIBRARY_OUTPUT_PATH ${CAMERADAR_CACHE_MANAGER_OUTPUT_PATH})
add_subdirectory(dumb_cache_manager) add_subdirectory(dumb_cache_manager)
add_subdirectory(mysql_cache_manager) add_subdirectory(mysql_cache_manager)
message (${CAMERADAR_CACHE_MANAGERS})
set (CAMERADAR_CACHE_MANAGERS ${CAMERADAR_CACHE_MANAGERS} PARENT_SCOPE) set (CAMERADAR_CACHE_MANAGERS ${CAMERADAR_CACHE_MANAGERS} PARENT_SCOPE)
@@ -29,6 +29,8 @@ private:
std::vector<etix::cameradar::stream_model> streams; std::vector<etix::cameradar::stream_model> streams;
std::shared_ptr<etix::cameradar::configuration> configuration; std::shared_ptr<etix::cameradar::configuration> configuration;
std::mutex m;
public: public:
using cache_manager_base::cache_manager_base; using cache_manager_base::cache_manager_base;
~dumb_cache_manager(); ~dumb_cache_manager();
@@ -38,6 +40,8 @@ public:
bool load_dumb_conf(std::shared_ptr<etix::cameradar::configuration> configuration); bool load_dumb_conf(std::shared_ptr<etix::cameradar::configuration> configuration);
bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override; bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override;
bool has_changed(const etix::cameradar::stream_model&);
void set_streams(std::vector<etix::cameradar::stream_model> model); void set_streams(std::vector<etix::cameradar::stream_model> model);
void update_stream(const etix::cameradar::stream_model& newmodel); void update_stream(const etix::cameradar::stream_model& newmodel);
@@ -47,14 +47,18 @@ dumb_cache_manager::load_dumb_conf(std::shared_ptr<etix::cameradar::configuratio
//! parameter //! parameter
void void
dumb_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> model) { dumb_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> model) {
std::lock_guard<std::mutex> lock(m);
this->streams = model; this->streams = model;
} }
//! Inserts a single stream to the cache //! Inserts a single stream to the cache
void void
dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel) { dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel) {
for (auto& it : this->streams) { std::lock_guard<std::mutex> lock(m);
if (it.address == newmodel.address && it.port == newmodel.port) { it = newmodel; } for (auto& stream : this->streams) {
if (stream.address == newmodel.address && stream.port == newmodel.port) {
stream = newmodel;
}
} }
} }
@@ -62,8 +66,9 @@ dumb_cache_manager::update_stream(const etix::cameradar::stream_model& newmodel)
std::vector<etix::cameradar::stream_model> std::vector<etix::cameradar::stream_model>
dumb_cache_manager::get_streams() { dumb_cache_manager::get_streams() {
std::vector<stream_model> ret; std::vector<stream_model> ret;
for (const auto& it : this->streams) { for (const auto& stream : this->streams) {
if (not it.service_name.compare("rtsp") && not it.state.compare("open")) ret.push_back(it); if (not stream.service_name.compare("rtsp") && not stream.state.compare("open"))
ret.push_back(stream);
} }
return ret; return ret;
} }
@@ -72,14 +77,23 @@ dumb_cache_manager::get_streams() {
std::vector<etix::cameradar::stream_model> std::vector<etix::cameradar::stream_model>
dumb_cache_manager::get_valid_streams() { dumb_cache_manager::get_valid_streams() {
std::vector<stream_model> ret; std::vector<stream_model> ret;
for (const auto& it : this->streams) { for (const auto& stream : this->streams) {
if ((not it.service_name.compare("rtsp") && not it.state.compare("open")) && it.ids_found && if (stream.ids_found && stream.path_found) ret.push_back(stream);
it.path_found)
ret.push_back(it);
} }
return ret; return ret;
} }
// Returns true if the stream passed as a parameter has changed in the cache
bool
dumb_cache_manager::has_changed(const etix::cameradar::stream_model& old) {
for (const auto& stream : this->streams) {
if (stream.address == old.address)
if (stream.path_found != old.path_found || stream.ids_found != old.ids_found)
return true;
}
return false;
}
extern "C" { extern "C" {
cache_manager_iface* cache_manager_iface*
cache_manager_instance_new() { cache_manager_instance_new() {
@@ -51,6 +51,8 @@ private:
etix::cameradar::mysql_configuration db_conf; etix::cameradar::mysql_configuration db_conf;
etix::cameradar::mysql::db_connection connection; etix::cameradar::mysql::db_connection connection;
std::mutex m;
static const std::string create_table_query; static const std::string create_table_query;
static const std::string insert_with_id_query; static const std::string insert_with_id_query;
static const std::string exist_query; static const std::string exist_query;
@@ -69,6 +71,8 @@ public:
bool load_mysql_conf(std::shared_ptr<etix::cameradar::configuration> configuration); bool load_mysql_conf(std::shared_ptr<etix::cameradar::configuration> configuration);
bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override; bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) override;
bool has_changed(const etix::cameradar::stream_model&);
void set_streams(std::vector<etix::cameradar::stream_model> model); void set_streams(std::vector<etix::cameradar::stream_model> model);
void update_stream(const etix::cameradar::stream_model& newmodel); void update_stream(const etix::cameradar::stream_model& newmodel);
@@ -81,7 +81,7 @@ db_connection::execute(const std::string& request) {
return_value = { execute_result::no_row_updated, "No row updated" }; return_value = { execute_result::no_row_updated, "No row updated" };
} }
} catch (sql::SQLException& e) { return_value = { execute_result::sql_error, e.what() }; } } catch (sql::SQLException& e) { return_value = { execute_result::sql_error, e.what() }; }
if (stmt) { delete stmt; } delete stmt;
return return_value; return return_value;
} }
@@ -103,8 +103,7 @@ db_connection::query(const std::string& query) {
} catch (sql::SQLException& e) { } catch (sql::SQLException& e) {
return_value = { nullptr, execute_result::sql_error, e.what() }; return_value = { nullptr, execute_result::sql_error, e.what() };
} }
delete stmt;
if (stmt) { delete stmt; }
return return_value; return return_value;
} }
@@ -123,7 +123,7 @@ mysql_cache_manager::load_mysql_conf(
this->db_conf.user = configuration->raw_conf["mysql_db"]["user"].asString(); this->db_conf.user = configuration->raw_conf["mysql_db"]["user"].asString();
this->db_conf.password = configuration->raw_conf["mysql_db"]["password"].asString(); this->db_conf.password = configuration->raw_conf["mysql_db"]["password"].asString();
this->db_conf.db_name = configuration->raw_conf["mysql_db"]["db_name"].asString(); this->db_conf.db_name = configuration->raw_conf["mysql_db"]["db_name"].asString();
} catch (std::exception& e) { } catch (const std::exception& e) {
LOG_ERR_("Configuration of the MySQL db failed : " + std::string(e.what()), LOG_ERR_("Configuration of the MySQL db failed : " + std::string(e.what()),
"mysql_cache_manager"); "mysql_cache_manager");
return false; return false;
@@ -148,29 +148,31 @@ mysql_cache_manager::load_mysql_conf(
void void
mysql_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> models) { mysql_cache_manager::set_streams(std::vector<etix::cameradar::stream_model> models) {
LOG_DEBUG_("Beginning stream list DB insertion", "mysql_cache_manager"); LOG_DEBUG_("Beginning stream list DB insertion", "mysql_cache_manager");
std::lock_guard<std::mutex> lock(m);
for (const auto& model : models) { for (const auto& model : models) {
auto query = tool::fmt( if (!model.service_name.compare("rtsp") && !model.state.compare("open")) {
this->exist_query, this->connection.get_db_name().c_str(), model.address.c_str()); auto query = tool::fmt(
auto result = this->connection.query(query); this->exist_query, this->connection.get_db_name().c_str(), model.address.c_str());
// If an entry already exists for this address in the database, auto result = this->connection.query(query);
// no need to insert it.
if (result.data->next()) return;
query = tool::fmt(this->insert_with_id_query, if (result.data->next()) continue;
this->connection.get_db_name().c_str(),
model.address.c_str(), query = tool::fmt(this->insert_with_id_query,
model.password.c_str(), this->connection.get_db_name().c_str(),
model.product.c_str(), model.address.c_str(),
model.protocol.c_str(), model.password.c_str(),
model.route.c_str(), model.product.c_str(),
model.service_name.c_str(), model.protocol.c_str(),
model.state.c_str(), model.route.c_str(),
model.thumbnail_path.c_str(), model.service_name.c_str(),
model.username.c_str(), model.state.c_str(),
std::to_string(model.port).c_str(), model.thumbnail_path.c_str(),
std::to_string(model.ids_found).c_str(), model.username.c_str(),
std::to_string(model.path_found).c_str()); std::to_string(model.port).c_str(),
execute_query(query); std::to_string(model.ids_found).c_str(),
std::to_string(model.path_found).c_str());
execute_query(query);
}
} }
} }
@@ -192,6 +194,7 @@ mysql_cache_manager::update_stream(const etix::cameradar::stream_model& model) {
std::to_string(model.ids_found).c_str(), std::to_string(model.ids_found).c_str(),
std::to_string(model.path_found).c_str(), std::to_string(model.path_found).c_str(),
model.address.c_str()); model.address.c_str());
std::lock_guard<std::mutex> lock(m);
execute_query(query); execute_query(query);
} }
@@ -212,12 +215,12 @@ mysql_cache_manager::get_streams() {
if (not result.data->getString("state").compare("open") && if (not result.data->getString("state").compare("open") &&
not result.data->getString("service_name").compare("rtsp")) { not result.data->getString("service_name").compare("rtsp")) {
stream_model s{ stream_model s{
result.data->getString("address"), result.data->getUInt("port"), result.data->getString("address"), result.data->getUInt("port"),
result.data->getString("username"), result.data->getString("password"), result.data->getString("username"), result.data->getString("password"),
result.data->getString("route"), result.data->getString("service_name"), result.data->getString("route"), result.data->getString("service_name"),
result.data->getString("product"), result.data->getString("protocol"), result.data->getString("product"), result.data->getString("protocol"),
result.data->getString("state"), result.data->getBoolean("ids_found"), result.data->getString("state"), result.data->getBoolean("path_found"),
result.data->getBoolean("path_found"), result.data->getString("thumbnail_path") result.data->getBoolean("ids_found"), result.data->getString("thumbnail_path")
}; };
lst.push_back(s); lst.push_back(s);
} }
@@ -244,12 +247,12 @@ mysql_cache_manager::get_valid_streams() {
if (not result.data->getString("ids_found").compare("1") && if (not result.data->getString("ids_found").compare("1") &&
not result.data->getString("path_found").compare("1")) { not result.data->getString("path_found").compare("1")) {
stream_model s{ stream_model s{
result.data->getString("address"), result.data->getUInt("port"), result.data->getString("address"), result.data->getUInt("port"),
result.data->getString("username"), result.data->getString("password"), result.data->getString("username"), result.data->getString("password"),
result.data->getString("route"), result.data->getString("service_name"), result.data->getString("route"), result.data->getString("service_name"),
result.data->getString("product"), result.data->getString("protocol"), result.data->getString("product"), result.data->getString("protocol"),
result.data->getString("state"), result.data->getBoolean("ids_found"), result.data->getString("state"), result.data->getBoolean("path_found"),
result.data->getBoolean("path_found"), result.data->getString("thumbnail_path") result.data->getBoolean("ids_found"), result.data->getString("thumbnail_path")
}; };
lst.push_back(s); lst.push_back(s);
} }
@@ -259,6 +262,26 @@ mysql_cache_manager::get_valid_streams() {
return lst; return lst;
} }
// Returns true if the stream passed as a parameter has changed in the cache
bool
mysql_cache_manager::has_changed(const etix::cameradar::stream_model& old) {
auto query = tool::fmt(this->get_results_query, this->connection.get_db_name().c_str());
auto result = this->connection.query(query);
if (not result.data) {
delete result.data;
return {};
}
while (result.data->next()) {
if (result.data->getString("address") == old.address)
if (result.data->getBoolean("ids_found") != old.ids_found ||
result.data->getBoolean("path_found") != old.path_found)
return true;
}
return false;
}
extern "C" { extern "C" {
cache_manager_iface* cache_manager_iface*
cache_manager_instance_new() { cache_manager_instance_new() {
+1 -1
View File
@@ -45,7 +45,7 @@ if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
endif() endif()
include (find_sources) include (find_sources)
find_sources ("src" "include" "src/models" "src/repositories" "src/tasks") find_sources ("src" "include" "src/tasks")
add_executable (cameradar ${SOURCES}) add_executable (cameradar ${SOURCES})
target_link_libraries (cameradar pthread jsoncpp dl curl ${GSTREAMER_LIBRARIES}) target_link_libraries (cameradar pthread jsoncpp dl curl ${GSTREAMER_LIBRARIES})
+8 -17
View File
@@ -1,25 +1,16 @@
{ {
"mysql_db" : { "mysql_db" : {
"host" : "0.0.0.0", "host" : "cameradar-database",
"port" : 3306, "port" : 3306,
"user": "root", "user": "root",
"password": "root", "password": "root",
"db_name": "cctv_dev" "db_name": "cmrdr"
}, },
"subnets" : "localhost",
"subnets" : "172.16.100.11",
// If not specified, will scan all ports (1-65535)
"ports" : "554,8554", "ports" : "554,8554",
"rtsp_url_file" : "conf/url.json", "rtsp_url_file" : "/cameradar/conf/url.json",
"rtsp_ids_file" : "conf/ids.json", "rtsp_ids_file" : "/cameradar/conf/ids.json",
"thumbnail_storage_path" : "/tmp/thumbs",
// You must give an accessible path to an already existing directory "cache_manager_path" : "/cameradar/cache_managers",
"thumbnail_storage_path" : "/tmp", "cache_manager_name" : "dumb"
// This is the path that will be used in the Docker container
// if you're not familiar with Docker, only change the
// cache_manager_name value
"cache_manager_path" : "../cache_managers",
"cache_manager_name" : "mysql"
} }
+1
View File
@@ -24,6 +24,7 @@
"/camera.stm", "/camera.stm",
"/ch0", "/ch0",
"/ch001.sdp", "/ch001.sdp",
"/ch01.264",
"/ch0_unicast_firststream", "/ch0_unicast_firststream",
"/ch0_unicast_secondstream", "/ch0_unicast_secondstream",
"/channel1", "/channel1",
+59 -49
View File
@@ -16,35 +16,39 @@
#include <configuration.h> #include <configuration.h>
#include <memory> #include <memory>
#include <mutex>
#include <stream_model.h> #include <stream_model.h>
#include <vector> #include <vector>
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! The interface a cache_manager should implement to be valid // The interface a cache_manager should implement to be valid
class cache_manager_iface { class cache_manager_iface {
public: public:
virtual ~cache_manager_iface() {} virtual ~cache_manager_iface() {}
//! Launches the manager configuration // Launches the manager configuration
//! \return false if failed // \return false if failed
virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0; virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0;
//! get the name of the cache manager // get the name of the cache manager
virtual const std::string& get_name() const = 0; virtual const std::string& get_name() const = 0;
//! Replaces all cached streams by the content of the vector given as // Replaces all cached streams by the content of the vector given as
//! parameter // parameter
virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0; virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0;
//! Inserts a single stream to the cache // Inserts a single stream to the cache
virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0; virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0;
//! Gets all cached streams // Returns true if the stream passed as a parameter has changed in the cache
virtual bool has_changed(const etix::cameradar::stream_model&) = 0;
// Gets all cached streams
virtual std::vector<etix::cameradar::stream_model> get_streams() = 0; virtual std::vector<etix::cameradar::stream_model> get_streams() = 0;
//! Gets all valid streams which have been accessed // Gets all valid streams which have been accessed
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0; virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0;
}; };
@@ -53,27 +57,30 @@ public:
cache_manager_base() = default; cache_manager_base() = default;
virtual ~cache_manager_base() = default; virtual ~cache_manager_base() = default;
//! Launches the cache manager configuration // Launches the cache manager configuration
//! \return false if failed // \return false if failed
virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0; virtual bool configure(std::shared_ptr<etix::cameradar::configuration> configuration) = 0;
//! get the name of the cache manager // get the name of the cache manager
virtual const std::string& get_name() const = 0; virtual const std::string& get_name() const = 0;
//! Replaces all cached streams by the content of the vector given as // Replaces all cached streams by the content of the vector given as
//! parameter // parameter
virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0; virtual void set_streams(std::vector<etix::cameradar::stream_model> model) = 0;
//! Updates a single stream to the cache // Returns true if the stream passed as a parameter has changed in the cache
virtual bool has_changed(const etix::cameradar::stream_model&) = 0;
// Updates a single stream to the cache
virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0; virtual void update_stream(const etix::cameradar::stream_model& newmodel) = 0;
//! Gets all cached streams // Gets all cached streams
virtual std::vector<etix::cameradar::stream_model> get_streams() = 0; virtual std::vector<etix::cameradar::stream_model> get_streams() = 0;
//! Gets all valid streams which have been accessed // Gets all valid streams which have been accessed
virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0; virtual std::vector<etix::cameradar::stream_model> get_valid_streams() = 0;
//! Get the manager's instance // Get the manager's instance
cache_manager_base& get_instance(); cache_manager_base& get_instance();
template <typename I, typename T> template <typename I, typename T>
@@ -87,59 +94,62 @@ public:
} }
}; };
//! The representation of a cache manager // The representation of a cache manager
//! //
//! This class loads a shared library, and tries to call an extern "C" // This class loads a shared library, and tries to call an extern "C"
//! function which should instanciate a new instance of the plugin. // function which should instanciate a new instance of the plugin.
class cache_manager { class cache_manager {
private: private:
static const std::string PLUGIN_EXT; static const std::string PLUGIN_EXT;
static const std::string default_symbol; static const std::string default_symbol;
//! the name of the cache manager // The name of the cache manager
std::string name; std::string name;
//! The path where the manager is located // The write mutex to avoid conflicts when multithreading
//! should be specified in the configuration file std::mutex m;
// The path where the manager is located
// should be specified in the configuration file
std::string path; std::string path;
//! The symbol entry point of the manager to // The symbol entry point of the manager to
//! call to create an instance from the shared library // call to create an instance from the shared library
std::string symbol; std::string symbol;
//! The handle to the shared library where is stored the manager // The handle to the shared library where is stored the manager
void* handle = nullptr; void* handle = nullptr;
//! The cache manager instance if it is successfully loaded // The cache manager instance if it is successfully loaded
cache_manager_iface* ptr = nullptr; cache_manager_iface* ptr = nullptr;
//! Internal function that creates the full path of the cache manager // Internal function that creates the full path of the cache manager
//! //
//! full path is composed of: the path, the name, the string "_cache-manager" // full path is composed of: the path, the name, the string "_cache-manager"
//! and the extension PLUGIN_EXT depending of the platform // and the extension PLUGIN_EXT depending of the platform
std::string make_full_path(); std::string make_full_path();
public: public:
//! Delete constructor // Delete constructor
cache_manager() = delete; cache_manager() = delete;
//! The manager needs a path and a symbol to be instantiated. // The manager needs a path and a symbol to be instantiated.
//! The symbol can be changed if the plugin entry point // The symbol can be changed if the plugin entry point
//! is different than the standard one. // is different than the standard one.
cache_manager(const std::string& path, cache_manager(const std::string& path,
const std::string& name, const std::string& name,
const std::string& symbol = default_symbol); const std::string& symbol = default_symbol);
// //! Copy constructor // // Copy constructor
// cache_manager(cache_manager &other); // cache_manager(cache_manager &other);
//! Move constructor // Move constructor
cache_manager(cache_manager&& old); cache_manager(cache_manager&& old);
~cache_manager(); ~cache_manager();
//! Creates the instance of the cache_manager // Creates the instance of the cache_manager
//! //
// \return false if the cache_manager failed to be instantiated or if // \return false if the cache_manager failed to be instantiated or if
// the cache_manager is not a valid cache manager, true otherwise // the cache_manager is not a valid cache manager, true otherwise
bool make_instance(); bool make_instance();
@@ -152,23 +162,23 @@ public:
return this->get<I, T>(); return this->get<I, T>();
} }
//! Helper to access internal loaded cache_manager // Helper to access internal loaded cache_manager
//! //
//! Gives access to the methods of the cache_manager using the operator // Gives access to the methods of the cache_manager using the operator
//! -> (e.g.: cache_manager->get_name()); // -> (e.g.: cache_manager->get_name());
cache_manager_iface* operator->(); cache_manager_iface* operator->();
const cache_manager_iface* operator->() const; const cache_manager_iface* operator->() const;
//! helper function to check if a cache_manager is instantiated or not // helper function to check if a cache_manager is instantiated or not
friend bool operator==(std::nullptr_t nullp, const cache_manager& p); friend bool operator==(std::nullptr_t nullp, const cache_manager& p);
//! helper function to check if a cache_manager is instantiated or not // helper function to check if a cache_manager is instantiated or not
friend bool operator==(const cache_manager& p, std::nullptr_t nullp); friend bool operator==(const cache_manager& p, std::nullptr_t nullp);
//! helper function to check if a cache_manager is instantiated or not // helper function to check if a cache_manager is instantiated or not
friend bool operator!=(std::nullptr_t nullp, const cache_manager& p); friend bool operator!=(std::nullptr_t nullp, const cache_manager& p);
//! helper function to check if a cache_manager is instantiated or not // helper function to check if a cache_manager is instantiated or not
friend bool operator!=(const cache_manager& p, std::nullptr_t nullp); friend bool operator!=(const cache_manager& p, std::nullptr_t nullp);
}; };
} }
+14 -7
View File
@@ -14,19 +14,26 @@
#pragma once #pragma once
#include <json/reader.h> // Json::Value
#include <json/value.h> // Json::Value
#include <logger.h> // _LOG_
#include <opt_parse.h> // parsing opt
#include <string> // std::string #include <string> // std::string
#include <utility> // std::pair #include <utility> // std::pair
#include <logger.h> // _LOG_
#include <json/value.h> // Json::Value
#include <json/reader.h> // Json::Value
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
static const std::string default_configuration_path = "conf/cameradar.conf.json"; static const std::string default_configuration_path = "conf/cameradar.conf.json";
static const std::string default_ids_file_path_ = "conf/ids.json";
static const std::string default_urls_file_path_ = "conf/url.json"; static const std::string default_ports = "554,8554";
static const std::string default_subnets = "localhost,168.0.0.0/24";
static const std::string default_thumbnail_storage_path = "/tmp";
static const std::string default_rtsp_url_file = "conf/url.json";
static const std::string default_rtsp_ids_file = "conf/ids.json";
static const std::string default_cache_manager_path = "../cache_managers/dumb_cache_manager";
static const std::string default_cache_manager_name = "dumb";
struct configuration { struct configuration {
std::string thumbnail_storage_path; std::string thumbnail_storage_path;
@@ -49,7 +56,7 @@ struct configuration {
const std::string& rtsp_ids_file, const std::string& rtsp_ids_file,
const std::string& cache_manager_path, const std::string& cache_manager_path,
const std::string& cache_manager_name, const std::string& cache_manager_name,
const std::string& ports = "1-65535") const std::string& ports)
: thumbnail_storage_path(thumbnail_storage_path) : thumbnail_storage_path(thumbnail_storage_path)
, subnets(subnets) , subnets(subnets)
, rtsp_url_file(rtsp_url_file) , rtsp_url_file(rtsp_url_file)
@@ -67,6 +74,6 @@ struct configuration {
}; };
std::pair<bool, std::string> read_file(const std::string& path); std::pair<bool, std::string> read_file(const std::string& path);
std::pair<bool, configuration> load(const std::string& path); std::pair<bool, configuration> load(const std::pair<bool, etix::tool::opt_parse>& args);
} }
} }
+1 -1
View File
@@ -64,7 +64,7 @@ public:
const std::pair<bool, etix::tool::opt_parse>& opts) const std::pair<bool, etix::tool::opt_parse>& opts)
: busy(false) : busy(false)
, current(task::init) , current(task::init)
, nmap_output("scans/scan" + std::to_string(std::chrono::system_clock::to_time_t( , nmap_output("/tmp/scans/scan" + std::to_string(std::chrono::system_clock::to_time_t(
std::chrono::system_clock::now())) + std::chrono::system_clock::now())) +
".xml") ".xml")
, conf(conf) , conf(conf)
+3 -3
View File
@@ -32,8 +32,8 @@ std::string decode64(const std::string& str_to_decode);
std::string base64_encode(unsigned char const*, unsigned int len); std::string base64_encode(unsigned char const*, unsigned int len);
std::string base64_decode(std::string const& s); std::string base64_decode(std::string const& s);
} //! encode } // encode
} //! tool } // tool
} //! etix } // etix
+2 -2
View File
@@ -24,8 +24,8 @@ namespace tool {
static std::mutex mutex; static std::mutex mutex;
//! Format a string with the given arguments // Format a string with the given arguments
//! same behavior as sprintf. // same behavior as sprintf.
template <class... Args> template <class... Args>
std::string std::string
fmt(const std::string& base, Args... args) { fmt(const std::string& base, Args... args) {
+2 -2
View File
@@ -34,8 +34,8 @@ bool create_folder(const std::string& folder);
bool create_recursive_folder(const std::string& folder); bool create_recursive_folder(const std::string& folder);
std::string home(); std::string home();
//! this functions take a copy because we need to make some operations on the string // this functions take a copy because we need to make some operations on the string
//! for example, we need to apply std::string::pop_back // for example, we need to apply std::string::pop_back
std::string get_file_folder(std::string full_file_path); std::string get_file_folder(std::string full_file_path);
bool copy(const std::string& src, const std::string& dst); bool copy(const std::string& src, const std::string& dst);
+8 -48
View File
@@ -23,20 +23,13 @@ namespace etix {
namespace tool { namespace tool {
//! Parse command line arguments
class opt_parse { class opt_parse {
private: private:
//! An argumetn representation to be passed to the program
struct opt_param { struct opt_param {
//! is it required
bool required; bool required;
//! Does he needs arguments
bool need_arg; bool need_arg;
//! What is its name
std::string name; std::string name;
//! Description
std::string desc; std::string desc;
//! the argument of the arguments !
std::string argument; std::string argument;
bool is_passed = false; bool is_passed = false;
@@ -44,22 +37,14 @@ private:
: required(required), need_arg(need_arg), name(name), desc(desc) {} : required(required), need_arg(need_arg), name(name), desc(desc) {}
}; };
//! Map of the different possibles argument as string and their
//! rertpresntation
std::unordered_map<std::string, opt_param> params; std::unordered_map<std::string, opt_param> params;
//! The total count of arguments for this program
int argc; int argc;
//! The list of arguments as a String
char** argv; char** argv;
//! The total count of params
int params_cnt = 0; int params_cnt = 0;
public: public:
//! An iterator to iterate over all the arguments of
//! the program
class iterator { class iterator {
private: private:
//! The arguments vector and their argumetns
std::vector<std::pair<std::string, std::string>> args; std::vector<std::pair<std::string, std::string>> args;
unsigned int opt_pos = 0; unsigned int opt_pos = 0;
@@ -71,65 +56,40 @@ public:
return *this; return *this;
} }
std::pair<std::string, std::string>& operator*() { return this->args.at(this->opt_pos); } std::pair<std::string, std::string>& operator*() { return this->args.at(this->opt_pos); }
bool operator==(const iterator& rhs) const { return this->opt_pos == rhs.opt_pos; } bool
bool operator!=(const iterator& rhs) const { return this->opt_pos != rhs.opt_pos; } operator==(const iterator& rhs) const {
return this->opt_pos == rhs.opt_pos;
}
bool
operator!=(const iterator& rhs) const {
return this->opt_pos != rhs.opt_pos;
}
}; };
opt_parse() = delete; opt_parse() = delete;
//! \param argc Total count of arguements
//! \param argv Cmdline arguments from program startup
opt_parse(int argc, char* argv[]); opt_parse(int argc, char* argv[]);
~opt_parse(); ~opt_parse();
//! Add a argument required for your program
//!
//! If the specified argument is not given in cmdline, a error will be
//! generated
//! \param name The name of the parameter as a string (e.g "-l")
//! \param desc A description that will be used by the function `print_help`
//! \param need_arg Does the argument require a parameter
void required(const std::string& name, const std::string& desc = "", bool need_arg = true); void required(const std::string& name, const std::string& desc = "", bool need_arg = true);
//! Add an optional argument for your program
//!
//! If the specified argument is not given in cmdline, a error will be
//! generated
//! \param name The name of the parameter as a string (e.g "-l")
//! \param desc A description that will be used by the function `print_help`
//! \param need_arg Does the argument require a parameter
void optional(const std::string& name, const std::string& desc = "", bool need_arg = true); void optional(const std::string& name, const std::string& desc = "", bool need_arg = true);
//! Process the parsing of the arguments
bool execute(); bool execute();
//! \return an iterator on the begin of the arguments
iterator begin() const; iterator begin() const;
//! \return the iterator on the end of the arguments
iterator end() const; iterator end() const;
//! Print the usage using the parameter setted when referencing the arguments
//! for the program
void print_usage() const; void print_usage() const;
//! Print an help message generated using all the specified arguments
void print_help() const; void print_help() const;
//! Is there on the parameters (missing parameter ? unknows ? missing
//! arguments ?)
//! \return true if there is error, false otherwise
bool has_error() const; bool has_error() const;
//! Does the option exist or not ?
//! \param opt The name of the option to check
//! \return true if the param exist, false otherwise
bool exist(const std::string& opt) const; bool exist(const std::string& opt) const;
//! Acces to an argument from its name
//! \param opt The name of the option to check
//! \return the the argument of the param as a string
std::string operator[](const std::string& opt) const; std::string operator[](const std::string& opt) const;
}; };
@@ -14,9 +14,10 @@
#pragma once #pragma once
#include <cameradar_task.h> // task interface
#include <cachemanager.h> // cacheManager #include <cachemanager.h> // cacheManager
#include <cameradar_task.h> // task interface
#include <describe.h> // send DESCRIBE through cURL #include <describe.h> // send DESCRIBE through cURL
#include <future> // std::async & std::future
#include <signal_handler.h> // signals #include <signal_handler.h> // signals
#include <stream_model.h> // data model #include <stream_model.h> // data model
@@ -41,6 +42,7 @@ public:
bool test_ids(const etix::cameradar::stream_model& cit, bool test_ids(const etix::cameradar::stream_model& cit,
const std::string& pit, const std::string& pit,
const std::string& uit) const; const std::string& uit) const;
bool bruteforce_camera(const stream_model& stream) const;
}; };
} }
} }
@@ -14,14 +14,15 @@
#pragma once #pragma once
#include <cachemanager.h> // cacheManager
#include <cameradar_task.h> // task interface #include <cameradar_task.h> // task interface
#include <memory> // std::shared_ptr
#include <logger.h> // LOG
#include <curl/curl.h> // cURL client for discovery #include <curl/curl.h> // cURL client for discovery
#include <describe.h> // send DESCRIBE through cURL #include <describe.h> // send DESCRIBE through cURL
#include <future> // std::async & std::future
#include <logger.h> // LOG
#include <memory> // std::shared_ptr
#include <signal_handler.h> // signals #include <signal_handler.h> // signals
#include <stream_model.h> // data model #include <stream_model.h> // data model
#include <cachemanager.h> // cacheManager
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
@@ -42,6 +43,7 @@ public:
virtual bool run() const; virtual bool run() const;
bool test_path(const etix::cameradar::stream_model& cit, const std::string& it) const; bool test_path(const etix::cameradar::stream_model& cit, const std::string& it) const;
bool bruteforce_camera(const stream_model& stream) const;
}; };
} }
} }
@@ -14,13 +14,14 @@
#pragma once #pragma once
#include <cachemanager.h> // cacheManager
#include <cameradar_task.h> // task interface #include <cameradar_task.h> // task interface
#include <fmt.h> // fmt
#include <future> // std::async & std::future
#include <launch_command.h> // launch_command #include <launch_command.h> // launch_command
#include <rtsp_path.h> // make_path
#include <signal_handler.h> // signals #include <signal_handler.h> // signals
#include <stream_model.h> // data model #include <stream_model.h> // data model
#include <cachemanager.h> // cacheManager
#include <fmt.h> // fmt
#include <rtsp_path.h> // make_path
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
@@ -41,6 +42,7 @@ public:
virtual bool run() const; virtual bool run() const;
std::string build_output_file_path(const std::string& path) const; std::string build_output_file_path(const std::string& path) const;
bool generate_thumbnail(const stream_model& stream) const;
}; };
} }
} }
+20 -9
View File
@@ -31,11 +31,11 @@ distribution.
#pragma warning(disable : 4786) #pragma warning(disable : 4786)
#endif #endif
#include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
// Help out windows: // Help out windows:
#if defined(_DEBUG) && !defined(DEBUG) #if defined(_DEBUG) && !defined(DEBUG)
@@ -43,9 +43,9 @@ distribution.
#endif #endif
#ifdef TIXML_USE_STL #ifdef TIXML_USE_STL
#include <string>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <string>
#define TIXML_STRING std::string #define TIXML_STRING std::string
#else #else
#include "tinystr.h" #include "tinystr.h"
@@ -1086,9 +1086,18 @@ public:
return const_cast<TiXmlAttribute*>((const_cast<const TiXmlAttribute*>(this))->Previous()); return const_cast<TiXmlAttribute*>((const_cast<const TiXmlAttribute*>(this))->Previous());
} }
bool operator==(const TiXmlAttribute& rhs) const { return rhs.name == name; } bool
bool operator<(const TiXmlAttribute& rhs) const { return name < rhs.name; } operator==(const TiXmlAttribute& rhs) const {
bool operator>(const TiXmlAttribute& rhs) const { return name > rhs.name; } return rhs.name == name;
}
bool
operator<(const TiXmlAttribute& rhs) const {
return name < rhs.name;
}
bool
operator>(const TiXmlAttribute& rhs) const {
return name > rhs.name;
}
/* Attribute parsing starts: first letter of the name /* Attribute parsing starts: first letter of the name
returns: the next char after the returns: the next char after the
@@ -1171,7 +1180,6 @@ private:
//*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute
//(sentinel-element), //(sentinel-element),
//*ME: this class must be also use a hidden/disabled copy-constructor //*ME: this class must be also use a hidden/disabled copy-constructor
//!!!
TiXmlAttributeSet(const TiXmlAttributeSet&); // not allowed TiXmlAttributeSet(const TiXmlAttributeSet&); // not allowed
void operator=(const TiXmlAttributeSet&); // not allowed (as TiXmlAttribute) void operator=(const TiXmlAttributeSet&); // not allowed (as TiXmlAttribute)
@@ -1509,7 +1517,8 @@ public:
#endif #endif
TiXmlText(const TiXmlText& copy) : TiXmlNode(TiXmlNode::TINYXML_TEXT) { copy.CopyTo(this); } TiXmlText(const TiXmlText& copy) : TiXmlNode(TiXmlNode::TINYXML_TEXT) { copy.CopyTo(this); }
TiXmlText& operator=(const TiXmlText& base) { TiXmlText&
operator=(const TiXmlText& base) {
base.CopyTo(this); base.CopyTo(this);
return *this; return *this;
} }
@@ -1664,7 +1673,8 @@ public:
TiXmlUnknown(const TiXmlUnknown& copy) : TiXmlNode(TiXmlNode::TINYXML_UNKNOWN) { TiXmlUnknown(const TiXmlUnknown& copy) : TiXmlNode(TiXmlNode::TINYXML_UNKNOWN) {
copy.CopyTo(this); copy.CopyTo(this);
} }
TiXmlUnknown& operator=(const TiXmlUnknown& copy) { TiXmlUnknown&
operator=(const TiXmlUnknown& copy) {
copy.CopyTo(this); copy.CopyTo(this);
return *this; return *this;
} }
@@ -2039,7 +2049,8 @@ public:
TiXmlHandle(TiXmlNode* _node) { this->node = _node; } TiXmlHandle(TiXmlNode* _node) { this->node = _node; }
/// Copy constructor /// Copy constructor
TiXmlHandle(const TiXmlHandle& ref) { this->node = ref.node; } TiXmlHandle(const TiXmlHandle& ref) { this->node = ref.node; }
TiXmlHandle operator=(const TiXmlHandle& ref) { TiXmlHandle
operator=(const TiXmlHandle& ref) {
if (&ref != this) this->node = ref.node; if (&ref != this) this->node = ref.node;
return *this; return *this;
} }
+18 -9
View File
@@ -13,10 +13,10 @@
// limitations under the License. // limitations under the License.
#include "cachemanager.h" // for cache_manager #include "cachemanager.h" // for cache_manager
#include <algorithm> // for move
#include <dlfcn.h> // for dlerror, dlclose, dlopen, dlsym, etc #include <dlfcn.h> // for dlerror, dlclose, dlopen, dlsym, etc
#include <logger.h> // for LOG_ERR_ #include <logger.h> // for LOG_ERR_
#include <stdbool.h> // for bool, false, true #include <stdbool.h> // for bool, false, true
#include <algorithm> // for move
#include <errno.h> #include <errno.h>
@@ -46,10 +46,7 @@ cache_manager::cache_manager(cache_manager&& old)
} }
cache_manager::~cache_manager() { cache_manager::~cache_manager() {
if (this->ptr) { delete this->ptr;
delete this->ptr;
this->ptr = nullptr;
}
if (this->handle) { dlclose(handle); } if (this->handle) { dlclose(handle); }
} }
@@ -105,13 +102,25 @@ cache_manager_iface* cache_manager::operator->() { return this->ptr; }
const cache_manager_iface* cache_manager::operator->() const { return this->ptr; } const cache_manager_iface* cache_manager::operator->() const { return this->ptr; }
bool operator==(std::nullptr_t nullp, const cache_manager& p) { return p.ptr == nullp; } bool
operator==(std::nullptr_t nullp, const cache_manager& p) {
return p.ptr == nullp;
}
bool operator==(const cache_manager& p, std::nullptr_t nullp) { return p.ptr == nullp; } bool
operator==(const cache_manager& p, std::nullptr_t nullp) {
return p.ptr == nullp;
}
bool operator!=(std::nullptr_t nullp, const cache_manager& p) { return p.ptr != nullp; } bool
operator!=(std::nullptr_t nullp, const cache_manager& p) {
return p.ptr != nullp;
}
bool operator!=(const cache_manager& p, std::nullptr_t nullp) { return p.ptr != nullp; } bool
operator!=(const cache_manager& p, std::nullptr_t nullp) {
return p.ptr != nullp;
}
cache_manager_base& cache_manager_base&
cache_manager_base::get_instance() { cache_manager_base::get_instance() {
+54 -13
View File
@@ -56,7 +56,7 @@ configuration::load_ids() {
"the default one " "the default one "
"instead.", "instead.",
"configuration"); "configuration");
content = read_file(default_ids_file_path_).second; content = read_file(default_rtsp_ids_file).second;
} }
if (content.size()) { if (content.size()) {
auto root = Json::Value(); auto root = Json::Value();
@@ -92,16 +92,16 @@ bool
configuration::load_url() { configuration::load_url() {
std::string content; std::string content;
LOG_DEBUG_("Trying to open ids file from " + this->rtsp_ids_file, "configuration"); LOG_DEBUG_("Trying to open url file from " + this->rtsp_url_file, "configuration");
if (this->rtsp_url_file.size()) { if (this->rtsp_url_file.size()) {
content = read_file(this->rtsp_url_file.c_str()).second; content = read_file(this->rtsp_url_file.c_str()).second;
} else { } else {
LOG_WARN_( LOG_WARN_(
"No ids file detected in your configuration, Cameradar will use " "No url file detected in your configuration, Cameradar will use "
"the default one " "the default one "
"instead.", "instead.",
"configuration"); "configuration");
content = read_file(default_urls_file_path_).second; content = read_file(default_rtsp_url_file).second;
} }
if (content.size()) { if (content.size()) {
auto root = Json::Value(); auto root = Json::Value();
@@ -131,18 +131,47 @@ serialize(const Json::Value& root) {
std::pair<bool, configuration> ret; std::pair<bool, configuration> ret;
try { try {
ret.second.ports = root["ports"].asString(); if (!root["ports"].isNull())
ret.second.subnets = root["subnets"].asString(); ret.second.ports = root["ports"].asString();
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString(); else
ret.second.rtsp_url_file = root["rtsp_url_file"].asString(); ret.second.ports = default_ports;
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
ret.second.cache_manager_path = root["cache_manager_path"].asString(); if (!root["subnets"].isNull())
ret.second.cache_manager_name = root["cache_manager_name"].asString(); ret.second.subnets = root["subnets"].asString();
else
ret.second.subnets = default_subnets;
if (!root["rtsp_ids_file"].isNull())
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString();
else
ret.second.rtsp_ids_file = default_rtsp_ids_file;
if (!root["rtsp_url_file"].isNull())
ret.second.rtsp_url_file = root["rtsp_url_file"].asString();
else
ret.second.rtsp_url_file = default_rtsp_url_file;
if (!root["thumbnail_storage_path"].isNull())
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
else
ret.second.thumbnail_storage_path = default_thumbnail_storage_path;
if (!root["cache_manager_path"].isNull())
ret.second.cache_manager_path = root["cache_manager_path"].asString();
else
ret.second.cache_manager_path = default_cache_manager_path;
if (!root["cache_manager_name"].isNull())
ret.second.cache_manager_name = root["cache_manager_name"].asString();
else
ret.second.cache_manager_name = default_cache_manager_name;
ret.first = true; ret.first = true;
} catch (std::exception& e) { } catch (const std::exception& e) {
LOG_ERR_("Configuration failed : " + std::string(e.what()), "configuration"); LOG_ERR_("Configuration failed : " + std::string(e.what()), "configuration");
ret.first = false; ret.first = false;
} }
return ret; return ret;
} }
@@ -156,7 +185,16 @@ configuration::get_raw() const {
// Will return true & valid configuration if success // Will return true & valid configuration if success
// Otherwise false & empty configuration // Otherwise false & empty configuration
std::pair<bool, configuration> std::pair<bool, configuration>
load(const std::string& path) { load(const std::pair<bool, etix::tool::opt_parse>& args) {
std::string path;
if (not args.second.exist("-c")) {
path = etix::cameradar::default_configuration_path;
LOG_WARN_("No custom path set, trying to use default path: " + path, "main");
} else {
path = args.second["-c"];
}
// Check if the file exists at the given path // Check if the file exists at the given path
if (access(path.c_str(), F_OK) == -1) { if (access(path.c_str(), F_OK) == -1) {
LOG_ERR_("Can't access: " + path, "configuration"); LOG_ERR_("Can't access: " + path, "configuration");
@@ -191,6 +229,9 @@ load(const std::string& path) {
conf.first &= conf.second.load_url(); conf.first &= conf.second.load_url();
conf.first &= conf.second.load_ids(); conf.first &= conf.second.load_ids();
if (args.second.exist("-s")) conf.second.subnets = args.second["-s"];
if (args.second.exist("-p")) conf.second.ports = args.second["-p"];
return conf; return conf;
} }
} }
+37 -16
View File
@@ -13,13 +13,26 @@
// limitations under the License. // limitations under the License.
#include <describe.h> #include <describe.h>
#include <mutex>
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! Sends a request to the camera using the OPTION method, std::mutex m;
//! then a DESCRIBE to check for valid IDs
//! then another DESCIBE with IDs if an authentication is needed // Ugly workaround
size_t
write_data(void* buffer, size_t size, size_t nmemb, void* userp) {
// I'm sorry for this
// Forget you ever saw it
if (not buffer || not size || not nmemb) return 0;
return size * nmemb;
}
// Sends a request to the camera using the OPTION method,
// then a DESCRIBE to check for valid IDs
// then another DESCIBE with IDs if an authentication is needed
bool bool
curl_describe(const std::string& path, bool logs) { curl_describe(const std::string& path, bool logs) {
CURL* csession; CURL* csession;
@@ -27,23 +40,24 @@ curl_describe(const std::string& path, bool logs) {
struct curl_slist* custom_msg = NULL; struct curl_slist* custom_msg = NULL;
char URL[256]; char URL[256];
long rc; long rc;
FILE* protofile = NULL; m.lock();
protofile = fopen("/dev/null", "wb"); curl_global_init(0);
m.unlock();
csession = curl_easy_init(); csession = curl_easy_init();
if (csession == NULL) return -1; if (csession == NULL) return -1;
sprintf(URL, "%s", path.c_str()); sprintf(URL, "%s", path.c_str());
// These are the options for all following cURL requests // These are the options for all following cURL requests
// Activate verbose if debug is needed // Activate verbose if debug is needed
curl_easy_setopt(csession, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(csession, CURLOPT_TIMEOUT, 1); curl_easy_setopt(csession, CURLOPT_TIMEOUT, 1);
curl_easy_setopt(csession, CURLOPT_NOBODY, 1); curl_easy_setopt(csession, CURLOPT_NOBODY, 1);
curl_easy_setopt(csession, CURLOPT_URL, URL); curl_easy_setopt(csession, CURLOPT_URL, URL);
curl_easy_setopt(csession, CURLOPT_RTSP_STREAM_URI, URL); curl_easy_setopt(csession, CURLOPT_RTSP_STREAM_URI, URL);
curl_easy_setopt(csession, CURLOPT_FOLLOWLOCATION, 0); curl_easy_setopt(csession, CURLOPT_FOLLOWLOCATION, 0);
curl_easy_setopt(csession, CURLOPT_HEADER, 0); curl_easy_setopt(csession, CURLOPT_HEADER, 0);
curl_easy_setopt(csession, CURLOPT_INTERLEAVEDATA, protofile);
curl_easy_setopt(csession, CURLOPT_VERBOSE, 0); curl_easy_setopt(csession, CURLOPT_VERBOSE, 0);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile); curl_easy_setopt(csession, CURLOPT_WRITEFUNCTION, write_data);
// This request will handshake the stream's server, it should always return 200 OK // This request will handshake the stream's server, it should always return 200 OK
curl_easy_perform(csession); curl_easy_perform(csession);
curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc); curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
@@ -51,11 +65,7 @@ curl_describe(const std::string& path, bool logs) {
custom_msg, "Accept: application/x-rtsp-mh, application/rtsl, application/sdp"); custom_msg, "Accept: application/x-rtsp-mh, application/rtsl, application/sdp");
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg); curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
// This request will check if the given path is right without the need of encrypted ids // This request will check if the given path is right without the need of encrypted ids
curl_easy_perform(
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
unsigned long pos = path.find("@"); unsigned long pos = path.find("@");
if (pos != std::string::npos) { if (pos != std::string::npos) {
std::string encoded = etix::tool::encode::encode64(path.substr(7, pos - 7)); std::string encoded = etix::tool::encode::encode64(path.substr(7, pos - 7));
@@ -63,20 +73,31 @@ curl_describe(const std::string& path, bool logs) {
curl_slist_append(custom_msg, std::string("Authorization: Basic " + encoded).c_str()); curl_slist_append(custom_msg, std::string("Authorization: Basic " + encoded).c_str());
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg); curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE); curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile); // curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
// This request will check if the given ids are good // This request will check if the given ids are good
curl_easy_perform(csession); // will return 404 if good ids, 401 if bad ids curl_easy_perform(csession); // will return 404 if good ids, 401 if bad ids
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc); res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
} else {
curl_easy_perform(
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
} }
curl_easy_cleanup(csession); curl_easy_cleanup(csession);
fclose(protofile);
m.lock();
curl_global_cleanup(); curl_global_cleanup();
m.unlock();
LOG_DEBUG_("[" + path + "] Response code : " + std::to_string(rc), "describe");
if (logs) { if (logs) {
if (rc != 401 && pos == std::string::npos) // Some cameras return 400 instead of 401, don't know why.
// Some cameras timeout and then curl considers the status as 0
// GST-RTSP-SERVER returns 404 instead of 401, then 401 instead of 404.
if (rc != 401 && rc != 400 && rc && pos == std::string::npos)
LOG_INFO_("Unprotected camera discovered.", "brutelogs"); LOG_INFO_("Unprotected camera discovered.", "brutelogs");
return ((res == CURLE_OK) && rc != 401); return ((res == CURLE_OK) && rc != 401 && rc != 400 && rc);
} }
return ((res == CURLE_OK) && rc != 404); return ((res == CURLE_OK) && rc != 404 && rc != 400 && rc);
} }
} }
} }
+20 -7
View File
@@ -17,6 +17,8 @@
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
using namespace std::chrono_literals;
// The main loop of the binary // The main loop of the binary
void void
dispatcher::run() { dispatcher::run() {
@@ -47,13 +49,13 @@ dispatcher::run() {
// Waiting for task to cleanup / force stop command // Waiting for task to cleanup / force stop command
while ((signal_handler::instance().should_stop() not_eq stop_priority::force_stop) and while ((signal_handler::instance().should_stop() not_eq stop_priority::force_stop) and
doing_stuff()) { doing_stuff()) {
std::this_thread::sleep_for(std::chrono::milliseconds(30)); std::this_thread::sleep_for(30ms);
} }
worker.join(); worker.join();
} }
//! This loop is used to add all the tasks specified in the command line // This loop is used to add all the tasks specified in the command line
//! And then run them successively // And then run them successively
void void
dispatcher::do_stuff() { dispatcher::do_stuff() {
if (opts.second.exist("-d")) { if (opts.second.exist("-d")) {
@@ -61,8 +63,13 @@ dispatcher::do_stuff() {
queue.push_back(new etix::cameradar::parsing(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::parsing(cache, conf, nmap_output));
} }
if (opts.second.exist("-b")) { if (opts.second.exist("-b")) {
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output)); if (opts.second.exist("--gst-rtsp-server")) {
queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output));
} else {
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output));
}
} }
if (opts.second.exist("-t")) { if (opts.second.exist("-t")) {
queue.push_back(new etix::cameradar::thumbnail(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::thumbnail(cache, conf, nmap_output));
@@ -74,8 +81,13 @@ dispatcher::do_stuff() {
!opts.second.exist("-g")) { !opts.second.exist("-g")) {
queue.push_back(new etix::cameradar::mapping(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::mapping(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::parsing(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::parsing(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output)); if (opts.second.exist("--gst-rtsp-server")) {
queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output));
} else {
queue.push_back(new etix::cameradar::brutelogs(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::brutepath(cache, conf, nmap_output));
}
queue.push_back(new etix::cameradar::thumbnail(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::thumbnail(cache, conf, nmap_output));
queue.push_back(new etix::cameradar::stream_check(cache, conf, nmap_output)); queue.push_back(new etix::cameradar::stream_check(cache, conf, nmap_output));
} }
@@ -88,6 +100,7 @@ dispatcher::do_stuff() {
"dispatcher"); "dispatcher");
break; break;
} }
std::this_thread::sleep_for(30ms);
} }
this->current = task::finished; this->current = task::finished;
} }
+2 -2
View File
@@ -88,12 +88,12 @@ create_recursive_folder(const std::string& folder) {
std::string std::string
get_file_folder(std::string full_file_path) { get_file_folder(std::string full_file_path) {
//! remove ending slash // remove ending slash
if (full_file_path.back() == '/') full_file_path.pop_back(); if (full_file_path.back() == '/') full_file_path.pop_back();
size_t last_slash_position = full_file_path.find_last_of('/'); size_t last_slash_position = full_file_path.find_last_of('/');
//! it there is no slash, there is no folder to return // it there is no slash, there is no folder to return
if (last_slash_position == std::string::npos) return ""; if (last_slash_position == std::string::npos) return "";
return std::string(full_file_path, 0, last_slash_position); return std::string(full_file_path, 0, last_slash_position);
+1 -1
View File
@@ -17,7 +17,7 @@
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! Launches a command and checks for the return value // Launches a command and checks for the return value
bool bool
launch_command(const std::string& cmd) { launch_command(const std::string& cmd) {
int status = system(cmd.c_str()); int status = system(cmd.c_str());
+12 -13
View File
@@ -24,7 +24,6 @@ void
print_version() { print_version() {
std::cout << "Cameradar version " << CAMERADAR_VERSION << std::endl; std::cout << "Cameradar version " << CAMERADAR_VERSION << std::endl;
std::cout << "Build " << CAMERADAR_VERSION_BUILD << std::endl; std::cout << "Build " << CAMERADAR_VERSION_BUILD << std::endl;
std::cout << "Git commit " << CAMERADAR_VERSION_GIT_SHA1 << std::endl;
} }
// Command line parsing // Command line parsing
@@ -32,6 +31,8 @@ std::pair<bool, etix::tool::opt_parse>
parse_cmdline(int argc, char* argv[]) { parse_cmdline(int argc, char* argv[]) {
auto opt_parse = etix::tool::opt_parse{ argc, argv }; auto opt_parse = etix::tool::opt_parse{ argc, argv };
opt_parse.optional("-s", "Set subnets (e.g.: `172.16.0.0/24`)", true);
opt_parse.optional("-p", "Set ports (e.g.: `554,8554`)", true);
opt_parse.optional("-c", "Path to the configuration file (-c /path/to/conf)", true); opt_parse.optional("-c", "Path to the configuration file (-c /path/to/conf)", true);
opt_parse.optional("-l", "Set log level (-l 4 will only show warnings and errors)", true); opt_parse.optional("-l", "Set log level (-l 4 will only show warnings and errors)", true);
opt_parse.optional("-d", "Launch the discovery tool on the given subnet", false); opt_parse.optional("-d", "Launch the discovery tool on the given subnet", false);
@@ -40,6 +41,12 @@ parse_cmdline(int argc, char* argv[]) {
opt_parse.optional("-g", "Check if the stream can be opened with GStreamer", false); opt_parse.optional("-g", "Check if the stream can be opened with GStreamer", false);
opt_parse.optional("-v", "Display Cameradar's version", false); opt_parse.optional("-v", "Display Cameradar's version", false);
opt_parse.optional("-h", "Display this help", false); opt_parse.optional("-h", "Display this help", false);
opt_parse.optional(
"--gst-rtsp-server",
"Change the order of the bruteforce to match GST RTSP Server's implementation of "
"RTSP. Some cameras and RTSP servers will use this standard instead of the more "
"standard one. For more information, see the README.md file.",
false);
opt_parse.execute(); opt_parse.execute();
if (opt_parse.exist("-h")) { if (opt_parse.exist("-h")) {
@@ -86,18 +93,10 @@ main(int argc, char* argv[]) {
if (not args.first) return EXIT_FAILURE; if (not args.first) return EXIT_FAILURE;
print_version(); print_version();
// configure file configuration path
auto conf_path = std::string{};
if (not args.second.exist("-c")) {
conf_path = etix::cameradar::default_configuration_path;
LOG_WARN_("No custom path set, trying to use default path: " + conf_path, "main");
} else {
conf_path = args.second["-c"];
}
if (not args.second.exist("-l")) { if (not args.second.exist("-l")) {
etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::INFO); etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::DEBUG);
LOG_INFO_("No log level set, using log level 2 (ignoring DEBUG)", "main"); LOG_INFO_("No log level set, using log level 1", "main");
} else { } else {
try { try {
int level = std::stoi(args.second["-l"]); int level = std::stoi(args.second["-l"]);
@@ -110,7 +109,7 @@ main(int argc, char* argv[]) {
} }
// Try to load the configuration // Try to load the configuration
auto conf = cmrdr::load(conf_path); auto conf = cmrdr::load(args);
if (not conf.first) { return EXIT_FAILURE; } if (not conf.first) { return EXIT_FAILURE; }
LOG_INFO_("Configuration successfully loaded", "main"); LOG_INFO_("Configuration successfully loaded", "main");
@@ -124,7 +123,7 @@ main(int argc, char* argv[]) {
auto plug = std::make_shared<etix::cameradar::cache_manager>(conf.second.cache_manager_path, auto plug = std::make_shared<etix::cameradar::cache_manager>(conf.second.cache_manager_path,
conf.second.cache_manager_name); conf.second.cache_manager_name);
if (not plug->make_instance()) { if (not plug || not plug->make_instance()) {
LOG_ERR_(std::string("Invalid cache manager "), "cameradar"); LOG_ERR_(std::string("Invalid cache manager "), "cameradar");
return false; return false;
} }
+12 -5
View File
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <rtsp_path.h>
#include <logger.h> #include <logger.h>
#include <rtsp_path.h>
namespace etix { namespace etix {
@@ -21,10 +21,17 @@ namespace cameradar {
const std::string const std::string
make_path(const stream_model& model) { make_path(const stream_model& model) {
std::string ret(model.service_name + "://" + model.username + ":" + model.password + "@" + if (model.password != "" || model.username != "") {
model.address + ":" + std::to_string(model.port) + model.route); std::string ret(model.service_name + "://" + model.username + ":" + model.password + "@" +
LOG_DEBUG_(ret, "debug"); model.address + ":" + std::to_string(model.port) + model.route);
return ret; LOG_DEBUG_(ret, "debug");
return ret;
} else {
std::string ret(model.service_name + "://" + model.address + ":" +
std::to_string(model.port) + model.route);
LOG_DEBUG_(ret, "debug");
return ret;
}
} }
} }
} }
+33 -26
View File
@@ -25,9 +25,9 @@ static const std::string no_ids_warning_ =
"default routes. " "default routes. "
"Path bruteforce is impossible without the IDs."; "Path bruteforce is impossible without the IDs.";
//! Tries to match the detected combination of Username / Password // Tries to match the detected combination of Username / Password
//! with the camera stream. Creates a resource in the DB upon // with the camera stream. Creates a resource in the DB upon
//! valid discovery // valid discovery
bool bool
brutelogs::test_ids(const etix::cameradar::stream_model& stream, brutelogs::test_ids(const etix::cameradar::stream_model& stream,
const std::string& password, const std::string& password,
@@ -35,23 +35,25 @@ brutelogs::test_ids(const etix::cameradar::stream_model& stream,
bool found = false; bool found = false;
std::string path = stream.service_name + "://"; std::string path = stream.service_name + "://";
if (username != "" || password != "") { path += username + ":" + password + "@"; } if (username != "" || password != "") { path += username + ":" + password + "@"; }
path += stream.address + ":" + std::to_string(stream.port); path += stream.address + ":" + std::to_string(stream.port) + stream.route;
LOG_DEBUG_("Testing ids : " + path, "brutelogs"); LOG_INFO_("Testing ids : " + path, "brutelogs");
try { try {
if (curl_describe(path, true)) { if (curl_describe(path, true)) {
LOG_DEBUG_("[FOUND IDS] : " + path, "brutelogs"); LOG_INFO_("[FOUND IDS] : " + path, "brutelogs");
found = true; found = true;
stream_model newstream{ stream_model newstream{
stream.address, stream.port, username, password, stream.address, stream.port, username, password,
stream.route, stream.service_name, stream.product, stream.protocol, stream.route, stream.service_name, stream.product, stream.protocol,
stream.state, stream.path_found, true, stream.thumbnail_path stream.state, stream.path_found, true, stream.thumbnail_path
}; };
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} else { } else {
stream_model newstream{ stream.address, stream.port, username, stream_model newstream{ stream.address, stream.port, username,
password, stream.route, stream.service_name, password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state, stream.product, stream.protocol, stream.state,
stream.path_found, false, stream.thumbnail_path }; stream.path_found, false, stream.thumbnail_path };
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} }
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
@@ -68,19 +70,35 @@ ids_already_found(std::vector<stream_model> streams, stream_model stream) {
return false; return false;
} }
//! Tries to discover the right IDs on all RTSP streams in DB bool
//! Uses the ids.json file to try different combinations brutelogs::bruteforce_camera(const stream_model& stream) const {
for (const auto& username : conf.usernames) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
for (const auto& password : conf.passwords) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
if ((*cache)->has_changed(stream)) return true;
if (test_ids(stream, password, username)) return true;
}
}
return false;
}
// Tries to discover the right IDs on all RTSP streams in DB
// Uses the ids.json file to try different combinations
bool bool
brutelogs::run() const { brutelogs::run() const {
std::vector<std::future<bool>> futures;
LOG_INFO_( LOG_INFO_(
"Beginning bruteforce of the usernames and passwords task, it may " "Beginning bruteforce of the usernames and passwords task, it may "
"take a while.", "take a while.",
"brutelogs"); "brutelogs");
std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams(); std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams();
bool doubleskip; LOG_DEBUG_("Found " + std::to_string(streams.size()) + " streams in the cache", "brutelogs");
size_t found = 0; size_t found = 0;
for (const auto& stream : streams) { for (const auto& stream : streams) {
doubleskip = false;
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running) if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break; break;
if ((found < streams.size()) && ids_already_found(streams, stream)) { if ((found < streams.size()) && ids_already_found(streams, stream)) {
@@ -91,24 +109,13 @@ brutelogs::run() const {
"brutelogs"); "brutelogs");
++found; ++found;
} else { } else {
for (const auto& username : conf.usernames) { futures.push_back(
if (doubleskip || std::async(std::launch::async, &brutelogs::bruteforce_camera, this, stream));
signal_handler::instance().should_stop() !=
etix::cameradar::stop_priority::running)
break;
for (const auto& password : conf.passwords) {
if (doubleskip ||
signal_handler::instance().should_stop() !=
etix::cameradar::stop_priority::running)
break;
if (test_ids(stream, password, username)) {
++found;
doubleskip = true;
}
}
}
} }
} }
for (auto& fit : futures) {
if (fit.get()) { ++found; }
}
if (!found) { if (!found) {
LOG_WARN_(no_ids_warning_, "brutelogs"); LOG_WARN_(no_ids_warning_, "brutelogs");
return false; return false;
+31 -17
View File
@@ -19,9 +19,9 @@ static const std::string no_route_found_ =
"default " "default "
"routes. Thumbnail generation is impossible without the path."; "routes. Thumbnail generation is impossible without the path.";
//! Tries to match the detected combination of Username / Password // Tries to match the detected combination of Username / Password
//! with a route for the camera stream. Creates a resource in the DB upon // with a route for the camera stream. Creates a resource in the DB upon
//! valid discovery // valid discovery
bool bool
brutepath::test_path(const stream_model& stream, const std::string& route) const { brutepath::test_path(const stream_model& stream, const std::string& route) const {
bool found = false; bool found = false;
@@ -29,7 +29,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.address + ":" + std::to_string(stream.port); stream.address + ":" + std::to_string(stream.port);
if (route.front() != '/') { path += "/"; } if (route.front() != '/') { path += "/"; }
path += route; path += route;
LOG_DEBUG_("Testing path : " + path, "brutepath"); LOG_INFO_("Testing path : " + path, "brutepath");
try { try {
if (curl_describe(path, false)) { if (curl_describe(path, false)) {
// insert in DB and go to the next port, print a cool message // insert in DB and go to the next port, print a cool message
@@ -40,6 +40,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.service_name, stream.product, stream.protocol, stream.state, true, stream.service_name, stream.product, stream.protocol, stream.state, true,
stream.ids_found, stream.thumbnail_path stream.ids_found, stream.thumbnail_path
}; };
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} else { } else {
stream_model newstream{ stream_model newstream{
@@ -47,6 +48,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.service_name, stream.product, stream.protocol, stream.state, false, stream.service_name, stream.product, stream.protocol, stream.state, false,
stream.ids_found, stream.thumbnail_path stream.ids_found, stream.thumbnail_path
}; };
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream); (*cache)->update_stream(newstream);
} }
} catch (const std::runtime_error& e) { LOG_INFO_(e.what(), "brutepath"); } } catch (const std::runtime_error& e) { LOG_INFO_(e.what(), "brutepath"); }
@@ -62,10 +64,26 @@ path_already_found(std::vector<stream_model> streams, stream_model model) {
return false; return false;
} }
//! Tries to discover a route on all RTSP streams in DB bool
//! Uses the url.json file to try different routes brutepath::bruteforce_camera(const stream_model& stream) const {
for (const auto& route : conf.paths) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
if ((*cache)->has_changed(stream)) return true;
if (test_path(stream, route)) {
return true;
break;
}
}
return false;
}
// Tries to discover a route on all RTSP streams in DB
// Uses the url.json file to try different routes
bool bool
brutepath::run() const { brutepath::run() const {
std::vector<std::future<bool>> futures;
LOG_INFO_("Beginning bruteforce of the camera paths task, it may take a while.", "bruteforce"); LOG_INFO_("Beginning bruteforce of the camera paths task, it may take a while.", "bruteforce");
std::vector<stream_model> streams = (*cache)->get_streams(); std::vector<stream_model> streams = (*cache)->get_streams();
int found = 0; int found = 0;
@@ -74,22 +92,18 @@ brutepath::run() const {
break; break;
if (path_already_found(streams, stream)) { if (path_already_found(streams, stream)) {
LOG_INFO_(stream.address + LOG_INFO_(stream.address +
" : This camera's path was already discovered in the database." " : This camera's path was already discovered in the database. Skipping "
"Skipping to the next camera.", "to the next camera.",
"brutepath"); "brutepath");
++found; ++found;
} else { } else {
for (const auto& route : conf.paths) { futures.push_back(
if (signal_handler::instance().should_stop() != std::async(std::launch::async, &brutepath::bruteforce_camera, this, stream));
etix::cameradar::stop_priority::running)
break;
if (test_path(stream, route)) {
found++;
break;
}
}
} }
} }
for (auto& fit : futures) {
if (fit.get()) { ++found; }
}
if (!found) { if (!found) {
LOG_WARN_(no_route_found_, "brutepath"); LOG_WARN_(no_route_found_, "brutepath");
+13 -16
View File
@@ -17,27 +17,24 @@
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! The first command checks if dpkg finds nmap in the system by cutting the // The first command checks if dpkg finds nmap in the system by cutting the
//! result and grepping // result and grepping
//! nmap from it. // nmap from it.
//! //
//! The second command checks the version of nmap, right now it needs to be the // The second command checks the version of nmap, right now it needs to be the
//! 6.47 but this could // 6.47 but this could
//! be changed to 6 or greater depending on the needs. In a docker container // be changed to 6 or greater depending on the needs. In a docker container
//! this should not be a // this should not be a
//! problem. // problem.
bool bool
nmap_is_ok() { nmap_is_ok() {
return ( return (
launch_command("test `dpkg -l | cut -c 5-9 | grep nmap` = nmap") (system("dpkg -l | cut -c 5-9 | grep nmap") == 0)
// && launch_command("test `nmap --version | cut -c 14-18 | head -n2 | tail -n1` = 6.47") && launch_command("mkdir -p /tmp/scans")); // Creates the directory in which the scans will be stored
&&
launch_command(
"mkdir -p scans")); // Creates the directory in which the scans will be stored
} }
//! Launches and checks the return of the nmap command // Launches and checks the return of the nmap command
//! Uses the subnets specified in the conf file to launch nmap // Uses the subnets specified in the conf file to launch nmap
bool bool
mapping::run() const { mapping::run() const {
if (nmap_is_ok()) { if (nmap_is_ok()) {
+13 -13
View File
@@ -24,16 +24,16 @@ static const std::string no_hosts_found_ =
"were " "were "
"accessible"; "accessible";
//! Avoids segfaults on unknown xml structure // Avoids segfaults on unknown xml structure
std::string std::string
xml_safe_get(const TiXmlElement* elem, const std::string& attr) { xml_safe_get(const TiXmlElement* elem, const std::string& attr) {
if (elem == nullptr) return "Closed"; if (elem == nullptr) return "closed";
if (elem->Attribute(attr.c_str()) != nullptr) return std::string(elem->Attribute(attr.c_str())); if (elem->Attribute(attr.c_str()) != nullptr) return std::string(elem->Attribute(attr.c_str()));
return "Closed"; return "closed";
} }
//! Parse a single host node (generally containing only one camera) // Parse a single host node (generally containing only one camera)
//! Pushes it back to the data structure // Pushes it back to the data structure
void void
parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) const { parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) const {
TiXmlElement* xml_streams = xml_host->FirstChild("ports")->ToElement(); TiXmlElement* xml_streams = xml_host->FirstChild("ports")->ToElement();
@@ -51,15 +51,15 @@ parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) c
stream.service_name = xml_safe_get(service, "name"); stream.service_name = xml_safe_get(service, "name");
stream.product = xml_safe_get(service, "product"); stream.product = xml_safe_get(service, "product");
} else { } else {
stream.service_name = "Closed"; stream.service_name = "closed";
stream.product = "Closed"; stream.product = "closed";
} }
data.push_back(stream); if (!stream.state.compare("open")) data.push_back(stream);
} }
} }
//! Prints all detected cameras into the data structure and stops the program if // Prints all detected cameras into the data structure and stops the program if
//! no open RTSP streams were found // no open RTSP streams were found
bool bool
parsing::print_detected_cameras(const std::vector<stream_model>& data) const { parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
int added = 0; int added = 0;
@@ -90,8 +90,8 @@ parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
return true; return true;
} }
//! Opens the nmap output file, parses the data of each discovered port // Opens the nmap output file, parses the data of each discovered port
//! Adds the RTSP ports only into the DB // Adds the RTSP ports only into the DB
bool bool
parsing::run() const { parsing::run() const {
std::vector<stream_model> data; std::vector<stream_model> data;
@@ -113,7 +113,7 @@ parsing::run() const {
LOG_WARN_(no_hosts_found_, "parsing"); LOG_WARN_(no_hosts_found_, "parsing");
if (data.size() == 0) { LOG_WARN_("No cameras were discovered", "parsing"); } if (data.size() == 0) { LOG_WARN_("No cameras were discovered", "parsing"); }
return print_detected_cameras(data); return print_detected_cameras(data);
} catch (std::exception& e) { } catch (const std::exception& e) {
LOG_ERR_("Error during parsing. brutepath aborted : " + std::string(e.what()), "parsing"); LOG_ERR_("Error during parsing. brutepath aborted : " + std::string(e.what()), "parsing");
return false; return false;
} }
+3 -3
View File
@@ -17,14 +17,14 @@
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! Launches and checks the return of the nmap command // Launches and checks the return of the nmap command
//! Uses the subnets specified in the conf file to launch nmap // Uses the subnets specified in the conf file to launch nmap
bool bool
print::run() const { print::run() const {
std::vector<stream_model> results = (*cache)->get_valid_streams(); std::vector<stream_model> results = (*cache)->get_valid_streams();
std::ofstream file; std::ofstream file;
bool first = true; bool first = true;
file.open("result.json"); file.open("/tmp/shared/result.json");
file << "[\n"; file << "[\n";
for (const auto& stream : results) { for (const auto& stream : results) {
LOG_INFO_("Found a valid RTSP Stream and generated a thumbnail at : " + LOG_INFO_("Found a valid RTSP Stream and generated a thumbnail at : " +
@@ -17,9 +17,9 @@
namespace etix { namespace etix {
namespace cameradar { namespace cameradar {
//! Gets all the discovered streams with good routes and logs // Gets all the discovered streams with good routes and logs
//! And launches an ffmpeg command to generate a thumbnail // And launches an ffmpeg command to generate a thumbnail
//! In order to check for the stream validity // In order to check for the stream validity
bool bool
stream_check::run() const { stream_check::run() const {
GstElement* pipeline; GstElement* pipeline;
@@ -29,18 +29,29 @@ stream_check::run() const {
std::vector<stream_model> streams = (*cache)->get_valid_streams(); std::vector<stream_model> streams = (*cache)->get_valid_streams();
if (not streams.size()) {
LOG_WARN_("There were no valid streams to check. Cameradar will stop.", "stream_check");
return false;
}
for (const auto& stream : streams) { for (const auto& stream : streams) {
GError* error = NULL; GError* error = NULL;
pipeline = pipeline =
gst_parse_launch("rtspsrc name=source ! rtph264depay ! h264parse ! fakesink", &error); gst_parse_launch("rtspsrc name=source ! rtph264depay ! h264parse ! fakesink", &error);
std::string location = "rtsp://";
location += stream.username + ":" + stream.password + "@" + stream.address + ":" +
std::to_string(stream.port);
if (pipeline == NULL) { if (pipeline == NULL) {
LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check"); LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check");
return false; return false;
} else { } else {
elem = gst_bin_get_by_name(GST_BIN(pipeline), "source"); elem = gst_bin_get_by_name(GST_BIN(pipeline), "source");
g_object_set(G_OBJECT(elem), "location", stream.address, "latency", 20, NULL); LOG_DEBUG_("Launching gstreamer check on rtsp://" + stream.username + ":" +
stream.password + "@" + stream.address + ":" +
std::to_string(stream.port),
"gstreamer check");
g_object_set(G_OBJECT(elem), "location", location.c_str(), "latency", 20, NULL);
if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
LOG_ERR_( LOG_ERR_(
@@ -56,7 +67,9 @@ stream_check::run() const {
(*cache)->update_stream(invalidstream); (*cache)->update_stream(invalidstream);
return false; return false;
} }
LOG_INFO_("[" + stream.address + "] Set pipeline to playing", "stream_check"); LOG_INFO_("[" + stream.address +
"] This stream is accessible and seems to be functional",
"stream_check");
} }
} }
LOG_INFO_("All streams could be accessed with GStreamer", "stream_check"); LOG_INFO_("All streams could be accessed with GStreamer", "stream_check");
+64 -45
View File
@@ -41,56 +41,75 @@ thumbnail::build_output_file_path(const std::string& path) const {
return ss.str(); return ss.str();
} }
//! Gets all the discovered streams with good routes and logs bool
//! And launches an ffmpeg command to generate a thumbnail thumbnail::generate_thumbnail(const stream_model& stream) const {
//! In order to check for the stream validity LOG_INFO_("Generating thumbnail for " + stream.address, "thumbnail_generation");
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
return false;
std::string ffmpeg_cmd =
"mkdir -p %s ; "
"timeout 20 "
"ffmpeg "
"-rtsp_transport tcp "
"-y "
"-nostdin "
"-loglevel quiet " // no logs
"-i '%s' " // input
"-vcodec mjpeg " // jpeg codec
"-vframes 1 " // only take one frame
"-an " // disable audio
"-f image2 " // force image
"-s 240x180 " // force size
"'%s'";
std::string fullpath = make_path(stream);
std::string output = build_output_file_path(stream.address);
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
output.substr(0, output.find_last_of("/")).c_str(),
fullpath.c_str(),
output.c_str());
if (!launch_command(ffmpeg_cmd)) {
LOG_WARN_("The following command [" + ffmpeg_cmd +
"] didn't work. That can either mean that the stream is "
"not valid or "
"that there is a problem with the camera.",
"thumbnail_generation");
return false;
} else {
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
try {
stream_model result{ stream.address, stream.port, stream.username,
stream.password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
stream.path_found, stream.ids_found, output };
(*cache)->update_stream(result);
} catch (const std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); }
}
return true;
}
// Gets all the discovered streams with good routes and logs
// And launches an ffmpeg command to generate a thumbnail
// In order to check for the stream validity
bool bool
thumbnail::run() const { thumbnail::run() const {
std::vector<std::future<bool>> futures;
std::vector<stream_model> streams = (*cache)->get_valid_streams(); std::vector<stream_model> streams = (*cache)->get_valid_streams();
LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
for (const auto& stream : streams) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
std::string ffmpeg_cmd =
"mkdir -p %s ; "
"ffmpeg "
"-y "
"-nostdin "
"-loglevel quiet "
"-i '%s' "
"-vcodec mjpeg "
"-vframes 1 "
"-an "
"-f image2 "
"-s 320x240 "
"'%s'";
std::string fullpath = make_path(stream);
std::string output = build_output_file_path(stream.address);
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
output.substr(0, output.find_last_of("/")).c_str(),
fullpath.c_str(),
output.c_str());
if (!launch_command(ffmpeg_cmd)) {
LOG_WARN_("The following command [" + ffmpeg_cmd +
"] didn't work. That can either mean that the stream is "
"not valid or "
"that there is a problem with the camera.",
"thumbnail_generation");
} else {
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
try {
stream_model result{ stream.address, stream.port, stream.username,
stream.password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
stream.path_found, stream.ids_found, output };
(*cache)->update_stream(result);
} catch (std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); } LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
} if (not streams.size()) {
LOG_WARN_("There were no valid streams to generate thumbnails from. Cameradar will stop.",
"thumbnail_generation");
return false;
}
int done = 0;
for (const auto& stream : streams) {
futures.push_back(
std::async(std::launch::async, &thumbnail::generate_thumbnail, this, stream));
}
for (auto& fit : futures) {
if (fit.get()) { ++done; }
} }
LOG_INFO_("All thumbnails have been successfully generated in " +
this->conf.thumbnail_storage_path,
"thumbnail_generation");
return true; return true;
} }
} }
-91
View File
@@ -1,91 +0,0 @@
## Copyright 2016 Etix Labs
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
message(STATUS "Configuring deps.boost")
set(BOOST_VERSION 1.60.0)
# set(BoostSHA1 2fc96c1651ac6fe9859b678b165bd78dc211e881)
# Set up general b2 (bjam) command line arguments
set(b2Args <SOURCE_DIR>/b2
# link=static
threading=multi
runtime-link=shared
--layout=tagged
--build-dir=build
--without-wave
--without-python
stage
-d+2
)
if(TARGET_ARCH STREQUAL "x86_64")
list(APPEND b2Args address-model=64)
endif()
string(REPLACE "." "_" BOOST_VERSION_UNDERSCORE ${BOOST_VERSION})
set(BOOST_DIR boost)
set(BOOST_PATH ${DEPS_DIR}/${BOOST_DIR})
# Set up build steps
include(ExternalProject)
ExternalProject_Add(
deps.boost
PREFIX ${BOOST_PATH}
URL http://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION_UNDERSCORE}.tar.bz2/download
TIMEOUT 600
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E make_directory <SOURCE_DIR>/build
BUILD_COMMAND "${b2Args}"
# BUILD_COMMAND "<SOURCE_DIR>/b2 address-model=64 threading=multi runtime-link=shared --layout=tagged --build-dir=<SOURCE_DIR>/build"
BUILD_IN_SOURCE ON
INSTALL_COMMAND ""
# INSTALL_COMMAND <SOURCE_DIR>/b2 install --prefix=${BOOST_PATH}
LOG_DOWNLOAD ON
LOG_UPDATE ON
LOG_CONFIGURE ON
LOG_BUILD ON
LOG_TEST ON
LOG_INSTALL ON
)
# Set extra step to build b2 (bjam)
set(b2Bootstrap "./bootstrap.sh")
ExternalProject_Add_Step(
deps.boost
make_b2
COMMAND ${b2Bootstrap}
COMMENT "Building b2..."
DEPENDEES download
DEPENDERS configure
WORKING_DIRECTORY <SOURCE_DIR>
LOG ON
)
ExternalProject_Get_Property(deps.boost SOURCE_DIR)
set(BOOST_INCLUDE_DIR ${SOURCE_DIR} PARENT_SCOPE)
set(BOOST_LIBRARY_DIR "${SOURCE_DIR}/stage/lib")
set(BOOST_LIBRARY_DIR ${BOOST_LIBRARY_DIR} PARENT_SCOPE)
# list all the boost libraries .dylib/.so
file(GLOB BOOST_INSTALL_DEPENDENCIES "${BOOST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}boost_*${CMAKE_SHARED_LIBRARY_SUFFIX}")
list (APPEND CAMERADAR_INSTALL_DEPENDENCIES ${BOOST_INSTALL_DEPENDENCIES})
# on linux
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
file(GLOB BOOST_INSTALL_DEPENDENCIES "${BOOST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}boost_*${CMAKE_SHARED_LIBRARY_SUFFIX}.${BOOST_VERSION}")
list (APPEND CAMERADAR_INSTALL_DEPENDENCIES ${BOOST_INSTALL_DEPENDENCIES})
endif()
set(CAMERADAR_INSTALL_DEPENDENCIES ${CAMERADAR_INSTALL_DEPENDENCIES} PARENT_SCOPE)
+13 -5
View File
@@ -1,8 +1,16 @@
# Copyright (C) 2015 Etix Labs - All Rights Reserved. ## Copyright 2016 Etix Labs
# All information contained herein is, and remains the property of Etix Labs and its suppliers, ##
# if any. The intellectual and technical concepts contained herein are proprietary to Etix Labs ## Licensed under the Apache License, Version 2.0 (the "License");
# Dissemination of this information or reproduction of this material is strictly forbidden unless ## you may not use this file except in compliance with the License.
# prior written permission is obtained from Etix Labs. ## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
# MySQL Connector dependency # MySQL Connector dependency
message(STATUS "Configuring deps.mysqlconnector") message(STATUS "Configuring deps.mysqlconnector")
+9 -5
View File
@@ -1,4 +1,4 @@
FROM ubuntu:15.10 FROM ubuntu:16.04
MAINTAINER brendan.leglaunec@etixgroup.com MAINTAINER brendan.leglaunec@etixgroup.com
@@ -7,17 +7,21 @@ ENV LD_LIBRARY_PATH="/cameradar/libraries"
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
nmap \ nmap \
ffmpeg \ ffmpeg \
libboost-all-dev \
libgstreamer1.0-dev \ libgstreamer1.0-dev \
gstreamer1.0-plugins-base \ gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \ gstreamer1.0-plugins-good \
libcurl4-openssl-dev \ libcurl4-openssl-dev \
libmysqlclient18 \ libmysqlclient20 \
mysql-client mysql-client
ADD cameradar_*_Release_Linux.tar.gz / ADD cameradar_*_Release_Linux.tar.gz /
RUN mv cameradar_*_Release_Linux cameradar RUN mv cameradar_*_Release_Linux cameradar
RUN mkdir /conf COPY conf /cameradar/conf
ADD run.sh /run.sh
CMD ["/run.sh"] COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s /usr/local/bin/docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["/cameradar/bin/cameradar", "-c", "/cameradar/conf/cameradar.conf.json"]
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
ESC_SEQ="\x1b["
COL_RESET=$ESC_SEQ"39;49;00m"
COL_RED=$ESC_SEQ"31;01m"
COL_GREEN=$ESC_SEQ"32;01m"
COL_YELLOW=$ESC_SEQ"33;01m"
COL_BLUE=$ESC_SEQ"34;01m"
COL_MAGENTA=$ESC_SEQ"35;01m"
COL_CYAN=$ESC_SEQ"36;01m"
echo -e $COL_YELLOW"Deleting old package ... "$COL_RESET
rm -f cameradar_*_Release_Linux.tar.gz
echo -e $COL_GREEN"OK!"$COL_RESET
echo -e $COL_YELLOW"Creating package ... "$COL_RESET
{
cd ..
mkdir build
cd build
rm -f cameradar_*_Release_Linux.tar.gz
cmake .. -DCMAKE_BUILD_TYPE=Release
make package
cp cameradar_*_Release_Linux.tar.gz ../deployment
cd ../deployment
} &> /dev/null
echo -e $COL_GREEN"OK!"$COL_RESET
Binary file not shown.
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
{
"mysql_db" : {
"host" : "__MYSQL_ADDR__",
"port" : __MYSQL_PORT__,
"user": "root",
"password": "root",
"db_name": "cmrdr"
},
"subnets" : "localhost",
// If not specified, will scan all ports (1-65535)
"ports" : "554,8554",
"rtsp_url_file" : "conf/url.json",
"rtsp_ids_file" : "conf/ids.json",
// You must give an accessible path to an already existing directory
"thumbnail_storage_path" : "/tmp",
// This is the path that will be used in the Docker container
// if you're not familiar with Docker, only change the
// cache_manager_name value
"cache_manager_path" : "/cameradar/cache_managers",
"cache_manager_name" : "mysql"
}
+16
View File
@@ -0,0 +1,16 @@
{
"mysql_db" : {
"host" : "cameradar-database",
"port" : 3306,
"user": "root",
"password": "$MYSQL_ROOT_PASSWORD",
"db_name": "cmrdr"
},
"subnets" : "$CAMERAS_SUBNETWORKS",
"ports" : "$CAMERAS_PORTS",
"rtsp_url_file" : "/cameradar/conf/url.json",
"rtsp_ids_file" : "/cameradar/conf/ids.json",
"thumbnail_storage_path" : "/tmp/thumbs",
"cache_manager_path" : "/cameradar/cache_managers",
"cache_manager_name" : "$CACHE_MANAGER"
}
+1
View File
@@ -24,6 +24,7 @@
"/camera.stm", "/camera.stm",
"/ch0", "/ch0",
"/ch001.sdp", "/ch001.sdp",
"/ch01.264",
"/ch0_unicast_firststream", "/ch0_unicast_firststream",
"/ch0_unicast_secondstream", "/ch0_unicast_secondstream",
"/channel1", "/channel1",
+25 -16
View File
@@ -1,16 +1,25 @@
cameradar: version: '2'
build: .
dockerfile: Dockerfile services:
env_file: env_file cameradar:
volumes: build: .
- "./conf:/tmp/conf:ro" container_name: cameradar
- "./cameradar_thumbnails:/tmp/cameradar_thumbnails" volumes:
links: - "./cameradar_thumbnails:/tmp/thumbs"
- ext_cctv_mysql - ".:/tmp/shared"
ext_cctv_mysql: environment:
image: mysql:5.7 - CAMERAS_SUBNETWORKS=localhost
environment: - CAMERAS_PORTS=554,8554
MYSQL_ROOT_PASSWORD: root - CACHE_MANAGER=dumb
MYSQL_DATABASE: cmrdr - MYSQL_ROOT_PASSWORD=root
ports: cameradar-database:
- "3306:3306" container_name: cameradar-database
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=cmrdr
ports:
- "3306:3306"
volumes:
mysql_data:
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
ESC_SEQ="\x1b["
COL_RESET=$ESC_SEQ"39;49;00m"
COL_RED=$ESC_SEQ"31;01m"
COL_GREEN=$ESC_SEQ"32;01m"
# if command starts with an option, prepend /cameradar/bin/cameradar
if [ "${1:0:1}" = '-' ]; then
set -- /cameradar/bin/cameradar "$@"
fi
# skip setup if they want an option that stops cameradar
wantHelp=
for arg; do
case "$arg" in
-v|-h)
wantHelp=1
break
;;
esac
done
envsubst < /cameradar/conf/cameradar.tmpl.conf.json > /cameradar/conf/cameradar.conf.json
if [ "$CACHE_MANAGER" == "mysql" ] && [ "$1" = '/cameradar/bin/cameradar' -a -z "$wantHelp" ]; then
echo -n "Waiting for cameradar-database to be ready..."
while ! mysqladmin ping -h "cameradar-database" -P3306 --silent; do
sleep 1; echo -n "."
done
echo -e $COL_GREEN"ok"$COL_RESET
echo "Cameradar init finished. Starting it."
fi
exec "$@"
-2
View File
@@ -1,2 +0,0 @@
CAMERAS_SUBNETWORKS=172.16.100.0/24,192.168.178.47
CAMERAS_PORTS=554,8554
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
ESC_SEQ="\x1b["
COL_RESET=$ESC_SEQ"39;49;00m"
COL_RED=$ESC_SEQ"31;01m"
COL_GREEN=$ESC_SEQ"32;01m"
COL_YELLOW=$ESC_SEQ"33;01m"
COL_BLUE=$ESC_SEQ"34;01m"
COL_MAGENTA=$ESC_SEQ"35;01m"
COL_CYAN=$ESC_SEQ"36;01m"
# declare usefuls vars
CONF=/conf/cameradar.conf.json
# copy configuration
cp /tmp/conf/* /conf/
echo -n "replacing cameras subnetworks in configuration "
sed -i s#__CAMERAS_SUBNETWORKS__#$CAMERAS_SUBNETWORKS#g $CONF
echo -e $COL_GREEN"ok"$COL_RESET
echo -n "replacing cameras ports in configuration "
sed -i s#__PORTS_TO_CHECK__#$CAMERAS_PORTS#g $CONF
echo -e $COL_GREEN"ok"$COL_RESET
# Replace ext_cctv_mysql with the IP address of your DB or the name of its Docker
# container. The container has to be linked in docker-compose.yml for cameradar
# to be able to interact with it.
echo -n "replacing mysql host and port in configuration "
sed -i s#__MYSQL_ADDR__#ext_cctv_mysql#g $CONF
# Reaplce 3306 with the port of your DB
sed -i s#__MYSQL_PORT__#3306#g $CONF
echo -e $COL_GREEN"ok"$COL_RESET
/cameradar/bin/cameradar -l 1 -c /conf/cameradar.conf.json &
cameradar_pid=$!
trap 'kill -2 $cameradar_pid; wait $cameradar_pid; exit $?' SIGTERM SIGINT
wait $cameradar_pid
+45
View File
@@ -0,0 +1,45 @@
FROM ubuntu:15.10
MAINTAINER brendan.leglaunec@etixgroup.com
ENV LD_LIBRARY_PATH="/cameradar/libraries"
# install go
RUN apt-get update && apt-get install -y make git wget curl
RUN wget https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz
RUN tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz
# set variable env
ENV GOPATH=/cameradartest/go
ENV PATH=$PATH:/go/bin
ENV PATH=$PATH:/usr/local/go/bin
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
# needed for cameradar
RUN apt-get update && apt-get install -y \
nmap \
libmysqlclient18 \
ffmpeg \
mysql-client \
libgstreamer1.0-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
libcurl4-openssl-dev
RUN apt-get install -y psmisc
ADD cameradar_*_Debug_Linux.tar.gz /
RUN mv cameradar_*_Debug_Linux cameradar
# create cameradaratest folder in go src path
RUN mkdir -p /cameradartest/go/src/cameradartest
COPY src/*.go /cameradartest/go/src/cameradartest/
COPY ./conf /conf
ADD ./docker/run_cameradartest.sh /run.sh
# get go deps
RUN go get github.com/go-sql-driver/mysql
RUN mkdir /thumbnails
WORKDIR /cameradartest/go/src/cameradartest
RUN go build -o cameradartest *.go
CMD ["/run.sh"]
+23
View File
@@ -0,0 +1,23 @@
FROM ubuntu:16.04
MAINTAINER brendan.leglaunec@etixgroup.com
RUN apt-get update && apt-get install -y \
libgstrtspserver-1.0-dev \
libgstreamer1.0-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-libav \
gstreamer1.0-tools \
libssl-dev mysql-client \
gstreamer1.0-plugins-good \
libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-bad1.0-dev
ADD ./docker/screen.png /vlc/screen.png
COPY ./docker/run_ces.sh /start.sh
COPY ./camera_emulation_server /camera_emulation_server
EXPOSE 8554
RUN ./camera_emulation_server&
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
ESC_SEQ="\x1b["
COL_RESET=$ESC_SEQ"39;49;00m"
COL_RED=$ESC_SEQ"31;01m"
COL_GREEN=$ESC_SEQ"32;01m"
COL_YELLOW=$ESC_SEQ"33;01m"
COL_BLUE=$ESC_SEQ"34;01m"
COL_MAGENTA=$ESC_SEQ"35;01m"
COL_CYAN=$ESC_SEQ"36;01m"
echo -e $COL_YELLOW"Deleting old package ... "$COL_RESET
rm -f cameradar_*_Debug_Linux.tar.gz
echo -e $COL_GREEN"OK!"$COL_RESET
echo -e $COL_YELLOW"Creating package ... "$COL_RESET
{
cd ..
mkdir build
cd build
rm -f cameradar_*_Debug_Linux.tar.gz
cmake .. -DCMAKE_BUILD_TYPE=Debug
make package
cp cameradar_*_Debug_Linux.tar.gz ../test
cd ../test
} &> /dev/null
echo -e $COL_GREEN"OK!"$COL_RESET
BIN
View File
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
{
"mysql_db" : {
"host" : "cameradar-database",
"port" : 3306,
"user": "root",
"password": "root",
"db_name": "cmrdr"
},
"subnets" : "localhost",
"ports" : "554,8554",
"rtsp_url_file" : "/conf/url.json",
"rtsp_ids_file" : "/conf/ids.json",
"thumbnail_storage_path" : "/tmp",
"cache_manager_path" : "/cameradar/cache_managers",
"cache_manager_name" : "dumb"
}
+44
View File
@@ -0,0 +1,44 @@
{
"Output": "cameratest.log.xml",
"Cameradar" : {
"Path": "/cameradar/cameradar_standalone/cameradar",
"Args": "-s 172.17.0.0/24 -c /conf/cameradar.conf.json --gst-rtsp-server",
"Ports": "554,5554,8554",
"IdsPath": "/conf/ids.json",
"RoutesPath": "/conf/url.json",
"ThumbPath": "/tmp",
"dbHost": "cameradar-database",
"dbPort": 3306,
"dbUser": "root",
"dbPassword": "root",
"dbName": "cmrdr",
"Console": false
},
"Tests" : [
{
"address" : "127.0.0.1",
"password" : "",
"port" : "8554",
"route" : "live.sdp",
"username" : "",
"valid" : true
},
{
"address" : "172.16.100.11",
"password" : "",
"port" : "553",
"route" : "live.sdp",
"username" : "admin",
"valid" : false
},
{
"address" : "172.16.100.13",
"password" : "",
"port" : "554",
"route" : "live.sdp",
"username" : "admin",
"valid" : true
}
]
}
+31
View File
@@ -0,0 +1,31 @@
{
"username": [
"",
"admin",
"Admin",
"root",
"supervisor",
"ubnt"
],
"password" : [
"",
"admin",
"9999",
"123456",
"pass",
"camera",
"1234",
"12345",
"fliradmin",
"system",
"jvc",
"meinsm",
"root",
"4321",
"1111111",
"password",
"ikwd",
"supervisor",
"ubnt"
]
}
+77
View File
@@ -0,0 +1,77 @@
{
"urls" : [
"/",
"/1.AMP",
"/1/stream1",
"/CAM_ID.password.mp2",
"/GetData.cgi",
"/MediaInput/h264",
"/MediaInput/mpeg4",
"/VideoInput/1/h264/1",
"/access_code",
"/access_name_for_stream_1_to_5",
"/av0_0",
"/av2",
"/avn=2",
"/axis-media/media.amp",
"/cam",
"/cam0_0",
"/cam0_1",
"/cam1/h264",
"/cam1/h264/multicast",
"/cam1/mjpeg",
"/cam1/mpeg4",
"/camera.stm",
"/ch0",
"/ch001.sdp",
"/ch0_unicast_firststream",
"/ch0_unicast_secondstream",
"/channel1",
"/h264",
"/h264/media.amp",
"/image.mpg",
"/img/media.sav",
"/img/video.asf",
"/img/video.sav",
"/ioImage/1",
"/ipcam.sdp",
"/ipcam_h264.sdp",
"/live.sdp",
"/live/h264",
"/live/mpeg4",
"/live_mpeg4.sdp",
"/livestream",
"/livestream/",
"/media/media.amp",
"/media/video1",
"/mjpeg/media.smp",
"/mp4",
"/mpeg4",
"/mpeg4/1/media.amp",
"/mpeg4/media.amp",
"/mpeg4/media.smp",
"/mpeg4unicast",
"/mpg4/rtsp.amp",
"/multicaststream",
"/now.mp4",
"/nph-h264.cgi",
"/nphMpeg4/g726-640x",
"/nphMpeg4/g726-640x480",
"/nphMpeg4/nil-320x240",
"/play1.sdp",
"/play2.sdp",
"/rtpvideo1.sdp",
"/rtsp_tunnel",
"/rtsph264",
"/stream1",
"/user.pin.mp2",
"/user_defined",
"/video",
"/video.3gp",
"/video.mp4",
"/video1",
"/video1+audio1",
"/vis",
"/wfov"
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"output": "test-results.xml",
"cameradar" : {
"path": "/cameradar/bin/cameradar",
"args": "-s 172.17.0.0/24 -c /conf/cameradar.conf.json --gst-rtsp-server",
"ports": "554,5554,8554",
"ids_path": "conf/ids.json",
"routes_path": "conf/url.json",
"thumb_path": "/tmp",
"db_host": "cameradar-database",
"db_port": 3306,
"db_user": "root",
"db_password": "root",
"db_name": "cmrdr",
"console": false
},
"tests" : __CAMERAS__
}
+31
View File
@@ -0,0 +1,31 @@
{
"username": [
"",
"admin",
"Admin",
"root",
"supervisor",
"ubnt"
],
"password" : [
"",
"admin",
"9999",
"123456",
"pass",
"camera",
"1234",
"12345",
"fliradmin",
"system",
"jvc",
"meinsm",
"root",
"4321",
"1111111",
"password",
"ikwd",
"supervisor",
"ubnt"
]
}
+77
View File
@@ -0,0 +1,77 @@
{
"urls" : [
"/",
"/1.AMP",
"/1/stream1",
"/CAM_ID.password.mp2",
"/GetData.cgi",
"/MediaInput/h264",
"/MediaInput/mpeg4",
"/VideoInput/1/h264/1",
"/access_code",
"/access_name_for_stream_1_to_5",
"/av0_0",
"/av2",
"/avn=2",
"/axis-media/media.amp",
"/cam",
"/cam0_0",
"/cam0_1",
"/cam1/h264",
"/cam1/h264/multicast",
"/cam1/mjpeg",
"/cam1/mpeg4",
"/camera.stm",
"/ch0",
"/ch001.sdp",
"/ch0_unicast_firststream",
"/ch0_unicast_secondstream",
"/channel1",
"/h264",
"/h264/media.amp",
"/image.mpg",
"/img/media.sav",
"/img/video.asf",
"/img/video.sav",
"/ioImage/1",
"/ipcam.sdp",
"/ipcam_h264.sdp",
"/live.sdp",
"/live/h264",
"/live/mpeg4",
"/live_mpeg4.sdp",
"/livestream",
"/livestream/",
"/media/media.amp",
"/media/video1",
"/mjpeg/media.smp",
"/mp4",
"/mpeg4",
"/mpeg4/1/media.amp",
"/mpeg4/media.amp",
"/mpeg4/media.smp",
"/mpeg4unicast",
"/mpg4/rtsp.amp",
"/multicaststream",
"/now.mp4",
"/nph-h264.cgi",
"/nphMpeg4/g726-640x",
"/nphMpeg4/g726-640x480",
"/nphMpeg4/nil-320x240",
"/play1.sdp",
"/play2.sdp",
"/rtpvideo1.sdp",
"/rtsp_tunnel",
"/rtsph264",
"/stream1",
"/user.pin.mp2",
"/user_defined",
"/video",
"/video.3gp",
"/video.mp4",
"/video1",
"/video1+audio1",
"/vis",
"/wfov"
]
}
+111
View File
@@ -0,0 +1,111 @@
#!/bin/bash
ports=('8554' '8554' '8554' '8554' '8554' '8554')
users=('admin' 'root' 'ubnt' 'Admin' 'supervisor' '')
passwords=('admin' 'root' '12345' 'ubnt' 'password' '')
routes=('cam0_0' 'live.sdp' 'ch001.sdp' 'cam' 'invalid' 'live_mpeg4.sdp')
cams_name_pattern="fake_camera_"
# json generation variable only
json="[\n"
first=true
# $1 = adress, $2 = port, $3 = path, $4 = usernam $5 = password, $6 = valid
function make_json {
# Get all data about the container, this will return three lines
# One empty that we ignore
# the two other ones with the IP of our container
# We take the second one using sed and cut to get only the IPAddress
address="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $CID)"
if [ "$first" = true ] ; then first=false
else json="$json,\n"; fi
json="$json{"
json="$json\"address\":\"$address\","
json="$json\"port\":$2,"
json="$json\"route\":\"$3\","
json="$json\"username\":\"$4\","
json="$json\"password\":\"$5\","
json="$json\"valid\":$6"
json="$json}"
}
# $1 = configuration template path
function generate_conf {
echo "generate configuration"
sed s#__CAMERAS__#$json#g $1 > cameradartest.conf.json
}
# $1 = numbers of cameras to generate
function start {
# Seed random generator
RANDOM=$(date +%s)
# start cameras
for (( i=1; i<=$1; i++ )); do
name="$cams_name_pattern$i"
# random conf
conf_idx=$(($RANDOM % ${#ports[@]}))
# get conf variables
port=${ports[$conf_idx]}
user=${users[$conf_idx]}
passw=${passwords[$conf_idx]}
route=${routes[$conf_idx]}
is_valid=true
# if conf_idx = 4 -> invalid conf
if [ "$conf_idx" == "4" ] ; then is_valid=false; fi
CID=$(docker run -d --name "$name" fake-camera /start.sh "$port" "$user" "$passw" "$route");
make_json "$name" "$port" "$route" "$user" "$passw" $is_valid $CID
done
# finalize json
json="$json]"
}
function stop {
# if no cameras containers are started just exit
camera_count="`docker ps -a -q --filter="name=$cams_name_pattern" | wc -l`"
if [ "$camera_count" == "0" ]; then
echo "error: no cameras started"; exit 1
fi
echo "stopping and removing $camera_count containers"
# docker stop $(docker ps -a -q --filter="name=$cams_name_pattern")
docker rm -f $(docker ps -a -q --filter="name=$cams_name_pattern") > /dev/null
}
# need first argument at least
if [ "$1" == "" ]; then
echo "error: invalid number of argument"
exit 1
fi
case $1 in
"start")
# check if the argument is a number.
re='^[0-9]+$'
if ! [[ $2 =~ $re ]] ; then
echo "error: argument is not a number"; exit 1
fi
if [[ "$3" == "" ]] ; then
echo "error: missing path to the configuration file template"; exit 1
fi
echo "starting $2 cameras"
start $2
generate_conf $3
;;
"stop")
echo "stopping all cameras tests"
stop
;;
"help")
echo "./gen_cameras.sh start CAMS_NB - start CAMS_NB cameras"
echo " stop - stop all started cameras"
echo " help - display this help"
exit 0
;;
*)
echo "invalid test name"
exit 1
;;
esac
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
while ! mysqladmin ping -h"cameradar-database" -P3306 --silent; do
sleep 1
done
cat /tmp/tests/cameradartest.conf.json
# build
go build
cp /tmp/tests/*.xml ./
# run test
./cameradartest /tmp/tests/cameradartest.conf.json
cat *.xml
cp *.xml /tmp/tests/
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
port=$1
user=$2
passw=$3
route=$4
url=""
# need first argument at least
if [ "$2" == "" ]; then
url="rtsp://:$port/$route"
else
url="rtsp://$user:$passw@:$port/$route"
fi
./camera_emulation_server -u $2 -p $3 -r $4
echo "Stream started on ${url}"
Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

BIN
View File
Binary file not shown.
Executable
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
# check if a debug package exist in the current folder
if ! ls ./cameradar_*_Debug_Linux.tar.gz 1> /dev/null 2>&1; then
(echo "no debug package in the current folder"; exit 137)
exit 137
fi
cams_name_pattern="fake_camera_"
cmd=""
function make_docker_command {
cmd="docker run --rm"
# start cameras
for (( i=1; i<=$1; i++ )); do
name="$cams_name_pattern$i"
cmd="$cmd --link=\"$name\""
done
# add mysql libk
cmd="$cmd --link=\"cameradar-database\""
# add cameradar srcs
cmd="$cmd -v \"`pwd`/src:/go/src/cameradartest\""
# add cmaeradar conf
cmd="$cmd -v \"`pwd`/:/tmp/tests\""
# add container name
cmd="$cmd -v \"`pwd`/:/tmp/shared\""
# add container name
cmd="$cmd cameradartest"
}
function start_test {
./docker/gen_cameras.sh start $1 ./docker/cameratest.conf.tmpl.json
eval $cmd
make_docker_command $1
./docker/gen_cameras.sh stop
}
# build images
echo "building docker images"
# building fake-camera container
docker build --no-cache -f Dockerfile-camera -t fake-camera .
# building cameradartest image
docker build --no-cache -t cameradartest .
# getting mysql
echo "starting mysql"
docker pull mysql:5.7
docker run --name cameradar-database -e MYSQL_DATABASE=cmrdr -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
start_test 1
start_test 5
# stop mysql
echo "stopping mysql"
docker rm -f cameradar-database
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"os"
)
func (t *Tester) parseConfig() bool {
// Get config file path
confPath := "conf/cameratest.conf.json"
av := len(os.Args)
if av > 1 {
confPath = os.Args[1]
}
// Load config
fmt.Printf("Loading Tester configuration file: %s ... ", confPath)
configFile, err := os.Open(confPath)
if err != nil {
fmt.Printf("\nCan't open Tester configuration file: %s\n", err)
return false
}
dec := json.NewDecoder(configFile)
if err = dec.Decode(&t); err != nil {
fmt.Printf("\nUnable to deserialize Tester configuration file: %s\n", err)
return false
}
fmt.Println("Tester configuration file successfully loaded")
return true
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"database/sql"
"fmt"
"strconv"
_ "github.com/go-sql-driver/mysql"
)
// MysqlDB contains the MySQL configuration
type MysqlDB struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
DbName string `json:"db_name"`
}
func (t *Tester) dropDB() bool {
dsn := t.DB.User + ":" + t.DB.Password + "@" + "tcp(" + t.DB.Host + ":" + strconv.Itoa(t.DB.Port) + ")/" + t.DB.DbName + "?charset=utf8"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println(err)
}
defer db.Close()
q := "DROP DATABASE " + t.DB.DbName + ";"
_, err = db.Exec(q)
if err != nil {
fmt.Println(err)
}
fmt.Println("------ Dropped CCTV Database -------")
return true
}
func (t *Tester) configureDatabase(DataBase *MysqlDB) bool {
var db MysqlDB
db.Host = t.Cameradar.DbHost
db.Port = t.Cameradar.DbPort
db.User = t.Cameradar.DbUser
db.Password = t.Cameradar.DbPassword
db.DbName = t.Cameradar.DbName
*DataBase = db
return true
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
"strconv"
)
func (m *manager) dropDB() bool {
dsn := m.DB.User + ":" + m.DB.Password + "@" + "tcp(" + m.DB.Host + ":" + strconv.Itoa(m.DB.Port) + ")/" + m.DB.Db_name + "?charset=utf8"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println(err)
}
defer db.Close()
q := "DROP DATABASE cctv;"
_, err = db.Exec(q)
if err != nil {
fmt.Println(err)
}
fmt.Println("------ Dropped CCTV Database -------")
return true
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"encoding/json"
"fmt"
"os"
)
// Launch it via goroutine
// Start read log of service
func getResult(test *[]Result, resultPath string) bool {
// Load config
resultFile, err := os.Open(resultPath)
if err != nil {
fmt.Printf("\nCan't open result file: %s\n", err)
return false
}
dec := json.NewDecoder(resultFile)
if err = dec.Decode(&test); err != nil {
fmt.Printf("\nUnable to deserialize result file: %s\n", err)
return false
}
return true
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
)
// Launch it via goroutine
// Start read log of service
func readLog(service *Service, reader io.ReadCloser) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
str := scanner.Text()
if service.Console {
fmt.Printf("[%s] %s\n", service.Path, str)
}
fmt.Printf("%s\n", str)
service.Mutex.Lock()
service.Logs = append(service.Logs, str)
service.Mutex.Unlock()
}
if err := scanner.Err(); err != nil {
fmt.Printf("[%s] Service failed: %s\n", service.Path, err)
}
fmt.Printf("Logger of service: [%s] stopped\n", service.Path)
service.Active = false
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
)
func main() {
Tester := new(Tester)
defer Tester.Stop()
// Parse conf (streams should already be launched by Jenkins)
fmt.Println("--- Initializing Cameradar Test Tool ... ---")
if !Tester.Init() {
fmt.Println("-> Cameradar Test Tool initialization FAILED")
return
}
// Run tests
if !Tester.Run() {
fmt.Println("-> Cameradar Test Tool FAILED")
}
// Write results
fmt.Println("--- Writing results... ---")
if !Tester.WriteResults(*(Tester.Result), Tester.Output) {
fmt.Println("-> Write results FAILED")
return
}
fmt.Println("--- Writing results done ---")
}
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"fmt"
"sync"
)
type manager struct {
Config
Tests []Result
Result *TestCase
DB mysql_db
}
// Config needs refacto
type Config struct {
Cameradar Service `json:"Cameradar"`
Output string
}
func (m *manager) Init() bool {
fmt.Println("- Parsing")
if !m.parseConfig() {
return false
}
fmt.Println("- Cleaning content")
killService(&m.Config.Cameradar)
return true
}
func (m *manager) Run() bool {
var wg sync.WaitGroup
fmt.Println("\n- Launching all tests")
var newTest = new(TestCase)
newTest.expected = m.Tests
if m.generateConfig(m.Tests, &m.DB) {
m.dropDB()
wg.Add(1)
go m.invokeTestCase(newTest, &wg)
m.Result = newTest
}
wg.Wait()
fmt.Printf("All tests completed\n")
return true
}
func (m *manager) Stop() bool {
killService(&m.Config.Cameradar)
return true
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
)
// Result contains the data of a Cameradar result, plus an error field in order to add error messages to the JUnit report
type Result struct {
Address string `json:"address"`
IDsFound bool `json:"ids_found"`
PathFound bool `json:"path_found"`
Password string `json:"password"`
Port int `json:"port"`
Route string `json:"route"`
ServiceName string `json:"service_name"`
Protocol string `json:"protocol"`
State string `json:"state"`
Username string `json:"username"`
Valid bool `json:"valid"`
Thumb string `json:"thumbnail_path"`
err error // in case of a fail, add a message
}
// Launch it via goroutine
// Start read log of service
func getResult(test *[]Result, resultPath string) bool {
// Load config
resultFile, err := os.Open(resultPath)
if err != nil {
fmt.Printf("\nCan't open result file: %s\n", err)
return false
}
dec := json.NewDecoder(resultFile)
if err = dec.Decode(&test); err != nil {
fmt.Printf("\nUnable to deserialize result file: %s\n", err)
return false
}
return true
}
func isValid(e Result, r Result) bool {
if e.Username != r.Username {
e.err = errors.New(e.Address + " had a different username than " + r.Username)
return false
}
if e.Password != r.Password {
e.err = errors.New(e.Address + " had a different password than " + r.Password)
return false
}
if e.Port != r.Port {
e.err = errors.New(e.Address + " had a different port than expected")
return false
}
if e.Valid != r.Valid {
e.err = errors.New(e.Address + " had a different validity than expected")
return false
}
return true
}
// Extend needs refacto
func Extend(slice []Result, element Result) []Result {
n := len(slice)
if n == cap(slice) {
// Slice is full; must grow.
// We double its size and add 1, so if the size is zero we still grow.
newSlice := make([]Result, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : n+1]
slice[n] = element
return slice
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os/exec"
"strings"
"sync"
)
// Service needs refacto
type Service struct {
Path string `json:"path"`
Args string `json:"args"`
Ports string `json:"ports"`
IdsPath string `json:"ids_path"`
RoutesPath string `json:"routes_path"`
ThumbPath string `json:"thumb_path"`
DbHost string `json:"db_host"`
DbPort int `json:"db_port"`
DbUser string `json:"db_user"`
DbPassword string `json:"db_password"`
DbName string `json:"db_name"`
Console bool `json:"console"`
Logs []string
Active bool // Based on io.ReadCloser status
Mutex sync.Mutex
cmd *exec.Cmd // Go handler of the service
}
func startService(service *Service) bool {
// Launch service
args := strings.Fields(service.Args)
service.cmd = exec.Command(service.Path, args...)
handler, err := service.cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
return false
}
errHandler, err := service.cmd.StderrPipe()
if err != nil {
fmt.Println(err)
return false
}
// Launch
err = service.cmd.Start()
if err != nil {
fmt.Println(err)
return false
}
fmt.Printf("Service: [%s] started\n", service.Path)
service.Active = true
// Read service logs and update service status
// Set pipes
go readLog(service, handler)
go readLog(service, errHandler)
return true
}
// Stop only specified service instance
func stopService(service *Service) {
service.cmd.Process.Kill()
}
// Kill all instances of specified service
func killService(service *Service) {
// Sending SIGTERM
fmt.Printf("Executing: killall %s\n", service.Path)
cmd := exec.Command("killall", service.Path)
err := cmd.Run()
if err != nil {
fmt.Println(err)
}
sigAbort := []string{service.Path, "-s", "SIGABRT"}
fmt.Printf("Executing: killall %s -s SIGABRT\n", service.Path)
cmd = exec.Command("killall", sigAbort...)
err = cmd.Run()
if err != nil {
fmt.Println(err)
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"errors"
"fmt"
"sync"
"time"
)
// Test represents a test launched with Cameradar
type Test struct {
expected []Result
result []Result
time time.Duration
}
// Invoke the test
// Wrap results in a TestResult object
func (t *Tester) invokeTestCase(testCase *Test, wg *sync.WaitGroup) {
startTime := time.Now()
t.runTestCase(testCase)
testCase.time = time.Since(startTime)
fmt.Printf("Test OK in %.6fs\n", testCase.time.Seconds())
wg.Done()
}
// Checks all valid results that are supposed to match
// Adds them to the valid results and leave the failed
// ones in the expected slice
//
// Then, if the result did not match the expected but it was supposed to fail
// Add it to the valid results and remove it from the expected slice
func (t *Tester) runTestCase(test *Test) {
startService(&t.Cameradar)
for t.Cameradar.Active {
time.Sleep(25 * time.Millisecond)
}
var validResults []Result
if getResult(&test.result, "/tmp/shared/result.json") {
for _, r := range test.result {
r.Valid = true
for index, e := range test.expected {
if e.Address == r.Address && isValid(e, r) {
// _, err := os.Stat(r.Thumb)
// if err == nil) {
fmt.Println("The result of ", r.Address, " is valid and the thumbnails were generated by Cameradar.")
validResults = Extend(validResults, r)
if len(test.expected) > 1 {
test.expected = append(test.expected[:index], test.expected[index+1:]...)
}
break
// } else {
// e.err = error{"The result of " + e.Address + " seemed valid, but the thumbnails could not be generated by Cameradar : " + err.Error()}
// }
}
}
}
for index, e := range test.expected {
if !e.Valid {
fmt.Println("The result of", e.Address, "successfully failed.")
validResults = Extend(validResults, e)
if len(test.expected) > 1 {
test.expected = append(test.expected[:index], test.expected[index+1:]...)
}
} else {
e.err = errors.New("The camera with the address " + e.Address + " was not found by cameradar")
fmt.Println("Should have been valid but was not found : ", e.Address)
}
}
test.result = validResults
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"sync"
)
// Tester is the structure that will manage the whole testing
type Tester struct {
Cameradar Service `json:"cameradar"`
Output string
Tests []Result
Result *Test
DB MysqlDB
}
// Init gets the testing configuration and makes sure that no other Cameradar service is running at the moment
func (t *Tester) Init() bool {
fmt.Println("- Parsing")
if !t.parseConfig() {
return false
}
fmt.Println("- Cleaning content")
killService(&t.Cameradar)
return true
}
// Run launches the tests that have been set up by the init method
func (t *Tester) Run() bool {
var wg sync.WaitGroup
fmt.Println("\n- Launching all tests")
var newTest = new(Test)
newTest.expected = t.Tests
if t.configureDatabase(&t.DB) {
t.dropDB()
wg.Add(1)
go t.invokeTestCase(newTest, &wg)
t.Result = newTest
}
wg.Wait()
fmt.Println("All tests completed")
return true
}
// Stop kills the service launched by the tester
func (t *Tester) Stop() bool {
killService(&t.Cameradar)
return true
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
)
////////////////////////////////////////////////
// Data declarations
// JUnitTestSuites is a collection of JUnit test suites.
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
TestSuites []JUnitTestSuite `xml:"testsuite"`
}
// JUnitTestSuite is a single JUnit test suite which may contain many
// testcases.
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
TestCases []JUnitTestCase `xml:"testcase"`
}
// JUnitTestCase is a single test case with its result.
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
Message string `xml:"message,attr"`
Time string `xml:"time,attr"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
// JUnitFailure contains data related to a failed test.
type JUnitFailure struct {
XMLName xml.Name `xml:"failure"`
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Contents string `xml:",chardata"`
}
// WriteResults will output the results in the standard output as well as concatenate them in an XML JUnit report
func (t *Tester) WriteResults(result Test, output string) bool {
fmt.Printf("Displaying results...\n")
t.writeConsoleReport(result)
file, err := os.OpenFile(output, os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("Error opening XML: %s\n", err)
return false
}
defer file.Close()
err = t.writeJUnitReportXML(result, file, output)
if err != nil {
fmt.Printf("Error writing XML: %s\n", err)
return false
}
fmt.Printf("-> JUnit XML report written: %s\n", output)
return true
}
// Write tests results under JUnit format on w
func (t *Tester) writeJUnitReportXML(result Test, rw io.ReadWriter, output string) error {
suites := JUnitTestSuites{}
buf, err := ioutil.ReadFile(output)
dec := xml.NewDecoder(bytes.NewBufferString(string(buf)))
err = dec.Decode(&suites)
if err != nil {
fmt.Printf("\nUnable to deserialize %s file: %s\n", output, err)
}
ts := JUnitTestSuite{
Tests: len(result.result) + len(result.expected),
Failures: 0,
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
TestCases: []JUnitTestCase{},
}
for _, r := range result.result {
testCase := JUnitTestCase{
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
Failure: nil,
}
testCase.Message = "The stream " + r.Address + " could be accessed and its thumbnail was properly generated"
ts.TestCases = append(ts.TestCases, testCase)
}
for _, e := range result.expected {
testCase := JUnitTestCase{
Time: fmt.Sprintf("%.6f", result.time.Seconds()),
Failure: nil,
}
if e.err != nil {
testCase.Failure = &JUnitFailure{
Message: e.err.Error(),
Type: "",
}
}
}
suites.TestSuites = append(suites.TestSuites, ts)
// Fix indent
bytes, err := xml.MarshalIndent(suites, "", "\t")
if err != nil {
return err
}
// Write in param stream
w, err := os.OpenFile(output, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
writer := io.Writer(w)
writer.Write(bytes)
return nil
}
func (t *Tester) writeConsoleReport(result Test) bool {
successCount := len(result.result)
failureCount := len(result.expected)
fmt.Println("--- Test summary ---")
if successCount > 0 {
fmt.Printf("Results: %d/%d (%d%%)\n", successCount, successCount+failureCount, successCount*100/(successCount+failureCount))
fmt.Printf("Time: %.6fs\n", result.time.Seconds())
} else {
fmt.Printf("No test in success\n")
}
return true
}