Merge branch 'main' into unifi-scanner-bugfix

This commit is contained in:
jokob-sk
2023-08-27 20:50:00 +00:00
committed by GitHub
27 changed files with 771 additions and 614 deletions

View File

@@ -20,8 +20,7 @@ You can access the following files:
| `notification_text.txt` | The plain text version of the last notification. |
| `notification_text.html` | The full HTML of the last email notification. |
| `notification_json_final.json` | The json version of the last notification (e.g. used for webhooks - [sample JSON](https://github.com/jokob-sk/Pi.Alert/blob/main/back/webhook_json_sample.json)). |
| `table_devices.json` | The current (at the time of the last update as mentioned above on this page) state of all of the available Devices detected by the app. |
| `table_nmap_scan.json` | The current state of the discovered ports by the regular NMAP scans. |
| `table_devices.json` | The current (at the time of the last update as mentioned above on this page) state of all of the available Devices detected by the app. |
| `table_pholus_scan.json` | The latest state of the [pholus](https://github.com/jokob-sk/Pi.Alert/tree/main/pholus) (A multicast DNS and DNS Service Discovery Security Assessment Tool) scan results. |
| `table_plugins_events.json` | The list of the unprocessed (pending) notification events (plugins_events DB table). |
| `table_plugins_history.json` | The list of notification events history. |

View File

@@ -11,8 +11,7 @@
|----------------------|----------------------| ----------------------|
| CurrentScan | Result of the current scan | ![Screen1][screen1] |
| Devices | The main devices database that also contains the Network tree mappings. If `ScanCycle` is set to `0` device is not scanned. | ![Screen2][screen2] |
| Events | Used to collect connection/disconnection events. | ![Screen4][screen4] |
| Nmap_Scan | Contains results of the scheduled Nmap scan, that is also displayed in the Nmap tab on each device. | ![Screen5][screen5] |
| Events | Used to collect connection/disconnection events. | ![Screen4][screen4] |
| Online_History | Used to display the `Device presence over time` chart | ![Screen6][screen6] |
| Parameters | Used to pass values between the frontend and backend. | ![Screen7][screen7] |
| Pholus_Scan | Scan results of the Pholus python network penetration script. | ![Screen8][screen8] |

View File

@@ -99,19 +99,22 @@
<div class="row">
<div class="col-lg-12 col-sm-12 col-xs-12">
<!-- <div class="box-transparent"> -->
<div id="navDevice" class="nav-tabs-custom">
<ul class="nav nav-tabs" style="fon t-size:16px;">
<li> <a id="tabDetails" href="#panDetails" data-toggle="tab"> <?= lang('DevDetail_Tab_Details');?> </a></li>
<?php
if ($_REQUEST['mac'] == 'Internet') { $DevDetail_Tap_temp = lang('DevDetail_Tab_Tools'); } else { $DevDetail_Tap_temp = lang('DevDetail_Tab_Nmap');}
?>
<li> <a id="tabNmap" href="#panNmap" data-toggle="tab"> <?php echo $DevDetail_Tap_temp;?> </a></li>
<?php
if ($_REQUEST['mac'] != 'Internet') {
?>
<li> <a id="tabNmap" href="#panNmap" data-toggle="tab"> <?= lang('DevDetail_Tab_Nmap');?> </a></li>
<?php
}
?>
<li> <a id="tabTools" href="#panTools" data-toggle="tab"> <?= lang('DevDetail_Tab_Tools');?> </a></li>
<li> <a id="tabSessions" href="#panSessions" data-toggle="tab"> <?= lang('DevDetail_Tab_Sessions');?> </a></li>
<li> <a id="tabPresence" href="#panPresence" data-toggle="tab"> <?= lang('DevDetail_Tab_Presence');?> </a></li>
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
<li> <a id="tabEvents" href="#panEvents" data-toggle="tab"> <?= lang('DevDetail_Tab_Events');?> </a></li>
<li> <a id="tabPholus" href="#panPholus" data-toggle="tab"> <?= lang('DevDetail_Tab_Pholus');?> </a></li>
<li> <a id="tabPlugins" href="#panPlugins" data-toggle="tab"> <?= lang('DevDetail_Tab_Plugins');?> </a></li>
<div class="btn-group pull-right">
<button type="button" class="btn btn-default" style="padding: 10px; min-width: 30px;"
@@ -124,9 +127,7 @@
id="btnNext" onclick="recordSwitch('next')"> <i class="fa fa-chevron-right"></i> </button>
</div>
</ul>
<div class="tab-content" style="min-height: 430px;">
<!-- tab page 1 ------------------------------------------------------------ -->
@@ -534,38 +535,10 @@
</table>
</div>
<!-- tab page 5 ------------------------------------------------------------ -->
<!-- tab page "Nmap" ------------------------------------------------------------ -->
<div class="tab-pane fade" id="panNmap">
<?php
if ($_REQUEST['mac'] == 'Internet') {
?>
<h4 class=""><?= lang('DevDetail_Internet_Speedtest');?></h4>
<div style="width:100%; text-align: center; margin-bottom: 50px;">
<button type="button" id="speedtestcli" class="btn btn-primary pa-btn" style="margin: auto;" onclick="speedtestcli()"><?= lang('DevDetail_Internet_Speedtest_Start');?></button>
</div>
<script>
function speedtestcli() {
$( "#scanoutput" ).empty();
$.ajax({
method: "POST",
url: "./php/server/speedtestcli.php",
beforeSend: function() { $('#scanoutput').addClass("ajax_scripts_loading"); },
complete: function() { $('#scanoutput').removeClass("ajax_scripts_loading"); },
success: function(data, textStatus) {
$("#scanoutput").html(data);
}
})
}
</script>
<?php
}
?>
<h4 class=""><?= lang('DevDetail_Nmap_Scans');?></h4>
<h4 class=""><i class="fa-solid fa-ethernet"></i> <?= lang('DevDetail_Nmap_Scans');?></h4>
<div style="width:100%; text-align: center;">
<script>
setTimeout(function(){
@@ -627,21 +600,47 @@
<th><?= lang("DevDetail_Tab_NmapTableExtra");?></th>
</tr>
</thead>
<!-- Comment out tbody when trying to implement better table with datatables here -->
<!-- Comment out tbody when trying to implement better table with datatables here -->
<tbody id="tableNmapBody">
<tr id="tableNmapPlc" class="text-center"><td colspan='7'><span><?= lang("DevDetail_Tab_NmapEmpty"); ?></span></td></tr>
</tbody>
</table>
</div>
<!-- tab page "Tools" ------------------------------------------------------------ -->
<div class="tab-pane fade" id="panTools">
<!-- ----------------------------------------------------------------------- -->
<?php
if ($_REQUEST['mac'] == 'Internet') {
?>
<h4 class=""><i class="fa-solid fa-globe"></i> <?= lang('DevDetail_Tab_Internet_Info_Title');?></h4>
<div style="width:100%; text-align: center; margin-bottom: 50px;">
<button type="button" id="internetinfo" class="btn btn-primary pa-btn" style="margin: auto;" onclick="internetinfo()"><?= lang('DevDetail_Tab_Internet_Info_Start');?></button>
<br><div id="internetinfooutput" style="margin-top: 10px;"></div>
</div>
<script>
function internetinfo() {
$( "#internetinfooutput" ).empty();
$.ajax({
method: "POST",
url: "./php/server/internetinfo.php",
beforeSend: function() { $('#internetinfooutput').addClass("ajax_scripts_loading"); },
complete: function() { $('#internetinfooutput').removeClass("ajax_scripts_loading"); },
success: function(data, textStatus) {
$("#internetinfooutput").html(data);
}
})
}
</script>
<?php
}
?>
</div>
<!-- tab page 3 ------------------------------------------------------------ -->
<div class="tab-pane fade table-responsive" id="panPresence">

View File

@@ -659,16 +659,16 @@
highlightedCss = nodeData.data.mac == selectedNodeMac ? " highlightedNode" : "";
return result = "<div class='box pointer "+statusCss+" "+highlightedCss+"' data-mytreemacmain='"+nodeData.data.mac+"' \
style='height:"+nodeData.settings.nodeHeight+"px;\
" + fontSize + "\
>\
return result = `<div class='box ${(nodeData.data.hasChildren)? "pointer":""} ${statusCss} ${highlightedCss}'
data-mytreemacmain='${nodeData.data.mac}'
style='height:${nodeData.settings.nodeHeight}px;${fontSize}
>
<div class='netNodeText '>\
<strong>" + devicePort + deviceIcon +
"<span class='spanNetworkTree anonymizeDev'>"+nodeData.data.name+"</span>\
</strong>"
+collapseExpandHtml+
"</div></div>";
<strong>${devicePort} ${deviceIcon}
<span class='spanNetworkTree anonymizeDev'>${nodeData.data.name}</span>\
</strong>
${collapseExpandHtml}
</div></div>`;
},
onNodeClick: nodeData => {

View File

@@ -563,27 +563,15 @@
"DynDNS_display_name" : "DynDNS",
"DynDNS_icon" : "<i class=\"fa fa-globe\"></i>",
"DDNS_ACTIVE_name" : "Enable DynDNS",
"DDNS_ACTIVE_description" : "",
"DDNS_ACTIVE_description" : "Enable DynDNS service",
"DDNS_DOMAIN_name" : "DynDNS domain URL",
"DDNS_DOMAIN_description" : "",
"DDNS_DOMAIN_description" : "DynDNS host URL (do not include http:// or https://).",
"DDNS_USER_name" : "DynDNS user",
"DDNS_USER_description" : "",
"DDNS_USER_description" : "The username used to login to the DynDNS service (sometimes a full email address).",
"DDNS_PASSWORD_name" : "DynDNS password",
"DDNS_PASSWORD_description" : "",
"DDNS_PASSWORD_description" : "The DynDNS service access password",
"DDNS_UPDATE_URL_name" : "DynDNS update URL",
"DDNS_UPDATE_URL_description" : "Update URL starting with <code>http://</code> or <code>https://</code>.",
"Nmap_display_name" : "Nmap",
"Nmap_icon" : "<i class=\"fa fa-ethernet\"></i>",
"NMAP_ACTIVE_name" : "Cycle run",
"NMAP_ACTIVE_description" : "If enabled this will execute a scan on a newly found device. For a scheduled or one-off scan, check the <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code> setting</a>.",
"NMAP_TIMEOUT_name" : "Run timeout",
"NMAP_TIMEOUT_description" : "Maximum time in seconds to wait for an Nmap scan to finish on any device.",
"NMAP_RUN_name" : "Scheduled run",
"NMAP_RUN_description" : "Enable a regular Nmap scan on your network on all devices. The scheduling settings can be found below. If you select <code>once</code> Nmap is run only once on start for the time specified in <a href=\"#NMAP_TIMEOUT\"><code>NMAP_TIMEOUT</code> setting</a>.",
"NMAP_RUN_SCHD_name" : "Schedule",
"NMAP_RUN_SCHD_description" : "Only enabled if you select <code>schedule</code> in the <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format.",
"NMAP_ARGS_name" : "Arguments",
"NMAP_ARGS_description" : "Arguments used to run the Nmap scan. Be careful to specify <a href=\"https://linux.die.net/man/1/nmap\" target=\"_blank\">the arguments</a> correctly. For example <code>-p -10000</code> scans ports from 1 to 10000.",
"API_display_name" : "API",
"API_icon" : "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
"API_CUSTOM_SQL_name" : "Custom endpoint",

View File

@@ -511,15 +511,17 @@
"Apprise_icon" : "<i class=\"fa fa-bullhorn\"></i>",
"REPORT_APPRISE_name" : "Habilitar Apprise",
"REPORT_APPRISE_description" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://hub.docker.com/r/caronc/apprise\">Apprise</a>.",
"APPRISE_HOST_description" : "Apprise host URL que comienza con <code>http://</code> o <code>https://</code>. (no olvide incluir <code>/notify</code> al final)",
"APPRISE_HOST_name" : "Apprise host URL",
"APPRISE_HOST_description" : "URL del host de Apprise que comienza con <code>http://</code> o <code>https://</code>. (no olvide incluir <code>/notify</code> al final)",
"APPRISE_HOST_name" : "URL del host de Apprise",
"APPRISE_URL_name" : "URL de notificación de Apprise",
"APPRISE_URL_description" : "Informar de la URL de destino de la notificación. Por ejemplo, para Telegram sería <code>tgram://{bot_token}/{chat_id}</code>.",
"APPRISE_SIZE_name": "Tamaño máximo de carga útil",
"APPRISE_SIZE_description": "El tamaño máximo de la carga útil de información como número de caracteres en la cadena pasada. Si supera el límite, se truncará y se agregará un mensaje <code>(text was truncated)</code>.",
"NTFY_display_name" : "NTFY",
"NTFY_icon" : "<i class=\"fa fa-terminal\"></i>",
"REPORT_NTFY_name" : "Habilitar NTFY",
"REPORT_NTFY_description" : "Habilitar el envío de notificaciones a través de <a target=\"_blank\" href=\"https://ntfy.sh/\">NTFY</a>.",
"NTFY_HOST_name" : "NTFY host URL",
"NTFY_HOST_name" : "URL del host NTFY",
"NTFY_HOST_description" : "URL de host NTFY que comienza con <code>http://</code> o <code>https://</code>. Puede usar la instancia alojada en <a target=\"_blank\" href=\"https://ntfy.sh/\">https://ntfy.sh</a> simplemente ingresando <code>https://ntfy. sh</código>.",
"NTFY_TOPIC_name" : "Tema de NTFY",
"NTFY_TOPIC_description" : "Tu tema secreto.",
@@ -556,26 +558,15 @@
"DynDNS_display_name" : "DynDNS",
"DynDNS_icon" : "<i class=\"fa fa-globe\"></i>",
"DDNS_ACTIVE_name" : "Habilitar DynDNS",
"DDNS_ACTIVE_description" : "",
"DDNS_ACTIVE_description" : "Habilitar el servicio DynDNS",
"DDNS_DOMAIN_name" : "URL del dominio DynDNS",
"DDNS_DOMAIN_description" : "",
"DDNS_DOMAIN_description" : "URL del host DynDNS (no incluya http:// o https://).",
"DDNS_USER_name" : "Usuario de DynDNS",
"DDNS_USER_description" : "",
"DDNS_USER_description" : "El nombre de usuario utilizado para iniciar sesión en el servicio DynDNS (a veces, una dirección de correo electrónico completa).",
"DDNS_PASSWORD_name" : "Contraseña de DynDNS",
"DDNS_PASSWORD_description" : "",
"DDNS_PASSWORD_description" : "La contraseña de acceso al servicio DynDNS.",
"DDNS_UPDATE_URL_name" : "URL de actualización de DynDNS",
"DDNS_UPDATE_URL_description" : "Actualice la URL que comienza con <code>http://</code> o <code>https://</code>.",
"Nmap_display_name" : "Nmap",
"Nmap_icon" : "<i class=\"fa fa-ethernet\"></i>",
"NMAP_ACTIVE_name" : "Ejecución del ciclo",
"NMAP_ACTIVE_description" : "Si está habilitado, ejecutará un escaneo en un dispositivo recién encontrado. Para un análisis programado o único, verifique la configuración de <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code></a>.",
"NMAP_TIMEOUT_description" : "Tiempo máximo en segundos para esperar a que finalice un escaneo de Nmap en cualquier dispositivo.",
"NMAP_RUN_name" : "Ejecución programada",
"NMAP_RUN_description" : "Habilite un escaneo regular de Nmap en su red en todos los dispositivos. Los ajustes de programación se pueden encontrar a continuación. Si selecciona <code>una vez</code>, Nmap se ejecuta solo una vez al inicio durante el tiempo especificado en la configuración de <a href=\"#NMAP_TIMEOUT\"><code>NMAP_TIMEOUT</code></a>.",
"NMAP_RUN_SCHD_name" : "Programar",
"NMAP_RUN_SCHD_description" : "Solo está habilitado si selecciona <code>programar</code> en la configuración de <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code></a>. Asegúrese de ingresar el cronograma en el formato tipo cron correcto.",
"NMAP_ARGS_name" : "Argumentos",
"NMAP_ARGS_description" : "Argumentos utilizados para ejecutar el análisis de Nmap. Tenga cuidado de especificar <a href=\"https://linux.die.net/man/1/nmap\" target=\"_blank\">los argumentos</a> correctamente. Por ejemplo, <code>-p -10000</code> escanea los puertos del 1 al 10000.",
"API_display_name" : "API",
"API_icon" : "<i class=\"fa fa-arrow-down-up-across-line\"></i>",
"API_CUSTOM_SQL_name" : "Endpoint personalizado",

View File

@@ -15,9 +15,12 @@
- [undiscoverables (UNDIS)](/front/plugins/undiscoverables/)
- [pholus_scan (ARPSCAN)](/front/plugins/pholus_scan/)
- [set_password (SETPWD)](/front/plugins/set_password/)
- [nmap_scan (NMAP)](/front/plugins/nmap_scan/)
### SQL query based plugins
- [nmap_services (NMAPSERV)](/front/plugins/nmap_services/)
- N/A, but the External SQLite based plugins work very similar
### template based plugins
- [newdev_template (NEWDEV)](/front/plugins/newdev_template/)
@@ -455,7 +458,9 @@ Below are some general additional notes, when defining `params`:
- `"type":"<sql|setting>"` - is used to specify the type of the params, currently only 2 supported (`sql`,`setting`).
- `"type":"sql"` - will execute the SQL query specified in the `value` property. The sql query needs to return only one column. The column is flattened and separated by commas (`,`), e.g: `SELECT dev_MAC from DEVICES` -> `Internet,74:ac:74:ac:74:ac,44:44:74:ac:74:ac`. This is then used to replace the wildcards in the `CMD` setting.
- `"type":"setting"` - The setting code name. A combination of the value from `unique_prefix` + `_` + `function` value, or otherwise the code name you can find in the Settings page under the Setting display name, e.g. `PIHOLE_RUN`.
- `"value" : "param_value"` - Needs to contain a setting code name or SQL query without wildcards.
- `"value": "param_value"` - Needs to contain a setting code name or SQL query without wildcards.
- `"timeoutMultiplier" : true` - used to indicate if the value should multiply the max timeout for the whole script run by the number of values in the given parameter.
- `"base64": true` - use base64 encoding to pass the value to the script (e.g. if there are spaces)
> 🔎Example:
@@ -463,20 +468,27 @@ Below are some general additional notes, when defining `params`:
> ```json
> {
> "params" : [{
> "name" : "macs",
> "type" : "sql",
> "value" : "SELECT dev_MAC from DEVICES"
> },
> {
> "name" : "urls",
> "type" : "setting",
> "value" : "WEBMON_urls_to_check"
> },
> {
> "name" : "internet_ip",
> "type" : "setting",
> "value" : "WEBMON_SQL_internet_ip"
> }]
> "name" : "ips",
> "type" : "sql",
> "value" : "SELECT dev_LastIP from DEVICES",
> "timeoutMultiplier" : true
> },
> {
> "name" : "macs",
> "type" : "sql",
> "value" : "SELECT dev_MAC from DEVICES"
> },
> {
> "name" : "timeout",
> "type" : "setting",
> "value" : "NMAP_RUN_TIMEOUT"
> },
> {
> "name" : "args",
> "type" : "setting",
> "value" : "NMAP_ARGS",
> "base64" : true
> }]
> }
> ```

View File

@@ -50,7 +50,8 @@
{
"name" : "subnets",
"type" : "setting",
"value" : "SCAN_SUBNETS"
"value" : "SCAN_SUBNETS",
"base64": true
}],
"settings": [

View File

@@ -28,9 +28,7 @@ def main():
# the script expects a parameter in the format of userSubnets=subnet1,subnet2,...
parser = argparse.ArgumentParser(description='Import devices from settings')
parser.add_argument('userSubnets', nargs='+', help="list of subnets with options")
values = parser.parse_args()
import base64
values = parser.parse_args()
# Assuming Plugin_Objects is a class or function that reads data from the RESULT_FILE
# and returns a list of objects called 'devices'.

View File

@@ -0,0 +1,11 @@
## Overview
This plugin scans your network for open ports. Only IPs are scanned that are accessible by the app container.
### Usage
- TBD
### Notes
- N/A

View File

@@ -1,8 +1,8 @@
{
"code_name": "nmap_services",
"unique_prefix": "NMAPSRV",
"code_name": "nmap_scan",
"unique_prefix": "NMAP",
"enabled": true,
"data_source": "pialert-db-query",
"data_source": "script",
"data_filters": [
{
"compare_column" : "ForeignKey",
@@ -38,7 +38,30 @@
"language_code":"es_es",
"string" : "Este complemento muestra todos los servicios descubiertos por escaneos NMAP."
}],
"params" : [],
"params" : [
{
"name" : "ips",
"type" : "sql",
"value" : "SELECT dev_LastIP from DEVICES",
"timeoutMultiplier" : true
},
{
"name" : "macs",
"type" : "sql",
"value" : "SELECT dev_MAC from DEVICES"
},
{
"name" : "timeout",
"type" : "setting",
"value" : "NMAP_RUN_TIMEOUT"
},
{
"name" : "args",
"type" : "setting",
"value" : "NMAP_ARGS",
"base64" : true
}
],
"database_column_definitions":
[
{
@@ -71,7 +94,7 @@
"column": "Object_PrimaryID",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"type": "device_name_mac",
"default_value":"",
"options": [],
"localized": ["name"],
@@ -94,17 +117,17 @@
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Ip and Port"
"string" : "Port"
},
{
"language_code":"es_es",
"string" : "IP y puerto"
"string" : "Puerto"
}]
} ,
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": false,
"show": true,
"type": "label",
"default_value":"",
"options": [],
@@ -141,22 +164,6 @@
"show": true,
"type": "label",
"default_value":"",
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Service"
},
{
"language_code":"es_es",
"string" : "Servicio"
}]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-1",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
@@ -168,6 +175,22 @@
"string" : "Estado"
}]
},
{
"column": "Watched_Value2",
"css_classes": "col-sm-1",
"show": true,
"type": "label",
"default_value":"",
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Service"
},
{
"language_code":"es_es",
"string" : "Servicio"
}]
},
{
"column": "Watched_Value3",
"css_classes": "col-sm-2",
@@ -272,75 +295,119 @@
}
],
"settings":[
{
"function": "RUN",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "When to run"
},
{
"language_code":"es_es",
"string" : "Cuando ejecutar"
}],
"description": [{
"language_code":"en_us",
"string" : "Specify when the SQL query is executed."
},
{
"language_code":"es_es",
"string" : "Especificar cuándo se ejecuta la consulta SQL."
}]
{
"function": "RUN",
"type": "text.select",
"default_value":"disabled",
"options": ["disabled", "once", "schedule", "always_after_scan", "on_new_device"],
"localized": ["name", "description"],
"name" :[{
"language_code":"en_us",
"string" : "When to run"
},
{
"function": "CMD",
"type": "text",
"default_value":"SELECT ns.MAC as Object_PrimaryID, cast('http://' || dv.dev_LastIP as VARCHAR(100)) || ':' || cast( SUBSTR(ns.Port ,0, INSTR(ns.Port , '/')) as VARCHAR(100)) as Object_SecondaryID, datetime() as DateTime, ns.Service as Watched_Value1, ns.State as Watched_Value2, dv.dev_Name as Watched_Value3, 'null' as Watched_Value4, ns.Extra as Extra, ns.MAC as ForeignKey FROM (SELECT * FROM Nmap_Scan) ns left JOIN (SELECT dev_Name, dev_MAC, dev_LastIP FROM Devices) dv ON ns.MAC = dv.dev_MAC",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"es_es",
"string" : "Cuando ejecutar"
}],
"description": [{
"language_code":"en_us",
"string" : "Enable a regular Nmap scan on your network on all devices. The scheduling settings can be found below. If you select <code>once</code> Nmap is run only once on start for the time specified in <a href=\"#NMAP_TIMEOUT\"><code>NMAP_TIMEOUT</code> setting</a>."
},
{
"language_code":"es_es",
"string" : "Habilite un escaneo regular de Nmap en su red en todos los dispositivos. Los ajustes de programación se pueden encontrar a continuación. Si selecciona <code>una vez</code>, Nmap se ejecuta solo una vez al inicio durante el tiempo especificado en la configuración de <a href=\"#NMAP_TIMEOUT\"><code>NMAP_TIMEOUT</code></a>"
}]
},
{
"function": "CMD",
"type": "text",
"default_value":"python3 /home/pi/pialert/front/plugins/nmap_scan/script.py ips={ips} macs={macs} timeout={timeout} args={args}",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "SQL to run"
},
{
"language_code":"es_es",
"string" : "Consulta SQL"
}],
"description": [{
"language_code":"en_us",
"string" : "This calls the script responsible for executing the NMAP scan."
}
]
},
{
"function": "ARGS",
"type": "text",
"default_value":"-p -10000",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Arguments"
},
{
"language_code":"es_es",
"string" : "Argumentos"
}],
"description": [
{
"language_code":"en_us",
"string" : "SQL to run"
"string" : "Arguments used to run the Nmap scan. Be careful to specify <a href=\"https://linux.die.net/man/1/nmap\" target=\"_blank\">the arguments</a> correctly. For example <code>-p -10000</code> scans ports from 1 to 10000."
},
{
{
"language_code":"es_es",
"string" : "Consulta SQL"
}],
"description": [{
"language_code":"en_us",
"string" : "This SQL query is used to populate the coresponding UI tables under the Plugins section."
},
{
"language_code":"es_es",
"string" : "Esta consulta SQL se usa para completar las tablas de IU correspondientes en la sección Complementos."
"string" : "Argumentos utilizados para ejecutar el análisis de Nmap. Tenga cuidado de especificar <a href=\"https://linux.die.net/man/1/nmap\" target=\"_blank\">los argumentos</a> correctamente. Por ejemplo, <code>-p -10000</code> escanea los puertos del 1 al 10000."
}
]
]
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value":"0 2 * * *",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value":"0 2 * * *",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Schedule"
"language_code":"es_es",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format."
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>programar</code> en la configuración de <a href=\"#NMAP_RUN\"><code>NMAP_RUN</code></a>. Asegúrese de ingresar el cronograma en el formato tipo cron correcto."
}]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 30,
"options": [],
"localized": ["name", "description"],
"name": [
{
"language_code": "en_us",
"string": "Run timeout"
},
{
"language_code":"es_es",
"string" : "Schedule"
}],
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#NMAPSRV_RUN\"><code>NMAPSRV_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#NMAPSRV_RUN\"><code>NMAPSRV_RUN</code> setting</a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>0 4 * * *</code> ejecutará el escaneo después de las 4 a.m. en el <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</ código> que configuró arriba</a>. Se ejecutará la PRÓXIMA vez que pase el tiempo."
}]
{
"language_code": "es_es",
"string": "Tiempo límite de ejecución"
}
],
"description": [
{
"language_code": "en_us",
"string": "Max run time per device in seconds."
}
]
},
{
"function": "WATCH",
@@ -352,7 +419,7 @@
"language_code":"en_us",
"string" : "Watched"
},
{
{
"language_code":"es_es",
"string" : "Visto"
}] ,
@@ -360,7 +427,7 @@
"language_code":"en_us",
"string" : "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is service type (e.g.: http, ssh)</li><li><code>Watched_Value2</code> is Status (open or closed)</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
},
{
{
"language_code":"es_es",
"string" : "Envíe una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es el tipo de servicio (p. ej., http, ssh)</li><li><code>Watched_Value2</code> es el estado (abierto o cerrado)</li> <li><code>Watched_Value3</code> no utilizado </li><li><code>Watched_Value4</code> no utilizado </li></ul>"
}]
@@ -375,7 +442,7 @@
"language_code":"en_us",
"string" : "Report on"
},
{
{
"language_code":"es_es",
"string" : "Informar sobre"
}] ,
@@ -383,7 +450,7 @@
"language_code":"en_us",
"string" : "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>Watched_ValueN</code> columns changed."
},
{
{
"language_code":"es_es",
"string" : "Envíe una notificación solo en estos estados. <code>new</code> significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). <code>watched-changed</code> significa que seleccionó <code>Watched_ValueN Las columnas </code> cambiaron."
}]

279
front/plugins/nmap_scan/script.py Executable file
View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python
import os
import pathlib
import argparse
import sys
import re
import base64
import subprocess
from time import strftime
sys.path.append("/home/pi/pialert/front/plugins")
sys.path.append('/home/pi/pialert/pialert')
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ
from const import logPath, pialertPath
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
#-------------------------------------------------------------------------------
def main():
# sample
# /home/pi/pialert/front/plugins/nmap_scan/script.py ips=192.168.1.66,192.168.1.9'
parser = argparse.ArgumentParser(description='Scan ports of devices specified by IP addresses')
parser.add_argument('ips', nargs='+', help="list of IPs to scan")
parser.add_argument('macs', nargs='+', help="list of MACs related to the supplied IPs in the same order")
parser.add_argument('timeout', nargs='+', help="timeout")
parser.add_argument('args', nargs='+', help="args")
values = parser.parse_args()
# Plugin_Objects is a class that reads data from the RESULT_FILE
# and returns a list of results.
results = Plugin_Objects(RESULT_FILE)
# Print a message to indicate that the script is starting.
mylog('debug', ['[NMAP Scan] In script '])
# Printing the params list to check its content.
mylog('debug', ['[NMAP Scan] values.ips: ', values.ips])
mylog('debug', ['[NMAP Scan] values.macs: ', values.macs])
mylog('debug', ['[NMAP Scan] values.timeout: ', values.timeout])
mylog('debug', ['[NMAP Scan] values.args: ', values.args])
argsDecoded = decodeBase64(values.args[0].split('=b')[1])
mylog('debug', ['[NMAP Scan] argsDecoded: ', argsDecoded])
entries = performNmapScan(values.ips[0].split('=')[1].split(','), values.macs[0].split('=')[1].split(',') , values.timeout[0].split('=')[1], argsDecoded)
for entry in entries:
results.add_object(
primaryId = entry.mac, # MAC (Device Name)
secondaryId = entry.port, # IP Address (always 0.0.0.0)
watched1 = entry.state, # Device Name
watched2 = entry.service,
watched3 = entry.ip + ":" + entry.port,
watched4 = "",
extra = "",
foreignKey = entry.extra
)
entries.write_result_file()
#-------------------------------------------------------------------------------
class nmap_entry:
def __init__(self, ip, mac, time, port, state, service, name = '', extra = '', index = 0):
self.ip = ip
self.mac = mac
self.time = time
self.port = port
self.state = state
self.service = service
self.extra = extra
self.index = index
self.hash = str(mac) + str(port)+ str(state)+ str(service)
#-------------------------------------------------------------------------------
def performNmapScan(deviceIPs, deviceMACs, timeoutSec, args):
"""
run nmap scan on a list of devices
discovers open ports and keeps track existing and new open ports
"""
if len(deviceIPs) > 0:
devTotal = len(deviceIPs)
mylog('verbose', ['[NMAP Scan] Scan: Nmap for max ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min) per device'])
mylog('verbose', ["[NMAP Scan] Estimated max delay: ", (devTotal * int(timeoutSec)), 's ', '(', round((devTotal * int(timeoutSec))/60,1) , 'min)' ])
# collect ports / new Nmap Entries
newEntriesTmp = []
devIndex = 0
for ip in deviceIPs:
# Execute command
output = ""
# prepare arguments from user supplied ones
nmapArgs = ['nmap'] + args.split() + [ip]
progress = ' (' + str(devIndex+1) + '/' + str(devTotal) + ')'
try:
# try runnning a subprocess with a forced (timeout) in case the subprocess hangs
output = subprocess.check_output (nmapArgs, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(float(timeoutSec)))
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ["[NMAP Scan] " ,e.output])
mylog('none', ["[NMAP Scan] Error - Nmap Scan - check logs", progress])
except subprocess.TimeoutExpired as timeErr:
mylog('verbose', ['[NMAP Scan] Nmap TIMEOUT - the process forcefully terminated as timeout reached for ', ip, progress])
if output == "": # check if the subprocess failed
mylog('minimal', ['[NMAP Scan] Nmap FAIL for ', ip, progress ,' check logs for details'])
else:
mylog('verbose', ['[NMAP Scan] Nmap SUCCESS for ', ip, progress])
# check the last run output
newLines = output.split('\n')
# regular logging
for line in newLines:
append_line_to_file (logPath + '/pialert_nmap.log', line +'\n')
index = 0
startCollecting = False
duration = ""
for line in newLines:
if 'Starting Nmap' in line:
if len(newLines) > index+1 and 'Note: Host seems down' in newLines[index+1]:
break # this entry is empty
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
startCollecting = True
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
startCollecting = False # end reached
elif startCollecting and len(line.split()) == 3:
newEntriesTmp.append(nmap_entry(ip, deviceMACs[devIndex], timeNowTZ(), line.split()[0], line.split()[1], line.split()[2]))
elif 'Nmap done' in line:
duration = line.split('scanned in ')[1]
index += 1
devIndex += 1
mylog('verbose', ['[NMAP Scan] Ports found by NMAP: ', len(newEntriesTmp)])
#end for loop
return newEntriesTmp
#===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
main()
# def process_discovered_ports(db, device, discoveredPorts):
# """
# process ports discovered by nmap
# compare to previosu ports
# update DB
# raise notifications
# """
# sql = db.sql # TO-DO
# # previous Nmap Entries
# oldEntries = []
# changedPortsTmp = []
# mylog('verbose', ['[NMAP Scan] Process ports found by NMAP: ', len(discoveredPorts)])
# if len(discoveredPorts) > 0:
# # get all current NMAP ports from the DB
# rows = db.read(sql_nmap_scan_all)
# for row in rows:
# # only collect entries matching the current MAC address
# if row["MAC"] == device["dev_MAC"]:
# oldEntries.append(nmap_entry(row["MAC"], row["Time"], row["Port"], row["State"], row["Service"], device["dev_Name"], row["Extra"], row["Index"]))
# newEntries = []
# # Collect all entries that don't match the ones in the DB
# for discoveredPort in discoveredPorts:
# found = False
# # Check the new entry is already available in oldEntries and remove from processing if yes
# for oldEntry in oldEntries:
# if discoveredPort.hash == oldEntry.hash:
# found = True
# if not found:
# newEntries.append(discoveredPort)
# mylog('verbose', ['[NMAP Scan] Nmap newly discovered or changed ports: ', len(newEntries)])
# # collect new ports, find the corresponding old entry and return for notification purposes
# # also update the DB with the new values after deleting the old ones
# if len(newEntries) > 0:
# # params to build the SQL query
# params = []
# indexesToDelete = ""
# # Find old entry matching the new entry hash
# for newEntry in newEntries:
# foundEntry = None
# for oldEntry in oldEntries:
# if oldEntry.hash == newEntry.hash:
# indexesToDelete = indexesToDelete + str(oldEntry.index) + ','
# foundEntry = oldEntry
# columnNames = ["Name", "MAC", "Port", "State", "Service", "Extra", "NewOrOld" ]
# # Old entry found
# if foundEntry is not None:
# # Build params for sql query
# params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, oldEntry.extra))
# # Build JSON for API and notifications
# changedPortsTmp.append({
# "Name" : foundEntry.name,
# "MAC" : newEntry.mac,
# "Port" : newEntry.port,
# "State" : newEntry.state,
# "Service" : newEntry.service,
# "Extra" : foundEntry.extra,
# "NewOrOld" : "New values"
# })
# changedPortsTmp.append({
# "Name" : foundEntry.name,
# "MAC" : foundEntry.mac,
# "Port" : foundEntry.port,
# "State" : foundEntry.state,
# "Service" : foundEntry.service,
# "Extra" : foundEntry.extra,
# "NewOrOld" : "Old values"
# })
# # New entry - no matching Old entry found
# else:
# # Build params for sql query
# params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, ''))
# # Build JSON for API and notifications
# changedPortsTmp.append({
# "Name" : "New device",
# "MAC" : newEntry.mac,
# "Port" : newEntry.port,
# "State" : newEntry.state,
# "Service" : newEntry.service,
# "Extra" : "",
# "NewOrOld" : "New device"
# })
# conf.changedPorts_json_struc = json_struc({ "data" : changedPortsTmp}, columnNames)
# # Delete old entries if available
# if len(indexesToDelete) > 0:
# sql.execute ("DELETE FROM Nmap_Scan where \"Index\" in (" + indexesToDelete[:-1] +")")
# db.commitDB()
# # Insert new values into the DB
# sql.executemany ("""INSERT INTO Nmap_Scan ("MAC", "Time", "Port", "State", "Service", "Extra") VALUES (?, ?, ?, ?, ?, ?)""", params)
# db.commitDB()

View File

@@ -1,11 +0,0 @@
## Overview
This plugin shows all Services discovered by regular NMAP scans. It's also a sample plugin showcasing how to use a SQL Query to show existing data from the PiAlert database.
### Usage
- The sql query from the `NMAPSRV_CMD` setting is used to create source data for this plugin. Column order and values need to adhere to the ones specified in the [documentation](https://github.com/jokob-sk/Pi.Alert/tree/main/front/plugins).
### Notes
- N/A

View File

@@ -20,6 +20,10 @@
{
"language_code": "en_us",
"string": "Pholus-Scan (Name discovery)"
},
{
"language_code": "en_us",
"string": "Pholus-Scan (Descubrimiento de nombre)"
}
],
"icon": [
@@ -36,7 +40,11 @@
{
"language_code": "en_us",
"string": "This plugin is to execute a Pholus-scan (name discovery) on the local network"
}
},
{
"language_code": "es_es",
"string": "Este plugin sirve para ejecutar un escaneo Pholus (descubrimiento de nombres) en la red local"
}
],
"params" : [
{
@@ -127,7 +135,11 @@
{
"language_code": "en_us",
"string": "Network scan time in seconds. Pholus scan will always run this long. The longer it runs the more device names might be resolved. Will be divided by the number of subnets."
}
},
{
"language_code": "es_es",
"string": "Tiempo de escaneo de red en segundos. El escaneo de Pholus siempre durará este tiempo. Cuanto más tiempo se ejecute, más nombres de dispositivos se podrán resolver. Se dividirá por el número de subredes."
}
]
},
{
@@ -147,6 +159,10 @@
"description": [{
"language_code":"en_us",
"string" : "Only enabled if you select <code>schedule</code> in the <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>30 3 * * *</code> will run the scan at 3:30 am. Will be run NEXT time the time passes. <br/>"
},
{
"language_code":"es_es",
"string" : "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#PHOLUS_RUN\"><code>PHOLUS_RUN</code></a>. Asegúrese de ingresar la programación en el formato cron correcto (por ejemplo, validar en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, al ingresar <code>30 3 * * *</code> se ejecutará el escaneo a las 3:30 am. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/>"
}]
},
{
@@ -191,6 +207,10 @@
"description":[{
"language_code":"en_us",
"string" : "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Info</li><li><code>Watched_Value2</code> is Record type</li><li><code>Watched_Value3</code> is Info </li><li><code>Watched_Value4</code> is N/A </li></ul>"
},
{
"language_code":"es_es",
"string" : "Enviar una notificación si los valores seleccionados cambian. Utilice <code>CTRL + Clic</code> para seleccionar/deseleccionar. <ul> <li><code>Watched_Value1</code> es Información</li><li><code>Watched_Value2</code> es Tipo de registro</li><li><code>Watched_Value3</code> es La información </li><li><code>Watched_Value4</code> es N/A </li></ul>"
}]
},
{
@@ -290,6 +310,10 @@
"name":[{
"language_code":"en_us",
"string" : "Type"
},
{
"language_code":"es_es",
"string" : "Tipo"
}]
} ,
{

View File

@@ -1,7 +1,14 @@
from time import strftime
import pytz
import sys
import base64
from datetime import datetime
sys.path.append("/home/pi/pialert/front/plugins")
sys.path.append('/home/pi/pialert/pialert')
from logger import mylog
#-------------------------------------------------------------------------------
def read_config_file():
"""
@@ -20,14 +27,36 @@ def read_config_file():
confDict = {} # config dictionary
exec(code, {"__builtins__": {}}, confDict)
return confDict
# -------------------------------------------------------------------
pialertConfigFile = read_config_file()
timeZoneSetting = pialertConfigFile['TIMEZONE']
timeZone = pytz.timezone(timeZoneSetting)
# -------------------------------------------------------------------
def decodeBase64(inputParamBase64):
# Printing the input list to check its content.
mylog('debug', ['[Plugins] Helper base64 input: ', input])
print('[Plugins] Helper base64 input: ')
print(input)
# Extract the base64-encoded subnet information from the first element
# The format of the element is assumed to be like 'param=b<base64-encoded-data>'.
# Printing the extracted base64-encoded information.
mylog('debug', ['[Plugins] Helper base64 inputParamBase64: ', inputParamBase64])
# Decode the base64-encoded subnet information to get the actual subnet information in ASCII format.
result = base64.b64decode(inputParamBase64).decode('ascii')
# Print the decoded subnet information.
mylog('debug', ['[Plugins] Helper base64 result: ', result])
return result
# -------------------------------------------------------------------
class Plugin_Object:

View File

@@ -64,16 +64,16 @@
"type" : "setting",
"value" : "UNFIMP_sites"
},
{
"name" : "protocol",
"type" : "setting",
"value" : "UNFIMP_protocol"
},
{
"name" : "port",
"type" : "setting",
"value" : "UNFIMP_port"
},
{
"name" : "verifyssl",
"type" : "setting",
"value" : "UNFIMP_verifyssl"
},
{
"name" : "version",
"type" : "setting",
@@ -414,7 +414,7 @@
{
"function": "CMD",
"type": "text",
"default_value":"python3 /home/pi/pialert/front/plugins/unifi_import/script.py username={username} password={password} host={host} sites={sites} protocol={protocol} port={port} version={version}",
"default_value":"python3 /home/pi/pialert/front/plugins/unifi_import/script.py username={username} password={password} host={host} sites={sites} port={port} verifyssl={verifyssl} version={version}",
"options": [],
"localized": ["name", "description"],
"name" : [{
@@ -488,29 +488,6 @@
"string" : "La contraseña utilizada para iniciar sesión en su controlador UNIFI."
}]
},
{
"function": "protocol",
"type": "text.select",
"default_value":"https://",
"options": ["https://", "http://"],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "Protocol"
},
{
"language_code":"es_es",
"string" : "Protocolo"
}],
"description": [{
"language_code":"en_us",
"string" : "The protocol to use to access the controller."
},
{
"language_code":"es_es",
"string" : "El protocolo a utilizar para acceder al controlador."
}]
},
{
"function": "host",
"type": "text",
@@ -557,6 +534,29 @@
"string" : "El número de puerto donde se ejecuta el controlador UNIFI. Normalmente es <code>8443</code>."
}]
},
{
"function": "verifyssl",
"type": "text",
"default_value":"false",
"options": [],
"localized": ["name", "description"],
"name" : [{
"language_code":"en_us",
"string" : "verify SSL"
},
{
"language_code": "es_es",
"string": "verificar SSL"
}],
"description": [{
"language_code":"en_us",
"string" : "verify SSL certificate validity <code>true|false</code>."
},
{
"language_code":"es_es",
"string" : "verificar la validez del certificado SSL <code>true|false</code>."
}]
},
{
"function": "version",
"type": "text",

View File

@@ -2,7 +2,10 @@
# Inspired by https://github.com/stevehoek/Pi.Alert
# Example call
# python3 /home/pi/pialert/front/plugins/unifi_import/script.py username=pialert password=passw0rd host=192.168.1.1 site=default protocol=https:// port=8443 version='UDMP-unifiOS'
# python3 /home/pi/pialert/front/plugins/unifi_import/script.py username=pialert password=passw0rd host=192.168.1.1 sites=sdefault port=8443 verifyssl=false version=v5
from __future__ import unicode_literals
from time import sleep, time, strftime
@@ -43,8 +46,7 @@ def main():
unifi_logger.info('Start scan')
# init global variables
global UNIFI_USERNAME, UNIFI_PASSWORD, UNIFI_HOST
global UNIFI_SITES, PORT, PROTOCOL, VERSION
global UNIFI_USERNAME, UNIFI_PASSWORD, UNIFI_HOST, UNIFI_SITES, PORT, VERIFYSSL, VERSION
# empty file
@@ -58,8 +60,8 @@ def main():
parser.add_argument('password', action="store", help="Password used to login into the UNIFI controller")
parser.add_argument('host', action="store", help="Host url or IP address where the UNIFI controller is hosted (excluding http://)")
parser.add_argument('sites', action="store", help="Name of the sites (usually 'default', check the URL in your UniFi controller UI). Separated by comma (,) if passing multiple sites")
parser.add_argument('protocol', action="store", help="https:// or http://")
parser.add_argument('port', action="store", help="Usually 8443")
parser.add_argument('verifyssl', action="store", help="verify SSL certificate [true|false]")
parser.add_argument('version', action="store", help="The base version of the controller API [v4|v5|unifiOS|UDMP-unifiOS]")
values = parser.parse_args()
@@ -74,8 +76,8 @@ def main():
UNIFI_PASSWORD = values.password.split('=')[1]
UNIFI_HOST = values.host.split('=')[1]
UNIFI_SITES = values.sites.split('=')[1]
PROTOCOL = values.protocol.split('=')[1]
PORT = values.port.split('=')[1]
VERIFYSSL = values.verifyssl.split('=')[1]
VERSION = values.version.split('=')[1]
newEntries = get_entries(newEntries)
@@ -89,6 +91,7 @@ def main():
# -----------------------------------------------------------------------------
def get_entries(newEntries):
global VERIFYSSL
sites = []
@@ -98,10 +101,14 @@ def get_entries(newEntries):
else:
sites.append(UNIFI_SITES)
if (VERIFYSSL.upper() == "TRUE"):
VERIFYSSL = True
else:
VERIFYSSL = False
for site in sites:
c = Controller(UNIFI_HOST, UNIFI_USERNAME, UNIFI_PASSWORD, version=VERSION, ssl_verify=False, site_id=site )
c = Controller(UNIFI_HOST, UNIFI_USERNAME, UNIFI_PASSWORD, port=PORT, version=VERSION, ssl_verify=VERIFYSSL, site_id=site )
unifi_logger.debug('identify Unifi Devices')
# get all Unifi devices

View File

@@ -34,7 +34,6 @@ from reporting import check_and_run_event, send_notifications
from plugin import run_plugin_scripts
# different scanners
from scanners.nmapscan import performNmapScan
from scanners.internet import check_internet_IP
@@ -58,8 +57,7 @@ main structure of Pi Alert
run scans
run plugins (scheduled)
check internet IP
check vendor
run NMAP
check vendor
run "scan_network()"
processing scan results
run plugins (after Scan)
@@ -160,25 +158,6 @@ def main ():
conf.cycle = 'update_vendors'
mylog('verbose', ['[MAIN] cycle:',conf.cycle])
update_devices_MAC_vendors(db)
# Execute scheduled or one-off Nmap scan if enabled and run conditions fulfilled
if conf.NMAP_RUN == "schedule" or conf.NMAP_RUN == "once":
nmapSchedule = [sch for sch in conf.mySchedules if sch.service == "nmap"][0]
run = False
# run once after application starts
if conf.NMAP_RUN == "once" and nmapSchedule.last_run == 0:
run = True
# run if overdue scheduled time
if conf.NMAP_RUN == "schedule":
run = nmapSchedule.runScheduleCheck()
if run:
nmapSchedule.last_run = timeNowTZ()
performNmapScan(db, get_all_devices(db))
# Run splugin scripts which are set to run every timne after a scans finished
pluginsState = run_plugin_scripts(db,'always_after_scan', pluginsState)
@@ -202,11 +181,7 @@ def main ():
# new devices were found
if len(newDevices) > 0:
# run all plugins registered to be run when new devices are found
pluginsState = run_plugin_scripts(db, 'on_new_device', pluginsState)
# Scan newly found devices with Nmap if enabled
if conf.NMAP_ACTIVE and len(newDevices) > 0:
performNmapScan( db, newDevices)
pluginsState = run_plugin_scripts(db, 'on_new_device', pluginsState)
# send all configured notifications
send_notifications(db)

View File

@@ -3,7 +3,7 @@ import json
# pialert modules
import conf
from const import (apiPath, sql_devices_all, sql_nmap_scan_all, sql_pholus_scan_all, sql_events_pending_alert,
from const import (apiPath, sql_devices_all, sql_events_pending_alert,
sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings)
from logger import mylog
from helper import write_file
@@ -26,9 +26,7 @@ def update_api(db, isNotification = False, updateOnlyDataSources = []):
# prepare database tables we want to expose
dataSourcesSQLs = [
["devices", sql_devices_all],
["nmap_scan", sql_nmap_scan_all],
["pholus_scan", sql_pholus_scan_all],
["devices", sql_devices_all],
["events_pending_alert", sql_events_pending_alert],
["settings", sql_settings],
["plugins_events", sql_plugins_events],

View File

@@ -33,7 +33,6 @@ mqtt_connected_to_broker = False
mqtt_sensors = []
client = None # mqtt client
# for notifications
changedPorts_json_struc = None
# ACTUAL CONFIGRATION ITEMS set to defaults
@@ -43,7 +42,7 @@ LOG_LEVEL = 'verbose'
TIMEZONE = 'Europe/Berlin'
PIALERT_WEB_PROTECTION = False
PIALERT_WEB_PASSWORD = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
INCLUDED_SECTIONS = ['internet', 'new_devices', 'down_devices', 'events', 'ports']
INCLUDED_SECTIONS = ['internet', 'new_devices', 'down_devices', 'events']
DAYS_TO_KEEP_EVENTS = 90
REPORT_DASHBOARD_URL = 'http://pi.alert/'
DIG_GET_IP_ARG = '-4 myip.opendns.com @resolver1.opendns.com'
@@ -103,12 +102,5 @@ DDNS_USER = 'dynu_user'
DDNS_PASSWORD = 'A0000000B0000000C0000000D0000000'
DDNS_UPDATE_URL = 'https://api.dynu.com/nic/update?'
# Nmap
NMAP_ACTIVE = True
NMAP_TIMEOUT = 150
NMAP_RUN = 'once'
NMAP_RUN_SCHD = '0 2 * * *'
NMAP_ARGS = '-p -10000 --max-parallelism 100'
# API
API_CUSTOM_SQL = 'SELECT * FROM Devices WHERE dev_PresentLastScan = 0'

View File

@@ -35,8 +35,6 @@ sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, A
(select count(*) from Devices a where dev_NewDevice = 1 ) as new,
(select count(*) from Devices a where dev_Name = '(unknown)' or dev_Name = '(name not found)' ) as unknown
from Online_History order by Scan_Date desc limit 1"""
sql_nmap_scan_all = "SELECT * FROM Nmap_Scan"
sql_pholus_scan_all = "SELECT * FROM Pholus_Scan"
sql_events_pending_alert = "SELECT * FROM Events where eve_PendingAlertEmail is not 0"
sql_settings = "SELECT * FROM Settings"
sql_plugins_objects = "SELECT * FROM Plugins_Objects"

View File

@@ -89,7 +89,7 @@ def importConfigs (db):
conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 250 , c_d, 'Keep history entries', 'integer', '', 'General')
conf.PIALERT_WEB_PROTECTION = ccd('PIALERT_WEB_PROTECTION', False , c_d, 'Enable logon', 'boolean', '', 'General')
conf.PIALERT_WEB_PASSWORD = ccd('PIALERT_WEB_PASSWORD', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' , c_d, 'Logon password', 'readonly', '', 'General')
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events', 'ports'] , c_d, 'Notify on', 'text.multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'ports', 'plugins']", 'General')
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events'] , c_d, 'Notify on', 'text.multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'plugins']", 'General')
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://pi.alert/' , c_d, 'PiAlert URL', 'text', '', 'General')
conf.DIG_GET_IP_ARG = ccd('DIG_GET_IP_ARG', '-4 myip.opendns.com @resolver1.opendns.com' , c_d, 'DIG arguments', 'text', '', 'General')
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'German', 'Spanish']", 'General')
@@ -154,13 +154,7 @@ def importConfigs (db):
conf.DDNS_PASSWORD = ccd('DDNS_PASSWORD', 'A0000000B0000000C0000000D0000000' , c_d, 'DynDNS password', 'password', '', 'DynDNS')
conf.DDNS_UPDATE_URL = ccd('DDNS_UPDATE_URL', 'https://api.dynu.com/nic/update?' , c_d, 'DynDNS update URL', 'text', '', 'DynDNS')
# Nmap
conf.NMAP_ACTIVE = ccd('NMAP_ACTIVE', True , c_d, 'Enable Nmap scans', 'boolean', '', 'Nmap')
conf.NMAP_TIMEOUT = ccd('NMAP_TIMEOUT', 150 , c_d, 'Nmap timeout', 'integer', '', 'Nmap')
conf.NMAP_RUN = ccd('NMAP_RUN', 'disabled' , c_d, 'Nmap enable schedule', 'text.select', "['disabled', 'once', 'schedule']", 'Nmap')
conf.NMAP_RUN_SCHD = ccd('NMAP_RUN_SCHD', '0 2 * * *' , c_d, 'Nmap schedule', 'text', '', 'Nmap')
conf.NMAP_ARGS = ccd('NMAP_ARGS', '-p -10000' , c_d, 'Nmap custom arguments', 'text', '', 'Nmap')
# Init timezone in case it changed
conf.tz = timezone(conf.TIMEZONE)
@@ -188,10 +182,6 @@ def importConfigs (db):
# reset schedules
conf.mySchedules = []
# init nmap schedule
nmapSchedule = Cron(conf.NMAP_RUN_SCHD).schedule(start_date=datetime.datetime.now(conf.tz))
conf.mySchedules.append(schedule_class("nmap", nmapSchedule, nmapSchedule.next(), False))
# Format and prepare the list of subnets
conf.userSubnets = updateSubnets(conf.SCAN_SUBNETS)
@@ -252,10 +242,7 @@ def importConfigs (db):
conf.plugins_once_run = False
# -----------------
# Plugins END
# write_file(self.path, json.dumps(self.jsonData))
# Insert settings into the DB
sql.execute ("DELETE FROM Settings")
@@ -270,9 +257,9 @@ def importConfigs (db):
# update only the settings datasource
update_api(db, False, ["settings"])
# run plugins that are modifying the config
pluginsState = run_plugin_scripts(db, 'before_config_save')
run_plugin_scripts(db, 'before_config_save' )
# Used to determine the next import
conf.lastImportedConfFile = os.path.getmtime(config_file)

View File

@@ -3,6 +3,7 @@ import sqlite3
import json
import subprocess
import datetime
import base64
from collections import namedtuple
@@ -16,16 +17,88 @@ from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_set
#-------------------------------------------------------------------------------
class plugins_state:
def __init__(self, processScan = False):
self.processScan = processScan
class plugin_param:
def __init__(self, param, plugin, db):
paramValuesCount = 1
# Get setting value
if param["type"] == "setting":
inputValue = get_setting(param["value"])
if inputValue != None:
setVal = inputValue[6] # setting value
setTyp = inputValue[3] # setting type
noConversion = ['text', 'string', 'integer', 'boolean', 'password', 'readonly', 'integer.select', 'text.select', 'integer.checkbox' ]
arrayConversion = ['text.multiselect', 'list', 'subnets']
jsonConversion = ['.template']
mylog('debug', f'[Plugins] setTyp: {setTyp}')
if '.select' in setTyp or setTyp in arrayConversion:
paramValuesCount = len(setVal)
if setTyp in noConversion:
resolved = setVal
elif setTyp in arrayConversion:
resolved = flatten_array(setVal)
elif setTyp in arrayConversionBase64:
resolved = flatten_array(setVal)
else:
for item in jsonConversion:
if setTyp.endswith(item):
return json.dumps(setVal)
else:
mylog('none', ['[Plugins] ERROR: Parameter not converted.'])
# Get SQL result
if param["type"] == "sql":
inputValue = db.get_sql_array(param["value"])
resolved = flatten_array(inputValue)
mylog('debug', f'[Plugins] Resolved value: {resolved}')
# Handle timeout multiplier if script executes multiple time
multiplyTimeout = False
if 'timeoutMultiplier' in param and param['timeoutMultiplier']:
multiplyTimeout = True
# Handle base64 encoding
encodeToBase64 = False
if 'base64' in param and param['base64']:
encodeToBase64 = True
mylog('debug', f'[Plugins] Convert to Base64: {encodeToBase64}')
if encodeToBase64:
resolved = str(base64.b64encode(resolved.encode('ascii')))
mylog('debug', f'[Plugins] base64 value: {resolved}')
self.resolved = resolved
self.inputValue = inputValue
self.base64 = encodeToBase64
self.name = param["name"]
self.type = param["type"]
self.value = param["value"]
self.paramValuesCount = paramValuesCount
self.multiplyTimeout = multiplyTimeout
#-------------------------------------------------------------------------------
def run_plugin_scripts(db, runType, pluginsState = None):
class plugins_state:
def __init__(self, processScan = False):
self.processScan = processScan
if pluginsState == None:
mylog('debug', ['[Plugins] pluginsState initialized '])
pluginsState = plugins_state()
#-------------------------------------------------------------------------------
def run_plugin_scripts(db, runType, pluginsState = plugins_state()):
# Header
updateState(db,"Run: Plugins")
@@ -73,12 +146,17 @@ def run_plugin_scripts(db, runType, pluginsState = None):
def execute_plugin(db, plugin, pluginsState = plugins_state() ):
sql = db.sql
if pluginsState is None:
mylog('debug', ['[Plugins] pluginsState is None'])
pluginsState = plugins_state()
# ------- necessary settings check --------
set = get_plugin_setting(plugin, "CMD")
# handle missing "function":"CMD" setting
if set == None:
return
return pluginsState
set_CMD = set["value"]
@@ -90,33 +168,31 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
else:
set_RUN_TIMEOUT = set["value"]
mylog('debug', ['[Plugins] Timeout: ', set_RUN_TIMEOUT])
# Prepare custom params
# Prepare custom params
params = []
if "params" in plugin:
for param in plugin["params"]:
resolved = ""
for param in plugin["params"]:
# Get setting value
if param["type"] == "setting":
resolved = get_setting(param["value"])
tempParam = plugin_param(param, plugin, db)
if resolved != None:
resolved = passable_string_from_setting(resolved)
# Get Sql result
if param["type"] == "sql":
resolved = flatten_array(db.get_sql_array(param["value"]))
if resolved == None:
mylog('none', [f'[Plugins] The parameter "name":"{param["name"]}" for "value": {param["value"]} was resolved as None'])
if tempParam.resolved == None:
mylog('none', [f'[Plugins] The parameter "name":"{tempParam.name}" for "value": {tempParam.value} was resolved as None'])
else:
params.append( [param["name"], resolved] )
# params.append( [param["name"], resolved] )
params.append( [tempParam.name, tempParam.resolved] )
if tempParam.multiplyTimeout:
set_RUN_TIMEOUT = set_RUN_TIMEOUT*tempParam.paramValuesCount
mylog('debug', [f'[Plugins] The parameter "name":"{param["name"]}" will multiply the timeout {tempParam.paramValuesCount} times. Total timeout: {set_RUN_TIMEOUT}s'])
mylog('debug', ['[Plugins] Timeout: ', set_RUN_TIMEOUT])
# build SQL query parameters to insert into the DB
sqlParams = []
@@ -241,7 +317,7 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
# handle missing "function":"DB_PATH" setting
if set == None:
mylog('none', ['[Plugins] Error: DB_PATH setting for plugin type sqlite-db-query missing.'])
return
return pluginsState
fullSqlitePath = set["value"]
@@ -281,7 +357,7 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
# check if the subprocess / SQL query failed / there was no valid output
if len(sqlParams) == 0:
mylog('none', ['[Plugins] No output received from the plugin ', plugin["unique_prefix"], ' - enable LOG_LEVEL=debug and check logs'])
return
return pluginsState
else:
mylog('verbose', ['[Plugins] SUCCESS, received ', len(sqlParams), ' entries'])
@@ -299,37 +375,6 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
#-------------------------------------------------------------------------------
# Flattens a setting to make it passable to a script
def passable_string_from_setting(globalSetting):
setVal = globalSetting[6] # setting value
setTyp = globalSetting[3] # setting type
noConversion = ['text', 'string', 'integer', 'boolean', 'password', 'readonly', 'integer.select', 'text.select', 'integer.checkbox' ]
arrayConversion = ['text.multiselect', 'list']
arrayConversionBase64 = ['subnets']
jsonConversion = ['.template']
mylog('debug', f'[Plugins] setTyp: {setTyp}')
if setTyp in noConversion:
return setVal
if setTyp in arrayConversion:
return flatten_array(setVal)
if setTyp in arrayConversionBase64:
return flatten_array(setVal, encodeBase64 = True)
for item in jsonConversion:
if setTyp.endswith(item):
return json.dumps(setVal)
mylog('none', ['[Plugins] ERROR: Parameter not converted.'])
#-------------------------------------------------------------------------------
# Check if watched values changed for the given plugin

View File

@@ -1,5 +1,4 @@
import os
import base64
import json
from logger import mylog
@@ -72,12 +71,12 @@ def get_plugin_string(props, el):
#-------------------------------------------------------------------------------
def flatten_array(arr, encodeBase64=False):
def flatten_array(arr):
tmp = ''
arrayItemStr = ''
mylog('debug', '[Plugins] Flattening the below array')
mylog('debug', f'[Plugins] Convert to Base64: {encodeBase64}')
mylog('debug', arr)
for arrayItem in arr:
@@ -93,12 +92,7 @@ def flatten_array(arr, encodeBase64=False):
tmp = tmp[:-1] # Remove last comma ','
mylog('debug', f'[Plugins] Flattened array: {tmp}')
if encodeBase64:
tmp = str(base64.b64encode(tmp.encode('ascii')))
mylog('debug', f'[Plugins] Flattened array (base64): {tmp}')
mylog('debug', f'[Plugins] Flattened array: {tmp}')
return tmp

View File

@@ -108,7 +108,7 @@ def construct_notifications(db, sqlQuery, tableTitle, skipText = False, supplied
def send_notifications (db):
sql = db.sql #TO-DO
global mail_text, mail_html, json_final, changedPorts_json_struc, partial_html, partial_txt, partial_json
global mail_text, mail_html, json_final, partial_html, partial_txt, partial_json
deviceUrl = conf.REPORT_DASHBOARD_URL + '/deviceDetails.php?mac='
plugins_report = False
@@ -234,24 +234,7 @@ def send_notifications (db):
mail_text = mail_text.replace ('<SECTION_EVENTS>', notiStruc.text + '\n')
mail_html = mail_html.replace ('<EVENTS_TABLE>', notiStruc.html)
mylog('verbose', ['[Notification] Events sections done.'])
if 'ports' in conf.INCLUDED_SECTIONS :
# collect "ports" for the webhook json
mylog('verbose', ['[Notification] Ports: conf.changedPorts_json_struc:', conf.changedPorts_json_struc])
if conf.changedPorts_json_struc is not None:
json_ports = conf.changedPorts_json_struc.json["data"]
notiStruc = construct_notifications(db, "", "Ports", True, conf.changedPorts_json_struc)
mail_html = mail_html.replace ('<PORTS_TABLE>', notiStruc.html)
portsTxt = ""
if conf.changedPorts_json_struc is not None:
portsTxt = "Ports \n---------\n Ports changed! Check PiAlert for details!\n"
mail_text = mail_text.replace ('<PORTS_TABLE>', portsTxt )
mylog('verbose', ['[Notification] Ports sections done.'])
mylog('verbose', ['[Notification] Events sections done.'])
if 'plugins' in conf.INCLUDED_SECTIONS:
# Compose Plugins Section
@@ -347,9 +330,7 @@ def send_notifications (db):
WHERE eve_PendingAlertEmail = 1""")
# clear plugin events
sql.execute ("DELETE FROM Plugins_Events")
conf.changedPorts_json_struc = None
sql.execute ("DELETE FROM Plugins_Events")
# DEBUG - print number of rows updated
mylog('minimal', ['[Notification] Notifications changes: ', sql.rowcount])
@@ -486,10 +467,13 @@ def skip_repeated_notifications (db):
#-------------------------------------------------------------------------------
def check_and_run_event(db, pluginsState):
mylog('debug', [f'[MAIN] processScan1: {pluginsState.processScan}'])
sql = db.sql # TO-DO
sql.execute(""" select * from Parameters where par_ID = "Front_Event" """)
rows = sql.fetchall()
mylog('debug', [f'[MAIN] processScan2: {pluginsState.processScan}'])
event, param = ['','']
if len(rows) > 0 and rows[0]['par_Value'] != 'finished':
keyValue = rows[0]['par_Value'].split('|')
@@ -498,7 +482,7 @@ def check_and_run_event(db, pluginsState):
event = keyValue[0]
param = keyValue[1]
else:
return
return pluginsState
if event == 'test':
handle_test(param)
@@ -511,6 +495,8 @@ def check_and_run_event(db, pluginsState):
# commit to DB
db.commitDB()
mylog('debug', [f'[MAIN] processScan3: {pluginsState.processScan}'])
return pluginsState
#-------------------------------------------------------------------------------

View File

@@ -1,211 +0,0 @@
import subprocess
import conf
from const import logPath, sql_nmap_scan_all
from helper import json_struc, timeNowTZ, updateState
from logger import append_line_to_file, mylog
#-------------------------------------------------------------------------------
class nmap_entry:
def __init__(self, mac, time, port, state, service, name = '', extra = '', index = 0):
self.mac = mac
self.time = time
self.port = port
self.state = state
self.service = service
self.name = name
self.extra = extra
self.index = index
self.hash = str(mac) + str(port)+ str(state)+ str(service)
#-------------------------------------------------------------------------------
def performNmapScan(db, devicesToScan):
"""
run nmap scan on a list of devices
discovers open ports and keeps track existing and new open ports
"""
if len(devicesToScan) > 0:
timeoutSec = conf.NMAP_TIMEOUT
devTotal = len(devicesToScan)
updateState(db,"Scan: Nmap")
mylog('verbose', ['[NMAP Scan] Scan: Nmap for max ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min) per device'])
mylog('verbose', ["[NMAP Scan] Estimated max delay: ", (devTotal * int(timeoutSec)), 's ', '(', round((devTotal * int(timeoutSec))/60,1) , 'min)' ])
devIndex = 0
for device in devicesToScan:
# Execute command
output = ""
# prepare arguments from user supplied ones
nmapArgs = ['nmap'] + conf.NMAP_ARGS.split() + [device["dev_LastIP"]]
progress = ' (' + str(devIndex+1) + '/' + str(devTotal) + ')'
try:
# try runnning a subprocess with a forced (timeout + 30 seconds) in case the subprocess hangs
output = subprocess.check_output (nmapArgs, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeoutSec + 30))
except subprocess.CalledProcessError as e:
# An error occured, handle it
mylog('none', ["[NMAP Scan] " ,e.output])
mylog('none', ["[NMAP Scan] Error - Nmap Scan - check logs", progress])
except subprocess.TimeoutExpired as timeErr:
mylog('verbose', ['[NMAP Scan] Nmap TIMEOUT - the process forcefully terminated as timeout reached for ', device["dev_LastIP"], progress])
if output == "": # check if the subprocess failed
mylog('minimal', ['[NMAP Scan] Nmap FAIL for ', device["dev_LastIP"], progress ,' check logs for details'])
else:
mylog('verbose', ['[NMAP Scan] Nmap SUCCESS for ', device["dev_LastIP"], progress])
devIndex += 1
# check the last run output
newLines = output.split('\n')
# regular logging
for line in newLines:
append_line_to_file (logPath + '/pialert_nmap.log', line +'\n')
# collect ports / new Nmap Entries
newEntriesTmp = []
index = 0
startCollecting = False
duration = ""
for line in newLines:
if 'Starting Nmap' in line:
if len(newLines) > index+1 and 'Note: Host seems down' in newLines[index+1]:
break # this entry is empty
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
startCollecting = True
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
startCollecting = False # end reached
elif startCollecting and len(line.split()) == 3:
newEntriesTmp.append(nmap_entry(device["dev_MAC"], timeNowTZ(), line.split()[0], line.split()[1], line.split()[2], device["dev_Name"]))
elif 'Nmap done' in line:
duration = line.split('scanned in ')[1]
index += 1
mylog('verbose', ['[NMAP Scan] Ports found by NMAP: ', len(newEntriesTmp)])
process_discovered_ports(db, device, newEntriesTmp)
#end for loop
def process_discovered_ports(db, device, discoveredPorts):
"""
process ports discovered by nmap
compare to previosu ports
update DB
raise notifications
"""
sql = db.sql # TO-DO
# previous Nmap Entries
oldEntries = []
changedPortsTmp = []
mylog('verbose', ['[NMAP Scan] Process ports found by NMAP: ', len(discoveredPorts)])
if len(discoveredPorts) > 0:
# get all current NMAP ports from the DB
rows = db.read(sql_nmap_scan_all)
for row in rows:
# only collect entries matching the current MAC address
if row["MAC"] == device["dev_MAC"]:
oldEntries.append(nmap_entry(row["MAC"], row["Time"], row["Port"], row["State"], row["Service"], device["dev_Name"], row["Extra"], row["Index"]))
newEntries = []
# Collect all entries that don't match the ones in the DB
for discoveredPort in discoveredPorts:
found = False
# Check the new entry is already available in oldEntries and remove from processing if yes
for oldEntry in oldEntries:
if discoveredPort.hash == oldEntry.hash:
found = True
if not found:
newEntries.append(discoveredPort)
mylog('verbose', ['[NMAP Scan] Nmap newly discovered or changed ports: ', len(newEntries)])
# collect new ports, find the corresponding old entry and return for notification purposes
# also update the DB with the new values after deleting the old ones
if len(newEntries) > 0:
# params to build the SQL query
params = []
indexesToDelete = ""
# Find old entry matching the new entry hash
for newEntry in newEntries:
foundEntry = None
for oldEntry in oldEntries:
if oldEntry.hash == newEntry.hash:
indexesToDelete = indexesToDelete + str(oldEntry.index) + ','
foundEntry = oldEntry
columnNames = ["Name", "MAC", "Port", "State", "Service", "Extra", "NewOrOld" ]
# Old entry found
if foundEntry is not None:
# Build params for sql query
params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, oldEntry.extra))
# Build JSON for API and notifications
changedPortsTmp.append({
"Name" : foundEntry.name,
"MAC" : newEntry.mac,
"Port" : newEntry.port,
"State" : newEntry.state,
"Service" : newEntry.service,
"Extra" : foundEntry.extra,
"NewOrOld" : "New values"
})
changedPortsTmp.append({
"Name" : foundEntry.name,
"MAC" : foundEntry.mac,
"Port" : foundEntry.port,
"State" : foundEntry.state,
"Service" : foundEntry.service,
"Extra" : foundEntry.extra,
"NewOrOld" : "Old values"
})
# New entry - no matching Old entry found
else:
# Build params for sql query
params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, ''))
# Build JSON for API and notifications
changedPortsTmp.append({
"Name" : "New device",
"MAC" : newEntry.mac,
"Port" : newEntry.port,
"State" : newEntry.state,
"Service" : newEntry.service,
"Extra" : "",
"NewOrOld" : "New device"
})
conf.changedPorts_json_struc = json_struc({ "data" : changedPortsTmp}, columnNames)
# Delete old entries if available
if len(indexesToDelete) > 0:
sql.execute ("DELETE FROM Nmap_Scan where \"Index\" in (" + indexesToDelete[:-1] +")")
db.commitDB()
# Insert new values into the DB
sql.executemany ("""INSERT INTO Nmap_Scan ("MAC", "Time", "Port", "State", "Service", "Extra") VALUES (?, ?, ?, ?, ?, ?)""", params)
db.commitDB()