zabbix-utils
Version:
TypeScript port of zabbix-utils - Python library for working with Zabbix API, Sender, and Getter protocols
295 lines • 12.2 kB
JavaScript
;
// zabbix_utils
//
// Copyright (C) 2001-2023 Zabbix SIA (Original Python library)
// Copyright (C) 2024-2025 Han Yong Lim <hanyong.lim@gmail.com> (TypeScript adaptation)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZabbixProtocol = exports.ModuleUtils = void 0;
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
const zlib = __importStar(require("zlib"));
class ModuleUtils {
/**
* Check url completeness
*
* @param url - Unchecked URL of Zabbix API
* @returns Checked URL of Zabbix API
*/
static checkUrl(url) {
if (!url.endsWith(this.JSONRPC_FILE)) {
url += url.endsWith('/') ? this.JSONRPC_FILE : '/' + this.JSONRPC_FILE;
}
if (!url.startsWith('http')) {
url = 'http://' + url;
}
return url;
}
/**
* Replace the most part of string to hiding mask.
*
* @param string - Raw string with without hiding.
* @param showLen - Number of signs shown on each side of the string. Defaults to 4.
* @returns String with hiding part.
*/
static maskSecret(string, showLen = 4) {
// If showLen is 0 or the length of the string is smaller than the hiding mask length
// and showLen from both sides of the string, return only hiding mask.
if (showLen === 0 || string.length <= (this.HIDING_MASK.length + showLen * 2)) {
return this.HIDING_MASK;
}
// Return the string with the hiding mask, surrounded by the specified number of characters
// to display on each side of the string.
return `${string.slice(0, showLen)}${this.HIDING_MASK}${string.slice(-showLen)}`;
}
/**
* Hide private data Zabbix info (e.g. token, password)
*
* @param inputData - Input dictionary with private fields.
* @param fields - Dictionary of private fields and their filtering regexps.
* @returns Result dictionary without private data.
*/
static hidePrivate(inputData, fields) {
const privateFields = fields || this.PRIVATE_FIELDS;
if (typeof inputData !== 'object' || inputData === null || Array.isArray(inputData)) {
throw new TypeError(`Unsupported data type '${typeof inputData}', only 'object' is expected`);
}
const genRepl = (match) => {
return this.maskSecret(match);
};
const hideStr = (k, v) => {
const regex = new RegExp(privateFields[k], 'g');
return v.replace(regex, genRepl);
};
const hideDict = (v) => {
return this.hidePrivate(v, privateFields);
};
const hideList = (k, v) => {
const result = [];
for (const item of v) {
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
result.push(hideDict(item));
continue;
}
if (Array.isArray(item)) {
result.push(hideList(k, item));
continue;
}
if (typeof item === 'string') {
const keyWithoutS = k.replace(/s$/, '');
if (keyWithoutS in privateFields) {
result.push(hideStr(keyWithoutS, item));
continue;
}
// The 'result' regex is used to hide only token or
// sessionid format for unknown values
if ('result' in privateFields) {
result.push(hideStr('result', item));
continue;
}
}
result.push(item);
}
return result;
};
const resultData = { ...inputData };
for (const [key, value] of Object.entries(resultData)) {
if (typeof value === 'string') {
if (key in privateFields) {
resultData[key] = hideStr(key, value);
}
}
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
resultData[key] = hideDict(value);
}
if (Array.isArray(value)) {
resultData[key] = hideList(key, value);
}
}
return resultData;
}
}
exports.ModuleUtils = ModuleUtils;
// Hiding mask for sensitive data
ModuleUtils.HIDING_MASK = "*".repeat(8);
// The main php-file of Zabbix API
ModuleUtils.JSONRPC_FILE = 'api_jsonrpc.php';
// Methods working without auth token
ModuleUtils.UNAUTH_METHODS = ['apiinfo.version', 'user.login', 'user.checkAuthentication'];
// Methods returning files contents
ModuleUtils.FILES_METHODS = ['configuration.export'];
// List of private fields and regular expressions to hide them
ModuleUtils.PRIVATE_FIELDS = {
"token": "^.+$",
"auth": "^.+$",
"passwd": "^.+$",
"sessionid": "^.+$",
"password": "^.+$",
"current_passwd": "^.+$",
"result": "^[A-Za-z0-9]{32}$", // To hide only token or sessionid in result
};
class ZabbixProtocol {
static prepareRequest(data) {
if (Buffer.isBuffer(data)) {
return data;
}
if (typeof data === 'string') {
return Buffer.from(data, 'utf-8');
}
if (Array.isArray(data) || (typeof data === 'object' && data !== null)) {
return Buffer.from(JSON.stringify(data), 'utf-8');
}
throw new TypeError("Unsupported data type, only 'Buffer', 'string', 'array' or 'object' is expected");
}
/**
* Create a packet for sending via the Zabbix protocol.
*
* @param payload - Payload of the future packet
* @param log - Logger object (console in Node.js)
* @param compression - Compression use flag. Defaults to false.
* @returns Generated Zabbix protocol packet
*/
static createPacket(payload, log, compression = false) {
const request = this.prepareRequest(payload);
const requestStr = request.toString('utf-8');
const displayStr = requestStr.length > 200 ? requestStr.slice(0, 197) + '...' : requestStr;
log.debug('Request data:', displayStr);
// 0x01 - Zabbix communications protocol
let flags = 0x01;
let data = request;
if (compression) {
data = zlib.deflateSync(request);
// 0x02 - compression flag
flags |= 0x02;
}
const header = Buffer.alloc(this.HEADER_SIZE);
this.ZABBIX_PROTOCOL.copy(header, 0);
header.writeUInt8(flags, 4);
header.writeUInt32LE(data.length, 5);
header.writeUInt32LE(request.length, 9);
return Buffer.concat([header, data]);
}
/**
* Receive packet data from socket
*
* @param conn - Socket connection
* @param size - Size of data to receive
* @param log - Logger object
* @returns Received data
*/
static receivePacket(conn, size, log) {
return new Promise((resolve, reject) => {
const chunks = [];
let received = 0;
const onData = (chunk) => {
chunks.push(chunk);
received += chunk.length;
if (received >= size) {
conn.removeListener('data', onData);
conn.removeListener('error', onError);
const result = Buffer.concat(chunks);
resolve(result.slice(0, size));
}
};
const onError = (error) => {
conn.removeListener('data', onData);
conn.removeListener('error', onError);
reject(error);
};
conn.on('data', onData);
conn.on('error', onError);
});
}
/**
* Parse a received synchronously Zabbix protocol packet.
*
* @param conn - Socket connection
* @param log - Logger object
* @param ExceptionClass - Exception class to throw
* @returns Body of the received packet
*/
static async parseSyncPacket(conn, log, ExceptionClass) {
const responseHeader = await this.receivePacket(conn, this.HEADER_SIZE, log);
log.debug('Zabbix response header:', responseHeader);
if (!responseHeader.subarray(0, 4).equals(this.ZABBIX_PROTOCOL) ||
responseHeader.length !== this.HEADER_SIZE) {
log.debug('Unexpected response was received from Zabbix.');
throw new ExceptionClass('Unexpected response was received from Zabbix.');
}
const flags = responseHeader.readUInt8(4);
const datalen = responseHeader.readUInt32LE(5);
const reserved = responseHeader.readUInt32LE(9);
// 0x01 - Zabbix communications protocol
if (!(flags & 0x01)) {
throw new ExceptionClass('Unexpected flags were received. ' +
'Check debug log for more information.');
}
// 0x04 - Using large packet mode
if (flags & 0x04) {
throw new ExceptionClass('A large packet flag was received. ' +
'Current module doesn\'t support large packets.');
}
let responseBody;
// 0x02 - Using packet compression mode
if (flags & 0x02) {
const compressedData = await this.receivePacket(conn, datalen, log);
responseBody = zlib.inflateSync(compressedData);
}
else {
responseBody = await this.receivePacket(conn, datalen, log);
}
return responseBody.toString('utf-8');
}
}
exports.ZabbixProtocol = ZabbixProtocol;
ZabbixProtocol.ZABBIX_PROTOCOL = Buffer.from('ZBXD');
ZabbixProtocol.HEADER_SIZE = 13;
//# sourceMappingURL=common.js.map