diff --git a/.gitignore b/.gitignore index 5971626..505a73b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,22 @@ pombo-*.conf gnu-linux/*.tar.gz mac/*.tar.gz windows/Output/ +output +/dist +/build +/*.spec htmlcov/ .mypy_cache/ .tox/ +.venv +index.php +pombo.conf +.vscode +auto_py_to_exe*json + +# Pombo Portable (IP file & log file) +pombo +pombo.log # GUI gui/bin/ diff --git a/build.pombo.bat b/build.pombo.bat new file mode 100644 index 0000000..099e7d9 --- /dev/null +++ b/build.pombo.bat @@ -0,0 +1,4 @@ +cxfreeze --script pombo.py --target-dir dist --base-name Win32GUI +copy pombo.conf dist +copy pombo.lnk dist +copy pombo_task.xml dist \ No newline at end of file diff --git a/index.php.example b/index.php.example new file mode 100644 index 0000000..188c37c --- /dev/null +++ b/index.php.example @@ -0,0 +1,4 @@ + -; W = Windows -; L = GNU/Linux -; M = Mac OSX -; - -; Complete path of GnuPG binary. Not needed if encryption is not used. -; [W] C:\\pombo\\bin\\gpg.exe -; [L] /usr/bin/gpg -; [M] /usr/local/bin/gpg -gpg_binary=C:\\pombo\\bin\\gpg.exe - -; Get the current TCP/IP network interfaces. -; [W] ipconfig /all -; [L] /sbin/ip a -; [L] /sbin/ifconfig -a (deprecated) -; [M] /sbin/ifconfig -a -network_config=ipconfig /all - -; Get the list of Access Points and Ad-Hoc cells in range, and -; optionally a whole bunch of information about them (ESSID, Qual- -; ity, Frequency, Mode ...). -; [W] wlan-dump.bat (only for Windows XP, installed since Pombo 0.0.10) -; [W] netsh wlan show all (note available on Windows XP) -; [L] /sbin/iwlist scanning -; [M] /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s -wifi_access_points=netsh wlan show all - -; Get the route over the network between two systems, listing all the -; intermediate routers a connection must pass through to get to its -; destination. -; [W] tracert -d www.example.org -; [L] /usr/bin/traceroute -q1 www.example.com -; [M] /usr/sbin/traceroute -q1 www.example.com -traceroute=tracert -d www.example.org - -; Get network connections (both incoming and outgoing), routing tables, -; and a number of network interface statistics. -; [W] netstat -n -; [L] /bin/ss -putn -; [L] /bin/netstat -putn (deprecated) -; [M] /usr/sbin/netstat -utn -network_trafic=netstat -n - -; Take screen shot? -; [M,L,W] yes or no -screenshot=yes - -; Take webcam shot? -; will be replaced by a filename, do not customize (required). -; [W] yes or no -; [L] /usr/bin/streamer -q -t 1 -r 2 -o -; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o -; [L] /usr/bin/streamer -q -w 3 -o -; [L] /usr/bin/streamer -q -j 100 -w 3 -s 640x480 -o -; [L] /usr/bin/gst-launch -q v4l2src num_buffers=1 decimate=70 ! pngenc ! filesink location= -; [M] /usr/local/bin/imagesnap -q -w 3.00 -camshot=yes - -; A try to fix most of webcam shot errors is to specify the picture -; extension to feet with your software/hardware. -; Try yourself into a console to find the good one. -; Few possible extensions are: png, jpeg, ppm, bmp, tiff. -; JPEG has the best ratio compression/quality. -; If PPM is chosen, it will be converted to JPEG using /usr/bin/convert. -; [W] not used -; [L,M] png, jpeg, ppm, bmp or tiff -camshot_filetype=jpeg +; Pombo configuration file + +[General] +; +; General parameters --------------------------------------------------- +; + +; Public keyID. +; [NOT recommended] Set it to "i_dont_wanna_use_encryption_and_i_assume" +; to disable report encryption. +gpgkeyid=BAADF00D + +; Password which must be the same as in pombo.php. +password=mysecret + +; Server URL. If several servers, separate them with a "|". +; Example: http://myserver.com/pombo.php +; Example: http://myserver.com/pombo.php|http://myserver2.com/pombo.php +server_url= + +; File to check on one server to tell pombo the computer was stolen. +; If file exists, pombo will send reports each 5 minutes. +; Must be the same as in pombo.php. +check_file=.stolen + +; Time between each check (in minutes). +; When stolen, time between each report is this option divided by 3: +; if time_limit=15, when stolen it will be 15/3 = 5 min. +; On GNU/Linux, think to adapt /etc/cron/pombo. +; On Mac OSX, think to adapt /etc/crontab. +time_limit=15 + +; Email ID to send report as attached file. +; Leave it blank if you do not want to use this feature. +email_id= + +; If True, pombo will check and send report only when IP is not +; the same as the first run. +; This option does not have effect for stolen computer. +; You could add several IP by using "add" option or use add-ip.bat +; on Windows. +only_on_ip_change=no + +; If True, pombo will send report on every check. +always_report=no + +; Enable informations logging (not recommended, only for debug/dev purpose) +enable_log=no + +; Authentification settings -------------------------------------------- +; Proxy +use_proxy=no + +; Prefer environment variables? +use_env=no + +; Proxy URL +; Example: http://proxyurl:proxyport +; Example with auth: http://username:password@proxyurl:proxyport +http_proxy= +https_proxy= + +; .htaccess authentification for one server +; For auth_server, only specify the domain, example: +; if serverurl=http://myserver.com/pombo.php, auth_server=myserver.com +auth_server= +auth_user= +auth_pswd= + + +[Commands] +; +; #################################################################### +; # # +; # /!\ For GNU/Linux & Mac OSX users /!\ # +; # # +; # Use the tool's full path # +; # You can try the 'which' command to know where they are situated. # +; # Example: which ifconfig # +; # # +; #################################################################### +; +; To disable a command, blank it (for example, camshot=). +; +; Examples are formated as: +; [OS] +; W = Windows +; L = GNU/Linux +; M = Mac OSX +; + +; Complete path of GnuPG binary. Not needed if encryption is not used. +; [W] C:\\pombo\\bin\\gpg.exe +; [L] /usr/bin/gpg +; [M] /usr/local/bin/gpg +gpg_binary=C:\\pombo\\bin\\gpg.exe + +; Get the current TCP/IP network interfaces. +; [W] ipconfig /all +; [L] /sbin/ip a +; [L] /sbin/ifconfig -a (deprecated) +; [M] /sbin/ifconfig -a +network_config=ipconfig /all + +; Get the list of Access Points and Ad-Hoc cells in range, and +; optionally a whole bunch of information about them (ESSID, Qual- +; ity, Frequency, Mode ...). +; [W] wlan-dump.bat (only for Windows XP, installed since Pombo 0.0.10) +; [W] netsh wlan show all (note available on Windows XP) +; [L] /sbin/iwlist scanning +; [M] /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s +wifi_access_points=netsh wlan show all + +; Get the route over the network between two systems, listing all the +; intermediate routers a connection must pass through to get to its +; destination. +; [W] tracert -d www.example.org +; [L] /usr/bin/traceroute -q1 www.example.com +; [M] /usr/sbin/traceroute -q1 www.example.com +traceroute=tracert -d www.example.org + +; Get network connections (both incoming and outgoing), routing tables, +; and a number of network interface statistics. +; [W] netstat -n +; [L] /bin/ss -putn +; [L] /bin/netstat -putn (deprecated) +; [M] /usr/sbin/netstat -utn +network_trafic=netstat -n + +; Take screen shot? +; [M,L,W] yes, stolen or (blank) to disable +; - yes: screenshot will always be included when sending the report +; - stolen: screenshot will be included in the report only when the computer is stolen +; - (blank): screenshot will never be taken +screenshot=stolen + +; Take webcam shot? +; will be replaced by a filename, do not customize (required). +; [W] yes, stolen or (blank) to disable +; [W] - yes: camshot will always be included when sending the report +; [W] - stolen: camshot will be included in the report only when the computer is stolen +; [W] - (blank): camshot will never be taken +; [L] /usr/bin/streamer -q -t 1 -r 2 -o +; [L] /usr/bin/streamer -q -t 1 -r 2 -j 100 -s 640x480 -o +; [L] /usr/bin/streamer -q -w 3 -o +; [L] /usr/bin/streamer -q -j 100 -w 3 -s 640x480 -o +; [L] /usr/bin/gst-launch -q v4l2src num_buffers=1 decimate=70 ! pngenc ! filesink location= +; [M] /usr/local/bin/imagesnap -q -w 3.00 +camshot= + +; A try to fix most of webcam shot errors is to specify the picture +; extension to feet with your software/hardware. +; Try yourself into a console to find the good one. +; Few possible extensions are: png, jpeg, ppm, bmp, tiff. +; JPEG has the best ratio compression/quality. +; If PPM is chosen, it will be converted to JPEG using /usr/bin/convert. +; [W] not used +; [L,M] png, jpeg, ppm, bmp or tiff +camshot_filetype=jpeg diff --git a/pombo.lnk b/pombo.lnk new file mode 100644 index 0000000..6d92156 Binary files /dev/null and b/pombo.lnk differ diff --git a/pombo.php b/pombo.php index 8bd56c6..4e4cc61 100644 --- a/pombo.php +++ b/pombo.php @@ -20,11 +20,6 @@ * require '../pombo.php'; * ?> */ - if ( !isset($PASSWORD) ) - $PASSWORD = ''; - if ( !isset($CHECKFILE) ) - $CHECKFILE = ''; - if ( !function_exists('hash_hmac') ) { //Calculate HMAC-SHA1 according to RFC2104 // http://www.ietf.org/rfc/rfc2104.txt @@ -40,24 +35,62 @@ function hash_hmac($hashfunc, $data, $key) { } } + class Pombo + { + private $password; + private $checkfile; + + public function __construct($password, $checkfile) + { + $this->password = $password; + $this->checkfile = $checkfile; + } + + public function isStolen() + { + return is_file($this->checkfile); + } + + public function markAsStolen() + { + if (!$this->isStolen()) { + $fh = fopen($this->checkfile, 'w'); + if ($fh === false) { + return false; + } + fclose($fh); + return true; + } + return true; + } + + public function getClientIP() + { + return !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + } + } + $pombo = new Pombo($PASSWORD, $CHECKFILE); + /* Stolen! */ if ( !empty($_GET) ) { if ( isset($_GET['check']) && $_GET['check'] == $CHECKFILE ) { - if ( is_file($CHECKFILE) ) + if ( $pombo->isStolen() ) { die('Computer already stolen!'); - if ( ($fh = fopen($CHECKFILE, 'w')) === false ) + } + if ( !$pombo->markAsStolen() ) { die('Could not create file.'); - fclose($fh); + } die('File created, Pombo will see it and check every 5 minutes.'); } - if ( isset($_GET['myip']) ) - die( !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']); + if ( isset($_GET['myip']) ) { + die($pombo->getClientIP()); + } die('Nothing to do ...'); } /* Routine */ - else { - if ( empty($_POST) ) - die('Nothing to do ...'); + elseif ( !empty($_POST) ) { + // if ( empty($_POST) ) + // die('Nothing to do ...'); if ( isset($_POST['verify']) ) if ( $_POST['verify'] != hash_hmac('sha1', $_POST['filedata'].'***'.$_POST['filename'], $PASSWORD) ) die('Wrong password!'); @@ -74,6 +107,252 @@ function hash_hmac($hashfunc, $data, $key) { if ( fwrite($fh, base64_decode($_POST['filedata'])) === false ) die('Could not write file.'); fclose($fh); + echo 'File stored.'; + } + + + +/** + * Archive Cleaner Script (v3) + * + * Cleans archived zip files in the script's directory based on retention rules. + * Outputs HTML-compatible line breaks. + * + * Filename format: {device}_{YYYYMMDD}_{HHMMSS}.zip + * Example: NOTASC271_20250331_093208.zip + * + * Retention Rules (per device): + * - Keep all files from the current day. + * - Keep the last file for each day in the past week (excluding today). + * - Keep the last file for each week in the past month (excluding the last week). + * - Keep the last file for each month in the past year (excluding the last month). + */ + +function cecho($msg) { + // echo($msg); +} + +// --- Configuration --- +// Set to false to actually delete files, true to only list what would be deleted. +define('DRY_RUN', false); +// Define the target directory (current directory where the script resides) +define('TARGET_DIR', __DIR__); +// Define the filename pattern using regex - UPDATED for YYYYMMDD_HHMMSS +// - Group 1: Device name ([a-zA-Z0-9_-]+) +// - Group 2: Date (YYYYMMDD) (\d{8}) << CORRECTED +// - Group 3: Time (HHMMSS) (\d{6}) +define('FILENAME_REGEX', '/^([a-zA-Z0-9_-]+)_(\d{8})_(\d{6})\.zip$/'); // << CORRECTED Regex +// Define the line break string +define('NL', "
\n"); + +// --- Initialization --- +// Use the timezone appropriate for the file timestamps / server location +// Set based on location context provided +date_default_timezone_set('Europe/Prague'); +$now = new DateTimeImmutable(); // Use immutable for safety +$allFilesData = []; +$filesToDelete = []; +$filesToKeep = []; // For verification/logging if needed + +cecho("Archive Cleaner Started: " . $now->format('Y-m-d H:i:s T') . NL); // Added Timezone abbreviation +cecho("Mode: " . (DRY_RUN ? "DRY RUN (No files will be deleted)" : "LIVE (Files will be deleted!)") . NL); +cecho("Target Directory: " . TARGET_DIR . NL); + +// --- Step 1: Find and Parse All Zip Files --- +$directoryIterator = new DirectoryIterator(TARGET_DIR); + +foreach ($directoryIterator as $fileInfo) { + if ($fileInfo->isFile() && $fileInfo->getExtension() === 'zip') { + $filename = $fileInfo->getFilename(); + if (preg_match(FILENAME_REGEX, $filename, $matches)) { + $deviceName = $matches[1]; + $dateStr = $matches[2]; // YYYYMMDD << CORRECTED interpretation + $timeStr = $matches[3]; // HHMMSS + $dateTimeStr = $dateStr . $timeStr; // Combined: YYYYMMDDHHMMSS + + try { + // UPDATED format string 'YmdHis' for YYYYMMDDHHMMSS << CORRECTED + $fileDateTime = DateTimeImmutable::createFromFormat('YmdHis', $dateTimeStr); + if ($fileDateTime === false) { + throw new Exception("Could not parse date/time using format 'YmdHis': $dateTimeStr"); + } + + // Store file info grouped by device name + if (!isset($allFilesData[$deviceName])) { + $allFilesData[$deviceName] = []; + } + $allFilesData[$deviceName][] = [ + 'path' => $fileInfo->getPathname(), + 'filename' => $filename, + 'datetime' => $fileDateTime, + 'timestamp' => $fileDateTime->getTimestamp(), + ]; + } catch (Exception $e) { + cecho("Skipping file (parse error): $filename - " . $e->getMessage() . NL); + } + } else { + cecho("Skipping file (format mismatch): $filename" . NL); + } + } +} + +// --- Step 2: Process Files Per Device --- +if (empty($allFilesData)) { + cecho("No matching zip files found." . NL); + exit; +} + +// Calculate cutoff dates (using DateTime objects for reliable comparisons) +$todayStart = $now->setTime(0, 0, 0); +// Start of the week for "past week" rule calculation +$startOfPastWeek = $now->modify('monday this week')->setTime(0, 0, 0); +// If today *is* Monday, "past week" should start from *last* Monday +if ($now->format('N') == 1) { // 'N' gives ISO-8601 day number (1=Monday, 7=Sunday) + $startOfPastWeek = $startOfPastWeek->modify('-7 days'); +} +// Use $startOfPastWeek directly for the lower bound of daily retention +$oneWeekAgo = $startOfPastWeek; // Keep daily files from this time up to todayStart +$oneMonthAgo = $now->modify('-1 month')->setTime(0,0,0); +$oneYearAgo = $now->modify('-1 year')->setTime(0,0,0); + + +cecho("Cutoff - Today Start: " . $todayStart->format('Y-m-d H:i:s') . NL); +// Clarify rule boundaries: +cecho("Cutoff - Keep Daily From: " . $oneWeekAgo->format('Y-m-d H:i:s') . " (Up to Today)" . NL); +cecho("Cutoff - Keep Weekly From: " . $oneMonthAgo->format('Y-m-d H:i:s') . " (Up to Start of Past Week: " . $oneWeekAgo->format('Y-m-d H:i:s') . ")" . NL); +cecho("Cutoff - Keep Monthly From: " . $oneYearAgo->format('Y-m-d H:i:s') . " (Up to 1 Month Ago: " . $oneMonthAgo->format('Y-m-d H:i:s') . ")" . NL); + + +foreach ($allFilesData as $deviceName => $files) { + cecho(NL . "--- Processing Device: $deviceName ---" . NL); + + // Sort files by datetime descending (newest first) + usort($files, function ($a, $b) { + return $b['timestamp'] <=> $a['timestamp']; // Newest first + }); + + $keptMarkers = [ + 'daily' => [], // Key: YYYY-MM-DD + 'weekly' => [], // Key: YYYY-WW (ISO-8601 week number, 'o-W' format) + 'monthly' => [], // Key: YYYY-MM + ]; + + $deviceFilesToKeep = []; + $deviceFilesToDelete = []; + + foreach ($files as $file) { + $fileDateTime = $file['datetime']; + $filePath = $file['path']; + $fileIdentifier = $file['filename']; // Use filename for easier debugging + + // Assume deletion unless a rule keeps it + $keepFile = false; + + // Rule 1: Keep all files from today + if ($fileDateTime >= $todayStart) { + // cecho("Keep (Today): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keepFile = true; + } + // Rule 2: Keep last file for each day in the past week (older than today, newer than or equal to start of past week) + elseif ($fileDateTime < $todayStart && $fileDateTime >= $oneWeekAgo) { + $dayKey = $fileDateTime->format('Y-m-d'); + if (!isset($keptMarkers['daily'][$dayKey])) { + // cecho("Keep (Last 7 Days - Day: $dayKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['daily'][$dayKey] = true; + $keepFile = true; + } + } + // Rule 3: Keep last file for each week in the past month (older than start of past week, newer than or equal to 1 month ago) + elseif ($fileDateTime < $oneWeekAgo && $fileDateTime >= $oneMonthAgo) { + $weekKey = $fileDateTime->format('o-W'); // ISO-8601 year and week number + if (!isset($keptMarkers['weekly'][$weekKey])) { + // cecho("Keep (Last Month - Week: $weekKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['weekly'][$weekKey] = true; + $keepFile = true; + } + } + // Rule 4: Keep last file for each month in the past year (older than 1 month, newer than or equal to 1 year ago) + elseif ($fileDateTime < $oneMonthAgo && $fileDateTime >= $oneYearAgo) { + $monthKey = $fileDateTime->format('Y-m'); + if (!isset($keptMarkers['monthly'][$monthKey])) { + // cecho("Keep (Last Year - Month: $monthKey): $fileIdentifier" . NL); // Uncomment for verbose debugging + $keptMarkers['monthly'][$monthKey] = true; + $keepFile = true; + } + } + + // Add to appropriate list + if ($keepFile) { + $deviceFilesToKeep[$fileIdentifier] = $filePath; + } else { + // Only add for deletion if it's older than 1 year OR not kept by any rule above + if ($fileDateTime < $oneYearAgo || !isset($deviceFilesToKeep[$fileIdentifier])) { + // cecho("Mark for Deletion: $fileIdentifier" . NL); // Uncomment for verbose debugging + $deviceFilesToDelete[$fileIdentifier] = $filePath; + } + } + } // End foreach file in device + + // Add to global lists + $filesToKeep = array_merge($filesToKeep, $deviceFilesToKeep); + $filesToDelete = array_merge($filesToDelete, $deviceFilesToDelete); + + cecho("Summary for $deviceName: " . count($files) . " total files found." . NL); + cecho(" - Files to keep: " . count($deviceFilesToKeep) . NL); + if (!empty($deviceFilesToKeep)) { + cecho("
    " . NL); + foreach(array_keys($deviceFilesToKeep) as $fname) { cecho("
  • $fname
  • " . NL); } + cecho("
" . NL); + } + cecho(" - Files to delete: " . count($deviceFilesToDelete) . NL); + if (!empty($deviceFilesToDelete)) { + cecho("
    " . NL); + foreach(array_keys($deviceFilesToDelete) as $fname) { cecho("
  • $fname
  • " . NL); } + cecho("
" . NL); } - echo 'File stored.'; -?> + + +} // End foreach device + +// --- Step 3: Perform Deletion (if not DRY_RUN) --- +cecho(NL . "--- Deletion Phase ---" . NL); +if (DRY_RUN) { + cecho("DRY RUN enabled. No files will be deleted." . NL); + if (empty($filesToDelete)) { + cecho("No files marked for deletion." . NL); + } else { + cecho("Files that would be deleted: " . count($filesToDelete) . NL); + // Optional: List files again if needed for clarity in dry run + // cecho("
    " . NL); + // foreach(array_keys($filesToDelete) as $fname) { cecho("
  • $fname
  • " . NL); } + // cecho("
" . NL); + } +} else { + if (empty($filesToDelete)) { + cecho("No files marked for deletion." . NL); + } else { + cecho("Deleting " . count($filesToDelete) . " files..." . NL); + $deletedCount = 0; + $errorCount = 0; + foreach ($filesToDelete as $filename => $path) { + cecho("Deleting: $filename ... "); + if (file_exists($path)) { + // Use @unlink to suppress potential warnings if deletion fails, handle manually + if (@unlink($path)) { + cecho("Success." . NL); + $deletedCount++; + } else { + $error = error_get_last(); + cecho("FAILED! (Reason: " . ($error['message'] ?? 'Unknown permission or lock issue') . ")" . NL); + $errorCount++; + } + } else { + cecho("Skipped (File not found at path: $path)." . NL); + $errorCount++; // Count as error if it was expected to be there based on initial scan + } + } + cecho("Deletion complete. $deletedCount files deleted, $errorCount errors." . NL); + } +} + +cecho(NL . "Archive Cleaner Finished." . NL); \ No newline at end of file diff --git a/pombo.py b/pombo.py index 6f63b76..f17100f 100644 --- a/pombo.py +++ b/pombo.py @@ -42,6 +42,7 @@ from socket import gaierror import sys import time +import urllib3 import zipfile from base64 import b64encode from datetime import datetime @@ -71,7 +72,6 @@ if sys.platform == "win32": # pragma: no cover from PIL import Image - from VideoCapture import Device __version__ = "1.1b1" @@ -81,7 +81,7 @@ CURRENT_OS = {"Linux": "Linux", "Darwin": "Mac", "Windows": "Windows"}[ platform.system() ] -ENCODING = sys.stdin.encoding or getdefaultlocale()[1] or "utf-8" +ENCODING = (sys.stdin.encoding if sys.stdin else None) or getdefaultlocale()[1] or "utf-8" VC_VERSION = "0.9.5" URL = "https://github.com/BoboTiG/pombo" UPLINK = "https://raw.github.com/BoboTiG/pombo/master/VERSION" @@ -93,6 +93,7 @@ "time_limit": 15, "email_id": "", "only_on_ip_change": False, + "always_report": False, "enable_log": False, "use_proxy": False, "use_env": False, @@ -106,7 +107,7 @@ "wifi_access_points": "", "traceroute": "", "network_trafic": "", - "screenshot": True, + "screenshot": "yes", "camshot": "", "camshot_filetype": "", } @@ -148,7 +149,10 @@ def hash_string(current_ip): def printerr(string=""): # type: (str) -> None """ Print an error message to STDERR. """ - sys.stderr.write(string + "\n") + try: + sys.stderr.write(string + "\n") + except: + pass def to_bool(value=""): @@ -165,9 +169,9 @@ class Pombo(object): # pylint: disable=too-many-public-methods,too-many-instance-attributes - conf = "c:\\pombo\\pombo.conf" if WINDOWS else "/etc/pombo.conf" - ip_file = "c:\\pombo\\pombo" if WINDOWS else "/var/local/pombo" - log_file = "c:\\pombo\\pombo.log" if WINDOWS else "/var/log/pombo.log" + conf = "C:\\Users\\Public\\pombo\\pombo.conf" if WINDOWS else "/etc/pombo.conf" + ip_file = "C:\\Users\\Public\\pombo\\pombo" if WINDOWS else "/var/local/pombo" + log_file = "C:\\Users\\Public\\pombo\\pombo.log" if WINDOWS else "/var/log/pombo.log" def __init__(self, testing=False): # type: (bool) -> None @@ -244,6 +248,7 @@ def config(self): config["only_on_ip_change"] = conf.getboolean( # type: ignore "General", "only_on_ip_change" ) + config["always_report"] = conf.getboolean("General", "always_report") # type: ignore config["enable_log"] = conf.getboolean("General", "enable_log") # type: ignore config["use_proxy"] = conf.getboolean("General", "use_proxy") # type: ignore config["use_env"] = conf.getboolean("General", "use_env") # type: ignore @@ -257,7 +262,7 @@ def config(self): config["wifi_access_points"] = conf.get("Commands", "wifi_access_points") config["traceroute"] = conf.get("Commands", "traceroute") config["network_trafic"] = conf.get("Commands", "network_trafic") - config["screenshot"] = conf.getboolean("Commands", "screenshot") # type: ignore + config["screenshot"] = conf.get("Commands", "screenshot") config["camshot"] = conf.get("Commands", "camshot") config["camshot_filetype"] = conf.get("Commands", "camshot_filetype") @@ -299,11 +304,11 @@ def get_manufacturer(self): if len(res) < 3: manufacturer = "Unknown" else: - manufacturer = "-".join( + manufacturer = " - ".join( [ - res[1].split("=")[1].strip(), - res[0].split("=")[1].strip(), - res[2].split("=")[1].strip(), + res[1].split("=")[1].strip() if '=' in res[1] else res[1], + res[0].split("=")[1].strip() if '=' in res[0] else res[0], + res[2].split("=")[1].strip() if '=' in res[2] else res[2], ] ) elif self.is_mac(): @@ -368,8 +373,8 @@ def get_serial(self): continue if self.is_windows(): - parts = res.split("=") - if not parts[0].startswith("ERR") and parts[1] != "0": + parts = serial = res.split("=") + if len(parts) >= 2 and not parts[0].startswith("ERR") and parts[1] != "0": serial = parts[1] break elif res != "System Serial Number": @@ -440,6 +445,9 @@ def need_report(self, current_ip): if is_stolen: return True, True + if self.configuration["always_report"]: + return True, False + if not self.configuration["only_on_ip_change"]: self.log.info("Skipping check based on IP change.") return False, False @@ -494,7 +502,7 @@ def request_url(self, url, method="get", params=None): ret = "" parts = urlsplit(url) - ssl_cert_verif = parts.scheme == "https" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # ssl_cert_verif = parts.scheme == "https" auth = None # type: Optional[Tuple[str, str]] if self.configuration["auth_server"] == parts.netloc: @@ -506,7 +514,7 @@ def request_url(self, url, method="get", params=None): url, params=params, proxies=proxies, - verify=ssl_cert_verif, + verify=False, # ssl_cert_verif, auth=auth, timeout=30, ) @@ -515,7 +523,7 @@ def request_url(self, url, method="get", params=None): url, data=params, proxies=proxies, - verify=ssl_cert_verif, + verify=False, # ssl_cert_verif, auth=auth, timeout=30, ) @@ -555,6 +563,7 @@ def runprocess(self, commandline, useshell=False): stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=useshell, + creationflags=subprocess.CREATE_NO_WINDOW if WINDOWS else 0, # hides new terminal window on Windows platform @see https://stackoverflow.com/questions/74048217/hide-popen-in-exe-mode ) sout, serr = myprocess.communicate() myprocess.wait() @@ -584,17 +593,21 @@ def runprocess(self, commandline, useshell=False): return "" - def screenshot(self, filename): - # type: (str) -> List[str] + def screenshot(self, filename, is_stolen=False): + # type: (str, bool) -> List[str] """ Takes a screenshot and returns the path to the saved image (in TMP). None if could not take the screenshot. """ files = [] # type: List[str] - if not self.configuration["screenshot"]: + if not self.configuration["screenshot"] or self.configuration["screenshot"] == 'no': self.log.info("Skipping screenshot.") return files + if self.configuration["screenshot"] == 'stolen' and not is_stolen: + self.log.info("Skipping screenshot, computer not stolen.") + return files + self.log.info("Taking screenshot") if not self.user: self.log.error("Could not determine current user. Cannot take screenshot.") @@ -634,8 +647,8 @@ def snapshot_sendto_server(self, filename, data): self.log.info(txt, sizeof_fmt(len(filedata)), urlsplit(distant).netloc) self.request_url(distant, "post", parameters) - def snapshot(self, current_ip): - # type: (str) -> None + def snapshot(self, current_ip, is_stolen=False): + # type: (str, bool) -> None """ Make a global snapshot of the system (ip, screenshot, webcam...) and sends it to the internet. If not internet connexion is available, will exit. @@ -653,15 +666,15 @@ def snapshot(self, current_ip): self.log.info("Filename: %s", report_name) self.log.info("Collecting system info") filepath = "{}.txt".format(os.path.join(temp, report_name)) - with open(filepath, "w") as fileh: + with open(filepath, "w", encoding='utf8') as fileh: fileh.write(self.system_report(current_ip)) filestozip = [filepath] # Take screenshot(s) - filestozip.extend(self.screenshot(report_name)) + filestozip.extend(self.screenshot(report_name, is_stolen)) # Take a webcam snapshot - webcam = self.webcamshot(report_name) + webcam = self.webcamshot(report_name, is_stolen) if webcam: filestozip.append(webcam) @@ -805,16 +818,23 @@ def system_report(self, current_ip): report = report.replace("\r\n", "\n") return report - def webcamshot(self, filename): - # type: (str) -> Union[str, None] + def webcamshot(self, filename, is_stolen=False): + # type: (str, bool) -> Union[str, None] """ Takes a snapshot with the webcam and returns the path to the saved image (in TMP). None if could not take the snapshot. """ - if not self.configuration["camshot"]: + if not self.configuration["camshot"] or self.configuration["camshot"] == 'no': self.log.info("Skipping webcamshot.") return None + if self.configuration["camshot"] == 'stolen' and not is_stolen: + self.log.info("Skipping webcamshot, computer not stolen.") + return None + + if sys.platform == "win32": # pragma: no cover + from VideoCapture import Device + temp = gettempdir() self.log.info("Taking webcamshot") if self.is_windows(): @@ -877,20 +897,23 @@ def work(self): self.snapshot(current_ip) wait_stolen = self.configuration["time_limit"] // 3 - if self.configuration["only_on_ip_change"]: - complement = "on ip change" + if self.configuration["always_report"]: + complement = "every {} minutes otherwise".format(self.configuration["time_limit"]) + elif self.configuration["only_on_ip_change"]: + complement = "on ip change otherwise" else: - complement = "every {} minutes".format(self.configuration["time_limit"]) + complement = "otherwise no report will be sent" self.log.info( ( "==> In real scenario, Pombo will send a report" - " every %d minutes if stolen, %s otherwise." + " every %d minutes if stolen, %s." ), wait_stolen, complement, ) return + runtime = 0 if self.is_windows(): # Cron job like for Windows :s while True: @@ -900,7 +923,7 @@ def work(self): report_needed, is_stolen = self.need_report(current_ip) if current_ip and report_needed: start = time.time() - self.snapshot(current_ip) + self.snapshot(current_ip, is_stolen) runtime = time.time() - start if is_stolen: time.sleep(wait_stolen - runtime) @@ -917,7 +940,7 @@ def work(self): for i in range(1, 4): self.log.info("* Attempt %d/3 *", i) start = time.time() - self.snapshot(current_ip) + self.snapshot(current_ip, is_stolen) runtime = time.time() - start if i < 3: time.sleep(wait - runtime) diff --git a/pombo_portable.lnk b/pombo_portable.lnk new file mode 100644 index 0000000..5f9fb93 Binary files /dev/null and b/pombo_portable.lnk differ diff --git a/pombo_portable.py b/pombo_portable.py new file mode 100644 index 0000000..07ee3df --- /dev/null +++ b/pombo_portable.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# coding: utf-8 +""" +Pombo +Theft-recovery tracking open-source software +https://github.com/BoboTiG/pombo +http://sebsauvage.net/pombo + +This program is distributed under the OSI-certified zlib/libpnglicense . +http://www.opensource.org/licenses/zlib-license.php + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. +""" + +from pombo import * + + +class PomboPortable(Pombo): + """ Pombo Portable core. """ + + conf = "pombo.conf" if WINDOWS else "pombo.conf" + ip_file = "pombo" if WINDOWS else "pombo" + log_file = "pombo.log" if WINDOWS else "pombo.log" + + +def main_portable(args): + # type: (List[str]) -> int + """ Usage example. """ + + ret = 0 + + try: + if args and args[0] != "check": + parser = PomboArg() + ret = parser.parse(args[0]) + else: + pombo = PomboPortable(testing="check" in args) + pombo.work() + except KeyboardInterrupt: + printerr("*** STOPPING operations ***") + ret = 1 + except Exception as ex: + printerr(str(ex)) + raise + + return ret + + +if __name__ == "__main__": + # pylint: disable=no-value-for-parameter + sys.exit(main_portable(sys.argv[1:])) diff --git a/pombo_task.xml b/pombo_task.xml new file mode 100644 index 0000000..be139d6 Binary files /dev/null and b/pombo_task.xml differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1275325 Binary files /dev/null and b/requirements.txt differ