From 1d8bd0c989e43570b9c885285fccb89d84f7f64e Mon Sep 17 00:00:00 2001 From: Alex Rennie-Lis Date: Fri, 5 Apr 2024 22:57:26 +0100 Subject: [PATCH] Initial push 0.2.0 --- code/index.js | 178 ++++ code/lib/data.js | 81 ++ code/lib/handlers.js | 63 ++ code/lib/logger.js | 6 + code/node_modules/.package-lock.json | 16 + code/node_modules/radius/.npmignore | 1 + code/node_modules/radius/.travis.yml | 7 + code/node_modules/radius/LICENSE | 24 + code/node_modules/radius/README.md | 258 +++++ code/node_modules/radius/accounting.js | 48 + code/node_modules/radius/decode.js | 29 + .../radius/dictionaries/dictionary.rfc2865 | 137 +++ .../radius/dictionaries/dictionary.rfc2866 | 56 + .../radius/dictionaries/dictionary.rfc2867 | 16 + .../radius/dictionaries/dictionary.rfc2868 | 54 + .../radius/dictionaries/dictionary.rfc2869 | 39 + .../radius/dictionaries/dictionary.rfc3162 | 13 + .../radius/dictionaries/dictionary.rfc3576 | 30 + .../radius/dictionaries/dictionary.rfc3580 | 16 + .../radius/dictionaries/dictionary.rfc4072 | 9 + .../radius/dictionaries/dictionary.rfc4372 | 8 + .../radius/dictionaries/dictionary.rfc4603 | 17 + .../radius/dictionaries/dictionary.rfc4675 | 28 + .../radius/dictionaries/dictionary.rfc4679 | 62 ++ .../radius/dictionaries/dictionary.rfc4818 | 11 + .../radius/dictionaries/dictionary.rfc4849 | 8 + .../radius/dictionaries/dictionary.rfc5090 | 27 + .../radius/dictionaries/dictionary.rfc5176 | 9 + .../radius/dictionaries/dictionary.rfc5580 | 41 + .../radius/dictionaries/dictionary.rfc5607 | 30 + .../radius/dictionaries/dictionary.rfc5904 | 22 + .../radius/examples/auth_client.js | 81 ++ .../radius/examples/auth_server.js | 54 + code/node_modules/radius/lib/radius.js | 886 ++++++++++++++++ code/node_modules/radius/package.json | 19 + code/node_modules/radius/short_password.js | 53 + .../test/captures/aruba_mac_auth.packet | Bin 0 -> 208 bytes .../test/captures/cisco_accounting.packet | Bin 0 -> 194 bytes .../captures/cisco_accounting_response.packet | Bin 0 -> 20 bytes .../test/captures/cisco_mac_auth.packet | Bin 0 -> 197 bytes .../captures/cisco_mac_auth_reject.packet | Bin 0 -> 20 bytes .../radius/test/captures/eap_request.packet | Bin 0 -> 396 bytes .../test/captures/invalid_register.packet | Bin 0 -> 902 bytes .../test/captures/motorola_accounting.packet | Bin 0 -> 208 bytes .../test/dictionaries/dictionary.airespace | 16 + .../radius/test/dictionaries/dictionary.aruba | 11 + .../dictionary.number_vendor_name | 8 + .../radius/test/dictionaries/dictionary.test1 | 3 + .../radius/test/dictionaries/dictionary.test2 | 4 + .../dictionaries/dictionary.test_tunnel_type | 3 + code/node_modules/radius/test/radius.test.js | 970 ++++++++++++++++++ code/package-lock.json | 24 + code/package.json | 20 + config/config.json | 10 + data.json | 9 + 55 files changed, 3515 insertions(+) create mode 100644 code/index.js create mode 100644 code/lib/data.js create mode 100644 code/lib/handlers.js create mode 100644 code/lib/logger.js create mode 100644 code/node_modules/.package-lock.json create mode 100644 code/node_modules/radius/.npmignore create mode 100644 code/node_modules/radius/.travis.yml create mode 100644 code/node_modules/radius/LICENSE create mode 100644 code/node_modules/radius/README.md create mode 100644 code/node_modules/radius/accounting.js create mode 100644 code/node_modules/radius/decode.js create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc2865 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc2866 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc2867 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc2868 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc2869 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc3162 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc3576 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc3580 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4072 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4372 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4603 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4675 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4679 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4818 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc4849 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc5090 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc5176 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc5580 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc5607 create mode 100644 code/node_modules/radius/dictionaries/dictionary.rfc5904 create mode 100644 code/node_modules/radius/examples/auth_client.js create mode 100644 code/node_modules/radius/examples/auth_server.js create mode 100644 code/node_modules/radius/lib/radius.js create mode 100644 code/node_modules/radius/package.json create mode 100644 code/node_modules/radius/short_password.js create mode 100644 code/node_modules/radius/test/captures/aruba_mac_auth.packet create mode 100644 code/node_modules/radius/test/captures/cisco_accounting.packet create mode 100644 code/node_modules/radius/test/captures/cisco_accounting_response.packet create mode 100644 code/node_modules/radius/test/captures/cisco_mac_auth.packet create mode 100644 code/node_modules/radius/test/captures/cisco_mac_auth_reject.packet create mode 100644 code/node_modules/radius/test/captures/eap_request.packet create mode 100644 code/node_modules/radius/test/captures/invalid_register.packet create mode 100644 code/node_modules/radius/test/captures/motorola_accounting.packet create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.airespace create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.aruba create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.number_vendor_name create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.test1 create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.test2 create mode 100644 code/node_modules/radius/test/dictionaries/dictionary.test_tunnel_type create mode 100755 code/node_modules/radius/test/radius.test.js create mode 100644 code/package-lock.json create mode 100644 code/package.json create mode 100644 config/config.json create mode 100644 data.json diff --git a/code/index.js b/code/index.js new file mode 100644 index 0000000..782872e --- /dev/null +++ b/code/index.js @@ -0,0 +1,178 @@ +// Baseline +const product = 'NetRadius'; +const version = '0.2.0'; + +// Load dependencies +const dgram = require ('dgram'); +const fs = require ('fs'); +const http = require ('http'); + +// Load modules +log = require ('./lib/logger.js'); +const handlers = require ('./lib/handlers.js'); + +// Load configuration +log.write (product + ' v' + version); +config = {}; +try { + config = JSON.parse (fs.readFileSync ('./config.json').toString ()); +} +catch (error) { + log.write ('Cannot open or read configuration file.'); + log.write ('Using defaults'); + config = { + ports: { + radius_authentication: 1812, + radius_accounting: 1813 + }, + storage: "json:./data.json", + client_secret: "password", + default_vlan_enabled: false + } +} +if (process.env['NETRADIUS_PORT_RADIUS_AUTH']) config.ports.radius_authentication = process.env['NETRADIUS_PORT_RADIUS_AUTH']; +if (process.env['NETRADIUS_PORT_RADIUS_ACCT']) config.ports.radius_accounting = process.env['NETRADIUS_PORT_RADIUS_ACCT']; +if (process.env['NETRADIUS_STORAGE']) config.storage = process.env['NETRADIUS_STORAGE']; +if (process.env['NETRADIUS_DEFAULT_VLAN']) config.default_vlan_enabled = process.env['NETRADIUS_DEFAULT_VLAN']; +if (process.env['NETRADIUS_DEFAULT_VLAN_ID']) config.default_vlan_id = process.env['NETRADIUS_DEFAULT_VLAN_ID']; +if (process.env['NETRADIUS_CLIENT_SECRET']) config.client_secret = process.env['NETRADIUS_CLIENT_SECRET']; +log.write ('Using configuration: ' + JSON.stringify (config)); + +// Listeners +var listeners = { + authentication: { + socket: dgram.createSocket ({ type: 'udp4', reuseAddr: true }), + handler: handlers.radius.authentication + }, + accounting: { + socket: dgram.createSocket ({ type: 'udp4', reuseAddr: true }), + handler: handlers.radius.accounting + } +}; + +// RADIUS authentication +listeners.authentication.socket.on ('message', (msg, rinfo) => { + handlers.radius.authentication (msg, rinfo, (response, err) => { + if (!err) { + listeners.authentication.socket.send (response, 0, response.length, rinfo.port, rinfo.address, function (err, bytes) { + if (err) { + console.log ('Error sending response to ', rinfo); + } + }); + } + }); +}); +listeners.authentication.socket.on ('listening', () => { + var address = listeners.authentication.socket.address (); + log.write ("Authentication listening on " + address.address + ":" + address.port); +}); +listeners.authentication.socket.bind (config.ports.radius_authentication); + +// RADIUS accounting +listeners.accounting.socket.on ('message', (msg, rinfo) => { + handlers.radius.accounting (msg, rinfo, (response, err) => { + if (!err) { + listeners.accounting.socket.send (response, 0, response.length, rinfo.port, rinfo.address, function (err, bytes) { + if (err) { + console.log ('Error sending response to ', rinfo); + } + }); + } + }); +}); +listeners.accounting.socket.on ('listening', () => { + var address = listeners.accounting.socket.address (); + log.write ("Accounting listening on " + address.address + ":" + address.port); +}); +listeners.accounting.socket.bind (config.ports.radius_accounting); + +// HTTP listener +http.createServer(function (req, res) { + var url = req.url.substring (0, req.url.lastIndexOf ("/")) || req.url; + var endpoint = req.method + " " + url; + switch (endpoint) { + + case "GET /health": + res.write ('OK\n\n'); + res.statusCode = 200; + res.end (); + break; + + case "POST /user": + var payload = ''; + req.on ('data', chunk => { + payload += chunk.toString (); + }); + req.on ('end', () => { + handlers.user.create (payload, (status, err) => { + if (err) { + res.write ('Error\n'); + res.statusCode = 500; + res.end (); + } + else { + res.write (status); + res.statusCode = 200; + res.end (); + } + }); + }); + break; + + case "UPDATE /user": + var payload = ''; + req.on ('data', chunk => { + payload += chunk.toString (); + }); + req.on ('end', () => { + handlers.user.update (payload, (status, err) => { + if (err) { + res.write ('Error\n'); + res.statusCode = 500; + res.end (); + } + else { + res.write (status); + res.statusCode = 200; + res.end (); + } + }); + }); + break; + + case "DELETE /user": + handlers.user.delete (req.url.substring (req.url.lastIndexOf ("/") + 1), (status, err) => { + if (err) { + res.write ('Error\n'); + res.statusCode = 500; + res.end (); + } + else { + res.write (status); + res.statusCode = 200; + res.end (); + } + }); + break; + + default: + res.write ('Not found\n'); + res.statusCode = 404; + res.end (); + + } +}).listen (8080); + +// Exit handles +const exitHandler = () => { + log.write ('Shutting down'); + listeners.authentication.socket.close (); + listeners.accounting.socket.close (); + log.write ('Exiting'); + process.exit (); +} + +process.on ('SIGTERM', exitHandler); +process.on ('SIGINT', exitHandler); +process.on ('SIGUSR1', exitHandler); +process.on ('SIGUSR2', exitHandler); \ No newline at end of file diff --git a/code/lib/data.js b/code/lib/data.js new file mode 100644 index 0000000..69ed050 --- /dev/null +++ b/code/lib/data.js @@ -0,0 +1,81 @@ +const fs = require ('fs'); + +try { + var data = JSON.parse (fs.readFileSync ('./data.json').toString ()); + log.write ('Opened data file'); +} +catch (error) { + var data = { + users: [] + }; + fs.writeFileSync ('./data.json', JSON.stringify (data, null, 2)); +} + +const persistData = () => { + var content = { + users: [] + }; + Object.keys (users).forEach ((user) => { + content.users.push ({ + username: user, + password: users[user].password, + vlan: users[user].vlan + }); + }); + fs.writeFileSync ('./data.json', JSON.stringify (content, null, 2)); +} + +users = {}; +data.users.forEach ((e) => { + users[e.username] = { + password: e.password, + vlan: e.vlan + } +}); + +module.exports = { + authUser: (username, password) => { + if (users[username] && users[username].password == password) { + return { + vlan: users[username].vlan + }; + } + else { + return false; + } + }, + + createUser: (payload, callback) => { + try { + payload = JSON.parse (payload); + var username = payload.username; + var password = payload.password; + var vlan = payload.vlan; + users[username] = { + password: password, + vlan: vlan + }; + persistData (); + callback ("OK\n\n", null); + } + catch (error) { + callback (null, "Error\n\n"); + } + }, + + updateUser: (payload, callback) => { + payload = JSON.parse (payload); + callback ("OK\n\n", null); + }, + + deleteUser: (username, callback) => { + try { + delete users[username]; + persistData (); + callback ("OK\n\n", null); + } + catch (error) { + callback (null, "Error\n\n"); + } + } +} \ No newline at end of file diff --git a/code/lib/handlers.js b/code/lib/handlers.js new file mode 100644 index 0000000..7979ebe --- /dev/null +++ b/code/lib/handlers.js @@ -0,0 +1,63 @@ +const radius = require ('radius'); +const data = require ('./data.js'); + +module.exports = { + radius: { + authentication: (msg, info, callback) => { + try { + var decoded = radius.decode ({ + packet: msg, + secret: config.client_secret + }); + if (decoded.code == 'Access-Request') { + var username = decoded.attributes['User-Name']; + var password = decoded.attributes['User-Password']; + var user = data.authUser (username, password); + var vlan = false; + if (user) { + log.write (username + " access granted to VLAN " + user.vlan); + code = 'Access-Accept'; + vlan = user.vlan; + } + else { + if (config.default_vlan_enabled && config.default_vlan_id) { + // Permit into default vlan if enabled + log.write (username + " unknown. Placing into default VLAN."); + code = 'Access-Accept'; + vlan = config.default_vlan_id; + } + else { + log.write (username + " access denied."); + code = 'Access-Reject'; + } + } + var response = radius.encode_response ({ + packet: decoded, + code: code, + secret: config.client_secret, + attributes: { + "Tunnel-Medium-Type": 6, + "Tunnel-Type": 13, + "Tunnel-Private-Group-Id": vlan + } + }); + callback (response, null); + } + } + catch (error) { + log.write ('Cannot read RADIUS packet'); + log.write (error); + callback (null, "Error. Cannot read RADIUS packet."); + } + }, + accounting: (msg, info, callback) => { + log.write ('Unsupported RADIUS packet'); + callback (null, "Error. Unsupported RADIUS packet."); + } + }, + user: { + create: data.createUser, + update: data.updateUser, + delete: data.deleteUser + } +} \ No newline at end of file diff --git a/code/lib/logger.js b/code/lib/logger.js new file mode 100644 index 0000000..249b569 --- /dev/null +++ b/code/lib/logger.js @@ -0,0 +1,6 @@ +module.exports = { + write: (message) => { + let now = new Date (); + console.log (now.toISOString () + ' ' + message); + } +} \ No newline at end of file diff --git a/code/node_modules/.package-lock.json b/code/node_modules/.package-lock.json new file mode 100644 index 0000000..7890269 --- /dev/null +++ b/code/node_modules/.package-lock.json @@ -0,0 +1,16 @@ +{ + "name": "netradius", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==", + "engines": { + "node": ">=0.8.0" + } + } + } +} diff --git a/code/node_modules/radius/.npmignore b/code/node_modules/radius/.npmignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/code/node_modules/radius/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/code/node_modules/radius/.travis.yml b/code/node_modules/radius/.travis.yml new file mode 100644 index 0000000..dd02fcf --- /dev/null +++ b/code/node_modules/radius/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - lts/* + - 6 + - "0.10" + - "0.12" + - "4.3" diff --git a/code/node_modules/radius/LICENSE b/code/node_modules/radius/LICENSE new file mode 100644 index 0000000..f8335d9 --- /dev/null +++ b/code/node_modules/radius/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Nearbuy Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Nearbuy Systems nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL NEARBUY SYSTEMS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/code/node_modules/radius/README.md b/code/node_modules/radius/README.md new file mode 100644 index 0000000..673f03f --- /dev/null +++ b/code/node_modules/radius/README.md @@ -0,0 +1,258 @@ +# node-radius [![Build Status](https://secure.travis-ci.org/retailnext/node-radius.png)](http://travis-ci.org/retailnext/node-radius) - A RADIUS library for node.js + +node-radius is a RADIUS packet encoding/decoding library for node.js written in Javascript. With node-radius you can easily decode received packets, encode packets to send, and prepare responses to received packets. node-radius supports both RADIUS authentication and RADIUS accounting packets. + +node-radius requires node.js v0.8.0. To install node-radius, simply run `npm install radius` in your project directory. + +Let's look at some examples of how to use node-radius: + + var radius = require('radius'); + + // ... receive raw_packet from UDP socket + + var decoded = radius.decode({ packet: raw_packet, secret: "shared_secret" }); + +"decoded" might look something like this: + + { + code: 'Access-Request', + identifer: 123, + length: 250, + attributes: { + 'NAS-IP-Address': '10.1.2.3', + 'User-Name': 'jlpicard', + 'User-Password': 'beverly123', + 'Vendor-Specific': { + 'Federation-Starship': 'Enterprise' + } + } + } + +To prepare your response packet, use the encode_response function: + + var response = radius.encode_response({ + packet: decoded, + code: "Access-Accept", + secret: "section31" + }); + +To prepare a stand-alone packet, try this: + + var packet = radius.encode({ + code: "Access-Request", + secret: "obsidian order", + attributes: [ + ['NAS-IP-Address', '10.5.5.5'], + ['User-Name', 'egarak'], + ['User-Password', 'tailoredfit'], + ['Vendor-Specific', 555, [['Real-Name', 'arobinson']]] + ] + }); + + +## Method descriptions: + +### radius.decode(\) + +decode takes as input an object with the following fields: + +- packet (required): a Buffer containing the raw UDP RADIUS packet (as read off a socket) +- secret (required): a String containing the RADIUS shared secret + +Using the dictionaries available, decode parses the raw packet and yields an object representation of the packet. The object has the following fields: + +- code: string representation of the packet code ("Access-Request", "Accounting-Response", etc) +- identifier: packet identifier number (used for duplicate packet detection) +- length: RADIUS packet length +- attributes: an object containing all attributes node-radius knew how to parse. If an attribute is repeated, its value in the "attributes" object will become an Array containing each value. Unfortunately the dictionary files do not specify which attributes are repeatable, so if an attribute might be repeated then you need to check if the value in "attributes" is a scalar value or an Array. +- raw_attributes: an array of arrays containing each raw attribute (attribute type and a Buffer containing the attribute value). This is mainly used by node-radius for generating the response packet, and would only be useful to you if you are missing relevant dictionaries and/or want to decode attributes yourself. + +When decoding requests (e.g. "Access-Request", "Accounting-Request"), decode will automatically verify the request authenticator and the Message-Authenticator attribute, if present. If the request doesn't check out, decode will raise an error. The error, an instance of Radius.InvalidSecretError, has a "decoded" field you can use to inspect the decoded but invalid message. The most common reason for an incorrect authenticator is using the wrong shared secret. + +### radius.decode_without_secret(\) + +Identical to decode, but does not need the secret. This can be useful to "pre-decode" a message, in order to look-up (or calculate) the secret to be used to properly decode the message later. + +A message decoded without a secret will contain null values for encrypted fields (typically passwords, etc.). + +### radius.encode(\) + +encode takes an object for arguments and returns a Buffer ready to be sent over the wire. The accepted arguments are: + +- code (required): string representation of the packet code ("Access-Request", "Accounting-Response", etc) +- secret (required): RADIUS shared secret +- identifier (optional): packet identifer number (defaults to a random number from 0 to 255) +- attributes (optional): RADIUS attributes you want to add to the packet +- add_message_authenticator (optional): a boolean value controlling whether the library adds the Message-Authenticator HMAC to the packet. See below for more details. + +encode will automatically add the Message-Authenticator when: + +- encoding a message with an "EAP-Message" attribute +- encoding a "Status-Server" message +- you manually pass the "add_message_authenticator: true" option + +encode will not add the Message-Authenticator when: + +- the attributes already contain a "Message-Authenticator" attribute +- you manually pass the "add_message_authenticator: false" option + +The attributes will typically be like the following (see above example): + + attributes: [ + [, ], + ... + ] + +If you don't care about attribute ordering, you can use a hash for the attributes: + + attributes: { + : , + ... + } + +If you want to send attributes that you haven't loaded a dictionary for, you can do: + + attributes: [ + [, ], + ... + ] + +Where the first item is the numeric attribute id and the second item is just a Buffer containing the value of the attribute (not including length). + +You can specify Vendor-Specific attributes like so: + + attributes: [ + ['Vendor-Specific', , [ + [, ], + [, ] + ], + ... + ] + +Or if you want each vendor attribute as a separate attribute, try this: + + attributes: [ + ['Vendor-Specific', , [[, ]]], + ['Vendor-Specific', , [[, ]]] + ... + ] + +Like regular attributes, you can also specify the attribute id and a raw Buffer value for VSAs. If your dictionary specifies vendor attributes using the BEGIN-VENDOR/END-VENDOR format, you can use the symbolic vendor name as defined in the dictionary in place of the numeric \. + +You can specify the tag field-attribute like so (see RFC2868): + + attributes: [ + [, , ], + ... + ] + +If the attribute has an optional tag and you don't want to send it, then only specify the \ and the \. + +### radius.encode\_response(\) + +encode_response prepares a response packet based on previously received and decoded packet. "args" is an object with the following properties: + +- packet (required): the output of a previous call to radius.decode +- code (required): String representation of the packet code ("Access-Reject, "Accounting-Response", etc) +- attributes (optional): RADIUS attributes you want to add to the packet + +encode_response does a few things for you to prepare the response: + +1. sets the response packet's message identifier to the identifer of the previously received packet +1. copies any "Proxy-State" attributes from the previously received packet into the response packet +1. calculates the appropriate response authenticator based on the request's authenticator +1. calculates and adds a Message-Authenticator attribute if the request contained one + +### radius.verify\_response(\) + +verify_response checks the authenticator and Message-Authenticator attribute, if applicable, of a response packet you receive. It returns true if the packet checks out, and false otherwise (likely because the other side's shared secret is wrong). "args" is an object with the following properties: + +- request (required): the request packet you previously sent (should be the raw packet, i.e. the output of a call to radius.encode) +- response (required): the response you received to your request packet (again, the raw packet) +- secret (required): RADIUS shared secret + +This method is useful if you are acting as the NAS. For example, if you send an "Access-Request", you can use this method to verify the response you get ("Reject" or "Accept") is legitimate. + +Note that if the request contained a Message-Authenticator, the response must also contain a Message-Authenticator. + +## Dictionaries + +node-radius supports reading freeradius-style RADIUS dictionary files. node-radius comes with a slew of RFC dictionary files, so you should only worry about adding any vendor-specific dictionary files you have. node-radius will load all the dictionaries it knows about (the default RFC ones and any you added) automatically the first time it needs to, so you should add your dictionaries before you start to use the module. + +### radius.add_dictionary(\) + +To add a dictionary to be loaded, use the **add_dictionary** function: + + var radius = require('radius'); + + radius.add_dictionary('/path/to/my/dictionary'); + +add\_dictionary takes either a file or a directory (given a directory, it assumes everything in the directory is a dictionary file). add\_dictionary does not block or perform any IO. It simply adds the given path to a list which is used to load dictionaries later. + +node-radius supports reading both the VENDORATTR and the BEGIN-VENDOR/END-VENDOR style for defining VSAs. node-radius also supports reading the following attribute modifiers: has_tag, encrypt=1. + +node-radius will also follow "$INCLUDE" directives inside of dictionary files (to load other dictionary files). + +## Example usage + +The following is an example of a simple radius authentication server: + + var radius = require('radius'); + var dgram = require("dgram"); + + var secret = 'radius_secret'; + var server = dgram.createSocket("udp4"); + + server.on("message", function (msg, rinfo) { + var code, username, password, packet; + packet = radius.decode({packet: msg, secret: secret}); + + if (packet.code != 'Access-Request') { + console.log('unknown packet type: ', packet.code); + return; + } + + username = packet.attributes['User-Name']; + password = packet.attributes['User-Password']; + + console.log('Access-Request for ' + username); + + if (username == 'jlpicard' && password == 'beverly123') { + code = 'Access-Accept'; + } else { + code = 'Access-Reject'; + } + + var response = radius.encode_response({ + packet: packet, + code: code, + secret: secret + }); + + console.log('Sending ' + code + ' for user ' + username); + server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, bytes) { + if (err) { + console.log('Error sending response to ', rinfo); + } + }); + }); + + server.on("listening", function () { + var address = server.address(); + console.log("radius server listening " + + address.address + ":" + address.port); + }); + + server.bind(1812); + +Client and server examples can be found in the examples directory. + +## Important notes: + +- node-radius in general does _not_ perform "higher-level" protocol validation, so for example node-radius will not complain if you encode an Access-Request packet but fail to include a NAS-IP-Address or NAS-Identifier. +- node-radius in general assumes most strings are UTF-8 encoded. This will work fine for ASCII and UTF-8 strings, but will not work for other encodings. At some point I might add an "encoding" option to override this default encoding, and/or a "raw" mode that just deals with Buffers (rather than Strings) when the encoding is not known. +- node-radius does not support non-standard VSAs (where type or length field for attributes are not one octet each). +- node-radius does not support special decoding/encoding for the following attribute types: ipv6addr, ifid, ipv6prefix, short. If node-radius encounters a type it doesn't support, node-radius will return a raw Buffer when decoding, and expect a Buffer when encoding. +- node-radius does not support any password encryption types other than that defined by RFC2865 for User-Password (e.g. does not support Tunnel-Password). + +But, on the plus-side, unlike many other RADIUS libraries node-radius supports encrypting/decrypting passwords longer than 16 bytes! diff --git a/code/node_modules/radius/accounting.js b/code/node_modules/radius/accounting.js new file mode 100644 index 0000000..98cef35 --- /dev/null +++ b/code/node_modules/radius/accounting.js @@ -0,0 +1,48 @@ +// Copyright (c) 2018, RetailNext, Inc. +// This material contains trade secrets and confidential information of +// RetailNext, Inc. Any use, reproduction, disclosure or dissemination +// is strictly prohibited without the explicit written permission +// of RetailNext, Inc. +// All rights reserved. + + +var fs = require("fs"); +var radius = require("./lib/radius"); +var dgram = require('dgram'); + +var dst_ip = "127.0.0.1"; + +var client = dgram.createSocket("udp4"); +client.bind(49001); + +var secret = "secret"; + +var attrs = { + 'User-Name': 'jlpicard', + 'User-Password': 'beverly', + 'Service-Type': 'Login-User', + 'NAS-IP-Address': '169.134.68.136' +}; +var packet = radius.encode({ + code: 'Access-Request', + identifier: 1, + attributes: attrs, + secret: secret +}); + +fs.writeFileSync("/tmp/short_password.packet", packet); + +client.send(packet, 0, packet.length, 1812, dst_ip, function() { + attrs['User-Password'] = 'beverly-crusher-123'; + packet = radius.encode({ + code: 'Access-Request', + identifier: 2, + attributes: attrs, + secret: secret + }); + + fs.writeFileSync("/tmp/long_password.packet", packet); + client.send(packet, 0, packet.length, 1812, dst_ip, function() { + client.close(); + }); +}); diff --git a/code/node_modules/radius/decode.js b/code/node_modules/radius/decode.js new file mode 100644 index 0000000..67ab76f --- /dev/null +++ b/code/node_modules/radius/decode.js @@ -0,0 +1,29 @@ +// Copyright (c) 2018, RetailNext, Inc. +// This material contains trade secrets and confidential information of +// RetailNext, Inc. Any use, reproduction, disclosure or dissemination +// is strictly prohibited without the explicit written permission +// of RetailNext, Inc. +// All rights reserved. + + + +var fs = require("fs"); +var radius = require("./lib/radius"); +var dgram = require('dgram'); + + + +var p = fs.readFileSync("/tmp/naughty_packet"); + + +var packet = p; +// var decoded = radius.decode({packet: p, secret: ''}); + + +var client = dgram.createSocket("udp4"); +client.bind(49001); + + +client.send(packet, 0, packet.length, 1812, '127.0.0.1', function() { + console.log("done!"); +}); diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc2865 b/code/node_modules/radius/dictionaries/dictionary.rfc2865 new file mode 100644 index 0000000..d2d6e87 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc2865 @@ -0,0 +1,137 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 2865. +# http://www.ietf.org/rfc/rfc2865.txt +# +# $Id: dictionary.rfc2865 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE User-Name 1 string +ATTRIBUTE User-Password 2 string encrypt=1 +ATTRIBUTE CHAP-Password 3 octets +ATTRIBUTE NAS-IP-Address 4 ipaddr +ATTRIBUTE NAS-Port 5 integer +ATTRIBUTE Service-Type 6 integer +ATTRIBUTE Framed-Protocol 7 integer +ATTRIBUTE Framed-IP-Address 8 ipaddr +ATTRIBUTE Framed-IP-Netmask 9 ipaddr +ATTRIBUTE Framed-Routing 10 integer +ATTRIBUTE Filter-Id 11 string +ATTRIBUTE Framed-MTU 12 integer +ATTRIBUTE Framed-Compression 13 integer +ATTRIBUTE Login-IP-Host 14 ipaddr +ATTRIBUTE Login-Service 15 integer +ATTRIBUTE Login-TCP-Port 16 integer +# Attribute 17 is undefined +ATTRIBUTE Reply-Message 18 string +ATTRIBUTE Callback-Number 19 string +ATTRIBUTE Callback-Id 20 string +# Attribute 21 is undefined +ATTRIBUTE Framed-Route 22 string +ATTRIBUTE Framed-IPX-Network 23 ipaddr +ATTRIBUTE State 24 octets +ATTRIBUTE Class 25 octets +ATTRIBUTE Vendor-Specific 26 octets +ATTRIBUTE Session-Timeout 27 integer +ATTRIBUTE Idle-Timeout 28 integer +ATTRIBUTE Termination-Action 29 integer +ATTRIBUTE Called-Station-Id 30 string +ATTRIBUTE Calling-Station-Id 31 string +ATTRIBUTE NAS-Identifier 32 string +ATTRIBUTE Proxy-State 33 octets +ATTRIBUTE Login-LAT-Service 34 string +ATTRIBUTE Login-LAT-Node 35 string +ATTRIBUTE Login-LAT-Group 36 octets +ATTRIBUTE Framed-AppleTalk-Link 37 integer +ATTRIBUTE Framed-AppleTalk-Network 38 integer +ATTRIBUTE Framed-AppleTalk-Zone 39 string + +ATTRIBUTE CHAP-Challenge 60 octets +ATTRIBUTE NAS-Port-Type 61 integer +ATTRIBUTE Port-Limit 62 integer +ATTRIBUTE Login-LAT-Port 63 string + +# +# Integer Translations +# + +# Service types + +VALUE Service-Type Login-User 1 +VALUE Service-Type Framed-User 2 +VALUE Service-Type Callback-Login-User 3 +VALUE Service-Type Callback-Framed-User 4 +VALUE Service-Type Outbound-User 5 +VALUE Service-Type Administrative-User 6 +VALUE Service-Type NAS-Prompt-User 7 +VALUE Service-Type Authenticate-Only 8 +VALUE Service-Type Callback-NAS-Prompt 9 +VALUE Service-Type Call-Check 10 +VALUE Service-Type Callback-Administrative 11 + +# Framed Protocols + +VALUE Framed-Protocol PPP 1 +VALUE Framed-Protocol SLIP 2 +VALUE Framed-Protocol ARAP 3 +VALUE Framed-Protocol Gandalf-SLML 4 +VALUE Framed-Protocol Xylogics-IPX-SLIP 5 +VALUE Framed-Protocol X.75-Synchronous 6 + +# Framed Routing Values + +VALUE Framed-Routing None 0 +VALUE Framed-Routing Broadcast 1 +VALUE Framed-Routing Listen 2 +VALUE Framed-Routing Broadcast-Listen 3 + +# Framed Compression Types + +VALUE Framed-Compression None 0 +VALUE Framed-Compression Van-Jacobson-TCP-IP 1 +VALUE Framed-Compression IPX-Header-Compression 2 +VALUE Framed-Compression Stac-LZS 3 + +# Login Services + +VALUE Login-Service Telnet 0 +VALUE Login-Service Rlogin 1 +VALUE Login-Service TCP-Clear 2 +VALUE Login-Service PortMaster 3 +VALUE Login-Service LAT 4 +VALUE Login-Service X25-PAD 5 +VALUE Login-Service X25-T3POS 6 +VALUE Login-Service TCP-Clear-Quiet 8 + +# Login-TCP-Port (see /etc/services for more examples) + +VALUE Login-TCP-Port Telnet 23 +VALUE Login-TCP-Port Rlogin 513 +VALUE Login-TCP-Port Rsh 514 + +# Termination Options + +VALUE Termination-Action Default 0 +VALUE Termination-Action RADIUS-Request 1 + +# NAS Port Types + +VALUE NAS-Port-Type Async 0 +VALUE NAS-Port-Type Sync 1 +VALUE NAS-Port-Type ISDN 2 +VALUE NAS-Port-Type ISDN-V120 3 +VALUE NAS-Port-Type ISDN-V110 4 +VALUE NAS-Port-Type Virtual 5 +VALUE NAS-Port-Type PIAFS 6 +VALUE NAS-Port-Type HDLC-Clear-Channel 7 +VALUE NAS-Port-Type X.25 8 +VALUE NAS-Port-Type X.75 9 +VALUE NAS-Port-Type G.3-Fax 10 +VALUE NAS-Port-Type SDSL 11 +VALUE NAS-Port-Type ADSL-CAP 12 +VALUE NAS-Port-Type ADSL-DMT 13 +VALUE NAS-Port-Type IDSL 14 +VALUE NAS-Port-Type Ethernet 15 +VALUE NAS-Port-Type xDSL 16 +VALUE NAS-Port-Type Cable 17 +VALUE NAS-Port-Type Wireless-Other 18 +VALUE NAS-Port-Type Wireless-802.11 19 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc2866 b/code/node_modules/radius/dictionaries/dictionary.rfc2866 new file mode 100644 index 0000000..1d7b2b0 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc2866 @@ -0,0 +1,56 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 2866. +# http://www.ietf.org/rfc/rfc2866.txt +# +# $Id: dictionary.rfc2866 39598 2011-10-26 05:33:30Z etxrab $ +# +ATTRIBUTE Acct-Status-Type 40 integer +ATTRIBUTE Acct-Delay-Time 41 integer +ATTRIBUTE Acct-Input-Octets 42 integer +ATTRIBUTE Acct-Output-Octets 43 integer +ATTRIBUTE Acct-Session-Id 44 string +ATTRIBUTE Acct-Authentic 45 integer +ATTRIBUTE Acct-Session-Time 46 integer +ATTRIBUTE Acct-Input-Packets 47 integer +ATTRIBUTE Acct-Output-Packets 48 integer +ATTRIBUTE Acct-Terminate-Cause 49 integer +ATTRIBUTE Acct-Multi-Session-Id 50 string +ATTRIBUTE Acct-Link-Count 51 integer + +# Accounting Status Types + +VALUE Acct-Status-Type Start 1 +VALUE Acct-Status-Type Stop 2 +VALUE Acct-Status-Type Interim-Update 3 +VALUE Acct-Status-Type Accounting-On 7 +VALUE Acct-Status-Type Accounting-Off 8 +VALUE Acct-Status-Type Failed 15 + +# Authentication Types + +VALUE Acct-Authentic RADIUS 1 +VALUE Acct-Authentic Local 2 +VALUE Acct-Authentic Remote 3 +VALUE Acct-Authentic Diameter 4 + +# Acct Terminate Causes + +VALUE Acct-Terminate-Cause User-Request 1 +VALUE Acct-Terminate-Cause Lost-Carrier 2 +VALUE Acct-Terminate-Cause Lost-Service 3 +VALUE Acct-Terminate-Cause Idle-Timeout 4 +VALUE Acct-Terminate-Cause Session-Timeout 5 +VALUE Acct-Terminate-Cause Admin-Reset 6 +VALUE Acct-Terminate-Cause Admin-Reboot 7 +VALUE Acct-Terminate-Cause Port-Error 8 +VALUE Acct-Terminate-Cause NAS-Error 9 +VALUE Acct-Terminate-Cause NAS-Request 10 +VALUE Acct-Terminate-Cause NAS-Reboot 11 +VALUE Acct-Terminate-Cause Port-Unneeded 12 +VALUE Acct-Terminate-Cause Port-Preempted 13 +VALUE Acct-Terminate-Cause Port-Suspended 14 +VALUE Acct-Terminate-Cause Service-Unavailable 15 +VALUE Acct-Terminate-Cause Callback 16 +VALUE Acct-Terminate-Cause User-Error 17 +VALUE Acct-Terminate-Cause Host-Request 18 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc2867 b/code/node_modules/radius/dictionaries/dictionary.rfc2867 new file mode 100644 index 0000000..ebda3e9 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc2867 @@ -0,0 +1,16 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 2867. +# http://www.ietf.org/rfc/rfc2867.txt +# +# $Id: dictionary.rfc2867 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Acct-Tunnel-Connection 68 string +ATTRIBUTE Acct-Tunnel-Packets-Lost 86 integer + +VALUE Acct-Status-Type Tunnel-Start 9 +VALUE Acct-Status-Type Tunnel-Stop 10 +VALUE Acct-Status-Type Tunnel-Reject 11 +VALUE Acct-Status-Type Tunnel-Link-Start 12 +VALUE Acct-Status-Type Tunnel-Link-Stop 13 +VALUE Acct-Status-Type Tunnel-Link-Reject 14 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc2868 b/code/node_modules/radius/dictionaries/dictionary.rfc2868 new file mode 100644 index 0000000..241a7fe --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc2868 @@ -0,0 +1,54 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 2868. +# http://www.ietf.org/rfc/rfc2868.txt +# +# $Id: dictionary.rfc2868 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Tunnel-Type 64 integer has_tag +ATTRIBUTE Tunnel-Medium-Type 65 integer has_tag +ATTRIBUTE Tunnel-Client-Endpoint 66 string has_tag +ATTRIBUTE Tunnel-Server-Endpoint 67 string has_tag + +ATTRIBUTE Tunnel-Password 69 string has_tag,encrypt=2 + +ATTRIBUTE Tunnel-Private-Group-Id 81 string has_tag +ATTRIBUTE Tunnel-Assignment-Id 82 string has_tag +ATTRIBUTE Tunnel-Preference 83 integer has_tag + +ATTRIBUTE Tunnel-Client-Auth-Id 90 string has_tag +ATTRIBUTE Tunnel-Server-Auth-Id 91 string has_tag + +# Tunnel Type + +VALUE Tunnel-Type PPTP 1 +VALUE Tunnel-Type L2F 2 +VALUE Tunnel-Type L2TP 3 +VALUE Tunnel-Type ATMP 4 +VALUE Tunnel-Type VTP 5 +VALUE Tunnel-Type AH 6 +VALUE Tunnel-Type IP 7 +VALUE Tunnel-Type MIN-IP 8 +VALUE Tunnel-Type ESP 9 +VALUE Tunnel-Type GRE 10 +VALUE Tunnel-Type DVS 11 +VALUE Tunnel-Type IP-in-IP 12 + +# Tunnel Medium Type + +VALUE Tunnel-Medium-Type IP 1 +VALUE Tunnel-Medium-Type IPv4 1 +VALUE Tunnel-Medium-Type IPv6 2 +VALUE Tunnel-Medium-Type NSAP 3 +VALUE Tunnel-Medium-Type HDLC 4 +VALUE Tunnel-Medium-Type BBN-1822 5 +VALUE Tunnel-Medium-Type IEEE-802 6 +VALUE Tunnel-Medium-Type E.163 7 +VALUE Tunnel-Medium-Type E.164 8 +VALUE Tunnel-Medium-Type F.69 9 +VALUE Tunnel-Medium-Type X.121 10 +VALUE Tunnel-Medium-Type IPX 11 +VALUE Tunnel-Medium-Type Appletalk 12 +VALUE Tunnel-Medium-Type DecNet-IV 13 +VALUE Tunnel-Medium-Type Banyan-Vines 14 +VALUE Tunnel-Medium-Type E.164-NSAP 15 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc2869 b/code/node_modules/radius/dictionaries/dictionary.rfc2869 new file mode 100644 index 0000000..10f053c --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc2869 @@ -0,0 +1,39 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 2869. +# http://www.ietf.org/rfc/rfc2869.txt +# +# $Id: dictionary.rfc2869 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Acct-Input-Gigawords 52 integer +ATTRIBUTE Acct-Output-Gigawords 53 integer + +ATTRIBUTE Event-Timestamp 55 date + +ATTRIBUTE ARAP-Password 70 octets # 16 octets of data +ATTRIBUTE ARAP-Features 71 octets # 14 octets of data +ATTRIBUTE ARAP-Zone-Access 72 integer +ATTRIBUTE ARAP-Security 73 integer +ATTRIBUTE ARAP-Security-Data 74 string +ATTRIBUTE Password-Retry 75 integer +ATTRIBUTE Prompt 76 integer +ATTRIBUTE Connect-Info 77 string +ATTRIBUTE Configuration-Token 78 string +ATTRIBUTE EAP-Message 79 octets +ATTRIBUTE Message-Authenticator 80 octets + +ATTRIBUTE ARAP-Challenge-Response 84 octets # 8 octets of data +ATTRIBUTE Acct-Interim-Interval 85 integer +# 86: RFC 2867 +ATTRIBUTE NAS-Port-Id 87 string +ATTRIBUTE Framed-Pool 88 string + +# ARAP Zone Access + +VALUE ARAP-Zone-Access Default-Zone 1 +VALUE ARAP-Zone-Access Zone-Filter-Inclusive 2 +VALUE ARAP-Zone-Access Zone-Filter-Exclusive 4 + +# Prompt +VALUE Prompt No-Echo 0 +VALUE Prompt Echo 1 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc3162 b/code/node_modules/radius/dictionaries/dictionary.rfc3162 new file mode 100644 index 0000000..9dc6568 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc3162 @@ -0,0 +1,13 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 3162. +# http://www.ietf.org/rfc/rfc3162.txt +# +# $Id: dictionary.rfc3162 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE NAS-IPv6-Address 95 ipv6addr +ATTRIBUTE Framed-Interface-Id 96 ifid +ATTRIBUTE Framed-IPv6-Prefix 97 ipv6prefix +ATTRIBUTE Login-IPv6-Host 98 ipv6addr +ATTRIBUTE Framed-IPv6-Route 99 string +ATTRIBUTE Framed-IPv6-Pool 100 string diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc3576 b/code/node_modules/radius/dictionaries/dictionary.rfc3576 new file mode 100644 index 0000000..d8bb3f7 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc3576 @@ -0,0 +1,30 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 3576. +# http://www.ietf.org/rfc/rfc3576.txt +# +# $Id: dictionary.rfc3576 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Error-Cause 101 integer + +# Service Types + +VALUE Service-Type Authorize-Only 17 + +# Error causes + +VALUE Error-Cause Residual-Context-Removed 201 +VALUE Error-Cause Invalid-EAP-Packet 202 +VALUE Error-Cause Unsupported-Attribute 401 +VALUE Error-Cause Missing-Attribute 402 +VALUE Error-Cause NAS-Identification-Mismatch 403 +VALUE Error-Cause Invalid-Request 404 +VALUE Error-Cause Unsupported-Service 405 +VALUE Error-Cause Unsupported-Extension 406 +VALUE Error-Cause Administratively-Prohibited 501 +VALUE Error-Cause Proxy-Request-Not-Routable 502 +VALUE Error-Cause Session-Context-Not-Found 503 +VALUE Error-Cause Session-Context-Not-Removable 504 +VALUE Error-Cause Proxy-Processing-Error 505 +VALUE Error-Cause Resources-Unavailable 506 +VALUE Error-Cause Request-Initiated 507 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc3580 b/code/node_modules/radius/dictionaries/dictionary.rfc3580 new file mode 100644 index 0000000..a5d4017 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc3580 @@ -0,0 +1,16 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 3580. +# http://www.ietf.org/rfc/rfc3580.txt +# +# $Id: dictionary.rfc3580 28946 2009-07-06 12:39:58Z wmeier $ +# +VALUE Acct-Terminate-Cause Supplicant-Restart 19 +VALUE Acct-Terminate-Cause Reauthentication-Failure 20 +VALUE Acct-Terminate-Cause Port-Reinit 21 +VALUE Acct-Terminate-Cause Port-Disabled 22 + +VALUE NAS-Port-Type Token-Ring 20 +VALUE NAS-Port-Type FDDI 21 + +VALUE Tunnel-Type VLAN 13 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4072 b/code/node_modules/radius/dictionaries/dictionary.rfc4072 new file mode 100644 index 0000000..532cd67 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4072 @@ -0,0 +1,9 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 4072 +# http://www.ietf.org/rfc/4072.txt +# +# $Id: dictionary.rfc4072 28946 2009-07-06 12:39:58Z wmeier $ +# + +ATTRIBUTE EAP-Key-Name 102 string diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4372 b/code/node_modules/radius/dictionaries/dictionary.rfc4372 new file mode 100644 index 0000000..20cfda3 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4372 @@ -0,0 +1,8 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 4372. +# http://www.ietf.org/rfc/4372.txt +# +# $Id: dictionary.rfc4372 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Chargeable-User-Identity 89 string diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4603 b/code/node_modules/radius/dictionaries/dictionary.rfc4603 new file mode 100644 index 0000000..e68014c --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4603 @@ -0,0 +1,17 @@ +# -*- text -*- +############################################################################## +# +# Attributes and values defined in RFC 4603. +# http://www.ietf.org/rfc/rfc4603.txt +# +# $Id: dictionary.rfc4603 39598 2011-10-26 05:33:30Z etxrab $ +# +############################################################################## + + +VALUE NAS-Port-Type PPPoA 30 +VALUE NAS-Port-Type PPPoEoA 31 +VALUE NAS-Port-Type PPPoEoE 32 +VALUE NAS-Port-Type PPPoEoVLAN 33 +VALUE NAS-Port-Type PPPoEoQinQ 34 + diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4675 b/code/node_modules/radius/dictionaries/dictionary.rfc4675 new file mode 100644 index 0000000..8b229eb --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4675 @@ -0,0 +1,28 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 4675. +# http://www.ietf.org/rfc/4675.txt +# +# $Id: dictionary.rfc4675 28946 2009-07-06 12:39:58Z wmeier $ +# + +# +# High byte = '1' (0x31) means the frames are tagged. +# High byte = '2' (0x32) means the frames are untagged. +# +# Next 12 bits MUST be zero. +# +# Lower 12 bits is the IEEE-802.1Q VLAN VID. +# +ATTRIBUTE Egress-VLANID 56 integer +ATTRIBUTE Ingress-Filters 57 integer + +# +# First byte == '1' (0x31) means that the frames are tagged. +# First byte == '2' (0x32) means that the frames are untagged. +# +ATTRIBUTE Egress-VLAN-Name 58 string +ATTRIBUTE User-Priority-Table 59 octets # 8 + +VALUE Ingress-Filters Enabled 1 +VALUE Ingress-Filters Disabled 2 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4679 b/code/node_modules/radius/dictionaries/dictionary.rfc4679 new file mode 100644 index 0000000..3ab0ccf --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4679 @@ -0,0 +1,62 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 4679. +# http://www.ietf.org/rfc/4679.txt +# +# $Id: dictionary.rfc4679 28946 2009-07-06 12:39:58Z wmeier $ +# + +VENDOR ADSL-Forum 3561 + +BEGIN-VENDOR ADSL-Forum + +# +# The first two attributes are prefixed with "ADSL-" because of +# conflicting names in dictionary.redback. +# +ATTRIBUTE ADSL-Agent-Circuit-Id 1 string +ATTRIBUTE ADSL-Agent-Remote-Id 2 string +ATTRIBUTE Actual-Data-Rate-Upstream 129 integer +ATTRIBUTE Actual-Data-Rate-Downstream 130 integer +ATTRIBUTE Minimum-Data-Rate-Upstream 131 integer +ATTRIBUTE Minimum-Data-Rate-Downstream 132 integer +ATTRIBUTE Attainable-Data-Rate-Upstream 133 integer +ATTRIBUTE Attainable-Data-Rate-Downstream 134 integer +ATTRIBUTE Maximum-Data-Rate-Upstream 135 integer +ATTRIBUTE Maximum-Data-Rate-Downstream 136 integer +ATTRIBUTE Minimum-Data-Rate-Upstream-Low-Power 137 integer +ATTRIBUTE Minimum-Data-Rate-Downstream-Low-Power 138 integer +ATTRIBUTE Maximum-Interleaving-Delay-Upstream 139 integer +ATTRIBUTE Actual-Interleaving-Delay-Upstream 140 integer +ATTRIBUTE Maximum-Interleaving-Delay-Downstream 141 integer +ATTRIBUTE Actual-Interleaving-Delay-Downstream 142 integer + +# +# This next attribute has a weird encoding. +# +# Octet[0] - 0x01 AAL5 +# Octet[0] - 0x02 Ethernet + +# Octet[1] - 0x00 Not Available +# Octet[1] - 0x01 Untagged Ethernet +# Octet[1] - 0x02 Single-Tagged Ethernet + +# Octet[2] - 0x00 Not available +# Octet[2] - 0x01 PPPoA LLC +# Octet[2] - 0x02 PPPoA Null +# Octet[2] - 0x03 IPoA LLC +# Octet[2] - 0x04 IPoA NULL +# Octet[2] - 0x05 Ethernet over AAL5 LLC with FCS +# Octet[2] - 0x06 Ethernet over AAL5 LLC without FCS +# Octet[2] - 0x07 Ethernet over AAL5 Null with FCS +# Octet[2] - 0x08 Ethernet over AAL5 Null without FCS +# +ATTRIBUTE Access-Loop-Encapsulation 144 octets # 3 + +# +# If this attribute exists, it means that IFW has been performed +# for the subscribers session. +# +ATTRIBUTE IWF-Session 252 octets # 0 + +END-VENDOR ADSL-Forum diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4818 b/code/node_modules/radius/dictionaries/dictionary.rfc4818 new file mode 100644 index 0000000..4862779 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4818 @@ -0,0 +1,11 @@ +# -*- text -*- +############################################################################## +# +# Attributes and values defined in RFC 4818. +# http://www.ietf.org/rfc/rfc4818.txt +# +# $Id: dictionary.rfc4818 28946 2009-07-06 12:39:58Z wmeier $ +# +############################################################################## + +ATTRIBUTE Delegated-IPv6-Prefix 123 ipv6prefix diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc4849 b/code/node_modules/radius/dictionaries/dictionary.rfc4849 new file mode 100644 index 0000000..2925d51 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc4849 @@ -0,0 +1,8 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 4849. +# http://www.ietf.org/rfc/rfc4849.txt +# +# $Id: dictionary.rfc4849 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE NAS-Filter-Rule 92 string diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc5090 b/code/node_modules/radius/dictionaries/dictionary.rfc5090 new file mode 100644 index 0000000..3474b7a --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc5090 @@ -0,0 +1,27 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 5090. +# http://www.ietf.org/rfc/rfc5090.txt +# +# $Id: dictionary.rfc5090 28946 2009-07-06 12:39:58Z wmeier $ +# +ATTRIBUTE Digest-Response 103 string +ATTRIBUTE Digest-Realm 104 string +ATTRIBUTE Digest-Nonce 105 string +ATTRIBUTE Digest-Response-Auth 106 string +ATTRIBUTE Digest-Nextnonce 107 string +ATTRIBUTE Digest-Method 108 string +ATTRIBUTE Digest-URI 109 string +ATTRIBUTE Digest-Qop 110 string +ATTRIBUTE Digest-Algorithm 111 string +ATTRIBUTE Digest-Entity-Body-Hash 112 string +ATTRIBUTE Digest-CNonce 113 string +ATTRIBUTE Digest-Nonce-Count 114 string +ATTRIBUTE Digest-Username 115 string +ATTRIBUTE Digest-Opaque 116 string +ATTRIBUTE Digest-Auth-Param 117 string +ATTRIBUTE Digest-AKA-Auts 118 string +ATTRIBUTE Digest-Domain 119 string +ATTRIBUTE Digest-Stale 120 string +ATTRIBUTE Digest-HA1 121 string +ATTRIBUTE SIP-AOR 122 string diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc5176 b/code/node_modules/radius/dictionaries/dictionary.rfc5176 new file mode 100644 index 0000000..3866397 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc5176 @@ -0,0 +1,9 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 5176. +# http://www.ietf.org/rfc/rfc5176.txt +# +# $Id: dictionary.rfc5176 28946 2009-07-06 12:39:58Z wmeier $ +# +VALUE Error-Cause Invalid-Attribute-Value 407 +VALUE Error-Cause Multiple-Session-Selection-Unsupported 508 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc5580 b/code/node_modules/radius/dictionaries/dictionary.rfc5580 new file mode 100644 index 0000000..cc0e5f8 --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc5580 @@ -0,0 +1,41 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 5580. +# http://www.ietf.org/rfc/rfc5580.txt +# +# $Id: dictionary.rfc5580 39598 2011-10-26 05:33:30Z etxrab $ +# + +# One ASCII character of Namespace ID +# 0 = TADIG (GSM) +# 1 = Realm +# 2 = E212 +# +# +# Followed by the actual string +ATTRIBUTE Operator-Name 126 string + +# +# Large blobs of stuff +# +ATTRIBUTE Location-Information 127 octets +ATTRIBUTE Location-Data 128 octets +ATTRIBUTE Basic-Location-Policy-Rules 129 octets +ATTRIBUTE Extended-Location-Policy-Rules 130 octets + +# +# Really a bit-packed field +# +ATTRIBUTE Location-Capable 131 integer +VALUE Location-Capable Civix-Location 1 +VALUE Location-Capable Geo-Location 2 +VALUE Location-Capable Users-Location 4 +VALUE Location-Capable NAS-Location 8 + +ATTRIBUTE Requested-Location-Info 132 integer +VALUE Requested-Location-Info Civix-Location 1 +VALUE Requested-Location-Info Geo-Location 2 +VALUE Requested-Location-Info Users-Location 4 +VALUE Requested-Location-Info NAS-Location 8 +VALUE Requested-Location-Info Future-Requests 16 +VALUE Requested-Location-Info None 32 diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc5607 b/code/node_modules/radius/dictionaries/dictionary.rfc5607 new file mode 100644 index 0000000..e90be0a --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc5607 @@ -0,0 +1,30 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 5607. +# http://www.ietf.org/rfc/rfc5607.txt +# +# $Id: dictionary.rfc5607 39598 2011-10-26 05:33:30Z etxrab $ +# + +VALUE Service-Type Framed-Management 18 + +ATTRIBUTE Framed-Management 133 integer + +VALUE Framed-Management SNMP 1 +VALUE Framed-Management Web-Based 2 +VALUE Framed-Management Netconf 3 +VALUE Framed-Management FTP 4 +VALUE Framed-Management TFTP 5 +VALUE Framed-Management SFTP 6 +VALUE Framed-Management RCP 7 +VALUE Framed-Management SCP 8 + +ATTRIBUTE Management-Transport-Protection 134 integer + +VALUE Management-Transport-Protection No-Protection 1 +VALUE Management-Transport-Protection Integrity-Protection 2 +VALUE Management-Transport-Protection Integrity-Confidentiality-Protection 3 + +ATTRIBUTE Management-Policy-Id 135 string + +ATTRIBUTE Management-Privilege-Level 136 integer diff --git a/code/node_modules/radius/dictionaries/dictionary.rfc5904 b/code/node_modules/radius/dictionaries/dictionary.rfc5904 new file mode 100644 index 0000000..79bf7da --- /dev/null +++ b/code/node_modules/radius/dictionaries/dictionary.rfc5904 @@ -0,0 +1,22 @@ +# -*- text -*- +# +# Attributes and values defined in RFC 5904. +# http://www.ietf.org/rfc/rfc5904.txt +# +# $Id: dictionary.rfc5904 39598 2011-10-26 05:33:30Z etxrab $ +# + +# The next two attributes are continued, like EAP-Message/ +ATTRIBUTE PKM-SS-Cert 137 octets +ATTRIBUTE PKM-CA-Cert 138 octets + +# 28 bytes of data, 7 integers +ATTRIBUTE PKM-Config-Settings 139 octets +ATTRIBUTE PKM-Cryptosuite-List 140 octets +ATTRIBUTE PKM-SAID 141 short + +# 6 bytes of data: SAID, 1 byte of type, 3 of cryptosuite +ATTRIBUTE PKM-SA-Descriptor 142 octets + +# 133 bytes of data: integer lifetime, 1 byte sequence, 128 bytes of key +ATTRIBUTE PKM-Auth-Key 143 octets diff --git a/code/node_modules/radius/examples/auth_client.js b/code/node_modules/radius/examples/auth_client.js new file mode 100644 index 0000000..eae32df --- /dev/null +++ b/code/node_modules/radius/examples/auth_client.js @@ -0,0 +1,81 @@ +// Example radius client sending auth packets. + +var radius = require('../lib/radius'); +var dgram = require('dgram'); +var util = require('util'); + +var secret = 'radius_secret'; + +var packet_accepted = { + code: "Access-Request", + secret: secret, + identifier: 0, + attributes: [ + ['NAS-IP-Address', '10.5.5.5'], + ['User-Name', 'jlpicard'], + ['User-Password', 'beverly123'] + ] +}; + +var packet_rejected = { + code: "Access-Request", + secret: secret, + identifier: 1, + attributes: [ + ['NAS-IP-Address', '10.5.5.5'], + ['User-Name', 'egarak'], + ['User-Password', 'tailoredfit'] + ] +}; + +var packet_wrong_secret = { + code: "Access-Request", + secret: "wrong_secret", + identifier: 2, + attributes: [ + ['NAS-IP-Address', '10.5.5.5'], + ['User-Name', 'riker'], + ['User-Password', 'Riker-Omega-3'] + ] +}; + +var client = dgram.createSocket("udp4"); + +client.bind(49001); + +var response_count = 0; + +client.on('message', function(msg, rinfo) { + var response = radius.decode({packet: msg, secret: secret}); + var request = sent_packets[response.identifier]; + + // although it's a slight hassle to keep track of packets, it's a good idea to verify + // responses to make sure you are talking to a server with the same shared secret + var valid_response = radius.verify_response({ + response: msg, + request: request.raw_packet, + secret: request.secret + }); + if (valid_response) { + console.log('Got valid response ' + response.code + ' for packet id ' + response.identifier); + // take some action based on response.code + } else { + console.log('WARNING: Got invalid response ' + response.code + ' for packet id ' + response.identifier); + // don't take action since server cannot be trusted (but maybe alert user that shared secret may be incorrect) + } + + if (++response_count == 3) { + client.close(); + } +}); + +var sent_packets = {}; + +[packet_accepted, packet_rejected, packet_wrong_secret].forEach(function(packet) { + var encoded = radius.encode(packet); + sent_packets[packet.identifier] = { + raw_packet: encoded, + secret: packet.secret + }; + client.send(encoded, 0, encoded.length, 1812, "localhost"); +}); diff --git a/code/node_modules/radius/examples/auth_server.js b/code/node_modules/radius/examples/auth_server.js new file mode 100644 index 0000000..bc39836 --- /dev/null +++ b/code/node_modules/radius/examples/auth_server.js @@ -0,0 +1,54 @@ +// Example radius server doing authentication + +var radius = require('../lib/radius'); +var dgram = require("dgram"); + +var secret = 'radius_secret'; +var server = dgram.createSocket("udp4"); + +server.on("message", function (msg, rinfo) { + var code, username, password, packet; + try { + packet = radius.decode({packet: msg, secret: secret}); + } catch (e) { + console.log("Failed to decode radius packet, silently dropping:", e); + return; + } + + if (packet.code != 'Access-Request') { + console.log('unknown packet type: ', packet.code); + return; + } + + username = packet.attributes['User-Name']; + password = packet.attributes['User-Password']; + + console.log('Access-Request for ' + username); + + if (username == 'jlpicard' && password == 'beverly123') { + code = 'Access-Accept'; + } else { + code = 'Access-Reject'; + } + + var response = radius.encode_response({ + packet: packet, + code: code, + secret: secret + }); + + console.log('Sending ' + code + ' for user ' + username); + server.send(response, 0, response.length, rinfo.port, rinfo.address, function(err, bytes) { + if (err) { + console.log('Error sending response to ', rinfo); + } + }); +}); + +server.on("listening", function () { + var address = server.address(); + console.log("radius server listening " + + address.address + ":" + address.port); +}); + +server.bind(1812); diff --git a/code/node_modules/radius/lib/radius.js b/code/node_modules/radius/lib/radius.js new file mode 100644 index 0000000..3a97dab --- /dev/null +++ b/code/node_modules/radius/lib/radius.js @@ -0,0 +1,886 @@ +var fs = require("fs"); +var util = require("util"); +var crypto = require("crypto"); +var path = require("path"); + +var Radius = {}; + +var attributes_map = {}, vendor_name_to_id = {}; +var dictionary_locations = [path.normalize(__dirname + "/../dictionaries")]; + +const NOT_LOADED = 1; +const LOADING = 2; +const LOADED = 3; + +var dictionaries_state = NOT_LOADED; + +const NO_VENDOR = -1; + +const ATTR_ID = 0; +const ATTR_NAME = 1; +const ATTR_TYPE = 2; +const ATTR_ENUM = 3; +const ATTR_REVERSE_ENUM = 4; +const ATTR_MODIFIERS = 5; + +const AUTH_START = 4; +const AUTH_END = 20; +const AUTH_LENGTH = 16; +const MESSAGE_AUTHENTICATOR_LENGTH = 16; + +Radius.InvalidSecretError = function(msg, decoded, constr) { + Error.captureStackTrace(this, constr || this); + this.message = msg || 'Error'; + this.decoded = decoded; +}; +util.inherits(Radius.InvalidSecretError, Error); +Radius.InvalidSecretError.prototype.name = 'Invalid Secret Error'; + +Radius.add_dictionary = function(file) { + dictionary_locations.push(path.resolve(file)); +}; + +var load_dictionaries_cbs = []; +Radius.load_dictionaries = function() { + var self = this; + + if (dictionaries_state == LOADED) { + return; + } + + dictionary_locations.forEach(function(file) { + if (!fs.existsSync(file)) { + throw new Error("Invalid dictionary location: " + file); + } + + if (fs.statSync(file).isDirectory()) { + var files = fs.readdirSync(file); + for (var j = 0; j < files.length; j++) { + self.load_dictionary(file + "/" + files[j]); + } + } else { + self.load_dictionary(file); + } + }); + + dictionaries_state = LOADED; +}; + +Radius.load_dictionary = function(file, seen_files) { + file = path.normalize(file); + var self = this; + + if (seen_files === undefined) { + seen_files = {}; + } + + if (seen_files[file]) { + return; + } + + seen_files[file] = true; + + var includes = self._load_dictionary(fs.readFileSync(file, "ascii")); + includes.forEach(function (i) { + self.load_dictionary(path.join(path.dirname(file), i), seen_files); + }); +}; + +Radius._load_dictionary = function(content) { + var lines = content.split("\n"); + + var vendor = NO_VENDOR, includes = [], attr_vendor; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + + line = line.replace(/#.*/, "").replace(/\s+/g, " "); + + var match = line.match(/^\s*VENDOR\s+(\S+)\s+(\d+)/); + if (match) { + vendor_name_to_id[match[1]] = match[2]; + continue; + } + + if ((match = line.match(/^\s*BEGIN-VENDOR\s+(\S+)/))) { + vendor = vendor_name_to_id[match[1]]; + continue; + } + + if (line.match(/^\s*END-VENDOR/)) { + vendor = NO_VENDOR; + continue; + } + + var init_entry = function(vendor, attr_id) { + if (!attributes_map[vendor]) { + attributes_map[vendor] = {}; + } + + if (!attributes_map[vendor][attr_id]) { + attributes_map[vendor][attr_id] = [null, null, null, {}, {}, {}]; + } + }; + + match = line.match(/^\s*(?:VENDORATTR\s+(\d+)|ATTRIBUTE)\s+(\S+)\s+(\d+)\s+(\S+)\s*(.+)?/); + if (match) { + attr_vendor = vendor; + if (match[1] !== undefined) { + attr_vendor = match[1]; + } + + var modifiers = {}; + if (match[5] !== undefined) { + match[5].replace(/\s*/g, "").split(",").forEach(function(m) { + modifiers[m] = true; + }); + } + + init_entry(attr_vendor, match[3]); + + attributes_map[attr_vendor][match[3]][ATTR_ID] = match[3]; + attributes_map[attr_vendor][match[3]][ATTR_NAME] = match[2]; + attributes_map[attr_vendor][match[3]][ATTR_TYPE] = match[4].toLowerCase(); + attributes_map[attr_vendor][match[3]][ATTR_MODIFIERS] = modifiers; + + var by_name = attributes_map[attr_vendor][match[2]]; + if (by_name !== undefined) { + var by_index = attributes_map[attr_vendor][match[3]]; + [ATTR_ENUM, ATTR_REVERSE_ENUM].forEach(function(field) { + for (var name in by_name[field]) { + by_index[field][name] = by_name[field][name]; + } + }); + } + attributes_map[attr_vendor][match[2]] = attributes_map[attr_vendor][match[3]]; + + continue; + } + + match = line.match(/^\s*(?:VENDOR)?VALUE\s+(\d+)?\s*(\S+)\s+(\S+)\s+(\d+)/); + if (match) { + attr_vendor = vendor; + if (match[1] !== undefined) { + attr_vendor = match[1]; + } + + init_entry(attr_vendor, match[2]); + + attributes_map[attr_vendor][match[2]][ATTR_ENUM][match[4]] = match[3]; + attributes_map[attr_vendor][match[2]][ATTR_REVERSE_ENUM][match[3]] = match[4]; + + continue; + } + + if ((match = line.match(/^\s*\$INCLUDE\s+(.*)/))) { + includes.push(match[1]); + } + } + + return includes; +}; + +Radius.unload_dictionaries = function() { + attributes_map = {}; + vendor_name_to_id = {}; + dictionaries_state = NOT_LOADED; +}; + +Radius.attr_name_to_id = function(attr_name, vendor_id) { + return this._attr_to(attr_name, vendor_id, ATTR_ID); +}; + +Radius.attr_id_to_name = function(attr_name, vendor_id) { + return this._attr_to(attr_name, vendor_id, ATTR_NAME); +}; + +Radius.vendor_name_to_id = function(vendor_name) { + return vendor_name_to_id[vendor_name]; +}; + +Radius._attr_to = function(attr, vendor_id, target) { + if (vendor_id === undefined) { + vendor_id = NO_VENDOR; + } + + if (!attributes_map[vendor_id]) { + return; + } + + var attr_info = attributes_map[vendor_id][attr]; + if (!attr_info) { + return; + } + + return attr_info[target]; +}; + +var code_map = { + 1: "Access-Request", + 2: "Access-Accept", + 3: "Access-Reject", + 4: "Accounting-Request", + 5: "Accounting-Response", + 6: "Interim-Accounting", + 7: "Password-Request", + 8: "Password-Ack", + 9: "Password-Reject", + 10: "Accounting-Message", + 11: "Access-Challenge", + 12: "Status-Server", + 13: "Status-Client", + 21: "Resource-Free-Request", + 22: "Resource-Free-Response", + 23: "Resource-Query-Request", + 24: "Resource-Query-Response", + 25: "Alternate-Resource-Reclaim-Request", + 26: "NAS-Reboot-Request", + 27: "NAS-Reboot-Response", + 29: "Next-Passcode", + 30: "New-Pin", + 31: "Terminate-Session", + 32: "Password-Expired", + 33: "Event-Request", + 34: "Event-Response", + 40: "Disconnect-Request", + 41: "Disconnect-ACK", + 42: "Disconnect-NAK", + 43: "CoA-Request", + 44: "CoA-ACK", + 45: "CoA-NAK", + 50: "IP-Address-Allocate", + 51: "IP-Address-Release" +}; + +var uses_random_authenticator = { + "Access-Request": true, + "Status-Server": true +}; + +var is_request_code = { + "Status-Server": true +}; + +var reverse_code_map = {}; +for (var code in code_map) { + reverse_code_map[code_map[code]] = code; + if (code_map[code].match(/Request/)) { + is_request_code[code_map[code]] = true; + } +} + +Radius.error = function(error_msg) { + var err = error_msg; + if (typeof(error_msg) === 'string') { + err = new Error(error_msg); + } + + throw err; +}; + +// this is a convenience method, "decode({..., no_secret: true})" will also do the job +Radius.decode_without_secret = function(args) { + // copy args' fields without modifiying the orginal + var nargs = {no_secret: true}; + for (var p in args) { + nargs[p] = args[p]; + } + return this.decode(nargs, this._decode); +}; + +Radius.decode = function(args) { + this.load_dictionaries(); + + var packet = args.packet; + if (!packet || packet.length < 4) { + this.error("decode: packet too short"); + return; + } + + var ret = {}; + + ret.code = code_map[packet.readUInt8(0)]; + + if (!ret.code) { + this.error("decode: invalid packet code '" + packet.readUInt8(0) + "'"); + return; + } + + ret.identifier = packet.readUInt8(1); + ret.length = packet.readUInt16BE(2); + + if (packet.length < ret.length) { + this.error("decode: incomplete packet"); + return; + } + + this.authenticator = ret.authenticator = packet.slice(AUTH_START, AUTH_END); + this.no_secret = args.no_secret; + this.secret = args.secret; + + var attrs = packet.slice(AUTH_END, ret.length); + ret.attributes = {}; + ret.raw_attributes = []; + + try { + this.decode_attributes(attrs, ret.attributes, NO_VENDOR, ret.raw_attributes); + } catch(err) { + this.error(err); + return; + } + + if (!uses_random_authenticator[ret.code] && is_request_code[ret.code] && !args.no_secret) { + var orig_authenticator = new Buffer(AUTH_LENGTH); + packet.copy(orig_authenticator, 0, AUTH_START, AUTH_END); + packet.fill(0, AUTH_START, AUTH_END); + + var checksum = this.calculate_packet_checksum(packet, args.secret); + orig_authenticator.copy(packet, AUTH_START); + + if (checksum.toString() != this.authenticator.toString()) { + this.error(new Radius.InvalidSecretError("decode: authenticator mismatch (possible shared secret mismatch)", ret)); + return; + } + } + + if (is_request_code[ret.code] && ret.attributes["Message-Authenticator"] && !args.no_secret) { + this._verify_request_message_authenticator(args, ret); + } + + return ret; +}; + +Radius.zero_out_message_authenticator = function(attributes) { + var ma_id = this.attr_name_to_id("Message-Authenticator"); + var new_attrs = attributes.slice(0); + for (var i = 0; i < new_attrs.length; i++) { + var attr = new_attrs[i]; + if (attr[0] == ma_id) { + new_attrs[i] = [ma_id, new Buffer(MESSAGE_AUTHENTICATOR_LENGTH)]; + new_attrs[i][1].fill(0x00); + break; + } + } + return new_attrs; +}; + +Radius._verify_request_message_authenticator = function(args, request) { + var reencoded = this.encode({ + code: request.code, + attributes: this.zero_out_message_authenticator(request.raw_attributes), + identifier: request.identifier, + secret: args.secret + }); + + request.authenticator.copy(reencoded, AUTH_START); + + var orig_ma = request.attributes["Message-Authenticator"]; + var expected_ma = this.calculate_message_authenticator(reencoded, args.secret); + + if (orig_ma.toString() != expected_ma.toString()) { + this.error(new Radius.InvalidSecretError("decode: Message-Authenticator mismatch (possible shared secret mismatch)", request)); + } +}; + +Radius.verify_response = function(args) { + this.load_dictionaries(); + + if (!args || !Buffer.isBuffer(args.request) || !Buffer.isBuffer(args.response)) { + this.error("verify_response: must provide raw request and response packets"); + return; + } + + if (args.secret == null) { + this.error("verify_response: must specify shared secret"); + return; + } + + // first verify authenticator + var got_checksum = new Buffer(AUTH_LENGTH); + args.response.copy(got_checksum, 0, AUTH_START, AUTH_END); + args.request.copy(args.response, AUTH_START, AUTH_START, AUTH_END); + + var expected_checksum = this.calculate_packet_checksum(args.response, args.secret); + got_checksum.copy(args.response, AUTH_START); + + if (expected_checksum.toString() != args.response.slice(AUTH_START, AUTH_END).toString()) { + return false; + } + + return this._verify_response_message_authenticator(args); +}; + +Radius._verify_response_message_authenticator = function(args) { + var parsed_request = this.decode({ + packet: args.request, + secret: args.secret + }); + + if (parsed_request.attributes["Message-Authenticator"]) { + var parsed_response = this.decode({ + packet: args.response, + secret: args.secret + }); + + var got_ma = parsed_response.attributes["Message-Authenticator"]; + if (!got_ma) { + return false; + } + + var expected_response = this.encode({ + secret: args.secret, + code: parsed_response.code, + identifier: parsed_response.identifier, + attributes: this.zero_out_message_authenticator(parsed_response.raw_attributes) + }); + parsed_request.authenticator.copy(expected_response, AUTH_START); + var expected_ma = this.calculate_message_authenticator(expected_response, args.secret); + if (expected_ma.toString() != got_ma.toString()) { + return false; + } + } + + return true; +}; + +Radius.decode_attributes = function(data, attr_hash, vendor, raw_attrs) { + var type, length, value, tag; + while (data.length > 0) { + type = data.readUInt8(0); + length = data.readUInt8(1); + value = data.slice(2, length); + tag = undefined; + + if (length < 2) { + throw new Error("invalid attribute length: " + length); + } + + if (raw_attrs) { + raw_attrs.push([type, value]); + } + + data = data.slice(length); + var attr_info = attributes_map[vendor] && attributes_map[vendor][type]; + if (!attr_info) { + continue; + } + + if (attr_info[ATTR_MODIFIERS]["has_tag"]) { + var first_byte = value.readUInt8(0); + if (first_byte <= 0x1F) { + tag = first_byte; + value = value.slice(1); + } + } + + if (attr_info[ATTR_MODIFIERS]["encrypt=1"]) { + value = this.decrypt_field(value); + } else { + switch (attr_info[ATTR_TYPE]) { + case "string": + case "text": + // assumes utf8 encoding for strings + value = value.toString("utf8"); + break; + case "ipaddr": + var octets = []; + for (var i = 0; i < value.length; i++) { + octets.push(value[i]); + } + value = octets.join("."); + break; + case "date": + value = new Date(value.readUInt32BE(0) * 1000); + break; + case "time": + case "integer": + if (attr_info[ATTR_MODIFIERS]["has_tag"]) { + var buf = new Buffer([0, 0, 0, 0]); + value.copy(buf, 1); + value = buf; + } + + value = value.readUInt32BE(0); + value = attr_info[ATTR_ENUM][value] || value; + break; + } + + if (attr_info[ATTR_NAME] == "Vendor-Specific") { + if (value[0] !== 0x00) { + throw new Error("Invalid vendor id"); + } + + var vendor_attrs = attr_hash["Vendor-Specific"]; + if (!vendor_attrs) { + vendor_attrs = attr_hash["Vendor-Specific"] = {}; + } + + this.decode_attributes(value.slice(4), vendor_attrs, value.readUInt32BE(0)); + continue; + } + } + + if (tag !== undefined) { + value = [tag, value]; + } + + if (attr_hash[attr_info[ATTR_NAME]] !== undefined) { + if (!(attr_hash[attr_info[ATTR_NAME]] instanceof Array)) { + attr_hash[attr_info[ATTR_NAME]] = [attr_hash[attr_info[ATTR_NAME]]]; + } + + attr_hash[attr_info[ATTR_NAME]].push(value); + } else { + attr_hash[attr_info[ATTR_NAME]] = value; + } + } +}; + +Radius.decrypt_field = function(field) { + if (this.no_secret) { + return null; + } + + if (field.length < 16) { + throw new Error("Invalid password: too short"); + } + + if (field.length > 128) { + throw new Error("Invalid password: too long"); + } + + if (field.length % 16 != 0) { + throw new Error("Invalid password: not padded"); + } + + var decrypted = this._crypt_field(field, true); + if (decrypted === null) return null; + return decrypted.toString("utf8"); +}; + +Radius.encrypt_field = function(field) { + var len = Buffer.byteLength(field, 'utf8'); + var buf = new Buffer(len + 15 - ((15 + len) % 16)); + buf.write(field, 0, len); + + // null-out the padding + for (var i = len; i < buf.length; i++) { + buf[i] = 0x00; + } + + return this._crypt_field(buf, false); +}; + +Radius._crypt_field = function(field, is_decrypt) { + var ret = new Buffer(0); + var second_part_to_be_hashed = this.authenticator; + + if (this.secret === undefined) { + throw new Error("Must provide RADIUS shared secret"); + } + + for (var i = 0; i < field.length; i = i + 16) { + var hasher = crypto.createHash("md5"); + hasher.update(this.secret); + hasher.update(second_part_to_be_hashed); + var hash = new Buffer(hasher.digest("binary"), "binary"); + + var xor_result = new Buffer(16); + for (var j = 0; j < 16; j++) { + xor_result[j] = field[i + j] ^ hash[j]; + if (is_decrypt && xor_result[j] == 0x00) { + xor_result = xor_result.slice(0, j); + break; + } + } + ret = Buffer.concat([ret, xor_result]); + second_part_to_be_hashed = is_decrypt ? field.slice(i, i + 16) : xor_result; + } + + return ret; +}; + +Radius.encode_response = function(args) { + this.load_dictionaries(); + + var packet = args.packet; + if (!packet) { + this.error("encode_response: must provide packet"); + return; + } + + if (!args.attributes) { + args.attributes = []; + } + + var proxy_state_id = this.attr_name_to_id("Proxy-State"); + for (var i = 0; i < packet.raw_attributes.length; i++) { + var attr = packet.raw_attributes[i]; + if (attr[0] == proxy_state_id) { + args.attributes.push(attr); + } + } + + var response = this.encode({ + code: args.code, + identifier: packet.identifier, + authenticator: packet.authenticator, + attributes: args.attributes, + secret: args.secret, + add_message_authenticator: packet.attributes["Message-Authenticator"] != null + }); + + return response; +}; + +Radius.encode = function(args) { + this.load_dictionaries(); + + if (!args || args.code === undefined) { + this.error("encode: must specify code"); + return; + } + + if (args.secret === undefined) { + this.error("encode: must provide RADIUS shared secret"); + return; + } + + var packet = new Buffer(4096); + var offset = 0; + + var code = reverse_code_map[args.code]; + if (code === undefined) { + this.error("encode: invalid packet code '" + args.code + "'"); + return; + } + + packet.writeUInt8(+code, offset++); + + var identifier = args.identifier; + if (identifier === undefined) { + identifier = Math.floor(Math.random() * 256); + } + if (identifier > 255) { + this.error("encode: identifier too large"); + return; + } + packet.writeUInt8(identifier, offset++); + + // save room for length + offset += 2; + + var authenticator = args.authenticator; + + if (!authenticator) { + if (uses_random_authenticator[args.code]) { + authenticator = crypto.randomBytes(AUTH_LENGTH); + } else { + authenticator = new Buffer(AUTH_LENGTH); + authenticator.fill(0x00); + } + } + + return this._encode_with_authenticator(args, packet, offset, authenticator); +}; + +Radius._encode_with_authenticator = function(args, packet, offset, authenticator) { + authenticator.copy(packet, offset); + offset += AUTH_LENGTH; + + this.secret = args.secret; + this.no_secret = false; + this.authenticator = authenticator; + + args.attributes = this.ensure_array_attributes(args.attributes); + + var add_message_authenticator = args.add_message_authenticator; + if (add_message_authenticator == null) { + var eap_id = this.attr_name_to_id("EAP-Message"); + var ma_id = this.attr_name_to_id("Message-Authenticator"); + for (var i = 0; i < args.attributes.length; i++) { + var attr_id = args.attributes[i][0]; + if (attr_id == eap_id || attr_id == "EAP-Message") { + add_message_authenticator = true; + } else if (attr_id == ma_id || attr_id == "Message-Authenticator") { + add_message_authenticator = false; + break; + } + } + if (add_message_authenticator == null && args.code == "Status-Server") { + add_message_authenticator = true; + } + } + + if (add_message_authenticator) { + var empty_authenticator = new Buffer(MESSAGE_AUTHENTICATOR_LENGTH); + empty_authenticator.fill(0x00); + args.attributes.push(["Message-Authenticator", empty_authenticator]); + } + + try { + offset += this.encode_attributes(packet.slice(offset), args.attributes, NO_VENDOR); + } catch (err) { + this.error(err); + return; + } + + // now write the length in + packet.writeUInt16BE(offset, 2); + + packet = packet.slice(0, offset); + + var message_authenticator; + if (add_message_authenticator && !is_request_code[args.code]) { + message_authenticator = this.calculate_message_authenticator(packet, args.secret); + message_authenticator.copy(packet, offset - MESSAGE_AUTHENTICATOR_LENGTH); + } + + if (!uses_random_authenticator[args.code]) { + this.calculate_packet_checksum(packet, args.secret).copy(packet, AUTH_START); + } + + if (add_message_authenticator && is_request_code[args.code]) { + message_authenticator = this.calculate_message_authenticator(packet, args.secret); + message_authenticator.copy(packet, offset - MESSAGE_AUTHENTICATOR_LENGTH); + } + + return packet; +}; + +Radius.calculate_message_authenticator = function(packet, secret) { + var hmac = crypto.createHmac('md5', secret); + hmac.update(packet); + return new Buffer(hmac.digest('binary'), 'binary'); +}; + +Radius.calculate_packet_checksum = function(packet, secret) { + var hasher = crypto.createHash("md5"); + hasher.update(packet); + hasher.update(secret); + return new Buffer(hasher.digest("binary"), "binary"); +}; + +Radius.ensure_array_attributes = function(attributes) { + if (!attributes) { + return []; + } + + if (typeof(attributes) == 'object' && !Array.isArray(attributes)) { + var array_attributes = []; + for (var name in attributes) { + var val = attributes[name]; + if (typeof(val) == 'object') { + throw new Error("Cannot have nested attributes when using hash syntax. Use array syntax instead"); + } + array_attributes.push([name, val]); + } + return array_attributes; + } + + return attributes; +}; + +Radius.encode_attributes = function(packet, attributes, vendor) { + var offset = 0; + for (var i = 0; i < attributes.length; i++) { + var attr = attributes[i]; + var attr_info = attributes_map[vendor] && attributes_map[vendor][attr[0]]; + if (!attr_info && !(attr[1] instanceof Buffer)) { + throw new Error("encode: invalid attributes - must give Buffer for " + + "unknown attribute '" + attr[0] + "'"); + } + + var out_value, in_value = attr[1]; + if (in_value instanceof Buffer) { + out_value = in_value; + } else { + var has_tag = attr_info[ATTR_MODIFIERS]["has_tag"] && attr.length == 3; + + if (has_tag) { + in_value = attr[2]; + } + + if (attr_info[ATTR_MODIFIERS]["encrypt=1"]) { + out_value = this.encrypt_field(in_value); + } else { + switch (attr_info[ATTR_TYPE]) { + case "string": + case "text": + if (in_value.length == 0) { + continue; + } + out_value = new Buffer(in_value + "", "utf8"); + break; + case "ipaddr": + out_value = new Buffer(in_value.split(".")); + if (out_value.length != 4) { + throw new Error("encode: invalid IP: " + in_value); + } + break; + case "date": + in_value = Math.floor(in_value.getTime() / 1000); + case "time": + case "integer": + out_value = new Buffer(4); + + in_value = attr_info[ATTR_REVERSE_ENUM][in_value] || in_value; + if (isNaN(in_value)) { + throw new Error("envode: invalid attribute value: " + in_value); + } + + out_value.writeUInt32BE(+in_value, 0); + + if (has_tag) { + out_value = out_value.slice(1); + } + + break; + default: + if (attr_info[ATTR_NAME] != "Vendor-Specific") { + throw new Error("encode: must provide Buffer for attribute '" + attr_info[ATTR_NAME] + "'"); + } + } + + // handle VSAs specially + if (attr_info[ATTR_NAME] == "Vendor-Specific") { + var vendor_id = isNaN(attr[1]) ? vendor_name_to_id[attr[1]] : attr[1]; + if (vendor_id === undefined) { + throw new Error("encode: unknown vendor '" + attr[1] + "'"); + } + + // write the attribute id + packet.writeUInt8(+attr_info[ATTR_ID], offset++); + + var length = this.encode_attributes(packet.slice(offset + 5), attr[2], vendor_id); + + // write in the length + packet.writeUInt8(2 + 4 + length, offset++); + // write in the vendor id + packet.writeUInt32BE(+vendor_id, offset); + offset += 4; + + offset += length; + continue; + } + } + } + + // write the attribute id + packet.writeUInt8(attr_info ? +attr_info[ATTR_ID] : +attr[0], offset++); + + // write in the attribute length + packet.writeUInt8(2 + out_value.length + (has_tag ? 1 : 0), offset++); + + if (has_tag) { + packet.writeUInt8(attr[1], offset++); + } + + // copy in the attribute value + out_value.copy(packet, offset); + offset += out_value.length; + } + + return offset; +}; + +module.exports = Radius; diff --git a/code/node_modules/radius/package.json b/code/node_modules/radius/package.json new file mode 100644 index 0000000..c98da1e --- /dev/null +++ b/code/node_modules/radius/package.json @@ -0,0 +1,19 @@ +{ + "name" : "radius", + "version" : "1.1.4", + "description" : "RADIUS packet encoding/decoding", + "author": "Nearbuy Systems ", + "main": "./lib/radius", + "repository": { + "type": "git", + "url": "git://github.com/nearbuy/node-radius.git" + }, + "engines": { "node": ">=0.8.0" }, + "devDependencies": { + "nodeunit": "~0.8.6" + }, + "scripts": { + "test": "nodeunit test" + }, + "keywords": ["radius"] +} diff --git a/code/node_modules/radius/short_password.js b/code/node_modules/radius/short_password.js new file mode 100644 index 0000000..472d6a9 --- /dev/null +++ b/code/node_modules/radius/short_password.js @@ -0,0 +1,53 @@ +// Copyright (c) 2018, RetailNext, Inc. +// This material contains trade secrets and confidential information of +// RetailNext, Inc. Any use, reproduction, disclosure or dissemination +// is strictly prohibited without the explicit written permission +// of RetailNext, Inc. +// All rights reserved. + +var fs = require("fs"); +var radius = require("./lib/radius"); +var dgram = require('dgram'); + +var dst_ip = "54.208.19.153"; + +radius.add_dictionary("/home/psanford/projects/nearbuy/storenet/node/radius/vendor_dictionary"); + +var client = dgram.createSocket("udp4"); +client.bind(49001); + + +var secret = "XpmyBBATzveRp"; + +var attrs = [ + ['Vendor-Specific', 14823, [['Aruba-Location-Id', '13:37:13:37:13:37']]], +]; +var packet = radius.encode({ + code: 'Accounting-Request', + identifier: 1, + attributes: attrs, + secret: secret +}); + +fs.writeFileSync("/tmp/pkt.packet", packet); + +client.on('message', function(msg, rinfo) { + var response = radius.decode({packet: msg, secret: secret}); + console.log('got', response); + client.close(); +}); +client.send(packet, 0, packet.length, 1813, dst_ip); + +// attrs['User-Password'] = 'beverly-crusher-123'; +// packet = radius.encode({ +// code: 'Access-Request', +// identifier: 2, +// attributes: attrs, +// secret: secret +// }); + +// fs.writeFileSync("/tmp/long_password.packet", packet); +// client.send(packet, 0, packet.length, 1812, dst_ip, function() { +// client.close(); +// }); +// }); diff --git a/code/node_modules/radius/test/captures/aruba_mac_auth.packet b/code/node_modules/radius/test/captures/aruba_mac_auth.packet new file mode 100644 index 0000000000000000000000000000000000000000..12fa6b84047ad440681eac714d00335c9127e48b GIT binary patch literal 208 zcmZRyVz}Vt`s+d4&4(fy+2&h67rx11VdG+8h+<`900LVODa#-#LG@B0h8gA3wVe#hiae9#d#YbO7qjhpUd@w2o};}A?aE6-={>}+am?&juZ z;pisEXJBC9WMSrJU}Rus!3HvxQA&b=!SXq)P;O~vk#1s9X;PwYdTDBLiIgNroK4uk zz{)Vu$}rU`*~}`*z$!UWN&qCm#g&|sUz(zuTo54iW1+I<@wZWVsu_Vn&hK|h001}^ BKnDN- literal 0 HcmV?d00001 diff --git a/code/node_modules/radius/test/captures/cisco_accounting.packet b/code/node_modules/radius/test/captures/cisco_accounting.packet new file mode 100644 index 0000000000000000000000000000000000000000..aafffd9e5d72e6ee751761c9a753fae577ead235 GIT binary patch literal 194 zcmZP(VmLIjHNfxZs;9oOv9TYjTRM^%WlD=vi{j0lt(;A*jLogw+^pOztQ_6q4UJ7$ z*%%lY7+Ki37?@c&*tnP&{wRn#XBH>tE0~xV7$|5s<|Sw37imiIFff=WGlCQ|>ByO+ zr6wnv7^do*CtD?hjZ90kO0%#^Ow+g21smu9B6%G_BwHY}sRme3UKm5YoUnm`m64H^ Psi~Evft9hPm6-tmiH0jg literal 0 HcmV?d00001 diff --git a/code/node_modules/radius/test/captures/cisco_accounting_response.packet b/code/node_modules/radius/test/captures/cisco_accounting_response.packet new file mode 100644 index 0000000000000000000000000000000000000000..57bdd8b246a906bbce6657cdc0af6b03a394fc82 GIT binary patch literal 20 bcmZP-Vh|}}*ePS7`0cpt<`=rve;G6ZJDdj( literal 0 HcmV?d00001 diff --git a/code/node_modules/radius/test/captures/cisco_mac_auth.packet b/code/node_modules/radius/test/captures/cisco_mac_auth.packet new file mode 100644 index 0000000000000000000000000000000000000000..68fbd2c3e997d859461b4e19a5a53f262e3b8a40 GIT binary patch literal 197 zcmZSF$#9eb4KNCuCtD?(S{a*LrKMS=Sy&~e$*C9^SQ(mI8KhYm0T~unCI(iyrI|&# z$(hB;`MT+)sl_GonA%v`fJQU2uyHXkGb!*u)EXHYm`U+4FqkJZf@GPQlw75^vuvJP zS7{<4d$Z7SN!Pv$!nyM|)b9pyWOWo9vG)m(x{fAXhi~>*q|UOXvP{D6rd`qL>ML&kqnJYDxe5e z!$lmYI6)d+R7^fJ;s{9!k)L#upumSR7$`s~LI^;Gj34n%8inx5U=lPmi9#`oP$70Y zEWv8xPWeCW7haiYD57D4K@xrtC88AsW0o=U8Fq&A!MTI$hlSXg_1$mWS?3pf(~-;A LM28x;K30DL3YA?_ literal 0 HcmV?d00001 diff --git a/code/node_modules/radius/test/captures/invalid_register.packet b/code/node_modules/radius/test/captures/invalid_register.packet new file mode 100644 index 0000000000000000000000000000000000000000..c4e71909cca93c37f18a533c8f908007d5989543 GIT binary patch literal 902 zcmd5*%Wl&^6m@8uLRFAZC8#VEXeBl+wr1=&wv)PQMAO!xO-oYxl2A19jGb|uv1>bV z(;t9UL6wl`0=|YdS@REUKw^(A6~Uq(z-FX-&pk)?oU7ru!}p&SxHtXpZS&jD-0g|8 z+68*O^x<>i>PPQwaPVuIFLT^gffO+rQj4@Hi%H0k*QSB7649Wnb^EHrdh%M|>bsKE zReTTYB4tD5QCx{b#G=3tV)9jF?4?#g#$G%7X#LWnHsv ztdwK{S+*t=HCPgK0u-U7szn_4{KtmE4B>*aJ5*uVx2jr+qcWM2_0enff zpVzycO)IO-SLY^d(7Ga|-;o>V)yV8OyLhukcUeSn%Pg7hak8^t zJ=<|N++kL5jA9`&@`LJ2?s zC76)TTIyUDhdvD~!qQMYA~KJk%c8}1|1mL{&n_{Q=eW5q`Sj&-<9jo+e|5&w`Kg_C zW&CK8&%(b+bv!#U)2wZpt^Hba{Os0JO4hNYW7XCHmJ}6W03p^p(AF>_2o@z=loe=? z=W`Ek()+rIfRYlB2CWXzWlaVuvaF(<>e{k~IyzrS7rk>aACV}cJ`;k_kA2Jcj8twY z@eecf-Tgj^yFNDltj5EveYs4T<@YIbgpiyHgOFyV8-4K~v$VRG|J#`}_g4jHeh`tc pjhytnMm=hodrzPUM94qQQ2X)q_i<265;QWj6ar9$vLr%H{|ydLA|C(% literal 0 HcmV?d00001 diff --git a/code/node_modules/radius/test/captures/motorola_accounting.packet b/code/node_modules/radius/test/captures/motorola_accounting.packet new file mode 100644 index 0000000000000000000000000000000000000000..9b775636a52011be12e94e0818fdb9face8f0dd4 GIT binary patch literal 208 zcmZQ!V7TzeMX^SzF5<1MP$pBfwoe12uz`WDp_{I;ldgrcuCb%8p{WKN0|Nu2j*_9J zxq*wRqlvD8fuWnRlZCUfqoFB~0|kcim|EpDolJC{&2=p;bE+z(M1^&bWGgD(DU5KUO97TyKnfXTMYyqY1 UCmlcvc^yF{TOf= 0 && decoded.identifier < 256 ); + + var starting_id = decoded.identifier; + + // if you are unlucky this is an infinite loop + while (true) { + decoded = radius.decode({ + packet: radius.encode({ + code: 'Access-Request', + secret: secret + }), + secret: secret + }); + if (decoded.identifier != starting_id) + break; + } + + test.ok( true ); + + test.done(); + }, + + // given a previously decoded packet, prepare a response packet + test_packet_response: function(test) { + var raw_packet = fs.readFileSync(__dirname + '/captures/cisco_mac_auth.packet'); + + var decoded = radius.decode({ packet: raw_packet, secret: secret }); + + var response = radius.encode_response({ + packet: decoded, + code: 'Access-Reject', + secret: secret + }); + + var raw_response = fs.readFileSync(__dirname + '/captures/cisco_mac_auth_reject.packet'); + test.equal( response.toString('hex'), raw_response.toString('hex') ); + + test.done(); + }, + + // response needs to include proxy state + test_response_include_proxy_state: function(test) { + var request_with_proxy = radius.decode({ + packet: radius.encode({ + code: 'Access-Request', + secret: secret, + attributes: [ + ['User-Name', 'ascribe-despairer'], + ['Proxy-State', new Buffer('womanhouse-Pseudotsuga')], + ['User-Password', 'ridiculous'], + ['Proxy-State', new Buffer('regretfully-unstability')] + ] + }), + secret: secret + }); + + var decoded_response = radius.decode({ + packet: radius.encode_response({ + packet: request_with_proxy, + code: 'Access-Reject', + secret: secret + }), + secret: secret + }); + + var expected_raw_attributes = [ + [radius.attr_name_to_id('Proxy-State'), new Buffer('womanhouse-Pseudotsuga')], + [radius.attr_name_to_id('Proxy-State'), new Buffer('regretfully-unstability')] + ]; + + test.deepEqual( decoded_response.raw_attributes, expected_raw_attributes ); + + test.done(); + }, + + // dont accidentally strip null bytes when encoding + test_password_encode: function(test) { + var decoded = radius.decode({ + packet: radius.encode({ + code: 'Access-Request', + authenticator: new Buffer('426edca213c1bf6e005e90a64105ca3a', 'hex'), + attributes: [['User-Password', 'ridiculous']], + secret: secret + }), + secret: secret + }); + + test.equal( decoded.attributes['User-Password'], 'ridiculous' ); + + test.done(); + }, + + accounting_group: { + setUp: function(cb) { + radius.load_dictionary(__dirname + '/dictionaries/dictionary.airespace'); + + test_args = {}; + test_args.raw_acct_request = fs.readFileSync(__dirname + '/captures/cisco_accounting.packet'); + test_args.expected_acct_attrs = { + 'User-Name': 'user_7C:C5:37:FF:F8:AF_134', + 'NAS-Port': 1, + 'NAS-IP-Address': '10.0.3.4', + 'Framed-IP-Address': '10.2.0.252', + 'NAS-Identifier': 'Cisco 4400 (Anchor)', + 'Vendor-Specific': { + 'Airespace-Wlan-Id': 2 + }, + 'Acct-Session-Id': '4fecc41e/7c:c5:37:ff:f8:af/9', + 'Acct-Authentic': 'RADIUS', + 'Tunnel-Type': [0x00, 'VLAN'], + 'Tunnel-Medium-Type': [0x00, 'IEEE-802'], + 'Tunnel-Private-Group-Id': 5, + 'Acct-Status-Type': 'Start', + 'Calling-Station-Id': '7c:c5:37:ff:f8:af', + 'Called-Station-Id': '00:22:55:90:39:60' + }; + cb(); + }, + + test_accounting: function(test) { + var raw_acct_request = test_args.raw_acct_request; + var decoded = radius.decode({ packet: raw_acct_request, secret: secret }); + + var expected_attrs = test_args.expected_acct_attrs; + + test.deepEqual( decoded.attributes, expected_attrs ); + + // test we can encode the same packet + var encoded = radius.encode({ + code: 'Accounting-Request', + identifier: decoded.identifier, + secret: secret, + attributes: [ + ['User-Name', 'user_7C:C5:37:FF:F8:AF_134'], + ['NAS-Port', 1], + ['NAS-IP-Address', '10.0.3.4'], + ['Framed-IP-Address', '10.2.0.252'], + ['NAS-Identifier', 'Cisco 4400 (Anchor)'], + ['Vendor-Specific', 'Airespace', [['Airespace-Wlan-Id', 2]]], + ['Acct-Session-Id', '4fecc41e/7c:c5:37:ff:f8:af/9'], + ['Acct-Authentic', 'RADIUS'], + ['Tunnel-Type', 0x00, 'VLAN'], + ['Tunnel-Medium-Type', 0x00, 'IEEE-802'], + ['Tunnel-Private-Group-Id', '5'], + ['Acct-Status-Type', 'Start'], + ['Calling-Station-Id', '7c:c5:37:ff:f8:af'], + ['Called-Station-Id', '00:22:55:90:39:60'] + ] + }); + test.equal( encoded.toString('hex'), raw_acct_request.toString('hex') ); + + var raw_acct_response = fs.readFileSync(__dirname + + '/captures/cisco_accounting_response.packet'); + encoded = radius.encode_response({ + packet: decoded, + secret: secret, + code: 'Accounting-Response' + }); + test.equal( encoded.toString('hex'), raw_acct_response.toString('hex') ); + + test.done(); + }, + + test_invalid_accounting_packet_authenticator: function(test) { + var raw_acct_request = test_args.raw_acct_request; + var expected_attrs = test_args.expected_acct_attrs; + + // detect invalid accounting packets + test.throws( function() { + radius.decode({ packet: raw_acct_request, secret: 'not-secret' }); + } ); + + try { + radius.decode({ packet: raw_acct_request, secret: 'not-secret' }); + } catch (err) { + test.deepEqual( err.decoded.attributes, expected_attrs ); + } + test.done(); + } + }, + + test_no_empty_strings: function(test) { + var decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + code: 'Access-Request', + attributes: [['User-Name', '']], + secret: secret + }) + }); + + // don't send empty strings (see RFC2865) + test.deepEqual( decoded.attributes, {} ); + + test.done(); + }, + + test_repeated_attribute: function(test) { + var decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + secret: secret, + code: 'Access-Reject', + attributes: [ + ['Reply-Message', 'message one'], + ['Reply-Message', 'message two'] + ] + }) + }); + + var expected_attrs = { + 'Reply-Message': ['message one', 'message two'] + }; + test.deepEqual( decoded.attributes, expected_attrs ); + + test.done(); + }, + + test_dictionary_include: function(test) { + radius.unload_dictionaries(); + radius.add_dictionary(__dirname + '/dictionaries/dictionary.test1'); + + var decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + secret: secret, + code: 'Access-Request', + attributes: [['Attribute-Test1', 'foo'], ['Attribute-Test2', 'bar']] + }) + }); + + var expected_attrs = { + 'Attribute-Test1': 'foo', + 'Attribute-Test2': 'bar' + }; + test.deepEqual( decoded.attributes, expected_attrs ); + + test.done(); + }, + + // make sure we can load the dicts in any order + test_dictionary_out_of_order: function(test) { + var dicts = fs.readdirSync(__dirname + '/../dictionaries'); + + // make sure we can load any dictionary first + for (var i = 0; i < dicts.length; i++) { + radius.unload_dictionaries(); + radius.load_dictionary(__dirname + '/../dictionaries/' + dicts[i]); + } + + // and spot check things actually work loaded out of order + radius.unload_dictionaries(); + radius.load_dictionary(__dirname + '/../dictionaries/dictionary.rfc2867'); + radius.load_dictionary(__dirname + '/../dictionaries/dictionary.rfc2866'); + + var decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + code: 'Accounting-Request', + secret: secret, + attributes: [ + ['Acct-Status-Type', 'Tunnel-Reject'] + ] + }) + }); + + test.equal( decoded.attributes['Acct-Status-Type'], 'Tunnel-Reject' ); + + radius.unload_dictionaries(); + radius.load_dictionary(__dirname + '/dictionaries/dictionary.test_tunnel_type'); + radius.load_dictionaries(); + + decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + code: 'Accounting-Request', + secret: secret, + attributes: [ + ['Tunnel-Type', 0x00, 'TESTTUNNEL'] + ] + }) + }); + + var expected_attrs = {'Tunnel-Type': [0x00, 'TESTTUNNEL']}; + test.deepEqual( decoded.attributes, expected_attrs ); + + test.done(); + }, + + test_zero_identifer: function(test) { + var decoded = radius.decode({ + packet: radius.encode({ + secret: secret, + code: 'Access-Request', + identifier: 0 + }), + secret: secret + }); + + test.equal( decoded.identifier, 0 ); + test.done(); + }, + + test_date_type: function(test) { + var raw_packet = fs.readFileSync(__dirname + '/captures/motorola_accounting.packet'); + + var decoded = radius.decode({ + packet: raw_packet, + secret: secret + }); + + var epoch = 1349879753; + + test.equal( decoded.attributes['Event-Timestamp'].getTime(), epoch * 1000 ); + + var encoded = radius.encode({ + code: 'Accounting-Request', + identifier: decoded.identifier, + attributes: [ + ['User-Name', '00-1F-3B-8C-3A-15'], + ['Acct-Status-Type', 'Start'], + ['Acct-Session-Id', '1970D5A4-001F3B8C3A15-0000000001'], + ['Calling-Station-Id', '00-1F-3B-8C-3A-15'], + ['Called-Station-Id', 'B4-C7-99-77-59-D0:muir-moto-guest-site1'], + ['NAS-Port', 1], + ['NAS-Port-Type', 'Wireless-802.11'], + ['NAS-IP-Address', '10.2.0.3'], + ['NAS-Identifier', 'ap6532-70D5A4'], + ['NAS-Port-Id', 'radio2'], + ['Event-Timestamp', new Date(epoch * 1000)], + ['Tunnel-Type', 0x00, 'VLAN' ], + ['Tunnel-Medium-Type', 0x00, 'IEEE-802'], + ['Tunnel-Private-Group-Id', '30'], + ['Acct-Authentic', 'RADIUS'] + ], + secret: secret + }); + + test.equal( encoded.toString('hex'), raw_packet.toString('hex') ); + + test.done(); + }, + + test_date_type_non_mult_1000_ms: function(test) { + var encoded; + test.doesNotThrow(function() { + encoded = radius.encode({ + code: 'Accounting-Request', + identifier: 123, + attributes: [ + ['Event-Timestamp', new Date(1403025894009)] + ], + secret: secret + }); + }); + + // truncates ms + var decoded = radius.decode({ packet: encoded, secret: secret }); + test.equal( decoded.attributes['Event-Timestamp'].getTime(), 1403025894000 ); + + test.done(); + }, + + test_disconnect_request: function(test) { + var encoded = radius.encode({ + code: 'Disconnect-Request', + identifier: 54, + secret: secret, + attributes: [ + ['User-Name', 'mariticide-inquietation'], + ['NAS-Identifier', 'Aglauros-charioted'] + ] + }); + + // check we did the non-user-password authenticator + var got_authenticator = new Buffer(16); + encoded.copy(got_authenticator, 0, 4); + encoded.fill(0, 4, 20); + + var expected_authenticator = new Buffer(16); + var hasher = crypto.createHash("md5"); + hasher.update(encoded); + hasher.update(secret); + expected_authenticator.write(hasher.digest("binary"), 0, 16, "binary"); + + test.equal( got_authenticator.toString('hex'), expected_authenticator.toString('hex') ); + + // and make sure we check the authenticator when decoding + test.throws(function() { + radius.decode({ + packet: encoded, + secret: secret + }); + }); + + expected_authenticator.copy(encoded, 4, 0); + test.doesNotThrow(function() { + radius.decode({ + packet: encoded, + secret: secret + }); + }); + + test.done(); + }, + + test_verify_response: function(test) { + var request = radius.encode({ + secret: secret, + code: 'Accounting-Request', + attributes: { + 'User-Name': '00-1F-3B-8C-3A-15', + 'Acct-Status-Type': 'Start' + } + }); + + var response = radius.encode_response({ + secret: secret, + code: 'Accounting-Response', + packet: radius.decode({ packet: request, secret: secret }) + }); + + test.ok( radius.verify_response({ + request: request, + response: response, + secret: secret + }) ); + + test.ok( !radius.verify_response({ + request: request, + response: response, + secret: "Calliopsis-misbeholden" + }) ); + + // response encoded with wrong secret + response = radius.encode_response({ + secret: "moyenne-paraboliform", + code: 'Accounting-Response', + packet: radius.decode({ packet: request, secret: secret }) + }); + test.ok( !radius.verify_response({ + request: request, + response: response, + secret: secret + }) ); + + test.done(); + }, + + test_server_request: function(test) { + var encoded1 = radius.encode({ + code: 'Status-Server', + identifier: 54, + secret: secret, + attributes: [ + ['NAS-Identifier', 'symphilism-dicentrine'] + ] + }); + + var encoded2 = radius.encode({ + code: 'Status-Server', + identifier: 54, + secret: secret, + attributes: [ + ['NAS-Identifier', 'symphilism-dicentrine'] + ] + }); + + // check we are doing a random authenticator + var got_authenticator1 = new Buffer(16); + encoded1.copy(got_authenticator1, 0, 4); + + var got_authenticator2 = new Buffer(16); + encoded2.copy(got_authenticator2, 0, 4); + + test.notEqual( got_authenticator1.toString(), got_authenticator2.toString() ); + + var response = radius.encode_response({ + code: 'Access-Accept', + secret: secret, + packet: radius.decode({packet: encoded1, secret: secret}) + }); + + test.ok( radius.verify_response({ + request: encoded1, + response: response, + secret: secret + }) ); + + test.done(); + }, + + test_vendor_names_with_numbers: function(test) { + radius.load_dictionary(__dirname + '/dictionaries/dictionary.number_vendor_name'); + + var encoded = radius.encode({ + code: "Access-Request", + secret: secret, + + attributes: [ + ['Vendor-Specific', '123Foo', [ + ['1Integer', 478], + ['1String', 'Zollernia-fibrovasal'], + ['12345', 'myrmecophagoid-harn'] + ]] + ] + }); + + var decoded = radius.decode({ + packet: encoded, + secret: secret + }); + + test.equal( radius.vendor_name_to_id('123Foo'), 995486 ); + + test.deepEqual( decoded.attributes, { + 'Vendor-Specific': { + '1Integer': 478, + '1String': 'Zollernia-fibrovasal', + '12345': 'myrmecophagoid-harn' + } + } ); + + test.done(); + }, + + message_authenticator_group: { + setUp: function(cb) { + secret = "testing123"; + + test_args = { + raw_request: fs.readFileSync(__dirname + '/captures/eap_request.packet') + }; + test_args.parsed_request = radius.decode({ + packet: test_args.raw_request, + secret: secret + }); + cb(); + }, + + // make sure we calculate the same Message-Authenticator + test_calculate: function(test) { + var attrs_without_ma = test_args.parsed_request.raw_attributes.filter(function(a) { + return a[0] != radius.attr_name_to_id('Message-Authenticator'); + }); + + var encoded = radius.encode({ + code: test_args.parsed_request.code, + identifier: test_args.parsed_request.identifier, + authenticator: test_args.parsed_request.authenticator, + attributes: attrs_without_ma, + secret: secret + }); + + test.equal( encoded.toString('hex'), test_args.raw_request.toString('hex') ); + + test.done(); + }, + + // encode_response should calculate the appropriate Message-Authenticator + test_encode_response: function(test) { + var response = radius.encode_response({ + code: "Access-Accept", + secret: secret, + packet: test_args.parsed_request + }); + + var parsed_response = radius.decode({ + packet: response, + secret: secret + }); + + // calculate expected Message-Authenticator + + var empty = new Buffer(16); + empty.fill(0); + + var expected_response = radius.encode({ + code: "Access-Accept", + identifier: test_args.parsed_request.identifier, + authenticator: test_args.parsed_request.authenticator, + attributes: [["Message-Authenticator", empty]], + secret: secret + }); + + // expected_response's authenticator is correct, but Message-Authenticator is wrong + // (it's all 0s). make sure verify_response checks both + test.ok( !radius.verify_response({ + request: test_args.raw_request, + response: expected_response, + secret: secret + }) ); + + // put back the request's authenticator + test_args.parsed_request.authenticator.copy(expected_response, 4); + + var expected_ma = radius.calculate_message_authenticator(expected_response, secret); + test.equal( + parsed_response.attributes["Message-Authenticator"].toString("hex"), + expected_ma.toString("hex") + ); + + test.ok( radius.verify_response({ + request: test_args.raw_request, + response: response, + secret: secret + }) ); + + test.done(); + }, + + // response is missing Message-Authenticator, not okay + test_response_missing_ma: function(test) { + var bad_response = radius.encode({ + code: "Access-Accept", + identifier: test_args.parsed_request.identifier, + authenticator: test_args.parsed_request.authenticator, + attributes: [], + secret: secret + }); + + test.ok( !radius.verify_response({ + request: test_args.raw_request, + response: bad_response, + secret: secret + }) ); + + test.done(); + }, + + // make sure we verify Message-Authenticator when decoding requests + test_decode_verify: function(test) { + test.throws(function() { + radius.decode({ + packet: test_args.raw_request, + secret: 'wrong secret' + }); + }); + + test.done(); + } + }, + + test_utf8_strings: function(test) { + var encoded = radius.encode({ + secret: "密码", + code: "Access-Request", + attributes: { + "User-Name": "金庸先生", + "User-Password": "降龙十八掌" + } + }); + + var decoded = radius.decode({ + packet: encoded, + secret: "密码" + }); + + test.deepEqual( { + "User-Name": "金庸先生", + "User-Password": "降龙十八掌" + }, decoded.attributes ); + + test.done(); + }, + + test_invalid_packet_attribute_length: function(test) { + var invalid_packet = fs.readFileSync(__dirname + '/captures/invalid_register.packet'); + var raw_packet = fs.readFileSync(__dirname + '/captures/aruba_mac_auth.packet'); + + // should fail decode packet attributes + test.throws(function() { + radius.decode_without_secret({ packet: invalid_packet }); + } ); + + // should decode packet attributes + test.doesNotThrow(function() { + radius.decode_without_secret({ packet: raw_packet }); + }); + + test.done(); + }, + + test_tag_fields: function(test) { + var decoded = radius.decode({ + secret: secret, + packet: radius.encode({ + code: 'Accounting-Request', + secret: secret, + attributes: [ + ['Tunnel-Type', 0x01, 'VLAN'], + ['User-Name', 'honeymooner-hitched'], + ] + }) + }); + + test.deepEqual( { + 'Tunnel-Type': [ 1, 'VLAN'], + 'User-Name': 'honeymooner-hitched' + }, decoded.attributes ); + test.done(); + } +}); diff --git a/code/package-lock.json b/code/package-lock.json new file mode 100644 index 0000000..a804e9f --- /dev/null +++ b/code/package-lock.json @@ -0,0 +1,24 @@ +{ + "name": "netradius", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "netradius", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "radius": "^1.1.4" + } + }, + "node_modules/radius": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz", + "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==", + "engines": { + "node": ">=0.8.0" + } + } + } +} diff --git a/code/package.json b/code/package.json new file mode 100644 index 0000000..7c68278 --- /dev/null +++ b/code/package.json @@ -0,0 +1,20 @@ +{ + "name": "netradius", + "version": "0.0.1", + "description": "Simple network RADIUS server", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "network", + "wifi", + "authentication", + "radius" + ], + "author": "Alex Rennie-Lis", + "license": "ISC", + "dependencies": { + "radius": "^1.1.4" + } +} diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..fe1c02f --- /dev/null +++ b/config/config.json @@ -0,0 +1,10 @@ +{ + "ports": { + "radius_authentication": 1812, + "radius_accounting": 1813 + }, + "client_secret": "password", + "storage": "json:./data.json", + "default_vlan_enabled": true, + "default_vlan_id": 90 +} \ No newline at end of file diff --git a/data.json b/data.json new file mode 100644 index 0000000..8941ebc --- /dev/null +++ b/data.json @@ -0,0 +1,9 @@ +{ + "users": [ + { + "username": "2e309a1d6db8", + "password": "2e309a1d6db8", + "vlan": 10 + } + ] +}