UNPKG

node-routeros-altostrat

Version:

Mikrotik Routerboard RouterOS API for NodeJS

420 lines 29.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Connector_1 = require("./connector/Connector"); const Channel_1 = require("./Channel"); const RosException_1 = require("./RosException"); const RStream_1 = require("./RStream"); const crypto = require("crypto"); const debug = require("debug"); const timers_1 = require("timers"); const events_1 = require("events"); const info = debug('routeros-api:api:info'); const error = debug('routeros-api:api:error'); /** * Creates a connection object with the credentials provided */ class RouterOSAPI extends events_1.EventEmitter { /** * Constructor, also sets the language of the thrown errors * * @param {Object} options */ constructor(options) { super(); /** * Connected flag */ this.connected = false; /** * Connecting flag */ this.connecting = false; /** * Closing flag */ this.closing = false; /** * Counter for channels open */ this.channelsOpen = 0; /** * Flag if the connection was held by the keepalive parameter * or keepaliveBy function */ this.holdingConnectionWithKeepalive = false; this.registeredStreams = []; this.setOptions(options); } /** * Set connection options, affects before connecting * * @param options connection options */ setOptions(options) { this.host = options.host; this.user = options.user; this.password = options.password; this.port = options.port || 8728; this.timeout = options.timeout || 10; this.tls = options.tls; this.keepalive = options.keepalive || false; this.streamingDelay = options.streamingDelay || 5000; } /** * Tries a connection to the routerboard with the provided credentials * * @returns {Promise} */ connect() { if (this.connecting) return Promise.reject('ALRDYCONNECTING'); if (this.connected) return Promise.resolve(this); info('Connecting on %s', this.host); this.connecting = true; this.connected = false; this.connector = new Connector_1.Connector({ host: this.host, port: this.port, timeout: this.timeout, tls: this.tls, }); return new Promise((resolve, reject) => { const endListener = (e) => { this.stopAllStreams(); this.connected = false; this.connecting = false; if (e) reject(e); }; this.connector.once('error', endListener); this.connector.once('timeout', endListener); this.connector.once('close', () => { this.emit('close'); endListener(); }); this.connector.once('connected', () => { this.login() .then(() => { this.connecting = false; this.connected = true; this.connector.removeListener('error', endListener); this.connector.removeListener('timeout', endListener); const connectedErrorListener = (e) => { this.connected = false; this.connecting = false; this.emit('error', e); }; this.connector.once('error', connectedErrorListener); this.connector.once('timeout', connectedErrorListener); if (this.keepalive) this.keepaliveBy('#'); info('Logged in on %s', this.host); resolve(this); }) .catch((e) => { this.connecting = false; this.connected = false; reject(e); }); }); this.connector.connect(); }); } /** * Writes a command over the socket to the routerboard * on a new channel * * @param {string|Array} params * @param {Array<string|string[]>} moreParams * @returns {Promise} */ write(params, ...moreParams) { params = this.concatParams(params, moreParams); let chann = this.openChannel(); this.holdConnection(); chann.once('close', () => { chann = null; // putting garbage collector to work :] this.decreaseChannelsOpen(); this.releaseConnectionHold(); }); return chann.write(params); } /** * Writes a command over the socket to the routerboard * on a new channel and return an event of what happens * with the responses. Listen for 'data', 'done', 'trap' and 'close' * events. * * @param {string|Array} params * @param {Array<string|string[]>} moreParams * @returns {RStream} */ writeStream(params, ...moreParams) { params = this.concatParams(params, moreParams); const stream = new RStream_1.RStream(this.openChannel(), params); stream.on('started', () => { this.holdConnection(); }); stream.on('stopped', () => { this.unregisterStream(stream); this.decreaseChannelsOpen(); this.releaseConnectionHold(); }); stream.start(); this.registerStream(stream); return stream; } /** * Returns a stream object for handling continuous data * flow. * * @param {string|Array} params * @param {function} callback * @returns {RStream} */ stream(params = [], ...moreParams) { let callback = moreParams.pop(); if (typeof callback !== 'function') { if (callback) moreParams.push(callback); callback = null; } params = this.concatParams(params, moreParams); const stream = new RStream_1.RStream(this.openChannel(), params, callback); stream.on('started', () => { this.holdConnection(); }); stream.on('stopped', () => { this.unregisterStream(stream); this.decreaseChannelsOpen(); this.releaseConnectionHold(); stream.removeAllListeners(); }); stream.start(); stream.prepareDebounceEmptyData(); this.registerStream(stream); return stream; } /** * Keep the connection alive by running a set of * commands provided instead of the random command * * @param {string|Array} params * @param {function} callback */ keepaliveBy(params = [], ...moreParams) { this.holdingConnectionWithKeepalive = true; if (this.keptaliveby) timers_1.clearTimeout(this.keptaliveby); let callback = moreParams.pop(); if (typeof callback !== 'function') { if (callback) moreParams.push(callback); callback = null; } params = this.concatParams(params, moreParams); const exec = () => { if (!this.closing) { if (this.keptaliveby) timers_1.clearTimeout(this.keptaliveby); this.keptaliveby = setTimeout(() => { this.write(params.slice()) .then((data) => { if (typeof callback === 'function') callback(null, data); exec(); }) .catch((err) => { if (typeof callback === 'function') callback(err, null); exec(); }); }, (this.timeout * 1000) / 2); } }; exec(); } /** * Closes the connection. * It can be openned again without recreating * an object from this class. * * @returns {Promise} */ close() { if (this.closing) { return Promise.reject(new RosException_1.RosException('ALRDYCLOSNG')); } if (!this.connected) { return Promise.resolve(this); } if (this.connectionHoldInterval) { timers_1.clearTimeout(this.connectionHoldInterval); } timers_1.clearTimeout(this.keptaliveby); this.stopAllStreams(); return new Promise((resolve) => { this.closing = true; this.connector.once('close', () => { this.connector.destroy(); this.connector = null; this.closing = false; this.connected = false; resolve(this); }); this.connector.close(); }); } /** * Opens a new channel for executing/writing queries * * @returns {Channel} */ openChannel() { this.increaseChannelsOpen(); const channel = new Channel_1.Channel(this.connector); // Set the global streaming delay for this channel channel.setStreamingDelay(this.streamingDelay); return channel; } increaseChannelsOpen() { this.channelsOpen++; } decreaseChannelsOpen() { this.channelsOpen--; } registerStream(stream) { this.registeredStreams.push(stream); } unregisterStream(stream) { this.registeredStreams = this.registeredStreams.filter((registeredStreams) => registeredStreams !== stream); } stopAllStreams() { for (const registeredStream of this.registeredStreams) { registeredStream.stop(); } } /** * Holds the connection if keepalive wasn't set * so when a channel opens, ensure that we * receive a response before a timeout */ holdConnection() { // If it's not the first connection to open // don't try to hold it again if (this.channelsOpen !== 1) return; if (this.connected && !this.holdingConnectionWithKeepalive) { if (this.connectionHoldInterval) timers_1.clearTimeout(this.connectionHoldInterval); const holdConnInterval = () => { this.connectionHoldInterval = setTimeout(() => { let chann = new Channel_1.Channel(this.connector); chann.on('close', () => { chann = null; }); chann .write(['#']) .then(() => { holdConnInterval(); }) .catch(() => { holdConnInterval(); }); }, (this.timeout * 1000) / 2); }; holdConnInterval(); } } /** * Release the connection that was held * when waiting for responses from channels open */ releaseConnectionHold() { // If there are channels still open // don't release the hold if (this.channelsOpen > 0) return; if (this.connectionHoldInterval) timers_1.clearTimeout(this.connectionHoldInterval); } /** * Login on the routerboard to provide * api functionalities, using the credentials * provided. * * @returns {Promise} */ login() { this.connecting = true; info('Sending 6.43+ login to %s', this.host); return this.write('/login', [ `=name=${this.user}`, `=password=${this.password}`, ]) .then((data) => { if (data.length === 0) { info('6.43+ Credentials accepted on %s, we are connected', this.host); return Promise.resolve(this); } else if (data.length === 1) { info('Received challenge on %s, will send credentials. Data: %o', this.host, data); const challenge = Buffer.alloc(this.password.length + 17); const challengeOffset = this.password.length + 1; // Here we have 32 chars with hex encoded 16 bytes of challenge data const ret = data[0].ret; challenge.write(String.fromCharCode(0) + this.password); // To write 32 hec chars to buffer as bytes we need to write 16 bytes challenge.write(ret, challengeOffset, ret.length / 2, 'hex'); const resp = '00' + crypto .createHash('MD5') .update(challenge) .digest('hex'); return this.write('/login', [ '=name=' + this.user, '=response=' + resp, ]) .then(() => { info('Credentials accepted on %s, we are connected', this.host); return Promise.resolve(this); }) .catch((err) => { if (err.message === 'cannot log in' || err.message === 'invalid user name or password (6)') { err = new RosException_1.RosException('CANTLOGIN'); } this.connector.destroy(); error("Couldn't loggin onto %s, Error: %O", this.host, err); return Promise.reject(err); }); } error('Unknown return from /login command on %s, data returned: %O', this.host, data); Promise.reject(new RosException_1.RosException('CANTLOGIN')); }) .catch((err) => { if (err.message === 'cannot log in' || err.message === 'invalid user name or password (6)') { err = new RosException_1.RosException('CANTLOGIN'); } this.connector.destroy(); error("Couldn't loggin onto %s, Error: %O", this.host, err); return Promise.reject(err); }); } concatParams(firstParameter, parameters) { if (typeof firstParameter === 'string') firstParameter = [firstParameter]; for (let parameter of parameters) { if (typeof parameter === 'string') parameter = [parameter]; if (parameter.length > 0) firstParameter = firstParameter.concat(parameter); } return firstParameter; } } exports.RouterOSAPI = RouterOSAPI; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUm91dGVyT1NBUEkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUm91dGVyT1NBUEkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSxxREFBa0Q7QUFDbEQsdUNBQW9DO0FBQ3BDLGlEQUE4QztBQUU5Qyx1Q0FBb0M7QUFDcEMsaUNBQWlDO0FBQ2pDLCtCQUErQjtBQUMvQixtQ0FBc0M7QUFDdEMsbUNBQXNDO0FBR3RDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO0FBQzVDLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO0FBRTlDOztHQUVHO0FBQ0gsTUFBYSxXQUFZLFNBQVEscUJBQVk7SUFxRnpDOzs7O09BSUc7SUFDSCxZQUFZLE9BQW9CO1FBQzVCLEtBQUssRUFBRSxDQUFDO1FBNURaOztXQUVHO1FBQ0ksY0FBUyxHQUFZLEtBQUssQ0FBQztRQUVsQzs7V0FFRztRQUNJLGVBQVUsR0FBWSxLQUFLLENBQUM7UUFFbkM7O1dBRUc7UUFDSSxZQUFPLEdBQVksS0FBSyxDQUFDO1FBc0JoQzs7V0FFRztRQUNLLGlCQUFZLEdBQVcsQ0FBQyxDQUFDO1FBRWpDOzs7V0FHRztRQUNLLG1DQUE4QixHQUFZLEtBQUssQ0FBQztRQVFoRCxzQkFBaUIsR0FBYyxFQUFFLENBQUM7UUFTdEMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM3QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLFVBQVUsQ0FBQyxPQUFvQjtRQUNsQyxJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDekIsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNqQyxJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFD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