├── start.sh ├── log.sh ├── hooks ├── status.sh ├── start.sh ├── stop.sh ├── start_feeder.sh └── start_shodan.sh ├── watch.sh ├── doc ├── scheme.mwb ├── scheme.png ├── architecture.png ├── workers_formula.ods ├── scheme.sql └── cameralist.txt ├── requirements.txt ├── utils ├── start.sh ├── mysql_reset_db.sh ├── log_monitor.sh ├── processmigration.py └── mysql_monitor.sh ├── controller.py ├── Readme.md ├── License └── model.py /start.sh: -------------------------------------------------------------------------------- 1 | utils/start.sh -------------------------------------------------------------------------------- /log.sh: -------------------------------------------------------------------------------- 1 | utils/log_monitor.sh -------------------------------------------------------------------------------- /hooks/status.sh: -------------------------------------------------------------------------------- 1 | curl -X GET 'http://10.0.0.20:3000/status' 2 | -------------------------------------------------------------------------------- /watch.sh: -------------------------------------------------------------------------------- 1 | watch -n 5 -d -t bash utils/mysql_monitor.sh idiots 2 | -------------------------------------------------------------------------------- /doc/scheme.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperBuker/CamHell/HEAD/doc/scheme.mwb -------------------------------------------------------------------------------- /doc/scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperBuker/CamHell/HEAD/doc/scheme.png -------------------------------------------------------------------------------- /doc/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperBuker/CamHell/HEAD/doc/architecture.png -------------------------------------------------------------------------------- /doc/workers_formula.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperBuker/CamHell/HEAD/doc/workers_formula.ods -------------------------------------------------------------------------------- /hooks/start.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -lt 1 ]]; then 2 | echo "Usage : $0 feeder" 3 | exit 4 | fi 5 | 6 | curl -H "Feeder: $1" -X POST 'http://10.0.0.20:3000/start_feeder' -------------------------------------------------------------------------------- /hooks/stop.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -lt 1 ]]; then 2 | echo "Usage : $0 feeder" 3 | exit 4 | fi 5 | 6 | curl -H "Feeder: $1" -X POST 'http://10.0.0.20:3000/stop_feeder' 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/censys/censys-python@2e89c5a #censys==0.1.0 2 | Flask==0.12.2 3 | Flask-Compress==1.4.0 4 | mysqlclient==1.3.12 5 | peewee==2.10.2 6 | requests==2.18.4 7 | shodan==1.7.7 8 | simplejson==3.13.2 9 | zoomeye==1.0.0 10 | -------------------------------------------------------------------------------- /hooks/start_feeder.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -lt 1 ]]; then 2 | echo "Usage : $0 feeder" 3 | exit 4 | fi 5 | 6 | [ `curl -s 'http://10.0.0.20:3000/status' | jq "if .Feeders.$1 == 0 then true else false end"` == true ] && curl -s -H "Feeder: $1" -X POST 'http://10.0.0.20:3000/start_feeder' > /dev/null 7 | -------------------------------------------------------------------------------- /utils/start.sh: -------------------------------------------------------------------------------- 1 | DATE=`date +%y%m%d` 2 | 3 | mkdir -p logs 4 | 5 | i=0 6 | SUF="-$i" 7 | 8 | while [ -e "logs/log_$DATE$SUF.log" ]; do 9 | i=$[$i+1] 10 | SUF="-$i" 11 | done 12 | reset 13 | 14 | echo -e "Writting: logs/log_$DATE$SUF.log\n" 15 | 16 | python controller.py | tee "logs/log_$DATE$SUF.log" 17 | -------------------------------------------------------------------------------- /hooks/start_shodan.sh: -------------------------------------------------------------------------------- 1 | if [[ $# -lt 1 ]]; then 2 | echo "Usage : $0 queue" 3 | exit 4 | fi 5 | 6 | [ `curl -s 'http://10.0.0.20:3000/status' | jq "if .Queues[0] < $1 and (.Feeders.ShodanProc | length == 0) then true else false end"` == true ] && curl -H 'Feeder: ShodanProc' -X POST 'http://10.0.0.20:3000/start_feeder' > /dev/null 7 | -------------------------------------------------------------------------------- /utils/mysql_reset_db.sh: -------------------------------------------------------------------------------- 1 | USER='' 2 | PASSWD='' 3 | HOST='127.0.0.1' 4 | PORT='3306' 5 | mysql -u $USER -p$PASSWD -h $HOST -P $PORT -e "DROP DATABASE $1;" 6 | mysql -u $USER -p$PASSWD -h $HOST -P $PORT -e "CREATE DATABASE $1 DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; GRANT ALL PRIVILEGES ON $1.* TO '$USER'@'$HOST'; FLUSH PRIVILEGES;" 7 | -------------------------------------------------------------------------------- /utils/log_monitor.sh: -------------------------------------------------------------------------------- 1 | LFILE=`ls logs/log_*.log -r | head -1` 2 | LFILE=${LFILE#logs/log_*} 3 | LFILE=${LFILE%*-*} 4 | 5 | i=0 6 | SUF="-$i" 7 | 8 | while [ -e "logs/log_$LFILE$SUF.log" ]; do 9 | i=$[$i+1] 10 | SUF="-$i" 11 | done 12 | SUF="-$[$i-1]" 13 | reset 14 | 15 | echo -e "Reading: logs/log_$LFILE$SUF.log\n" 16 | 17 | tail -f "logs/log_$LFILE$SUF.log" | grep --line-buffered '^\[ProcController\]' 18 | -------------------------------------------------------------------------------- /utils/processmigration.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process 2 | import model 3 | from processmodel import Protocam 4 | import sys 5 | 6 | class DBMig(Process): 7 | threadID = 0 8 | threadName = "DBMig" 9 | feedingQueue = None 10 | 11 | def __init__(self, tID, q): 12 | Process.__init__(self) 13 | self.threadID = tID 14 | self.feedingQueue = q 15 | 16 | def run(self): 17 | self.myprint('Thread starting') 18 | myaddr = model.Address.select() 19 | print('Results: found %s cameras in DB' % (len(myaddr))) 20 | 21 | if len(myaddr) != 0: 22 | for addr in myaddr: 23 | protocam = Protocam.fromAddr(addr) 24 | if protocam is not None: 25 | self.feedingQueue.put(protocam) 26 | self.myprint('Process terminated') 27 | 28 | def myprint(self, string): 29 | print('[%s-%s] %s' % (self.threadName, self.threadID, string)) 30 | sys.stdout.flush() 31 | -------------------------------------------------------------------------------- /utils/mysql_monitor.sh: -------------------------------------------------------------------------------- 1 | USER='' 2 | PASSWD='' 3 | HOST='127.0.0.1' 4 | PORT='3306' 5 | 6 | T6=$(date -d '6 hour ago' "+%Y-%m-%d %H:%M:%S") 7 | T12=$(date -d '12 hour ago' "+%Y-%m-%d %H:%M:%S") 8 | T24=$(date -d '24 hour ago' "+%Y-%m-%d %H:%M:%S") 9 | T48=$(date -d '48 hour ago' "+%Y-%m-%d %H:%M:%S") 10 | 11 | RESULT=($(mysql --raw --batch -s -u $USER -p$PASSWD -h $HOST -P $PORT -D $1 < '$T6'; 16 | SELECT Count(*) FROM timestamp WHERE timestamp.date > '$T12'; 17 | SELECT Count(*) FROM timestamp WHERE timestamp.date > '$T24'; 18 | SELECT Count(*) FROM timestamp WHERE timestamp.date > '$T48'; 19 | SELECT Count(*) FROM address; 20 | SELECT Count(*) FROM credentials; 21 | SELECT Count(*) FROM ddns; 22 | SELECT Count(*) FROM ftp; 23 | SELECT Count(*) FROM mail; 24 | SELECT Count(*) FROM status; 25 | SELECT Count(*) FROM smarteye; 26 | SELECT Count(*) FROM wifi_scan; 27 | SELECT Count(*) FROM wifi_ap; 28 | SELECT Count(*) FROM wifi; 29 | SELECT Count(*) FROM location; 30 | SELECT Count(*) FROM location WHERE detail = 0; 31 | SELECT Count(*) FROM location WHERE detail < 2; 32 | SELECT Count(DISTINCT addr_country) FROM location; 33 | EOF 34 | )) 35 | 36 | echo -e "Cameras: ${RESULT[0]}\n\t-Active: ${RESULT[1]}\nTimestamp: ${RESULT[2]}\n\t-Last 6 hours: ${RESULT[3]}\n\t-Last 12 hours: ${RESULT[4]}\n\t-Last 24 hours: ${RESULT[5]}\n\t-Last 48 hours: ${RESULT[6]}\nAddress: ${RESULT[7]}\nCredentials: ${RESULT[8]}\nDDNS: ${RESULT[9]}\nFTP: ${RESULT[10]}\nMail: ${RESULT[11]}\nStatus: ${RESULT[12]}\nSmartEye: ${RESULT[13]}\nWifi Scan: ${RESULT[14]}\n\t-APs: ${RESULT[15]}\n\t-Wifis: ${RESULT[16]}\nLocation: ${RESULT[17]}\n\t-Precise: ${RESULT[18]}\n\t-LatLng: ${RESULT[19]}\n\t-Countries: ${RESULT[20]}" 37 | -------------------------------------------------------------------------------- /doc/scheme.sql: -------------------------------------------------------------------------------- 1 | -- MySQL Script generated by MySQL Workbench 2 | -- Sat Feb 10 15:35:14 2018 3 | -- Model: New Model Version: 1.0 4 | -- MySQL Workbench Forward Engineering 5 | 6 | SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; 7 | SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; 8 | SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; 9 | 10 | -- ----------------------------------------------------- 11 | -- Schema idiots 12 | -- ----------------------------------------------------- 13 | 14 | -- ----------------------------------------------------- 15 | -- Schema idiots 16 | -- ----------------------------------------------------- 17 | CREATE SCHEMA IF NOT EXISTS `camhell` DEFAULT CHARACTER SET utf8mb4 ; 18 | USE `camhell` ; 19 | 20 | -- ----------------------------------------------------- 21 | -- Table `camhell`.`camera` 22 | -- ----------------------------------------------------- 23 | CREATE TABLE IF NOT EXISTS `camhell`.`camera` ( 24 | `id` VARCHAR(255) NOT NULL, 25 | `mac` VARCHAR(255) NOT NULL, 26 | `wifimac` VARCHAR(255) NOT NULL, 27 | `deviceid` VARCHAR(255) NOT NULL, 28 | `retry` INT(11) NOT NULL, 29 | `creation_date` DATETIME NOT NULL, 30 | PRIMARY KEY (`id`)) 31 | ENGINE = InnoDB 32 | DEFAULT CHARACTER SET = utf8mb4 33 | ROW_FORMAT = DYNAMIC; 34 | 35 | 36 | -- ----------------------------------------------------- 37 | -- Table `camhell`.`timestamp` 38 | -- ----------------------------------------------------- 39 | CREATE TABLE IF NOT EXISTS `camhell`.`timestamp` ( 40 | `id` INT(11) NOT NULL AUTO_INCREMENT, 41 | `date` DATETIME NOT NULL, 42 | PRIMARY KEY (`id`)) 43 | ENGINE = InnoDB 44 | AUTO_INCREMENT = 1450516 45 | DEFAULT CHARACTER SET = utf8mb4 46 | ROW_FORMAT = DYNAMIC; 47 | 48 | 49 | -- ----------------------------------------------------- 50 | -- Table `camhell`.`address` 51 | -- ----------------------------------------------------- 52 | CREATE TABLE IF NOT EXISTS `camhell`.`address` ( 53 | `id` INT(11) NOT NULL AUTO_INCREMENT, 54 | `camera_id` VARCHAR(255) NOT NULL, 55 | `date_id` INT(11) NOT NULL, 56 | `ip` VARCHAR(255) NOT NULL, 57 | `port` INT(11) NOT NULL, 58 | PRIMARY KEY (`id`), 59 | INDEX `address_camera_id` (`camera_id` ASC), 60 | INDEX `address_date_id` (`date_id` ASC), 61 | CONSTRAINT `address_ibfk_1` 62 | FOREIGN KEY (`camera_id`) 63 | REFERENCES `camhell`.`camera` (`id`), 64 | CONSTRAINT `address_ibfk_2` 65 | FOREIGN KEY (`date_id`) 66 | REFERENCES `camhell`.`timestamp` (`id`)) 67 | ENGINE = InnoDB 68 | AUTO_INCREMENT = 136574 69 | DEFAULT CHARACTER SET = utf8mb4 70 | ROW_FORMAT = DYNAMIC; 71 | 72 | 73 | -- ----------------------------------------------------- 74 | -- Table `camhell`.`credentials` 75 | -- ----------------------------------------------------- 76 | CREATE TABLE IF NOT EXISTS `camhell`.`credentials` ( 77 | `id` INT(11) NOT NULL AUTO_INCREMENT, 78 | `camera_id` VARCHAR(255) NOT NULL, 79 | `date_id` INT(11) NOT NULL, 80 | `user_admin` VARCHAR(255) NOT NULL, 81 | `passwd_admin` VARCHAR(255) NOT NULL, 82 | `user_mod` VARCHAR(255) NOT NULL, 83 | `passwd_mod` VARCHAR(255) NOT NULL, 84 | `user_guest` VARCHAR(255) NOT NULL, 85 | `passwd_guest` VARCHAR(255) NOT NULL, 86 | PRIMARY KEY (`id`), 87 | INDEX `credentials_camera_id` (`camera_id` ASC), 88 | INDEX `credentials_date_id` (`date_id` ASC), 89 | CONSTRAINT `credentials_ibfk_1` 90 | FOREIGN KEY (`camera_id`) 91 | REFERENCES `camhell`.`camera` (`id`), 92 | CONSTRAINT `credentials_ibfk_2` 93 | FOREIGN KEY (`date_id`) 94 | REFERENCES `camhell`.`timestamp` (`id`)) 95 | ENGINE = InnoDB 96 | AUTO_INCREMENT = 103479 97 | DEFAULT CHARACTER SET = utf8mb4 98 | ROW_FORMAT = DYNAMIC; 99 | 100 | 101 | -- ----------------------------------------------------- 102 | -- Table `camhell`.`ddns` 103 | -- ----------------------------------------------------- 104 | CREATE TABLE IF NOT EXISTS `camhell`.`ddns` ( 105 | `id` INT(11) NOT NULL AUTO_INCREMENT, 106 | `camera_id` VARCHAR(255) NOT NULL, 107 | `date_id` INT(11) NOT NULL, 108 | `service` INT(11) NOT NULL, 109 | `host` VARCHAR(255) NOT NULL, 110 | `user` VARCHAR(255) NOT NULL, 111 | `passwd` VARCHAR(255) NOT NULL, 112 | `proxy_host` VARCHAR(255) NOT NULL, 113 | `proxy_port` INT(11) NOT NULL, 114 | `status` INT(11) NOT NULL, 115 | PRIMARY KEY (`id`), 116 | INDEX `ddns_camera_id` (`camera_id` ASC), 117 | INDEX `ddns_date_id` (`date_id` ASC), 118 | CONSTRAINT `ddns_ibfk_1` 119 | FOREIGN KEY (`camera_id`) 120 | REFERENCES `camhell`.`camera` (`id`), 121 | CONSTRAINT `ddns_ibfk_2` 122 | FOREIGN KEY (`date_id`) 123 | REFERENCES `camhell`.`timestamp` (`id`)) 124 | ENGINE = InnoDB 125 | AUTO_INCREMENT = 155000 126 | DEFAULT CHARACTER SET = utf8mb4 127 | ROW_FORMAT = DYNAMIC; 128 | 129 | 130 | -- ----------------------------------------------------- 131 | -- Table `camhell`.`ftp` 132 | -- ----------------------------------------------------- 133 | CREATE TABLE IF NOT EXISTS `camhell`.`ftp` ( 134 | `id` INT(11) NOT NULL AUTO_INCREMENT, 135 | `camera_id` VARCHAR(255) NOT NULL, 136 | `date_id` INT(11) NOT NULL, 137 | `host` VARCHAR(255) NOT NULL, 138 | `port` INT(11) NOT NULL, 139 | `user` VARCHAR(255) NOT NULL, 140 | `passwd` VARCHAR(255) NOT NULL, 141 | `path` VARCHAR(255) NOT NULL, 142 | `mode` TINYINT(1) NOT NULL, 143 | `upload_interval` INT(11) NOT NULL, 144 | PRIMARY KEY (`id`), 145 | INDEX `ftp_camera_id` (`camera_id` ASC), 146 | INDEX `ftp_date_id` (`date_id` ASC), 147 | CONSTRAINT `ftp_ibfk_1` 148 | FOREIGN KEY (`camera_id`) 149 | REFERENCES `camhell`.`camera` (`id`), 150 | CONSTRAINT `ftp_ibfk_2` 151 | FOREIGN KEY (`date_id`) 152 | REFERENCES `camhell`.`timestamp` (`id`)) 153 | ENGINE = InnoDB 154 | AUTO_INCREMENT = 852795 155 | DEFAULT CHARACTER SET = utf8mb4 156 | ROW_FORMAT = DYNAMIC; 157 | 158 | 159 | -- ----------------------------------------------------- 160 | -- Table `camhell`.`location` 161 | -- ----------------------------------------------------- 162 | CREATE TABLE IF NOT EXISTS `camhell`.`location` ( 163 | `id` INT(11) NOT NULL AUTO_INCREMENT, 164 | `camera_id` VARCHAR(255) NOT NULL, 165 | `date_id` INT(11) NOT NULL, 166 | `lat` DOUBLE NULL DEFAULT NULL, 167 | `lng` DOUBLE NULL DEFAULT NULL, 168 | `accuracy` DOUBLE NULL DEFAULT NULL, 169 | `addr_street_number` LONGTEXT NULL DEFAULT NULL, 170 | `addr_route` LONGTEXT NULL DEFAULT NULL, 171 | `addr_city` LONGTEXT NULL DEFAULT NULL, 172 | `addr_region` LONGTEXT NULL DEFAULT NULL, 173 | `addr_postal_code` LONGTEXT NULL DEFAULT NULL, 174 | `addr_country` LONGTEXT NOT NULL, 175 | `addr_formatted` LONGTEXT NULL DEFAULT NULL, 176 | `detail` INT(11) NOT NULL, 177 | PRIMARY KEY (`id`), 178 | INDEX `location_camera_id` (`camera_id` ASC), 179 | INDEX `location_date_id` (`date_id` ASC), 180 | CONSTRAINT `location_ibfk_1` 181 | FOREIGN KEY (`camera_id`) 182 | REFERENCES `camhell`.`camera` (`id`), 183 | CONSTRAINT `location_ibfk_2` 184 | FOREIGN KEY (`date_id`) 185 | REFERENCES `camhell`.`timestamp` (`id`)) 186 | ENGINE = InnoDB 187 | AUTO_INCREMENT = 115872 188 | DEFAULT CHARACTER SET = utf8mb4 189 | ROW_FORMAT = DYNAMIC; 190 | 191 | 192 | -- ----------------------------------------------------- 193 | -- Table `camhell`.`mail` 194 | -- ----------------------------------------------------- 195 | CREATE TABLE IF NOT EXISTS `camhell`.`mail` ( 196 | `id` INT(11) NOT NULL AUTO_INCREMENT, 197 | `camera_id` VARCHAR(255) NOT NULL, 198 | `date_id` INT(11) NOT NULL, 199 | `email` VARCHAR(255) NOT NULL, 200 | `host` VARCHAR(255) NOT NULL, 201 | `port` INT(11) NOT NULL, 202 | `user` VARCHAR(255) NOT NULL, 203 | `passwd` VARCHAR(255) NOT NULL, 204 | `ssl` TINYINT(1) NOT NULL, 205 | `receiver1` VARCHAR(255) NOT NULL, 206 | `receiver2` VARCHAR(255) NOT NULL, 207 | `receiver3` VARCHAR(255) NOT NULL, 208 | `receiver4` VARCHAR(255) NOT NULL, 209 | `inet_ip` TINYINT(1) NOT NULL, 210 | PRIMARY KEY (`id`), 211 | INDEX `mail_camera_id` (`camera_id` ASC), 212 | INDEX `mail_date_id` (`date_id` ASC), 213 | CONSTRAINT `mail_ibfk_1` 214 | FOREIGN KEY (`camera_id`) 215 | REFERENCES `camhell`.`camera` (`id`), 216 | CONSTRAINT `mail_ibfk_2` 217 | FOREIGN KEY (`date_id`) 218 | REFERENCES `camhell`.`timestamp` (`id`)) 219 | ENGINE = InnoDB 220 | AUTO_INCREMENT = 127148 221 | DEFAULT CHARACTER SET = utf8mb4 222 | ROW_FORMAT = DYNAMIC; 223 | 224 | 225 | -- ----------------------------------------------------- 226 | -- Table `camhell`.`smarteye` 227 | -- ----------------------------------------------------- 228 | CREATE TABLE IF NOT EXISTS `camhell`.`smarteye` ( 229 | `id` INT(11) NOT NULL AUTO_INCREMENT, 230 | `camera_id` VARCHAR(255) NOT NULL, 231 | `date_id` INT(11) NOT NULL, 232 | `enable` TINYINT(1) NOT NULL, 233 | `domain` VARCHAR(255) NOT NULL, 234 | `port` INT(11) NOT NULL, 235 | `user` VARCHAR(255) NOT NULL, 236 | `passwd` VARCHAR(255) NOT NULL, 237 | `service` VARCHAR(255) NOT NULL, 238 | `interval` INT(11) NOT NULL, 239 | `status` INT(11) NOT NULL, 240 | PRIMARY KEY (`id`), 241 | INDEX `smarteye_camera_id` (`camera_id` ASC), 242 | INDEX `smarteye_date_id` (`date_id` ASC), 243 | CONSTRAINT `smarteye_ibfk_1` 244 | FOREIGN KEY (`camera_id`) 245 | REFERENCES `camhell`.`camera` (`id`), 246 | CONSTRAINT `smarteye_ibfk_2` 247 | FOREIGN KEY (`date_id`) 248 | REFERENCES `camhell`.`timestamp` (`id`)) 249 | ENGINE = InnoDB 250 | AUTO_INCREMENT = 11730 251 | DEFAULT CHARACTER SET = utf8mb4 252 | ROW_FORMAT = DYNAMIC; 253 | 254 | 255 | -- ----------------------------------------------------- 256 | -- Table `camhell`.`status` 257 | -- ----------------------------------------------------- 258 | CREATE TABLE IF NOT EXISTS `camhell`.`status` ( 259 | `id` INT(11) NOT NULL AUTO_INCREMENT, 260 | `camera_id` VARCHAR(255) NOT NULL, 261 | `date_id` INT(11) NOT NULL, 262 | `alias` VARCHAR(255) NOT NULL, 263 | `sys_ver` VARCHAR(255) NOT NULL, 264 | `app_ver` VARCHAR(255) NOT NULL, 265 | `oem_id` VARCHAR(255) NOT NULL, 266 | `sd_status` INT(11) NOT NULL, 267 | `syswifi_mode` INT(11) NOT NULL, 268 | PRIMARY KEY (`id`), 269 | INDEX `status_camera_id` (`camera_id` ASC), 270 | INDEX `status_date_id` (`date_id` ASC), 271 | CONSTRAINT `status_ibfk_1` 272 | FOREIGN KEY (`camera_id`) 273 | REFERENCES `camhell`.`camera` (`id`), 274 | CONSTRAINT `status_ibfk_2` 275 | FOREIGN KEY (`date_id`) 276 | REFERENCES `camhell`.`timestamp` (`id`)) 277 | ENGINE = InnoDB 278 | AUTO_INCREMENT = 107994 279 | DEFAULT CHARACTER SET = utf8mb4 280 | ROW_FORMAT = DYNAMIC; 281 | 282 | 283 | -- ----------------------------------------------------- 284 | -- Table `camhell`.`wifi_scan` 285 | -- ----------------------------------------------------- 286 | CREATE TABLE IF NOT EXISTS `camhell`.`wifi_scan` ( 287 | `id` INT(11) NOT NULL AUTO_INCREMENT, 288 | `camera_id` VARCHAR(255) NOT NULL, 289 | `date_id` INT(11) NOT NULL, 290 | `enabled` TINYINT(1) NOT NULL, 291 | PRIMARY KEY (`id`), 292 | INDEX `wifi_scan_camera_id` (`camera_id` ASC), 293 | INDEX `wifi_scan_date_id` (`date_id` ASC), 294 | CONSTRAINT `wifi_scan_ibfk_1` 295 | FOREIGN KEY (`camera_id`) 296 | REFERENCES `camhell`.`camera` (`id`), 297 | CONSTRAINT `wifi_scan_ibfk_2` 298 | FOREIGN KEY (`date_id`) 299 | REFERENCES `camhell`.`timestamp` (`id`)) 300 | ENGINE = InnoDB 301 | AUTO_INCREMENT = 1450493 302 | DEFAULT CHARACTER SET = utf8mb4 303 | ROW_FORMAT = DYNAMIC; 304 | 305 | 306 | -- ----------------------------------------------------- 307 | -- Table `camhell`.`wifi` 308 | -- ----------------------------------------------------- 309 | CREATE TABLE IF NOT EXISTS `camhell`.`wifi` ( 310 | `id` INT(11) NOT NULL AUTO_INCREMENT, 311 | `scan_id` INT(11) NOT NULL, 312 | `ssid` VARCHAR(255) NOT NULL, 313 | `mac` VARCHAR(255) NOT NULL, 314 | `mode` TINYINT(1) NOT NULL, 315 | `security` INT(11) NOT NULL, 316 | `channel` INT(11) NOT NULL, 317 | `power` INT(11) NOT NULL, 318 | `is_ap` TINYINT(1) NOT NULL, 319 | PRIMARY KEY (`id`), 320 | INDEX `wifi_scan_id` (`scan_id` ASC), 321 | CONSTRAINT `wifi_ibfk_1` 322 | FOREIGN KEY (`scan_id`) 323 | REFERENCES `camhell`.`wifi_scan` (`id`)) 324 | ENGINE = InnoDB 325 | AUTO_INCREMENT = 16248362 326 | DEFAULT CHARACTER SET = utf8mb4 327 | ROW_FORMAT = DYNAMIC; 328 | 329 | 330 | -- ----------------------------------------------------- 331 | -- Table `camhell`.`wifi_ap` 332 | -- ----------------------------------------------------- 333 | CREATE TABLE IF NOT EXISTS `camhell`.`wifi_ap` ( 334 | `id` INT(11) NOT NULL AUTO_INCREMENT, 335 | `scan_id` INT(11) NOT NULL, 336 | `ssid` VARCHAR(255) NOT NULL, 337 | `mode` TINYINT(1) NOT NULL, 338 | `security` INT(11) NOT NULL, 339 | `wep_encrypt` TINYINT(1) NOT NULL, 340 | `password` VARCHAR(255) NOT NULL, 341 | `password_bits` TINYINT(1) NOT NULL, 342 | PRIMARY KEY (`id`), 343 | INDEX `wifi_ap_scan_id` (`scan_id` ASC), 344 | CONSTRAINT `wifi_ap_ibfk_1` 345 | FOREIGN KEY (`scan_id`) 346 | REFERENCES `camhell`.`wifi_scan` (`id`)) 347 | ENGINE = InnoDB 348 | AUTO_INCREMENT = 108990 349 | DEFAULT CHARACTER SET = utf8mb4 350 | ROW_FORMAT = DYNAMIC; 351 | 352 | 353 | SET SQL_MODE=@OLD_SQL_MODE; 354 | SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; 355 | SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; 356 | -------------------------------------------------------------------------------- /controller.py: -------------------------------------------------------------------------------- 1 | from model import db, create_tables, get_stats 2 | from processmodel import DBProc, SmartEyeDBProc, ShodanProc, ShodanStreamProc, CensysProc, ZoomEyeProc, SmartEyeProc, PwnProc, GeoProc, PriorityQueue 3 | 4 | from flask import Flask, request, render_template, send_from_directory, send_file, redirect, Response 5 | #from flask.logging import default_handler 6 | from flask_compress import Compress 7 | 8 | import sys 9 | from os import kill, getpid 10 | from signal import signal, SIGALRM, SIGTERM 11 | 12 | from multiprocessing import Event, Queue, Value 13 | from threading import Thread 14 | from math import log 15 | from time import sleep 16 | from json import dumps as json_dumps 17 | from datetime import datetime 18 | 19 | 20 | class Controller: 21 | runFlag = True 22 | myFeeders = {feeder: [] for feeder in ['DBProc', 'SmartEyeDBProc', 'ShodanProc', 23 | 'ShodanStreamProc', 'CensysProc', 'ZoomEyeProc', 'SmartEyeProc']} 24 | myDB = [] 25 | myShodan = [] 26 | q = [PriorityQueue(3), PriorityQueue(1), None] 27 | myProcs = {'class': [PwnProc, GeoProc], 'name': [ 28 | 'PwnProc', 'GeoProc'], 'list': [[], []]} 29 | 30 | # Idealworkers formula 31 | workers_formula = [{'base_log': 100, 'exp': 4.5}, 32 | {'base_log': 10, 'exp': 2.3}] 33 | 34 | #countries = ['ES', 'PT', 'AD', 'FR', 'BE', 'IT', 'CH', 'NL', 'DE','GB', 'IE'] 35 | countries = [None] 36 | 37 | def __init__(self): 38 | pass 39 | 40 | def setup_db(): 41 | print('Setting DB') 42 | sys.stdout.flush() 43 | create_tables() 44 | 45 | def connect_db(): 46 | print('Processing started') 47 | print('Main PID: %s' % (getpid())) 48 | db.connect() 49 | 50 | def start_feeder(self, feeder): 51 | if feeder == 'ShodanProc': 52 | for i in range(len(self.countries)): 53 | s_p = ShodanProc( 54 | len(self.myFeeders[feeder]), self.q[0], self.countries[i]) 55 | self.myFeeders[feeder].append(s_p) 56 | s_p.start() 57 | sleep(2) 58 | return True 59 | elif feeder in ['DBProc', 'SmartEyeDBProc', 'ShodanStreamProc', 'CensysProc', 'ZoomEyeProc', 'SmartEyeProc']: 60 | if feeder == 'DBProc': 61 | f_p = DBProc(len(self.myFeeders[feeder]), self.q[0], 12) 62 | elif feeder == 'SmartEyeDBProc': 63 | f_p = SmartEyeDBProc(len(self.myFeeders[feeder]), self.q[0]) 64 | elif feeder == 'ShodanStreamProc': 65 | f_p = ShodanStreamProc(len(self.myFeeders[feeder]), self.q[0]) 66 | elif feeder == 'CensysProc': 67 | f_p = CensysProc(len(self.myFeeders[feeder]), self.q[0]) 68 | elif feeder == 'ZoomEyeProc': 69 | f_p = ZoomEyeProc(len(self.myFeeders[feeder]), self.q[0]) 70 | elif feeder == 'SmartEyeProc': 71 | f_p = SmartEyeProc(len(self.myFeeders[feeder]), self.q[0]) 72 | self.myFeeders[feeder].append(f_p) 73 | f_p.start() 74 | sleep(1) 75 | return True 76 | else: 77 | return False 78 | 79 | def stop_feeder(self, feeder): 80 | if feeder in self.myFeeders: 81 | feeder_list = [ 82 | feeder for feeder in self.myFeeders[feeder] if feeder.getFlag()] 83 | if len(feeder_list) > 0: 84 | feeder_list[-1].stop() 85 | return True 86 | return False 87 | 88 | def loop(self): 89 | Controller.connect_db() 90 | loop = 0 91 | while(int(self.runFlag) + len(self.myProcs['list'][0]) + len(self.myProcs['list'][1]) > 0): 92 | for feeder in self.myFeeders: 93 | self.myFeeders[feeder] = [ 94 | f_p for f_p in self.myFeeders[feeder] if f_p.getFlag() and f_p.is_alive()] 95 | 96 | for i in range(len(self.myProcs['list'])): 97 | self.myProcs['list'][i] = [ 98 | p_p for p_p in self.myProcs['list'][i] if p_p.is_alive()] 99 | ulock = [[p_p.getID(), kill(p_p.pid, SIGALRM)] 100 | for p_p in self.myProcs['list'][i] if p_p.beat() == 0] 101 | if len(ulock) != 0: # Unlocking hanged processes 102 | print('[ProcController] Unlocking process', [p_p[0] 103 | for p_p in ulock]) 104 | self.myProcs['list'][i] = [ 105 | p_p for p_p in self.myProcs['list'][i] if p_p.is_alive()] 106 | 107 | for i in range(len(self.q) - 1): 108 | print('[ProcController] Status Q%s: %s elements' % 109 | (i + 1, self.q[i].qsize(True))) 110 | sys.stdout.flush() 111 | 112 | for i in range(len(self.myProcs['class'])): 113 | if self.runFlag: 114 | idealWorkers = max(int(round(log(self.q[i].qsize() + 0.509 * self.workers_formula[0]['base_log'], self.workers_formula[0]['base_log'])**self.workers_formula[0]['exp'])), 115 | int(round(log(self.q[i].qsize() + 0.509 * self.workers_formula[1]['base_log'], self.workers_formula[1]['base_log'])**self.workers_formula[1]['exp']))) 116 | if self.q[i].qsize() > 0 and idealWorkers == 0: # Safety measure 117 | idealWorkers = 1 118 | else: 119 | idealWorkers = 0 120 | # Consider this a graceful stop. Not the best because all the process (including the GeoProcs) are stopped. 121 | 122 | futureWorkers = [p_p.getFlag() 123 | for p_p in self.myProcs['list'][i]].count(True) 124 | print('[ProcController] %s: %s of %s' % 125 | (self.myProcs['name'][i], futureWorkers, idealWorkers)) 126 | sys.stdout.flush() 127 | 128 | if idealWorkers < futureWorkers: 129 | diff = futureWorkers - idealWorkers 130 | counter = 0 131 | for p_p in reversed(self.myProcs['list'][i]): 132 | if counter == diff: 133 | break 134 | if p_p.is_alive() and p_p.getFlag(): 135 | p_p.stop() 136 | counter += 1 137 | print('[ProcController] %s: -%s' % 138 | (self.myProcs['name'][i], counter)) 139 | sys.stdout.flush() 140 | 141 | elif idealWorkers > futureWorkers: 142 | diff = idealWorkers - futureWorkers 143 | counter = 0 144 | for p_p in self.myProcs['list'][i]: 145 | if counter == diff: 146 | break 147 | if p_p.is_alive() and not p_p.getFlag(): 148 | p_p.rerun() 149 | counter += 1 150 | while counter < diff: 151 | myThreadIDs = [ 152 | p_p.getID() for p_p in self.myProcs['list'][i] if p_p.is_alive()] 153 | if myThreadIDs: 154 | p_p = self.myProcs['class'][i]( 155 | (set(range(max(myThreadIDs) + 2)) - set(myThreadIDs)).pop(), self.q[i], self.q[i + 1]) 156 | else: 157 | p_p = self.myProcs['class'][i]( 158 | (set(range(2)) - set(myThreadIDs)).pop(), self.q[i], self.q[i + 1]) 159 | counter += 1 160 | self.myProcs['list'][i].append(p_p) 161 | p_p.start() 162 | print('[ProcController] %s: +%s' % 163 | (self.myProcs['name'][i], counter)) 164 | sys.stdout.flush() 165 | 166 | loop += 1 167 | sleep(10) 168 | Controller.disconnect_db() 169 | 170 | def feeders_status(self, feeder): 171 | if feeder == 'ShodanProc': 172 | return {idx: p_p.getWorkers() for idx, p_p in enumerate(self.myFeeders[feeder])} 173 | else: 174 | return len(self.myFeeders[feeder]) 175 | 176 | def status(self): 177 | return {'Queues': [self.q[idx].qsize() for idx in range(len(self.q) - 1)], 178 | 'Workers': {self.myProcs['name'][idx]: [p_p.getFlag() for p_p in self.myProcs['list'][idx]].count(True) for idx in range(len(self.myProcs['class']))}, 179 | 'Feeders': {feeder: self.feeders_status(feeder) for feeder in self.myFeeders} 180 | } 181 | 182 | def disconnect_db(): 183 | if not db.is_closed(): 184 | db.close() 185 | print('Processing finished') 186 | sys.stdout.flush() 187 | 188 | 189 | app = Flask(__name__, static_url_path='') 190 | Compress(app) 191 | '''formatter = RequestFormatter( 192 | '[ProcController REST] %(remote_addr)s - %(asctime)s - %(url)s' 193 | ) 194 | default_handler.setFormatter(formatter)''' 195 | controller = None 196 | log_com = [] 197 | 198 | 199 | @app.route('/') 200 | def main(): 201 | return Response(json_dumps({'status': 'ok'}, sort_keys=True, indent=4), mimetype='application/json') 202 | 203 | 204 | @app.route('/status') 205 | def status(): 206 | global controller 207 | if controller is not None: 208 | return Response(json_dumps(controller.status(), sort_keys=True, indent=4), mimetype='application/json') 209 | else: 210 | return Response(json_dumps({'Queues': [controller.q[0].qsize(), controller.q[1].qsize()], 211 | 'Workers': {'PwnProc': 0, 'GeoProc': 0}, 212 | 'Feeders': {'ShodanProc': {}, 'DBProc': 0}}, sort_keys=True, indent=4), status=500, mimetype='application/json') 213 | 214 | 215 | @app.route('/status_db') 216 | def status_db(): 217 | global controller 218 | return Response(json_dumps(get_stats(), sort_keys=True, indent=4), mimetype='application/json') 219 | 220 | 221 | @app.route('/start_feeder', methods=["POST"]) 222 | def start_feeder(): 223 | global controller 224 | global log_com 225 | feeder = request.headers.get('Feeder') 226 | rst = 'fail' 227 | if controller is not None: 228 | if controller.runFlag: 229 | rst = controller.start_feeder(feeder) 230 | log_com.append('[%s] START %s: %s' % ( 231 | datetime.now().strftime("%Y/%m/%d %H:%M:%S"), feeder, rst)) 232 | log_com = log_com[-10:] 233 | return Response(json_dumps({'result': rst}, sort_keys=True, indent=4), mimetype='application/json') 234 | 235 | 236 | @app.route('/stop_feeder', methods=["POST"]) 237 | def stop_feeder(): 238 | global controller 239 | global log_com 240 | feeder = request.headers.get('Feeder') 241 | rst = 'fail' 242 | if controller is not None: 243 | if controller.runFlag: 244 | rst = controller.stop_feeder(feeder) 245 | log_com.append('[%s] STOP %s: %s' % ( 246 | datetime.now().strftime("%Y/%m/%d %H:%M:%S"), feeder, rst)) 247 | log_com = log_com[-10:] 248 | return Response(json_dumps({'result': rst}, sort_keys=True, indent=4), mimetype='application/json') 249 | 250 | 251 | def shutdown_server(): 252 | func = request.environ.get('werkzeug.server.shutdown') 253 | if func is None: 254 | raise RuntimeError('Not running with the Werkzeug Server') 255 | func() 256 | 257 | 258 | @app.route('/stop', methods=["POST"]) 259 | def stop(): 260 | global controller 261 | if controller is not None: 262 | controller.runFlag = False 263 | try: 264 | shutdown_server() 265 | except RuntimeError: 266 | pass 267 | return Response(json_dumps({'result': 'ok'}, sort_keys=True, indent=4), mimetype='application/json') 268 | 269 | 270 | @app.route('/log') 271 | def log_commands(): 272 | return Response(json_dumps(log_com, sort_keys=True, indent=4), mimetype='application/json') 273 | 274 | 275 | def signal_handler(signum, stack): 276 | if signum == SIGTERM: 277 | global controller 278 | if controller is not None: 279 | controller.runFlag = False 280 | try: 281 | shutdown_server() 282 | except RuntimeError: 283 | pass 284 | 285 | 286 | @app.after_request 287 | def apply_caching(response): 288 | response.headers['X-Frame-Options'] = 'DENY' 289 | response.headers['X-XSS-Protection'] = '1; mode=block' 290 | response.headers['X-Content-Type-Options'] = 'nosniff' 291 | if response.cache_control.max_age == None: 292 | response.cache_control.max_age = 300 293 | return response 294 | 295 | 296 | def flask_thread(): 297 | app.run(host='0.0.0.0', port=3000) 298 | 299 | 300 | if __name__ == '__main__': 301 | signal(SIGTERM, signal_handler) 302 | controller = Controller() 303 | Controller.setup_db() 304 | Thread(target=flask_thread).start() 305 | controller.loop() 306 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Welcome to CamHell! 2 | CamHell is an IP camera crawler capable of hacking, parsing, storing and postprocessing most of the data available in all the products based on the **Ingenic T10** platform and affected by [this](https://pierrekim.github.io/blog/2017-03-08-camera-goahead-0day.html) and [this](https://blogs.securiteam.com/index.php/archives/3043) bugs. Have fun! 3 | 4 | # The origins 5 | 6 | ## Long story short 7 | This software follows the work of Pierre Kim, SSD and Cybereason, who published in March 2017 multiple vulnerabilities hitting the Ingenic T10 based products and all his owners. 8 | Given that the platform was present in a huge number of products (of multiple rebranders), it was very difficult to determine how many and which of them were affected. Pierre Kim published a [list](https://github.com/SuperBuker/CamHell/tree/master/doc/cameralist.txt), maybe too early, maybe not as polished as the rest of his work, but it was a beginning: more than **1000 affected products**. Sadly, after some time, that list disappeared from the original report but still available in some internet caches. 9 | 10 | Two months after the publication of the vulnerabilities these cameras were included in the botnet [ELF_PERSIRAI.A](http://blog.trendmicro.com/trendlabs-security-intelligence/persirai-new-internet-things-iot-botnet-targets-ip-cameras/), and now they're part of the [Satori](https://securityboulevard.com/2018/02/satori-adds-known-exploit-chain-to-enslave-wireless-ip-cameras/) botnet. 11 | 12 | At this moment, the vast majority of the rebranders haven't published any security update, or warned their clients; betraying them. Now, as the original list of products and brands is no longer available, they claim they're not affected. They lie, and this software is the proof. 13 | 14 | ## Motivations 15 | As a cybersecurity enthousiast I wanted to understand the consequences of putting the marketing and financial departments dealing with a huge security issue. Please don't laugh, this kind of investigations shall be run by experts and not by dressed up cockroaches. 16 | Also, as a customer and owner of an affected product, I wanted to know how many devices were vulnerable through internet; how much information was exposed, and in which ways it could be exploited. In my early tests I managed to geolocate my camera using the wifi scanner utility and Google Maps API, but that was just the beginning. 17 | 18 | # Architecture 19 | ![CamHell architecture](https://github.com/SuperBuker/CamHell/raw/master/doc/architecture.png) 20 | CamHell is composed by three stages: Feeders, PwnProc, and GeoProc, connected through multiprocessing pipes and leaded by the ProcController. 21 | 22 | ## Feeders 23 | The first stage is composed by the feeders: Shodan, Censys, ZoomEye, Local Database and SmartEye. 24 | The first three ones are online crawlers. Just pay the subscription, put the right query and you'll get a list of host candidates. The forth one is the local database, with all the previous hacked cameras. And the fifth one is SmartEye, a dynamic DNS provided with the cameras and lacking of authentication procedures. 25 | The online crawlers work as external source of camera candidates. The Local Database work as internal source, reusing the last working address of every camera. And finally, SmartEye provides the address of the cameras connected to that service. 26 | 27 | ### Shodan 28 | Shodan, our european crawler, offers two kind of services: regular queries and real-time stream. Each one has his own feeder. Both services require a valid user API key. 29 | **ShodanProc** asks Shodan services how many host are available in their database, then launches as many **ShodanWorkers** to assign up to 100 pages (each one with 100 host) to each one; finally enters in token-bucket mode, synchronizing the workers queries. It's not allowed to send more than 1 request/s, but multiple workers can be waiting for a reply at the same time. It's quite common that Shodan doesn't reply (and sdk throws a timeout) or replies with 0 hosts. In those cases, the previous request is automatically resent. 30 | **ShodanStreamProc** subscribes to a kind of NMAP data stream, then filters the given results locally. It is possible to launch as ShodanStreamProcs as desired, but currently it's not possible to check duplicated hosts in the processing queues, so be careful. 31 | It's important to notice that both feeders only put in their feeding queue those hosts who are not currently set as active in database. 32 | 33 | ### Censys 34 | Censys, our «gringo» crawler, offers a service similar to Shodan regular queries, but much more limited in performance and daily fresh results. Anyway, it's good enough as secondary feeder, and requires a valid user API key. 35 | Censys only allows access to the last 10k results through his API; to access the rest a special permission is needed. Never tell Censys your true purposes because this application breaks their ToS. 36 | As Censys has a very limited number of queries in their free accounts it's useful to have multiple accounts and API keys. 37 | It's important to notice that this feeder only puts in his feeding queue those hosts who are not currently set as active in database. 38 | 39 | ### ZoomEye 40 | ZoomEye, our «dim sum» crawler, also offers the same kind of service as Censys and Shodan regular queries. This service is suitable as secondary feeder and requires a valid user API key. 41 | As ZoomEye has a very limited number of queries in their free accounts it's useful to have multiple accounts and API keys. 42 | It's important to notice that this feeder only puts in his feeding queue those hosts who are not currently set as active in database. 43 | 44 | ### Local Database 45 | The local database offers fast and reliable access to the last network address recorded for each camera. **DBProc** has multiple filters allowing to select only the active cameras (with retries < 3), and outdated cameras (with last record > 12h), this parameters are customizable. The camera candidate objects put in the feeding queue have the Id of the camera in addition to the network address, this speeds up the recovery of the last valid credentials at the PwnProc stage. 46 | It's important to notice that this feeder works as a garbage collector, forcing current active cameras to be checked at least once a day until they're no longer available. 47 | 48 | ### SmartEye 49 | SmartEye is an insecure DDNS service hardcoded in a considerable number of cameras. 50 | Each camera has a unique identifier for that service and can be accessed through `http://id.domain.com:80`. All the subdomains point to the same IP, and depending on the subdomain requested an Apache server replies with an `HTTP 301 Moved Permanently`, redirecting to the current address of the camera. 51 | As a consequence of the secuencialisation of the subdomain names, it's possible to determine the dimensions of the name space and guess the valid ones. There are around 500k valid subdomains, that can be accessed with **SmartEyeProc** and processed with PwnProc in less than 24 hours. 52 | For daily use, we can ask just for the current address of the valid subdomains, the ones stored in the database and related to a camera. **SmartEyeDBProc** was designed for that purpose. 53 | It's important to notice that both feeders only put in their feeding queue those hosts who are not currently set as active in database. 54 | 55 | ## PwnProc 56 | The second stage is composed by the **PwnProc**. This process is in charge of hacking the cameras, downloading and parsing all the available data, and finally storing it. 57 | 58 | The process complies the following guidelines: 59 | - Ensure the process concurrency and isolate the points of collision 60 | - Allow concurrent access to database 61 | - Store deltas in database, and each wifiscan report 62 | - Recover already hacked credentials, when possible 63 | - Connect through anonymous proxy (TOR like), and be protected against temporary network instabilities 64 | - Support heartbeat and graceful unlock, in case the process hangs in a request 65 | - Support as many camera variants as possible 66 | - Output data to the postprocessing stage 67 | 68 | ## GeoProc 69 | The third and last stage is **GeoProc**, currently our only postprocessor. 70 | This process formats the last wifiscan and sends it to Google Maps API Geolocation service; receiving the location of the camera in the 85% of the cases, and with an accuracy of around 40m. Then ask the Geocoding service for the street address of that location. 71 | The wifiscan is sent to Google only if the camera is new, or the last available record is older than 7 day. Also, the geolocations with a accuracy higher than 150m are discarded. 72 | If the Google geolocation service fails, the process records the country based on the IP address using `http://ip2c.org` service. 73 | 74 | ## Controller 75 | The **ProcController** is the main process of the platform. 76 | Does the initial setup, launches and stops the feeders on demand and provisions PwnProcs and GeoProcs depending on the size of the multiprocessing queues. 77 | It also monitors the heartbeats of the PwnProcs, sending them a SIGALARM if they hang. 78 | 79 | For making easier the tunning of the "desired workers" formula, a LibreOffice Calc [file](https://github.com/SuperBuker/CamHell/tree/master/doc/workers_formula.ods) is available. 80 | 81 | ### API REST 82 | The ProcController API REST is a simple flask server runned by a thread; allowing monitoring and managing the different processes and feeders. All the responses are in JSON format. 83 | 84 | **GET status:** Current active processes 85 | 86 | $ curl -X GET 'http://localhost:3000/status' 87 | 88 | **GET database status:** Database statistics 89 | 90 | $ curl -X GET 'http://localhost:3000/status_db' 91 | 92 | **GET log:** Last 10 feeders launched or stopped 93 | 94 | $ curl -X GET 'http://localhost:3000/status' 95 | 96 | **POST start feeder:** Start a certain feeder 97 | 98 | $ curl -H "Feeder: $FEEDER" -X POST 'http://localhost:3000/start_feeder' 99 | 100 | **POST stop feeder:** Stop a certain feeder 101 | 102 | $ curl -H "Feeder: $FEEDER" -X POST 'http://localhost:3000/stop_feeder' 103 | *$FEEDER* should be one of the following feeders: ['DBProc', 'SmartEyeDBProc', 'ShodanProc', 'ShodanStreamProc', 'CensysProc', 'ZoomEyeProc', 'SmartEyeProc'] 104 | 105 | **POST stop:** Stop the controller and his subprocesses 106 | 107 | $ curl -X POST 'http://localhost:3000/stop' 108 | 109 | ### Automation and Hooks 110 | In order to simplify the user interaction with the controller through API REST multiple bash scripts have been preconfigured and are available in the [hooks](https://github.com/SuperBuker/CamHell/tree/master/hooks) directory. 111 | 112 | - `$ ./hooks/status.sh` ⇒ GET status 113 | - `$ ./hooks/start.sh $1` ⇒ POST start feeder $1 114 | - `$ ./hooks/stop.sh $1` ⇒ POST stop feeder $1. 115 | - `$ ./hooks/start_feeder.sh $1` ⇒ GET status + POST start feeder $1 if 116 | not currently running 117 | - `$ ./hooks/start_shodan.sh` ⇒ GET status + POST start shodan if not running and queue < $1 118 | 119 | # Installation 120 | CamHell requires at least one host with **Python**, running the program, and a MySQL/**MariaDB** server. The SQL server can be hosted in a different computer, and multiple computers can run CamHell in parallel; selecting different feeders for each one and sharing the database. In order to anonymize all the generated traffic, it's quite recommended to setup a **TOR** proxy socks and edit the corresponding config in the `processmodel.py` file. 121 | 122 | ## Python dependencies 123 | CamHell requieres Python 3 and the following packages: 124 | - censys==0.1.0 125 | - Flask==0.12.2 126 | - Flask-Compress==1.4.0 127 | - mysqlclient==1.3.12 128 | - peewee==2.10.2 129 | - requests==2.18.4 130 | - shodan==1.7.7 131 | - simplejson==3.13.2 132 | - zoomeye==1.0.0 133 | 134 | For automatic dependencies instalation execute: 135 | 136 | $ pip install -r requirements.txt 137 | 138 | ## API keys 139 | Google API, Shodan, Censys and ZoomEye requiere a valid user API key. The almost mandatory ones are Google and Shodan, but the final decision relies on the final user. Those API Keys are currently defined as variables at the beginning of the `processmodel.py`. 140 | Also, it's possible (and very easy) to modify the application allowing more than one API per service, in order to multiply your free service quota on those services; but please don't ask for that feature. 141 | 142 | ## Database setup 143 | The database chosen for development has been MariaDB. Due to the use of utf8mb4 and the significant number of parallel connections, the database server config (`/etc/mysql/my.cnf`) requires some tweaks to work properly. 144 | 145 | [client] 146 | default-character-set = utf8mb4 147 | 148 | [mysqld] 149 | collation_server = utf8mb4_unicode_ci 150 | character_set_server = utf8mb4 151 | innodb_file_format = barracuda 152 | innodb_file_per_table = 1 153 | innodb_large_prefix = 1 154 | 155 | max_connections = 500 156 | max_user_connections = 500 157 | 158 | [mysql] 159 | default-character-set = utf8mb4 160 | 161 | Addionally, an SQL scheme can be found in [scheme.sql](https://github.com/SuperBuker/CamHell/tree/master/doc/scheme.sql) 162 | ![SQL scheme](https://github.com/SuperBuker/CamHell/raw/master/doc/scheme.png) 163 | 164 | The chosen server should be defined in the configuration of the database, located just at the beginning of the `model.py`. 165 | Also, the scripts `utils/mysql_{monitor,reset_db}.sh` make easier the management and visualization of the DB; both expect the database name as first argument. 166 | 167 | Finally, even if the datamodel was written in Peewee, in order to allow compatibility with other databases, that feature was broken due to some "custom queries" specific for MariaDB. In theory, PostgreSQL still is a valid alternative, but will require changing the Peewee SQL driver, and modifying the specific MariaDB queries. 168 | 169 | ## Cron setup 170 | The best and simplest way I found to setup a tunable logic in the ProcController was to run some preconfigured scripts periodically, using cron. 171 | 172 | This is my cron config: 173 | 174 | 55 3 * * * bash '/pathtocamhell/hooks/start_feeder.sh' DBProc 175 | 55 6 * * * bash '/pathtocamhell/hooks/start_feeder.sh' CensysProc 176 | 55 8 * * * bash '/pathtocamhell/hooks/start_feeder.sh' ZoomEyeProc 177 | 55 11 * * * bash '/pathtocamhell/hooks/start_feeder.sh' SmartEyeDBProc 178 | */15 * * * * bash '/pathtocamhell/hooks/start_shodan.sh' 30000 179 | 180 | This setup is just an example, it might change if newer or custom feeders are added to the current processing architecture. 181 | 182 | ## Start 183 | For starting the platform just execute: 184 | 185 | $ ./start.sh 186 | This script launches the `controller.py` and records the log in `logs/yymmdd-i.log`. The script has been programmed expecting `/usr/bin/python` to be Python 3. If you're using a Debian distribution or any other distribution having Python 3 as `/usr/bin/python3` please modify the script. 187 | 188 | As the process has a huge verbosity and it's difficult to follow the ProController outputs, a log grep utility has been programmed: 189 | 190 | $ ./log.sh 191 | 192 | ## Fine tuning 193 | The current platform is enough flexible to accept custom inputs. Also, the PwnProc might be completely changed on order to do alternative tasks; for example spreading Mirai or any other botnet. But, if you decide to preserve a big part of this software you might be interested in going deep in the code and tune the global constant variables in order to adapt this solution to your network, infrastructure or even needs. Feel free to suggest any optimization or enhancement. 194 | 195 | ## Coming Soon 196 | - More documentation (`model.py` and `controller.py` aren't documented at this moment) 197 | - `model.py` enhancement, please make any suggestion 198 | - Configuration by JSON file 199 | - Connection to the cameras though cloud (requires privative protocol reversal) 200 | - API token balancer 201 | - FOFA feeder, requires translation 202 | - Support for country selection on CensysProc and ZoomEyeProc or deletion of country support in ShodanProc 203 | - Bug fixes, and more bug fixes ;) 204 | 205 | # Last thoughts 206 | As you can see, CamHell is a powerful tool, that should have never been made; but here it is. 207 | 208 | At this point I would encourage you to meditate about the responsibility that the manufacturers have regarding the black boxes (also called IoTs) they sell; and up to which point that responsibility should be reinforced by law. Also, by our side as electronics consumers, it would be interesting to meditate how much trust and personal information we put in IoTs we don't know how they really work, because they lack of any external certification. 209 | 210 | We can not buy a $15 IP camera and expect the support of a $200 one. In the end cheap becomes expensive, and you're paying with your privacy and personal data. 211 | 212 | Happy hunting! 213 | 214 | # License 215 | 216 | The software is available as open source under the terms of the [GPLv3](https://opensource.org/licenses/GPL-3.0). 217 | -------------------------------------------------------------------------------- /doc/cameralist.txt: -------------------------------------------------------------------------------- 1 | # Vulnerable Camera list published by Pierre Kim at Mar 08 2017 12:54AM 2 | # Author: Pierre Kim (pierre kim sec gmail com) 3 | # Source: https://www.securityfocus.com/archive/1/540234 4 | 5 | 3G+IPCam Other 6 | 3SVISION Other 7 | 3com CASA 8 | 3com Other 9 | 3xLogic Other 10 | 3xLogic Radio 11 | 4UCAM Other 12 | 4XEM Other 13 | 555 Other 14 | 7Links 3677 15 | 7Links 3677-675 16 | 7Links 3720-675 17 | 7Links 3720-919 18 | 7Links IP-Cam-in 19 | 7Links IP-Wi-Fi 20 | 7Links IPC-760HD 21 | 7Links IPC-770HD 22 | 7Links Incam 23 | 7Links Other 24 | 7Links PX-3615-675 25 | 7Links PX-3671-675 26 | 7Links PX-3720-675 27 | 7Links PX3309 28 | 7Links PX3615 29 | 7Links ipc-720 30 | 7Links px-3675 31 | 7Links px-3719-675 32 | 7Links px-3720-675 33 | A4Tech Other 34 | ABS Other 35 | ADT RC8021W 36 | AGUILERA AQUILERA 37 | AJT AJT-019129-BBCEF 38 | ALinking ALC 39 | ALinking Other 40 | ALinking dax 41 | AMC Other 42 | ANRAN ip180 43 | APKLINK Other 44 | AQUILA AV-IPE03 45 | AQUILA AV-IPE04 46 | AVACOM 5060 47 | AVACOM 5980 48 | AVACOM H5060W 49 | AVACOM NEW 50 | AVACOM Other 51 | AVACOM h5060w 52 | AVACOM h5080w 53 | Acromedia IN-010 54 | Acromedia Other 55 | Advance Other 56 | Advanced+home lc-1140 57 | Aeoss J6358 58 | Aetos 400w 59 | Agasio A500W 60 | Agasio A502W 61 | Agasio A512 62 | Agasio A533W 63 | Agasio A602W 64 | Agasio A603W 65 | Agasio Other 66 | AirLink Other 67 | Airmobi HSC321 68 | Airsight Other 69 | Airsight X10 70 | Airsight X34A 71 | Airsight X36A 72 | Airsight XC39A 73 | Airsight XX34A 74 | Airsight XX36A 75 | Airsight XX40A 76 | Airsight XX60A 77 | Airsight x10 78 | Airsight x10Airsight 79 | Airsight xc36a 80 | Airsight xc49a 81 | Airsight xx39A 82 | Airsight xx40a 83 | Airsight xx49a 84 | Airsight xx51A 85 | Airsight xx51a 86 | Airsight xx52a 87 | Airsight xx59a 88 | Airsight xx60a 89 | Akai AK7400 90 | Akai SP-T03WP 91 | Alecto 150 92 | Alecto Atheros 93 | Alecto DVC-125IP 94 | Alecto DVC-150-IP 95 | Alecto DVC-1601 96 | Alecto DVC-215IP 97 | Alecto DVC-255-IP 98 | Alecto dv150 99 | Alecto dvc-150ip 100 | Alfa 0002HD 101 | Alfa Other 102 | Allnet 2213 103 | Allnet ALL2212 104 | Allnet ALL2213 105 | Amovision Other 106 | Android+IP+cam IPwebcam 107 | Anjiel ip-sd-sh13d 108 | Apexis AH9063CW 109 | Apexis APM-H803-WS 110 | Apexis APM-H804-WS 111 | Apexis APM-J011 112 | Apexis APM-J011-Richard 113 | Apexis APM-J011-WS 114 | Apexis APM-J012 115 | Apexis APM-J012-WS 116 | Apexis APM-J0233 117 | Apexis APM-J8015-WS 118 | Apexis GENERIC 119 | Apexis H 120 | Apexis HD 121 | Apexis J 122 | Apexis Other 123 | Apexis PIPCAM8 124 | Apexis Pyle 125 | Apexis XF-IP49 126 | Apexis apexis 127 | Apexis apm- 128 | Apexis dealextreme 129 | Aquila+Vizion Other 130 | Area51 Other 131 | ArmorView Other 132 | Asagio A622W 133 | Asagio Other 134 | Asgari 720U 135 | Asgari Other 136 | Asgari PTG2 137 | Asgari UIR-G2 138 | Atheros ar9285 139 | AvantGarde SUMPPLE 140 | Axis 1054 141 | Axis 241S 142 | B-Qtech Other 143 | B-Series B-1 144 | BRAUN HD-560 145 | BRAUN HD505 146 | Beaulieu Other 147 | Bionics Other 148 | Bionics ROBOCAM 149 | Bionics Robocam 150 | Bionics T6892WP 151 | Bionics t6892wp 152 | Black+Label B2601 153 | Bravolink Other 154 | Breno Other 155 | CDR+king APM-J011-WS 156 | CDR+king Other 157 | CDR+king SEC-015-C 158 | CDR+king SEC-016-NE 159 | CDR+king SEC-028-NE 160 | CDR+king SEC-029-NE 161 | CDR+king SEC-039-NE 162 | CDR+king sec-016-ne 163 | CDXX Other 164 | CDXXcamera Any 165 | CP+PLUS CP-EPK-HC10L1 166 | CPTCAM Other 167 | Camscam JWEV-372869-BCBAB 168 | Casa Other 169 | Cengiz Other 170 | Chinavasion Gunnie 171 | Chinavasion H30 172 | Chinavasion IP611W 173 | Chinavasion Other 174 | Chinavasion ip609aw 175 | Chinavasion ip611w 176 | Cloud MV1 177 | Cloud Other 178 | CnM IP103 179 | CnM Other 180 | CnM sec-ip-cam 181 | Compro NC150/420/500 182 | Comtac CS2 183 | Comtac CS9267 184 | Conceptronic CIPCAM720PTIWL 185 | Conceptronic cipcamptiwl 186 | Cybernova Other 187 | Cybernova WIP604 188 | Cybernova WIP604MW 189 | D-Link DCS-910 190 | D-Link DCS-930L 191 | D-Link L-series 192 | D-Link Other 193 | DB+Power 003arfu 194 | DB+Power DBPOWER 195 | DB+Power ERIK 196 | DB+Power HC-WV06 197 | DB+Power HD011P 198 | DB+Power HD012P 199 | DB+Power HD015P 200 | DB+Power L-615W 201 | DB+Power LA040 202 | DB+Power Other 203 | DB+Power Other2 204 | DB+Power VA-033K 205 | DB+Power VA0038K 206 | DB+Power VA003K+ 207 | DB+Power VA0044_M 208 | DB+Power VA033K 209 | DB+Power VA033K+ 210 | DB+Power VA035K 211 | DB+Power VA036K 212 | DB+Power VA038 213 | DB+Power VA038k 214 | DB+Power VA039K 215 | DB+Power VA039K-Test 216 | DB+Power VA040 217 | DB+Power VA390k 218 | DB+Power b 219 | DB+Power b-series 220 | DB+Power extcams 221 | DB+Power eye 222 | DB+Power kiskFirstCam 223 | DB+Power va033k 224 | DB+Power va039k 225 | DB+Power wifi 226 | DBB IP607W 227 | DEVICECLIENTQ CNB 228 | DKSEG Other 229 | DNT CamDoo 230 | DVR DVR 231 | DVS-IP-CAM Other 232 | DVS-IP-CAM Outdoor/IR 233 | Dagro DAGRO-003368-JLWYX 234 | Dagro Other 235 | Dericam H216W 236 | Dericam H502W 237 | Dericam M01W 238 | Dericam M2/6/8 239 | Dericam M502W 240 | Dericam M601W 241 | Dericam M801W 242 | Dericam Other 243 | Digix Other 244 | Digoo BB-M2 245 | Digoo MM==BB-M2 246 | Digoo bb-m2 247 | Dinon 8673 248 | Dinon 8675 249 | Dinon SEGEV-105 250 | Dinon segev-103 251 | Dome Other 252 | Drilling+machines Other 253 | E-Lock 1000 254 | ENSIDIO IP102W 255 | EOpen Open730 256 | EST ES-IP602IW 257 | EST IP743W 258 | EST Other 259 | EZCam EPK-EP10L1 260 | EZCam EZCam 261 | EZCam Other 262 | EZCam PAN/TILT 263 | EZCam Pan/Tilt 264 | EasyCam EC-101HD 265 | EasyCam EC-101HDSD 266 | EasyCam EC-101SD 267 | EasyCam EC-102 268 | EasyCam Other 269 | EasyN 187 270 | EasyN 1BF 271 | EasyN 720P 272 | EasyN F 273 | EasyN F-136 274 | EasyN F-M136 275 | EasyN F-M166 276 | EasyN F-M181 277 | EasyN F-M1b1 278 | EasyN F-SERIES 279 | EasyN F133 280 | EasyN F2-611B 281 | EasyN F3 282 | EasyN F3-166 283 | EasyN F3-176M 284 | EasyN F3-M166 285 | EasyN F3-SERIES 286 | EasyN F3-Series 287 | EasyN F3-m187 288 | EasyN F3M187 289 | EasyN FS-613A-M136 290 | EasyN FS-613B 291 | EasyN FS-613B-M166 292 | EasyN FS-613B-MJPEG 293 | EasyN FS613 294 | EasyN F_M10R 295 | EasyN H3-V10R 296 | EasyN H6-M137h 297 | EasyN M091 298 | EasyN Other 299 | EasyN est-007660-611b 300 | EasyN est-007660333 301 | EasyN f 302 | EasyN f-Series 303 | EasyN f138 304 | EasyN f_series 305 | EasyN fseries 306 | EasyN kitch 307 | EasyN s 308 | EasySE F/B/N/I 309 | EasySE H3 310 | EasySE H3e 311 | EasySE Other 312 | Ebode IPV38W 313 | Ebode IPV58 314 | Ebode Other 315 | Ego Other 316 | Elro 901 317 | Elro 903 318 | Elro 903IP 319 | Elro C7031P 320 | Elro C703IP2 321 | Elro C704-IP 322 | Elro C704IP 323 | Elro C704IP.2 324 | Elro C704ip 325 | Elro C803IP 326 | Elro C903IP 327 | Elro C903IP.2 328 | Elro C904IP 329 | Elro C904IP.2 330 | Elro IP901 331 | Elro Other 332 | Eminent 6564 333 | Eminent EM6220 334 | Eminent EM6564 335 | Eminent em6220 336 | Esky C5900 337 | Esky L 338 | Esky Live 339 | Esky c5900 340 | Eura-Tech IC-03C3 341 | EyeCam ICAM-608 342 | EyeCam IP65IW 343 | EyeCam Other 344 | EyeCam STORAGEOPTIONS 345 | EyeIPCam IP901W 346 | EyeSight ES-IP607W 347 | EyeSight ES-IP811W 348 | EyeSight ES-IP909IW 349 | EyeSight ES-IP935FW 350 | EyeSight ES-IP935IW 351 | EyeSight IP910IW 352 | EyeSight IP915IW 353 | EyeSight Other 354 | EyeSight ip609IW 355 | EyeSight ip909iw 356 | EyeSight ip915iw 357 | EyeSight mjpeg 358 | EyeSpy247 Other 359 | F-Series FSERIES 360 | F-Series Ip 361 | F-Series Other 362 | F-Series ip 363 | First+Concept Other 364 | Focuscam F19821W 365 | Foscam FI18904w 366 | Foscam FI18905E 367 | Foscam FI18905W 368 | Foscam FI18906w 369 | Foscam FI1890W 370 | Foscam FI18910E 371 | Foscam FI18910W 372 | Foscam FI18910w 373 | Foscam FI18916W 374 | Foscam FI18918W 375 | Foscam FI18919W 376 | Foscam FI19810W 377 | Foscam FI8094W 378 | Foscam FI81904W 379 | Foscam FI8601W 380 | Foscam FI8602W 381 | Foscam FI8606W 382 | Foscam FI8610w 383 | Foscam FI8903W 384 | Foscam FI8903W_Elita 385 | Foscam FI8904 386 | Foscam FI8904W 387 | Foscam FI8905E 388 | Foscam FI8905W 389 | Foscam FI8905w 390 | Foscam FI8906w 391 | Foscam FI8907W 392 | Foscam FI8908W 393 | Foscam FI8909W 394 | Foscam FI890W 395 | Foscam FI8910 396 | Foscam FI8910E 397 | Foscam FI8910W 398 | Foscam FI8910W_DW 399 | Foscam FI8910w 400 | Foscam FI8916W 401 | Foscam FI8918 402 | Foscam FI89180w 403 | Foscam FI8918E 404 | Foscam FI8918W 405 | Foscam FI8918w 406 | Foscam FI8919W 407 | Foscam FI9804W 408 | Foscam FI9805E 409 | Foscam FI9810 410 | Foscam FI9810W 411 | Foscam FI9818 412 | Foscam FI9820w 413 | Foscam FI9821W 414 | Foscam FI9821w 415 | Foscam FL8910 416 | Foscam FS18908W 417 | Foscam FS8910 418 | Foscam Fi8910 419 | Foscam Other 420 | Foscam fI8989w 421 | Foscam fi1890w 422 | Foscam fl8910w 423 | FoxCam PTZ2084-L 424 | GIGA gb 425 | GT+ROAD HS-006344-SPSLM 426 | General Other 427 | Generic All-in-one 428 | Generic Billy 429 | Generic DomeA-Outdoor 430 | Generic IP 431 | Generic Other 432 | Gi-star+srl IP6031W 433 | Gigaeye GB 434 | GoAhead EC-101SD 435 | GoAhead GoAheadWebs 436 | GoAhead IPCAM1 437 | GoAhead IPCAM2 438 | GoAhead Other 439 | GoAhead thedon 440 | GoCam Other 441 | Goclever EYE 442 | Goclever EYE2 443 | Gotake GTK-TH01B 444 | H+264+network+DVR 720p 445 | H+264+network+DVR Other 446 | H.264 Other 447 | H6837WI Other 448 | HD+IPC Other 449 | HD+IPC SV3C 450 | HDIPCAM Other 451 | Heden CAMH04IPWE 452 | Heden CAMHED02IPW 453 | Heden CAMHED04IP 454 | Heden CAMHED04IPWN 455 | Heden CAMHEDIPWP 456 | Heden Other 457 | Heden VisionCam 458 | Heden visionCam 459 | HiSilicon Other 460 | Hikvision DS-2CD2132 461 | Histream RTSP 462 | HooToo F-SERIES 463 | HooToo HOOTOO 464 | HooToo HT-IP006 465 | HooToo HT-IP006N 466 | HooToo HT-IP009HDP 467 | HooToo HT-IP206 468 | HooToo HT-IP207F 469 | HooToo HT-IP210HDP 470 | HooToo HT-IP210P 471 | HooToo HT-IP212 472 | HooToo IP009HDP 473 | HooToo Other 474 | HooToo apm-h803-mpc 475 | Hsmartlink Other 476 | Hungtek WIFI 477 | ICAMView Other 478 | ICam I908W 479 | ICam IP-1 480 | ICam Other 481 | ICam Other2 482 | ICam dome 483 | INISOFT-CAM Stan 484 | INSTAR 4010 485 | INVID Other 486 | IO+Data Other 487 | IP66 Other 488 | IPC IPC02 489 | IPC Other 490 | IPC S5030-TF 491 | IPC S5030-m 492 | IPC SRICAM 493 | IPCC 3XPTZ 494 | IPCC 7210W 495 | IPCC IPCC-7210W 496 | IPCC x01 497 | IPTeles Other 498 | IPUX ip-100 499 | ISIT Other 500 | IZOtech Other 501 | IZTOUCH 0009 502 | IZTOUCH A001 503 | IZTOUCH IZ-009 504 | IZTOUCH LTH-A8645-c15 505 | IZTOUCH Other 506 | IZTOUCH Other1 507 | IZTOUCH ap001 508 | IeGeek Other 509 | IeGeek ukn 510 | Inkovideo V-104 511 | Iprobot3 Other 512 | JRECam JM3866W 513 | JWcam JWEV 514 | JWcam Other 515 | Jaycar 3834 516 | Jaycar 720P 517 | Jaycar Other 518 | Jaycar QC-3831 519 | Jaycar QC-3832 520 | Jaycar QC-3834 521 | Jaycar QC-3836 522 | Jaycar QC-3839 523 | Jaytech IP6021W 524 | JhempCAM Back 525 | JhempCAM Other 526 | KaiKong 1601 527 | KaiKong 1602w 528 | KaiKong Other 529 | KaiKong SIP 530 | KaiKong SIP1602 531 | KaiKong SIP1602W 532 | KaiKong sip 533 | KaiKong sip1602w 534 | Kenton gjc02 535 | Kinson C720PWIP 536 | Klok Other 537 | Knewmart KW01B 538 | Knewmart KW02B 539 | Kogan KAIPC01BLKA 540 | Kogan KAIPCO1BLKA 541 | Kogan Other 542 | Kogan encoder 543 | Kogan kaipc01blkb 544 | Kompernass IUK 545 | Koolertron Other 546 | Koolertron PnP 547 | Koolertron SP-SHEX21-SL 548 | LC+security Other 549 | LW lw-h264tf 550 | LYD H1385H 551 | Lager Other 552 | Leadtek C351 553 | LevelOne 1010/2010 554 | Libor Other 555 | LifeTech MyLifeTech 556 | LifeTech Other 557 | LifeTech dd 558 | Lilly Other 559 | Linq Other 560 | Lloyds 1107 561 | Loftek CXS 562 | Loftek Nexus 563 | Loftek Other 564 | Loftek SPECTOR 565 | Loftek Sendinel 566 | Loftek Sentinel 567 | LogiLink WC0030A 568 | LogiLink wc0044 569 | Logitech C920 570 | MCL 610 571 | MJPEG Other 572 | Maginon 100 573 | Maginon 10AC 574 | Maginon 20C 575 | Maginon IP-20c 576 | Maginon IPC 577 | Maginon IPC-1 578 | Maginon IPC-10 579 | Maginon IPC-100 580 | Maginon IPC-100AC 581 | Maginon IPC-10AC 582 | Maginon IPC-2 583 | Maginon IPC-20 584 | Maginon IPC20C 585 | Maginon IPC_1A 586 | Maginon Other 587 | Maginon SUPRA 588 | Maginon Supra 589 | Maginon ipc 590 | Maginon ipc-1a 591 | Maginon ipc100a 592 | Maginon ipx 593 | Maginon w2 594 | Marmitek GM-8126 595 | Maygion IP 596 | Maygion OTHER2 597 | Maygion Other 598 | Maygion V3 599 | Maygion black 600 | Mediatech mt4050 601 | Medisana SmartBabyMonitor 602 | Merlin IP 603 | Merlin Other 604 | Merlin vstc 605 | Messoa Other 606 | Mingyoushi S6203Y-WR 607 | Momentum 2002 608 | Momentum MO-CAM 609 | NEXCOM S-CAM 610 | NIP NIP-004500-KMTLU 611 | NIP NIP-075007-UPHTF 612 | NIP NIP-11BGPW 613 | NIP NIP-14 614 | NTSE Other 615 | Neewer Other 616 | Neewer V-100 617 | Neo+CoolCam NIP 618 | Neo+CoolCam NIP-02(OAM) 619 | Neo+CoolCam NIP-06 620 | Neo+CoolCam NIP-066777-BWESL 621 | Neo+CoolCam NIP-102428-DFBEF 622 | Neo+CoolCam NIP-H20(OZX) 623 | Neo+CoolCam OBJ-007260-LYLDU 624 | Neo+CoolCam Other 625 | Neo+CoolCam neo 626 | Neo+CoolCam nip-11 627 | Neo+CoolCam nip-20 628 | Ness Other 629 | NetView Other 630 | Netcam Dual-HD 631 | Netcam HSL-232245-CWXES 632 | Netcam OUVIS 633 | Netcam Other 634 | Netware Other 635 | Nexxt+Solution Xpy 636 | Nixzen Other 637 | NorthQ NQ-9006 638 | Office+One CM-I11123BK 639 | Office+One IP-900 640 | Office+One IP-99 641 | Office+One Other 642 | Office+One SC-10IP 643 | Office+One ip-900 644 | Office+One ip900 645 | Opexia OPCS 646 | Optica+Video FI-8903W 647 | Optica+Video FI-8918W 648 | Optica+Video Other 649 | Otto 4eye 650 | Overmax CamSpot 651 | Overmax Camspot 652 | OwlCam CP-6M201W 653 | P2p wificam 654 | PCS Other 655 | Panasonic BL-C131A 656 | PeopleFu IPC-674 657 | PeopleFu IPCAM1 658 | PeopleFu IPCAM2 659 | PeopleFu IPCAM3 660 | PeopleFu IPCAM5 661 | Pixpo 1Z074A2A0301627785 662 | Pixpo PIX006428BFYZY 663 | Pixpo PIX009491MLJYM 664 | Pixpo PIX009495HURFE 665 | Pixpo PIX010584DFACE 666 | Plaisio IP 667 | Planex Other 668 | Planex PLANEX 669 | Polariod P351S 670 | Polaroid IP-100 671 | Polaroid IP-101W 672 | Polaroid IP-200B 673 | Polaroid IP-201B 674 | Polaroid IP-350 675 | Polaroid IP-351S 676 | Polaroid IP-360S 677 | Polaroid IP-810W 678 | Polaroid IP-810WZ 679 | Polaroid Other 680 | Polaroid POLIP101W 681 | Polaroid POLIP201B 682 | Polaroid POLIP201W 683 | Polaroid POLIP351S 684 | Polaroid POLIP35i5 685 | PowerLead Caue 686 | PowerLead PC012 687 | ProveCam IP2521 688 | Provision 717 689 | Provision F-717 690 | Provision F-737 691 | Provision PT-737 692 | Provision WP-711 693 | Provision WP-717P 694 | Pyle HD 695 | Pyle HD22 696 | Pyle HD46 697 | Pyle Mine 698 | Pyle PIPCAM15 699 | Pyle Pipcam12 700 | Pyle cam5 701 | Pyle pipcam25 702 | Pyle pipcam5 703 | Q-nest QN-100S 704 | Q-nest qn-100s 705 | Queback 720p 706 | ROCAM NC-400 707 | ROCAM NC-500 708 | ROCAM NC300 709 | ROCAM NC300-1 710 | ROHS IP 711 | ROHS none 712 | RTX 06R 713 | RTX DVS 714 | RTX IP-06R 715 | RTX IP-26H 716 | RTX Other 717 | Rollei safetycam-10hd 718 | SES Other 719 | SKJM Other 720 | SST SST-CNS-BUI18 721 | SVB+International SIP-018262-RYERR 722 | SafeHome 278042 723 | SafeHome 616-W 724 | SafeHome IP601W-hd 725 | SafeHome Other 726 | SafeHome VGA 727 | SafeHome iprobot 728 | Samsung Other 729 | Santec-Video Other 730 | Sarotech IPCAM-1000 731 | Sarotech ip300 732 | Scricam 004 733 | Scricam 192.168.1.7 734 | Scricam AP-004 735 | Scricam AP-009 736 | Scricam AP0006 737 | Scricam AP006 738 | Secam+CCTV IPCAM 739 | Secam+CCTV Other 740 | Seculink 10709 741 | Seculink Other 742 | Secur+Eye xxc5330 743 | Seisa JK-H616WS 744 | Senao PTZ-01H 745 | Sequrecam Other 746 | Sequrecam PNP-125 747 | Sercomm Other 748 | Shenwhen+Neo+Electronic+Co NC-541 749 | Shenwhen+Neo+Electronic+Co Other 750 | Shenwhen+Neo+Electronic+Co X-5000B 751 | Shenzhen 720P 752 | Shixin+China IP-129HW 753 | Siepem IPC 754 | Siepem S5001Y-BW 755 | Siepem S6203y 756 | Siepem S6211Y-WR 757 | Simi+IP+Camera+Viewer Other 758 | Sineoji Other 759 | Sineoji PT-315V 760 | Sineoji PT-3215P 761 | Sineoji PT-325IP 762 | Sinocam Other 763 | Sky+Genious Genious 764 | Skytronic IP 765 | Skytronic IP99 766 | Skytronic Other 767 | Skytronic WiFi 768 | Skytronic dome 769 | SmartEye Other 770 | SmartWares C723IP 771 | SmartWares c724ip 772 | SmartWares c923ip 773 | SmartWares c924ip 774 | Solwise SEC-1002W-IR 775 | Spy+Cameras WF-100PCX 776 | Spy+Cameras WF-110V 777 | Sricam 0001 778 | Sricam 004 779 | Sricam A0009 780 | Sricam A001 781 | Sricam AP-001 782 | Sricam AP-003 783 | Sricam AP-004 784 | Sricam AP-005 785 | Sricam AP-006 786 | Sricam AP-009 787 | Sricam AP-012 788 | Sricam AP-CAM 789 | Sricam AP0009 790 | Sricam AP002 791 | Sricam AP995 792 | Sricam Cam1 793 | Sricam Front 794 | Sricam Home 795 | Sricam Other 796 | Sricam SP005 797 | Sricam SP012 798 | Sricam SP013 799 | Sricam SP015 800 | Sricam SRICAM 801 | Sricam SRICAM1 802 | Sricam aj-c2wa-c118 803 | Sricam ap 804 | Sricam ap006 805 | Sricam ap1 806 | Sricam h.264 807 | Sricam sp013 808 | Sricctv A-0006 809 | Sricctv A-009 810 | Sricctv AJ-006 811 | Sricctv AP-0001 812 | Sricctv AP-0005 813 | Sricctv AP-0009 814 | Sricctv AP-001 815 | Sricctv AP-002 816 | Sricctv AP-003 817 | Sricctv AP-004 818 | Sricctv AP-004AF 819 | Sricctv AP-005 820 | Sricctv AP-006 821 | Sricctv AP-007 822 | Sricctv AP-008 823 | Sricctv AP-009 824 | Sricctv AP-011 825 | Sricctv AP-014 826 | Sricctv H-264 827 | Sricctv Other 828 | Sricctv P2P-BLACK 829 | Sricctv P2P-Black 830 | Sricctv SP-007 831 | Sricctv SR-001 832 | Sricctv SR-004 833 | Star+Vedia 6836 834 | Star+Vedia 7837-WIP 835 | Star+Vedia C-7835WIP 836 | Star+Vedia Other 837 | Star+Vedia T-6836WTP 838 | Star+Vedia T-7833WIP 839 | Star+Vedia T-7837WIP 840 | Star+Vedia T-7838WIP 841 | StarCam C33-X4 842 | StarCam EY4 843 | StarCam F6836W 844 | StarCam Other 845 | StarCam c7837wip 846 | Stipelectronics Other 847 | Storage+Options HOMEGUARD 848 | Storage+Options Other 849 | Storage+Options SON-IPC1 850 | Sumpple 610 851 | Sumpple 610S 852 | Sumpple 631 853 | Sumpple 960P 854 | Sumpple S601 855 | Sumpple S610 856 | Sumpple S631 857 | Sumpple S651 858 | Sumpple qd300 859 | Sumpple s631 860 | SunVision+US Other 861 | Sunbio Other 862 | Suneyes Other 863 | Suneyes SP-T01EWP 864 | Suneyes SP-T01WP 865 | Suneyes SP-TM01EWP 866 | Suneyes SP-TM01WP 867 | Suneyes SP-tm05wp 868 | Sunluxy H-264 869 | Sunluxy HZCam 870 | Sunluxy Other 871 | Sunluxy PTZ 872 | Sunluxy SL-701 873 | Supra+Space IPC 874 | Supra+Space IPC-1 875 | Supra+Space IPC-100AC 876 | Supra+Space IPC-10AC 877 | Supra+Space Other11 878 | Supra+Space ipc-20c 879 | Sure-Eye Other 880 | Surecom LN-400 881 | Swann 005FTCD 882 | Swann 440 883 | Swann 440-IPC 884 | Swann ADS-440 885 | Swann ADS-440-PTZ 886 | Swann ADS-CAMAX1 887 | Swann Other 888 | Swann SWADS-440-IPC 889 | Swann SWADS-440IPC-AU 890 | Sygonix 43176A 891 | Sygonix 43558A 892 | Szneo CAM0X 893 | Szneo CoolCam 894 | Szneo NIP 895 | Szneo NIP-0 896 | Szneo NIP-02 897 | Szneo NIP-031 898 | Szneo NIP-031H 899 | Szneo NIP-06 900 | Szneo NIP-12 901 | Szneo NIP-2 902 | Szneo NIP-20 903 | Szneo NIP-210485-ABABC 904 | Szneo NIP-26 905 | Szneo NIP-X 906 | Szneo NP-254095 907 | Szneo Other 908 | Szneo TFD 909 | TAS-Tech Other 910 | Technaxx tx-23 911 | Techview GM8126 912 | Techview QC-3638 913 | Techview qc3839 914 | Temvis Other 915 | Tenda C50S 916 | Tenda c30 917 | Tenda c5+ 918 | Tenvis 0012 919 | Tenvis 3815 920 | Tenvis 3815-W 921 | Tenvis 3815W 922 | Tenvis 3815W. 923 | Tenvis 3815W2013 924 | Tenvis IP-319W 925 | Tenvis IP-319w 926 | Tenvis IP-391W 927 | Tenvis IP-391WHD 928 | Tenvis IP-602W 929 | Tenvis IP602W 930 | Tenvis IPROBOT 931 | Tenvis JP-3815W 932 | Tenvis JPT-3814WP2P 933 | Tenvis JPT-3815 934 | Tenvis JPT-3815-P2P 935 | Tenvis JPT-3815W 936 | Tenvis JPT-3815W+ 937 | Tenvis JPT-3815WP2P 938 | Tenvis JPT-3815w 939 | Tenvis JPT-3818 940 | Tenvis MINI-319W 941 | Tenvis Mini-319 942 | Tenvis Other 943 | Tenvis PT-7131W 944 | Tenvis TH-661 945 | Tenvis TR-3818 946 | Tenvis TR-3828 947 | Tenvis TR3815W 948 | Tenvis TZ100 949 | Tenvis TZ100/IPROBOT3 950 | Tenvus JPG3815W 951 | Threeboy IP-660 952 | Topcam SL-30IPC01Z 953 | Topcam SL-720IPC02Z 954 | Topcam SL-910IW30 955 | Topica+CCTV Other 956 | Trivision NC-335PW-HD-10 957 | Trust NW-7500 958 | Turbo+X Endurance 959 | Turbo+X IIPC-20 960 | Uokoo 720P 961 | VCatch Other 962 | VCatch VC-MIC720HK 963 | Valtronics IP 964 | Valtronics Other 965 | Vandesc IP900 966 | Vantech Other 967 | Vantech PTZ 968 | Videosec+Security IPC-103 969 | Videosec+Security IPP-105 970 | Vimicro Other 971 | Vitek+CCTV Other 972 | Vstarcam 7823 973 | Vstarcam C-7824WIP 974 | Vstarcam C-7833WIP-X4 975 | Vstarcam C-7833wip 976 | Vstarcam C-7837WIP 977 | Vstarcam C-7838WIP 978 | Vstarcam C50S 979 | Vstarcam C7816W 980 | Vstarcam C7824WIP 981 | Vstarcam C782WIP 982 | Vstarcam C7842WIP 983 | Vstarcam C93 984 | Vstarcam C=7824WIP 985 | Vstarcam Cam360 986 | Vstarcam F-6836W 987 | Vstarcam H-6837WI 988 | Vstarcam H-6837WIP 989 | Vstarcam H-6850 990 | Vstarcam H-6850WIP 991 | Vstarcam H-6850wip 992 | Vstarcam ICAM-608 993 | Vstarcam Other 994 | Vstarcam T-6835WIP 995 | Vstarcam T-6836WTP 996 | Vstarcam T-6892wp 997 | Vstarcam T-7815WIP 998 | Vstarcam T-7833WIP 999 | Vstarcam T-7833wip 1000 | Vstarcam T-7837WIP 1001 | Vstarcam T-7838WIP 1002 | Vstarcam T-7892WIP 1003 | Vstarcam T6836WTP 1004 | Vstarcam T7837WIP 1005 | Vstarcam c7815wip 1006 | Vstarcam c7833wip 1007 | Vstarcam c7850wip 1008 | Wanscam 00D6FB01980F 1009 | Wanscam 106B 1010 | Wanscam 118 1011 | Wanscam 541-W 1012 | Wanscam 543-W 1013 | Wanscam 790 1014 | Wanscam AJ-C0WA-198 1015 | Wanscam AJ-C0WA-B106 1016 | Wanscam AJ-C0WA-B116 1017 | Wanscam AJ-C0WA-B168 1018 | Wanscam AJ-C0WA-B1D8 1019 | Wanscam AJ-C0WA-C0D8 1020 | Wanscam AJ-C0WA-C116 1021 | Wanscam AJ-C0WA-C126 1022 | Wanscam AJ-C2WA-B118 1023 | Wanscam AJ-C2WA-C116 1024 | Wanscam AJ-C2WA-C118 1025 | Wanscam AJ-C2WA-C198 1026 | Wanscam AJ-COWA-B1D8 1027 | Wanscam AJ-COWA-C116 1028 | Wanscam AJ-COWA-C126 1029 | Wanscam AJ-COWA-C128 1030 | Wanscam AW00004J 1031 | Wanscam B1D8-1 1032 | Wanscam C-118 1033 | Wanscam C-126 1034 | Wanscam Colour 1035 | Wanscam FI-18904w 1036 | Wanscam FR-4020A2 1037 | Wanscam FR4020A2 1038 | Wanscam HD-100W 1039 | Wanscam HW-0021 1040 | Wanscam HW-0022 1041 | Wanscam HW-0022HD 1042 | Wanscam HW-0023 1043 | Wanscam HW-0024 1044 | Wanscam HW-0025 1045 | Wanscam HW-0026 1046 | Wanscam HW-0028 1047 | Wanscam HW-0033 1048 | Wanscam HW-0036 1049 | Wanscam HW-0038 1050 | Wanscam HW-0039 1051 | Wanscam HW-22 1052 | Wanscam HW0030 1053 | Wanscam IP 1054 | Wanscam JW-0001 1055 | Wanscam JW-0003 1056 | Wanscam JW-0004 1057 | Wanscam JW-0004m 1058 | Wanscam JW-0005 1059 | Wanscam JW-0006 1060 | Wanscam JW-0008 1061 | Wanscam JW-0009 1062 | Wanscam JW-0010 1063 | Wanscam JW-0011 1064 | Wanscam JW-0011l 1065 | Wanscam JW-0012 1066 | Wanscam JW-0018 1067 | Wanscam JW-004 1068 | Wanscam JW-009 1069 | Wanscam JW-CD 1070 | Wanscam JW000008 1071 | Wanscam JW0009 1072 | Wanscam JW001 1073 | Wanscam JW0012 1074 | Wanscam JW008 1075 | Wanscam JWEV 1076 | Wanscam JWEV-011777-NSRVV 1077 | Wanscam JWEV-011921-RXSXT 1078 | Wanscam JWEV-360171-BBEAC 1079 | Wanscam JWEV-380096-CECDB 1080 | Wanscam JWEV-PEPLOW 1081 | Wanscam NBC-543W 1082 | Wanscam NC-530 1083 | Wanscam NC-541 1084 | Wanscam NC-541/W 1085 | Wanscam NC-541W 1086 | Wanscam NC-541w 1087 | Wanscam NC-543W 1088 | Wanscam NCB-534W 1089 | Wanscam NCB-540W 1090 | Wanscam NCB-541W 1091 | Wanscam NCB-541WB 1092 | Wanscam NCB-543W 1093 | Wanscam NCBL-618W 1094 | Wanscam NCH-532MW 1095 | Wanscam NCL-610W 1096 | Wanscam NCL-612W 1097 | Wanscam NCL-616W 1098 | Wanscam NCL-S616W 1099 | Wanscam Other 1100 | Wanscam TG-002 1101 | Wanscam WJ-0004 1102 | Wanscam WX-617 1103 | Wanscam Works 1104 | Wanscam XHA-120903181 1105 | Wanscam XHA-4020a2 1106 | Wanscam __PTZ 1107 | Wanscam chiOthernese 1108 | Wanscam ip 1109 | Wanscam jw0005 1110 | Wanscam jw0010 1111 | Wansview 541 1112 | Wansview 625W 1113 | Wansview MCM-627 1114 | Wansview N540w 1115 | Wansview NCB-534W 1116 | Wansview NCB-541W 1117 | Wansview NCB-541w 1118 | Wansview NCB-543W 1119 | Wansview NCB541W 1120 | Wansview NCB545W 1121 | Wansview NCL-610W 1122 | Wansview NCL610D04 1123 | Wansview NCL614W 1124 | Wansview Other 1125 | Wansview dcs543w 1126 | Wansview nc543w 1127 | Wardmay+CCTV WDM-6702AL 1128 | Watch+bot+Camera resup 1129 | WebcamXP Other 1130 | WinBook Other 1131 | WinBook T-6835 1132 | WinBook T-6835WIP 1133 | WinBook T-7838 1134 | Winic NVT-530004 1135 | Wise+Group Other 1136 | X-Price Other 1137 | X10 39A 1138 | X10 AIRSIGHT 1139 | X10 AirSight 1140 | X10 Airsight 1141 | X10 Jake 1142 | X10 Other 1143 | X10 XC-38A 1144 | X10 XX-36A 1145 | X10 XX-39A 1146 | X10 XX-56A 1147 | X10 XX-59A 1148 | X10 XX-60 1149 | X10 XX-69A 1150 | X10 XX41Ahome 1151 | XVision Other 1152 | XXCamera 53100 1153 | XXCamera 5330-E 1154 | XXCamera Other 1155 | XXCamera XXC-000723-NJFJD 1156 | XXCamera XXC-092411-DCAFC 1157 | XXCamera XXC-50100-H 1158 | XXCamera XXC-50100-T 1159 | XXCamera XXC-5030-E 1160 | XXCamera XXC-53100-T 1161 | XXCamera XXC52130 1162 | Xin+Ling Other 1163 | Yawcam Other 1164 | Zilink Other 1165 | Zmodo CMI-11123BK 1166 | Zmodo IP-900 1167 | Zmodo Other 1168 | Zodiac+Security 909 1169 | Zodiac+Security Other 1170 | Zoneway NC638MW-P 1171 | ZyXEL Other 1172 | alexim Other 1173 | alexim cam22822 1174 | alias Other 1175 | all+in+one+ Other 1176 | all+in+one+ b1 1177 | all-in-one Other 1178 | allecto DVC-150IP 1179 | apc Other 1180 | asw-006 Other 1181 | boh l 1182 | bravo Other 1183 | bush+plus BU-300WF 1184 | ccam p2p 1185 | china 8904W 1186 | china HDIPCAM 1187 | china IPCAM 1188 | china Other 1189 | china PTZCAM 1190 | china np-02 1191 | ciana+exports antani 1192 | cina Other 1193 | coolead L 1194 | coolead L610WS 1195 | dax Other 1196 | denver IPC-320 1197 | denver IPO-320 1198 | e-landing 720p 1199 | eScam QF100 1200 | ebw Other 1201 | epexis PIPCAMHD82 1202 | epexis pipcam5 1203 | esecure nvp 1204 | geeya C602 1205 | geeya P2P 1206 | geeya c801 1207 | hdcam Other 1208 | homeguard 720P 1209 | homeguard Other 1210 | homeguard Wireless 1211 | homeguard wifi 1212 | iView ID002A 1213 | iView Other 1214 | insteon 75790 1215 | insteon 75790wh 1216 | insteon High 1217 | insteon Other 1218 | insteon Wireless 1219 | iuk 5A1 1220 | ivision hdwificam 1221 | iwitness bullet 1222 | jwt Other 1223 | jyacam JYA8010 1224 | kadymay KDM-6800 1225 | kadymay KDM6702 1226 | kadymay KMD-6800 1227 | kadymay Other 1228 | kang+xun xxc5030-t 1229 | kines Other 1230 | kiocong 1601 1231 | kiocong 1602 1232 | kiocong 1609 1233 | kiocong Other 1234 | kodak 201pl 1235 | koicong 1601 1236 | l+series CAM0758 1237 | l+series CAM0760 1238 | l+series Other 1239 | l+series V100 1240 | logan n8504hh 1241 | meyetech 095475-caeca 1242 | meyetech 188091-EFBAE 1243 | meyetech Other 1244 | meyetech WirelessCam 1245 | micasaverde VistaCamSD 1246 | pipcam HD17 1247 | pni 941w 1248 | pni IP451W 1249 | pni IP541W 1250 | pni IP941W 1251 | pni IP951W 1252 | pni Other 1253 | pnp IP 1254 | pnp Other 1255 | semac Other 1256 | skylink WC-300PS 1257 | storex D-10H 1258 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | from playhouse.pool import PooledMySQLDatabase 3 | from playhouse.shortcuts import RetryOperationalError 4 | import datetime 5 | from math import radians, cos, sin, asin, sqrt 6 | 7 | 8 | class MyRetryDatabase(RetryOperationalError, MySQLDatabase): 9 | commit_select = False 10 | 11 | 12 | # MySQL Database cfg 13 | db = MyRetryDatabase( 14 | '', # Database 15 | host='', 16 | port=3306, 17 | user='', 18 | passwd='', 19 | charset='utf8mb4', 20 | threadlocals=True) 21 | 22 | 23 | class BaseModel(Model): 24 | class Meta: 25 | database = db 26 | 27 | 28 | class Camera(BaseModel): 29 | id = CharField(primary_key=True) 30 | mac = CharField() 31 | wifimac = CharField() 32 | deviceid = CharField() 33 | retry = IntegerField(default=0) 34 | creation_date = DateTimeField(default=datetime.datetime.now) 35 | 36 | class Meta: 37 | order_by = ('id',) 38 | 39 | def _create(mac, wifimac, deviceid): 40 | cam_id = Camera.calc_id(mac, wifimac, deviceid) 41 | cam = Camera._get(cam_id) 42 | if cam: 43 | return cam.set_online() 44 | 45 | camera = None 46 | try: 47 | with db.execution_context() as ctx: 48 | # Attempt to create the camera. If the id is taken, due to the 49 | # unique constraint, the database will raise an IntegrityError. 50 | camera = Camera.create( 51 | id=cam_id, 52 | # replace('-', ':') was not supposed to be necessary, but... 53 | mac=mac.replace('-', ':').upper(), 54 | # replace('-', ':') was not supposed to be necessary, but... 55 | wifimac=wifimac.replace('-', ':').upper(), 56 | deviceid=deviceid 57 | ) 58 | except IntegrityError: 59 | pass 60 | return camera 61 | 62 | def _get(id): 63 | camera = None 64 | try: 65 | with db.execution_context() as ctx: 66 | camera = Camera.get(Camera.id == id) 67 | except Camera.DoesNotExist: 68 | pass 69 | return camera 70 | 71 | def _get_by_ip(ip): 72 | camera = None 73 | try: 74 | with db.execution_context() as ctx: 75 | subquery = Address.select(Address.camera).where( 76 | Address.ip == ip).order_by(Address.date_id.desc()).limit(1) 77 | camera = Camera.get(Camera.id == subquery) 78 | except Camera.DoesNotExist: 79 | pass 80 | return camera 81 | 82 | def _get_by_addr(ip, port): 83 | camera = None 84 | try: 85 | with db.execution_context() as ctx: 86 | subquery = Address.select(Address.camera).where((Address.ip == ip) & ( 87 | Address.port == port)).order_by(Address.date_id.desc()).limit(1) 88 | camera = Camera.get(Camera.id == subquery) 89 | except Camera.DoesNotExist: 90 | pass 91 | return camera 92 | 93 | def set_online(self): 94 | try: 95 | with db.execution_context() as ctx: 96 | # Attempt to set a camera online. If the camera doesn't exist 97 | # the database will raise an IntegrityError. 98 | self.retry = 0 99 | self.save() 100 | except IntegrityError: 101 | pass 102 | return self 103 | 104 | def set_offline(self): 105 | try: 106 | with db.execution_context() as ctx: 107 | # Attempt to set a camera offline. If the camera doesn't exist 108 | # the database will raise an IntegrityError. 109 | self.retry += 1 110 | self.save() 111 | except IntegrityError: 112 | pass 113 | return self 114 | 115 | def get_creds(self): 116 | creds = None 117 | try: 118 | with db.execution_context() as ctx: 119 | creds = Credentials.select().where(Credentials.camera == self).order_by( 120 | Credentials.date_id.desc()).get() 121 | except Credentials.DoesNotExist: 122 | pass 123 | return creds 124 | 125 | def get_creds_hist(self): 126 | creds = None 127 | try: 128 | with db.execution_context() as ctx: 129 | creds = list(Credentials.select().distinct().where( 130 | Credentials.camera == self)) 131 | except Credentials.DoesNotExist: 132 | pass 133 | return creds 134 | 135 | def get_addr(self): 136 | address = None 137 | try: 138 | with db.execution_context() as ctx: 139 | address = Address.select().where( 140 | Address.camera == self).order_by(Address.date_id.desc()).get() 141 | except Address.DoesNotExist: 142 | pass 143 | return address 144 | 145 | def get_addr_hist(self): 146 | address = None 147 | try: 148 | with db.execution_context() as ctx: 149 | address = list(Address.select().distinct().where( 150 | Address.camera == self)) 151 | except Address.DoesNotExist: 152 | pass 153 | return address 154 | 155 | def get_ddns(self): 156 | ddns = None 157 | try: 158 | with db.execution_context() as ctx: 159 | ddns = DDNS.select().where(DDNS.camera == self).order_by(DDNS.date_id.desc()).get() 160 | except DDNS.DoesNotExist: 161 | pass 162 | return ddns 163 | 164 | def get_ddns_hist(self): 165 | ddns = None 166 | try: 167 | with db.execution_context() as ctx: 168 | ddns = list(DDNS.select().distinct().where( 169 | DDNS.camera == self)) 170 | except DDNS.DoesNotExist: 171 | pass 172 | return ddns 173 | 174 | def get_ftp(self): 175 | ftp = None 176 | try: 177 | with db.execution_context() as ctx: 178 | ftp = FTP.select().where(FTP.camera == self).order_by(FTP.date_id.desc()).get() 179 | except FTP.DoesNotExist: 180 | pass 181 | return ftp 182 | 183 | def get_ftp_hist(self): 184 | ftp = None 185 | try: 186 | with db.execution_context() as ctx: 187 | ftp = list(FTP.select().distinct().where(FTP.camera == self)) 188 | except FTP.DoesNotExist: 189 | pass 190 | return ftp 191 | 192 | def get_loc(self, det=3): 193 | loc = None 194 | try: 195 | with db.execution_context() as ctx: 196 | loc = Location.select().where((Location.camera == self) & ( 197 | Location.detail < det)).order_by(Location.date_id.desc()).get() 198 | except Location.DoesNotExist: 199 | pass 200 | return loc 201 | 202 | def get_loc_hist(self): 203 | loc = None 204 | try: 205 | with db.execution_context() as ctx: 206 | loc = list(Location.select().distinct().where( 207 | Location.camera == self)) 208 | except Location.DoesNotExist: 209 | pass 210 | return loc 211 | 212 | def get_mail(self): 213 | mail = None 214 | try: 215 | with db.execution_context() as ctx: 216 | mail = Mail.select().where(Mail.camera == self).order_by(Mail.date_id.desc()).get() 217 | except Mail.DoesNotExist: 218 | pass 219 | return mail 220 | 221 | def get_mail_hist(self): 222 | mail = None 223 | try: 224 | with db.execution_context() as ctx: 225 | mail = list(Mail.select().distinct().where( 226 | Mail.camera == self)) 227 | except Mail.DoesNotExist: 228 | pass 229 | return mail 230 | 231 | def get_status(self): 232 | status = None 233 | try: 234 | with db.execution_context() as ctx: 235 | status = Status.select().where(Status.camera == self).order_by( 236 | Status.date_id.desc()).get() 237 | except Status.DoesNotExist: 238 | pass 239 | return status 240 | 241 | def get_status_hist(self): 242 | status = None 243 | try: 244 | with db.execution_context() as ctx: 245 | status = list(Status.select().distinct().where( 246 | Status.camera == self)) 247 | except Status.DoesNotExist: 248 | pass 249 | return status 250 | 251 | def get_smarteye(self): 252 | smarteye = None 253 | try: 254 | with db.execution_context() as ctx: 255 | smarteye = SmartEye.select().where( 256 | SmartEye.camera == self).order_by(SmartEye.date_id.desc()).get() 257 | except SmartEye.DoesNotExist: 258 | pass 259 | return smarteye 260 | 261 | def get_smarteye_hist(self): 262 | status = None 263 | try: 264 | with db.execution_context() as ctx: 265 | smarteye = list(SmartEye.select().distinct().where( 266 | SmartEye.camera == self)) 267 | except SmartEye.DoesNotExist: 268 | pass 269 | return smarteye 270 | 271 | def get_wifiscan(self): 272 | wifi_scan = None 273 | try: 274 | with db.execution_context() as ctx: 275 | wifi_scan = Wifi_scan.select().where( 276 | Wifi_scan.camera == self).order_by(Wifi_scan.date_id.desc()).get() 277 | except Wifi_scan.DoesNotExist: 278 | pass 279 | return wifi_scan 280 | 281 | def get_wifiscan_hist(self): 282 | wifi_scan = None 283 | try: 284 | with db.execution_context() as ctx: 285 | wifi_scan = list(Wifi_scan.select().distinct().where( 286 | Wifi_scan.camera == self)) 287 | except Wifi_scan.DoesNotExist: 288 | pass 289 | return wifi_scan 290 | 291 | def get_wifiap(self): 292 | wifi_ap = None 293 | try: 294 | wifi_ap = Wifi_AP.select().join(Wifi_scan).where( 295 | Wifi_scan.camera == self).order_by(Wifi_scan.date_id.desc()).get() 296 | except Wifi_AP.DoesNotExist: 297 | pass 298 | return wifi_ap 299 | 300 | def get_wifiap_hist(self): 301 | wifi_ap = None 302 | try: 303 | with db.execution_context() as ctx: 304 | wifi_ap = list(Wifi_AP.select().distinct().join( 305 | Wifi_scan).where(Wifi_scan.camera == self)) 306 | except Wifi_AP.DoesNotExist: 307 | pass 308 | return wifi_ap 309 | 310 | def get_last(self): 311 | ts = None 312 | try: 313 | with db.execution_context() as ctx: 314 | ts = Timestamp.select().join(Wifi_scan).where(Wifi_scan.camera == 315 | self).order_by(Wifi_scan.date_id.desc()).get() 316 | except Timestamp.DoesNotExist: 317 | pass 318 | return ts 319 | 320 | def get_last_hist(self): 321 | ts = None 322 | try: 323 | with db.execution_context() as ctx: 324 | ts = list(Timestamp.select().distinct().join( 325 | Wifi_scan).where(Wifi_scan.camera == self)) 326 | except Timestamp.DoesNotExist: 327 | pass 328 | return ts 329 | 330 | def _get_online(): 331 | cameras = None 332 | try: 333 | with db.execution_context() as ctx: 334 | cameras = list(Camera.select().where(Camera.retry < 3)) 335 | except Camera.DoesNotExist: 336 | pass 337 | return cameras 338 | 339 | def _get_outdated(hh=12): 340 | datetime_span = datetime.datetime.now() - datetime.timedelta(hours=hh) 341 | cameras = None 342 | try: 343 | with db.execution_context() as ctx: 344 | subquery = Camera.select(Camera.id).distinct().join(Wifi_scan).join( 345 | Timestamp).where((Camera.retry < 3) & (Timestamp.date > datetime_span)) 346 | cameras = list(Camera.select().distinct().join(Address).where((Camera.retry < 3) & ~( 347 | Camera.id << subquery)).order_by(fn.Rand())) # .order_by(fn.Random()) for Postgresql and Sqlite 348 | except Camera.DoesNotExist: 349 | pass 350 | return cameras 351 | 352 | def _get_outdated_dict(hh=12): 353 | datetime_span = datetime.datetime.now() - datetime.timedelta(hours=hh) 354 | mydict = None 355 | try: 356 | with db.execution_context() as ctx: 357 | subquery1 = Camera.select(Camera.id).distinct().join(Wifi_scan).join( 358 | Timestamp).where((Camera.retry < 3) & (Timestamp.date > datetime_span)) 359 | subquery2 = Camera.select(Camera.id.alias('camid')).annotate(Address, fn.Max(Address.id).alias( 360 | 'addrid')).where((Camera.retry < 3) & ~(Camera.id << subquery1)).alias('subquery2') 361 | mydict = list(Camera.select(Camera.id, Address.ip, Address.port).join(subquery2, on=(Camera.id == subquery2.c.camid).alias('cam')).join( 362 | Address, on=(Address.id == subquery2.c.addrid).alias('addr')).order_by(fn.Rand()).dicts()) # .order_by(fn.Random()) for Postgresql and Sqlite 363 | except Camera.DoesNotExist: 364 | pass 365 | return mydict 366 | 367 | def _get_located(det=2): 368 | cameras = None 369 | try: 370 | with db.execution_context() as ctx: 371 | cameras = list(Camera.select().distinct().join(Location).where((Camera.retry < 3) & ( 372 | Location.detail < det)).order_by(fn.Rand())) # .order_by(fn.Random()) for Postgresql and Sqlite 373 | except Camera.DoesNotExist: 374 | pass 375 | return cameras 376 | 377 | def _get_located_dict(det=2, **kwargs): 378 | mydict = None 379 | try: 380 | with db.execution_context() as ctx: 381 | camquery = Camera.select(Camera.id.alias( 382 | 'camid')).where(Camera.retry < 3) 383 | subquery = Camera.select(Camera.id.alias('camid')).annotate(Address, fn.Max(Address.id).alias('addrid')).annotate(Credentials, fn.Max( 384 | Credentials.id).alias('credid')).annotate(Location, fn.Max(Location.id).alias('locid')).where(Camera.id << camquery).alias('subquery') 385 | if 'country' in kwargs: 386 | mydict = list(Camera.select(Camera.deviceid, Camera.mac, Camera.wifimac, Camera.retry, Address.ip, Address.port, Credentials.user_admin, Credentials.passwd_admin, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=(Address.id == subquery.c.addrid).alias( 387 | 'addr')).join(Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where((Location.detail < 1) & (Location.addrcountry == kwargs['country'])).order_by(fn.Rand()).dicts()) 388 | # .order_by(fn.Random()) for Postgresql and Sqlite 389 | else: 390 | mydict = list(Camera.select(Camera.deviceid, Camera.mac, Camera.wifimac, Camera.retry, Address.ip, Address.port, Credentials.user_admin, Credentials.passwd_admin, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=( 391 | Address.id == subquery.c.addrid).alias('addr')).join(Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).order_by(fn.Rand()).dicts()) 392 | # .order_by(fn.Random()) for Postgresql and Sqlite 393 | except Camera.DoesNotExist: 394 | pass 395 | return mydict 396 | 397 | def _get_all_located_dict(det=2, **kwargs): 398 | mydict = None 399 | try: 400 | with db.execution_context() as ctx: 401 | subquery = Camera.select(Camera.id.alias('camid')).annotate(Address, fn.Max(Address.id).alias('addrid')).annotate( 402 | Credentials, fn.Max(Credentials.id).alias('credid')).annotate(Location, fn.Max(Location.id).alias('locid')).alias('subquery') 403 | if 'country' in kwargs: 404 | mydict = list(Camera.select(Camera.deviceid, Camera.mac, Camera.wifimac, Camera.retry, Address.ip, Address.port, Credentials.user_admin, Credentials.passwd_admin, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=(Address.id == subquery.c.addrid).alias( 405 | 'addr')).join(Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where((Location.detail < 1) & (Location.addrcountry == kwargs['country'])).order_by(fn.Rand()).dicts()) 406 | # .order_by(fn.Random()) for Postgresql and Sqlite 407 | else: 408 | mydict = list(Camera.select(Camera.deviceid, Camera.mac, Camera.wifimac, Camera.retry, Address.ip, Address.port, Credentials.user_admin, Credentials.passwd_admin, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=( 409 | Address.id == subquery.c.addrid).alias('addr')).join(Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).order_by(fn.Rand()).dicts()) 410 | # .order_by(fn.Random()) for Postgresql and Sqlite 411 | except Camera.DoesNotExist: 412 | pass 413 | return mydict 414 | 415 | ###Statistics### 416 | def _get_distinct_deviceid_stats(retry=None): 417 | cameras = None 418 | try: 419 | with db.execution_context() as ctx: 420 | if retry is None or retry < 0: 421 | cameras = Camera.select( 422 | fn.Count(fn.Distinct(Camera.deviceid))).scalar() 423 | else: 424 | cameras = Camera.select(fn.Count(fn.Distinct(Camera.deviceid))).where( 425 | Camera.retry < retry + 1).scalar() 426 | except Camera.DoesNotExist: 427 | pass 428 | return cameras 429 | 430 | def _get_located_stats_dict(det=2): 431 | mydict = None 432 | try: 433 | with db.execution_context() as ctx: 434 | camquery = Camera.select(Camera.id.alias( 435 | 'camid')).where(Camera.retry < 3) 436 | subquery = Camera.select(Camera.id.alias('camid')).annotate(Address, fn.Max(Address.id).alias('addrid')).annotate(Credentials, fn.Max( 437 | Credentials.id).alias('credid')).annotate(Location, fn.Max(Location.id).alias('locid')).where(Camera.id << camquery).alias('subquery') 438 | mydict = list(Camera.select(Location.addr_country.alias('country'), fn.Count(Camera.id).alias('camera')).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=(Address.id == subquery.c.addrid).alias('addr')).join( 439 | Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).group_by(SQL('country')).order_by(SQL('camera').desc()).dicts()) 440 | mydict = {d['country']: d['camera'] for d in mydict} 441 | except Camera.DoesNotExist: 442 | pass 443 | return mydict 444 | 445 | def _get_all_located_stats_dict(det=2): 446 | mydict = None 447 | try: 448 | with db.execution_context() as ctx: 449 | subquery = Camera.select(Camera.id.alias('camid')).annotate(Address, fn.Max(Address.id).alias('addrid')).annotate( 450 | Credentials, fn.Max(Credentials.id).alias('credid')).annotate(Location, fn.Max(Location.id).alias('locid')).alias('subquery') 451 | mydict = list(Camera.select(Location.addr_country.alias('country'), fn.Count(Camera.id).alias('camera')).join(subquery, on=(Camera.id == subquery.c.camid).alias('cam')).join(Address, on=(Address.id == subquery.c.addrid).alias('addr')).join( 452 | Credentials, on=(Credentials.id == subquery.c.credid).alias('creds')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).group_by(SQL('country')).order_by(SQL('camera').desc()).dicts()) 453 | mydict = {d['country']: d['camera'] for d in mydict} 454 | except Camera.DoesNotExist: 455 | pass 456 | return mydict 457 | 458 | def calc_id(mac, wifimac, deviceid): 459 | macs = [mac, wifimac] 460 | for i in range(len(macs)): 461 | # replace('-', '') was not supposed to be necessary, but... 462 | macs[i] = macs[i].replace(':', '').replace('-', '') 463 | if not macs[i]: 464 | macs[i] = '0' 465 | macs[i] = int(str(macs[i]), 16) 466 | return('%s-%s-%s' % (macs[0], macs[1], deviceid)) 467 | 468 | def cmp(self, camera_candidate): # compare 469 | return self.id == camera_candidate.id 470 | 471 | 472 | class Timestamp(BaseModel): 473 | date = DateTimeField(default=datetime.datetime.now) 474 | 475 | class Meta: 476 | order_by = ('-date',) 477 | 478 | def _create(): 479 | try: 480 | with db.execution_context() as ctx: 481 | # Attempt to create the timestamp. 482 | date = Timestamp.create() 483 | except IntegrityError: 484 | pass 485 | return date 486 | 487 | ###Statistics### 488 | def _get_stats(hh=None): 489 | dates = None 490 | try: 491 | with db.execution_context() as ctx: 492 | if hh is None or hh < 0: 493 | dates = Timestamp.select().count() 494 | else: 495 | datetime_span = datetime.datetime.now() - datetime.timedelta(hours=hh) 496 | dates = Timestamp.select().where(Timestamp.date > datetime_span).count() 497 | except Timestamp.DoesNotExist: 498 | pass 499 | return dates 500 | 501 | 502 | class Credentials(BaseModel): 503 | camera = ForeignKeyField(Camera, related_name='creds_belongs_to') 504 | date = ForeignKeyField(Timestamp, related_name='creds_at') 505 | user_admin = CharField() 506 | passwd_admin = CharField() 507 | user_mod = CharField() 508 | passwd_mod = CharField() 509 | user_guest = CharField() 510 | passwd_guest = CharField() 511 | 512 | class Meta: 513 | order_by = ('camera', '-date') 514 | 515 | def _create(camera, date, user_a, passwd_a, user_m, passwd_m, user_g, passwd_g): 516 | try: 517 | with db.execution_context() as ctx: 518 | # Attempt to create the credentials. 519 | credentials = Credentials.create( 520 | camera=camera, 521 | date=date, 522 | user_admin=user_a, 523 | passwd_admin=passwd_a, 524 | user_mod=user_m, 525 | passwd_mod=passwd_m, 526 | user_guest=user_g, 527 | passwd_guest=passwd_g 528 | ) 529 | except IntegrityError: 530 | pass 531 | return credentials 532 | 533 | def _create_dict(camera, date, cred_dict): 534 | try: 535 | with db.execution_context() as ctx: 536 | # Attempt to create the credentials. 537 | credentials = Credentials.create( 538 | camera=camera, 539 | date=date, 540 | user_admin=cred_dict.get('3_name', ''), 541 | passwd_admin=cred_dict.get('3_pwd', ''), 542 | user_mod=cred_dict.get('2_name', ''), 543 | passwd_mod=cred_dict.get('2_pwd', ''), 544 | user_guest=cred_dict.get('1_name', ''), 545 | passwd_guest=cred_dict.get('1_pwd', '') 546 | ) 547 | except IntegrityError: 548 | pass 549 | return credentials 550 | 551 | ###Statistics### 552 | def _get_stats(limit=50): 553 | credentials = None 554 | try: 555 | with db.execution_context() as ctx: 556 | subquery = Camera.select(Camera.id.alias('camid')).annotate( 557 | Credentials, fn.Max(Credentials.id).alias('credid')).alias('subquery') 558 | user = list(Credentials.select(Credentials.user_admin.alias('user'), fn.Count(Credentials.id).alias('rep')).join(subquery, on=( 559 | Credentials.id == subquery.c.credid).alias('credid')).group_by(SQL('user')).order_by(SQL('rep').desc()).limit(limit).dicts()) 560 | passwd = list(Credentials.select(Credentials.passwd_admin.alias('passwd'), fn.Count(Credentials.id).alias('rep')).join(subquery, on=( 561 | Credentials.id == subquery.c.credid).alias('credid')).group_by(SQL('passwd')).order_by(SQL('rep').desc()).limit(limit).dicts()) 562 | credentials = {'user': {d['user']: d['rep'] for d in user}, 'passwd': { 563 | d['passwd']: d['rep'] for d in passwd}} 564 | except Credentials.DoesNotExist: 565 | pass 566 | return credentials 567 | 568 | def cmp(self, user_a, passwd_a, user_m, passwd_m, user_g, passwd_g): # compare 569 | return self.user_admin == user_a and self.passwd_admin == passwd_a and self.user_mod == user_m and self.passwd_mod == passwd_m and \ 570 | self.user_guest == user_g and self.passwd_guest == passwd_g 571 | 572 | def cmp_dict(self, cred_dict): # compare 573 | return self.user_admin == cred_dict.get('3_name', '') and self.passwd_admin == cred_dict.get('3_pwd', '') and self.user_mod == cred_dict.get('2_name', '') and \ 574 | self.passwd_mod == cred_dict.get('2_pwd', '') and self.user_guest == cred_dict.get( 575 | '1_name', '') and self.passwd_guest == cred_dict.get('1_pwd', '') 576 | 577 | 578 | class Address(BaseModel): 579 | camera = ForeignKeyField(Camera, related_name='addr_belongs_to') 580 | date = ForeignKeyField(Timestamp, related_name='addr_at') 581 | ip = CharField() 582 | port = IntegerField() 583 | 584 | class Meta: 585 | order_by = ('camera', '-date') 586 | 587 | def _create(camera, date, ip, port): 588 | try: 589 | with db.execution_context() as ctx: 590 | # Attempt to create the address. 591 | address = Address.create( 592 | camera=camera, 593 | date=date, 594 | ip=ip, 595 | port=port 596 | ) 597 | except IntegrityError: 598 | pass 599 | return address 600 | 601 | ###Statistics### 602 | def _get_stats(limit=50): 603 | address = None 604 | try: 605 | with db.execution_context() as ctx: 606 | subquery = Camera.select(Camera.id.alias('camid')).annotate( 607 | Address, fn.Max(Address.id).alias('addrid')).alias('subquery') 608 | ip_prefix = list(Address.select(fn.substring_index(Address.ip, '.', 2).alias('ip_prefix'), fn.Count(Address.id).alias('rep')).join( 609 | subquery, on=(Address.id == subquery.c.addrid).alias('addrid')).group_by(SQL('ip_prefix')).order_by(SQL('rep').desc()).limit(limit).dicts()) 610 | port = list(Address.select(Address.port.alias('port'), fn.Count(Address.id).alias('rep')).join(subquery, on=( 611 | Address.id == subquery.c.addrid).alias('addrid')).group_by(SQL('port')).order_by(SQL('rep').desc()).limit(limit).dicts()) 612 | address = {'ip_prefix': {d['ip_prefix']: d['rep'] for d in ip_prefix}, 'port': { 613 | d['port']: d['rep'] for d in port}} 614 | except Address.DoesNotExist: 615 | pass 616 | return address 617 | 618 | def cmp(self, ip, port): # compare 619 | return self.ip == ip and self.port == port 620 | 621 | 622 | class DDNS(BaseModel): 623 | camera = ForeignKeyField(Camera, related_name='ddns_belongs_to') 624 | date = ForeignKeyField(Timestamp, related_name='ddns_at') 625 | service = IntegerField() 626 | host = CharField() 627 | user = CharField() 628 | passwd = CharField() 629 | proxy_host = CharField() 630 | proxy_port = IntegerField() 631 | status = IntegerField() 632 | 633 | def _create(camera, date, service, host, user, passwd, proxy_host, proxy_port, status): 634 | try: 635 | with db.execution_context() as ctx: 636 | # Attempt to create the ddns. 637 | ddns = DDNS.create( 638 | camera=camera, 639 | date=date, 640 | service=int(service), 641 | host=host, 642 | user=user, 643 | passwd=passwd, 644 | proxy_host=proxy_host, 645 | proxy_port=int(proxy_port), 646 | status=int(status) 647 | ) 648 | except IntegrityError: 649 | pass 650 | return ddns 651 | 652 | def _create_dict(camera, date, ddns_dict): 653 | try: 654 | with db.execution_context() as ctx: 655 | # Attempt to create the ddns with dict. 656 | ddns = DDNS.create( 657 | camera=camera, 658 | date=date, 659 | service=int(ddns_dict.get('service', '0')), 660 | host=ddns_dict.get('host', ''), 661 | user=ddns_dict.get('user', ''), 662 | passwd=ddns_dict.get('pwd', ''), 663 | proxy_host=ddns_dict.get('proxy_svr', ''), 664 | proxy_port=int(ddns_dict.get('proxy_port', '0')), 665 | status=int(ddns_dict.get('status', '0')) 666 | ) 667 | except IntegrityError: 668 | pass 669 | return ddns 670 | 671 | def cmp(self, service, host, user, passwd, proxy_host, proxy_port, status): # compare 672 | return self.service == int(service) and self.host == host and self.user == user and self.passwd == passwd and \ 673 | self.proxy_host == proxy_host and self.proxy_port == int( 674 | proxy_port) and self.status == int(status) 675 | 676 | def cmp_dict(self, ddns_dict): # compare 677 | return self.service == int(ddns_dict.get('service', '0')) and self.host == ddns_dict.get('host', '') and self.user == ddns_dict.get('user', '') and \ 678 | self.passwd == ddns_dict.get('pwd', '') and self.proxy_host == ddns_dict.get('proxy_host', '') and \ 679 | self.proxy_port == int(ddns_dict.get('proxy_port', '0')) and self.status == int( 680 | ddns_dict.get('status', '0')) 681 | 682 | 683 | class FTP(BaseModel): 684 | camera = ForeignKeyField(Camera, related_name='ftp_belongs_to') 685 | date = ForeignKeyField(Timestamp, related_name='ftp_at') 686 | host = CharField() 687 | port = IntegerField() 688 | user = CharField() 689 | passwd = CharField() 690 | path = CharField() 691 | mode = BooleanField() 692 | upload_interval = IntegerField() 693 | 694 | def _create(camera, date, host, port, user, passwd, path, mode, upload_interval): 695 | try: 696 | with db.execution_context() as ctx: 697 | # Attempt to create the ftp. 698 | ftp = FTP.create( 699 | camera=camera, 700 | date=date, 701 | host=host, 702 | port=int(port), 703 | user=user, 704 | passwd=passwd, 705 | path=path, 706 | mode=str(mode) == '1', 707 | upload_interval=int(upload_interval) 708 | ) 709 | except IntegrityError: 710 | pass 711 | return ftp 712 | 713 | def _create_dict(camera, date, ftp_dict): 714 | try: 715 | with db.execution_context() as ctx: 716 | # Attempt to create the cftp with a dict. 717 | ftp = FTP.create( 718 | camera=camera, 719 | date=date, 720 | host=ftp_dict.get('svr', ''), 721 | port=int(ftp_dict.get('port', '0')), 722 | user=ftp_dict.get('user', ''), 723 | passwd=ftp_dict.get('pwd', ''), 724 | path=ftp_dict.get('dir', ''), 725 | mode=str(ftp_dict.get('mode', '0')) == '1', 726 | upload_interval=int(ftp_dict.get('upload_interval', '0')) 727 | ) 728 | except IntegrityError: 729 | pass 730 | return ftp 731 | 732 | def cmp(self, host, port, user, passwd, path, mode, upload_interval): # compare 733 | return self.host == host and self.port == int(port) and self.user == user and self.passwd == passwd and self.path == path and \ 734 | self.mode == (str(mode) == '1') and self.upload_interval == int( 735 | upload_interval) 736 | 737 | def cmp_dict(self, ftp_dict): # compare 738 | return self.host == ftp_dict.get('svr', '') and self.port == int(ftp_dict.get('port', '0')) and self.user == ftp_dict.get('user', '') and \ 739 | self.passwd == ftp_dict.get('pwd', '') and self.path == ftp_dict.get('dir', '') and \ 740 | self.mode == (str(ftp_dict.get('mode', '0')) == '1') and self.upload_interval == int( 741 | ftp_dict.get('upload_interval', '0')) 742 | 743 | 744 | class Location(BaseModel): 745 | camera = ForeignKeyField(Camera, related_name='loc_belongs_to') 746 | date = ForeignKeyField(Timestamp, related_name='loc_at') 747 | lat = DoubleField(null=True) 748 | lng = DoubleField(null=True) 749 | accuracy = DoubleField(null=True) 750 | max_accuracy = 150 751 | addr_street_number = TextField(null=True) 752 | addr_route = TextField(null=True) 753 | addr_city = TextField(null=True) 754 | addr_region = TextField(null=True) 755 | addr_postal_code = TextField(null=True) 756 | addr_country = TextField() 757 | addr_formatted = TextField(null=True) 758 | # detail [0: geoloc+geocode, 1:geoloc+country(ip based), 2:country(ip based)] 759 | detail = IntegerField() 760 | 761 | class Meta: 762 | order_by = ('camera', '-date') 763 | 764 | def _create(camera, date, geoloc, addr): 765 | try: 766 | with db.execution_context() as ctx: 767 | # Attempt to create the location with geoloc and a dict. 768 | location = Location.create( 769 | camera=camera, 770 | date=date, 771 | lat=geoloc['lat'], 772 | lng=geoloc['lng'], 773 | accuracy=geoloc['accuracy'], 774 | # street_number, premise, sublocality_level_4 775 | addr_street_number=addr.get('street_number', ''), 776 | # route, sublocality_level_2 777 | addr_route=addr.get('route', ''), 778 | addr_city=addr.get('city', ''), # locality 779 | # administrative_area_level_1 780 | addr_region=addr.get('region', ''), 781 | addr_postal_code=addr.get('postal_code', ''), 782 | addr_country=addr.get('country', ''), 783 | addr_formatted=addr.get('formatted', ''), 784 | detail=0 785 | ) 786 | except IntegrityError: 787 | pass 788 | return location 789 | 790 | def _create_loc(camera, date, geoloc, country): 791 | try: 792 | with db.execution_context() as ctx: 793 | # Attempt to create the address only with geoloc and country (ip based). 794 | location = Location.create( 795 | camera=camera, 796 | date=date, 797 | lat=geoloc['lat'], 798 | lng=geoloc['lng'], 799 | accuracy=geoloc['accuracy'], 800 | addr_country=country, 801 | detail=1 802 | ) 803 | except IntegrityError: 804 | pass 805 | return location 806 | 807 | def _create_country(camera, date, country): 808 | try: 809 | with db.execution_context() as ctx: 810 | # Attempt to create the address only with a country (ip based). 811 | location = Location.create( 812 | camera=camera, 813 | date=date, 814 | addr_country=country, 815 | detail=2 816 | ) 817 | except IntegrityError: 818 | pass 819 | return location 820 | 821 | def distance(self, lat, lng): 822 | # Calculate the great circle distance between two points 823 | # on the earth (specified in decimal degrees) 824 | for i in [self.lat, self.lng]: 825 | if type(i) != int: 826 | return 0 827 | 828 | for i in [lat, lng]: 829 | if type(i) != int: 830 | return self.max_accuracy 831 | 832 | lat1, lng1, lat2, lng2 = map(radians, [self.lat, self.lng, lat, lng]) 833 | 834 | dlat = lat2 - lat1 835 | dlon = lng2 - lng1 836 | 837 | a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 838 | c = 2 * asin(sqrt(a)) 839 | m = 6367000 * c 840 | return m 841 | 842 | ###Statistics### 843 | def _get_stats_dict(det=2): 844 | mydict = None 845 | try: 846 | with db.execution_context() as ctx: 847 | subquery = Camera.select(Camera.id.alias('camid')).annotate( 848 | Location, fn.Max(Location.id).alias('locid')).alias('subquery') 849 | locations_raw = list(Location.select(Location.addr_country.alias('country'), Location.addr_region.alias('region'), fn.Count(SQL('*')).alias('rep')).join( 850 | subquery, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).group_by(SQL('country, region')).order_by(SQL('rep').desc()).dicts()) 851 | mydict = {} 852 | for loc in locations_raw: 853 | loc_country = loc.get('country', '') 854 | if loc_country is None or loc_country == '': 855 | loc_country = 'unkown' 856 | loc_region = loc.get('region', '') 857 | if loc_region is None or loc_region == '': 858 | loc_region = 'unkown' 859 | country = mydict.get( 860 | loc_country, {'total': 0, 'region': {}}) 861 | region = country.get('region') 862 | region.update({loc_region: loc.get('rep', 0)}) 863 | country.update({'total': country.get( 864 | 'total') + loc.get('rep', 0), 'region': region}) 865 | mydict.update({loc_country: country}) 866 | except Location.DoesNotExist: 867 | pass 868 | return mydict 869 | 870 | def cmp(self, lat, lng, accuracy): # Returns false if nos equal or accuracy shitty 871 | if self.accuracy: 872 | # if self.accuracy is not None: 873 | if accuracy < self.max_accuracy: 874 | return self.distance(lat, lng) < min(self.accuracy, accuracy) 875 | else: 876 | return False 877 | else: 878 | return True 879 | 880 | def cmp_country(self, country): 881 | return self.addr_country == country 882 | 883 | def addr_format(self): 884 | if self.detail == 0: 885 | return "%s %s, %s, %s %s, %s" % (self.addr_street_number, self.addr_route, self.addr_city, self.addr_region, self.addr_postal_code, self.addr_country) 886 | else: 887 | return self.addr_country 888 | 889 | 890 | class Mail(BaseModel): 891 | camera = ForeignKeyField(Camera, related_name='mail_belongs_to') 892 | date = ForeignKeyField(Timestamp, related_name='mail_at') 893 | email = CharField() 894 | host = CharField() 895 | port = IntegerField() 896 | user = CharField() 897 | passwd = CharField() 898 | ssl = BooleanField() 899 | receiver1 = CharField() 900 | receiver2 = CharField() 901 | receiver3 = CharField() 902 | receiver4 = CharField() 903 | inet_ip = BooleanField() 904 | 905 | def _create(camera, date, email, host, port, user, passwd, ssl, receiver1, receiver2, receiver3, receiver4, inet_ip): 906 | receivers = sorted([receiver1, receiver2, receiver3, 907 | receiver4], key=lambda x: (x == "", x.lower())) 908 | try: 909 | with db.execution_context() as ctx: 910 | # Attempt to create the mail. 911 | mail = Mail.create( 912 | camera=camera, 913 | date=date, 914 | email=email, 915 | host=host, 916 | port=int(port), 917 | user=user, 918 | passwd=passwd, 919 | ssl=str(ssl) == '1', 920 | receiver1=receivers[0], 921 | receiver2=receivers[1], 922 | receiver3=receivers[2], 923 | receiver4=receivers[3], 924 | inet_ip=str(inet_ip) == '1' 925 | ) 926 | except IntegrityError: 927 | pass 928 | return mail 929 | 930 | def _create_dict(camera, date, mail_dict): 931 | receivers = sorted([mail_dict.get('receiver1', ''), mail_dict.get('receiver2', ''), mail_dict.get( 932 | 'receiver3', ''), mail_dict.get('receiver4', '')], key=lambda x: (x == "", x.lower())) 933 | try: 934 | with db.execution_context() as ctx: 935 | # Attempt to create the credentials. 936 | mail = Mail.create( 937 | camera=camera, 938 | date=date, 939 | email=mail_dict.get('sender', ''), 940 | host=mail_dict.get('svr', ''), 941 | port=int(mail_dict.get('port', '0')), 942 | user=mail_dict.get('user', ''), 943 | passwd=mail_dict.get('pwd', ''), 944 | ssl=str(mail_dict.get('ssl', '0')) == '1', 945 | receiver1=receivers[0], 946 | receiver2=receivers[1], 947 | receiver3=receivers[2], 948 | receiver4=receivers[3], 949 | inet_ip=str(mail_dict.get('inet_ip', '0')) == '1' 950 | ) 951 | except IntegrityError: 952 | pass 953 | return mail 954 | 955 | def cmp(self, email, host, port, user, passwd, ssl, receiver1, receiver2, receiver3, receiver4, inet_ip): # compare 956 | receivers = sorted([receiver1, receiver2, receiver3, 957 | receiver4], key=lambda x: (x == "", x.lower())) 958 | return self.email == email and self.host == host and self.port == int(port) and self.user == user and self.passwd == passwd and self.ssl == (str(ssl) == '1') and \ 959 | self.receiver1 == receivers[0] and self.receiver2 == receivers[1] and self.receiver3 == receivers[ 960 | 2] and self.receiver4 == receivers[3] and self.inet_ip == (str(inet_ip) == '1') 961 | 962 | def cmp_dict(self, mail_dict): # compare 963 | receivers = sorted([mail_dict.get('receiver1', ''), mail_dict.get('receiver2', ''), mail_dict.get( 964 | 'receiver3', ''), mail_dict.get('receiver4', '')], key=lambda x: (x == "", x.lower())) 965 | return self.email == mail_dict.get('sender', '') and self.host == mail_dict.get('svr', '') and self.port == int(mail_dict.get('port', '0')) and \ 966 | self.user == mail_dict.get('user', '') and self.passwd == mail_dict.get('pwd', '') and self.ssl == (str(mail_dict.get('ssl', '0')) == '1') and \ 967 | self.receiver1 == receivers[0] and self.receiver2 == receivers[1] and self.receiver3 == receivers[2] and self.receiver4 == receivers[3] and \ 968 | self.inet_ip == (str(mail_dict.get('inet_ip', '0')) == '1') 969 | 970 | 971 | class Status(BaseModel): 972 | camera = ForeignKeyField(Camera, related_name='stat_belongs_to') 973 | date = ForeignKeyField(Timestamp, related_name='stat_at') 974 | alias = CharField() 975 | sys_ver = CharField() 976 | app_ver = CharField() 977 | oem_id = CharField() 978 | sd_status = IntegerField() 979 | syswifi_mode = IntegerField() 980 | 981 | class Meta: 982 | order_by = ('camera', '-date') 983 | 984 | def _create(camera, date, alias, sys_ver, app_ver, oem_id, sd_status, syswifi_mode): 985 | try: 986 | with db.execution_context() as ctx: 987 | # Attempt to create the status. 988 | status = Status.create( 989 | camera=camera, 990 | date=date, 991 | alias=alias, 992 | sys_ver=sys_ver, 993 | app_ver=app_ver, 994 | oem_id=oem_id, 995 | sd_status=int(sd_status), 996 | syswifi_mode=int(syswifi_mode) 997 | ) 998 | except IntegrityError: 999 | pass 1000 | return status 1001 | 1002 | def cmp(self, alias, sys_ver, app_ver, oem_id, sd_status, syswifi_mode): 1003 | return self.alias == alias and self.sys_ver == sys_ver and self.app_ver == app_ver and self.oem_id == oem_id and self.sd_status == int(sd_status) and self.syswifi_mode == int(syswifi_mode) 1004 | 1005 | 1006 | class SmartEye(BaseModel): 1007 | camera = ForeignKeyField(Camera, related_name='se_belongs_to') 1008 | date = ForeignKeyField(Timestamp, related_name='se_at') 1009 | enable = BooleanField() 1010 | domain = CharField() 1011 | port = IntegerField() 1012 | user = CharField() 1013 | passwd = CharField() 1014 | service = CharField() 1015 | interval = IntegerField() 1016 | status = IntegerField() 1017 | 1018 | class Meta: 1019 | order_by = ('camera', '-date') 1020 | 1021 | def _create(camera, date, enable, name, port, user, pwd, svr, interval, status): 1022 | try: 1023 | with db.execution_context() as ctx: 1024 | # Attempt to create the status. 1025 | smarteye = SmartEye.create( 1026 | camera=camera, 1027 | date=date, 1028 | enable=str(enable) == '1', 1029 | domain=name, 1030 | port=port, 1031 | user=user, 1032 | passwd=pwd, 1033 | service=svr, 1034 | interval=int(interval), 1035 | status=int(status) 1036 | ) 1037 | except IntegrityError: 1038 | pass 1039 | return smarteye 1040 | 1041 | def _create_dict(camera, date, se_dict): 1042 | try: 1043 | with db.execution_context() as ctx: 1044 | # Attempt to create the credentials. 1045 | smarteye = SmartEye.create( 1046 | camera=camera, 1047 | date=date, 1048 | enable=str(se_dict.get('enable', '0')) == '1', 1049 | domain=se_dict.get('name', ''), 1050 | port=int(se_dict.get('port', '0')), 1051 | user=se_dict.get('user', ''), 1052 | passwd=se_dict.get('pwd', ''), 1053 | service=se_dict.get('svr', ''), 1054 | interval=int(se_dict.get('interval', '0')), 1055 | status=int(se_dict.get('status', '0')) 1056 | ) 1057 | except IntegrityError: 1058 | pass 1059 | return smarteye 1060 | 1061 | def _get_distinct_id(provider=None): 1062 | smarteye = None 1063 | try: 1064 | with db.execution_context() as ctx: 1065 | if provider is not None: 1066 | smarteye = list(SmartEye.select(SmartEye.domain, SmartEye.service).distinct( 1067 | ).where((SmartEye.service == provider) and (SmartEye.domain != '')).dicts()) 1068 | else: 1069 | smarteye = list(SmartEye.select(SmartEye.domain, SmartEye.service).distinct( 1070 | ).where((SmartEye.service != '') and (SmartEye.domain != '')).dicts()) 1071 | 1072 | except SmartEye.DoesNotExist: 1073 | pass 1074 | return smarteye 1075 | 1076 | def cmp(self, enable, name, port, user, pwd, svr, interval, status): # compare 1077 | return self.enable == (str(enable) == '1') and self.domain == name and self.port == int(port) and self.user == user and self.passwd == passwd and self.service == svr and \ 1078 | self.interval == int(interval) and self.status == int(status) 1079 | 1080 | def cmp_dict(self, se_dict): # compare 1081 | return self.enable == (str(se_dict.get('enable', '0')) == '1') and self.domain == se_dict.get('name', '') and self.port == int(se_dict.get('port', '0')) and \ 1082 | self.user == se_dict.get('user', '') and self.passwd == se_dict.get('pwd', '') and self.service == se_dict.get('svr', '') and \ 1083 | self.interval == int(se_dict.get('interval', '0')) and self.status == int( 1084 | se_dict.get('status', '0')) 1085 | 1086 | 1087 | class Wifi_scan(BaseModel): 1088 | camera = ForeignKeyField(Camera, related_name='ws_belongs_to') 1089 | date = ForeignKeyField(Timestamp, related_name='ws_at') 1090 | enabled = BooleanField() 1091 | 1092 | class Meta: 1093 | order_by = ('camera', '-date') 1094 | 1095 | def _create(camera, date, enabled): 1096 | try: 1097 | with db.execution_context() as ctx: 1098 | # Attempt to create the wifi_scan. 1099 | wifi_scan = Wifi_scan.create( 1100 | camera=camera, 1101 | date=date, 1102 | enabled=enabled 1103 | ) 1104 | except IntegrityError: 1105 | pass 1106 | return wifi_scan 1107 | 1108 | def get_wifis(self): 1109 | wifis = None 1110 | try: 1111 | with db.execution_context() as ctx: 1112 | wifis = list(Wifi.select().distinct().where(Wifi.scan == self)) 1113 | except Wifi_scan.DoesNotExist: 1114 | pass 1115 | return wifis 1116 | 1117 | 1118 | class Wifi(BaseModel): 1119 | scan = ForeignKeyField(Wifi_scan, related_name='wi_belongs_to') 1120 | ssid = CharField() 1121 | mac = CharField() 1122 | mode = BooleanField() # True infrastructure, False ad-hoc 1123 | security = IntegerField() # 0-7 1124 | channel = IntegerField() # 0-14 1125 | power = IntegerField() # 0-100 1126 | is_ap = BooleanField(default=False) 1127 | security_types = [ 1128 | "None", "WEP", "WPAPSK(TKIP)", "WPAPSK(AES)", "WPA2PSK(AES)", "WPA2PSK(TKIP)", "Not supported"] 1129 | 1130 | class Meta: 1131 | order_by = ('scan',) 1132 | 1133 | def _create(scan, ssid, mac, mode, security, channel, power, is_ap): 1134 | try: 1135 | with db.execution_context() as ctx: 1136 | # Attempt to create the wifi. 1137 | wifi = Wifi.create( 1138 | scan=scan, 1139 | ssid=ssid, 1140 | # replace('-', ':') was not supposed to be necessary, but... 1141 | mac=mac.replace('-', ':').upper(), 1142 | mode=mode, 1143 | security=security, 1144 | channel=channel, 1145 | power=power, 1146 | is_ap=is_ap 1147 | ) 1148 | return wifi 1149 | except IntegrityError: 1150 | pass 1151 | 1152 | def _create_dict(scan, wifi_d, is_ap): 1153 | try: 1154 | with db.execution_context() as ctx: 1155 | # Attempt to create the wifi with a dict. 1156 | wifi = Wifi.create( 1157 | scan=scan, 1158 | ssid=wifi_d.get('ap_ssid', ''), 1159 | # replace('-', ':') was not supposed to be necessary, but... 1160 | mac=wifi_d['ap_mac'].replace('-', ':').upper(), 1161 | mode=str(wifi_d['ap_mode']) == '0', 1162 | security=wifi_d['ap_security'], 1163 | channel=wifi_d['ap_channel'], 1164 | power=wifi_d['ap_dbm0'], 1165 | is_ap=wifi_d['is_ap'] 1166 | ) 1167 | except IntegrityError: 1168 | pass 1169 | return wifi 1170 | 1171 | def _create_dict_bulk(scan, wifis_d): 1172 | wifis = [] 1173 | if len(wifis_d) != 0: 1174 | try: 1175 | with db.execution_context() as ctx: 1176 | # Attempt to create the wifis with a dict, bulk method. 1177 | for wifi_d in wifis_d: 1178 | wifi = Wifi.create( 1179 | scan=scan, 1180 | ssid=wifi_d.get('ap_ssid', ''), 1181 | # replace('-', ':') was not supposed to be necessary, but damn dim sum... 1182 | mac=wifi_d['ap_mac'].replace('-', ':').upper(), 1183 | mode=str(wifi_d['ap_mode']) == '0', 1184 | security=wifi_d['ap_security'], 1185 | channel=wifi_d['ap_channel'], 1186 | power=wifi_d['ap_dbm0'], 1187 | is_ap=wifi_d['is_ap'] 1188 | ) 1189 | wifis.append(wifi) 1190 | except IntegrityError: 1191 | pass 1192 | return wifis 1193 | 1194 | def _get_distinct_mac(): 1195 | wifis = None 1196 | try: 1197 | with db.execution_context() as ctx: 1198 | wifis = list(Wifi.select(Wifi.mac).distinct().dicts()) 1199 | except Wifi.DoesNotExist: 1200 | pass 1201 | return wifis 1202 | 1203 | ###Statistics### 1204 | def _get_stats(): 1205 | wifis = None 1206 | try: 1207 | with db.execution_context() as ctx: 1208 | wifis = Wifi.select(fn.Count(fn.Distinct(Wifi.mac))).scalar() 1209 | except Wifi.DoesNotExist: 1210 | pass 1211 | return wifis 1212 | 1213 | 1214 | class Wifi_AP(BaseModel): 1215 | scan = ForeignKeyField(Wifi_scan, related_name='wa_belongs_to') 1216 | ssid = CharField() 1217 | mode = BooleanField() # True infrastructure, False ad-hoc 1218 | security = IntegerField() # 0-7 1219 | wep_encrypt = BooleanField(default=False) 1220 | password = CharField() 1221 | password_bits = BooleanField(default=False) 1222 | 1223 | class Meta: 1224 | order_by = ('scan',) 1225 | 1226 | def _create(scan, ssid, mode, security, wep_encrypt, password, password_bits): 1227 | try: 1228 | with db.execution_context() as ctx: 1229 | # Attempt to create the wifi_ap. 1230 | wifi = Wifi_AP.create( 1231 | scan=scan, 1232 | ssid=ssid, 1233 | mode=mode, 1234 | security=security, 1235 | wep_encrypt=wep_encrypt, 1236 | password=password, 1237 | password_bits=password_bits 1238 | ) 1239 | except IntegrityError: 1240 | pass 1241 | return wifi 1242 | 1243 | def _create_open(scan, ssid, mode): 1244 | return Wifi_AP._create(scan, ssid, mode, 0, False, '', False) 1245 | 1246 | def _create_wep(scan, ssid, mode, wep_encrypt, password, password_bits): 1247 | return Wifi_AP._create(scan, ssid, mode, 1, wep_encrypt, password, password_bits) 1248 | 1249 | def _create_wpa(scan, ssid, mode, security, password): 1250 | return Wifi_AP._create(scan, ssid, mode, security, False, password, False) 1251 | 1252 | def _get_located_dict(det=2, **kwargs): 1253 | mydict = None 1254 | try: 1255 | with db.execution_context() as ctx: 1256 | wifiquery = Wifi_AP.select(Wifi_AP.scan_id.alias('wifiid')) 1257 | scanquery = Wifi_scan.select(Wifi_scan.id, Wifi_scan.camera_id).where( 1258 | Wifi_scan.id << wifiquery).alias('wscanid') 1259 | subquery = Camera.select(fn.Max(scanquery.c.id).alias('scanid'), fn.Max(Location.id).alias('locid')).join( 1260 | scanquery, on=(Camera.id == scanquery.c.camera_id)).join(Location).group_by(Camera.id).alias('subquery') 1261 | if 'country' in kwargs: 1262 | mydict = list(Wifi_AP.select(Wifi_AP.ssid, Wifi_AP.security, Wifi_AP.password, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=(Wifi_AP.scan_id == subquery.c.scanid).alias( 1263 | 'wifi')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where((Location.detail < 1) & (Location.addrcountry == kwargs['country'])).dicts()) 1264 | else: 1265 | mydict = list(Wifi_AP.select(Wifi_AP.ssid, Wifi_AP.security, Wifi_AP.password, Location.lat, Location.lng, Location.addr_formatted).join(subquery, on=( 1266 | Wifi_AP.scan_id == subquery.c.scanid).alias('wifi')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).dicts()) 1267 | except Wifi_AP.DoesNotExist: 1268 | pass 1269 | return mydict 1270 | 1271 | ###Statistics### 1272 | def _get_stats(limit=50): 1273 | mydict = None 1274 | try: 1275 | with db.execution_context() as ctx: 1276 | wifiquery = Wifi_AP.select(Wifi_AP.scan_id.alias('wifiid')) 1277 | scanquery = Wifi_scan.select(Wifi_scan.id, Wifi_scan.camera_id).where( 1278 | Wifi_scan.id << wifiquery).alias('wscanid') 1279 | subquery = Camera.select(fn.Max(scanquery.c.id).alias('scanid')).join(scanquery, on=( 1280 | Camera.id == scanquery.c.camera_id)).group_by(Camera.id).alias('subquery') 1281 | ssid = list(Wifi_AP.select(Wifi_AP.ssid.alias('ssid'), fn.Count(SQL('*')).alias('rep')).join(subquery, on=( 1282 | Wifi_AP.scan_id == subquery.c.scanid).alias('wifi')).group_by(SQL('ssid')).order_by(SQL('rep').desc()).limit(limit).dicts()) 1283 | passwd = list(Wifi_AP.select(Wifi_AP.password.alias('passwd'), fn.Count(SQL('*')).alias('rep')).join(subquery, on=( 1284 | Wifi_AP.scan_id == subquery.c.scanid).alias('wifi')).group_by(SQL('passwd')).order_by(SQL('rep').desc()).limit(limit).dicts()) 1285 | security = list(Wifi_AP.select(Wifi_AP.security.alias('security'), fn.Count(SQL('*')).alias('rep')).join(subquery, on=( 1286 | Wifi_AP.scan_id == subquery.c.scanid).alias('wifi')).group_by(SQL('security')).order_by(SQL('rep').desc()).limit(limit).dicts()) 1287 | mydict = {'ssid': {d['ssid']: d['rep'] for d in ssid}, 'passwd': {d['passwd']: d['rep'] for d in passwd}, 'security': { 1288 | Wifi.security_types[int(d['security'])]: d['rep'] for d in security}} 1289 | except Wifi_AP.DoesNotExist: 1290 | pass 1291 | return mydict 1292 | 1293 | def _get_located_stats_dict(det=2): 1294 | mydict = None 1295 | try: 1296 | with db.execution_context() as ctx: 1297 | wifiquery = Wifi_AP.select(Wifi_AP.scan_id.alias('wifiid')) 1298 | scanquery = Wifi_scan.select(Wifi_scan.id, Wifi_scan.camera_id).where( 1299 | Wifi_scan.id << wifiquery).alias('wscanid') 1300 | subquery = Camera.select(fn.Max(scanquery.c.id).alias('scanid'), fn.Max(Location.id).alias('locid')).join( 1301 | scanquery, on=(Camera.id == scanquery.c.camera_id)).join(Location).group_by(Camera.id).alias('subquery') 1302 | list(Wifi_AP.select(Location.addr_country.alias('country'), fn.Count(Wifi_AP.id).alias('wifi_ap')).join(subquery, on=(Wifi_AP.scan_id == subquery.c.scanid).alias( 1303 | 'wifi')).join(Location, on=(Location.id == subquery.c.locid).alias('loc')).where(Location.detail < det).group_by(Location.addr_country).dicts()) 1304 | except Wifi_AP.DoesNotExist: 1305 | pass 1306 | return mydict 1307 | 1308 | def cmp(self, ssid, mode, security, wep_encrypt, password, password_bits): 1309 | return self.ssid == ssid and self.mode == mode and self.security == security and self.wep_encrypt == wep_encrypt and self.password == password, self.password_bits == password_bits 1310 | 1311 | 1312 | def get_stats(): 1313 | ###Statistics### 1314 | mytuples = None 1315 | mydict = None 1316 | dt = [datetime.datetime.now() - datetime.timedelta(hours=hh) 1317 | for hh in [6, 12, 24, 48]] 1318 | try: 1319 | with db.execution_context() as ctx: 1320 | mydict = {'Camera': {'Total': Camera.select().count(), 'Active': Camera.select().where(Camera.retry == 0).count()}, 1321 | 'Timestamp': {'Total': Timestamp.select().count(), 'last 6h': Timestamp.select().where(Timestamp.date > dt[0]).count(), 'last 12h': Timestamp.select().where(Timestamp.date > dt[1]).count(), 'last 24h': Timestamp.select().where(Timestamp.date > dt[2]).count(), 'last 48h': Timestamp.select().where(Timestamp.date > dt[3]).count()}, 1322 | 'Address': Address.select().count(), 1323 | 'Credentials': Credentials.select().count(), 1324 | 'DDNS': DDNS.select().count(), 1325 | 'FTP': FTP.select().count(), 1326 | 'Mail': Mail.select().count(), 1327 | 'Status': Status.select().count(), 1328 | 'SmartEye': SmartEye.select().count(), 1329 | 'Wifi Scan': {'Total': Wifi_scan.select().count(), 'APs': Wifi_AP.select().count(), 'Wifis': Wifi.select().count()}, 1330 | 'Location': {'Total': Location.select().count(), 'Precise': Location.select().where(Location.detail < 1).count(), 'LatLng': Location.select().where(Location.detail < 2).count(), 'Countries': Location.select(fn.Count(fn.Distinct(Location.addr_country))).scalar()}} 1331 | except Exception: 1332 | pass 1333 | return mydict 1334 | 1335 | 1336 | def create_tables(): 1337 | db.connect() 1338 | 1339 | db.create_tables([Camera, Timestamp, Credentials, Address, DDNS, FTP, 1340 | Location, Mail, Status, SmartEye, Wifi_scan, Wifi, Wifi_AP], safe=True) 1341 | 1342 | if not db.is_closed(): 1343 | db.close() 1344 | --------------------------------------------------------------------------------