├── .gitignore
├── public
├── scriptblocks.txt
└── images-commander
│ ├── Thumbs.db
│ ├── icons26.png
│ ├── icons50.png
│ ├── logoback.png
│ ├── certificate.png
│ ├── icons200-1-1.png
│ ├── icons200-2-1.png
│ ├── icons200-3-1.png
│ ├── icons200-4-1.png
│ ├── icons200-5-1.png
│ └── icons200-6-1.png
├── config
├── sea-config.json
└── rollup.config.mjs
├── compose.yaml
├── Dockerfile
├── .dockerignore
├── api.js
├── meshcommander.js
├── package.json
├── readme.md
├── common.js
├── webserver.js
├── LICENSE
└── interceptor.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | out
4 | computers.json
5 | .env
--------------------------------------------------------------------------------
/public/scriptblocks.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/scriptblocks.txt
--------------------------------------------------------------------------------
/public/images-commander/Thumbs.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/Thumbs.db
--------------------------------------------------------------------------------
/public/images-commander/icons26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons26.png
--------------------------------------------------------------------------------
/public/images-commander/icons50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons50.png
--------------------------------------------------------------------------------
/public/images-commander/logoback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/logoback.png
--------------------------------------------------------------------------------
/public/images-commander/certificate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/certificate.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-1-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-1-1.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-2-1.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-3-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-3-1.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-4-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-4-1.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-5-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-5-1.png
--------------------------------------------------------------------------------
/public/images-commander/icons200-6-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrytonSalisbury/mesh-mini/HEAD/public/images-commander/icons200-6-1.png
--------------------------------------------------------------------------------
/config/sea-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "build\\bundled-mesh.js",
3 | "output": "build\\sea-prep.blob",
4 | "disableExperimentalSEAWarning": true
5 | }
6 |
--------------------------------------------------------------------------------
/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | server:
3 | build:
4 | context: .
5 | environment:
6 | NODE_ENV: production
7 | ports:
8 | - 3000:3000
9 |
--------------------------------------------------------------------------------
/config/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import nodeResolve from "@rollup/plugin-node-resolve";
2 | import commonjs from "@rollup/plugin-commonjs";
3 | import json from "@rollup/plugin-json";
4 |
5 | export default {
6 | input: ["meshcommander.js"],
7 | output: {
8 | file: ".\\build\\bundled-mesh.js",
9 | format: "cjs",
10 | },
11 | plugins: [commonjs(), nodeResolve(), json()],
12 | };
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | ARG NODE_VERSION=21.6.0
4 | FROM node:${NODE_VERSION}-alpine
5 | ENV NODE_ENV production
6 | WORKDIR /usr/src/app
7 |
8 | # Download dependencies as a separate step to take advantage of Docker's caching.
9 | # Leverage a cache mount to /root/.npm to speed up subsequent builds.
10 | # Leverage a bind mounts to package.json and package-lock.json to avoid having to copy them into
11 | # into this layer.
12 | RUN --mount=type=bind,source=package.json,target=package.json \
13 | --mount=type=bind,source=package-lock.json,target=package-lock.json \
14 | --mount=type=cache,target=/root/.npm \
15 | npm ci --omit=dev
16 |
17 | COPY . .
18 | EXPOSE 3000
19 | CMD node meshcommander.js
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Include any files or directories that you don't want to be copied to your
2 | # container here (e.g., local build artifacts, temporary files, etc.).
3 | #
4 | # For more help, visit the .dockerignore file reference guide at
5 | # https://docs.docker.com/go/build-context-dockerignore/
6 |
7 | **/.classpath
8 | **/.dockerignore
9 | **/.env
10 | **/.git
11 | **/.gitignore
12 | **/.project
13 | **/.settings
14 | **/.toolstarget
15 | **/.vs
16 | **/.vscode
17 | **/.next
18 | **/.cache
19 | **/*.*proj.user
20 | **/*.dbmdl
21 | **/*.jfm
22 | **/charts
23 | **/docker-compose*
24 | **/compose*
25 | **/Dockerfile*
26 | **/node_modules
27 | **/npm-debug.log
28 | **/obj
29 | **/secrets.dev.yaml
30 | **/values.dev.yaml
31 | **/build
32 | **/dist
33 | LICENSE
34 | README.md
35 | computers.json
36 |
--------------------------------------------------------------------------------
/api.js:
--------------------------------------------------------------------------------
1 | const { readFileSync, writeFileSync } = require("fs");
2 |
3 | const express = require("express");
4 |
5 | const ApiRouter = express.Router();
6 | const bodyParser = require("body-parser");
7 | ApiRouter.use(bodyParser.json({ limit: "10mb" }));
8 | ApiRouter.use(computerListCheck);
9 |
10 | const computerPath = process.env.COMPUTER_PATH
11 | ? process.env.COMPUTER_PATH
12 | : "./computers.json";
13 |
14 | ApiRouter.get("/computerlist", (_req, res) => {
15 | const computerList = JSON.parse(readFileSync(computerPath));
16 | res.json(computerList);
17 | });
18 |
19 | ApiRouter.post("/computerlist", (req, res) => {
20 | writeFileSync(computerPath, JSON.stringify(req.body));
21 | res.json(req.body);
22 | });
23 |
24 | function computerListCheck(_req, _res, next) {
25 | try {
26 | readFileSync(computerPath);
27 | } catch (err) {
28 | writeFileSync(computerPath, JSON.stringify([]));
29 | }
30 | next();
31 | }
32 |
33 | module.exports = ApiRouter;
34 |
--------------------------------------------------------------------------------
/meshcommander.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description MeshCommander
3 | * @author Ylian Saint-Hilaire
4 | * @copyright Intel Corporation 2018
5 | * @license Apache-2.0
6 | * @version v0.0.3
7 | */
8 |
9 | function CreateMeshCommanderServer(args) {
10 | var obj = {};
11 | obj.args = require("minimist")(process.argv.slice(2));
12 |
13 | // Start the Meshcommander server
14 | obj.Start = function () {
15 | obj.webserver = require("./webserver.js").CreateWebServer(obj.args);
16 | };
17 |
18 | // Stop the Meshcommander server
19 | obj.Stop = function () {
20 | if (obj.webserver) {
21 | obj.webserver = null;
22 | }
23 | };
24 |
25 | return obj;
26 | }
27 |
28 | function launchBrowser() {
29 | var obj = {};
30 | obj.args = require("minimist")(process.argv.slice(2)); // slice off the first 2 default args
31 |
32 | const port = obj.args.port || 3000;
33 | let browser = "";
34 | switch (obj.args.browser) {
35 | case "chrome":
36 | browser = "chrome";
37 | break;
38 |
39 | case "edge":
40 | browser = "msedge";
41 | break;
42 |
43 | case "firefox":
44 | browser = "firefox";
45 | break;
46 |
47 | case "none":
48 | browser = null;
49 | break;
50 |
51 | default:
52 | browser = "chrome";
53 | break;
54 | }
55 | if (browser) {
56 | var child_process = require("child_process");
57 | child_process.exec(`start ${browser} http://localhost:${port}`);
58 | }
59 | }
60 |
61 | launchBrowser();
62 |
63 | CreateMeshCommanderServer().Start();
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "author": {
4 | "email": "ysainthilaire@hotmail.com",
5 | "name": "Ylian Saint-Hilaire"
6 | },
7 | "dependencies": {
8 | "express": "^4.18.2",
9 | "express-ws": "^2.0.0",
10 | "minimist": "^1.2.8"
11 | },
12 | "description": "MeshCommander web server",
13 | "files": [
14 | "*.js",
15 | "license.txt",
16 | "readme.txt",
17 | "public"
18 | ],
19 | "homepage": "http://meshcommander.com",
20 | "keywords": [
21 | "Intel Active Management Technology",
22 | "Remote Management",
23 | "Intel AMT",
24 | "Active Management",
25 | "Remote Desktop"
26 | ],
27 | "license": "Apache-2.0",
28 | "maintainers": [
29 | {
30 | "email": "brytonsalisbury@gmail.com",
31 | "name": "Bryton Salisbury"
32 | }
33 | ],
34 | "name": "mesh-mini",
35 | "readme": "readme.md",
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/BrytonSalisbury/mesh-mini"
39 | },
40 | "scripts": {
41 | "install-deps": "npm i",
42 | "make-dir": "mkdir build out",
43 | "bundle": "rollup --config .\\config\\rollup.config.mjs",
44 | "sea-blob": "node --experimental-sea-config .\\config\\sea-config.json",
45 | "copy-node": "node -e \"require('fs').copyFileSync(process.execPath, 'mesh-mini.exe')\" && move mesh-mini.exe .\\out ",
46 | "inject-node": "npx postject .\\out\\mesh-mini.exe NODE_SEA_BLOB .\\build\\sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite",
47 | "copy-public": "xcopy public .\\out\\public\\ /D /E",
48 | "success": "echo Build Successful!",
49 | "build": "npm run install-deps && npm run make-dir && npm run bundle && npm run sea-blob && npm run copy-node && npm run inject-node && npm run copy-public && npm run success",
50 | "clean": "rmdir build out /q /s"
51 | },
52 | "devDependencies": {
53 | "@rollup/plugin-commonjs": "^25.0.4",
54 | "@rollup/plugin-json": "^6.0.0",
55 | "@rollup/plugin-node-resolve": "^15.2.0",
56 | "postject": "^1.0.0-alpha.6",
57 | "rollup": "^3.28.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Downloads
2 |
3 | ### [Client Version](https://github.com/BrytonSalisbury/mesh-mini/releases/download/v1.0.1/mesh-mini.zip)
4 |
5 | For regular use, download the client version.
6 |
7 | ### [Server Version](https://github.com/BrytonSalisbury/mesh-mini/releases/download/v1.0.2/mesh-mini-server.zip)
8 |
9 | The server version is intended to be used in a distributed environment where there will be multiple clients accessing a single instance of Mesh-Mini. A computer list can be stored on the server, then accessed remotely from clients so only a single computer list needs to be maintained. Docker would be recommended for this, explained further below.
10 |
11 | # Mesh-Mini
12 |
13 | Mesh-Mini is a fork of the npm package MeshCommander, due to the ending of support of the MeshCommander application found here - https://www.meshcommander.com/meshcommander.
14 |
15 | The code has been modified to be bundled and injected into a copy of node.exe and is accessed via your localhost in a browser. Docker images are also provided below. This solution is **much** more performant than the original MeshCommander due to running in a modern browser.
16 |
17 | ## Run
18 |
19 | Refer to Downloads at the top, extract the contents, and run mesh-mini.exe
20 |
21 | ## Executable flags
22 |
23 | The .exe can be passed a --port and/or --browser flag to specify the port to run on, and the browser to autolaunch.
24 |
25 | The port can be any integer between 0 and 65,536.
26 |
27 | Browser options are:
28 |
29 | - chrome
30 | - edge
31 | - firefox
32 | - none
33 |
34 | ## Docker
35 |
36 | A Docker image can be pulled and then run with the following commands, where {architecture} is amd64 or arm64 depending on your platform:
37 |
38 | `docker pull brytonsalisbury/mesh-mini:{architecture}`
39 |
40 | `docker run -p 3000:3000 brytonsalisbury/mesh-mini:{architecture}`
41 |
42 | The Docker image can be built from source using:
43 |
44 | `docker build -t mesh-mini .`
45 |
46 | ### Note
47 |
48 | The Docker image is running the 'server' version of Mesh-Mini which can store a central computer list inside the container. The container can be passed an environment variable, _COMPUTER_PATH_, which controls where the computer list is stored, i.e `COMPUTER_PATH=/config/computers.json`
49 |
50 | This can be used in conjunction with a mounted volume so a computer list can be provided and maintained from local storage.
51 |
52 | If the environment variable isn't provided, when a computer list is pushed to the server it will be stored in _/usr/src/app/computers.json_
53 |
54 | https://hub.docker.com/r/brytonsalisbury/mesh-mini
55 |
56 | ## To build the .exe from source
57 |
58 | ### Prerequisites
59 |
60 | Node.js 19.7.0 or greater is required for the use of the --experimental-sea-config flag.
61 |
62 | [node-v20.5.1](https://nodejs.org/dist/v20.5.1/node-v20.5.1-x64.msi)
63 |
64 | Testing was done on Windows 11 using Node.js 20.5.1.
65 |
66 | ### Steps
67 |
68 | 1. Clone/download the repo and open a cmd prompt inside the root /mesh-mini directory
69 | 2. Execute `npm run build` to build the .exe.
70 | 3. The contents will be copied into the subfolder /out which will contain mesh-mini.exe and the /public folder required for Express.
71 |
72 | `npm run clean` can be used to clean the root directory.
73 |
--------------------------------------------------------------------------------
/common.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description MeshCentral Common Library
3 | * @author Ylian Saint-Hilaire
4 | * @copyright Intel Corporation 2018
5 | * @license Apache-2.0
6 | * @version v0.0.1
7 | */
8 |
9 | // Binary encoding and decoding functions
10 | module.exports.ReadShort = function(v, p) { return (v.charCodeAt(p) << 8) + v.charCodeAt(p + 1); }
11 | module.exports.ReadShortX = function(v, p) { return (v.charCodeAt(p + 1) << 8) + v.charCodeAt(p); }
12 | module.exports.ReadInt = function(v, p) { return (v.charCodeAt(p) * 0x1000000) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); } // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
13 | module.exports.ReadIntX = function(v, p) { return (v.charCodeAt(p + 3) * 0x1000000) + (v.charCodeAt(p + 2) << 16) + (v.charCodeAt(p + 1) << 8) + v.charCodeAt(p); }
14 | module.exports.ShortToStr = function(v) { return String.fromCharCode((v >> 8) & 0xFF, v & 0xFF); }
15 | module.exports.ShortToStrX = function(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF); }
16 | module.exports.IntToStr = function(v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); }
17 | module.exports.IntToStrX = function(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF); }
18 | module.exports.MakeToArray = function(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; }
19 | module.exports.SplitArray = function(v) { return v.split(','); }
20 | module.exports.Clone = function(v) { return JSON.parse(JSON.stringify(v)); }
21 |
22 | // Move an element from one position in an array to a new position
23 | module.exports.ArrayElementMove = function(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); };
24 |
25 | // Print object for HTML
26 | module.exports.ObjectToStringEx = function(x, c) {
27 | var r = "";
28 | if (x != 0 && (!x || x == null)) return "(Null)";
29 | if (x instanceof Array) { for (var i in x) { r += '
' + gap(c) + "Item #" + i + ": " + module.exports.ObjectToStringEx(x[i], c + 1); } }
30 | else if (x instanceof Object) { for (var i in x) { r += '
' + gap(c) + i + " = " + module.exports.ObjectToStringEx(x[i], c + 1); } }
31 | else { r += x; }
32 | return r;
33 | }
34 |
35 | // Print object for console
36 | module.exports.ObjectToStringEx2 = function(x, c) {
37 | var r = "";
38 | if (x != 0 && (!x || x == null)) return "(Null)";
39 | if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + "Item #" + i + ": " + module.exports.ObjectToStringEx2(x[i], c + 1); } }
40 | else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + " = " + module.exports.ObjectToStringEx2(x[i], c + 1); } }
41 | else { r += x; }
42 | return r;
43 | }
44 |
45 | // Create an ident gap
46 | module.exports.gap = function(c) { var x = ''; for (var i = 0; i < (c * 4) ; i++) { x += ' '; } return x; }
47 | module.exports.gap2 = function(c) { var x = ''; for (var i = 0; i < (c * 4) ; i++) { x += ' '; } return x; }
48 |
49 | // Print an object in html
50 | module.exports.ObjectToString = function(x) { return module.exports.ObjectToStringEx(x, 0); }
51 | module.exports.ObjectToString2 = function(x) { return module.exports.ObjectToStringEx2(x, 0); }
52 |
53 | // Convert a hex string to a raw string
54 | module.exports.hex2rstr = function(d) {
55 | var r = '', m = ('' + d).match(/../g), t;
56 | while (t = m.shift()) r += String.fromCharCode('0x' + t);
57 | return r
58 | }
59 |
60 | // Convert decimal to hex
61 | module.exports.char2hex = function(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
62 |
63 | // Convert a raw string to a hex string
64 | module.exports.rstr2hex = function(input) {
65 | var r = '', i;
66 | for (i = 0; i < input.length; i++) { r += module.exports.char2hex(input.charCodeAt(i)); }
67 | return r;
68 | }
69 |
70 | // UTF-8 encoding & decoding functions
71 | module.exports.encode_utf8 = function(s) { return unescape(encodeURIComponent(s)); }
72 | module.exports.decode_utf8 = function(s) { return decodeURIComponent(escape(s)); }
73 |
74 | // Convert a string into a blob
75 | module.exports.data2blob = function(data) {
76 | var bytes = new Array(data.length);
77 | for (var i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i);
78 | var blob = new Blob([new Uint8Array(bytes)]);
79 | return blob;
80 | }
81 |
82 | // Generate random numbers
83 | module.exports.random = function(max) { return Math.floor(Math.random() * max); }
--------------------------------------------------------------------------------
/webserver.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description MeshCommander web server
3 | * @author Ylian Saint-Hilaire
4 | * @copyright Intel Corporation 2018-2020
5 | * @license Apache-2.0
6 | * @version v0.0.3
7 | */
8 |
9 | module.exports.CreateWebServer = function (args) {
10 | var obj = {};
11 |
12 | obj.fs = require("fs");
13 | obj.net = require("net");
14 | obj.tls = require("tls");
15 | obj.path = require("path");
16 | obj.args = args;
17 | obj.express = require("express");
18 | obj.app = obj.express();
19 | obj.expressWs = require("express-ws")(obj.app);
20 | obj.interceptor = require("./interceptor");
21 | obj.common = require("./common.js");
22 | obj.constants = require("constants");
23 | obj.computerlist = null;
24 | obj.apiRouter = require("./api");
25 |
26 | obj.debug = function (msg) {
27 | if (args.debug) {
28 | console.log(msg);
29 | }
30 | };
31 |
32 | obj.getAmtPassword = function (host) {
33 | if (!obj.computerlist || obj.computerlist == null) return null;
34 | for (var i in obj.computerlist) {
35 | if (obj.computerlist[i].host == host) {
36 | return [obj.computerlist[i].user, obj.computerlist[i].pass];
37 | }
38 | }
39 | return null;
40 | };
41 |
42 | // Indicates to ExpressJS that the public folder should be used to serve static files. Mesh Commander will be at "default.htm".
43 | obj.app.use(obj.express.static(obj.path.join(__dirname, "public")));
44 |
45 | // API router to push and pull computers
46 | obj.app.use("/api", obj.apiRouter);
47 |
48 | // Indicates that any request to "/" should be redirected to "/default.htm" which is the Mesh Commander web application.
49 | obj.app.get("/", function (req, res) {
50 | // Select the best supported language for this browser
51 | var language = null,
52 | availableLanguages = [
53 | "en",
54 | "de",
55 | "es",
56 | "fr",
57 | "it",
58 | "ja",
59 | "ko",
60 | "nl",
61 | "pt",
62 | "ru",
63 | "zh",
64 | ];
65 | if (req.headers["accept-language"] != null) {
66 | var acceptLanguageSplit = req.headers["accept-language"].split(";");
67 | for (var i in acceptLanguageSplit) {
68 | var acceptLanguageSplitEx = acceptLanguageSplit[i].split(",");
69 | for (var j in acceptLanguageSplitEx) {
70 | if (acceptLanguageSplitEx[j].startsWith("q=") == false) {
71 | var l = acceptLanguageSplitEx[j].toLowerCase().split("-")[0];
72 | if (language == null && availableLanguages.indexOf(l) >= 0) {
73 | language = l;
74 | }
75 | }
76 | }
77 | }
78 | }
79 | if (language == null || language == "en") {
80 | language = "";
81 | } else {
82 | if (language == "zh") {
83 | language = "zh-chz";
84 | }
85 | language = "-" + language;
86 | }
87 | res.set({
88 | "Cache-Control": "no-cache, no-store, must-revalidate",
89 | Pragma: "no-cache",
90 | Expires: "0",
91 | });
92 | res.redirect("/default" + language + ".htm");
93 | });
94 |
95 | // For the server version of Mesh Commander, we send the computer list without credential and insertion credentials in the stream.
96 | obj.app.get("/webrelay.ashx", function (req, res) {
97 | res.set({
98 | "Cache-Control": "no-cache, no-store, must-revalidate",
99 | Pragma: "no-cache",
100 | Expires: "0",
101 | });
102 | if (req.query.action == "getcomputerlist") {
103 | obj.fs.readFile("computerlist.config", "utf8", function (err, data) {
104 | if (err == null) {
105 | var list = JSON.parse(data);
106 | obj.computerlist = obj.common.Clone(list);
107 | for (var i in list) {
108 | delete list[i].pass;
109 | } // Remove all passwords.
110 | res.set({ "Content-Type": "application/json" });
111 | res.send(JSON.stringify(list));
112 | }
113 | });
114 | }
115 | try {
116 | res.close();
117 | } catch (e) {}
118 | });
119 |
120 | // Indicates to ExpressJS what we want to handle websocket requests on "/webrelay.ashx". This is the same URL as IIS making things simple, we can use the same web application for both IIS and Node.
121 | obj.app.ws("/webrelay.ashx", function (ws, req) {
122 | ws.pause();
123 |
124 | // When data is received from the web socket, forward the data into the associated TCP connection.
125 | // If the TCP connection is pending, buffer up the data until it connects.
126 | ws.on("message", function (msg) {
127 | // Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
128 | var msg2 = "";
129 | for (var i = 0; i < msg.length; i++) {
130 | msg2 += String.fromCharCode(msg[i]);
131 | }
132 | msg = msg2;
133 |
134 | if (ws.interceptor) {
135 | msg = ws.interceptor.processBrowserData(msg);
136 | } // Run data thru interceptor
137 | ws.forwardclient.write(Buffer.from(msg, "ascii")); // Forward data to the associated TCP connection.
138 | });
139 |
140 | // If the web socket is closed, close the associated TCP connection.
141 | ws.on("close", function (req) {
142 | obj.debug(
143 | "Closing web socket connection to " +
144 | ws.upgradeReq.query.host +
145 | ":" +
146 | ws.upgradeReq.query.port +
147 | "."
148 | );
149 | if (ws.forwardclient) {
150 | try {
151 | ws.forwardclient.destroy();
152 | } catch (e) {}
153 | }
154 | });
155 |
156 | // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
157 | obj.debug(
158 | "Opening web socket connection to " +
159 | req.query.host +
160 | ":" +
161 | req.query.port +
162 | "."
163 | );
164 | if (req.query.tls == 0) {
165 | // If this is TCP (without TLS) set a normal TCP socket
166 | ws.forwardclient = new obj.net.Socket();
167 | ws.forwardclient.setEncoding("binary");
168 | ws.forwardclient.forwardwsocket = ws;
169 | } else {
170 | // If TLS is going to be used, setup a TLS socket
171 | var tlsoptions = {
172 | secureProtocol:
173 | req.query.tls1only == 1 ? "TLSv1_method" : "SSLv23_method",
174 | ciphers: "RSA+AES:!aNULL:!MD5:!DSS",
175 | secureOptions:
176 | obj.constants.SSL_OP_NO_SSLv2 |
177 | obj.constants.SSL_OP_NO_SSLv3 |
178 | obj.constants.SSL_OP_NO_COMPRESSION |
179 | obj.constants.SSL_OP_CIPHER_SERVER_PREFERENCE,
180 | rejectUnauthorized: false,
181 | };
182 | ws.forwardclient = obj.tls.connect(
183 | req.query.port,
184 | req.query.host,
185 | tlsoptions,
186 | function () {
187 | // The TLS connection method is the same as TCP, but located a bit differently.
188 | obj.debug(
189 | "TLS connected to " + req.query.host + ":" + req.query.port + "."
190 | );
191 | ws.resume();
192 | }
193 | );
194 | ws.forwardclient.setEncoding("binary");
195 | ws.forwardclient.forwardwsocket = ws;
196 | }
197 |
198 | // When we receive data on the TCP connection, forward it back into the web socket connection.
199 | ws.forwardclient.on("data", function (data) {
200 | if (ws.interceptor) {
201 | data = ws.interceptor.processAmtData(data);
202 | } // Run data thru interceptor
203 | try {
204 | ws.send(Buffer.from(data, "ascii"));
205 | } catch (ex) {}
206 | });
207 |
208 | // If the TCP connection closes, disconnect the associated web socket.
209 | ws.forwardclient.on("close", function () {
210 | obj.debug(
211 | "TCP disconnected from " + req.query.host + ":" + req.query.port + "."
212 | );
213 | try {
214 | ws.close();
215 | } catch (ex) {}
216 | });
217 |
218 | // If the TCP connection causes an error, disconnect the associated web socket.
219 | ws.forwardclient.on("error", function (err) {
220 | obj.debug(
221 | "TCP disconnected with error from " +
222 | req.query.host +
223 | ":" +
224 | req.query.port +
225 | ": " +
226 | err.code +
227 | ", " +
228 | req.url
229 | );
230 | try {
231 | ws.close();
232 | } catch (ex) {}
233 | });
234 |
235 | // Fetch Intel AMT credentials & Setup interceptor
236 | var credentials = obj.getAmtPassword(req.query.host);
237 | if (credentials != null) {
238 | if (req.query.p == 1) {
239 | ws.interceptor = obj.interceptor.CreateHttpInterceptor({
240 | host: req.query.host,
241 | port: req.query.port,
242 | user: credentials[0],
243 | pass: credentials[1],
244 | });
245 | } else if (req.query.p == 2) {
246 | ws.interceptor = obj.interceptor.CreateRedirInterceptor({
247 | user: credentials[0],
248 | pass: credentials[1],
249 | });
250 | }
251 | }
252 |
253 | if (req.query.tls == 0) {
254 | // A TCP connection to Intel AMT just connected, send any pending data and start forwarding.
255 | ws.forwardclient.connect(req.query.port, req.query.host, function () {
256 | obj.debug(
257 | "TCP connected to " + req.query.host + ":" + req.query.port + "."
258 | );
259 | ws.resume();
260 | });
261 | }
262 | });
263 |
264 | // Start the ExpressJS web server
265 | var port = 3000;
266 | if (args.port != null) {
267 | port = parseInt(args.port);
268 | }
269 | if (
270 | isNaN(port) ||
271 | port == null ||
272 | typeof port != "number" ||
273 | port < 0 ||
274 | port > 65536
275 | ) {
276 | port = 3000;
277 | }
278 | if (args.any != null) {
279 | obj.app.listen(port, function () {
280 | console.log("MeshCommander running on http://*:" + port + ".");
281 | });
282 | } else {
283 | obj.app.listen(port, "0.0.0.0", function () {
284 | console.log("MeshCommander running on http://localhost:" + port + ".");
285 | });
286 | }
287 |
288 | return obj;
289 | };
290 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2017 Intel Corporation
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/interceptor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description Intel AMT Interceptor
3 | * @author Ylian Saint-Hilaire
4 | * @copyright Intel Corporation 2018
5 | * @license Apache-2.0
6 | * @version v0.0.3
7 | */
8 |
9 | var crypto = require('crypto');
10 | var common = require('./common.js');
11 |
12 | var HttpInterceptorAuthentications = {};
13 | var RedirInterceptorAuthentications = {};
14 |
15 | // Construct a HTTP interceptor object
16 | module.exports.CreateHttpInterceptor = function (args) {
17 | var obj = {};
18 |
19 | // Create a random hex string of a given length
20 | obj.randomValueHex = function (len) { return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len); }
21 |
22 | obj.args = args;
23 | obj.amt = { acc: '', mode: 0, count: 0, error: false }; // mode: 0:Header, 1:LengthBody, 2:ChunkedBody, 3:UntilClose
24 | obj.ws = { acc: '', mode: 0, count: 0, error: false, authCNonce: obj.randomValueHex(10), authCNonceCount: 1 };
25 |
26 | // Private method
27 | obj.Debug = function (msg) { console.log(msg); }
28 |
29 | // Process data coming from Intel AMT
30 | obj.processAmtData = function (data) {
31 | obj.amt.acc += data; // Add data to accumulator
32 | data = '';
33 | var datalen = 0;
34 | do {
35 | datalen = data.length;
36 | data += obj.processAmtDataEx();
37 | } while (datalen != data.length); // Process as much data as possible
38 | return data;
39 | }
40 |
41 | // Process data coming from AMT in the accumulator
42 | obj.processAmtDataEx = function () {
43 | if (obj.amt.mode == 0) { // Header Mode
44 | // Decode the HTTP header
45 | var headerend = obj.amt.acc.indexOf('\r\n\r\n');
46 | if (headerend < 0) return '';
47 | var headerlines = obj.amt.acc.substring(0, headerend).split('\r\n');
48 | obj.amt.acc = obj.amt.acc.substring(headerend + 4);
49 | obj.amt.directive = headerlines[0].split(' ');
50 | var headers = headerlines.slice(1);
51 | obj.amt.headers = {};
52 | obj.amt.mode = 3; // UntilClose
53 | for (var i in headers) {
54 | var j = headers[i].indexOf(':');
55 | if (j > 0) {
56 | var v1 = headers[i].substring(0, j).trim().toLowerCase();
57 | var v2 = headers[i].substring(j + 1).trim();
58 | obj.amt.headers[v1] = v2;
59 | if (v1.toLowerCase() == 'www-authenticate') {
60 | HttpInterceptorAuthentications[obj.args.host + ':' + obj.args.port] = v2;
61 | } else if (v1.toLowerCase() == 'content-length') {
62 | obj.amt.count = parseInt(v2);
63 | if (obj.amt.count > 0) {
64 | obj.amt.mode = 1; // LengthBody
65 | } else {
66 | obj.amt.mode = 0; // Header
67 | }
68 | } else if (v1.toLowerCase() == 'transfer-encoding' && v2.toLowerCase() == 'chunked') {
69 | obj.amt.mode = 2; // ChunkedBody
70 | }
71 | }
72 | }
73 |
74 | // Reform the HTTP header
75 | var r = obj.amt.directive.join(' ') + '\r\n';
76 | for (var i in obj.amt.headers) { r += (i + ': ' + obj.amt.headers[i] + '\r\n'); }
77 | r += '\r\n';
78 | return r;
79 | } else if (obj.amt.mode == 1) { // Length Body Mode
80 | // Send the body of content-length size
81 | var rl = obj.amt.count;
82 | if (rl < obj.amt.acc.length) rl = obj.amt.acc.length;
83 | var r = obj.amt.acc.substring(0, rl);
84 | obj.amt.acc = obj.amt.acc.substring(rl);
85 | obj.amt.count -= rl;
86 | if (obj.amt.count == 0) { obj.amt.mode = 0; }
87 | return r;
88 | } else if (obj.amt.mode == 2) { // Chunked Body Mode
89 | // Send data one chunk at a time
90 | var headerend = obj.amt.acc.indexOf('\r\n');
91 | if (headerend < 0) return '';
92 | var chunksize = parseInt(obj.amt.acc.substring(0, headerend), 16);
93 | if (chunksize == 0 && obj.amt.acc.length >= headerend + 4) {
94 | // Send the ending chunk (NOTE: We do not support trailing headers)
95 | var r = obj.amt.acc.substring(0, headerend + 4);
96 | obj.amt.acc = obj.amt.acc.substring(headerend + 4);
97 | obj.amt.mode = 0;
98 | return r;
99 | } else if (chunksize > 0 && obj.amt.acc.length >= headerend + 4) {
100 | // Send a chunk
101 | var r = obj.amt.acc.substring(0, headerend + chunksize + 4);
102 | obj.amt.acc = obj.amt.acc.substring(headerend + chunksize + 4);
103 | return r;
104 | }
105 | } else if (obj.amt.mode == 3) { // Until Close Mode
106 | var r = obj.amt.acc;
107 | obj.amt.acc = '';
108 | return r;
109 | }
110 | return '';
111 | }
112 |
113 | // Process data coming from the Browser
114 | obj.processBrowserData = function (data) {
115 | obj.ws.acc += data; // Add data to accumulator
116 | data = '';
117 | var datalen = 0;
118 | do {
119 | datalen = data.length;
120 | data += obj.processBrowserDataEx();
121 | } while (datalen != data.length); // Process as much data as possible
122 | return data;
123 | }
124 |
125 | // Process data coming from the Browser in the accumulator
126 | obj.processBrowserDataEx = function () {
127 | if (obj.ws.mode == 0) { // Header Mode
128 | // Decode the HTTP header
129 | var headerend = obj.ws.acc.indexOf('\r\n\r\n');
130 | if (headerend < 0) return '';
131 | var headerlines = obj.ws.acc.substring(0, headerend).split('\r\n');
132 | obj.ws.acc = obj.ws.acc.substring(headerend + 4);
133 | obj.ws.directive = headerlines[0].split(' ');
134 | var headers = headerlines.slice(1);
135 | obj.ws.headers = {};
136 | obj.ws.mode = 3; // UntilClose
137 | for (var i in headers) {
138 | var j = headers[i].indexOf(':');
139 | if (j > 0) {
140 | var v1 = headers[i].substring(0, j).trim().toLowerCase();
141 | var v2 = headers[i].substring(j + 1).trim();
142 | obj.ws.headers[v1] = v2;
143 | if (v1.toLowerCase() == 'www-authenticate') {
144 | HttpInterceptorAuthentications[obj.args.host + ':' + obj.args.port] = v2;
145 | } else if (v1.toLowerCase() == 'content-length') {
146 | obj.ws.count = parseInt(v2);
147 | if (obj.ws.count > 0) {
148 | obj.ws.mode = 1; // LengthBody
149 | } else {
150 | obj.ws.mode = 0; // Header
151 | }
152 | } else if (v1.toLowerCase() == 'transfer-encoding' && v2.toLowerCase() == 'chunked') {
153 | obj.ws.mode = 2; // ChunkedBody
154 | }
155 | }
156 | }
157 |
158 | // Insert authentication
159 | if (obj.args.user && obj.args.pass && HttpInterceptorAuthentications[obj.args.host + ':' + obj.args.port]) {
160 | // We have authentication data, lets use it.
161 | var AuthArgs = obj.GetAuthArgs(HttpInterceptorAuthentications[obj.args.host + ':' + obj.args.port]);
162 | var hash = obj.ComputeDigesthash(obj.args.user, obj.args.pass, AuthArgs.realm, obj.ws.directive[0], obj.ws.directive[1], AuthArgs.qop, AuthArgs.nonce, obj.ws.authCNonceCount, obj.ws.authCNonce);
163 | var authstr = 'Digest username="' + obj.args.user + '",realm="' + AuthArgs.realm + '",nonce="' + AuthArgs.nonce + '",uri="' + obj.ws.directive[1] + '",qop=' + AuthArgs.qop + ',nc=' + obj.ws.authCNonceCount + ',cnonce="' + obj.ws.authCNonce + '",response="' + hash + '"';
164 | if (AuthArgs.opaque) { authstr += ',opaque="' + AuthArgs.opaque + '"'}
165 | obj.ws.headers.authorization = authstr;
166 | obj.ws.authCNonceCount++;
167 | } else {
168 | // We don't have authentication, clear it out of the header if needed.
169 | if (obj.ws.headers.authorization) { delete obj.ws.headers.authorization; }
170 | }
171 |
172 | // Reform the HTTP header
173 | var r = obj.ws.directive.join(' ') + '\r\n';
174 | for (var i in obj.ws.headers) { r += (i + ': ' + obj.ws.headers[i] + '\r\n'); }
175 | r += '\r\n';
176 | return r;
177 | } else if (obj.ws.mode == 1) { // Length Body Mode
178 | // Send the body of content-length size
179 | var rl = obj.ws.count;
180 | if (rl < obj.ws.acc.length) rl = obj.ws.acc.length;
181 | var r = obj.ws.acc.substring(0, rl);
182 | obj.ws.acc = obj.ws.acc.substring(rl);
183 | obj.ws.count -= rl;
184 | if (obj.ws.count == 0) { obj.ws.mode = 0; }
185 | return r;
186 | } else if (obj.ws.mode == 2) { // Chunked Body Mode
187 | // Send data one chunk at a time
188 | var headerend = obj.ws.acc.indexOf('\r\n');
189 | if (headerend < 0) return '';
190 | var chunksize = parseInt(obj.ws.acc.substring(0, headerend), 16);
191 | if ((chunksize == 0) && (obj.ws.acc.length >= headerend + 4)) {
192 | // Send the ending chunk (NOTE: We do not support trailing headers)
193 | var r = obj.ws.acc.substring(0, headerend + 4);
194 | obj.ws.acc = obj.ws.acc.substring(headerend + 4);
195 | obj.ws.mode = 0;
196 | return r;
197 | } else if ((chunksize > 0) && (obj.ws.acc.length >= (headerend + 4 + chunksize))) {
198 | // Send a chunk
199 | var r = obj.ws.acc.substring(0, headerend + chunksize + 4);
200 | obj.ws.acc = obj.ws.acc.substring(headerend + chunksize + 4);
201 | return r;
202 | }
203 | } else if (obj.ws.mode == 3) { // Until Close Mode
204 | var r = obj.ws.acc;
205 | obj.ws.acc = '';
206 | return r;
207 | }
208 | return '';
209 | }
210 |
211 | // Parse authentication values from the HTTP header
212 | obj.GetAuthArgs = function (authheader) {
213 | var authargs = {};
214 | var authargsstr = authheader.substring(7).split(',');
215 | for (var j in authargsstr) {
216 | var argstr = authargsstr[j];
217 | var i = argstr.indexOf('=');
218 | var k = argstr.substring(0, i).trim().toLowerCase();
219 | var v = argstr.substring(i + 1).trim();
220 | if (v.startsWith('\"')) { v = v.substring(1, v.length - 1); }
221 | if (i > 0) authargs[k] = v;
222 | }
223 | return authargs;
224 | }
225 |
226 | // Compute the MD5 digest hash for a set of values
227 | obj.ComputeDigesthash = function (username, password, realm, method, path, qop, nonce, nc, cnonce) {
228 | var ha1 = crypto.createHash('md5').update(username + ':' + realm + ':' + password).digest('hex');
229 | var ha2 = crypto.createHash('md5').update(method + ':' + path).digest('hex');
230 | return crypto.createHash('md5').update(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).digest('hex');
231 | }
232 |
233 | return obj;
234 | }
235 |
236 |
237 | // Construct a redirection interceptor object
238 | module.exports.CreateRedirInterceptor = function (args) {
239 | var obj = {};
240 |
241 | // Create a random hex string of a given length
242 | obj.randomValueHex = function (len) { return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').slice(0, len); }
243 |
244 | obj.args = args;
245 | obj.amt = { acc: '', mode: 0, count: 0, error: false, direct: false};
246 | obj.ws = { acc: '', mode: 0, count: 0, error: false, direct: false, authCNonce: obj.randomValueHex(10), authCNonceCount: 1 };
247 |
248 | obj.RedirectCommands = { StartRedirectionSession: 0x10, StartRedirectionSessionReply: 0x11, EndRedirectionSession: 0x12, AuthenticateSession: 0x13, AuthenticateSessionReply: 0x14 };
249 | obj.StartRedirectionSessionReplyStatus = { SUCCESS: 0, TYPE_UNKNOWN: 1, BUSY: 2, UNSUPPORTED: 3, ERROR: 0xFF };
250 | obj.AuthenticationStatus = { SUCCESS: 0, FALIURE: 1, NOTSUPPORTED: 2 };
251 | obj.AuthenticationType = { QUERY: 0, USERPASS: 1, KERBEROS: 2, BADDIGEST: 3, DIGEST: 4 };
252 |
253 | // Private method
254 | obj.Debug = function (msg) { console.log(msg); }
255 |
256 | // Process data coming from Intel AMT
257 | obj.processAmtData = function (data) {
258 | obj.amt.acc += data; // Add data to accumulator
259 | data = '';
260 | var datalen = 0;
261 | do { datalen = data.length; data += obj.processAmtDataEx(); } while (datalen != data.length); // Process as much data as possible
262 | return data;
263 | }
264 |
265 | // Process data coming from AMT in the accumulator
266 | obj.processAmtDataEx = function () {
267 | if (obj.amt.acc.length == 0) return '';
268 | if (obj.amt.direct == true) {
269 | var data = obj.amt.acc;
270 | obj.amt.acc = '';
271 | return data;
272 | } else {
273 | //console.log(obj.amt.acc.charCodeAt(0));
274 | switch (obj.amt.acc.charCodeAt(0)) {
275 | case obj.RedirectCommands.StartRedirectionSessionReply: {
276 | if (obj.amt.acc.length < 4) return '';
277 | if (obj.amt.acc.charCodeAt(1) == obj.StartRedirectionSessionReplyStatus.SUCCESS) {
278 | if (obj.amt.acc.length < 13) return '';
279 | var oemlen = obj.amt.acc.charCodeAt(12);
280 | if (obj.amt.acc.length < 13 + oemlen) return '';
281 | var r = obj.amt.acc.substring(0, 13 + oemlen);
282 | obj.amt.acc = obj.amt.acc.substring(13 + oemlen);
283 | return r;
284 | }
285 | break;
286 | }
287 | case obj.RedirectCommands.AuthenticateSessionReply: {
288 | if (obj.amt.acc.length < 9) return '';
289 | var l = common.ReadIntX(obj.amt.acc, 5);
290 | if (obj.amt.acc.length < 9 + l) return '';
291 | var authstatus = obj.amt.acc.charCodeAt(1);
292 | var authType = obj.amt.acc.charCodeAt(4);
293 |
294 | if (authType == obj.AuthenticationType.DIGEST && authstatus == obj.AuthenticationStatus.FALIURE) {
295 | // Grab and keep all authentication parameters
296 | var realmlen = obj.amt.acc.charCodeAt(9);
297 | obj.amt.digestRealm = obj.amt.acc.substring(10, 10 + realmlen);
298 | var noncelen = obj.amt.acc.charCodeAt(10 + realmlen);
299 | obj.amt.digestNonce = obj.amt.acc.substring(11 + realmlen, 11 + realmlen + noncelen);
300 | var qoplen = obj.amt.acc.charCodeAt(11 + realmlen + noncelen);
301 | obj.amt.digestQOP = obj.amt.acc.substring(12 + realmlen + noncelen, 12 + realmlen + noncelen + qoplen);
302 | }
303 | else if (authType != obj.AuthenticationType.QUERY && authstatus == obj.AuthenticationStatus.SUCCESS) {
304 | // Intel AMT relayed that authentication was succesful, go to direct relay mode in both directions.
305 | obj.ws.direct = true;
306 | obj.amt.direct = true;
307 | }
308 |
309 | var r = obj.amt.acc.substring(0, 9 + l);
310 | obj.amt.acc = obj.amt.acc.substring(9 + l);
311 | return r;
312 | }
313 | default: {
314 | obj.amt.error = true;
315 | return '';
316 | }
317 | }
318 | }
319 | return '';
320 | }
321 |
322 | // Process data coming from the Browser
323 | obj.processBrowserData = function (data) {
324 | obj.ws.acc += data; // Add data to accumulator
325 | data = '';
326 | var datalen = 0;
327 | do { datalen = data.length; data += obj.processBrowserDataEx(); } while (datalen != data.length); // Process as much data as possible
328 | return data;
329 | }
330 |
331 | // Process data coming from the Browser in the accumulator
332 | obj.processBrowserDataEx = function () {
333 | if (obj.ws.acc.length == 0) return '';
334 | if (obj.ws.direct == true) {
335 | var data = obj.ws.acc;
336 | obj.ws.acc = '';
337 | return data;
338 | } else {
339 | switch (obj.ws.acc.charCodeAt(0)) {
340 | case obj.RedirectCommands.StartRedirectionSession: {
341 | if (obj.ws.acc.length < 8) return '';
342 | var r = obj.ws.acc.substring(0, 8);
343 | obj.ws.acc = obj.ws.acc.substring(8);
344 | return r;
345 | }
346 | case obj.RedirectCommands.EndRedirectionSession: {
347 | if (obj.ws.acc.length < 4) return '';
348 | var r = obj.ws.acc.substring(0, 4);
349 | obj.ws.acc = obj.ws.acc.substring(4);
350 | return r;
351 | }
352 | case obj.RedirectCommands.AuthenticateSession: {
353 | if (obj.ws.acc.length < 9) return '';
354 | var l = common.ReadIntX(obj.ws.acc, 5);
355 | if (obj.ws.acc.length < 9 + l) return '';
356 |
357 | var authType = obj.ws.acc.charCodeAt(4);
358 | if (authType == obj.AuthenticationType.DIGEST && obj.args.user && obj.args.pass) {
359 | var authurl = '/RedirectionService';
360 | if (obj.amt.digestRealm) {
361 | // Replace this authentication digest with a server created one
362 | // We have everything we need to authenticate
363 | var nc = obj.ws.authCNonceCount;
364 | obj.ws.authCNonceCount++;
365 | var digest = obj.ComputeDigesthash(obj.args.user, obj.args.pass, obj.amt.digestRealm, 'POST', authurl, obj.amt.digestQOP, obj.amt.digestNonce, nc, obj.ws.authCNonce);
366 |
367 | // Replace this authentication digest with a server created one
368 | // We have everything we need to authenticate
369 | var r = String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04);
370 | r += common.IntToStrX(obj.args.user.length + obj.amt.digestRealm.length + obj.amt.digestNonce.length + authurl.length + obj.ws.authCNonce.length + nc.toString().length + digest.length + obj.amt.digestQOP.length + 8);
371 | r += String.fromCharCode(obj.args.user.length); // Username Length
372 | r += obj.args.user; // Username
373 | r += String.fromCharCode(obj.amt.digestRealm.length); // Realm Length
374 | r += obj.amt.digestRealm; // Realm
375 | r += String.fromCharCode(obj.amt.digestNonce.length); // Nonce Length
376 | r += obj.amt.digestNonce; // Nonce
377 | r += String.fromCharCode(authurl.length); // Authentication URL "/RedirectionService" Length
378 | r += authurl; // Authentication URL
379 | r += String.fromCharCode(obj.ws.authCNonce.length); // CNonce Length
380 | r += obj.ws.authCNonce; // CNonce
381 | r += String.fromCharCode(nc.toString().length); // NonceCount Length
382 | r += nc.toString(); // NonceCount
383 | r += String.fromCharCode(digest.length); // Response Length
384 | r += digest; // Response
385 | r += String.fromCharCode(obj.amt.digestQOP.length); // QOP Length
386 | r += obj.amt.digestQOP; // QOP
387 |
388 | obj.ws.acc = obj.ws.acc.substring(9 + l); // Don't relay the original message
389 | return r;
390 | } else {
391 | // Replace this authentication digest with a server created one
392 | // Since we don't have authentication parameters, fill them in with blanks to get an error back what that info.
393 | var r = String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04);
394 | r += common.IntToStrX(obj.args.user.length + authurl.length + 8);
395 | r += String.fromCharCode(obj.args.user.length);
396 | r += obj.args.user;
397 | r += String.fromCharCode(0x00, 0x00, authurl.length);
398 | r += authurl;
399 | r += String.fromCharCode(0x00, 0x00, 0x00, 0x00);
400 | obj.ws.acc = obj.ws.acc.substring(9 + l); // Don't relay the original message
401 | return r;
402 | }
403 | }
404 |
405 | var r = obj.ws.acc.substring(0, 9 + l);
406 | obj.ws.acc = obj.ws.acc.substring(9 + l);
407 | return r;
408 | }
409 | default: {
410 | obj.ws.error = true;
411 | return '';
412 | }
413 | }
414 | }
415 | return '';
416 | }
417 |
418 | // Compute the MD5 digest hash for a set of values
419 | obj.ComputeDigesthash = function (username, password, realm, method, path, qop, nonce, nc, cnonce) {
420 | var ha1 = crypto.createHash('md5').update(username + ':' + realm + ':' + password).digest('hex');
421 | var ha2 = crypto.createHash('md5').update(method + ':' + path).digest('hex');
422 | return crypto.createHash('md5').update(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).digest('hex');
423 | }
424 |
425 | return obj;
426 | }
--------------------------------------------------------------------------------