diff --git a/CoreModule.js b/CoreModule.js index af28464d..18b46e00 100644 --- a/CoreModule.js +++ b/CoreModule.js @@ -719,6 +719,50 @@ var http = require('http'); var net = require('net'); var fs = require('fs'); var rtc = require('ILibWebRTC'); + +// OpenFrame: Read machine ID from shared location +var openframeMachineId = null; +function getOpenFrameMachineId() { + if (openframeMachineId != null) return openframeMachineId; + try { + var machineIdPath = (process.platform == 'win32') + ? (process.env['ProgramData'] + '\\OpenFrame\\machine_id') + : ((process.platform == 'darwin') + ? '/Library/Application Support/OpenFrame/machine_id' + : '/var/lib/openframe/machine_id'); + openframeMachineId = fs.readFileSync(machineIdPath).toString().trim(); + } catch (ex) { openframeMachineId = null; } + return openframeMachineId; +} + +// OpenFrame: Add x-machine-id and Authorization headers to request options (only in openFrameMode) +function addOpenFrameHeaders(options) { + // Only add headers if running in OpenFrame mode + if (!mesh.openFrameMode) return options; + + if (!options.headers) options.headers = {}; + + // Native http adds Host only when no headers object exists; we made one, so set it. + if (options.host && !options.headers['Host']) { + var ofIsTLS = (options.protocol == 'wss:' || options.protocol == 'https:'); + var ofPort = '' + options.port; + options.headers['Host'] = ((ofPort == '443' && ofIsTLS) || (ofPort == '80' && !ofIsTLS)) ? options.host : (options.host + ':' + options.port); + } + + // Add x-machine-id header + var machineId = getOpenFrameMachineId(); + if (machineId) { + options.headers['x-machine-id'] = machineId; + } + + // Add Authorization header with JWT token + var token = mesh.authToken(); + if (token) { + options.headers['Authorization'] = 'Bearer ' + token; + } + + return options; +} var amt = null; var processManager = require('process-manager'); var wifiScannerLib = null; @@ -1002,10 +1046,12 @@ function getServerTargetUrl(path) { if (x == null) { return null; } var url = x.protocol + '//' + x.host + '/ws/tools/agent/meshcentral-server/' + path; - // Inject Openframe JWT token + // Inject Openframe JWT token, only when one is actually available var token = mesh.authToken(); - var separator = path.indexOf('?') !== -1 ? '&' : '?'; - url += separator + 'authorization=' + token; + if (token) { + var separator = path.indexOf('?') !== -1 ? '&' : '?'; + url += separator + 'authorization=' + encodeURIComponent(token); + } return url; } @@ -1169,6 +1215,7 @@ function handleServerCommand(data) { //sendConsoleText(JSON.stringify(woptions)); //sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2)); + addOpenFrameHeaders(woptions); // Add X-MACHINE-ID and Authorization headers var tunnel = http.request(woptions); tunnel.upgrade = onTunnelUpgrade; tunnel.on('error', tunnel_onError); @@ -1812,6 +1859,7 @@ function downloadFile(downloadoptions) { if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } } //options.checkServerIdentity.servertlshash = downloadoptions.serverhash; + addOpenFrameHeaders(options); // Add X-MACHINE-ID header trustedDownloads[downloadoptions.name] = downloadoptions; trustedDownloads[downloadoptions.name].dl = require('https').get(options); trustedDownloads[downloadoptions.name].dl.on('error', function (e) { downloadoptions.func(downloadoptions, false); delete trustedDownloads[downloadoptions.name]; }); @@ -1864,6 +1912,7 @@ function serverFetchFile() { agentFileHttpOptions.checkServerIdentity.servertlshash = data.servertlshash; if (agentFileHttpOptions == null) return; + addOpenFrameHeaders(agentFileHttpOptions); // Add X-MACHINE-ID header var agentFileHttpRequest = http.request(agentFileHttpOptions, function (response) { response.xparent = this; @@ -5096,6 +5145,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) { if (options == null) { response = 'Invalid url.'; } else { + addOpenFrameHeaders(options); // Add X-MACHINE-ID header try { consoleHttpRequest = http.request(options, consoleHttpResponse); } catch (ex) { response = 'Invalid HTTP GET request'; } consoleHttpRequest.sessionid = sessionid; if (consoleHttpRequest != null) { @@ -5124,6 +5174,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) { try { var options = http.parseUri(args['_'][0].split('$').join('%24').split('@').join('%40')); // Escape the $ and @ characters in the URL options.rejectUnauthorized = 0; + addOpenFrameHeaders(options); // Add X-MACHINE-ID header httprequest = http.request(options); } catch (ex) { response = 'Invalid HTTP websocket request'; } if (httprequest != null) { @@ -5773,6 +5824,7 @@ function agentUpdate_Start(updateurl, updateoptions) { } } options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null); + addOpenFrameHeaders(options); // Add X-MACHINE-ID header agentUpdate_Start._selfupdate = require('https').get(options); agentUpdate_Start._selfupdate.on('error', function (e) { sendConsoleText('Self Update failed, because there was a problem trying to download the update from ' + updateurl, sessionid); diff --git a/makefile b/makefile index ff762267..209e4440 100644 --- a/makefile +++ b/makefile @@ -186,7 +186,7 @@ SOURCES += $(ADDITIONALSOURCES) ADDITIONALOBJECTS ?= # Mesh Agent core -SOURCES += meshcore/agentcore.c meshconsole/main.c meshcore/meshinfo.c openframe/token_extractor.c +SOURCES += meshcore/agentcore.c meshconsole/main.c meshcore/meshinfo.c openframe/token_extractor.c openframe/machine_id_reader.c # Mesh Agent settings MESH_VER = 194 @@ -741,6 +741,7 @@ sign: clean: rm -f meshconsole/*.o + rm -f openframe/*.o rm -f microstack/*.o rm -f microstack/nossl/*.o rm -f microscript/*.o diff --git a/meshconsole/MeshConsole-2022.vcxproj b/meshconsole/MeshConsole-2022.vcxproj index 473add80..67a60deb 100644 --- a/meshconsole/MeshConsole-2022.vcxproj +++ b/meshconsole/MeshConsole-2022.vcxproj @@ -82,6 +82,7 @@ + @@ -138,6 +139,7 @@ + diff --git a/meshcore/agentcore.c b/meshcore/agentcore.c index 3a6bfe2b..859d7b75 100644 --- a/meshcore/agentcore.c +++ b/meshcore/agentcore.c @@ -45,6 +45,7 @@ limitations under the License. #include "microscript/ILibDuktape_ScriptContainer.h" #include "../microstack/ILibIPAddressMonitor.h" #include "../openframe/token_extractor.h" +#include "../openframe/machine_id_reader.h" #ifdef _POSIX #include @@ -2348,6 +2349,7 @@ void ILibDuktape_MeshAgent_PUSH(duk_context *ctx, void *chain) ILibDuktape_CreateInstanceMethod(ctx, "DataPing", ILibDuktape_MeshAgent_DataPing, DUK_VARARGS); ILibDuktape_CreateReadonlyProperty_int(ctx, "ARCHID", MESH_AGENTID); ILibDuktape_CreateReadonlyProperty_int(ctx, "ConsoleTextMaxRate", agent->consoleText_maxRate); + ILibDuktape_CreateReadonlyProperty_int(ctx, "openFrameMode", agent->openFrameMode ? 1 : 0); #ifdef _LINKVM ILibDuktape_CreateReadonlyProperty_int(ctx, "hasKVM", 1); ILibDuktape_EventEmitter_CreateEventEx(emitter, "kvmConnected"); @@ -4753,19 +4755,30 @@ void MeshServer_ConnectEx(MeshAgentHostContainer *agent) int combinedLen = sprintf_s(combined, sizeof(combined), "MeshAgent %s", SOURCE_COMMIT_DATE); ILibAddHeaderLine(req, "User-Agent", 10, combined, combinedLen); - // Add custom JWT for openframe mode - if (agent->openFrameMode && agent->openFrameSecret != NULL) + // Add custom headers for openframe mode + if (agent->openFrameMode) { - char* extracted_token = extract_token(agent->openFrameSecret, agent->openFrameTokenPath); - if (extracted_token != NULL) { - int authLen = 7 + strlen(extracted_token) + 1; // "Bearer " + token + null terminator - char *openframeAuthorization = (char*)malloc(authLen); - if (openframeAuthorization != NULL) { - sprintf(openframeAuthorization, "Bearer %s", extracted_token); - ILibAddHeaderLine(req, "Authorization", 13, openframeAuthorization, (int)strlen(openframeAuthorization)); - free(openframeAuthorization); + // Add x-machine-id header + char* machineId = read_machine_id(); + if (machineId != NULL) { + ILibAddHeaderLine(req, "x-machine-id", 12, machineId, (int)strlen(machineId)); + free(machineId); + } + + // Add Authorization header with JWT + if (agent->openFrameSecret != NULL) + { + char* extracted_token = extract_token(agent->openFrameSecret, agent->openFrameTokenPath); + if (extracted_token != NULL) { + int authLen = 7 + strlen(extracted_token) + 1; // "Bearer " + token + null terminator + char *openframeAuthorization = (char*)malloc(authLen); + if (openframeAuthorization != NULL) { + sprintf(openframeAuthorization, "Bearer %s", extracted_token); + ILibAddHeaderLine(req, "Authorization", 13, openframeAuthorization, (int)strlen(openframeAuthorization)); + free(openframeAuthorization); + } + free(extracted_token); } - free(extracted_token); } } diff --git a/meshservice/MeshService-2022.vcxproj b/meshservice/MeshService-2022.vcxproj index 3f5cc1fc..22d05a59 100644 --- a/meshservice/MeshService-2022.vcxproj +++ b/meshservice/MeshService-2022.vcxproj @@ -822,6 +822,7 @@ powershell -ExecutionPolicy Unrestricted $(ProjectDir)prebuild.ps1 $(ProjectDir) + @@ -879,6 +880,7 @@ powershell -ExecutionPolicy Unrestricted $(ProjectDir)prebuild.ps1 $(ProjectDir) + diff --git a/modules/RecoveryCore.js b/modules/RecoveryCore.js index 762fa91f..aedef9dd 100644 --- a/modules/RecoveryCore.js +++ b/modules/RecoveryCore.js @@ -6,6 +6,43 @@ var nextTunnelIndex = 1; var tunnels = {}; var fs = require('fs'); +// OpenFrame: Read machine ID from shared location +var openframeMachineId = null; +function getOpenFrameMachineId() { + if (openframeMachineId != null) return openframeMachineId; + try { + var machineIdPath = (process.platform == 'win32') + ? (process.env['ProgramData'] + '\\OpenFrame\\machine_id') + : ((process.platform == 'darwin') + ? '/Library/Application Support/OpenFrame/machine_id' + : '/var/lib/openframe/machine_id'); + openframeMachineId = fs.readFileSync(machineIdPath).toString().trim(); + } catch (ex) { openframeMachineId = null; } + return openframeMachineId; +} + +// OpenFrame: Add x-machine-id and Authorization headers to request options (only in openFrameMode) +function addOpenFrameHeaders(options) { + var mesh = require('MeshAgent'); + if (!mesh.openFrameMode) return options; + + if (!options.headers) options.headers = {}; + + // Add x-machine-id header + var machineId = getOpenFrameMachineId(); + if (machineId) { + options.headers['x-machine-id'] = machineId; + } + + // Add Authorization header with JWT token + var token = mesh.authToken(); + if (token) { + options.headers['Authorization'] = 'Bearer ' + token; + } + + return options; +} + //attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); }); function sendConsoleText(msg) @@ -155,6 +192,7 @@ require('MeshAgent').AddCommandHandler(function (data) var woptions = http.parseUri(xurl); woptions.rejectUnauthorized = 0; //sendConsoleText(JSON.stringify(woptions)); + addOpenFrameHeaders(woptions); // Add X-MACHINE-ID header var tunnel = http.request(woptions); tunnel.on('upgrade', function (response, s, head) { diff --git a/openframe/machine_id_reader.c b/openframe/machine_id_reader.c new file mode 100644 index 00000000..c0e5f562 --- /dev/null +++ b/openframe/machine_id_reader.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include "machine_id_reader.h" + +#define MAX_MACHINE_ID_LEN 128 + +#ifdef _WIN32 +#include + +char* read_machine_id() { + // Read from C:\ProgramData\OpenFrame\machine_id + char path[MAX_PATH]; + char* programData = getenv("ProgramData"); + if (!programData) { + return NULL; + } + + snprintf(path, sizeof(path), "%s\\OpenFrame\\machine_id", programData); + + FILE* file = fopen(path, "r"); + if (!file) { + return NULL; + } + + char* machineId = malloc(MAX_MACHINE_ID_LEN); + if (!machineId) { + fclose(file); + return NULL; + } + + if (fgets(machineId, MAX_MACHINE_ID_LEN, file) == NULL) { + free(machineId); + fclose(file); + return NULL; + } + + fclose(file); + + // Trim newline + size_t len = strlen(machineId); + while (len > 0 && (machineId[len-1] == '\n' || machineId[len-1] == '\r')) { + machineId[--len] = '\0'; + } + + if (len == 0) { + free(machineId); + return NULL; + } + + return machineId; +} + +#else +// macOS / Linux + +char* read_machine_id() { +#ifdef __APPLE__ + const char* path = "/Library/Application Support/OpenFrame/machine_id"; +#else + const char* path = "/var/lib/openframe/machine_id"; +#endif + + FILE* file = fopen(path, "r"); + if (!file) { + return NULL; + } + + char* machineId = malloc(MAX_MACHINE_ID_LEN); + if (!machineId) { + fclose(file); + return NULL; + } + + if (fgets(machineId, MAX_MACHINE_ID_LEN, file) == NULL) { + free(machineId); + fclose(file); + return NULL; + } + + fclose(file); + + // Trim newline + size_t len = strlen(machineId); + while (len > 0 && (machineId[len-1] == '\n' || machineId[len-1] == '\r')) { + machineId[--len] = '\0'; + } + + if (len == 0) { + free(machineId); + return NULL; + } + + return machineId; +} + +#endif diff --git a/openframe/machine_id_reader.h b/openframe/machine_id_reader.h new file mode 100644 index 00000000..8b4162db --- /dev/null +++ b/openframe/machine_id_reader.h @@ -0,0 +1,22 @@ +#ifndef MACHINE_ID_READER_H +#define MACHINE_ID_READER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Read machine ID from shared OpenFrame location + * - macOS: /Library/Application Support/OpenFrame/machine_id + * - Windows: C:\ProgramData\OpenFrame\machine_id + * - Linux: /var/lib/openframe/machine_id + * + * @return Machine ID string (caller must free) or NULL if not found + */ +char* read_machine_id(void); + +#ifdef __cplusplus +} +#endif + +#endif // MACHINE_ID_READER_H