From 12b89f7e2419b9b8cf0e12763596cc819e993d8e Mon Sep 17 00:00:00 2001 From: Jokob-sk Date: Mon, 18 Sep 2023 14:29:44 +1000 Subject: [PATCH] vendor_update plugin --- front/php/templates/language/en_us.json | 2 +- front/plugins/vendor_update/config.json | 729 ++++++++++++++---------- front/plugins/vendor_update/script.py | 55 +- pialert/const.py | 5 +- pialert/device.py | 58 +- 5 files changed, 502 insertions(+), 347 deletions(-) diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json index d346b3617..57258190e 100755 --- a/front/php/templates/language/en_us.json +++ b/front/php/templates/language/en_us.json @@ -458,7 +458,7 @@ "Plugins_Out_of" : "out of", "Plugins_no_control" : "No form control was found to render this value.", "Settings_Metadata_Toggle" : "Show/hide metadata for the given setting.", - "settings_missing" : "Not all settings loaded, refresh the page! This is probably caused by a high load on the database.", + "settings_missing" : "Not all settings loaded, refresh the page! This is probably caused by a high load on the database or app startup sequence.", "settings_missing_block" : "You can not save your settings without specifying all setting keys. Refresh the page. This is probably caused by a high load on the database.", "settings_old" : "Importing settings and re-initializing...", "settings_saved" : "
Settings saved to the pialert.conf file.

A time-stamped backup of the previous file created.

Reloading...
", diff --git a/front/plugins/vendor_update/config.json b/front/plugins/vendor_update/config.json index 096eff434..e04eb87e2 100755 --- a/front/plugins/vendor_update/config.json +++ b/front/plugins/vendor_update/config.json @@ -2,10 +2,13 @@ "code_name": "vendor_update", "unique_prefix": "VNDRPDT", "enabled": true, - "data_source": "script", + "data_source": "script", "show_ui": true, - "localized": ["display_name", "description", "icon"], - + "localized": [ + "display_name", + "description", + "icon" + ], "display_name": [ { "language_code": "en_us", @@ -22,41 +25,54 @@ { "language_code": "en_us", "string": "A plugin to schedule vendor database updates for mac based vendor resolution." - } + } ], - "params" : [ - ], - + "params": [], "settings": [ { - "function": "RUN", - "type": "text.select", - "default_value":"schedule", - "options": ["disabled", "once", "schedule", "always_after_scan"], - "localized": ["name", "description"], - "name" :[{ - "language_code":"en_us", - "string" : "When to run" - }, - { - "language_code":"es_es", - "string" : "Cuándo ejecutar" - }, - { - "language_code":"de_de", - "string" : "Wann laufen" - }], - "description": [{ - "language_code":"en_us", - "string" : "When the cleanup should be performed. An overnight weekly SCHEDULE is recommended." - }] - }, + "function": "RUN", + "type": "text.select", + "default_value": "schedule", + "options": [ + "disabled", + "once", + "schedule", + "always_after_scan" + ], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "When to run" + }, + { + "language_code": "es_es", + "string": "Cuándo ejecutar" + }, + { + "language_code": "de_de", + "string": "Wann laufen" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "When the cleanup should be performed. An overnight weekly SCHEDULE is recommended." + } + ] + }, { "function": "CMD", "type": "readonly", "default_value": "python3 /home/pi/pialert/front/plugins/vendor_update/script.py", "options": [], - "localized": ["name", "description"], + "localized": [ + "name", + "description" + ], "name": [ { "language_code": "en_us", @@ -69,7 +85,7 @@ { "language_code": "de_de", "string": "Befehl" - } + } ], "description": [ { @@ -83,46 +99,56 @@ { "language_code": "de_de", "string": "Befehl zum Ausführen. Dies kann nicht geändert werden" - } + } ] }, { "function": "RUN_SCHD", "type": "text", - "default_value":"0 4 * * *", + "default_value": "0 4 * * *", "options": [], - "localized": ["name", "description"], - "name" : [{ - "language_code":"en_us", - "string" : "Schedule" - }, - { - "language_code":"es_es", - "string" : "Schedule" - }, - { - "language_code":"de_de", - "string" : "Schedule" - }], - "description": [{ - "language_code":"en_us", - "string" : "Only enabled if you select schedule in the VNDRPDT_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." - }, - { - "language_code":"es_es", - "string" : "Solo está habilitado si selecciona schedule en la configuración VNDRPDT_RUN. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en crontab.guru). Por ejemplo, ingresar 0 4 * * * ejecutará el escaneo después de las 4 a.m. en el TIMEZONE que configuró arriba. Se ejecutará la PRÓXIMA vez que pase el tiempo." - }, - { - "language_code":"de_de", - "string" : "Nur aktiviert, wenn Sie schedule in der VNDRPDT_RUN-Einstellung auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter crontab.guru). Wenn Sie beispielsweise 0 4 * * * eingeben, wird der Scan nach 4 Uhr morgens in der TIMEZONE den Sie oben festgelegt haben. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht." - }] - }, + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "Schedule" + }, + { + "language_code": "es_es", + "string": "Schedule" + }, + { + "language_code": "de_de", + "string": "Schedule" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Only enabled if you select schedule in the VNDRPDT_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." + }, + { + "language_code": "es_es", + "string": "Solo está habilitado si selecciona schedule en la configuración VNDRPDT_RUN. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en crontab.guru). Por ejemplo, ingresar 0 4 * * * ejecutará el escaneo después de las 4 a.m. en el TIMEZONE que configuró arriba. Se ejecutará la PRÓXIMA vez que pase el tiempo." + }, + { + "language_code": "de_de", + "string": "Nur aktiviert, wenn Sie schedule in der VNDRPDT_RUN-Einstellung auswählen. Stellen Sie sicher, dass Sie den Zeitplan im richtigen Cron-ähnlichen Format eingeben (z. B. validieren unter crontab.guru). Wenn Sie beispielsweise 0 4 * * * eingeben, wird der Scan nach 4 Uhr morgens in der TIMEZONE den Sie oben festgelegt haben. Wird das NÄCHSTE Mal ausgeführt, wenn die Zeit vergeht." + } + ] + }, { "function": "RUN_TIMEOUT", "type": "integer", - "default_value": 300, + "default_value": 600, "options": [], - "localized": ["name", "description"], + "localized": [ + "name", + "description" + ], "name": [ { "language_code": "en_us", @@ -135,7 +161,7 @@ { "language_code": "de_de", "string": "Zeitüberschreitung" - } + } ], "description": [ { @@ -149,260 +175,389 @@ { "language_code": "de_de", "string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen." - } + } + ] + }, + { + "function": "WATCH", + "type": "text.multiselect", + "default_value": [ + "Watched_Value1" + ], + "options": [ + "Watched_Value1", + "Watched_Value2", + "Watched_Value3", + "Watched_Value4" + ], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "Watched" + }, + { + "language_code": "es_es", + "string": "Visto" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is response status code (e.g.: 200, 404)
  • Watched_Value2 is Latency (not recommended)
  • Watched_Value3 unused
  • Watched_Value4 unused
" + }, + { + "language_code": "es_es", + "string": "Envíe una notificación si los valores seleccionados cambian. Use CTRL + Click para seleccionar/deseleccionar.
  • Watched_Value1 es un código de estado de respuesta (por ejemplo: 200, 404)
  • Valor_observado2 es Latencia (no recomendado)
  • Valor_observado3 no utilizado
  • Valor_observado4 sin usar
" + } + ] + }, + { + "function": "REPORT_ON", + "type": "text.multiselect", + "default_value": [ + "new", + "watched-changed" + ], + "options": [ + "new", + "watched-changed", + "watched-not-changed", + "missing-in-last-scan" + ], + "localized": [ + "name", + "description" + ], + "name": [ + { + "language_code": "en_us", + "string": "Report on" + }, + { + "language_code": "es_es", + "string": "Informar sobre" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + }, + { + "language_code": "es_es", + "string": "Envíe una notificación solo en estos estados. new significa que se descubrió un nuevo objeto único (combinación única de PrimaryId y SecondaryId). watched-changed significa que seleccionó Watched_ValueN Las columnas cambiaron." + } ] } ], - - "database_column_definitions": - [ + "database_column_definitions": [ { - "column": "Index", + "column": "Index", "css_classes": "col-sm-2", "show": false, - "type": "label", - "default_value":"", + "type": "label", + "default_value": "", "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "N/A" - }, - { - "language_code":"es_es", - "string" : "N/A" - }] - } , + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + }, + { + "language_code": "es_es", + "string": "N/A" + } + ] + }, { - "column": "Plugin", - "css_classes": "col-sm-2", - "show": false, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "N/A" - }, - { - "language_code":"es_es", - "string" : "N/A" - }] + "column": "Plugin", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + }, + { + "language_code": "es_es", + "string": "N/A" + } + ] }, { - "column": "Object_PrimaryID", - "mapped_to_column": "cur_MAC", - "css_classes": "col-sm-2", - "show": true, - "type": "device_mac", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "MAC address" - }, - { - "language_code":"es_es", - "string" : "Dirección MAC" - }] + "column": "Object_PrimaryID", + "mapped_to_column": "cur_MAC", + "css_classes": "col-sm-2", + "show": true, + "type": "device_mac", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "MAC address" + }, + { + "language_code": "es_es", + "string": "Dirección MAC" + } + ] }, { - "column": "Object_SecondaryID", - "mapped_to_column": "cur_IP", - "css_classes": "col-sm-2", - "show": true, - "type": "device_ip", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "IP" - }, - { - "language_code":"es_es", - "string" : "IP" - }] - } , + "column": "Object_SecondaryID", + "mapped_to_column": "cur_IP", + "css_classes": "col-sm-2", + "show": true, + "type": "device_ip", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "IP" + }, + { + "language_code": "es_es", + "string": "IP" + } + ] + }, { - "column": "DateTimeCreated", - "css_classes": "col-sm-2", - "show": true, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Created" - }, - { - "language_code":"es_es", - "string" : "Creado" - }] + "column": "DateTimeCreated", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Created" + }, + { + "language_code": "es_es", + "string": "Creado" + } + ] }, { - "column": "DateTimeChanged", - "mapped_to_column": "cur_DateTime", - "css_classes": "col-sm-2", - "show": true, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Changed" - }, - { - "language_code":"es_es", - "string" : "Cambiado" - }] + "column": "DateTimeChanged", + "mapped_to_column": "cur_DateTime", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Changed" + }, + { + "language_code": "es_es", + "string": "Cambiado" + } + ] }, { "column": "Dummy", - "mapped_to_column": "cur_ScanMethod", + "mapped_to_column": "cur_ScanMethod", "mapped_to_column_data": { - "value": "VNDRPDT" - }, + "value": "VNDRPDT" + }, "css_classes": "col-sm-2", "show": true, - "type": "label", - "default_value":"", + "type": "label", + "default_value": "", "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Scan method" - }, - { - "language_code":"es_es", - "string" : "Método de escaneo" - }] - } , + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Scan method" + }, + { + "language_code": "es_es", + "string": "Método de escaneo" + } + ] + }, { - "column": "Watched_Value1", - "mapped_to_column": "cur_Vendor", - "css_classes": "col-sm-2", - "show": true, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Vendor" - }] + "column": "Watched_Value1", + "mapped_to_column": "cur_Vendor", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Vendor" + } + ] }, { - "column": "Watched_Value2", - "mapped_to_column": "cur_Name", - "css_classes": "col-sm-2", - "show": true, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Hostname" - }, - { - "language_code":"es_es", - "string" : "Nombre de host" - }] + "column": "Watched_Value2", + "mapped_to_column": "cur_Name", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Hostname" + }, + { + "language_code": "es_es", + "string": "Nombre de host" + } + ] }, { - "column": "Watched_Value3", - "css_classes": "col-sm-2", - "show": false, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "N/A" - }] - } , + "column": "Watched_Value3", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + } + ] + }, { - "column": "Watched_Value4", - "css_classes": "col-sm-2", - "show": false, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "N/A" - }] - } , + "column": "Watched_Value4", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + } + ] + }, { - "column": "UserData", - "css_classes": "col-sm-2", - "show": false, - "type": "textbox_save", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Comments" - }, - { - "language_code":"es_es", - "string" : "Comentarios" - }] - }, + "column": "UserData", + "css_classes": "col-sm-2", + "show": false, + "type": "textbox_save", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Comments" + }, + { + "language_code": "es_es", + "string": "Comentarios" + } + ] + }, { - "column": "Extra", - "css_classes": "col-sm-3", - "show": false, - "type": "label", - "default_value":"", - "options": [], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "N/A" - }] - }, + "column": "Extra", + "css_classes": "col-sm-3", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + } + ] + }, { - "column": "Status", - "css_classes": "col-sm-1", - "show": true, - "type": "replace", - "default_value":"", - "options": [ - { - "equals": "watched-not-changed", - "replacement": "
" - }, - { - "equals": "watched-changed", - "replacement": "
" - }, - { - "equals": "new", - "replacement": "
" - }, - { - "equals": "missing-in-last-scan", - "replacement": "
" - } - ], - "localized": ["name"], - "name":[{ - "language_code":"en_us", - "string" : "Status" - }, - { - "language_code":"es_es", - "string" : "Estado" - }] - } + "column": "Status", + "css_classes": "col-sm-1", + "show": true, + "type": "replace", + "default_value": "", + "options": [ + { + "equals": "watched-not-changed", + "replacement": "
" + }, + { + "equals": "watched-changed", + "replacement": "
" + }, + { + "equals": "new", + "replacement": "
" + }, + { + "equals": "missing-in-last-scan", + "replacement": "
" + } + ], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Status" + }, + { + "language_code": "es_es", + "string": "Estado" + } + ] + } ] -} +} \ No newline at end of file diff --git a/front/plugins/vendor_update/script.py b/front/plugins/vendor_update/script.py index 2b08f0997..90325cbea 100755 --- a/front/plugins/vendor_update/script.py +++ b/front/plugins/vendor_update/script.py @@ -32,7 +32,7 @@ def main(): mylog('verbose', ['[VNDRPDT] In script']) # Get newest DB - update_vendor_database() + # update_vendor_database() TODOz # Resolve missing vendors plugin_objects = Plugin_Objects(RESULT_FILE) @@ -63,52 +63,65 @@ def update_vendor_database(): mylog('none', [' FAILED: Updating vendors DB, set LOG_LEVEL=debug for more info']) mylog('none', [e.output]) - +# ------------------------------------------------------------------------------ # resolve missing vendors def update_vendors (dbPath, plugin_objects): # Connect to the PiAlert SQLite database - conn = sqlite3.connect(dbPath) - cursor = conn.cursor() + conn = sqlite3.connect(dbPath) + sql = conn.cursor() # Initialize variables recordsToUpdate = [] ignored = 0 notFound = 0 - # All devices loop - mylog('verbose', [' Searching devices vendor']) + + mylog('verbose', [' Searching devices vendor']) + + # Get devices without a vendor + sql.execute ("""SELECT + dev_MAC, + dev_LastIP, + dev_Name, + dev_Vendor + FROM Devices + WHERE dev_Vendor = '(unknown)' + OR dev_Vendor = '' + OR dev_Vendor IS NULL + """) + devices = sql.fetchall() + conn.commit() - for device in cursor.execute ("""SELECT * FROM Devices - WHERE dev_Vendor = '(unknown)' - OR dev_Vendor ='' - OR dev_Vendor IS NULL""") : + # Close the database connection + conn.close() + + # All devices loop + for device in devices: # Search vendor in HW Vendors DB - vendor = query_MAC_vendor (device['dev_MAC']) + vendor = query_MAC_vendor (device[0]) if vendor == -1 : notFound += 1 elif vendor == -2 : ignored += 1 else : plugin_objects.add_object( - primaryId = device['dev_MAC'], # MAC (Device Name) - secondaryId = device['dev_LastIP'], # IP Address (always 0.0.0.0) + primaryId = device[0], # MAC (Device Name) + secondaryId = device[1], # IP Address (always 0.0.0.0) watched1 = vendor, - watched2 = device['dev_Name'], + watched2 = device[2], watched3 = "", watched4 = "", extra = "", - foreignKey = device['dev_MAC'] + foreignKey = device[0] ) # Print log - mylog('verbose', [" Devices Ignored: ", ignored]) - mylog('verbose', [" Vendors Not Found:", notFound]) - mylog('verbose', [" Vendors updated: ", len(plugin_objects) ]) + mylog('verbose', [" Devices Ignored : ", ignored]) + mylog('verbose', [" Devices with missing vendor : ", len(devices)]) + mylog('verbose', [" Vendors Not Found : ", notFound]) + mylog('verbose', [" Vendors updated : ", len(plugin_objects) ]) - conn.commit() - # Close the database connection - conn.close() return plugin_objects diff --git a/pialert/const.py b/pialert/const.py index 932b6e144..98caee860 100755 --- a/pialert/const.py +++ b/pialert/const.py @@ -15,10 +15,7 @@ apiPath = pialertPath + '/front/api/' fullConfPath = pialertPath + confPath fullDbPath = pialertPath + dbPath - - - -vendorsDB = '/usr/share/arp-scan/ieee-oui.txt' +vendorsPath = '/usr/share/arp-scan/ieee-oui.txt' diff --git a/pialert/device.py b/pialert/device.py index 16c872b9c..41959bb77 100755 --- a/pialert/device.py +++ b/pialert/device.py @@ -6,7 +6,7 @@ from helper import timeNowTZ, get_setting, get_setting_value,resolve_device_name_dig, resolve_device_name_pholus from scanners.internet import check_IP_format, get_internet_IP from logger import mylog, print_log -from const import vendorsDB +from const import vendorsPath #------------------------------------------------------------------------------- @@ -319,37 +319,27 @@ def check_mac_or_internet(input_str): #------------------------------------------------------------------------------- def query_MAC_vendor (pMAC): - try : - # BUGFIX #6 - Fix pMAC parameter as numbers - pMACstr = str(pMAC) - - # Check MAC parameter - mac = pMACstr.replace (':','') - if len(pMACstr) != 17 or len(mac) != 12 : - return -2 - - # Search vendor in HW Vendors DB - mac = mac[0:6] - grep_args = ['grep', '-i', mac, vendorsDB] - - # Execute command - if conf.LOG_LEVEL == 'debug': - # try runnning a subprocess - grep_output = subprocess.check_output (grep_args) - else: - try: - # try runnning a subprocess - grep_output = subprocess.check_output (grep_args) - except subprocess.CalledProcessError as e: - # An error occured, handle it - mylog('none', ["[Mac Vendor Check] Error: ", e.output]) - grep_output = " There was an error, check logs for details" - - # Return Vendor - vendor = grep_output[7:] - vendor = vendor.rstrip() - return vendor - - # not Found - except subprocess.CalledProcessError : + + pMACstr = str(pMAC) + + # Check MAC parameter + mac = pMACstr.replace (':','') + if len(pMACstr) != 17 or len(mac) != 12 : + return -2 # return -2 if ignored MAC + + # Search vendor in HW Vendors DB + mac_start_string = mac[0:6] + + try: + with open(vendorsPath, 'r') as f: + for line in f: + if line.startswith(mac_start_string): + vendor = line.split(' ', 1)[1].strip() + mylog('debug', [f"[Vendor Check] Found '{vendor}' for '{pMAC}'"]) + return vendor + + return -1 # MAC address not found in the database + except FileNotFoundError: + mylog('none', [f"[Vendor Check] Error: Vendors file {vendorsPath} not found."]) return -1 +