UNPKG

routeros-api

Version:

Mikrotik Routerboard RouterOS API for NodeJS

417 lines 29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RouterOSAPI = void 0; 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; } /** * 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 either for just writing or streaming * * @returns {Channel} */ openChannel() { this.increaseChannelsOpen(); return new Channel_1.Channel(this.connector); } 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUm91dGVyT1NBUEkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUm91dGVyT1NBUEkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQWtEO0FBQ2xELHVDQUFvQztBQUNwQyxpREFBOEM7QUFFOUMsdUNBQW9DO0FBQ3BDLGlDQUFpQztBQUNqQywrQkFBK0I7QUFDL0IsbUNBQXNDO0FBQ3RDLG1DQUFzQztBQUd0QyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsdUJBQXVCLENBQUMsQ0FBQztBQUM1QyxNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUU5Qzs7R0FFRztBQUNILE1BQWEsV0FBWSxTQUFRLHFCQUFZO0lBZ0Z6Qzs7OztPQUlHO0lBQ0gsWUFBWSxPQUFvQjtRQUM1QixLQUFLLEVBQUUsQ0FBQztRQXZEWjs7V0FFRztRQUNJLGNBQVMsR0FBWSxLQUFLLENBQUM7UUFFbEM7O1dBRUc7UUFDSSxlQUFVLEdBQVksS0FBSyxDQUFDO1FBRW5DOztXQUVHO1FBQ0ksWUFBTyxHQUFZLEtBQUssQ0FBQztRQWlCaEM7O1dBRUc7UUFDSyxpQkFBWSxHQUFXLENBQUMsQ0FBQztRQUVqQzs7O1dBR0c7UUFDSyxtQ0FBOEIsR0FBWSxLQUFLLENBQUM7UUFRaEQsc0JBQWlCLEdBQWMsRUFBRSxDQUFDO1FBU3RDLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxVQUFVLENBQUMsT0FBb0I7UUFDbEMsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztRQUN6QixJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDakMsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxJQUFJLElBQUksQ0FB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