UNPKG

iobroker.kisshome-defender

Version:
938 lines 74.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KISSHomeResearchAdapter = void 0; const adapter_core_1 = require("@iobroker/adapter-core"); const node_path_1 = require("node:path"); const node_fs_1 = require("node:fs"); const axios_1 = __importDefault(require("axios")); const node_crypto_1 = require("node:crypto"); const node_schedule_1 = __importDefault(require("node-schedule")); const node_os_1 = require("node:os"); const DockerManager_1 = __importDefault(require("./lib/DockerManager")); const utils_1 = require("./lib/utils"); const recording_1 = require("./lib/recording"); const fritzbox_1 = require("./lib/fritzbox"); const CloudSync_1 = __importStar(require("./lib/CloudSync")); const IDSCommunication_1 = require("./lib/IDSCommunication"); const Statistics_1 = __importDefault(require("./lib/Statistics")); const node_child_process_1 = require("node:child_process"); // save files every 60 minutes const SAVE_DATA_EVERY_MS = 3600000; // save files if bigger than 50 Mb const SAVE_DATA_IF_BIGGER = 50 * 1024 * 1024; function secondsToMs(seconds) { const m = Math.floor(seconds / 60); const s = seconds % 60; const sDisplay = s.toString().padStart(2, '0'); return `${m}:${sDisplay} ${adapter_core_1.I18n.translate('minutes')}`; } class KISSHomeResearchAdapter extends adapter_core_1.Adapter { constructor(options = {}) { super({ ...options, name: 'kisshome-defender', useFormatDate: true, }); this.tempDir = ''; this.uniqueMacs = []; this.sid = ''; this.emailAlarmText = ''; this.sidCreated = 0; this.nextSave = 0; this.group = 'A'; this.visProject = null; this.context = { terminate: false, controller: null, first: false, filtered: { packets: [], totalBytes: 0, totalPackets: 0, buffer: Buffer.from([]), }, full: { packets: [], totalBytes: 0, totalPackets: 0, buffer: Buffer.from([]), }, modifiedMagic: false, libpCapFormat: false, networkType: 1, started: 0, lastSaved: 0, }; this.recordingRunning = false; this.workingCloudDir = ''; this.workingIdsDir = ''; this.lastDebug = 0; this.uuid = ''; this.iotInstance = ''; this.recordingEnabled = false; this.IPs = []; this.cloudSync = null; this.idsCommunication = null; this.statistics = null; this.questionnaireTimer = null; this.dailyReportSchedule = null; this.secondPartSchedule = null; this.generateEvent = async (scanUUID, message, subject) => { // admin await this.registerNotification('kisshome-defender', 'alert', message); if (!this.config.emailDisabled) { // email try { await axios_1.default.post(`https://${CloudSync_1.PCAP_HOST}/api/v2/sendEmail/${encodeURIComponent(this.config.email)}?uuid=${encodeURIComponent(this.uuid)}`, { subject, text: this.generateEmail(message, subject), }); } catch (e) { this.log.error(`${adapter_core_1.I18n.translate('Cannot send email')}: ${e}`); return; } } // iobroker.iot // find iobroker.iot instance this.iotInstance = await this.getAliveIotInstance(); if (this.iotInstance) { const data = await this.findVisProject(); void this.setForeignStateAsync(`${this.iotInstance}.app.message`, JSON.stringify({ message, title: subject, expire: 3600, priority: 'high', payload: { openUrl: `https://iobroker.pro/vis-2/?${data.project || 'main'}#${data.view || 'kisshome'}/${data.widget}/${scanUUID}`, }, })); } }; const pack = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '..', 'package.json'), 'utf8')); this.versionPack = pack.version.replace(/\./g, '-'); this.on('ready', () => this.onReady()); this.on('unload', callback => this.onUnload(callback)); this.on('message', this.onMessage.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); } async onMessage(msg) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2; if (typeof msg === 'object') { switch (msg.command) { case 'getDockerVolume': if (msg.callback) { this.sendTo(msg.from, msg.command, (_a = this.idsCommunication) === null || _a === void 0 ? void 0 : _a.getDockerVolumePath(), msg.callback); } break; case 'getDefaultGateway': if (msg.callback && msg.message) { if (msg.message.value !== '0.0.0.0') { this.sendTo(msg.from, msg.command, msg.message.value, msg.callback); } else { try { const ip = await (0, utils_1.getDefaultGateway)(); this.sendTo(msg.from, msg.command, ip, msg.callback); } catch (e) { this.sendTo(msg.from, msg.command, { error: e.message }, msg.callback); } } } break; case 'getUsers': { if (msg.callback) { try { if (((_b = msg.message) === null || _b === void 0 ? void 0 : _b.ip) || this.config.fritzbox) { const users = await (0, fritzbox_1.getFritzBoxUsers)(((_c = msg.message) === null || _c === void 0 ? void 0 : _c.ip) || this.config.fritzbox); this.sendTo(msg.from, msg.command, users, msg.callback); } else { this.sendTo(msg.from, msg.command, [], msg.callback); } } catch (e) { this.sendTo(msg.from, msg.command, { error: e.message }, msg.callback); } } break; } case 'getFilter': { if (msg.callback) { try { if (((_d = msg.message) === null || _d === void 0 ? void 0 : _d.ip) || (this.config.fritzbox && ((_e = msg.message) === null || _e === void 0 ? void 0 : _e.login)) || (this.config.login && ((_f = msg.message) === null || _f === void 0 ? void 0 : _f.password)) || this.config.password) { const filter = await (0, fritzbox_1.getFritzBoxFilter)(((_g = msg.message) === null || _g === void 0 ? void 0 : _g.ip) || this.config.fritzbox, ((_h = msg.message) === null || _h === void 0 ? void 0 : _h.login) || this.config.login, ((_j = msg.message) === null || _j === void 0 ? void 0 : _j.password) || this.config.password); this.sendTo(msg.from, msg.command, { text: filter ? adapter_core_1.I18n.translate('Fritz!Box supports Filter-Feature') : adapter_core_1.I18n.translate('Fritz!Box does not support Filter-Feature'), style: { color: filter ? 'green' : 'red', }, }, msg.callback); } else { this.sendTo(msg.from, msg.command, false, msg.callback); } } catch (e) { this.sendTo(msg.from, msg.command, { error: e.message }, msg.callback); } } break; } case 'getModelStatus': { this.sendTo(msg.from, msg.command, { modelStatus: ((_k = this.idsCommunication) === null || _k === void 0 ? void 0 : _k.getModelStatus()) || {} }, msg.callback); break; } case 'getInterfaces': { if (msg.callback) { try { if (((_l = msg.message) === null || _l === void 0 ? void 0 : _l.ip) || (this.config.fritzbox && ((_m = msg.message) === null || _m === void 0 ? void 0 : _m.login)) || (this.config.login && ((_o = msg.message) === null || _o === void 0 ? void 0 : _o.password)) || this.config.password) { const ifaces = await (0, fritzbox_1.getFritzBoxInterfaces)(((_p = msg.message) === null || _p === void 0 ? void 0 : _p.ip) || this.config.fritzbox, (_q = msg.message) === null || _q === void 0 ? void 0 : _q.login, (_r = msg.message) === null || _r === void 0 ? void 0 : _r.password, ((_s = msg.message) === null || _s === void 0 ? void 0 : _s.login) === this.config.login && ((_t = msg.message) === null || _t === void 0 ? void 0 : _t.password) === this.config.password ? this.sid : undefined); const lan1 = ifaces === null || ifaces === void 0 ? void 0 : ifaces.find(i => i.label === '1-lan'); if (lan1) { lan1.label += ' (default)'; } const index = ifaces === null || ifaces === void 0 ? void 0 : ifaces.findIndex(it => it === lan1); // place lan1 on the first position if (ifaces && index && index !== -1) { ifaces.splice(0, 0, ifaces.splice(index, 1)[0]); } this.sendTo(msg.from, msg.command, ifaces, msg.callback); } else { this.sendTo(msg.from, msg.command, [], msg.callback); } } catch (e) { this.sendTo(msg.from, msg.command, { error: e.message }, msg.callback); } } break; } case 'getMacForIps': if (msg.callback && msg.message) { try { const devices = msg.message; const result = await this.getMacForIps(devices); this.sendTo(msg.from, msg.command, { result }, msg.callback); } catch (e) { this.sendTo(msg.from, msg.command, { error: e.message }, msg.callback); } } break; case 'getTotals': { if (msg.callback) { this.sendTo(msg.from, msg.command, (_u = this.statistics) === null || _u === void 0 ? void 0 : _u.getTotals(), msg.callback); } break; } case 'getData': { if (msg.callback && msg.message) { const requestType = msg.message.type || 'allStatistics'; if (requestType === 'dataVolumePerDay') { this.sendTo(msg.from, msg.command, (_v = this.statistics) === null || _v === void 0 ? void 0 : _v.getDataVolumePerDay(), msg.callback); } else if (requestType === 'dataVolumePerDevice') { this.sendTo(msg.from, msg.command, (_w = this.statistics) === null || _w === void 0 ? void 0 : _w.getDataVolumePerDevice(), msg.callback); } else if (requestType === 'dataVolumePerCountry') { this.sendTo(msg.from, msg.command, (_x = this.statistics) === null || _x === void 0 ? void 0 : _x.getDataVolumePerCountry(), msg.callback); } else if (requestType === 'dataVolumePerDaytime') { this.sendTo(msg.from, msg.command, (_y = this.statistics) === null || _y === void 0 ? void 0 : _y.getDataVolumePerDaytime(), msg.callback); } else { const results = (_z = this.statistics) === null || _z === void 0 ? void 0 : _z.getAllStatistics(); const result = { analysisDurationMs: 0, totalBytes: 0, packets: 0, results: results || [], countries: {}, names: {}, }; // aggregate results for (let r = 0; r < result.results.length; r++) { result.analysisDurationMs += result.results[r].statistics.analysisDurationMs; result.totalBytes += result.results[r].statistics.totalBytes; result.packets += result.results[r].statistics.packets; result.results[r].statistics.devices.forEach(device => { const ips = Object.keys(device.external_ips); ips.forEach(ip => { var _a; const country = device.external_ips[ip].country; (_a = result.countries)[country] || (_a[country] = 0); result.countries[country] += device.external_ips[ip].data_volume_bytes; }); }); } this.IPs.forEach(item => { var _a; if (item.mac) { result.names[item.mac.toLowerCase()] = { ip: item.ip || '', desc: item.desc || '', vendor: ((_a = KISSHomeResearchAdapter.macCache[item.ip]) === null || _a === void 0 ? void 0 : _a.vendor) || '', }; } }); if (process.env.TEST) { // Add test data const testData = { '00:06:78:A6:8F:F0': { ip: '192.168.188.113', desc: 'denon', }, '12:72:74:40:F2:D0': { ip: '192.168.188.119', desc: 'upnp', }, '0A:B4:FE:A0:2F:1A': { ip: '192.168.188.122', desc: 'upnp', }, 'B0:B2:1C:18:CB:7C': { ip: '192.168.188.126', desc: 'shelly', }, '24:A1:60:20:85:08': { ip: '192.168.188.131', desc: 'shelly', }, '3C:61:05:DC:AD:24': { ip: '192.168.188.133', desc: 'shelly', }, '8C:98:06:07:AA:80': { ip: '192.168.188.156', desc: 'upnp', }, 'D8:BB:C1:0A:1C:89': { ip: '192.168.188.157', desc: 'shelly', }, '8C:98:06:08:61:3D': { ip: '192.168.188.158', desc: 'upnp', }, 'B0:B2:1C:18:F4:A8': { ip: '192.168.188.168', desc: 'shelly', }, 'E0:98:06:B5:7B:65': { ip: '192.168.188.29', desc: 'shelly', }, '8C:CE:4E:E1:8E:F9': { ip: '192.168.188.31', desc: 'shelly', }, '00:17:88:4B:A3:FC': { ip: '192.168.188.32', desc: 'hue', }, '22:A6:2F:E7:25:3B': { ip: '192.168.188.35', desc: 'upnp', }, '40:F5:20:01:A5:99': { ip: '192.168.188.36', desc: 'shelly', }, 'E0:98:06:B4:B5:8C': { ip: '192.168.188.39', desc: 'shelly', }, 'E0:98:06:B5:22:8B': { ip: '192.168.188.41', desc: 'shelly', }, '22:A6:2F:4A:82:CB': { ip: '192.168.188.43', desc: 'upnp', }, '00:04:20:FC:3A:C7': { ip: '192.168.188.49', desc: 'upnp', }, '34:94:54:7A:EB:E4': { ip: '192.168.188.51', desc: 'shelly', }, '70:2A:D5:CD:77:03': { ip: '192.168.188.54', desc: 'upnp', }, '80:C7:55:7B:86:C0': { ip: '192.168.188.56', desc: 'upnp', }, '00:11:32:B2:A0:50': { ip: '192.168.188.66', desc: 'synology', }, '44:17:93:CE:4B:50': { ip: '192.168.188.70', desc: 'shelly', }, 'DC:A6:32:93:B7:AF': { ip: '192.168.188.90', desc: 'hm-rpc', }, '64:1C:AE:46:50:F3': { ip: '192.168.188.92', desc: 'upnp', }, '00:07:E9:13:37:46': { ip: '192.168.178.2', desc: 'qemu', }, }; for (const mac in testData) { if (testData[mac] && !result.names[mac.toLowerCase()]) { result.names[mac.toLowerCase()] = { ip: testData[mac].ip, desc: testData[mac].desc, vendor: ((_0 = KISSHomeResearchAdapter.macCache[testData[mac].ip]) === null || _0 === void 0 ? void 0 : _0.vendor) || '', }; } } } result.analysisDurationMs = Math.floor(result.analysisDurationMs); this.sendTo(msg.from, msg.command, result, msg.callback); } } break; } case 'reportUxEvents': { if (msg.message) { // Save UX events to the file (_1 = this.cloudSync) === null || _1 === void 0 ? void 0 : _1.reportUxEvents(msg.message); } break; } case 'questionnaireAnswer': { // Send the questionnaire answer to the server if (msg.message && typeof msg.message === 'object' && this.config.email) { try { const response = await axios_1.default.post(`https://${CloudSync_1.PCAP_HOST}/api/v2/questionnaire?email=${encodeURIComponent(this.config.email)}&uuid=${encodeURIComponent(this.uuid)}`, msg.message); if (response.status === 200 || response.status === 201) { if (msg.callback) { this.sendTo(msg.from, msg.command, { result: 'ok' }, msg.callback); } this.log.info(`${adapter_core_1.I18n.translate('Questionnaire answer sent successfully')}`); } else { this.log.error(`${adapter_core_1.I18n.translate('Failed to send questionnaire answer')}: ${response.statusText}`); if (msg.callback) { this.sendTo(msg.from, msg.command, { error: adapter_core_1.I18n.translate('Cannot send answer') }, msg.callback); } } } catch (e) { this.log.error(`${adapter_core_1.I18n.translate('Error sending questionnaire answer')}: ${e}`); if (msg.callback) { this.sendTo(msg.from, msg.command, { error: adapter_core_1.I18n.translate('Cannot send answer') }, msg.callback); } } // If the state has the same ID => delete it const state = await this.getStateAsync('info.cloudSync.questionnaire'); if (state === null || state === void 0 ? void 0 : state.val) { const questionnaire = JSON.parse(state.val); if (questionnaire.id === msg.message.id) { // Clear questionnaire await this.setStateAsync('info.cloudSync.questionnaire', JSON.stringify({ id: questionnaire.id, done: true }), true); } } } else { // Cannot happen, but just in case this.log.warn(adapter_core_1.I18n.translate('No email provided for questionnaire answer or empty message')); if (msg.callback) { this.sendTo(msg.from, msg.command, { error: adapter_core_1.I18n.translate('Invalid answer') }, msg.callback); } } break; } case 'questionnaireCancel': { // Send the questionnaire answer to the server if (msg.message && typeof msg.message === 'object') { // If the state has the same ID => delete it const state = await this.getStateAsync('info.cloudSync.questionnaire'); if (state === null || state === void 0 ? void 0 : state.val) { const questionnaire = JSON.parse(state.val); if (questionnaire.id === msg.message.id) { // Clear questionnaire await this.setStateAsync('info.cloudSync.questionnaire', JSON.stringify({ id: questionnaire.id, done: true }), true); } } if (msg.callback) { this.sendTo(msg.from, msg.command, { result: 'ok' }, msg.callback); } } break; } case 'getBindAddresses': { const list = []; if (process.platform === 'win32') { // Get standard interfaces of the host const interfaces = (0, node_os_1.networkInterfaces)(); // List ALL ip 4 and 6 addresses Object.keys(interfaces).forEach(ifname => { (interfaces[ifname] || []).forEach(iface => { // Select only IPv4 and no loopback if (iface.family === 'IPv4' && !iface.internal) { list.push({ label: `${ifname} (${iface.address})`, value: iface.address }); } }); }); } else { const out = (0, node_child_process_1.execSync)('ip -j addr', { encoding: 'utf8' }); const data = JSON.parse(out); if ((_2 = data === null || data === void 0 ? void 0 : data[0]) === null || _2 === void 0 ? void 0 : _2.addr_info) { data.forEach(item => { item.addr_info.forEach((addr) => { if (addr.family === 'inet' && addr.local !== '127.0.0.1') { list.push({ label: `${item.ifname} (${addr.local})`, value: addr.local }); } }); }); } } // place docker interfaces at the start list.sort((a, b) => { if (a.label.startsWith('docker') && !b.label.startsWith('docker')) { return -1; } if (!a.label.startsWith('docker') && b.label.startsWith('docker')) { return 1; } return a.label.localeCompare(b.label); }); if (msg.callback) { this.sendTo(msg.from, msg.command, list, msg.callback); } } } } } generateEmail(message, title) { this.emailAlarmText || (this.emailAlarmText = (0, node_fs_1.readFileSync)(`${__dirname}/emails/alert.html`, 'utf8')); return this.emailAlarmText.replace('{{title}}', title).replace('{{message}}', message); } async onReady() { var _a, _b, _c; // read UUID const uuidObj = await this.getForeignObjectAsync('system.meta.uuid'); if ((_a = uuidObj === null || uuidObj === void 0 ? void 0 : uuidObj.native) === null || _a === void 0 ? void 0 : _a.uuid) { this.uuid = uuidObj.native.uuid; const hash = (0, node_crypto_1.createHash)('sha256') .update((this.config.email || '').trim().toLowerCase()) .digest(); this.group = hash[hash.length - 1] & 1 ? 'B' : 'A'; await this.setState('info.ids.group', this.group, true); } else { this.log.error('Cannot read UUID'); return; } // Check if the docker available and if not, but the config wants to use it, disable it if (this.config.docker.selfHosted) { let dockerVersion = null; try { const dockerManager = new DockerManager_1.default(this); const info = await dockerManager.getDockerDaemonInfo(); dockerVersion = (info === null || info === void 0 ? void 0 : info.version) || null; void dockerManager.destroy(); } catch { // ignore } if (!dockerVersion) { this.log.warn(`Docker self-hosted is enabled in the configuration, but Docker is not available`); const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); if ((_b = obj === null || obj === void 0 ? void 0 : obj.native) === null || _b === void 0 ? void 0 : _b.docker) { obj.native.docker.selfHosted = false; await this.setForeignObjectAsync(obj._id, obj); return; } } } const statePeriod = await this.getStateAsync('info.ids.period'); if (new Date(IDSCommunication_1.CHANGE_TIME).getTime() <= Date.now() && !(statePeriod === null || statePeriod === void 0 ? void 0 : statePeriod.val)) { await this.setStateAsync('info.ids.period', true, true); } else if (new Date(IDSCommunication_1.CHANGE_TIME).getTime() > Date.now() && (!statePeriod || statePeriod.val)) { await this.setStateAsync('info.ids.period', false, true); } await adapter_core_1.I18n.init(__dirname, this); // remove running flag const runningState = await this.getStateAsync('info.connection'); if (runningState === null || runningState === void 0 ? void 0 : runningState.val) { await this.setState('info.connection', false, true); await this.setState('info.recording.running', false, true); } let captured = await this.getStateAsync('info.recording.capturedFull'); if (captured === null || captured === void 0 ? void 0 : captured.val) { await this.setState('info.recording.capturedFull', 0, true); } captured = await this.getStateAsync('info.recording.capturedFiltered'); if (captured === null || captured === void 0 ? void 0 : captured.val) { await this.setState('info.recording.capturedFiltered', 0, true); } if (!this.config.fritzbox) { this.log.error(`Fritz!Box is not defined`); return; } // Check the second(time interval) threshold for saving data if (!this.config.saveThresholdSeconds) { this.config.saveThresholdSeconds = SAVE_DATA_EVERY_MS / 1000; } else { this.config.saveThresholdSeconds = parseInt(this.config.saveThresholdSeconds.toString(), 10) || SAVE_DATA_EVERY_MS / 1000; } if (this.config.saveThresholdSeconds < 600) { this.log.warn(adapter_core_1.I18n.translate('The saveThresholdSeconds is set to %s seconds, but it should be at least 600 seconds to avoid too frequent saves.', this.config.saveThresholdSeconds)); this.config.saveThresholdSeconds = 600; } else if (this.config.saveThresholdSeconds > 3600) { this.log.warn(adapter_core_1.I18n.translate('The saveThresholdSeconds is set to %s seconds, but it should be less than 3600 seconds to avoid too infrequent saves.', this.config.saveThresholdSeconds)); this.config.saveThresholdSeconds = 3600; } this.readQuestionnaire(); // try to get MAC addresses for all IPs this.IPs = this.config.devices.filter(item => item.enabled && (item.ip || item.mac) && item.ip !== this.config.fritzbox); const tasks = this.IPs.filter(ip => !ip.mac); let fritzMac = ''; try { // determine the MAC of Fritzbox const fritzEntry = await this.getMacForIps([ { ip: this.config.fritzbox, mac: '', enabled: true, desc: 'FritzBox', uuid: '1' }, ]); fritzMac = ((_c = fritzEntry[0]) === null || _c === void 0 ? void 0 : _c.mac) || ''; } catch { this.log.debug(`Cannot determine MAC addresses of Fritz!Box`); } if (tasks.length) { try { const macs = await this.getMacForIps(tasks); for (let i = 0; i < tasks.length; i++) { const mac = macs[i]; if (mac === null || mac === void 0 ? void 0 : mac.mac) { const item = this.IPs.find(t => t.ip === mac.ip); if (item) { item.mac = mac.mac; } } } // print out the IP addresses without MAC addresses const missing = this.IPs.filter(item => !item.mac); if (missing.length) { this.log.warn(`${adapter_core_1.I18n.translate('Cannot get MAC addresses for the following IPs')}: ${missing.map(t => t.ip).join(', ')}`); } } catch (e) { if (e.toString().includes('no results')) { this.log.warn(`${adapter_core_1.I18n.translate('Cannot get MAC addresses for the following IPs')}: ${tasks.map(t => t.ip).join(', ')}`); } else { this.log.error(`Cannot get MAC addresses: ${e}`); } } } // take only unique MAC addresses and not the MAC address of Fritz!Box this.uniqueMacs = []; this.IPs.forEach(item => { var _a; return !this.uniqueMacs.includes(item.mac) && ((_a = item.mac) === null || _a === void 0 ? void 0 : _a.trim()) && item.mac !== fritzMac && this.uniqueMacs.push(item.mac); }); this.uniqueMacs = this.uniqueMacs.filter(mac => mac); // detect temp directory this.tempDir = this.config.tempDir || '/run/shm'; if (!(0, node_fs_1.existsSync)(this.tempDir)) { if ((0, node_fs_1.existsSync)('/run/shm')) { this.tempDir = '/run/shm'; } else if ((0, node_fs_1.existsSync)('/tmp')) { this.tempDir = '/tmp'; } else { this.log.warn(adapter_core_1.I18n.translate('Cannot find any temporary directory. Please specify manually in the configuration. For best performance it should be a RAM disk')); return; } } this.log.info(adapter_core_1.I18n.translate('Using "%s" as temporary directory', this.tempDir)); this.tempDir = this.tempDir.replace(/\\/g, '/'); if (this.tempDir.endsWith('/')) { this.tempDir = this.tempDir.substring(0, this.tempDir.length - 1); } this.workingCloudDir = `${this.tempDir}/cloud_pcaps`; this.workingIdsDir = `${this.tempDir}/ids_pcaps`; // create cloud directory try { if (!(0, node_fs_1.existsSync)(this.workingCloudDir)) { (0, node_fs_1.mkdirSync)(this.workingCloudDir); } } catch (e) { this.log.error(`${adapter_core_1.I18n.translate('Cannot create %s working directory', 'cloud')} "${this.workingCloudDir}": ${e}`); return; } // create ids directory try { if (!(0, node_fs_1.existsSync)(this.workingIdsDir)) { (0, node_fs_1.mkdirSync)(this.workingIdsDir); } } catch (e) { this.log.error(`${adapter_core_1.I18n.translate('Cannot create %s working directory', 'IDS')} "${this.workingIdsDir}": ${e}`); return; } // this.clearWorkingCloudDir(); // this.clearWorkingIdsDir(); if (!this.config.email) { this.log.error(adapter_core_1.I18n.translate('No email provided. Please provide an email address in the configuration.')); this.log.error(adapter_core_1.I18n.translate('You must register this email first on https://kisshome-research.if-is.net/#register.')); return; } await this.setState('info.recording.running', false, true); await this.setState('info.recording.triggerWrite', false, true); this.statistics = new Statistics_1.default(this, this.IPs); if (!this.uniqueMacs.length) { this.log.warn(`[PCAP] ${adapter_core_1.I18n.translate('No any MAC addresses provided for recording. Please provide some MAC addresses or Ip addresses, that could be resolved to MAC address')}`); return; } this.subscribeStates('info.recording.enabled'); this.subscribeStates('info.recording.triggerWrite'); // Delete it later this.subscribeStates('info.ids.simulate'); const simulationActivated = await this.getStateAsync('info.ids.simulate'); this.recordingEnabled = ((await this.getStateAsync('info.recording.enabled')) || {}).val || false; this.cloudSync = new CloudSync_1.default(this, { workingDir: this.workingCloudDir, context: this.context, uuid: this.uuid, IPs: this.IPs, version: this.versionPack, }); console.log(adapter_core_1.I18n.translate('Saved UX events to file "%s"', `${this.cloudSync.workingDir}/${(0, utils_1.getTimestamp)()}_ux_events.json`)); this.idsCommunication = new IDSCommunication_1.IDSCommunication(this, this.config, (0, utils_1.getDescriptionObject)(this.IPs), { workingFolder: this.workingIdsDir, generateEvent: this.generateEvent, group: this.group, workingCloudDir: this.workingCloudDir, }); if (this.recordingEnabled) { // Send the data every hour to the cloud this.cloudSync.start(); if (await this.cloudSync.isEmailOk()) { // start the monitoring try { await this.startRecording(); } catch (e) { this.log.error(`[PCAP] ${adapter_core_1.I18n.translate('Cannot start recording')}: ${e}`); } // Start communication with IDS await this.idsCommunication.start(); this.idsCommunication.activateSimulation(!!(simulationActivated === null || simulationActivated === void 0 ? void 0 : simulationActivated.val), true); } // } else { // // TODO: Delete it later as the test is not needed // await this.idsCommunication.start(); // this.idsCommunication.activateSimulation(!!simulationActivated?.val, true); // } } // else { // // TODO: Delete it later as the test is not needed // await this.idsCommunication.start(); // this.idsCommunication.activateSimulation(!!simulationActivated?.val, true); // this.log.warn(I18n.translate('Recording is not enabled. Do nothing.')); // } // Start every day at 20:00 the status report this.dailyReportSchedule = node_schedule_1.default.scheduleJob('0 0 18 * * *', () => { this.generateStatusReport().catch(e => { this.log.error(`Cannot send status report: ${e}`); }); }); if (new Date(IDSCommunication_1.CHANGE_TIME).getTime() > Date.now()) { this.secondPartSchedule = node_schedule_1.default.scheduleJob(new Date(IDSCommunication_1.CHANGE_TIME), () => { void this.setStateAsync('info.ids.period', true, true); }); } this.subscribeStates('info.ids.period'); } readQuestionnaire() { if (this.questionnaireTimer) { this.clearTimeout(this.questionnaireTimer); this.questionnaireTimer = null; } // Read the questionnaire file axios_1.default .get(`https://${CloudSync_1.PCAP_HOST}/api/v2/questionnaire?email=${encodeURIComponent(this.config.email)}&uuid=${encodeURIComponent(this.uuid)}`) .then(async (response) => { var _a; if (response.status === 200 && typeof response.data === 'object' && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.id)) { // Check if the questionnaire file has changed const state = await this.getStateAsync('info.cloudSync.questionnaire'); if (state === null || state === void 0 ? void 0 : state.val) { const questionnaire = JSON.parse(state.val); if (questionnaire.id !== response.data.id) { // Save the new questionnaire await this.setStateAsync('info.cloudSync.questionnaire', JSON.stringify(response.data), true); this.log.info(`${adapter_core_1.I18n.translate('New questionnaire received')}: ${response.data.id}`); } } else { // Save the questionnaire for the first time await this.setStateAsync('info.cloudSync.questionnaire', JSON.stringify(response.data), true); this.log.info(`${adapter_core_1.I18n.translate('New questionnaire received')}: ${response.data.id}`); } } }) .catch(e => { this.log.error(`${adapter_core_1.I18n.translate('Cannot read questionnaire')}: ${e}`); }); this.questionnaireTimer = this.setTimeout(() => { this.questionnaireTimer = null; this.readQuestionnaire(); }, 60 * 60 * 1000); // every hour } onStateChange(id, state) { var _a, _b, _c, _d, _e; if (state) { if (id === `${this.namespace}.info.ids.period` && !state.ack) { void this.generateStatusReport(!!(state === null || state === void 0 ? void 0 : state.val)); } else if (id === `${this.namespace}.info.recording.enabled` && !state.ack) { if (state.val) { // If recording is not running if (!this.recordingEnabled) { this.recordingEnabled = true; this.context.terminate = false; this.startRecording().catch(e => { this.log.error(`${adapter_core_1.I18n.translate('Cannot start recording')}: ${e}`); }); // Send the data every hour to the cloud (_a = this.cloudSync) === null || _a === void 0 ? void 0 : _a.start(); void ((_b = this.idsCommunication) === null || _b === void 0 ? void 0 : _b.start()); } } else if (this.recordingEnabled) { this.recordingEnabled = false; this.context.terminate = true; if (this.context.controller) { this.context.controller.abort(); this.context.controller = null; } (_c = this.cloudSync) === null || _c === void 0 ? void 0 : _c.stop(); void ((_d = this.idsCommunication) === null || _d === void 0 ? void 0 : _d.destroy()); } } else if (id === `${this.namespace}.info.recording.triggerWrite` && !state.ack) { if (state.val) { this.triggerWriteFile(); } } else if (id === `${this.namespace}.info.ids.simulate` && !state.ack) { // Simulate IDS events (_e = this.idsCommunication) === null || _e === void 0 ? void 0 : _e.activateSimulation(!!state.val); } } } triggerWriteFile() { var _a; if (this.recordingRunning) { void this.setState('info.recording.triggerWrite', false, true).catch(e => this.log.error(`${adapter_core_1.I18n.translate('Cannot set triggerWrite')}: ${e}`)); this.savePacketsToFile(); setTimeout(() => { var _a; (_a = this.cloudSync) === null || _a === void 0 ? void 0 : _a.startCloudSynchronization().catch(e => { this.log.error(`[RSYNC] ${adapter_core_1.I18n.translate('Cannot synchronize')}: ${e}`); }); }, 2000); (_a = this.idsCommunication) === null || _a === void 0 ? void 0 : _a.triggerUpdate(); } } restartRecording() { if (this.startTimeout) { clearTimeout(this.startTimeout); } this.startTimeout = this.setTimeout(() => { this.startTimeout = undefined; this.startRecording().catch(e => { this.log.error(`${adapter_core_1.I18n.translate('Cannot start recording')}: ${e}`); }); }, 10000); } savePacketsToFile() { var _a; const timeStamp = (0, utils_1.getTimestamp)(); if (this.context.filtered.packets.length) { const packetsToSave = this.context.filtered.packets; this.context.filtered.packets = []; this.context.filtered.totalBytes = 0; const fileName = `${this.workingCloudDir}/${timeStamp}.pcap`; // get file descriptor of a file const fd = (0, node_fs_1.openSync)(fileName, 'w'); let offset = 0; const magic = packetsToSave[0].readUInt32LE(0); const STANDARD_MAGIC = 0xa1b2c3d4; // https://wiki.wireshark.org/Development/LibpcapFileFormat const MODIFIED_MAGIC = 0xa1b2cd34; // d