UNPKG

iobroker.kisshome-defender

Version:
380 lines 18 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PCAP_HOST = void 0; const adapter_core_1 = require("@iobroker/adapter-core"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_crypto_1 = require("node:crypto"); const axios_1 = __importDefault(require("axios")); const utils_1 = require("./utils"); const SYNC_INTERVAL = 3600000; // 1 hour; exports.PCAP_HOST = 'kisshome-experiments.if-is.net'; class CloudSync { constructor(adapter, options) { this.syncRunning = false; this.syncTimer = null; this.emailOk = null; this.justSending = ''; this.collectUxEvents = null; this.timeoutUxEvents = null; this.version = options.version; this.adapter = adapter; this.config = this.adapter.config; this.workingDir = options.workingDir; this.context = options.context; this.uuid = options.uuid; this.IPs = options.IPs; this.saveMetaFile(); this.ready = new Promise(resolve => this.init(resolve)); } async analyseError(response) { if (response.status === 404) { this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${adapter_core_1.I18n.translate('Unknown email address')}`); } else if (response.status === 403) { this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${adapter_core_1.I18n.translate('UUID changed. Please contact us via kisshome@internet-sicherheit.de')}`); await this.adapter.registerNotification('kisshome-defender', 'uuid', 'UUID wurde geändert'); } else if (response.status === 401) { this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${adapter_core_1.I18n.translate('invalid password')}`); } else if (response.status === 422) { this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${adapter_core_1.I18n.translate('missing email, public key or uuid')}`); } else { this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${response.data || response.statusText || response.status}`); } } async init(callback) { var _a, _b; try { // register on the cloud const response = await axios_1.default.post(`https://${exports.PCAP_HOST}/api/v2/checkEmail?email=${encodeURIComponent(this.config.email)}&uuid=${encodeURIComponent(this.uuid)}`, { timeout: 10000, // 10-second timeout }); if (response.status === 200) { if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.command) === 'terminate') { this.adapter.log.warn(adapter_core_1.I18n.translate('Server requested to terminate the adapter')); const obj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.adapter.namespace}`); if ((_b = obj === null || obj === void 0 ? void 0 : obj.common) === null || _b === void 0 ? void 0 : _b.enabled) { obj.common.enabled = false; await this.adapter.setForeignObjectAsync(obj._id, obj); } } else { this.emailOk = true; this.adapter.log.info(adapter_core_1.I18n.translate('Successfully registered on the cloud')); } } else { this.emailOk = false; await this.analyseError(response); } } catch (error) { if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') { // timeout: retry in 5 seconds this.adapter.log.warn(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: timeout`); } else if (error.response) { this.emailOk = false; await this.analyseError(error.response); } else { this.emailOk = false; this.adapter.log.error(`${adapter_core_1.I18n.translate('Cannot register on the kisshome-cloud')}: ${error}`); } } if (this.emailOk === null && !this.context.terminate) { setTimeout(() => { void this.init(callback); }, 5000); } else { callback(); } } start() { this.syncJob(); } syncJob() { // Send the data every hour to the cloud if (this.syncTimer) { clearTimeout(this.syncTimer); this.syncTimer = null; } if (this.context.terminate) { return; } const started = Date.now(); void this.startCloudSynchronization() .catch(e => { this.adapter.log.error(`[RSYNC] ${adapter_core_1.I18n.translate('Cannot synchronize')}: ${e}`); }) .then(() => { const duration = Date.now() - started; this.syncTimer = setTimeout(() => { this.syncTimer = null; this.syncJob(); }, SYNC_INTERVAL - duration > 0 ? SYNC_INTERVAL - duration : 0); }); } stop() { var _a; if (this.syncTimer) { clearTimeout(this.syncTimer); this.syncTimer = null; } if (this.timeoutUxEvents) { clearTimeout(this.timeoutUxEvents); this.timeoutUxEvents = null; } if ((_a = this.collectUxEvents) === null || _a === void 0 ? void 0 : _a.length) { this.saveUxEvents(this.collectUxEvents); this.collectUxEvents = null; } } async isEmailOk() { await this.ready; return this.emailOk; } static calculateMd5(content) { const hash = (0, node_crypto_1.createHash)('md5'); hash.update(content); return hash.digest('hex'); } async sendOneFileToCloud(fileName, size) { var _a, _b; try { if (!(0, node_fs_1.existsSync)(fileName)) { this.adapter.log.warn(`[RSYNC] ${adapter_core_1.I18n.translate('File "%s" does not exist. Size: %s', fileName, size ? (0, utils_1.size2text)(size) : 'unknown')}`); return; } const data = (0, node_fs_1.readFileSync)(fileName); const name = (0, node_path_1.basename)(fileName); const len = data.length; const md5 = CloudSync.calculateMd5(data); this.justSending = fileName; // check if the file was sent successfully try { const responseCheck = await axios_1.default.get(`https://${exports.PCAP_HOST}/api/v2/upload/${encodeURIComponent(this.config.email)}/${encodeURIComponent(name)}?uuid=${encodeURIComponent(this.uuid)}`); if (((_a = responseCheck.data) === null || _a === void 0 ? void 0 : _a.command) === 'terminate') { const obj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.adapter.namespace}`); if ((_b = obj === null || obj === void 0 ? void 0 : obj.common) === null || _b === void 0 ? void 0 : _b.enabled) { obj.common.enabled = false; await this.adapter.setForeignObjectAsync(obj._id, obj); } return; } if (responseCheck.status === 200 && responseCheck.data === md5) { // file already uploaded, do not upload it again if (!name.endsWith('_meta.json')) { this.justSending = ''; (0, node_fs_1.unlinkSync)(fileName); } return; } } catch { // ignore } const responsePost = await (0, axios_1.default)({ method: 'post', url: `https://${exports.PCAP_HOST}/api/v2/upload/${encodeURIComponent(this.config.email)}/${encodeURIComponent(name)}?&uuid=${encodeURIComponent(this.uuid)}`, data, headers: { 'Content-Type': 'application/vnd.tcpdump.pcap' }, }); // check if the file was sent successfully const response = await axios_1.default.get(`https://${exports.PCAP_HOST}/api/v2/upload/${encodeURIComponent(this.config.email)}/${encodeURIComponent(name)}?&uuid=${encodeURIComponent(this.uuid)}`); if (response.status === 200 && response.data === md5) { if (!name.endsWith('_meta.json')) { (0, node_fs_1.unlinkSync)(fileName); } this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Sent file "%s"(%s) to the cloud', fileName, (0, utils_1.size2text)(len))} (${size ? (0, utils_1.size2text)(size) : adapter_core_1.I18n.translate('unknown')}): ${responsePost.status}`); } else { this.adapter.log.warn(`[RSYNC] ${adapter_core_1.I18n.translate('File sent to server, but check fails (%s). "%s" to the cloud', size ? (0, utils_1.size2text)(size) : adapter_core_1.I18n.translate('unknown'), fileName)}: status=${responsePost.status}, len=${len}, response=${response.data}`); } } catch (e) { this.adapter.log.error(`[RSYNC] ${adapter_core_1.I18n.translate('Cannot send file "%s" to the cloud', fileName)} (${size ? (0, utils_1.size2text)(size) : adapter_core_1.I18n.translate('unknown')}): ${e}`); } } saveMetaFile() { var _a; const text = (0, utils_1.getDescriptionFile)(this.IPs); const newFile = `${this.workingDir}/${(0, utils_1.getTimestamp)()}_v${this.version}_${((_a = this.config.docker) === null || _a === void 0 ? void 0 : _a.selfHosted) ? 'auto' : 'manual'}_meta.json`; try { // find the latest file let changed = false; let files = (0, node_fs_1.readdirSync)(this.workingDir); // sort descending files.sort((a, b) => b.localeCompare(a)); // if two JSON files are coming after each other, the older one must be deleted for (let f = files.length - 1; f > 0; f--) { if (files[f].endsWith('_meta.json') && files[f - 1].endsWith('_meta.json')) { (0, node_fs_1.unlinkSync)(`${this.workingDir}/${files[f]}`); changed = true; } } // read the list anew as it was changed if (changed) { files = (0, node_fs_1.readdirSync)(this.workingDir); // sort descending files.sort((a, b) => b.localeCompare(a)); } // find the latest file and delete all other _meta.json files const latestFile = files.find(f => f.endsWith('_meta.json')); // if existing meta file found if (latestFile) { // compare the content const oldFile = (0, node_fs_1.readFileSync)(`${this.workingDir}/${latestFile}`, 'utf8'); if (oldFile !== text) { this.adapter.log.debug(adapter_core_1.I18n.translate('Meta file updated')); // delete the old JSON file only if no pcap files exists if (files[0].endsWith('_meta.json')) { (0, node_fs_1.unlinkSync)(`${this.workingDir}/${latestFile}`); } (0, node_fs_1.writeFileSync)(newFile, text); return newFile; } return `${this.workingDir}/${latestFile}`; } this.adapter.log.info(adapter_core_1.I18n.translate('Meta file created')); // if not found => create new one (0, node_fs_1.writeFileSync)(newFile, text); return newFile; } catch (e) { this.adapter.log.warn(`${adapter_core_1.I18n.translate('Cannot save meta file "%s"', newFile)}: ${e}`); return ''; } } reportUxEvents(uxEvents) { this.collectUxEvents || (this.collectUxEvents = []); this.collectUxEvents.push(...uxEvents); this.timeoutUxEvents || (this.timeoutUxEvents = setTimeout(() => { var _a; this.timeoutUxEvents = null; if ((_a = this.collectUxEvents) === null || _a === void 0 ? void 0 : _a.length) { this.saveUxEvents(this.collectUxEvents); this.collectUxEvents = null; } }, 120000)); } saveUxEvents(uxEvents) { // Find UX events files let fileName; const files = (0, node_fs_1.readdirSync)(this.workingDir) .filter(f => f.includes('ux_events') && f.endsWith('.json')) .sort(); if (!files.length || files[files.length - 1] === this.justSending) { // create a new file fileName = `${this.workingDir}/${(0, utils_1.getTimestamp)()}_ux_events.json`; } else { // use the last file fileName = `${this.workingDir}/${files[files.length - 1]}`; } let existingEvents = []; if ((0, node_fs_1.existsSync)(fileName)) { try { existingEvents = JSON.parse((0, node_fs_1.readFileSync)(fileName, 'utf8')); } catch (e) { this.adapter.log.warn(`${adapter_core_1.I18n.translate('Cannot read UX events file "%s"', fileName)}: ${e}`); } } existingEvents.push(...uxEvents); // save the file try { (0, node_fs_1.writeFileSync)(fileName, JSON.stringify(existingEvents, null, 2), 'utf8'); this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Saved UX events to file "%s"', fileName)} (${(0, utils_1.size2text)(Buffer.byteLength(JSON.stringify(existingEvents, null, 2)))})`); } catch (e) { this.adapter.log.warn(`${adapter_core_1.I18n.translate('Cannot save UX events file "%s"', fileName)}: ${e}`); } } async startCloudSynchronization() { var _a; await this.ready; if (this.context.terminate) { this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Requested termination. No synchronization')}`); return; } if (!this.emailOk) { this.adapter.log.warn(`[RSYNC] ${adapter_core_1.I18n.translate('Email not registered. No synchronization')}`); return; } // if UX events are collected, save them if ((_a = this.collectUxEvents) === null || _a === void 0 ? void 0 : _a.length) { this.saveUxEvents(this.collectUxEvents); this.collectUxEvents = null; } // calculate the total number of bytes let totalBytes = 0; this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Start synchronization...')}`); // calculate the total number of bytes in pcap files let pcapFiles; let allFiles; const sizes = {}; try { allFiles = (0, node_fs_1.readdirSync)(this.workingDir); pcapFiles = allFiles.filter(f => f.endsWith('.pcap')); for (const file of pcapFiles) { sizes[file] = (0, node_fs_1.statSync)(`${this.workingDir}/${file}`).size; totalBytes += sizes[file]; } } catch (e) { this.adapter.log.error(`[RSYNC] ${adapter_core_1.I18n.translate('Cannot read working directory "%s" for sync', this.workingDir)}: ${e}`); return; } if (!totalBytes) { this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('No files to sync')}`); return; } if (this.syncRunning) { this.adapter.log.warn(`[RSYNC] ${adapter_core_1.I18n.translate('Synchronization still running...')}`); return; } this.syncRunning = true; await this.adapter.setState('info.cloudSync.running', true, true); this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Syncing files to the cloud')} (${(0, utils_1.size2text)(totalBytes)})`); // send files to the cloud // first send JSON files and meta file let sentMeta = false; for (let i = 0; i < allFiles.length; i++) { const file = allFiles[i]; if (file.endsWith('.json')) { await this.sendOneFileToCloud(`${this.workingDir}/${file}`); if (file.endsWith('_meta.json')) { sentMeta = true; } } } if (!sentMeta) { // create meta file anew and send it to the cloud const fileName = this.saveMetaFile(); if (fileName) { await this.sendOneFileToCloud(fileName); } else { this.adapter.log.debug(`[RSYNC] ${adapter_core_1.I18n.translate('Cannot create META file. No synchronization')}`); return; } } // send all pcap files for (let i = 0; i < pcapFiles.length; i++) { const file = pcapFiles[i]; await this.sendOneFileToCloud(`${this.workingDir}/${file}`, sizes[file]); } this.syncRunning = false; await this.adapter.setState('info.cloudSync.running', false, true); } } exports.default = CloudSync; //# sourceMappingURL=CloudSync.js.map