iobroker.kisshome-defender
Version:
Collection of information for KISSHome defender
938 lines • 74.1 kB
JavaScript
"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