mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-26 23:25:53 -05:00
324 lines
26 KiB
JavaScript
324 lines
26 KiB
JavaScript
|
/*
|
||
|
Copyright 2021 Intel Corporation
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
|
||
|
* @description Mini DHCP Client Module, to fetch configuration data
|
||
|
* @author Bryan Roe & Ylian Saint-Hilaire
|
||
|
*/
|
||
|
|
||
|
// DHCP Information
|
||
|
if (Function.prototype.internal == null) { Object.defineProperty(Function.prototype, 'internal', { get: function () { return (this); } }); }
|
||
|
if (global._hide == null)
|
||
|
{
|
||
|
global._hide = function _hide(v)
|
||
|
{
|
||
|
if(v==null || (v!=null && typeof(v)=='boolean'))
|
||
|
{
|
||
|
var ret = _hide.currentObject;
|
||
|
if (v) { _hide.currentObject = null; }
|
||
|
return (ret);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_hide.currentObject = v;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
addModule('promise2', Buffer.from('LyoNCkNvcHlyaWdodCAyMDE4IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgcmVmVGFibGUgPSB7fTsNCg0KZnVuY3Rpb24gcHJvbWlzZUluaXRpYWxpemVyKHIsaikNCnsNCiAgICB0aGlzLl9yZXMgPSByOw0KICAgIHRoaXMuX3JlaiA9IGo7DQp9DQoNCmZ1bmN0aW9uIGdldFJvb3RQcm9taXNlKG9iaikNCnsNCiAgICB3aGlsZShvYmoucGFyZW50UHJvbWlzZSkNCiAgICB7DQogICAgICAgIG9iaiA9IG9iai5wYXJlbnRQcm9taXNlOw0KICAgIH0NCiAgICByZXR1cm4gKG9iaik7DQp9DQoNCmZ1bmN0aW9uIGV2ZW50X3N3aXRjaGVyKGRlc2lyZWRfY2FsbGVlLCB0YXJnZXQpDQp7DQogICAgcmV0dXJuICh7IF9PYmplY3RJRDogJ2V2ZW50X3N3aXRjaGVyJywgZnVuYzogdGFyZ2V0LmJpbmQoZGVzaXJlZF9jYWxsZWUpIH0pOw0KfQ0KDQpmdW5jdGlvbiBldmVudF9mb3J3YXJkZXIoc291cmNlT2JqLCBzb3VyY2VOYW1lLCB0YXJnZXRPYmosIHRhcmdldE5hbWUpDQp7DQogICAgc291cmNlT2JqLm9uKHNvdXJjZU5hbWUsIHRhcmdldE9iai5lbWl0LmJpbmQodGFyZ2V0T2JqKSk7DQp9DQoNCg0KZnVuY3Rpb24gcmV0dXJuX3Jlc29sdmVkKCkNCnsNCiAgICB2YXIgcGFybXMgPSBbJ3Jlc29sdmVkJ107DQogICAgZm9yICh2YXIgYWkgaW4gYXJndW1lbnRzKQ0KICAgIHsNCiAgICAgICAgcGFybXMucHVzaChhcmd1bWVudHNbYWldKTsNCiAgICB9DQogICAgdGhpcy5fWFNMRi5lbWl0LmFwcGx5KHRoaXMuX1hTTEYsIHBhcm1zKTsNCn0NCmZ1bmN0aW9uIHJldHVybl9yZWplY3RlZCgpDQp7DQogICAgdGhpcy5fWFNMRi5wcm9taXNlLl9fY2hpbGRQcm9taXNlLl9yZWooZSk7DQp9DQpmdW5jdGlvbiBlbWl0cmVqZWN0KGEpDQp7DQogICAgcHJvY2Vzcy5lbWl0KCd1bmNhdWdodEV4Y2VwdGlvbicsICdwcm9taXNlLnVuY2F1Z2h0UmVqZWN0aW9uOiAnICsgSlNPTi5zdHJpbmdpZnkoYSkpOw0KfQ0KZnVuY3Rpb24gUHJvbWlzZShwcm9taXNlRnVuYykNCnsNCiAgICB0aGlzLl9PYmplY3RJRCA9ICdwcm9taXNlJzsNCiAgICB0aGlzLnByb21pc2UgPSB0aGlzOw0KICAgIHRoaXMuX2ludGVybmFsID0geyBfT2JqZWN0SUQ6ICdwcm9taXNlLmludGVybmFsJywgcHJvbWlzZTogdGhpcywgY29tcGxldGVkOiBmYWxzZSwgZXJyb3JzOiBmYWxzZSwgY29tcGxldGVkQXJnczogW10sIGludGVybmFsQ291bnQ6IDAsIF91cDogbnVsbCB9Ow0KICAgIHJlcXVpcmUoJ2V2ZW50cycpLkV2ZW50RW1pdHRlci5jYWxsKHRoaXMuX2ludGVybmFsKTsNCiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgInBhcmVudFByb21pc2UiLA0KICAgICAgICB7DQogICAgICAgICAgICBnZXQ6IGZ1bmN0aW9uICgpIHsgcmV0dXJuICh0aGlzLl91cCk7IH0sDQogICAgICAgICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBpZiAodmFsdWUgIT0gbnVsbCAmJiB0aGlzLl91cCA9PSBudWxsKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgLy8gV2UgYXJlIG5vIGxvbmdlciBhbiBvcnBoYW4NCiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0ICE9IG51bGwpDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgIGNsZWFySW1tZWRpYXRlKHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0KTsNCiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0ID0gbnVsbDsNCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICB0aGlzLl91cCA9IHZhbHVlOw0KICAgICAgICAgICAgfQ0KICAgICAgICB9KTsNCiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgImRlc2NyaXB0b3JNZXRhZGF0YSIsDQogICAgICAgIHsNCiAgICAgICAgICAgIGdldDogZnVuY3Rpb24gKCkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICByZXR1cm4gKHJlcXVpcmUoJ2V2ZW50cycpLmdldFByb3BlcnR5LmNhbGwodGhpcy5faW50ZXJuYWwsICc/X0ZpbmFsaXplckRlYnVnTWVzc2FnZScpKTsNCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICByZXF1aXJlKCdldmVudHMnKS5zZXRQcm9wZXJ0eS5jYWxsKHRoaXMuX2ludGVybmFsLCAnP19GaW5hbGl6ZXJEZWJ1Z01lc3NhZ2UnLCB2YWx1ZSk7DQogICAgICAgICAgICB9DQogICAgICAgIH0pOw0KICAgIHRoaXMuX2ludGVybmFsLm9uKCd+JywgZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHRoaXMuY29tcGxldGVkQXJncyA9IFtdOw0KICAgIH0pOw0KICAgIHRoaXMuX2ludGVybmFsLm9uKCduZXdMaXN0ZW5lcjInLCAoZnVuY3Rpb24gKGV2ZW50TmFtZSwgZXZlbnRDYWxsYmFjaykNCiAgICB7DQogICAgICAgIC8vY29uc29sZS5sb2coJ25ld0xpc3RlbmVyJywgZXZlbnROYW1lLCAnZXJyb3Jz
|
||
|
var promise = require('promise2');
|
||
|
function promise_default(res, rej)
|
||
|
{
|
||
|
this._res = res;
|
||
|
this._rej = rej;
|
||
|
}
|
||
|
|
||
|
|
||
|
function buf2addr(buf)
|
||
|
{
|
||
|
return (buf[0] + '.' + buf[1] + '.' + buf[2] + '.' + buf[3]);
|
||
|
}
|
||
|
function parseDHCP(buffer)
|
||
|
{
|
||
|
var i;
|
||
|
var packet = Buffer.alloc(buffer.length);
|
||
|
for (i = 0; i < buffer.length; ++i) { packet[i] = buffer[i]; }
|
||
|
|
||
|
var ret = { op: packet[0] == 0 ? 'REQ' : 'RES', hlen: packet[2] }; // OP Code
|
||
|
ret.xid = packet.readUInt32BE(4); // Transaction ID
|
||
|
ret.ciaddr = buf2addr(packet.slice(12, 16));
|
||
|
ret.yiaddr = buf2addr(packet.slice(16, 20));
|
||
|
ret.siaddr = buf2addr(packet.slice(20, 24));
|
||
|
ret.giaddr = buf2addr(packet.slice(24, 28));
|
||
|
ret.chaddr = packet.slice(28, 28 + ret.hlen).toString('hex:');
|
||
|
if (packet[236] == 99 && packet[237] == 130 && packet[238] == 83 && packet[239] == 99)
|
||
|
{
|
||
|
// Magic Cookie Validated
|
||
|
ret.magic = true;
|
||
|
ret.options = {};
|
||
|
|
||
|
i = 240;
|
||
|
while(i<packet.length)
|
||
|
{
|
||
|
switch(packet[i])
|
||
|
{
|
||
|
case 0:
|
||
|
i += 1;
|
||
|
break;
|
||
|
case 255:
|
||
|
ret.options[255] = true;
|
||
|
i += 2;
|
||
|
break;
|
||
|
default:
|
||
|
ret.options[packet[i]] = packet.slice(i + 2, i + 2 + packet[i + 1]);
|
||
|
switch(packet[i])
|
||
|
{
|
||
|
case 1: // Subnet Mask
|
||
|
ret.options.subnetmask = buf2addr(ret.options[1]);
|
||
|
delete ret.options[1];
|
||
|
break;
|
||
|
case 3: // Router
|
||
|
ret.options.router = [];
|
||
|
var ti = 0;
|
||
|
while (ti < ret.options[3].length)
|
||
|
{
|
||
|
ret.options.router.push(buf2addr(ret.options[3].slice(ti, ti + 4)));
|
||
|
ti += 4;
|
||
|
}
|
||
|
delete ret.options[3];
|
||
|
break;
|
||
|
case 6: // DNS
|
||
|
ret.options.dns = buf2addr(ret.options[6]);
|
||
|
delete ret.options[6];
|
||
|
break;
|
||
|
case 15: // Domain Name
|
||
|
ret.options.domainname = ret.options[15].toString();
|
||
|
delete ret.options[15];
|
||
|
break;
|
||
|
case 28: // Broadcast Address
|
||
|
ret.options.broadcastaddr = buf2addr(ret.options[28]);
|
||
|
delete ret.options[28];
|
||
|
break;
|
||
|
case 51: // Lease Time
|
||
|
ret.options.lease = { raw: ret.options[51].readInt32BE() };
|
||
|
delete ret.options[51];
|
||
|
ret.options.lease.hours = Math.floor(ret.options.lease.raw / 3600);
|
||
|
ret.options.lease.minutes = Math.floor((ret.options.lease.raw % 3600) / 60);
|
||
|
ret.options.lease.seconds = (ret.options.lease.raw % 3600) % 60;
|
||
|
break;
|
||
|
case 53: // Message Type
|
||
|
ret.options.messageType = ret.options[53][0];
|
||
|
delete ret.options[53];
|
||
|
break;
|
||
|
case 54: // Server
|
||
|
ret.options.server = buf2addr(ret.options[54]);
|
||
|
delete ret.options[54];
|
||
|
break;
|
||
|
}
|
||
|
i += (2 + packet[i + 1]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
function createPacket(messageType, data)
|
||
|
{
|
||
|
var b = Buffer.alloc(245);
|
||
|
|
||
|
switch(messageType)
|
||
|
{
|
||
|
//case 0x02:
|
||
|
//case 0x04:
|
||
|
//case 0x05:
|
||
|
//case 0x06:
|
||
|
// b[0] = 0x00; // Reply
|
||
|
// break;
|
||
|
//case 0x01:
|
||
|
//case 0x03:
|
||
|
//case 0x07:
|
||
|
case 0x08:
|
||
|
b[0] = 0x01; // Request
|
||
|
break;
|
||
|
default:
|
||
|
throw ('DHCP(' + messageType + ') NOT SUPPORTED');
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Headers
|
||
|
b[1] = 0x01; // Ethernet
|
||
|
b[2] = 0x06; // HW Address Length
|
||
|
b[3] = 0x00; // HOPS
|
||
|
|
||
|
// Transaction ID
|
||
|
var r = Buffer.alloc(4); r.randomFill();
|
||
|
b.writeUInt32BE(r.readUInt32BE(), 4);
|
||
|
b.writeUInt16BE(0x8000, 10);
|
||
|
|
||
|
// Magic Cookie
|
||
|
b[236] = 99;
|
||
|
b[237] = 130;
|
||
|
b[238] = 83;
|
||
|
b[239] = 99;
|
||
|
|
||
|
// DHCP Message Type
|
||
|
b[240] = 53;
|
||
|
b[241] = 1;
|
||
|
b[242] = messageType;
|
||
|
b[243] = 255;
|
||
|
|
||
|
switch(messageType)
|
||
|
{
|
||
|
case 0x08:
|
||
|
if (data.ciaddress == null) { throw ('ciadress missing'); }
|
||
|
if (data.chaddress == null) { throw ('chaddress missing'); }
|
||
|
|
||
|
// ciaddress
|
||
|
var a = data.ciaddress.split('.');
|
||
|
var ci = parseInt(a[0]);
|
||
|
ci = ci << 8;
|
||
|
ci = ci | parseInt(a[1]);
|
||
|
ci = ci << 8;
|
||
|
ci = ci | parseInt(a[2]);
|
||
|
ci = ci << 8;
|
||
|
ci = ci | parseInt(a[3]);
|
||
|
b.writeInt32BE(ci, 12);
|
||
|
|
||
|
// chaddress
|
||
|
var y = data.chaddress.split(':').join('');
|
||
|
y = Buffer.from(y, 'hex');
|
||
|
y.copy(b, 28);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return (b);
|
||
|
}
|
||
|
|
||
|
function raw(localAddress, port, buffer, handler)
|
||
|
{
|
||
|
var ret = new promise(promise_default);
|
||
|
ret.socket = require('dgram').createSocket({ type: 'udp4' });
|
||
|
try
|
||
|
{
|
||
|
ret.socket.bind({ address: localAddress, port: (port != null && port != 0) ? port : null });
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
ret._rej('Unable to bind to ' + localAddress);
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
ret.socket.setBroadcast(true);
|
||
|
ret.socket.setMulticastInterface(localAddress);
|
||
|
ret.socket.setMulticastTTL(1);
|
||
|
ret.socket.descriptorMetadata = 'DHCP (' + localAddress + ')';
|
||
|
ret.socket.on('message', handler.bind(ret));
|
||
|
ret.socket.send(buffer, 67, '255.255.255.255');
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
function info(interfaceName, port)
|
||
|
{
|
||
|
var f = require('os').networkInterfaces();
|
||
|
if (interfaceName.split(':').length == 6)
|
||
|
{
|
||
|
var newname = null;
|
||
|
for(var n in f)
|
||
|
{
|
||
|
for (var nx in f[n])
|
||
|
{
|
||
|
if(f[n][nx].mac.toUpperCase() == interfaceName.toUpperCase())
|
||
|
{
|
||
|
newname = n;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(newname)
|
||
|
{
|
||
|
interfaceName = newname;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (f[interfaceName] != null)
|
||
|
{
|
||
|
var i;
|
||
|
for(i=0;i<f[interfaceName].length;++i)
|
||
|
{
|
||
|
if(f[interfaceName][i].family == 'IPv4' && f[interfaceName][i].mac != '00:00:00:00:00:00')
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var b = createPacket(8, { ciaddress: f[interfaceName][i].address, chaddress: f[interfaceName][i].mac });
|
||
|
_hide(raw(f[interfaceName][i].address, port, b, function infoHandler(msg)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var res = parseDHCP(msg);
|
||
|
if (res.chaddr.toUpperCase() == this.hwaddr.toUpperCase() && res.options != null && res.options.lease != null)
|
||
|
{
|
||
|
clearTimeout(this.timeout);
|
||
|
setImmediate(function (s) { try { s.removeAllListeners('message'); } catch (x) { } }, this.socket); // Works around bug in older dgram.js
|
||
|
this._res(res);
|
||
|
}
|
||
|
}
|
||
|
catch(z)
|
||
|
{
|
||
|
}
|
||
|
}));
|
||
|
_hide().hwaddr = f[interfaceName][i].mac;
|
||
|
_hide().timeout = setTimeout(function (x)
|
||
|
{
|
||
|
x.socket.removeAllListeners('message');
|
||
|
x._rej('timeout');
|
||
|
}, 2000, _hide());
|
||
|
return (_hide(true));
|
||
|
}
|
||
|
catch(e)
|
||
|
{
|
||
|
var ret = new promise(promise_default);
|
||
|
ret._rej(e);
|
||
|
return (ret);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ret = new promise(promise_default);
|
||
|
ret._rej('interface (' + interfaceName + ') not found');
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
module.exports =
|
||
|
{
|
||
|
client: { info: info, raw: raw },
|
||
|
MESSAGE_TYPES:
|
||
|
{
|
||
|
DISCOVER: 1,
|
||
|
OFFER: 2,
|
||
|
REQUEST: 3,
|
||
|
DECLINE: 4,
|
||
|
ACK: 5,
|
||
|
NACK: 6,
|
||
|
RELEASE: 7,
|
||
|
INFO: 8
|
||
|
}
|
||
|
};
|
||
|
|