node-routeros-altostrat
Version:
Mikrotik Routerboard RouterOS API for NodeJS
420 lines • 29.8 kB
JavaScript
"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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