Compare commits
413 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a765bb13 | ||
|
|
7ad4b917be | ||
|
|
d10173a018 | ||
|
|
0e3a6b4915 | ||
|
|
b949cecc5f | ||
|
|
c7cbf2f12a | ||
|
|
d49afdd7bf | ||
|
|
133e77c8c6 | ||
|
|
e404e86b9f | ||
|
|
c6da201af8 | ||
|
|
9a27d7637c | ||
|
|
5aa2467409 | ||
|
|
9398afd07e | ||
|
|
b2cd84035b | ||
|
|
97547d72a3 | ||
|
|
7faf043c35 | ||
|
|
9df0330896 | ||
|
|
42f61ea46e | ||
|
|
0d65080a8a | ||
|
|
bd4d8b12d4 | ||
|
|
18ae8bdbf4 | ||
|
|
46c76f7234 | ||
|
|
e65bf77111 | ||
|
|
f19ad6c664 | ||
|
|
fe2f12149d | ||
|
|
91bd5ae702 | ||
|
|
38f5bf2e0f | ||
|
|
79f00bcaab | ||
|
|
c55065505b | ||
|
|
e0e8a3fcaa | ||
|
|
3f77cfa93a | ||
|
|
5ee9aa2410 | ||
|
|
0ab3f01ca6 | ||
|
|
4b621a01fb | ||
|
|
f2681de87d | ||
|
|
c90fa55c99 | ||
|
|
edeef03f00 | ||
|
|
d7fe87d1db | ||
|
|
9d4f51e970 | ||
|
|
0b376fe5a0 | ||
|
|
9fd40751b2 | ||
|
|
d246307fae | ||
|
|
711bb56a93 | ||
|
|
5734bcc33a | ||
|
|
1310c57397 | ||
|
|
854d6c00d2 | ||
|
|
c96d7ff1ca | ||
|
|
712f06db3c | ||
|
|
cac505e2cd | ||
|
|
9d962bc523 | ||
|
|
f079692b16 | ||
|
|
3ee06abfe8 | ||
|
|
b46ddf2f70 | ||
|
|
f7b958d28b | ||
|
|
64c8d2c238 | ||
|
|
31f2224a93 | ||
|
|
de685556c8 | ||
|
|
92375ddc93 | ||
|
|
ea80f8595e | ||
|
|
763f76b68f | ||
|
|
1a02539f23 | ||
|
|
90b71e924f | ||
|
|
73c18c4dd5 | ||
|
|
def62075c7 | ||
|
|
998769a888 | ||
|
|
ca6ec5ebc9 | ||
|
|
0dd56d5708 | ||
|
|
95729d2a88 | ||
|
|
1ae7b9f641 | ||
|
|
c66a9a12ef | ||
|
|
2c310450cf | ||
|
|
b6e022c01e | ||
|
|
e66776c5da | ||
|
|
d1cb184e9e | ||
|
|
6aa60d556f | ||
|
|
8d4e9bcede | ||
|
|
eb0d24cf0b | ||
|
|
fe02d3158d | ||
|
|
61d3487f8a | ||
|
|
a23725eb40 | ||
|
|
9b60271f31 | ||
|
|
31f328086e | ||
|
|
7aa4061cad | ||
|
|
54bb0177ee | ||
|
|
ce70f4ac08 | ||
|
|
f712cd9425 | ||
|
|
c4592dcc4f | ||
|
|
2a274fe569 | ||
|
|
5d0b5acdfb | ||
|
|
f80ba62cfc | ||
|
|
5da849063b | ||
|
|
68ac8cf86c | ||
|
|
8e70cd7187 | ||
|
|
5cf468159d | ||
|
|
c92b88a374 | ||
|
|
1b01b90cd6 | ||
|
|
6a366fe174 | ||
|
|
e2362a0547 | ||
|
|
59fcc0dbc6 | ||
|
|
988983b880 | ||
|
|
a1854fa074 | ||
|
|
22fc95926a | ||
|
|
c2eb1f2516 | ||
|
|
832d11739b | ||
|
|
ab7be919a0 | ||
|
|
624d61db43 | ||
|
|
b0d8e3fe48 | ||
|
|
a33047747a | ||
|
|
39e81befe1 | ||
|
|
8eeb96fb0d | ||
|
|
f9228ad0eb | ||
|
|
ce4217c346 | ||
|
|
d9262f7c9d | ||
|
|
8e8ec4f88a | ||
|
|
18167499d9 | ||
|
|
da5d03b0e7 | ||
|
|
c41eb72a2c | ||
|
|
ef4d764ab4 | ||
|
|
c16ff89902 | ||
|
|
041e8021d1 | ||
|
|
6e31562ecb | ||
|
|
874ef23c40 | ||
|
|
7bd5b66ebc | ||
|
|
975e49a190 | ||
|
|
462c383b77 | ||
|
|
dbb5b4ba11 | ||
|
|
545bf58e8d | ||
|
|
30b390bdbf | ||
|
|
d0a51e90e9 | ||
|
|
c773857b17 | ||
|
|
cae1f7ea14 | ||
|
|
b398cb7fa9 | ||
|
|
5a1a97ca7e | ||
|
|
dd21f14f4e | ||
|
|
3da60b43ac | ||
|
|
727080ab68 | ||
|
|
54170c44a0 | ||
|
|
8a5ad1563d | ||
|
|
911d987a84 | ||
|
|
9a8f4e8ebe | ||
|
|
badee98b71 | ||
|
|
d44faed29e | ||
|
|
01c585f7f1 | ||
|
|
777eb53476 | ||
|
|
b71c69e81d | ||
|
|
7d59210d05 | ||
|
|
fc387ca417 | ||
|
|
fc83211e90 | ||
|
|
9ebd23a518 | ||
|
|
3f8301e9d7 | ||
|
|
b39235643e | ||
|
|
0ec8b061c8 | ||
|
|
e58d659fa9 | ||
|
|
45169b2cfd | ||
|
|
c09d2fad3e | ||
|
|
7928f7fb30 | ||
|
|
438289b2ed | ||
|
|
561fc67f33 | ||
|
|
aa7767f37c | ||
|
|
f23792881e | ||
|
|
c920b28acc | ||
|
|
36f1b4d5be | ||
|
|
141bec559f | ||
|
|
0d885e6fa0 | ||
|
|
e10f5277e9 | ||
|
|
f33768fe32 | ||
|
|
1e565768d1 | ||
|
|
5193fef888 | ||
|
|
63930c4b33 | ||
|
|
ac27034542 | ||
|
|
cfe9345b53 | ||
|
|
1e2d736d6d | ||
|
|
0c825251eb | ||
|
|
ccf00b7d06 | ||
|
|
6d412a7bea | ||
|
|
5a0d3054b8 | ||
|
|
6dbc6d2d07 | ||
|
|
ea8e1b1076 | ||
|
|
d1368791e9 | ||
|
|
590166f847 | ||
|
|
19d0df7e7f | ||
|
|
1d87c42977 | ||
|
|
ec7505987d | ||
|
|
37729269ba | ||
|
|
952bcde25f | ||
|
|
4848df4faf | ||
|
|
41d1f9d26f | ||
|
|
113adb5b85 | ||
|
|
8e5aa35bf3 | ||
|
|
1139a37338 | ||
|
|
b20e51561a | ||
|
|
5ff44bbae8 | ||
|
|
2beeb6f644 | ||
|
|
3eede1bf43 | ||
|
|
9f8ea3a6b5 | ||
|
|
23679d119b | ||
|
|
a3fd6008a0 | ||
|
|
d0014b3f8b | ||
|
|
04c96eb2ff | ||
|
|
df64c750cc | ||
|
|
b90b2ac0bf | ||
|
|
39a1755b3d | ||
|
|
61fb6898c0 | ||
|
|
bc34f140c8 | ||
|
|
0bee2be3cf | ||
|
|
1d67172dd3 | ||
|
|
b99a97eb48 | ||
|
|
a1899a719f | ||
|
|
5fcfa8f369 | ||
|
|
7172d1f701 | ||
|
|
8bc760855e | ||
|
|
5fc3683ebb | ||
|
|
626416a202 | ||
|
|
d84afb939a | ||
|
|
3cd875d6ee | ||
|
|
f5e63b7cbd | ||
|
|
8b20f44dd5 | ||
|
|
59a3a22ea5 | ||
|
|
2f34b7e83b | ||
|
|
4d5ec6cac1 | ||
|
|
7635109f6d | ||
|
|
de60b7f952 | ||
|
|
405261a67e | ||
|
|
4bcec73e07 | ||
|
|
d81c00c0b0 | ||
|
|
d2e4f12ea3 | ||
|
|
f7c79166da | ||
|
|
2b5337329a | ||
|
|
21206b670c | ||
|
|
1d04a13a64 | ||
|
|
9e309584db | ||
|
|
0d56504d96 | ||
|
|
d17918936d | ||
|
|
0a64b80654 | ||
|
|
35e38a71c0 | ||
|
|
6fe30b7730 | ||
|
|
516a14b4ca | ||
|
|
390991123a | ||
|
|
d367b2ed87 | ||
|
|
5a410ccd5b | ||
|
|
ad1d82152a | ||
|
|
7c79dbcda1 | ||
|
|
cf23a3df81 | ||
|
|
e8cbebaffe | ||
|
|
ac0d805378 | ||
|
|
7b48e3b5f5 | ||
|
|
ea6682e06a | ||
|
|
4e37455471 | ||
|
|
b4323223cc | ||
|
|
bf00de4425 | ||
|
|
f95dbdd404 | ||
|
|
e1e59953f4 | ||
|
|
d2d9f7a13e | ||
|
|
21e196e35c | ||
|
|
0ccce62c9d | ||
|
|
3a78cb83c4 | ||
|
|
8dd5545a2b | ||
|
|
2f61e3edad | ||
|
|
7caf2aa05d | ||
|
|
cee181fb61 | ||
|
|
fa39f8a105 | ||
|
|
c1e3354c91 | ||
|
|
aae551dab9 | ||
|
|
3a28e33efb | ||
|
|
92385e3d73 | ||
|
|
adeaac1588 | ||
|
|
77f44fc308 | ||
|
|
3efa680361 | ||
|
|
899ff0c742 | ||
|
|
61f1c22c27 | ||
|
|
08877844c4 | ||
|
|
698b7fb056 | ||
|
|
fbd4533477 | ||
|
|
6f2b57998f | ||
|
|
a6acb35a31 | ||
|
|
fc29e60939 | ||
|
|
5b76a31644 | ||
|
|
41e4213fc5 | ||
|
|
999ae7f67f | ||
|
|
44991975d3 | ||
|
|
6da9222871 | ||
|
|
10b57dcf9e | ||
|
|
1cfe0e2c31 | ||
|
|
7e504a28e6 | ||
|
|
92869ec78b | ||
|
|
9264b9d281 | ||
|
|
d8a91d3150 | ||
|
|
5e71bcc634 | ||
|
|
4d75d48eea | ||
|
|
df6474802d | ||
|
|
31c323583b | ||
|
|
4b891c5be0 | ||
|
|
86713cacac | ||
|
|
95d60fef13 | ||
|
|
b4e7e7384d | ||
|
|
62cae4cf8a | ||
|
|
aaad8b79cc | ||
|
|
b0d9b17e36 | ||
|
|
991c23c5f9 | ||
|
|
87c5745594 | ||
|
|
707982a71b | ||
|
|
a8fc5e1187 | ||
|
|
40ac6aa636 | ||
|
|
1d9de2e144 | ||
|
|
ee0018e4d1 | ||
|
|
721c909158 | ||
|
|
2630931eee | ||
|
|
13b8ca3686 | ||
|
|
a59da2fb9a | ||
|
|
c3470f493b | ||
|
|
3d815824ba | ||
|
|
9619a83ba7 | ||
|
|
f6c7761afb | ||
|
|
9fd3e4c569 | ||
|
|
118b0c58dc | ||
|
|
57442e4988 | ||
|
|
602eb3c64a | ||
|
|
28c522c5bb | ||
|
|
df91c90d33 | ||
|
|
81557ab2d4 | ||
|
|
6b21bacad2 | ||
|
|
46ebadf440 | ||
|
|
6c3e60e13c | ||
|
|
7955bc4954 | ||
|
|
482e79f913 | ||
|
|
0a89d07937 | ||
|
|
c053c14dd0 | ||
|
|
5950b2c829 | ||
|
|
42a07e9d74 | ||
|
|
d7341ab153 | ||
|
|
74d6252699 | ||
|
|
b08f3827f5 | ||
|
|
6976992735 | ||
|
|
b1c3e2a8e7 | ||
|
|
c67a76bcc2 | ||
|
|
62199d8057 | ||
|
|
52a2194116 | ||
|
|
2b3c329a54 | ||
|
|
17cf36edd9 | ||
|
|
a171cde2ff | ||
|
|
5d5e861a4a | ||
|
|
26ac23c80d | ||
|
|
5a7e3d9869 | ||
|
|
abbb0fa9ee | ||
|
|
89b67ff999 | ||
|
|
6c685d5557 | ||
|
|
49b561260a | ||
|
|
aa8f45f4a9 | ||
|
|
7cf14a2b69 | ||
|
|
7e7361de9b | ||
|
|
4cd7b408fa | ||
|
|
bc6451fee5 | ||
|
|
f1ba76a423 | ||
|
|
385a4738cd | ||
|
|
5c13f178be | ||
|
|
323ef2d50a | ||
|
|
dd249938b3 | ||
|
|
30d958fbd9 | ||
|
|
1c8d664962 | ||
|
|
b22e56b6d2 | ||
|
|
bc2f34b629 | ||
|
|
e8da6a607c | ||
|
|
77d268d064 | ||
|
|
23ee76e26a | ||
|
|
be3e333b40 | ||
|
|
e3f68226d2 | ||
|
|
b71b4d04e6 | ||
|
|
bf7957ebff | ||
|
|
19eb1235f5 | ||
|
|
274bb525a5 | ||
|
|
33c0e82286 | ||
|
|
56d6527bf5 | ||
|
|
3ce2fd92c0 | ||
|
|
eb27334b82 | ||
|
|
414d9b9561 | ||
|
|
1747ff7550 | ||
|
|
f39b6f8859 | ||
|
|
ca868afdd1 | ||
|
|
410c84c30b | ||
|
|
18b731fd36 | ||
|
|
832e618602 | ||
|
|
7b8cf85740 | ||
|
|
1dca9e2235 | ||
|
|
30d570f28b | ||
|
|
f854c80421 | ||
|
|
f5891f2946 | ||
|
|
1da33f0ade | ||
|
|
e025e9558b | ||
|
|
ccf57bee1a | ||
|
|
4ba08a96f7 | ||
|
|
548edd13d6 | ||
|
|
31ebb21e0b | ||
|
|
4a3c6db0ea | ||
|
|
f9af1ffc90 | ||
|
|
95e7997e60 | ||
|
|
9081a6aeac | ||
|
|
afc6165827 | ||
|
|
c9c0a6cb67 | ||
|
|
b46c322c41 | ||
|
|
4ff5a5c912 | ||
|
|
65d1346e06 | ||
|
|
5d1c8ca68b | ||
|
|
9294488d4e | ||
|
|
d2a0946f22 | ||
|
|
3be8ec5add | ||
|
|
102489447d | ||
|
|
8e8cc4b327 | ||
|
|
ce93c896ce | ||
|
|
7b67b992e2 | ||
|
|
95bbd7157f | ||
|
|
8e6cc14981 | ||
|
|
862e2ee80b | ||
|
|
81e98033fc |
2
LICENSE
|
|
@ -186,7 +186,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-2021 Intel Corporation
|
||||
Copyright 2017-2025 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
|||
|
|
@ -594,12 +594,14 @@
|
|||
<Content Include="readme.md" />
|
||||
<Content Include="sample-config-advanced.json" />
|
||||
<Content Include="sample-config.json" />
|
||||
<Content Include="SECURITY.md" />
|
||||
<Content Include="SourceFileList.txt" />
|
||||
<Content Include="translate\readme.txt" />
|
||||
<Content Include="translate\translate.json" />
|
||||
<Content Include="views\agentinvite.handlebars" />
|
||||
<Content Include="views\default-mobile.handlebars" />
|
||||
<Content Include="views\default.handlebars" />
|
||||
<Content Include="views\default3.handlebars" />
|
||||
<Content Include="views\download.handlebars" />
|
||||
<Content Include="views\download2.handlebars" />
|
||||
<Content Include="views\error404-mobile.handlebars" />
|
||||
|
|
|
|||
|
|
@ -682,5 +682,67 @@
|
|||
],
|
||||
"statusDescription": "Jelenlegi agent állapota",
|
||||
"description": "Kattintson a Telepítés vagy Eltávolítás gombokra a Távfelügyeleti alkalmazás telepítéséhez vagy eltávolításához. Telepítés után ez az alkalmazás a háttérben fut, lehetővé téve, hogy a számítógépet egy távoli rendszergazda kezelje."
|
||||
},
|
||||
"ca": {
|
||||
"agent": "Agent",
|
||||
"agentVersion": "Nova versió",
|
||||
"group": "Grup de dispositius",
|
||||
"url": "URL del servidor",
|
||||
"meshName": "Nom del grup",
|
||||
"meshId": "Identificador de grup",
|
||||
"serverId": "Identificador del servidor",
|
||||
"setup": "Configuració",
|
||||
"update": "Actualització",
|
||||
"install": "Instal·lar",
|
||||
"uninstall": "Desinstal·la",
|
||||
"connect": "Connecta't",
|
||||
"disconnect": "Desconnecta",
|
||||
"cancel": "Cancel · lar",
|
||||
"close": "Tanca",
|
||||
"pressok": "Premeu D'acord per desconnectar",
|
||||
"elevation": "Es necessiten permisos elevats per instal·lar/desinstal·lar aquest programari.",
|
||||
"sudo": "Si us plau, torna-ho a provar amb sudo.",
|
||||
"ctrlc": "Premeu Ctrl-C per sortir.",
|
||||
"commands": "Podeu executar la versió de text des de la línia d'ordres amb les següents ordres",
|
||||
"graphicalerror": "La versió gràfica d'aquest instal·lador no pot executar-se en aquest sistema",
|
||||
"zenity": "Proveu d'instal·lar/actualitzar Zenity i torneu a executar-lo",
|
||||
"status": [
|
||||
"NO ESTÀ INSTAL · LAT",
|
||||
"CÓRRER",
|
||||
"NO CORRE"
|
||||
],
|
||||
"statusDescription": "Estat actual de l'agent",
|
||||
"description": "Feu clic als botons següents per instal·lar o desinstal·lar aquest programari de gestió remota. Quan s'instal·la, aquest programari s'executa en segon pla i permet que aquest ordinador sigui gestionat i controlat per un administrador remot."
|
||||
},
|
||||
"uk": {
|
||||
"agent": "Агент",
|
||||
"agentVersion": "Нова Версія",
|
||||
"group": "Група Пристроїв",
|
||||
"url": "URL Сервера",
|
||||
"meshName": "Ім'я Групи",
|
||||
"meshId": "Ідентифікатор групи",
|
||||
"serverId": "Ідентифікатор серверу",
|
||||
"setup": "Налаштувати",
|
||||
"update": "Оновлення",
|
||||
"install": "Інсталювати",
|
||||
"uninstall": "Видалити",
|
||||
"connect": "Підключитися",
|
||||
"disconnect": "Відключити",
|
||||
"cancel": "Скасувати",
|
||||
"close": "Закрити",
|
||||
"pressok": "Натисніть OK, щоб від'єднатися",
|
||||
"elevation": "Для інсталяції/деінсталяції цього програмного забезпечення потрібні підвищені дозволи.",
|
||||
"sudo": "Будь ласка, спробуйте ще раз за допомогою sudo.",
|
||||
"ctrlc": "Натисніть Ctrl-C, щоб вийти",
|
||||
"commands": "Ви можете запустити текстову версію з командного рядка за допомогою таких команд",
|
||||
"graphicalerror": "Графічна версія цього інсталятора не може працювати в цій системі",
|
||||
"zenity": "Спробуйте встановити/оновити Zenity та запустіть наново",
|
||||
"status": [
|
||||
"НЕ ВСТАНОВЛЕНО",
|
||||
"ВИКОНУЄТЬСЯ",
|
||||
"НЕ ПРАЦЮЄ"
|
||||
],
|
||||
"statusDescription": "Поточний Статус Агента",
|
||||
"description": "Клікнути кнопки нижче, щоб інсталювати або видалити це програмне забезпечення для віддаленого керування. Після інсталювання ця програма працює у фоновому режимі, що дозволяє віддаленому адміністратору керувати цим комп'ютером."
|
||||
}
|
||||
}
|
||||
|
|
@ -588,7 +588,7 @@ function run(argv) {
|
|||
}
|
||||
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
|
||||
amtMei.getProvisioningMode(function (result) { if (result) { mestate.ProvisioningMode = result; } });
|
||||
amtMei.getEHBCState(function (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); });
|
||||
amtMei.getEHBCState(function (result) { if (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); } });
|
||||
amtMei.getControlMode(function (result) { if (result) { mestate.controlmode = result; } });
|
||||
amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } });
|
||||
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } });
|
||||
|
|
|
|||
|
|
@ -225,19 +225,14 @@ function macos_memUtilization()
|
|||
function windows_thermals()
|
||||
{
|
||||
var ret = [];
|
||||
child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', '/namespace:\\\\root\\wmi', 'PATH', 'MSAcpi_ThermalZoneTemperature', 'get', 'CurrentTemperature']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
|
||||
if(child.stdout.str.trim!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
for (var i = 1; i < lines.length; ++i)
|
||||
{
|
||||
if (lines[i].trim() != '') { ret.push(((parseFloat(lines[i]) / 10) - 273.15).toFixed(2)); }
|
||||
try {
|
||||
ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
|
||||
if (ret[0]) {
|
||||
for (var i = 0; i < ret.length; ++i) {
|
||||
ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
|
@ -285,16 +280,10 @@ function macos_thermals()
|
|||
return (ret);
|
||||
}
|
||||
|
||||
switch(process.platform)
|
||||
{
|
||||
case 'linux':
|
||||
module.exports = { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals };
|
||||
break;
|
||||
case 'win32':
|
||||
module.exports = { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals };
|
||||
break;
|
||||
case 'darwin':
|
||||
module.exports = { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_thermals };
|
||||
break;
|
||||
}
|
||||
const platformConfig = {
|
||||
linux: { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals },
|
||||
win32: { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals },
|
||||
darwin: { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_thermals }
|
||||
};
|
||||
|
||||
module.exports = platformConfig[process.platform];
|
||||
|
|
|
|||
|
|
@ -70,31 +70,38 @@ function linux_identifiers()
|
|||
var values = {};
|
||||
|
||||
if (!require('fs').existsSync('/sys/class/dmi/id')) {
|
||||
if(require('fs').existsSync('/sys/firmware/devicetree/base/model')){
|
||||
if(require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim().startsWith('Raspberry')){
|
||||
if (require('fs').existsSync('/sys/firmware/devicetree/base/model')) {
|
||||
if (require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim().startsWith('Raspberry')) {
|
||||
identifiers['board_vendor'] = 'Raspberry Pi';
|
||||
identifiers['board_name'] = require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim();
|
||||
identifiers['board_serial'] = require('fs').readFileSync('/sys/firmware/devicetree/base/serial-number').toString().trim();
|
||||
}else{
|
||||
const memorySlots = [];
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('vcgencmd get_mem arm && vcgencmd get_mem gpu\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
const lines = child.stdout.str.trim().split('\n');
|
||||
if (lines.length == 2) {
|
||||
memorySlots.push({ Locator: "ARM Memory", Size: lines[0].split('=')[1].trim() })
|
||||
memorySlots.push({ Locator: "GPU Memory", Size: lines[1].split('=')[1].trim() })
|
||||
ret.memory = { Memory_Device: memorySlots };
|
||||
}
|
||||
} catch (xx) { }
|
||||
} else {
|
||||
throw('Unknown board');
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
throw ('this platform does not have DMI statistics');
|
||||
}
|
||||
} else {
|
||||
var entries = require('fs').readdirSync('/sys/class/dmi/id');
|
||||
for(var i in entries)
|
||||
{
|
||||
if (require('fs').statSync('/sys/class/dmi/id/' + entries[i]).isFile())
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var i in entries) {
|
||||
if (require('fs').statSync('/sys/class/dmi/id/' + entries[i]).isFile()) {
|
||||
try {
|
||||
ret[entries[i]] = require('fs').readFileSync('/sys/class/dmi/id/' + entries[i]).toString().trim();
|
||||
}
|
||||
catch(z)
|
||||
{
|
||||
}
|
||||
if (ret[entries[i]] == 'None') { delete ret[entries[i]];}
|
||||
} catch(z) { }
|
||||
if (ret[entries[i]] == 'None') { delete ret[entries[i]]; }
|
||||
}
|
||||
}
|
||||
entries = null;
|
||||
|
|
@ -144,11 +151,19 @@ function linux_identifiers()
|
|||
child.stdin.write("lshw -class disk | tr '\\n' '`' | awk '" + '{ len=split($0,lines,"*"); printf "["; for(i=2;i<=len;++i) { model=""; caption=""; size=""; clen=split(lines[i],item,"`"); for(j=2;j<clen;++j) { split(item[j],tokens,":"); split(tokens[1],key," "); if(key[1]=="description") { caption=substr(tokens[2],2); } if(key[1]=="product") { model=substr(tokens[2],2); } if(key[1]=="size") { size=substr(tokens[2],2); } } if(model=="") { model=caption; } if(caption!="" || model!="") { printf "%s{\\"Caption\\":\\"%s\\",\\"Model\\":\\"%s\\",\\"Size\\":\\"%s\\"}",(i==2?"":","),caption,model,size; } } printf "]"; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
try { identifiers['storage_devices'] = JSON.parse(child.stdout.str.trim()); } catch (xx) { }
|
||||
child = null;
|
||||
|
||||
// Fetch storage volumes using df
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -T | awk \'NR==1 || $1 ~ ".+"{print $3, $4, $5, $7, $2}\' | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\",\\"type\\":\\"%s\\"},", $1, $2, $3, $4, $5}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try { ret.volumes = JSON.parse(child.stdout.str.trim()); } catch (xx) { }
|
||||
child = null;
|
||||
|
||||
values.identifiers = identifiers;
|
||||
values.linux = ret;
|
||||
trimIdentifiers(values.identifiers);
|
||||
child = null;
|
||||
|
||||
var dmidecode = require('lib-finder').findBinary('dmidecode');
|
||||
if (dmidecode != null)
|
||||
|
|
@ -343,6 +358,36 @@ function linux_identifiers()
|
|||
child = null;
|
||||
}
|
||||
|
||||
// Linux Last Boot Up Time
|
||||
try {
|
||||
child = require('child_process').execFile('/usr/bin/uptime', ['', '-s']); // must include blank value at begining for some reason?
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.on('data', function () { });
|
||||
child.waitExit();
|
||||
var regex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||
if (regex.test(child.stdout.str.trim())) {
|
||||
values.linux.LastBootUpTime = child.stdout.str.trim();
|
||||
} else {
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('date -d "@$(( $(date +%s) - $(awk \'{print int($1)}\' /proc/uptime) ))" "+%Y-%m-%d %H:%M:%S"\nexit\n');
|
||||
child.waitExit();
|
||||
if (regex.test(child.stdout.str.trim())) {
|
||||
values.linux.LastBootUpTime = child.stdout.str.trim();
|
||||
}
|
||||
}
|
||||
child = null;
|
||||
} catch (ex) { }
|
||||
|
||||
// Linux TPM
|
||||
try {
|
||||
if (require('fs').statSync('/sys/class/tpm/tpm0').isDirectory()){
|
||||
values.tpm = {
|
||||
SpecVersion: require('fs').readFileSync('/sys/class/tpm/tpm0/tpm_version_major').toString().trim()
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
return (values);
|
||||
}
|
||||
|
||||
|
|
@ -377,104 +422,6 @@ function windows_wmic_results(str)
|
|||
return (result);
|
||||
}
|
||||
|
||||
function windows_volumes()
|
||||
{
|
||||
var promise = require('promise');
|
||||
var p1 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
||||
var p2 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
||||
|
||||
p1._p2 = p2;
|
||||
p2._p1 = p1;
|
||||
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-']);
|
||||
p1.child = child;
|
||||
child.promise = p1;
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('Get-Volume | Select-Object -Property DriveLetter,FileSystemLabel,FileSystemType,Size,SizeRemaining,DriveType | ConvertTo-Csv -NoTypeInformation\nexit\n');
|
||||
child.on('exit', function (c)
|
||||
{
|
||||
var a, i, tokens, key;
|
||||
var ret = {};
|
||||
|
||||
a = this.stdout.str.trim().split('\r\n');
|
||||
for (i = 1; i < a.length; ++i)
|
||||
{
|
||||
tokens = a[i].split(',');
|
||||
if (tokens[0] != '' && tokens[1] != undefined)
|
||||
{
|
||||
ret[tokens[0].split('"')[1]] =
|
||||
{
|
||||
name: tokens[1].split('"')[1],
|
||||
type: tokens[2].split('"')[1],
|
||||
size: tokens[3].split('"')[1],
|
||||
sizeremaining: tokens[4].split('"')[1],
|
||||
removable: tokens[5].split('"')[1] == 'Removable',
|
||||
cdrom: tokens[5].split('"')[1] == 'CD-ROM'
|
||||
};
|
||||
}
|
||||
}
|
||||
this.promise._res({ r: ret, t: tokens });
|
||||
});
|
||||
|
||||
p1.then(function (j)
|
||||
{
|
||||
var ret = j.r;
|
||||
var tokens = j.t;
|
||||
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-']);
|
||||
p2.child = child;
|
||||
child.promise = p2;
|
||||
child.tokens = tokens;
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('Get-BitLockerVolume | Select-Object -Property MountPoint,VolumeStatus,ProtectionStatus | ConvertTo-Csv -NoTypeInformation\nexit\n');
|
||||
child.on('exit', function ()
|
||||
{
|
||||
var i;
|
||||
var a = this.stdout.str.trim().split('\r\n');
|
||||
for (i = 1; i < a.length; ++i)
|
||||
{
|
||||
tokens = a[i].split(',');
|
||||
key = tokens[0].split(':').shift().split('"').pop();
|
||||
if (ret[key] != null)
|
||||
{
|
||||
ret[key].volumeStatus = tokens[1].split('"')[1];
|
||||
ret[key].protectionStatus = tokens[2].split('"')[1];
|
||||
try {
|
||||
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
|
||||
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ', tokens[0].split('"')[1], ' -Type recoverypassword'], {});
|
||||
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
keychild.waitExit();
|
||||
var lines = keychild.stdout.str.trim().split('\r\n');
|
||||
for (var x = 0; x < lines.length; x++) { // Loop each line
|
||||
var abc = lines[x].trim();
|
||||
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
|
||||
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
|
||||
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
|
||||
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
|
||||
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
|
||||
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
|
||||
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
|
||||
var nextline = lines[x + 1].trim();
|
||||
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
|
||||
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
|
||||
foundIDMarkedLine = true;
|
||||
}else if (x + 1 < lines.length && nextline !== '') {
|
||||
password = nextline;
|
||||
foundMarkedLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret[key].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
|
||||
ret[key].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
|
||||
} catch(ex) { }
|
||||
}
|
||||
}
|
||||
this.promise._res(ret);
|
||||
});
|
||||
});
|
||||
return (p2);
|
||||
}
|
||||
|
||||
function windows_identifiers()
|
||||
{
|
||||
var ret = { windows: {} };
|
||||
|
|
@ -559,6 +506,23 @@ function windows_identifiers()
|
|||
}
|
||||
|
||||
try { ret.identifiers.cpu_name = ret.windows.cpu[0].Name; } catch (x) { }
|
||||
|
||||
// Windows TPM
|
||||
IntToStr = function (v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); };
|
||||
try {
|
||||
values = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftTpm', "SELECT * FROM Win32_Tpm", ['IsActivated_InitialValue','IsEnabled_InitialValue','IsOwned_InitialValue','ManufacturerId','ManufacturerVersion','SpecVersion']);
|
||||
if(values[0]) {
|
||||
ret.tpm = {
|
||||
SpecVersion: values[0].SpecVersion.split(",")[0],
|
||||
ManufacturerId: IntToStr(values[0].ManufacturerId).replace(/[^\x00-\x7F]/g, ""),
|
||||
ManufacturerVersion: values[0].ManufacturerVersion,
|
||||
IsActivated: values[0].IsActivated_InitialValue,
|
||||
IsEnabled: values[0].IsEnabled_InitialValue,
|
||||
IsOwned: values[0].IsOwned_InitialValue,
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
return (ret);
|
||||
}
|
||||
function macos_identifiers()
|
||||
|
|
@ -623,7 +587,7 @@ function macos_identifiers()
|
|||
var key = parts[0].trim();
|
||||
var value = parts[1].trim();
|
||||
value = (key == 'Part Number' || key == 'Manufacturer') ? hexToAscii(parts[1].trim()) : parts[1].trim();
|
||||
slotObj[key] = value; // Store attribute in the slot object
|
||||
slotObj[key.replace(' ','')] = value; // Store attribute in the slot object
|
||||
}
|
||||
});
|
||||
memorySlots.push(slotObj);
|
||||
|
|
@ -674,6 +638,50 @@ function macos_identifiers()
|
|||
ret.identifiers.storage_devices = devices;
|
||||
}
|
||||
|
||||
// Fetch storage volumes using df
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -aHY | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\",\\"type\\":\\"%s\\"},", $3, $4, $5, $10, $2}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
ret.darwin.volumes = JSON.parse(child.stdout.str.trim());
|
||||
for (var index = 0; index < ret.darwin.volumes.length; index++) {
|
||||
if (ret.darwin.volumes[index].type == 'auto_home'){
|
||||
ret.darwin.volumes.splice(index,1);
|
||||
}
|
||||
}
|
||||
if (ret.darwin.volumes.length == 0) { // not sonima OS so dont show type for now
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -aH | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\"},", $2, $3, $4, $9}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
ret.darwin.volumes = JSON.parse(child.stdout.str.trim());
|
||||
for (var index = 0; index < ret.darwin.volumes.length; index++) {
|
||||
if (ret.darwin.volumes[index].size == 'auto_home'){
|
||||
ret.darwin.volumes.splice(index,1);
|
||||
}
|
||||
}
|
||||
} catch (xx) { }
|
||||
}
|
||||
} catch (xx) { }
|
||||
child = null;
|
||||
|
||||
// MacOS Last Boot Up Time
|
||||
try {
|
||||
child = require('child_process').execFile('/usr/sbin/sysctl', ['', 'kern.boottime']); // must include blank value at begining for some reason?
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.on('data', function () { });
|
||||
child.waitExit();
|
||||
const timestampMatch = /\{ sec = (\d+), usec = \d+ \}/.exec(child.stdout.str.trim());
|
||||
if (!ret.darwin) {
|
||||
ret.darwin = { LastBootUpTime: parseInt(timestampMatch[1]) };
|
||||
} else {
|
||||
ret.darwin.LastBootUpTime = parseInt(timestampMatch[1]);
|
||||
}
|
||||
child = null;
|
||||
} catch (ex) { }
|
||||
|
||||
trimIdentifiers(ret.identifiers);
|
||||
|
||||
child = null;
|
||||
|
|
@ -694,32 +702,35 @@ function hexToAscii(hexString) {
|
|||
|
||||
function win_chassisType()
|
||||
{
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'SystemEnclosure', 'get', 'ChassisTypes']);
|
||||
// needs to be replaced with win-wmi but due to bug in win-wmi it doesnt handle arrays correctly
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
|
||||
if (child == null) { return ([]); }
|
||||
child.descriptorMetadata = 'process-manager';
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('Get-WmiObject Win32_SystemEnclosure | Select-Object -ExpandProperty ChassisTypes\r\n');
|
||||
child.stdin.write('exit\r\n');
|
||||
child.waitExit();
|
||||
|
||||
try
|
||||
{
|
||||
var tok = child.stdout.str.split('{')[1].split('}')[0];
|
||||
var val = tok.split(',')[0];
|
||||
return (parseInt(val));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
try {
|
||||
return (parseInt(child.stdout.str));
|
||||
} catch (e) {
|
||||
return (2); // unknown
|
||||
}
|
||||
}
|
||||
|
||||
function win_systemType()
|
||||
{
|
||||
var CSV = '/FORMAT:"' + require('util-language').wmicXslPath + 'csv"';
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'ComputerSystem', 'get', 'PCSystemType', CSV]);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
try {
|
||||
var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT PCSystemType FROM Win32_ComputerSystem', ['PCSystemType']);
|
||||
if (tokens[0]) {
|
||||
return (parseInt(tokens[0]['PCSystemType']));
|
||||
} else {
|
||||
return (parseInt(1)); // default is desktop
|
||||
}
|
||||
} catch (ex) {
|
||||
return (parseInt(1)); // default is desktop
|
||||
}
|
||||
|
||||
return (parseInt(child.stdout.str.trim().split(',').pop()));
|
||||
}
|
||||
|
||||
function win_formFactor(chassistype)
|
||||
|
|
@ -844,6 +855,7 @@ module.exports.isVM = function isVM()
|
|||
case 'Xen':
|
||||
case 'SeaBIOS':
|
||||
case 'EFI Development Kit II / OVMF':
|
||||
case 'Proxmox distribution of EDK II':
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -881,15 +893,10 @@ module.exports.isVM = function isVM()
|
|||
return (ret);
|
||||
};
|
||||
|
||||
if (process.platform == 'win32')
|
||||
{
|
||||
module.exports.volumes_promise = windows_volumes;
|
||||
}
|
||||
|
||||
// bios_date = BIOS->ReleaseDate
|
||||
// bios_vendor = BIOS->Manufacturer
|
||||
// bios_version = BIOS->SMBIOSBIOSVersion
|
||||
// board_name = BASEBOARD->Product = ioreg/board-id
|
||||
// board_serial = BASEBOARD->SerialNumber = ioreg/serial-number | ioreg/IOPlatformSerialNumber
|
||||
// board_vendor = BASEBOARD->Manufacturer = ioreg/manufacturer
|
||||
// board_version = BASEBOARD->Version
|
||||
// board_version = BASEBOARD->Version
|
||||
|
|
|
|||
|
|
@ -262,5 +262,29 @@
|
|||
"desktopNotify": "{0} távoli asztali munkamenetet indított.",
|
||||
"fileNotify": "{0} távoli fájlmunkamenetet indított.",
|
||||
"privacyBar": "Asztal megosztás aktív: {0} felhasználóval"
|
||||
},
|
||||
"ca": {
|
||||
"allow": "Permetre",
|
||||
"deny": "Negar",
|
||||
"autoAllowForFive": "Accepta automàticament totes les connexions durant els propers 5 minuts",
|
||||
"terminalConsent": "{0} sol·licitant accés al terminal remot. Accés garantit?",
|
||||
"desktopConsent": "{0} sol·licitant accés a l'escriptori remot. Accés garantit?",
|
||||
"fileConsent": "{0} sol·licitant accés remot al fitxer. Accés garantit?",
|
||||
"terminalNotify": "{0} va iniciar una sessió de terminal remota.",
|
||||
"desktopNotify": "{0} va iniciar una sessió d'escriptori remot.",
|
||||
"fileNotify": "{0} va iniciar una sessió de fitxer remota.",
|
||||
"privacyBar": "Compartint escriptori amb: {0}"
|
||||
},
|
||||
"uk": {
|
||||
"allow": "Дозволити",
|
||||
"deny": "Відмовити",
|
||||
"autoAllowForFive": "Автоматично приймати всі підключення впродовж наступних 5 хвилин",
|
||||
"terminalConsent": "{0} запитує доступ до віддаленого терміналу. Надати доступ?",
|
||||
"desktopConsent": "{0} запитує віддалений доступ до стільниці. Надати доступ?",
|
||||
"fileConsent": "{0} запитує віддалений доступ до файлу. Надати доступ?",
|
||||
"terminalNotify": "{0} почав сеанс віддаленого терміналу.",
|
||||
"desktopNotify": "{0} розпочав сеанс віддаленої стільниці.",
|
||||
"fileNotify": "{0} розпочав віддалений файловий сеанс.",
|
||||
"privacyBar": "Поширити доступ до стільниці з: {0}"
|
||||
}
|
||||
}
|
||||
|
|
@ -209,9 +209,13 @@ function macos_memUtilization()
|
|||
{
|
||||
var usage = lines[0].split(':')[1];
|
||||
var bdown = usage.split(',');
|
||||
|
||||
mem.MemTotal = parseInt(bdown[0].trim().split(' ')[0]);
|
||||
mem.MemFree = parseInt(bdown[1].trim().split(' ')[0]);
|
||||
if (bdown.length > 2){ // new style - PhysMem: 5750M used (1130M wired, 634M compressor), 1918M unused.
|
||||
mem.MemFree = parseInt(bdown[2].trim().split(' ')[0]);
|
||||
} else { // old style - PhysMem: 6683M used (1606M wired), 9699M unused.
|
||||
mem.MemFree = parseInt(bdown[1].trim().split(' ')[0]);
|
||||
}
|
||||
mem.MemUsed = parseInt(bdown[0].trim().split(' ')[0]);
|
||||
mem.MemTotal = (mem.MemFree + mem.MemUsed);
|
||||
mem.percentFree = ((mem.MemFree / mem.MemTotal) * 100);//.toFixed(2);
|
||||
mem.percentConsumed = (((mem.MemTotal - mem.MemFree) / mem.MemTotal) * 100);//.toFixed(2);
|
||||
return (mem);
|
||||
|
|
@ -225,25 +229,14 @@ function macos_memUtilization()
|
|||
function windows_thermals()
|
||||
{
|
||||
var ret = [];
|
||||
child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', '/namespace:\\\\root\\wmi', 'PATH', 'MSAcpi_ThermalZoneTemperature', 'get', 'CurrentTemperature,InstanceName', '/FORMAT:CSV']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
if(child.stdout.str.trim()!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
var keys = lines[0].trim().split(',');
|
||||
for (var i = 1; i < lines.length; ++i)
|
||||
{
|
||||
var obj = {};
|
||||
var tokens = lines[i].trim().split(',');
|
||||
for (var key = 0; key < keys.length; ++key)
|
||||
{
|
||||
if (tokens[key]) { obj[keys[key]] = key==1 ? ((parseFloat(tokens[key]) / 10) - 273.15).toFixed(2) : tokens[key]; }
|
||||
try {
|
||||
ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
|
||||
if (ret[0]) {
|
||||
for (var i = 0; i < ret.length; ++i) {
|
||||
ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
|
||||
}
|
||||
ret.push(obj);
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,28 +18,21 @@ var promise = require('promise');
|
|||
|
||||
function qfe()
|
||||
{
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'qfe', 'list', 'full', '/FORMAT:CSV']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
var keys = lines[0].split(',');
|
||||
var i, key;
|
||||
var tokens;
|
||||
var result = [];
|
||||
|
||||
for (i = 1; i < lines.length; ++i)
|
||||
{
|
||||
var obj = {};
|
||||
tokens = lines[i].split(',');
|
||||
for (key = 0; key < keys.length; ++key)
|
||||
{
|
||||
if (tokens[key]) { obj[keys[key]] = tokens[key]; }
|
||||
try {
|
||||
var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_QuickFixEngineering');
|
||||
if (tokens[0]){
|
||||
for (var index = 0; index < tokens.length; index++) {
|
||||
for (var key in tokens[index]) {
|
||||
if (key.startsWith('__')) delete tokens[index][key];
|
||||
}
|
||||
}
|
||||
return (tokens);
|
||||
} else {
|
||||
return ([]);
|
||||
}
|
||||
result.push(obj);
|
||||
} catch (ex) {
|
||||
return ([]);
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
function av()
|
||||
{
|
||||
|
|
@ -258,8 +251,12 @@ function defender(){
|
|||
ret.child.stdin.write('exit\r\n');
|
||||
ret.child.on('exit', function (c) {
|
||||
if (this.stdout.str == '') { this.promise._resolve({}); return; }
|
||||
var abc = JSON.parse(this.stdout.str.trim())
|
||||
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected });
|
||||
try {
|
||||
var abc = JSON.parse(this.stdout.str.trim());
|
||||
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected });
|
||||
} catch (ex) {
|
||||
this.promise._resolve({}); return;
|
||||
}
|
||||
});
|
||||
return (ret);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,17 +39,90 @@ function getVolumes()
|
|||
{
|
||||
ret[v[i].DeviceID] = trimObject(v[i]);
|
||||
}
|
||||
|
||||
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
||||
for (i in v)
|
||||
{
|
||||
var tmp = trimObject(v[i]);
|
||||
for (var k in tmp)
|
||||
try {
|
||||
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
||||
for (i in v)
|
||||
{
|
||||
ret[tmp.DeviceID][k] = tmp[k];
|
||||
var tmp = trimObject(v[i]);
|
||||
for (var k in tmp)
|
||||
{
|
||||
ret[tmp.DeviceID][k] = tmp[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
module.exports = { getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } } };
|
||||
function windows_volumes()
|
||||
{
|
||||
var promise = require('promise');
|
||||
var p1 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
||||
var ret = {};
|
||||
var values = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_LogicalDisk', ['DeviceID', 'VolumeName', 'FileSystem', 'Size', 'FreeSpace', 'DriveType']);
|
||||
if(values[0]){
|
||||
for (var i = 0; i < values.length; ++i) {
|
||||
var drive = values[i]['DeviceID'].slice(0,-1);
|
||||
ret[drive] = {
|
||||
name: (values[i]['VolumeName'] ? values[i]['VolumeName'] : ""),
|
||||
type: (values[i]['FileSystem'] ? values[i]['FileSystem'] : "Unknown"),
|
||||
size: (values[i]['Size'] ? values[i]['Size'] : 0),
|
||||
sizeremaining: (values[i]['FreeSpace'] ? values[i]['FreeSpace'] : 0),
|
||||
removable: (values[i]['DriveType'] == 2),
|
||||
cdrom: (values[i]['DriveType'] == 5)
|
||||
};
|
||||
}
|
||||
}
|
||||
try {
|
||||
values = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume', ['DriveLetter','ConversionStatus','ProtectionStatus']);
|
||||
if(values[0]){
|
||||
for (var i = 0; i < values.length; ++i) {
|
||||
var drive = values[i]['DriveLetter'].slice(0,-1);
|
||||
var statuses = {
|
||||
0: 'FullyDecrypted',
|
||||
1: 'FullyEncrypted',
|
||||
2: 'EncryptionInProgress',
|
||||
3: 'DecryptionInProgress',
|
||||
4: 'EncryptionPaused',
|
||||
5: 'DecryptionPaused'
|
||||
};
|
||||
ret[drive].volumeStatus = statuses.hasOwnProperty(values[i].ConversionStatus) ? statuses[values[i].ConversionStatus] : 'FullyDecrypted';
|
||||
ret[drive].protectionStatus = (values[i].ProtectionStatus == 0 ? 'Off' : (values[i].ProtectionStatus == 1 ? 'On' : 'Unknown'));
|
||||
try {
|
||||
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
|
||||
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ' + drive + ': -Type recoverypassword'], {});
|
||||
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
keychild.waitExit();
|
||||
var lines = keychild.stdout.str.trim().split('\r\n');
|
||||
for (var x = 0; x < lines.length; x++) { // Loop each line
|
||||
var abc = lines[x].trim();
|
||||
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
|
||||
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
|
||||
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
|
||||
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
|
||||
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
|
||||
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
|
||||
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
|
||||
var nextline = lines[x + 1].trim();
|
||||
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
|
||||
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
|
||||
foundIDMarkedLine = true;
|
||||
}else if (x + 1 < lines.length && nextline !== '') {
|
||||
password = nextline;
|
||||
foundMarkedLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret[drive].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
|
||||
ret[drive].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
|
||||
} catch(ex) { } // just carry on as we cant get bitlocker key
|
||||
}
|
||||
}
|
||||
p1._res(ret);
|
||||
} catch (ex) { p1._res(ret); } // just return volumes as cant get encryption/bitlocker
|
||||
return (p1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } },
|
||||
volumes_promise: windows_volumes
|
||||
};
|
||||
|
|
@ -485,8 +485,8 @@ function windows_execve(name, agentfilename, sessionid) {
|
|||
var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
|
||||
var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
|
||||
var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
|
||||
var arg2 = require('_GenericMarshal').CreateVariable('/C wmic service "' + name + '" call stopservice & "' + cwd + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
|
||||
' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
|
||||
var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + cwd + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
|
||||
' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -955,7 +955,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
});
|
||||
}
|
||||
|
||||
// Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL
|
||||
// Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL, 15 = Power on to PXE, 16 = Reset to PXE
|
||||
function performPowerAction(nodeid, action) {
|
||||
console.log('performPowerAction', nodeid, action);
|
||||
var devices = obj.amtDevices[nodeid];
|
||||
|
|
@ -970,7 +970,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset
|
||||
try { dev.amtstack.RequestPowerStateChange(action, performPowerActionResponse); } catch (ex) { }
|
||||
} else {
|
||||
// 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL
|
||||
// 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL, 15 = Power on to PXE, 16 = Reset to PXE
|
||||
dev.amtstack.BatchEnum(null, ['*AMT_BootSettingData'], performAdvancedPowerActionResponse);
|
||||
}
|
||||
}
|
||||
|
|
@ -1003,8 +1003,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Ready boot parameters
|
||||
bootSettingData['BIOSSetup'] = ((action >= 11) && (action <= 14));
|
||||
bootSettingData['UseSOL'] = ((action >= 13) && (action <= 14));
|
||||
if ((action == 11) || (action == 13)) { dev.powerAction = 2; } // Power on
|
||||
if ((action == 12) || (action == 14)) { dev.powerAction = 10; } // Reset
|
||||
if ((action == 11) || (action == 13) || (action == 15)) { dev.powerAction = 2; } // Power on
|
||||
if ((action == 12) || (action == 14) || (action == 16)) { dev.powerAction = 10; } // Reset
|
||||
|
||||
// Set boot parameters
|
||||
dev.amtstack.Put('AMT_BootSettingData', bootSettingData, function (stack, name, response, status, tag) {
|
||||
|
|
@ -1015,7 +1015,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
const dev = stack.dev;
|
||||
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
|
||||
// Set boot order
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder(null, function (stack, name, response, status) {
|
||||
var bootDevice = (action === 15 || action === 16) ? '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: Force PXE Boot</Selector></SelectorSet></ReferenceParameters>' : null;
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder(bootDevice, function (stack, name, response, status) {
|
||||
const dev = stack.dev;
|
||||
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
|
||||
// Perform power action
|
||||
|
|
@ -1068,7 +1069,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); delete dev.ocrfile; return; }
|
||||
|
||||
// Check if this Intel AMT device supports OCR
|
||||
if (responses['AMT_PublicKeyCertificate'].responses['ForceUEFIHTTPSBoot'] !== true) {
|
||||
if (responses['AMT_BootCapabilities'].response['ForceUEFIHTTPSBoot'] !== true) {
|
||||
dev.consoleMsg("This Intel AMT device does not support UEFI HTTPS boot (" + status + ")."); delete dev.ocrfile; return;
|
||||
}
|
||||
|
||||
|
|
@ -1098,11 +1099,14 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
|
||||
// Generate the one-time URL.
|
||||
var cookie = obj.parent.encodeCookie({ a: 'f', f: dev.ocrfile }, obj.parent.loginCookieEncryptionKey)
|
||||
var url = 'https://' + parent.webserver.certificates.AmtMpsName + ':' + ((parent.args.mpsaliasport != null) ? parent.args.mpsaliasport : parent.args.mpsport) + '/c/' + cookie + '.iso';
|
||||
var url = 'https://' + parent.webserver.certificates.AmtMpsName + ':' + ((parent.args.mpsaliasport != null) ? parent.args.mpsaliasport : parent.args.mpsport) + '/c/' + cookie + '.efi';
|
||||
delete dev.ocrfile;
|
||||
|
||||
// Generate the boot data for OCR with URL
|
||||
var r = response.Body;
|
||||
r['BIOSPause'] = false;
|
||||
r['BIOSSetup'] = false;
|
||||
r['EnforceSecureBoot'] = false;
|
||||
r['UefiBootParametersArray'] = Buffer.from(makeUefiBootParam(1, url) + makeUefiBootParam(20, 1, 1) + makeUefiBootParam(30, 0, 2), 'binary').toString('base64');
|
||||
r['UefiBootNumberOfParams'] = 3;
|
||||
r['BootMediaIndex'] = 0; // Do not use boot media index for One Click Recovery (OCR)
|
||||
|
|
@ -1123,8 +1127,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
dev.amtstack.SetBootConfigRole(1, function (stack, name, response, status) {
|
||||
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
|
||||
if (status != 200) { dev.consoleMsg("Failed to set boot config role (" + status + ")."); return; }
|
||||
var bootSource = 'Force OCR UEFI HTTPS Boot';
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder((bootSource == null) ? bootSource : '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: ' + bootSource + '</Selector></SelectorSet></ReferenceParameters>', function (stack, name, response, status) {
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder('<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: Force OCR UEFI HTTPS Boot</Selector></SelectorSet></ReferenceParameters>', function (stack, name, response, status) {
|
||||
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
|
||||
if (status != 200) { dev.consoleMsg("Failed to set boot config (" + status + ")."); return; }
|
||||
dev.amtstack.RequestPowerStateChange(10, function (stack, name, response, status) { // 10 = Reset, 2 = Power Up
|
||||
|
|
|
|||
14
apprelays.js
|
|
@ -717,9 +717,10 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i, header[i]); } // Set the headers if not blocked
|
||||
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), header[i]); } // Set the headers if not blocked
|
||||
}
|
||||
obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
// Dont set any Content-Security-Policy at all because some applications like Node-Red, access external websites from there javascript which would be forbidden by the below CSP
|
||||
//obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
//obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays
|
||||
}
|
||||
|
|
@ -983,6 +984,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
|
|||
if ((node == null) || (visible == false) || ((rights & MESHRIGHT_REMOTECONTROL) == 0)) { obj.close(); return; }
|
||||
if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_REMOTEVIEWONLY) != 0)) { obj.viewonly = true; }
|
||||
if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0)) { obj.limitedinput = true; }
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
obj.mtype = node.mtype; // Store the device group type
|
||||
obj.meshid = node.meshid; // Store the MeshID
|
||||
|
||||
|
|
@ -1315,7 +1317,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
// Check if we have SSH credentials for this device
|
||||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (node.ssh[obj.userid] == null) || (typeof node.ssh[obj.userid].u != 'string') || ((typeof node.ssh[obj.userid].p != 'string') && (typeof node.ssh[obj.userid].k != 'string'))) {
|
||||
// Send a request for SSH authentication
|
||||
try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { }
|
||||
|
|
@ -1363,7 +1365,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
obj.termSize = msg;
|
||||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
if (node.ssh != null) {
|
||||
obj.username = node.ssh.u;
|
||||
obj.privateKey = node.ssh.k;
|
||||
|
|
@ -1405,7 +1407,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if (obj.cookie == null) return; // obj has been cleaned up, just exit.
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) { parent.parent.debug('relay', 'SSH: Invalid device'); obj.close(); }
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
obj.nodeid = node._id; // Store the NodeID
|
||||
obj.meshid = node.meshid; // Store the MeshID
|
||||
obj.mtype = node.mtype; // Store the device group type
|
||||
|
|
@ -1738,6 +1740,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
|
|||
if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
|
||||
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
|
||||
if (obj.ws == null) return; // obj has been cleaned up, just exit.
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
|
||||
// Check permissions
|
||||
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights
|
||||
|
|
@ -2267,6 +2270,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
|
|||
if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
|
||||
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
|
||||
if (obj.ws == null) return; // obj has been cleaned up, just exit.
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
|
||||
// Check permissions
|
||||
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights
|
||||
|
|
|
|||
|
|
@ -1566,7 +1566,11 @@ function createAuthenticodeHandler(path) {
|
|||
options.protocol = timeServerUrl.protocol;
|
||||
options.hostname = timeServerUrl.hostname;
|
||||
options.path = timeServerUrl.pathname;
|
||||
options.port = ((timeServerUrl.port == '') ? 80 : parseInt(timeServerUrl.port));
|
||||
let http = require("http")
|
||||
if (options.protocol === "https:"){
|
||||
http = require("https")
|
||||
}
|
||||
options.port = ((timeServerUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(timeServerUrl.port));
|
||||
|
||||
if (options.proxy == null) {
|
||||
// No proxy needed
|
||||
|
|
@ -1584,7 +1588,7 @@ function createAuthenticodeHandler(path) {
|
|||
|
||||
// Set up the request
|
||||
var responseAccumulator = '';
|
||||
var req = require('http').request(options, function (res) {
|
||||
var req = http.request(options, function (res) {
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function (chunk) { responseAccumulator += chunk; });
|
||||
res.on('end', function () { func(null, responseAccumulator); });
|
||||
|
|
@ -1605,12 +1609,12 @@ function createAuthenticodeHandler(path) {
|
|||
proxyOptions.protocol = proxyUrl.protocol;
|
||||
proxyOptions.hostname = proxyUrl.hostname;
|
||||
proxyOptions.path = options.hostname + ':' + options.port;
|
||||
proxyOptions.port = ((proxyUrl.port == '') ? 80 : parseInt(proxyUrl.port));
|
||||
proxyOptions.port = ((proxyUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(proxyUrl.port));
|
||||
}
|
||||
|
||||
// Set up the proxy request
|
||||
var responseAccumulator = '';
|
||||
var req = require('http').request(proxyOptions);
|
||||
var req = http.request(proxyOptions);
|
||||
req.on('error', function (err) { func('' + err); });
|
||||
req.on('connect', function (res, socket, head) {
|
||||
// Make a request over the HTTP tunnel
|
||||
|
|
|
|||
|
|
@ -1049,6 +1049,7 @@ module.exports.CertificateOperations = function (parent) {
|
|||
config.domains[i].certs = r.dns[i];
|
||||
} else {
|
||||
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
|
||||
rcountmax++;
|
||||
}
|
||||
} else {
|
||||
// If the web certificate already exist, load it. Load both certificate and private key
|
||||
|
|
|
|||
28
common.js
|
|
@ -155,12 +155,12 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) {
|
|||
return obj;
|
||||
};
|
||||
|
||||
// Escape and unescape field names so there are no invalid characters for MongoDB
|
||||
module.exports.escapeFieldName = function (name) { if ((name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24'); };
|
||||
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
||||
// Escape and unescape field names so there are no invalid characters for MongoDB/NeDB ("$", ",", ".", see https://github.com/seald/nedb/tree/master?tab=readme-ov-file#inserting-documents)
|
||||
module.exports.escapeFieldName = function (name) { if ((name.indexOf(',') == -1) && (name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24').split(',').join('%2C'); };
|
||||
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2C').join(',').split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
||||
|
||||
// Escape all links, SSH and RDP usernames
|
||||
// This is required for databases like NeDB that don't accept "." as part of a field name.
|
||||
// This is required for databases like NeDB that don't accept "." or "," as part of a field name.
|
||||
module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
|
||||
module.exports.escapeLinksFieldName = function (docx) {
|
||||
var doc = Object.assign({}, docx);
|
||||
|
|
@ -334,6 +334,11 @@ module.exports.meshServerRightsArrayToNumber = function (val) {
|
|||
if (r == 'locked') { newAccRights |= 32; }
|
||||
if (r == 'nonewgroups') { newAccRights |= 64; }
|
||||
if (r == 'notools') { newAccRights |= 128; }
|
||||
if (r == 'usergroups') { newAccRights |= 256; }
|
||||
if (r == 'recordings') { newAccRights |= 512; }
|
||||
if (r == 'locksettings') { newAccRights |= 1024; }
|
||||
if (r == 'allevents') { newAccRights |= 2048; }
|
||||
if (r == 'nonewdevices') { newAccRights |= 4096; }
|
||||
}
|
||||
return newAccRights;
|
||||
}
|
||||
|
|
@ -399,4 +404,19 @@ module.exports.convertStrArray = function (object, split) {
|
|||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.uniqueArray = function (a) {
|
||||
var seen = {};
|
||||
var out = [];
|
||||
var len = a.length;
|
||||
var j = 0;
|
||||
for(var i = 0; i < len; i++) {
|
||||
var item = a[i];
|
||||
if(seen[item] !== 1) {
|
||||
seen[item] = 1;
|
||||
out[j++] = item;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
"archiver": "7.0.0",
|
||||
"body-parser": "1.20.2",
|
||||
"@seald-io/nedb": "4.0.4",
|
||||
"archiver": "7.0.1",
|
||||
"body-parser": "1.20.3",
|
||||
"cbor": "5.2.0",
|
||||
"compression": "1.7.4",
|
||||
"cookie-session": "2.0.0",
|
||||
"express": "4.18.2",
|
||||
"express-handlebars": "5.3.5",
|
||||
"express-ws": "4.0.0",
|
||||
"compression": "1.7.5",
|
||||
"cookie-session": "2.1.0",
|
||||
"express": "4.21.2",
|
||||
"express-handlebars": "7.1.3",
|
||||
"express-ws": "5.0.2",
|
||||
"ipcheck": "0.1.0",
|
||||
"minimist": "1.2.8",
|
||||
"multiparty": "4.2.3",
|
||||
"@yetzt/nedb": "1.8.0",
|
||||
"node-forge": "1.3.1",
|
||||
"ua-parser-js": "1.0.37",
|
||||
"ws": "8.14.2",
|
||||
"ua-parser-js": "1.0.39",
|
||||
"ws": "8.18.0",
|
||||
"yauzl": "2.10.0"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder
|
||||
|
||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||
COPY ./ /opt/meshcentral/meshcentral/
|
||||
|
|
@ -18,7 +18,7 @@ RUN if ! [ -z "$DISABLE_TRANSLATE" ] && [ "$DISABLE_TRANSLATE" != "yes" ] && [ "
|
|||
fi
|
||||
|
||||
# install translate/minify modules if need too
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral && npm install html-minifier jsdom minify-js; fi
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral && npm install html-minifier@4.0.0 jsdom@22.1.0 esprima@4.0.1; fi
|
||||
|
||||
# first extractall if need too
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral/translate && node translate.js extractall; fi
|
||||
|
|
@ -34,7 +34,7 @@ RUN rm -rf /opt/meshcentral/meshcentral/docker
|
|||
RUN rm -rf /opt/meshcentral/meshcentral/node_modules
|
||||
|
||||
|
||||
FROM --platform=$TARGETPLATFORM alpine:3.19
|
||||
FROM --platform=$TARGETPLATFORM alpine:3.21
|
||||
|
||||
#Add non-root user, add installation directories and assign proper permissions
|
||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||
|
|
@ -62,8 +62,8 @@ ENV MONGO_URL=""
|
|||
ENV HOSTNAME="localhost"
|
||||
ENV ALLOW_NEW_ACCOUNTS="true"
|
||||
ENV ALLOWPLUGINS="false"
|
||||
ENV LOCALSESSIONRECORDING="false"
|
||||
ENV MINIFY="true"
|
||||
ENV LOCALSESSIONRECORDING="true"
|
||||
ENV MINIFY="false"
|
||||
ENV WEBRTC="false"
|
||||
ENV IFRAME="false"
|
||||
ENV SESSION_KEY=""
|
||||
|
|
@ -83,12 +83,12 @@ COPY --from=builder /opt/meshcentral/meshcentral /opt/meshcentral/meshcentral
|
|||
COPY ./docker/startup.sh ./startup.sh
|
||||
COPY ./docker/config.json.template /opt/meshcentral/config.json.template
|
||||
|
||||
# install dependencies from package.json and nedb
|
||||
RUN cd meshcentral && npm install && npm install nedb
|
||||
# install dependencies from package.json
|
||||
RUN cd meshcentral && npm install
|
||||
|
||||
# NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart()
|
||||
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.13.0 saslprep@1.0.3; fi
|
||||
RUN if ! [ -z "$PREINSTALL_LIBS" ] && [ "$PREINSTALL_LIBS" == "true" ]; then cd meshcentral && npm install ssh2@1.15.0 semver@7.5.4 nodemailer@6.9.8 image-size@1.0.2 wildleek@2.0.0 otplib@10.2.3 yubikeyotp@0.2.0; fi
|
||||
RUN if ! [ -z "$PREINSTALL_LIBS" ] && [ "$PREINSTALL_LIBS" == "true" ]; then cd meshcentral && npm install ssh2@1.16.0 semver@7.5.4 nodemailer@6.9.15 image-size@1.1.1 wildleek@2.0.0 otplib@10.2.3 yubikeyotp@0.2.0; fi
|
||||
|
||||
EXPOSE 80 443 4433
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@
|
|||
"": {
|
||||
"_title": "MyServer",
|
||||
"_title2": "Servername",
|
||||
"minify": true,
|
||||
"minify": false,
|
||||
"NewAccounts": true,
|
||||
"localSessionRecording": false,
|
||||
"localSessionRecording": true,
|
||||
"_userNameIsEmail": true,
|
||||
"_certUrl": "my.reverse.proxy"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ else
|
|||
sed -i "s/\"NewAccounts\": true/\"NewAccounts\": $ALLOW_NEW_ACCOUNTS/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"enabled\": false/\"enabled\": $ALLOWPLUGINS/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"minify\": true/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"minify\": false/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}"
|
||||
if [ -z "$SESSION_KEY" ]; then
|
||||
|
|
|
|||
BIN
docs/docs/how-to-contribute/images/translation-msg-output.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
56
docs/docs/how-to-contribute/index.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Contribute to MeshCentral
|
||||
|
||||
## Contributing to MeshCentral via GitHub Pull Request
|
||||
|
||||
If you're looking to contribute beyond translations, such as updating documentation or enhancing the software by adding features or fixing bugs, the process involves several key steps:
|
||||
|
||||
1. **Fork the Repository:** Start by forking the [MeshCentral](https://github.com/Ylianst/MeshCentral) repository on GitHub. This creates a copy of the repository under your own GitHub account, allowing you to make changes without affecting the original project.
|
||||
|
||||
2. **Make Your Changes**
|
||||
- In your forked repository, create a new branch to keep your changes organized. This helps in managing different contributions separately.
|
||||
- Make the necessary changes in your repository. This could involve updating documentation files or modifying code to add new features or fix bugs.
|
||||
|
||||
3. **Review Your Changes:** Before submitting your work, carefully review the changes you’ve made. Check the "Files Changed" section on GitHub to ensure that all modifications are intended and correctly implemented.
|
||||
|
||||
4. **Submit a Pull Request**
|
||||
- Once your changes are ready and reviewed, submit a pull request (PR) from your branch to the `master` branch of the main MeshCentral repository.
|
||||
- When creating the pull request, provide a clear and detailed description of what changes have been made and why. This helps maintainers understand the purpose of your contributions.
|
||||
|
||||
5. **Wait for Review:** After submitting your pull request, wait for a project maintainer to review your contribution. Review time can vary depending on the complexity of the changes and the availability of the maintainers.
|
||||
|
||||
6. **Respond to Feedback:** The maintainer may request further modifications or provide feedback on your pull request. Be prepared to make additional changes based on their suggestions to ensure that your contribution meets the project’s standards and requirements.
|
||||
|
||||
7. **Final Steps:** Once your pull request is approved and merged by a maintainer, your contributions will be incorporated into the MeshCentral project. Congratulations, and thank you for helping improve MeshCentral!
|
||||
|
||||
---
|
||||
|
||||
## Contribute to MeshCentral's Multilingual Support
|
||||
|
||||
To make MeshCentral multilingual, your contributions are crucial. Follow these steps to translate the interface into various languages.
|
||||
|
||||
1. **Remove Local Translations:** Delete `translate.json` from your `meshcentral-data` folder. This file contains your local copy of translations, which may become outdated as new features and texts are added.
|
||||
|
||||
2. **Access MeshCentral:** Ensure you are logged into MeshCentral.
|
||||
3. **Open Translation Tool:** Visit `https://YOURMESHCENTRALSERVER.COM/translator.htm` to access the translation interface.
|
||||
4. **Choose a Language:** Select the language you wish to translate from the list provided.
|
||||
|
||||
5. **Translate Text:** Use the search function or scroll through the list to find text segments you want to translate. Utilize the "show no translations only" checkbox to filter untranslated texts.
|
||||
6. **Enter Translations:** For each text segment, enter your translation in the bottom box (not the top one) and click `SET (F1)`.
|
||||
7. **Repeat Translation:** Continue translating by repeating steps 5 and 6 for other texts as desired.
|
||||
|
||||
8. **Save and Apply Translations**
|
||||
- Click `SAVE TO SERVER (F3)` to save your translations to `meshcentral-data/translate.json` locally in your MeshCentral server.
|
||||
- Optionally, click `SAVE TO FILE (F4)` to download the `translate.json` file for offline review or sharing.
|
||||
|
||||
9. **Deploy Translations:** Click `TRANSLATE SERVER` and allow some time for the process to complete (approximately 5-15 minutes depending on server specifications). This command line output will indicate when the translation is complete.
|
||||

|
||||
|
||||
10. **Finalize Changes:** It’s crucial to restart MeshCentral to ensure that the translated files are picked up correctly.
|
||||
11. **Share your translations:** Once a language translation is complete, take the latest `translation.json` and share it by emailing it to the maintainer (Ylianst, `ylianst@gmail.com`) or by submitting it to the MeshCentral GitHub repository via a pull request.
|
||||
|
||||
---
|
||||
|
||||
#### Additional Information:
|
||||
- If you make any changes to `default.handlebars`, run the translate server to propagate these modifications to the language-specific handlebar files located in `node_modules/meshcentral/views/translations`.
|
||||
|
||||
By following these steps, you help MeshCentral support any language you choose, making it more accessible worldwide. By sharing your translations with us, you also help make these languages available to other users, improving the community and extending the software's reach.
|
||||
|
|
@ -12,7 +12,7 @@ For more information, [visit MeshCentral.com](https://www.meshcentral.com/).
|
|||
|
||||
[Reddit](https://www.reddit.com/r/MeshCentral/)
|
||||
|
||||
[Twitter](https://twitter.com/MeshCentral)
|
||||
[BlueSky](https://bsky.app/profile/meshcentral.bsky.social)
|
||||
|
||||
[BlogSpot](https://meshcentral2.blogspot.com/)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ You can run the MeshCentral Server with --help to get options for background ins
|
|||
|
||||
Once you get MeshCentral installed, the first user account that is created will be the server administrator. So, don't delay and navigate to the login page and create a new account. You can then start using your server right away. A lot of the fun with MeshCentral is the 100's of configuration options that are available in the config.json file. You can put your own branding on the web pages, setup a SMTP email server, SMS services and much more.
|
||||
|
||||
You can look [here for simple config.json](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config.json), [here for a more advanced configuration](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config-advanced.json) and [here for all possible configuration options](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json). You can also take a look at the [MeshCentral User's Guide](https://meshcentral.com/docs/MeshCentral2InstallGuide.pdf) and [tutorial videos](https://www.youtube.com/@MeshCentral/videos) for additional help.
|
||||
You can look [here for simple config.json](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config.json), [here for a more advanced configuration](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config-advanced.json) and [here for all possible configuration options](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json). You can also take a look at the [tutorial videos](https://www.youtube.com/@MeshCentral/videos) for additional help.
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,34 @@ docker pull ghcr.io/ylianst/meshcentral:master
|
|||
!!!warning
|
||||
Do not use the built in mesh update function. Update docker the docker way.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```
|
||||
version: '3'
|
||||
services:
|
||||
meshcentral:
|
||||
restart: unless-stopped # always restart the container unless you stop it
|
||||
image: ghcr.io/ylianst/meshcentral:1.1.27 # 1.1.27 is a version number OR use master for the master branch of bug fixes
|
||||
ports:
|
||||
- 80:80 # HTTP
|
||||
- 443:443 # HTTPS
|
||||
- 4433:4433 # AMT (Optional)
|
||||
volumes:
|
||||
- data:/opt/meshcentral/meshcentral-data # config.json and other important files live here
|
||||
- user_files:/opt/meshcentral/meshcentral-files # where file uploads for users live
|
||||
- backup:/opt/meshcentral/meshcentral-backups # location for the meshcentral backups - this should be mounted to an external storage
|
||||
- web:/opt/meshcentral/meshcentral-web # location for site customization files
|
||||
volumes:
|
||||
data:
|
||||
driver: local
|
||||
user_files:
|
||||
driver: local
|
||||
backup:
|
||||
driver: local
|
||||
web:
|
||||
driver: local
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
For some who want to skip this document entirely, there are quick install scripts that will get a MeshCentral2 instance up and running on Linux in a few minutes. These scripts will pretty much do what this document explains very rapidly. Right now, there are two such scripts available:
|
||||
|
|
@ -51,6 +79,12 @@ chmod 755 mc-azure-ubuntu1804.sh
|
|||
|
||||
In this situation, port 3389 will be used to receive Intel AMT CIRA connections instead of port 4433. After these scripts are run, try accessing the server using a browser. MeshCentral will take a minute or two to create certificates after that, the server will be up. The first account to be created will be the site administrator – so don’t delay and create an account right away. Once running, move on to the MeshCentral’s user’s guide to configure your new server.
|
||||
|
||||
### Elestio
|
||||
|
||||
You can deploy MeshCentral on Elestio using one-click deployment. Elestio handles version updates, maintenance, securtiy, backups, etc. Additionally, Elestio supports MeshCentral by providing revenue share so go ahead and click below to deploy and start using.
|
||||
|
||||
[](https://elest.io/open-source/meshcentral)
|
||||
|
||||
## Server Security - Adding Crowdsec
|
||||
|
||||
MeshCentral has built-in support for a CrowdSec bouncer. This allows MeshCentral to get threat signals from the community and block or CAPTCHA requests coming from known bad IP addresses.
|
||||
|
|
@ -895,7 +929,7 @@ The last line will run MeshCentral manually and allow it to install any missing
|
|||
|
||||
```
|
||||
sudo chown -R meshcentral:meshcentral /opt/meshcentral
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-*
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-*
|
||||
```
|
||||
|
||||
To make this work, you will need to make MeshCentral work with MongoDB because the /meshcentral-data folder will be read-only. In addition, MeshCentral will not be able to update itself since the account does not have write access to the /node_modules files, so the update will have to be manual. First used systemctl to stop the MeshCentral server process, than use this:
|
||||
|
|
@ -912,7 +946,7 @@ This will perform the update to the latest server on NPM and re-set the permissi
|
|||
MeshCentral allows users to upload and download files stores in the server’s `meshcentral-files` folder. In an increased security setup, we still want the server to be able to read and write files to this folder and we can allow this with:
|
||||
|
||||
```
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-files
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-files
|
||||
```
|
||||
|
||||
If you plan on using the increased security installation along with MeshCentral built-in Let’s Encrypt support you will need to type the following commands to make the `letsencrypt` folder in `meshcentral-data` writable.
|
||||
|
|
@ -920,7 +954,7 @@ If you plan on using the increased security installation along with MeshCentral
|
|||
```
|
||||
sudo mkdir /opt/meshcentral/meshcentral-data
|
||||
sudo mkdir /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
```
|
||||
|
||||
This will allow the server to get and periodically update its Let’s Encrypt certificate. If this is not done, the server will generate an `ACCES: permission denied` exception.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ For link invitation web page customization:
|
|||

|
||||
|
||||
### Email Invitation
|
||||
This option will show up if you have a SMTP email server setup with MeshCentral.
|
||||
This option will show up if you have an SMTP email server set up with MeshCentral.
|
||||
|
||||
For invitation email customization:
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ When doing sign/unsign, you can also change resource properties of the generated
|
|||
|
||||
## Automatic Agent Code Signing
|
||||
|
||||
If you want to self-sign the mesh agent so you can whitelist the software in your AV, and lock it to your server and organization.
|
||||
If you want to self-sign the mesh agent so you can whitelist the software in your AV, as well as lock it to your server and organization:
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/qMAestNgCwc" frameborder="0" allowfullscreen></iframe>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Customization
|
||||
|
||||
Whitelabeling your MeshCentral installation to personalize it to your companies brand, as well as having your own terms of use is one of the first things many people do after installation.
|
||||
Whitelabeling your MeshCentral installation to personalize it to your company's brand, as well as having your own terms of use is one of the first things many people do after installation.
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/xUZ1w9RSKpQ" frameborder="0" allowfullscreen></iframe>
|
||||
|
|
@ -8,7 +8,7 @@ Whitelabeling your MeshCentral installation to personalize it to your companies
|
|||
|
||||
## Web Branding
|
||||
|
||||
You can put you own logo on the top of the web page. To get started, get the file “logoback.png” from the folder “node_modules/meshcentral/public/images” and copy it to your “meshcentral-data” folder. In this example, we will change the name of the file “logoback.png” to “title-mycompany.png”. Then use any image editor to change the image and place your logo.
|
||||
You can put your own logo on the top of the web page. To get started, get the file “logoback.png” from the folder “node_modules/meshcentral/public/images” and copy it to your “meshcentral-data” folder. In this example, we will change the name of the file “logoback.png” to “title-mycompany.png”. Then use any image editor to change the image and place your logo.
|
||||
|
||||

|
||||
|
||||
|
|
@ -33,7 +33,7 @@ Once done, edit the config.json file and set one or all of the following values:
|
|||
},
|
||||
```
|
||||
|
||||
This will set the title and sub-title text to empty and set the background image to the new title picture file. You can now restart the serve and take a look at the web page. Both the desktop and mobile sites will change.
|
||||
This will set the title and sub-title text to empty and set the background image to the new title picture file. You can now restart the server and take a look at the web page. Both the desktop and mobile sites will change.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Make sure you understand how MeshCentral works with your browser using chrome de
|
|||
|
||||
### Understanding node and paths
|
||||
|
||||
Note that when running MeshCentral, you should always run like from the path that is parent to node_modules, so you do this:
|
||||
Note that when running MeshCentral, you should always run from the path that is parent to node_modules, so you do this:
|
||||
|
||||
```
|
||||
cd C:\Program Files\Open Source\MeshCentral
|
||||
|
|
@ -39,7 +39,7 @@ node meshcentral
|
|||
|
||||
The problem with the second command is that NPM may install missing modules in the incorrect location.
|
||||
|
||||
Also, in general I recommend not using the MeshCentral MSI Installer and just install manually unless you are very much scared of the command prompt. Anyone that knows about bit about the shell should install MeshCentral like this:
|
||||
Also, in general I recommend not using the MeshCentral MSI Installer and just install manually unless you are very scared of the command prompt. Anyone that knows a bit about the shell should install MeshCentral like this:
|
||||
|
||||
```
|
||||
mkdir c:\meshcentral
|
||||
|
|
@ -50,11 +50,11 @@ node node_modules\meshcentral
|
|||
node node_modules\meshcentral --install
|
||||
```
|
||||
|
||||
This way, just have a lot more control over what is going on. Just my opinion, the MSI installer basically does the same thing and installs NodeJS for you.
|
||||
This way, you have a lot more control over what is going on. In my opinion, the MSI installer basically does the same thing and installs NodeJS for you.
|
||||
|
||||
### Unable to update server
|
||||
|
||||
Generally the problem is that MeshCentral can't find the npm tool and so, can't run it to see if there is a new version. You can fix this by setting the path to npm in the config.json like this:
|
||||
Generally the problem is that MeshCentral can't find the npm tool and therefore, can't run it to see if there is a new version. You can fix this by setting the path to npm in the config.json like this:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -86,14 +86,59 @@ node node_modules/meshcentral --stop
|
|||
|
||||
### Port Troubleshooting on server
|
||||
|
||||
If you're getting a `port 4433 is not available` error, this is because someone else is using this port, very likely another instance of MeshCentral. If your MeshCentral server is bound to ports 81/444 MeshCentral could not get port 80/443 and got the next available ones.
|
||||
If you're getting a `port 4433 is not available` error, this is because another process is using this port, very likely another instance of MeshCentral. If your MeshCentral server is bound to ports 81/444 MeshCentral could not get port 80/443 and got the next available ones.
|
||||
|
||||
In general the problem is that you are running two MeshCentral instances at the same time. Probably one as a background Windows Service and one in the command line. Which ever instance can grab port 4433 will have a running MPS and CIRA should work, but the second instance will not have port 4433 and CIRA will not work.
|
||||
|
||||
### Running Meshcentral server in debug mode
|
||||
|
||||
Debug more will cause MeshCentral to output a lot of debug messages to the console. To display all debug messages, run MeshCentral like this:
|
||||
|
||||
```bash
|
||||
node node_modules/meshcentral --debug
|
||||
```
|
||||
|
||||
A more practical way to run the debug command it to specify what messages you want printed out using a comma seperated list, for example:
|
||||
|
||||
```bash
|
||||
node node_modules/meshcentral --debug web,amt,mps
|
||||
```
|
||||
|
||||
Here is the list of all debug options:
|
||||
|
||||
```
|
||||
cookie - Cookie encoder
|
||||
dispatch - Message Dispatcher
|
||||
main - Main Server Messages
|
||||
peer - MeshCentral Server Peering
|
||||
agent - MeshAgent traffic
|
||||
agentupdate - MeshAgent update
|
||||
cert - Server Certificate
|
||||
db - Server Database
|
||||
email - Email/SMS/Push Traffic
|
||||
web - Web Server
|
||||
webrequest - Web Server Requests
|
||||
relay - Web Socket Relay
|
||||
httpheaders - Web Server HTTP Headers
|
||||
authlog - User Authentication Log
|
||||
amt - Intel AMT
|
||||
webrelay - Connection Relay
|
||||
mps - CIRA Server
|
||||
mpscmd - CIRA Server Commands
|
||||
```
|
||||
|
||||
You can also specify the `debug` option in the config.json file in the `settings` section. For example:
|
||||
|
||||
```
|
||||
"settings": {
|
||||
"debug": "web,amt,mps"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Enabling trace in your browser Dev Tools
|
||||
|
||||
`Trace=1` as a parameter in chrome dev tools for debugging
|
||||
|
||||
You can enable browser console tracing by adding `trace=1` as a parameter to the URL of the MeshCentral main web page. For example `https://myserver.com/?trace=1`. Once present, open the browser's console window to see all web client tracing messages.
|
||||
|
||||
To log all database queries, change log_statement in /etc/postgresql/13/main/postgresql.conf
|
||||
|
||||
|
|
|
|||
|
|
@ -67,17 +67,17 @@ To be able to control keyboard and mouse
|
|||
|
||||

|
||||
|
||||
## I'm using cloudflare and I'm getting a black screen but the mouse moves?
|
||||
## I'm using CloudFlare and I'm getting a black screen but the mouse moves?
|
||||
|
||||
If you are using cloudflare for your DNS hosting and your remote screen is black, DONT PANIC!
|
||||
If you are using CloudFlare for your DNS hosting and your remote screen is black, DONT PANIC!
|
||||
|
||||
Unfortunately, MeshCentral currently doesnt work with CloudFlares Proxy DNS Mode, so the remote agent devices have to connect directly to your MeshCentral Server instead of being proxied!
|
||||
Unfortunately, MeshCentral doesn't always work with CloudFlare's Proxy DNS Mode.
|
||||
|
||||
The fix is to simply disable the 'Proxy Status' to OFF inside your DNS A Record.
|
||||
The fix is to simply set the 'Proxy Status' to OFF inside your DNS A Record, within the CloudFlare control panel.
|
||||
|
||||
Simple follow the steps [here](https://developers.cloudflare.com/fundamentals/setup/manage-domains/pause-cloudflare/#disable-proxy-on-dns-records)
|
||||
Simply follow the steps [here](https://developers.cloudflare.com/fundamentals/setup/manage-domains/pause-cloudflare/#disable-proxy-on-dns-records)
|
||||
|
||||
Once done, open your firewall for the `port` and `agentPort` ports of where your meshcentral is hosted, then restart your MeshCentral Server
|
||||
|
||||
The is currently a PINNED GitHub issue about this [here](https://github.com/Ylianst/MeshCentral/issues/5302)
|
||||
There is currently a PINNED GitHub issue about this [here](https://github.com/Ylianst/MeshCentral/issues/5302)
|
||||
|
||||
|
|
|
|||
BIN
docs/docs/meshcentral/images/In-production.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/docs/meshcentral/images/OAuth-Internal-External.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/docs/meshcentral/images/gc-newproject.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthconsent.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthconsent2.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthcredentials.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthscopes.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/docs/meshcentral/images/gc-playground.webp
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
# Meshcentral2 Guide
|
||||
# MeshCentral Guide
|
||||
|
||||
[MeshCentral2 Guide](https://meshcentral.com/docs/MeshCentral2UserGuide.pdf)
|
||||
[MeshCentral Guide](https://meshcentral.com/docs/MeshCentral2UserGuide.pdf)
|
||||
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/docs/MeshCmdUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral User's Guide v0.2.9.odt?raw=true)
|
||||
|
||||
|
|
@ -305,6 +305,120 @@ When the MongoDB is setup for the first time, a unique identifier is generated a
|
|||
|
||||
Once peered, all of the servers should act like one single host, no matter which server the user(s) are connected to.
|
||||
|
||||
## Email Setup
|
||||
|
||||
We highly recommend the use of an email server (SMTP) because we could allow MeshCentral to verify user account’s email address by sending a confirmation request to the user to complete the account registration and for password recovery, should a user forget account password as illustrated below
|
||||
|
||||
A verification email is sent when a new account is created or if the user requests it in the “My Account” tab.
|
||||
|
||||

|
||||
|
||||
The password recovery flow when “Reset Account” is triggered at the login page.
|
||||
|
||||

|
||||
|
||||
Both account verification and password recovery are triggered automatically once SMTP mail server configuration is included into the config.json file.
|
||||
|
||||
#### SMTP: User/Pass
|
||||
##### Normal Server
|
||||
|
||||
Update the config.json with “smtp” section as shown below and restart the server.
|
||||
|
||||
```json
|
||||
{
|
||||
"smtp": {
|
||||
"host": "smtp.server.com",
|
||||
"port": 25,
|
||||
"from": "myaddress@server.com",
|
||||
"user": "myaddress@server.com", # Optional
|
||||
"pass": "mypassword", # Optional
|
||||
"tls": false # Optional, default false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Please map the host, port values to connect to the right host that provides this SMTP service. For “from” value, administrators may put something like donotreply@server.com, but often times it needs to be a valid address since SMTP server will not send out messages with an invalid reply address.
|
||||
|
||||
Some SMTP servers will require a valid username and password to login to the mail server. This is to prevent unauthorized e-mail correspondence. TLS option can be set to ‘true’ if the SMTP server requires TLS.
|
||||
|
||||
##### Gmail
|
||||
|
||||
One option is to configure MeshCentral work with Google Gmail by setting “host” with smtp.gmail.com, and “port” with 587. In the config.json file, use user’s Gmail address for both “from” and “user” and Gmail password in the “pass” value. You will also need to enable “Less secure app access” in for this Google account. It’s in the account settings, security section:
|
||||
|
||||

|
||||
|
||||
If a Google account is setup with 2-factor authentication, the option to allow less secure applications not be available. Because the Google account password is in the MeshCentral config.json file and that strong authentication can’t be used, it’s preferable to use a dedicated Google account for MeshCentral email.
|
||||
|
||||
#### SMTP: OAuth Authentication
|
||||
##### Gmail
|
||||
|
||||
Google has announced that less secure app access will be phased out. For Google Workspace or G-Suite accounts, the following process can be used to allow OAuth2 based authentication with Google's SMTP server. It is likely a very similar process for regular Gmail accounts.
|
||||
|
||||
Start by visiting the Google API console:
|
||||
|
||||
https://console.developers.google.com/
|
||||
|
||||
First, you will create a new project. Name it something unique in case you need to create more in the future. In this example, I've named the project "MeshCentral"
|
||||
|
||||

|
||||
|
||||
Click on the "OAuth Consent Screen" link, Under "APIs and Services" from the left hand menu:
|
||||
|
||||

|
||||
|
||||
If you have a Google Workspace account, you will have the option to choose "Internal" application and skip the next steps. If not, you will be required to provide Google with information about why you want access, as well as verifying domain ownership.
|
||||
|
||||

|
||||
|
||||
Add the Gmail address under which you have created this project to the fields labelled ‘User support email’ and ‘Developer contact information’ so that you will be allowed for authentication. After that, you will want to add a scope for your app, so that your token is valid for gmail:
|
||||
|
||||

|
||||
|
||||
Once this is complete, the next step will be to add credentials.
|
||||
|
||||

|
||||
|
||||
Choose OAuth Client
|
||||
|
||||
You will obtain a Client ID and a Client secret once you've completed the process. Be sure to store the secret immediately, as you won't be able to retreive it after you've dismissed the window.
|
||||
|
||||
Next, you will need to visit the Google OAuth Playground:
|
||||
|
||||
https://developers.google.com/oauthplayground
|
||||
|
||||

|
||||
|
||||
Enter your Client ID and secret from the last step. On the left side of the page, you should now see a text box that allows you to add your own scopes. Enter https://mail.google.com and click Authorize API.
|
||||
|
||||
You will need to follow the instructions provided to finish the authorization process. Once that is complete, you should receive a refresh token. The refresh token, Client ID and Client Secret are the final items we need to complete the SMTP section of our config.json. It should now look something like this:
|
||||
|
||||
```
|
||||
"smtp": {
|
||||
"host": "smtp.gmail.com",
|
||||
"port": 587,
|
||||
"from": "my@googleaccount.com",
|
||||
"auth": {
|
||||
"clientId": "<YOUR-CLIENT-ID>",
|
||||
"clientSecret": "<YOUR-CLIENT-SECRET>",
|
||||
"refreshToken": "<YOUR-REFRESH-TOKEN>"
|
||||
},
|
||||
"user": "noreply@authorizedgooglealias.com",
|
||||
"emailDelaySeconds": 10,
|
||||
"tls": false,
|
||||
"verifyEmail": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Regardless of what SMTP account is used, MeshCentral will perform a test connection to make sure the server if working as expected when starting. Hence, the user will be notified if Meshcentral and SMTP server has been configured correctly as shown below.
|
||||
|
||||

|
||||
|
||||
After successfully configuring the Gmail SMTP server, switch the OAuth 'Publishing Status' from `Testing` to `In Production`. This step prevents the need for frequent refresh token generation. Verification of your project isn't required to make this change.
|
||||
|
||||

|
||||
|
||||
|
||||
## Database
|
||||
|
||||
A critical component of MeshCentral is the database. The database stores all of the user account information, groups and node data, historical power and event, etc. By default MeshCentral uses NeDB (https://github.com/louischatriot/nedb) that is written entirely in NodeJS and is setup automatically when MeshCentral is installed with the npm tool. The file “meshcentral.db” will be created in the “meshcentral-data” folder when MeshCentral is first launched. This database works well for small deployments scenarios.
|
||||
|
|
@ -438,14 +552,14 @@ To make this happen, we will be using the following command line options from Me
|
|||
| --dblistconfigfiles | List the names and size of all configuration files in the database. |
|
||||
| --dbshowconfigfile (filename) | Show the content of a specified filename from the database. --configkey is required. |
|
||||
| --dbdeleteconfigfiles | Delete all configuration files from the database. |
|
||||
| --dbpushconfigfiles (*) or (folder path) | Push a set of configuration files into the database, removing any existing files in the process. When * is specified, the “meshcentral-data” folder up pushed into the database. --configkey is required. |
|
||||
| --dbpushconfigfiles '*' or (folder path) | Push a set of configuration files into the database, removing any existing files in the process. When * is specified, the “meshcentral-data” folder up pushed into the database. --configkey is required. |
|
||||
| --dbpullconfigfiles (folder path) | Get all of the configuration files from the database and place them in the specified folder. Files in the target folder may be overwritten. --configkey is required. |
|
||||
| --loadconfigfromdb (key) | Runs MeshCentral server using the configuration files found in the database. The configkey may be specified with this command or --configkey can be used. |
|
||||
|
||||
Once we have MeshCentral running as expected using the “meshcentral-data” folder, we can simply push that configuration into the database and run using the database alone like this:
|
||||
|
||||
```
|
||||
node ./node_modules/meshcentral --dbpushconfigfiles * --configkey mypassword
|
||||
node ./node_modules/meshcentral --dbpushconfigfiles '*' --configkey mypassword
|
||||
|
||||
node ./node_modules/meshcentral --loadconfigfromdb mypassword --mongodb "mongodb://127.0.0.1:27017/meshcentral"
|
||||
```
|
||||
|
|
@ -609,46 +723,6 @@ All the lines that start with a number or `:` will be used, everything else is i
|
|||
95.85.81.0/24
|
||||
```
|
||||
|
||||
## Email Setup
|
||||
|
||||
We highly recommend the use of an email server (SMTP) because we could allow MeshCentral to verify user account’s email address by sending a confirmation request to the user to complete the account registration and for password recovery, should a user forget account password as illustrated below
|
||||
|
||||
A verification email is sent when a new account is created or if the user requests it in the “My Account” tab.
|
||||
|
||||

|
||||
|
||||
The password recovery flow when “Reset Account” is triggered at the login page.
|
||||
|
||||

|
||||
|
||||
Both account verification and password recovery are triggered automatically once SMTP mail server configuration is included into the config.json file. Update the config.json with “smtp” section as shown below and restart the server.
|
||||
|
||||
```json
|
||||
{
|
||||
"smtp": {
|
||||
"host": "smtp.server.com",
|
||||
"port": 25,
|
||||
"from": "myaddress@server.com",
|
||||
"user": "myaddress@server.com", Optional
|
||||
"pass": "mypassword", Optional
|
||||
"tls": false Optional, default false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Please map the host, port values to connect to the right host that provides this SMTP service. For “from” value, administrators may put something like donotreply@server.com, but often times it needs to be a valid address since SMTP server will not send out messages with an invalid reply address.
|
||||
|
||||
Some SMTP servers will require a valid username and password to login to the mail server. This is to prevent unauthorized e-mail correspondence. TLS option can be set to ‘true’ if the SMTP server requires TLS.
|
||||
|
||||
One option is to configure MeshCentral work with Google Gmail* by setting “host” with smtp.gmail.com, and “port” with 587. In the config.json file, use user’s Gmail* address for both “from” and “user” and Gmail* password in the “pass” value. You will also need to enable “Less secure app access” in for this Google account. It’s in the account settings, security section:
|
||||
|
||||

|
||||
|
||||
If a Google account is setup with 2-factor authentication, the option to allow less secure applications not be available. Because the Google account password is in the MeshCentral config.json file and that strong authentication can’t be used, it’s preferable to use a dedicated Google account for MeshCentral email.
|
||||
|
||||
Regardless of what SMTP account is used, MeshCentral will perform a test connection to make sure the server if working as expected when starting. Hence, the user will be notified if Meshcentral and SMTP server has been configured correctly as shown below.
|
||||
|
||||

|
||||
|
||||
## Embedding MeshCentral
|
||||
|
||||
|
|
@ -1204,6 +1278,8 @@ And taking authentication to the next step is removing the login page entirely.
|
|||
<iframe width="320" height="180" src="https://www.youtube.com/embed/-WKY8Wy0Huk" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
You can also setup [Duo 2FA](https://github.com/Ylianst/MeshCentral/blob/master/docs/docs/meshcentral/security.md#duo-2fa-setup) which is a commertial offering.
|
||||
|
||||
## Server Backup & Restore
|
||||
|
||||
It’s very important that the server be backed up regularly and that a backup be kept offsite. Luckily, performing a full backup of the MeshCentral server is generally easy to do. For all installations make sure to back up the following two folders and all sub-folders.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,23 @@ OpenID Connect allows clients of all types, including Web-based, mobile, and Jav
|
|||
|
||||
That description was straight from [OpenID Connect Documentation](https://openid.net/connect/), but basically, OAuth2 is the foundation upon which OpenID Connect was built, allowing for wide ranging compatability and interconnection. OpenID Connect appends the secure user *authentication* OAuth2 is known for, with user *authorization* by allowing the request of additional *scopes* that provide additional *claims* or access to API's in an easily expandable way.
|
||||
|
||||
### Annotations
|
||||
|
||||
#### Own IDP, CA and Docker
|
||||
|
||||
If you operate your own identity provider, your own certification authority and MeshCentral via Docker, it is necessary to provide the complete certificate chain, otherwise NodeJS (in particular the openid-client module) will refuse the connection to the IDP server.
|
||||
|
||||
The following errors can be found in the log file:
|
||||
> OIDC: Discovery failed.
|
||||
|
||||
> UNABLE_TO_GET_ISSUER_CERT_LOCALLY
|
||||
|
||||
To solve this problem, the certificate chain in PEM format must be placed in the data directory and the following entry must be added to the docker-compose.yml file in the “environment” section:
|
||||
```
|
||||
environment:
|
||||
- NODE_EXTRA_CA_CERTS=/opt/meshcentral/meshcentral-data/chain.pem
|
||||
```
|
||||
|
||||
## Basic Config
|
||||
|
||||
### *Introduction*
|
||||
|
|
@ -104,13 +121,13 @@ There are plenty of options at your disposal if you need them. In fact, you can
|
|||
"issuer": "https://sso.your.domain",
|
||||
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
|
||||
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
|
||||
"endsession_endpoint": "https://sso.your.domain/logout",
|
||||
"end_session_endpoint": "https://sso.your.domain/logout",
|
||||
"jwks_uri": "https://sso.your.domain/jwks-uri"
|
||||
},
|
||||
"client": {
|
||||
"client_id": "110d5612-0822-4449-a057-8a0dbe26eca5",
|
||||
"client_secret": "4TqST46K53o3Z2Q88p39YwR6YwJb7Cka",
|
||||
"redirect_uri": "https://mesh.your.domain/oauth2/oidc/redirect",
|
||||
"redirect_uri": "https://mesh.your.domain/auth-oidc-callback",
|
||||
"post_logout_redirect_uri": "https://mesh.your.domain/login",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"response_types": "authorization_code"
|
||||
|
|
@ -161,14 +178,14 @@ In the advanced example config above, did you notice that the issuer property ha
|
|||
"issuer": "https://sso.your.domain",
|
||||
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
|
||||
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
|
||||
"endsession_endpoint": "https://sso.your.domain/logout",
|
||||
"end_session_endpoint": "https://sso.your.domain/logout",
|
||||
"jwks_uri": "https://sso.your.domain/jwks-uri"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Required and Commonly Used Configs*
|
||||
|
||||
The `issuer` property in the `issuer` object is the only one required, and its only required if you aren't using a preset. Besides the issuer, these are mostly options related to the endpoints and their configuration. The schema below looks intimidating but it comes down to being able to support any IdP. Setting the issuer, and endsession_endpoint are the two main ones you want to setup.
|
||||
The `issuer` property in the `issuer` object is the only one required, and its only required if you aren't using a preset. Besides the issuer, these are mostly options related to the endpoints and their configuration. The schema below looks intimidating but it comes down to being able to support any IdP. Setting the issuer, and end_session_endpoint are the two main ones you want to setup.
|
||||
|
||||
#### *Schema*
|
||||
|
||||
|
|
@ -236,7 +253,7 @@ There are just about as many option as possible here since openid-client also pr
|
|||
"client": {
|
||||
"client_id": "00b3875c-8d82-4238-a8ef-25303fa7f9f2",
|
||||
"client_secret": "7PP453H577xbFDCqG8nYEJg8M3u8GT8F",
|
||||
"redirect_uri": "https://mesh.your.domain/oauth2/oidc/redirect",
|
||||
"redirect_uri": "https://mesh.your.domain/auth-oidc-callback",
|
||||
"post_logout_redirect_uri": "https://mesh.your.domain/login",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"response_types": "authorization_code"
|
||||
|
|
@ -651,4 +668,4 @@ https://github.com/panva/node-openid-client
|
|||
|
||||
https://openid.net/connect/
|
||||
|
||||
> You just read `openidConnectStrategy.ms v1.0.1` by [@mstrhakr](https://github.com/mstrhakr)
|
||||
> You just read `openidConnectStrategy.ms v1.0.1` by [@mstrhakr](https://github.com/mstrhakr)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
## Use Cases
|
||||
|
||||
Certain feature requests may not be suitable for all MeshCentral users and thus are available as a plugin. Furthermore users can develop their own plugins - as described further below - to extend functionality or benefit from integrating the powerful MeshCentral into their existing application environment much better.
|
||||
Certain feature requests may not be suitable for all MeshCentral users and thus are available as a plugin. Furthermore users can develop their own plugins - as described further below - to extend functionality or benefit from integrating MeshCentral into their existing application environment.
|
||||
|
||||
## List of publically available plugins
|
||||
|
||||
|
|
@ -17,10 +17,10 @@ Certain feature requests may not be suitable for all MeshCentral users and thus
|
|||
>"plugins": {
|
||||
> "enabled": true
|
||||
>},
|
||||
2. Restart MeshCentral in case you just enabled plugins in the configuration.
|
||||
2. Restart MeshCentral if you needed to change the configuration.
|
||||
2. Log into MeshCentral as full administrator.
|
||||
3. Go my `My Server` -> `Plugins`, hit the Download plugin button.
|
||||
4. A dialog opens requesting an URL, e.g. put in: <https://github.com/ryanblenis/MeshCentral-ScriptTask>
|
||||
3. Go my `My Server` -> `Plugins`, then hit the Download plugin button.
|
||||
4. A dialog opens requesting a URL, e.g. put in: <https://github.com/ryanblenis/MeshCentral-ScriptTask>
|
||||
5. The plugin pops up in the plugin list below the download button, you can now configure and enable/disable it.
|
||||
|
||||
# Plugins - Development & Hooks
|
||||
|
|
@ -30,7 +30,7 @@ Certain feature requests may not be suitable for all MeshCentral users and thus
|
|||
|
||||
## Overview
|
||||
|
||||
Not all feature requests may be suitable for all MeshCentral users and thus can't be integrated into MeshCentral directly. Hwoever, Instead of maintaining a complete fork of MeshCentral it is so much easier to benefit from and extend MeshCentral's functionality by using hooks and writing plugins for it.
|
||||
Not all feature requests may be suitable for all MeshCentral users and thus can't be integrated into MeshCentral directly. Hwoever, Instead of maintaining a complete fork of MeshCentral it is much easier to extend MeshCentral's functionality using hooks and writing plugins for it.
|
||||
|
||||
## Anatomy of a plugin:
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ A valid JSON object within a file named `config.json` in the root folder of your
|
|||
| configUrl | Yes | string | the URL to the config.json of the project |
|
||||
| downloadUrl | Yes | string | the URL to a ZIP of the project (used for installation/upgrades) |
|
||||
| repository | Yes | JSON object | contains the following attributes |
|
||||
| repository.type | Yes | string | valid values are `git` and in the future, `npm` will also be supported in the future |
|
||||
| repository.type | Yes | string | valid values are `git` and in the future, `npm` will also be supported. |
|
||||
| repository.url | Yes | string | the URL to the project's repository |
|
||||
| versionHistoryUrl | No | string | the URL to the project's versions/tags |
|
||||
| meshCentralCompat | Yes | string | the minimum version string of required compatibility with the MeshCentral server, can be formatted as "0.1.2-c" or ">=0.1.2-c". Currently only supports minimum version, not full semantic checking. |
|
||||
|
|
@ -123,6 +123,10 @@ Use of the optional file `plugin_name.js` in the optional folder `modules_meshco
|
|||
Much of MeshCentral revolves around returning objects for your structures, and plugins are no different. Within your plugin you can traverse all the way up to the web server and MeshCentral Server classes to access all the functionality those layers provide. This is done by passing the current object to newly created objects, and assigning that reference to a `parent` variable within that object.
|
||||
|
||||
|
||||
## Ping-Pong
|
||||
|
||||
If you build a plugin which makes use of `meshrelay.ashx`, keep in mind to either handle ping-pong messages (`serverPing`, `serverPong`) on the control channel or to request MeshCentral to not send such messages through sending the `noping=1` parameter in the connection URL. For a deeper sight search for "PING/PONG" in `meshrelay.js`.
|
||||
|
||||
## Versioning
|
||||
|
||||
Versioning your plugin correctly and consistently is essential to ensure users of your plugin are prompted to upgrade when it is available. Semantic versioning is recommended.
|
||||
|
|
|
|||
|
|
@ -27,3 +27,29 @@ Adjust these items in your `config.json`
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Duo 2FA setup
|
||||
|
||||
MeshCentral supports Duo as a way for users to add two-factor authentication and Duo offers free accounts for user 10 users. To get started, go to [Duo.com](https://duo.com/) and create a free account. Once logged into Duo, select "Applications" and "Protect an Application" on the left side. Search for "Web SDK" and hit the "Protect" button. You will see a screen with the following information:
|
||||
|
||||
- Client ID
|
||||
- Client secret
|
||||
- API hostname
|
||||
|
||||
Copy these three values in a safe place and do not share these values with anyone. Then, in your MeshCentral config.json file, add the following in the domains section:
|
||||
|
||||
```json
|
||||
{
|
||||
"domains": {
|
||||
"": {
|
||||
"duo2factor": {
|
||||
"integrationkey": "ClientId",
|
||||
"secretkey": "ClientSecret",
|
||||
"apihostname": "api-xxxxxxxxxxx.duosecurity.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Restart MeshCentral and your server should now be Duo capable. Users will see an option to enable it in the "My Account" tab. When enabling it, users will be walked thru the process of downloading the mobile application and going thru a trial run on 2FA. Users that get setup will be added to your Duo account under the "Users" / "Users" screen in Duo. Note that the "admin" user is not valid in Duo, so, if you have a user with the name "Admin" in MeshCentral, they will get an error trying to setup Duo.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ nav:
|
|||
- 'Tips n Tricks': 'meshcentral/tipsntricks.md'
|
||||
- 'Messaging': 'messaging/index.md'
|
||||
- 'Customization': 'meshcentral/customization.md'
|
||||
- 'openidConnectStrategy': 'meshcentral/openidConnectStrategy.md'
|
||||
|
||||
- Design and Architecture:
|
||||
- design/index.md
|
||||
|
|
@ -38,6 +39,9 @@ nav:
|
|||
- Intel AMT:
|
||||
- intelamt/index.md
|
||||
|
||||
- How to Contribute:
|
||||
- how-to-contribute/index.md
|
||||
|
||||
- Other:
|
||||
- other/adfs_sso_guide.md
|
||||
- other/meshcentral_satellite.md
|
||||
|
|
|
|||
246
firebase.js
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* @description MeshCentral Firebase communication module
|
||||
* @author Ylian Saint-Hilaire
|
||||
* @copyright Intel Corporation 2018-2022
|
||||
* @license Apache-2.0
|
||||
* @version v0.0.1
|
||||
*/
|
||||
|
|
@ -14,27 +13,31 @@
|
|||
/*jshint esversion: 6 */
|
||||
"use strict";
|
||||
|
||||
// Construct the Firebase object
|
||||
module.exports.CreateFirebase = function (parent, senderid, serverkey) {
|
||||
var obj = {};
|
||||
// Initialize the Firebase Admin SDK
|
||||
module.exports.CreateFirebase = function (parent, serviceAccount) {
|
||||
|
||||
// Import the Firebase Admin SDK
|
||||
const admin = require('firebase-admin');
|
||||
|
||||
const obj = {};
|
||||
obj.messageId = 0;
|
||||
obj.relays = {};
|
||||
obj.stats = {
|
||||
mode: "Real",
|
||||
mode: 'Real',
|
||||
sent: 0,
|
||||
sendError: 0,
|
||||
received: 0,
|
||||
receivedNoRoute: 0,
|
||||
receivedBadArgs: 0
|
||||
};
|
||||
|
||||
const tokenToNodeMap = {}; // Token --> { nid: nodeid, mid: meshid }
|
||||
|
||||
// Initialize Firebase Admin with server key and project ID
|
||||
if (!admin.apps.length) {
|
||||
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
|
||||
}
|
||||
|
||||
const Sender = require('node-xcs').Sender;
|
||||
const Message = require('node-xcs').Message;
|
||||
const Notification = require('node-xcs').Notification;
|
||||
const xcs = new Sender(senderid, serverkey);
|
||||
|
||||
var tokenToNodeMap = {} // Token --> { nid: nodeid, mid: meshid }
|
||||
|
||||
|
||||
// Setup logging
|
||||
if (parent.config.firebase && (parent.config.firebase.log === true)) {
|
||||
obj.logpath = parent.path.join(parent.datapath, 'firebase.txt');
|
||||
|
|
@ -42,155 +45,108 @@ module.exports.CreateFirebase = function (parent, senderid, serverkey) {
|
|||
} else {
|
||||
obj.log = function () { }
|
||||
}
|
||||
|
||||
// Messages received from client (excluding receipts)
|
||||
xcs.on('message', function (messageId, from, data, category) {
|
||||
const jsonData = JSON.stringify(data);
|
||||
obj.log('Firebase-Message: ' + jsonData);
|
||||
parent.debug('email', 'Firebase-Message: ' + jsonData);
|
||||
|
||||
if (typeof data.r == 'string') {
|
||||
// Lookup push relay server
|
||||
parent.debug('email', 'Firebase-RelayRoute: ' + data.r);
|
||||
const wsrelay = obj.relays[data.r];
|
||||
if (wsrelay != null) {
|
||||
delete data.r;
|
||||
try { wsrelay.send(JSON.stringify({ from: from, data: data, category: category })); } catch (ex) { }
|
||||
}
|
||||
} else {
|
||||
// Lookup node information from the cache
|
||||
var ninfo = tokenToNodeMap[from];
|
||||
if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
|
||||
|
||||
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
|
||||
obj.stats.received++;
|
||||
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid);
|
||||
} else {
|
||||
obj.stats.receivedBadArgs++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Only fired for messages where options.delivery_receipt_requested = true
|
||||
/*
|
||||
xcs.on('receipt', function (messageId, from, data, category) { console.log('Firebase-Receipt', messageId, from, data, category); });
|
||||
xcs.on('connected', function () { console.log('Connected'); });
|
||||
xcs.on('disconnected', function () { console.log('disconnected'); });
|
||||
xcs.on('online', function () { console.log('online'); });
|
||||
xcs.on('error', function (e) { console.log('error', e); });
|
||||
xcs.on('message-error', function (e) { console.log('message-error', e); });
|
||||
*/
|
||||
|
||||
xcs.start();
|
||||
|
||||
obj.log('CreateFirebase-Setup');
|
||||
parent.debug('email', 'CreateFirebase-Setup');
|
||||
|
||||
// EXAMPLE
|
||||
//var payload = { notification: { title: command.title, body: command.msg }, data: { url: obj.msgurl } };
|
||||
//var options = { priority: 'High', timeToLive: 5 * 60 }; // TTL: 5 minutes, priority 'Normal' or 'High'
|
||||
|
||||
|
||||
// Function to send notifications
|
||||
obj.sendToDevice = function (node, payload, options, func) {
|
||||
if (typeof node == 'string') {
|
||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
||||
if (typeof node === 'string') {
|
||||
parent.db.Get(node, function (err, docs) {
|
||||
if (!err && docs && docs.length === 1) {
|
||||
obj.sendToDeviceEx(docs[0], payload, options, func);
|
||||
} else {
|
||||
func(0, 'error');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
obj.sendToDeviceEx(node, payload, options, func);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Send an outbound push notification
|
||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||
parent.debug('email', 'Firebase-sendToDevice');
|
||||
if ((node == null) || (typeof node.pmt != 'string')) return;
|
||||
if (!node || typeof node.pmt !== 'string') {
|
||||
func(0, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||
|
||||
|
||||
// Fill in our lookup table
|
||||
if (node._id != null) { tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
|
||||
|
||||
// Built the on-screen notification
|
||||
var notification = null;
|
||||
if (payload.notification) {
|
||||
var notification = new Notification('ic_message')
|
||||
.title(payload.notification.title)
|
||||
.body(payload.notification.body)
|
||||
.build();
|
||||
if (node._id) {
|
||||
tokenToNodeMap[node.pmt] = {
|
||||
nid: node._id,
|
||||
mid: node.meshid,
|
||||
did: node.domain
|
||||
};
|
||||
}
|
||||
|
||||
// Build the message
|
||||
var message = new Message('msg_' + (++obj.messageId));
|
||||
if (options.priority) { message.priority(options.priority); }
|
||||
if (payload.data) { for (var i in payload.data) { message.addData(i, payload.data[i]); } }
|
||||
if ((payload.data == null) || (payload.data.shash == null)) { message.addData('shash', parent.webserver.agentCertificateHashBase64); } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
||||
if (notification) { message.notification(notification) }
|
||||
message.build();
|
||||
|
||||
// Send the message
|
||||
function callback(result) {
|
||||
if (result.getError() == null) { obj.stats.sent++; obj.log('Success'); } else { obj.stats.sendError++; obj.log('Fail'); }
|
||||
callback.func(result.getMessageId(), result.getError(), result.getErrorDescription())
|
||||
}
|
||||
callback.func = func;
|
||||
parent.debug('email', 'Firebase-sending');
|
||||
xcs.sendNoRetry(message, node.pmt, callback);
|
||||
}
|
||||
|
||||
|
||||
const message = {
|
||||
token: node.pmt,
|
||||
notification: payload.notification,
|
||||
data: payload.data,
|
||||
android: {
|
||||
priority: options.priority || 'high',
|
||||
ttl: options.timeToLive ? options.timeToLive * 1000 : undefined
|
||||
}
|
||||
};
|
||||
|
||||
admin.messaging().send(message).then(function (response) {
|
||||
obj.stats.sent++;
|
||||
obj.log('Success');
|
||||
func(response);
|
||||
}).catch(function (error) {
|
||||
obj.stats.sendError++;
|
||||
obj.log('Fail: ' + error);
|
||||
func(0, error);
|
||||
});
|
||||
};
|
||||
|
||||
// Setup a two way relay
|
||||
obj.setupRelay = function (ws) {
|
||||
// Select and set a relay identifier
|
||||
ws.relayId = getRandomPassword();
|
||||
while (obj.relays[ws.relayId] != null) { ws.relayId = getRandomPassword(); }
|
||||
while (obj.relays[ws.relayId]) { ws.relayId = getRandomPassword(); }
|
||||
obj.relays[ws.relayId] = ws;
|
||||
|
||||
// On message, parse it
|
||||
ws.on('message', function (msg) {
|
||||
parent.debug('email', 'FBWS-Data(' + this.relayId + '): ' + msg);
|
||||
if (typeof msg == 'string') {
|
||||
if (typeof msg === 'string') {
|
||||
obj.log('Relay: ' + msg);
|
||||
|
||||
// Parse the incoming push request
|
||||
var data = null;
|
||||
try { data = JSON.parse(msg) } catch (ex) { return; }
|
||||
if (typeof data != 'object') return;
|
||||
if (parent.common.validateObjectForMongo(data, 4096) == false) return; // Perform sanity checking on this object.
|
||||
if (typeof data.pmt != 'string') return;
|
||||
if (typeof data.payload != 'object') return;
|
||||
if (typeof data.payload.notification == 'object') {
|
||||
if (typeof data.payload.notification.title != 'string') return;
|
||||
if (typeof data.payload.notification.body != 'string') return;
|
||||
}
|
||||
if (typeof data.options != 'object') return;
|
||||
if ((data.options.priority != 'Normal') && (data.options.priority != 'High')) return;
|
||||
if ((typeof data.options.timeToLive != 'number') || (data.options.timeToLive < 1)) return;
|
||||
if (typeof data.payload.data != 'object') { data.payload.data = {}; }
|
||||
data.payload.data.r = ws.relayId; // Set the relay id.
|
||||
|
||||
// Send the push notification
|
||||
obj.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err, errdesc) {
|
||||
if (err == null) {
|
||||
try { wsrelay.send(JSON.stringify({ sent: true })); } catch (ex) { }
|
||||
|
||||
let data;
|
||||
try { data = JSON.parse(msg); } catch (ex) { return; }
|
||||
if (typeof data !== 'object') return;
|
||||
if (!parent.common.validateObjectForMongo(data, 4096)) return;
|
||||
if (typeof data.pmt !== 'string' || typeof data.payload !== 'object') return;
|
||||
|
||||
data.payload.data = data.payload.data || {};
|
||||
data.payload.data.r = ws.relayId;
|
||||
|
||||
obj.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err) {
|
||||
if (!err) {
|
||||
try { ws.send(JSON.stringify({ sent: true })); } catch (ex) { }
|
||||
} else {
|
||||
try { wsrelay.send(JSON.stringify({ sent: false })); } catch (ex) { }
|
||||
try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// If error, close the relay
|
||||
ws.on('error', function (err) {
|
||||
parent.debug('email', 'FBWS-Error(' + this.relayId + '): ' + err);
|
||||
delete obj.relays[this.relayId];
|
||||
});
|
||||
|
||||
|
||||
// Close the relay
|
||||
ws.on('close', function () {
|
||||
parent.debug('email', 'FBWS-Close(' + this.relayId + ')');
|
||||
delete obj.relays[this.relayId];
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
function getRandomPassword() {
|
||||
return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').replace(/\//g, '@');
|
||||
}
|
||||
|
||||
function getRandomPassword() { return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
|
||||
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
|
@ -212,7 +168,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
const querystring = require('querystring');
|
||||
const relayUrl = require('url').parse(url);
|
||||
parent.debug('email', 'CreateFirebaseRelay-Setup');
|
||||
|
||||
|
||||
// Setup logging
|
||||
if (parent.config.firebaserelay && (parent.config.firebaserelay.log === true)) {
|
||||
obj.logpath = parent.path.join(parent.datapath, 'firebaserelay.txt');
|
||||
|
|
@ -220,7 +176,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
} else {
|
||||
obj.log = function () { }
|
||||
}
|
||||
|
||||
|
||||
obj.log('Starting relay to: ' + relayUrl.href);
|
||||
if (relayUrl.protocol == 'wss:') {
|
||||
// Setup two-way push notification channel
|
||||
|
|
@ -252,7 +208,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
parent.debug('email', 'FBWS-Disconnected');
|
||||
obj.wsclient = null;
|
||||
obj.wsopen = false;
|
||||
|
||||
|
||||
// Compute the backoff timer
|
||||
if (obj.reconnectTimer == null) {
|
||||
if ((obj.lastConnect != null) && ((Date.now() - obj.lastConnect) > 10000)) { obj.backoffTimer = 0; }
|
||||
|
|
@ -263,12 +219,12 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function processMessage(messageId, from, data, category) {
|
||||
// Lookup node information from the cache
|
||||
var ninfo = obj.tokenToNodeMap[from];
|
||||
if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
|
||||
|
||||
|
||||
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
|
||||
obj.stats.received++;
|
||||
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid);
|
||||
|
|
@ -276,7 +232,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
obj.stats.receivedBadArgs++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
obj.sendToDevice = function (node, payload, options, func) {
|
||||
if (typeof node == 'string') {
|
||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
||||
|
|
@ -284,19 +240,19 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
obj.sendToDeviceEx(node, payload, options, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||
parent.debug('email', 'Firebase-sendToDevice-webSocket');
|
||||
if ((node == null) || (typeof node.pmt != 'string')) { func(0, 'error'); return; }
|
||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||
|
||||
|
||||
// Fill in our lookup table
|
||||
if (node._id != null) { obj.tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
|
||||
|
||||
|
||||
// Fill in the server agent cert hash
|
||||
if (payload.data == null) { payload.data = {}; }
|
||||
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
||||
|
||||
|
||||
// If the web socket is open, send now
|
||||
if (obj.wsopen == true) {
|
||||
try { obj.wsclient.send(JSON.stringify({ pmt: node.pmt, payload: payload, options: options })); } catch (ex) { func(0, 'error'); obj.stats.sendError++; return; }
|
||||
|
|
@ -314,7 +270,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
} else if (relayUrl.protocol == 'https:') {
|
||||
// Send an outbound push notification using an HTTPS POST
|
||||
obj.pushOnly = true;
|
||||
|
||||
|
||||
obj.sendToDevice = function (node, payload, options, func) {
|
||||
if (typeof node == 'string') {
|
||||
parent.db.Get(node, function (err, docs) { if ((err == null) && (docs != null) && (docs.length == 1)) { obj.sendToDeviceEx(docs[0], payload, options, func); } else { func(0, 'error'); } })
|
||||
|
|
@ -322,18 +278,18 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
obj.sendToDeviceEx(node, payload, options, func);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
obj.sendToDeviceEx = function (node, payload, options, func) {
|
||||
parent.debug('email', 'Firebase-sendToDevice-httpPost');
|
||||
if ((node == null) || (typeof node.pmt != 'string')) return;
|
||||
|
||||
|
||||
// Fill in the server agent cert hash
|
||||
if (payload.data == null) { payload.data = {}; }
|
||||
if (payload.data.shash == null) { payload.data.shash = parent.webserver.agentCertificateHashBase64; } // Add the server agent hash, new Android agents will reject notifications that don't have this.
|
||||
|
||||
|
||||
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
|
||||
const querydata = querystring.stringify({ 'msg': JSON.stringify({ pmt: node.pmt, payload: payload, options: options }) });
|
||||
|
||||
|
||||
// Send the message to the relay
|
||||
const httpOptions = {
|
||||
hostname: relayUrl.hostname,
|
||||
|
|
@ -357,6 +313,6 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
|
|||
req.end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
|
@ -28,6 +28,8 @@ module.exports.CreateLetsEncrypt = function (parent) {
|
|||
obj.challenges = {};
|
||||
obj.runAsProduction = false;
|
||||
obj.redirWebServerHooked = false;
|
||||
obj.zerossl = false;
|
||||
obj.csr = null;
|
||||
obj.configErr = null;
|
||||
obj.configOk = false;
|
||||
obj.pendingRequest = false;
|
||||
|
|
@ -57,6 +59,7 @@ module.exports.CreateLetsEncrypt = function (parent) {
|
|||
// Get the current certificate
|
||||
obj.getCertificate = function(certs, func) {
|
||||
obj.runAsProduction = (obj.parent.config.letsencrypt.production === true);
|
||||
obj.zerossl = ((typeof obj.parent.config.letsencrypt.zerossl == 'object') ? obj.parent.config.letsencrypt.zerossl : false);
|
||||
obj.log("Getting certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
|
||||
if (certs.CommonName.indexOf('.') == -1) { obj.configErr = "Add \"cert\" value to settings in config.json before using Let's Encrypt."; parent.addServerWarning(obj.configErr); obj.log("WARNING: " + obj.configErr); func(certs); return; }
|
||||
if (obj.parent.config.letsencrypt == null) { obj.configErr = "No Let's Encrypt configuration"; parent.addServerWarning(obj.configErr); obj.log("WARNING: " + obj.configErr); func(certs); return; }
|
||||
|
|
@ -164,26 +167,36 @@ module.exports.CreateLetsEncrypt = function (parent) {
|
|||
obj.log("Generating private key...");
|
||||
acme.forge.createPrivateKey().then(function (accountKey) {
|
||||
|
||||
// TODO: ZeroSSL
|
||||
// https://acme.zerossl.com/v2/DV90
|
||||
|
||||
// Create the ACME client
|
||||
obj.log("Setting up ACME client...");
|
||||
obj.client = new acme.Client({
|
||||
directoryUrl: obj.runAsProduction ? acme.directory.letsencrypt.production : acme.directory.letsencrypt.staging,
|
||||
accountKey: accountKey
|
||||
});
|
||||
if (obj.zerossl) {
|
||||
if (obj.zerossl.kid == "") { obj.log("EAB KID hasn't been set, invalid configuration."); return; }
|
||||
if (obj.zerossl.hmackey == "") { obj.log("EAB HMAC KEY hasn't been set, invalid configuration."); return; }
|
||||
obj.client = new acme.Client({
|
||||
directoryUrl: acme.directory.zerossl.production,
|
||||
accountKey: accountKey,
|
||||
externalAccountBinding: {
|
||||
kid: obj.zerossl.kid,
|
||||
hmacKey: obj.zerossl.hmackey
|
||||
}
|
||||
});
|
||||
} else {
|
||||
obj.client = new acme.Client({
|
||||
directoryUrl: obj.runAsProduction ? acme.directory.letsencrypt.production : acme.directory.letsencrypt.staging,
|
||||
accountKey: accountKey
|
||||
});
|
||||
}
|
||||
|
||||
// Create Certificate Request (CSR)
|
||||
obj.log("Creating certificate request...");
|
||||
var certRequest = { commonName: obj.leDomains[0] };
|
||||
if (obj.leDomains.length > 1) { certRequest.altNames = obj.leDomains; }
|
||||
acme.forge.createCsr(certRequest).then(function (r) {
|
||||
var csr = r[1];
|
||||
obj.csr = r[1];
|
||||
obj.tempPrivateKey = r[0];
|
||||
obj.log("Requesting certificate from Let's Encrypt...");
|
||||
if(obj.zerossl) { obj.log("Requesting certificate from ZeroSSL..."); } else { obj.log("Requesting certificate from Let's Encrypt..."); }
|
||||
obj.client.auto({
|
||||
csr,
|
||||
csr: obj.csr,
|
||||
email: obj.parent.config.letsencrypt.email,
|
||||
termsOfServiceAgreed: true,
|
||||
skipChallengeVerification: (obj.parent.config.letsencrypt.skipchallengeverification === true),
|
||||
|
|
|
|||
2
mcrec.js
|
|
@ -321,7 +321,7 @@ function setup() { InstallModules(['image-size'], start); }
|
|||
function start() { startEx(process.argv); }
|
||||
function startEx(argv) {
|
||||
if (argv.length > 2) { indexFile(argv[2]); } else {
|
||||
log("MeshCentral Session Recodings Processor");
|
||||
log("MeshCentral Session Recordings Processor");
|
||||
log("This tool will index a .mcrec file so that the player can seek thru the file.");
|
||||
log("");
|
||||
log(" Usage: node mcrec [file]");
|
||||
|
|
|
|||
25
meshagent.js
|
|
@ -58,15 +58,15 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
dataAccounting();
|
||||
|
||||
if ((arg == 1) || (arg == null)) { try { ws.close(); if (obj.nodeid != null) { parent.parent.debug('agent', 'Soft disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Soft close, close the websocket
|
||||
if (arg == 2) {
|
||||
try {
|
||||
if (arg == 2) {
|
||||
try {
|
||||
if (ws._socket._parent != null)
|
||||
ws._socket._parent.end();
|
||||
else
|
||||
ws._socket.end();
|
||||
|
||||
if (obj.nodeid != null) {
|
||||
parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')');
|
||||
|
||||
if (obj.nodeid != null) {
|
||||
parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')');
|
||||
}
|
||||
} catch (e) { console.log(e); }
|
||||
}
|
||||
|
|
@ -616,7 +616,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
}
|
||||
|
||||
if ((mesh == null) && (typeof domain.orphanagentuser == 'string')) {
|
||||
const adminUser = parent.users['user/' + domain.id + '/' + domain.orphanagentuser.toLowerCase()];
|
||||
const adminUser = parent.users['user/' + domain.id + '/' + domain.orphanagentuser];
|
||||
if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
|
||||
// Mesh name is hex instead of base64
|
||||
const meshname = obj.meshid.substring(0, 18);
|
||||
|
|
@ -1058,7 +1058,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
|
||||
if ((iplocs != null) && (iplocs.length == 1)) {
|
||||
// We have a location in the database for this remote IP
|
||||
const iploc = nodes[0], x = {};
|
||||
const iploc = iplocs[0], x = {};
|
||||
if ((iploc != null) && (iploc.ip != null) && (iploc.loc != null)) {
|
||||
x.publicip = iploc.ip;
|
||||
x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000));
|
||||
|
|
@ -1067,10 +1067,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
} else {
|
||||
// Check if we need to ask for the IP location
|
||||
var doIpLocation = 0;
|
||||
if (device.iploc == null) {
|
||||
if (obj.iploc == null) {
|
||||
doIpLocation = 1;
|
||||
} else {
|
||||
const loc = device.iploc.split(',');
|
||||
const loc = obj.iploc.split(',');
|
||||
if (loc.length < 3) {
|
||||
doIpLocation = 2;
|
||||
} else {
|
||||
|
|
@ -1924,6 +1924,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
if (!device.defender) { device.defender = {}; }
|
||||
if (JSON.stringify(device.defender) != JSON.stringify(command.defender)) { /*changes.push('Defender status');*/ device.defender = command.defender; change = 1; log = 1; }
|
||||
}
|
||||
if (command.lastbootuptime != null) { // Last Boot Up Time
|
||||
if (!device.lastbootuptime) { device.lastbootuptime = ""; }
|
||||
if (device.lastbootuptime != command.lastbootuptime) { /*changes.push('Last Boot Up Time');*/ device.lastbootuptime = command.lastbootuptime; change = 1; log = 1; }
|
||||
}
|
||||
|
||||
// Push Messaging Token
|
||||
if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) {
|
||||
|
|
@ -1932,8 +1936,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|||
change = 1; // Don't save this change as an event to the db, so no log=1.
|
||||
parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
|
||||
}
|
||||
|
||||
|
||||
if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db.
|
||||
if ((command.lusers != null) && (Array.isArray(command.lusers)) && (device.lusers != command.lusers)) { device.lusers = command.lusers; change = 1; } // Don't save this to the db.
|
||||
if ((mesh.mtype == 2) && (!args.wanonly)) {
|
||||
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
|
||||
if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
|
||||
|
|
|
|||
|
|
@ -94,9 +94,41 @@
|
|||
}
|
||||
},
|
||||
"sqlite3": {
|
||||
"type": "boolean",
|
||||
"type": [ "boolean", "string", "object" ],
|
||||
"default": false,
|
||||
"description": "Set true to use SQLite3 as a local MeshCentral database."
|
||||
"description": "Set boolean true to use SQLite3 as a local MeshCentral database with default db filename 'meshcentral' or enter a string for a different db filename. Extension .sqlite is appended",
|
||||
"properties":{
|
||||
"name": {
|
||||
"type": "string",
|
||||
"default": "meshcentral",
|
||||
"description": "Database filename. '.sqlite' is appended"
|
||||
},
|
||||
"journalMode": {
|
||||
"type": "string",
|
||||
"default": "delete",
|
||||
"description": "DELETE, TRUNCATE, PERSIST, MEMORY, WAL. NONE not allowed. See: https://www.sqlite.org/pragma.html#pragma_journal_mode"
|
||||
},
|
||||
"journalSize": {
|
||||
"type": "integer",
|
||||
"default": 4096000,
|
||||
"description": "Maximum size of the journal file in bytes. Can grow larger if needed, but will shrink to this size. -1 is unlimited growth. See: https://www.sqlite.org/pragma.html#pragma_journal_size_limit"
|
||||
},
|
||||
"autoVacuum": {
|
||||
"type": "string",
|
||||
"default": "incremental",
|
||||
"description": "none, full, incremental. Removes unused pages and shrinks databasefile during maintenance. See: https://www.sqlite.org/pragma.html#pragma_auto_vacuum"
|
||||
},
|
||||
"incrementalVacuum": {
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
"description": "Maximum amount of pages to free during maintenance. Default page size is 4k, so default frees up to 400k from the databasefile. See: https://www.sqlite.org/pragma.html#pragma_incremental_vacuum"
|
||||
},
|
||||
"startupVacuum": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do a full VACUUM at startup. Shrinks the db file and optimizes it. This can take some time with a large database and can temporarily take up to double the database size on disk. See: https://www.sqlite.org/lang_vacuum.html"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mySQL": {
|
||||
"type": "object",
|
||||
|
|
@ -488,11 +520,6 @@
|
|||
"default": true,
|
||||
"description": "When enabled, MeshCentral will automatically monitor and manage Intel AMT devices."
|
||||
},
|
||||
"orphanAgentUser": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "If an agent attempts to connect to a unknown device group, automatically create a new device group and grant access to the specified user. Example: admin"
|
||||
},
|
||||
"agentIdleTimeout": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
|
|
@ -608,7 +635,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, only users from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, only users from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:userAllowedIP.txt\""
|
||||
},
|
||||
"userBlockedIP": {
|
||||
"type": [
|
||||
|
|
@ -616,7 +643,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, users from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, users from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:userBlockedIP.txt\""
|
||||
},
|
||||
"agentAllowedIP": {
|
||||
"type": [
|
||||
|
|
@ -624,7 +651,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, only agents from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, only agents from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:agentAllowedIP.txt\""
|
||||
},
|
||||
"agentBlockedIP": {
|
||||
"type": [
|
||||
|
|
@ -632,7 +659,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, agents from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, agents from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:agentBlockedIP.txt\""
|
||||
},
|
||||
"authLog": {
|
||||
"type": "string",
|
||||
|
|
@ -740,13 +767,27 @@
|
|||
"default": false,
|
||||
"description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect."
|
||||
},
|
||||
"prometheus": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"number"
|
||||
],
|
||||
"default": false,
|
||||
"description": "When set to true, a prometheus metrics endpoint will be available \"0.0.0.0:9464/metrics\". If you specify a number instead, the prometheus metrics will listen on this port instead of the default 9464."
|
||||
},
|
||||
"no2FactorAuth": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"debug": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "You can set this value in the format 'web,agent,relay', where each value is separated by a comma. Each specified value will output logs to the MeshCentral console. Setting it to '*' will output all logs."
|
||||
},
|
||||
"log": {
|
||||
"type": "string",
|
||||
"default": null
|
||||
"default": null,
|
||||
"description": "You can set this value in the format 'web,agent,relay', where each value is separated by a comma. Each specified value will output logs to a file named 'log.txt' located inside the 'meshcentral-data' folder. Setting it to '*' will output all logs."
|
||||
},
|
||||
"syslog": {
|
||||
"type": "string",
|
||||
|
|
@ -767,7 +808,7 @@
|
|||
},
|
||||
"webrtcConfig": {
|
||||
"type": "object",
|
||||
"description": "The STUN servers used for WebRTC, if not specified the Google and Mozilla servers and used when the server is not in LAN mode.",
|
||||
"description": "The STUN/TURN servers used for WebRTC, if not specified the Google and Cloudflare STUN servers are used when the server is not in LAN mode.",
|
||||
"properties": {
|
||||
"iceServers": {
|
||||
"type": "array",
|
||||
|
|
@ -776,7 +817,8 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"urls": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "STUN/TURN URLs, examples are stun:stun3.l.google.com:19302 or turn:openrelay.metered.ca:443"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -818,8 +860,11 @@
|
|||
]
|
||||
},
|
||||
"autoBackup": {
|
||||
"type": "object",
|
||||
"description": "Enable automatic backups of your meshcentral-data",
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
],
|
||||
"description": "Enabled by default or if set to true. Disabled if set to false. An object can be provided with additional properties to set autobackup options.",
|
||||
"properties": {
|
||||
"mongoDumpPath": {
|
||||
"type": "string",
|
||||
|
|
@ -831,26 +876,67 @@
|
|||
"default": "mysqldump",
|
||||
"description": "The file path of where \"mysqldump\" is located. Default is \"mysqldump\""
|
||||
},
|
||||
"pgDumpPath": {
|
||||
"type": "string",
|
||||
"default": "pg_dump",
|
||||
"description": "The file path of where \"pg_dump\" is located. Default is \"pg_dump\""
|
||||
},
|
||||
"backupIntervalHours": {
|
||||
"type": "integer",
|
||||
"default": 24,
|
||||
"description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours"
|
||||
},
|
||||
"backupHour": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "At which hour the autobackup should run. This forces a daily backup, overrules a custom 'backupIntervalHours'."
|
||||
},
|
||||
"keepLastDaysBackup": {
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "How many days of backups should the autobackup keep? Default is 10 Days worth"
|
||||
},
|
||||
"zipCompression" : {
|
||||
"type": "integer",
|
||||
"default": "5",
|
||||
"description": "Set the zip compression level, 1=fast/less small file to 9=slow/smallest file."
|
||||
},
|
||||
"zipPassword": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "When specified, the ZIP backups will be password protected with the zipPassword"
|
||||
"default": "",
|
||||
"minLength": 1,
|
||||
"description": "When specified, the ZIP backups will be password protected with the zipPassword and the password cannot be a blank value"
|
||||
},
|
||||
"backupPath": {
|
||||
"type": "string",
|
||||
"default": "meshcentral-backups",
|
||||
"description": "The file path where backup files are kept. The default is \"meshcentral-backups\" which sits next to \"meshcentral-data\"."
|
||||
},
|
||||
"backupName": {
|
||||
"type": "string",
|
||||
"default": "meshcentral-autobackup-",
|
||||
"description": "The filename of the backupfile. The default is \"meshcentral-autobackup-\", the filename is appended with the time of backup."
|
||||
},
|
||||
"backupWebFolders": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Add views, public and emails directories if overridden"
|
||||
},
|
||||
"backupOtherFolders": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Also add files and recordings folder to the backup"
|
||||
},
|
||||
"backupIgnoreFilesGlob": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"description": "Glob for ignoring files in the data directory. For example [\"**/*.log\"] !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !! Don't do string..."
|
||||
},
|
||||
"backupSkipFoldersGlob":{
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"description": "Glob for ignoring directories in the data directory. For example [\"**/signedagents\"]"
|
||||
},
|
||||
"googleDrive": {
|
||||
"type": "object",
|
||||
"description": "Enabled automated upload of the server backups to a Google Drive account, once enabled you need to go in \"My Server\" tab as administrator to associate the account.",
|
||||
|
|
@ -894,6 +980,54 @@
|
|||
"description": "The maximum number of files to keep in the WebDAV folder, older files will be removed if needed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"s3": {
|
||||
"type": "object",
|
||||
"description": "Enabled automated upload of the server backups to an S3 server.",
|
||||
"required": [
|
||||
"accessKey",
|
||||
"secretKey",
|
||||
"bucketName"
|
||||
],
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"default": "s3.amazonaws.com",
|
||||
"description": "S3 Endpoint address e.g. myS3.myserver.com"
|
||||
},
|
||||
"accessKey": {
|
||||
"type": "string",
|
||||
"description": "S3 accessKey, Required"
|
||||
},
|
||||
"secretKey": {
|
||||
"type": "string",
|
||||
"description": "S3 secretKey, Required"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"default": 443,
|
||||
"description": "S3 Endpoint port number, Default is 443"
|
||||
},
|
||||
"ssl": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "If \"true\", the S3 Endpoint will use \"https\", else it will use \"http\", Default is true"
|
||||
},
|
||||
"bucketName": {
|
||||
"type": "string",
|
||||
"description": "S3 Bucket Name, Required"
|
||||
},
|
||||
"folderName": {
|
||||
"type": "string",
|
||||
"default": "MeshCentral-Backups",
|
||||
"description": "The name of the folder to create in the S3 Bucket. Defaults to \"MeshCentral-Backups\"."
|
||||
},
|
||||
"maxFiles": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "The maximum number of files to keep in the S3 folder, older files will be removed if needed. Default is 0 which is keep everything."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1000,6 +1134,24 @@
|
|||
"required": [
|
||||
"enabled"
|
||||
]
|
||||
},
|
||||
"watchdog": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"interval",
|
||||
"timeout"
|
||||
],
|
||||
"description": "This is used to monitor if NodeJS is servicing IO correctly or getting held up a lot. You MUST specify \"interval\" and \"timeout\".",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"description": " This will check every X ms"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "number",
|
||||
"description": " If the timer is more than X ms late, it will warn to console AND mesherrors.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1021,6 +1173,11 @@
|
|||
"default": 2,
|
||||
"description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages."
|
||||
},
|
||||
"showModernUIToggle": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When set to true, the user will be able to toggle between the modern and classic UI."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"default": "MeshCentral",
|
||||
|
|
@ -1041,6 +1198,11 @@
|
|||
"default": null,
|
||||
"description": "Web site .png logo file placed in meshcentral-data that used on the login page when sitestyle is 2."
|
||||
},
|
||||
"pwaLogo": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Web site .png logo file that is 512x512 in size placed in meshcentral-data that is used for PWA installs on iOS and Android."
|
||||
},
|
||||
"rootRedirect": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
|
|
@ -1051,6 +1213,11 @@
|
|||
"default": true,
|
||||
"description": "When set to false, this setting will disable the mobile site."
|
||||
},
|
||||
"orphanAgentUser": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "If an agent attempts to connect to a unknown device group, automatically create a new device group and grant access to the specified user. Example: admin"
|
||||
},
|
||||
"maxDeviceView": {
|
||||
"type": "integer",
|
||||
"default": null,
|
||||
|
|
@ -1203,7 +1370,7 @@
|
|||
"allowSavingDeviceCredentials": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Allow users to save SSH, RDP, VNC device credentials on the server that can be used by any other user."
|
||||
"description": "Allow users to save SSH, RDP, VNC device credentials in the server."
|
||||
},
|
||||
"trustedCert": {
|
||||
"type": "boolean",
|
||||
|
|
@ -1515,6 +1682,11 @@
|
|||
"default": true,
|
||||
"description": "Set to false to disable SMS 2FA."
|
||||
},
|
||||
"duo2factor": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Set to false to disable Duo 2FA."
|
||||
},
|
||||
"push2factor": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
|
@ -1701,6 +1873,11 @@
|
|||
"default": false,
|
||||
"description": "When enabled, this will show the notes panel in the device view"
|
||||
},
|
||||
"userSessionsSort": {
|
||||
"type": "string",
|
||||
"default": "SessionId",
|
||||
"description": "Arrange the Connect sessions offered when multiple Terminal Sessions are present by 'SessionId', 'StationName' or 'Username' with 'SessionId' as the default sorting criteria."
|
||||
},
|
||||
"agentInviteCodes": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
|
@ -1742,6 +1919,11 @@
|
|||
"default": false,
|
||||
"description": "Enables the geo-location feature and device location map in the user interface, this feature is not being worked on."
|
||||
},
|
||||
"ipLocation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When enabled, the remote agents will submit there approximate location to MeshCentral, Use in combination with \"geoLocation\"."
|
||||
},
|
||||
"novnc": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
|
@ -1749,7 +1931,7 @@
|
|||
},
|
||||
"mstsc": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"default": true,
|
||||
"description": "When enabled, activates the built-in web-based RDP client."
|
||||
},
|
||||
"ssh": {
|
||||
|
|
@ -1789,6 +1971,16 @@
|
|||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, user consent is accepted after the timeout."
|
||||
},
|
||||
"autoAcceptIfNoUser": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, user consent is accepted if no user is logged in."
|
||||
},
|
||||
"oldStyle": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, user consent will be shown in an old style prompt box rather than the new style consent-box."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1957,7 +2149,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, only users from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, only users from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:userAllowedIP.txt\""
|
||||
},
|
||||
"userBlockedIP": {
|
||||
"type": [
|
||||
|
|
@ -1965,7 +2157,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, users from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, users from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:userBlockedIP.txt\""
|
||||
},
|
||||
"agentAllowedIP": {
|
||||
"type": [
|
||||
|
|
@ -1973,7 +2165,7 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, only agents from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, only agents from allowed IP address ranges can connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:agentAllowedIP.txt\""
|
||||
},
|
||||
"agentBlockedIP": {
|
||||
"type": [
|
||||
|
|
@ -1981,13 +2173,18 @@
|
|||
"array"
|
||||
],
|
||||
"default": null,
|
||||
"description": "When set, agents from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\""
|
||||
"description": "When set, agents from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\" \"file:agentBlockedIP.txt\""
|
||||
},
|
||||
"userSessionIdleTimeout": {
|
||||
"type": "integer",
|
||||
"default": null,
|
||||
"description": "When set, idle users will be disconnected after a set amounts of minutes."
|
||||
},
|
||||
"logoutOnIdleSessionTimeout": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Determines whether MeshCentral should logout after the session idle timeout elapsed or should just disconnect remote desktop, terminal and files."
|
||||
},
|
||||
"userConsentFlags": {
|
||||
"type": "object",
|
||||
"description": "Use this section to require user consent for this domain.",
|
||||
|
|
@ -2097,7 +2294,7 @@
|
|||
"login"
|
||||
],
|
||||
"default": "any",
|
||||
"description": "Indicate what terminal options are available when the user clicks the right mouse button on the terminal connect button."
|
||||
"description": "Indicates the default linux terminal thats used when the user clicks the terminal connect button and disables the right mouse button on the terminal connect button when this is set."
|
||||
},
|
||||
"launchCommand": {
|
||||
"type": "object",
|
||||
|
|
@ -2146,12 +2343,12 @@
|
|||
"TlsConnections": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "When set to false, MeshCentral will use TLS to connect to Intel AMT, this is not recommended."
|
||||
"description": "When set to false, MeshCentral will NOT use TLS to connect to Intel AMT, this is not recommended."
|
||||
},
|
||||
"TlsAcmActivation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When set to false, MeshCentral will not attempt a TLS ACM activation on Intel AMT v14+"
|
||||
"description": "When set to true, MeshCentral will attempt a TLS ACM activation on Intel AMT v14+"
|
||||
},
|
||||
"AdminAccounts": {
|
||||
"description": "List of username and passwords to try when connecting to Intel AMT.",
|
||||
|
|
@ -2520,6 +2717,26 @@
|
|||
},
|
||||
"description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL."
|
||||
},
|
||||
"duo2factor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"integrationkey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Integration key from Duo"
|
||||
},
|
||||
"secretkey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Secret key from Duo"
|
||||
},
|
||||
"apihostname": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "API Hostname from Duo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"yubikey": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2677,7 +2894,7 @@
|
|||
"name": {
|
||||
"type": "string",
|
||||
"format": "hostname",
|
||||
"description": "Optional hostname of the client, this defaults to the hostname of the machine. This is useful for SMTP relays."
|
||||
"description": "Optional hostname of the client, this defaults to the hostname of the machine. This is useful for SMTP relays. This can also be set to \"console\" for console output debugging."
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
|
|
@ -2688,18 +2905,30 @@
|
|||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "SMTP server port number."
|
||||
"default": 587,
|
||||
"description": "SMTP server port number. This defaults to 587 if \"tls\" is false or 465 if \"tls\" is true)"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "Email address used in the messages from field."
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"description": "SMTP username."
|
||||
},
|
||||
"pass": {
|
||||
"type": "string",
|
||||
"description": "SMTP password."
|
||||
},
|
||||
"tls": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Set SMTP to use TLS on connections, the default is false"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "This is used for OAuth2 authentication",
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
|
|
@ -2709,6 +2938,11 @@
|
|||
},
|
||||
"refreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "login",
|
||||
"description": "Setting this indicates the authetication type, 'login' as default or 'oauth2'"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -2735,7 +2969,10 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"from"
|
||||
"host",
|
||||
"port",
|
||||
"from",
|
||||
"tls"
|
||||
]
|
||||
},
|
||||
"sendmail": {
|
||||
|
|
@ -3034,7 +3271,6 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"newAccounts": {
|
||||
"type": "boolean",
|
||||
|
|
@ -3242,8 +3478,7 @@
|
|||
"required": [
|
||||
"client_id",
|
||||
"client_secret"
|
||||
],
|
||||
"additionalProperties": false
|
||||
]
|
||||
},
|
||||
"issuer": {
|
||||
"type": [
|
||||
|
|
@ -3333,8 +3568,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"type": "object",
|
||||
|
|
@ -3385,8 +3619,7 @@
|
|||
"type": "string",
|
||||
"description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"type": "object",
|
||||
|
|
@ -3398,14 +3631,12 @@
|
|||
},
|
||||
"required": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "Access is only granted to users who are a member of at least one of the listed required groups."
|
||||
},
|
||||
"siteadmin": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "Full site admin priviledges will be granted to users who are a member of at least one of the listed admin groups."
|
||||
|
|
@ -3424,7 +3655,6 @@
|
|||
"properties": {
|
||||
"filter": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "Only groups listed here are mirrored into MeshCentral user groups."
|
||||
|
|
@ -3441,8 +3671,7 @@
|
|||
"default": "groups",
|
||||
"description": "Custom claim to use."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3482,7 +3711,32 @@
|
|||
"production": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "By default a test certificate will be obtained from Let's Encrypt. Always start by getting a test certificate and make sure that works before setting this to true and obtaining a production certificate. Making too many bad requests for a production certificate will get you banned for a long period of time."
|
||||
"description": "By default a test certificate will be obtained from Let's Encrypt. Setting \"zerossl\", will ignore this setting. Always start by getting a test certificate and make sure that works before setting this to true and obtaining a production certificate. Making too many bad requests for a production certificate will get you banned for a long period of time."
|
||||
},
|
||||
"nochecks": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If you choose \"true\", MeshCentral won't verify if \"email\" is valid, has a valid MX record, AND if \"names\" doesn't contain a wildcard, can be resolved by DNS A/AAAA record."
|
||||
},
|
||||
"zerossl": {
|
||||
"type": "object",
|
||||
"description": "If this object is set, we will use ZeroSSL for SSL creation instead of Let's Encrypt",
|
||||
"required": [
|
||||
"kid",
|
||||
"hmacKey"
|
||||
],
|
||||
"properties": {
|
||||
"kid": {
|
||||
"type": "string",
|
||||
"description": "EAB KID",
|
||||
"default": ""
|
||||
},
|
||||
"hmackey": {
|
||||
"type": "string",
|
||||
"description": "EAB HMAC KEY",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -3553,22 +3807,65 @@
|
|||
"description": "Connects MeshCentral to a SMTP email server, allows MeshCentral to send email messages for 2FA or user notification.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"format": "hostname",
|
||||
"description": "Optional hostname of the client, this defaults to the hostname of the machine. This is useful for SMTP relays. This can also be set to \"console\" for console output debugging."
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"format": "hostname"
|
||||
"format": "hostname",
|
||||
"description": "Hostname of the SMTP server."
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
"maximum": 65535,
|
||||
"default": 587,
|
||||
"description": "SMTP server port number. This defaults to 587 if \"tls\" is false or 465 if \"tls\" is true)"
|
||||
},
|
||||
"from": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "Email address used in the messages from field."
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"description": "SMTP username."
|
||||
},
|
||||
"pass": {
|
||||
"type": "string",
|
||||
"description": "SMTP password."
|
||||
},
|
||||
"tls": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Set SMTP to use TLS on connections, the default is false"
|
||||
},
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"description": "This is used for OAuth2 authentication",
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"refreshToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"default": "login",
|
||||
"description": "Setting this indicates the authetication type, 'login' as default or 'oauth2'"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"clientId",
|
||||
"clientSecret",
|
||||
"refreshToken"
|
||||
]
|
||||
},
|
||||
"tlscertcheck": {
|
||||
"type": "boolean"
|
||||
|
|
@ -3580,6 +3877,11 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "When set to false, the email format and DNS MX record are not checked."
|
||||
},
|
||||
"emailDelaySeconds": {
|
||||
"type": "integer",
|
||||
"default": 300,
|
||||
"description": "Time to wait before sending a device connection/disconnection notification email. If many events occur, they will be merged into a single email."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -3589,6 +3891,36 @@
|
|||
"tls"
|
||||
]
|
||||
},
|
||||
"sendmail": {
|
||||
"title": "Send email using the sendmail command",
|
||||
"description": "Makes MeshCentral send emails using the Unix sendmail command. Allows MeshCentral to send email messages for 2FA or user notification.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newline": {
|
||||
"type": "string",
|
||||
"default": "unix",
|
||||
"description": "Possible values are unix or windows"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"default": "sendmail",
|
||||
"description": "Path to the sendmail command"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": null,
|
||||
"description": "Array or arguments to pass to sendmail"
|
||||
},
|
||||
"emailDelaySeconds": {
|
||||
"type": "integer",
|
||||
"default": 300,
|
||||
"description": "Time to wait before sending a device connection/disconnection notification email. If many events occur, they will be merged into a single email."
|
||||
}
|
||||
}
|
||||
},
|
||||
"sms": {
|
||||
"title": "SMS provider",
|
||||
"description": "Connects MeshCentral to a SMS text messaging provider, allows MeshCentral to send SMS messages for 2FA or user notification.",
|
||||
|
|
|
|||
348
meshcentral.js
|
|
@ -139,9 +139,23 @@ function CreateMeshCentralServer(config, args) {
|
|||
try { require('./pass').hash('test', function () { }, 0); } catch (ex) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
|
||||
|
||||
// Check for invalid arguments
|
||||
const validArguments = ['_', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showitem', 'listuserids', 'showusergroups', 'shownodes', 'showallmeshes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbfix', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'usenodedefaulttlsciphers', 'tlsciphers', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats', 'translate', 'createaccount', 'setuptelegram', 'resetaccount', 'pass', 'removesubdomain', 'adminaccount', 'domain', 'email', 'configfile', 'maintenancemode', 'nedbtodb', 'removetestagents', 'agentupdatetest', 'hashpassword', 'hashpass', 'indexmcrec', 'mpsdebug', 'dumpcores', 'dev'];
|
||||
const validArguments = ['_', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showitem', 'listuserids', 'showusergroups', 'shownodes', 'showallmeshes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbfix', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'usenodedefaulttlsciphers', 'tlsciphers', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'oldencrypt', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats', 'translate', 'createaccount', 'setuptelegram', 'resetaccount', 'pass', 'removesubdomain', 'adminaccount', 'domain', 'email', 'configfile', 'maintenancemode', 'nedbtodb', 'removetestagents', 'agentupdatetest', 'hashpassword', 'hashpass', 'indexmcrec', 'mpsdebug', 'dumpcores', 'dev', 'mysql', 'mariadb', 'trustedproxy'];
|
||||
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
|
||||
const ENVVAR_PREFIX = "meshcentral_"
|
||||
let envArgs = []
|
||||
for (let [envvar, envval] of Object.entries(process.env)) {
|
||||
if (envvar.toLocaleLowerCase().startsWith(ENVVAR_PREFIX)) {
|
||||
let argname = envvar.slice(ENVVAR_PREFIX.length).toLocaleLowerCase()
|
||||
if (!!argname && !(validArguments.indexOf(argname) == -1)) {
|
||||
envArgs = envArgs.concat([`--${argname}`, envval])
|
||||
}
|
||||
}
|
||||
}
|
||||
envArgs = require('minimist')(envArgs)
|
||||
obj.args = Object.assign(envArgs, obj.args)
|
||||
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
|
||||
if (obj.args.mysql == true) { console.log('Must specify: --mysql [connectionstring] \r\nExample mysql://user:password@127.0.0.1:3306/database'); return; }
|
||||
if (obj.args.mariadb == true) { console.log('Must specify: --mariadb [connectionstring] \r\nExample mariadb://user:password@127.0.0.1:3306/database'); return; }
|
||||
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
|
||||
|
||||
if ((obj.args.help == true) || (obj.args['?'] == true)) {
|
||||
|
|
@ -220,6 +234,12 @@ function CreateMeshCentralServer(config, args) {
|
|||
translateEngine.startEx(['', '', 'translateall', translationFile]);
|
||||
translateEngine.startEx(['', '', 'extractall', translationFile]);
|
||||
didSomething = true;
|
||||
} else {
|
||||
// Translate all of the default files
|
||||
translateEngine.startEx(['', '', 'minifyall']);
|
||||
translateEngine.startEx(['', '', 'translateall']);
|
||||
translateEngine.startEx(['', '', 'extractall']);
|
||||
didSomething = true;
|
||||
}
|
||||
|
||||
// Check if "meshcentral-web" exists, if so, translate all pages in that folder.
|
||||
|
|
@ -235,11 +255,34 @@ function CreateMeshCentralServer(config, args) {
|
|||
files = obj.fs.readdirSync(obj.webViewsOverridePath);
|
||||
for (var i in files) {
|
||||
var file = obj.path.join(obj.webViewsOverridePath, files[i]);
|
||||
if (file.endsWith('.handlebars') || file.endsWith('-min.handlebars')) {
|
||||
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
|
||||
translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check domains and see if "meshcentral-web-DOMAIN" exists, if so, translate all pages in that folder
|
||||
for (i in obj.config.domains) {
|
||||
if (i == "") continue;
|
||||
var path = obj.path.join(obj.datapath, '..', 'meshcentral-web-' + i, 'views');
|
||||
if (require('fs').existsSync(path)) {
|
||||
didSomething = true;
|
||||
var files = obj.fs.readdirSync(path);
|
||||
for (var a in files) {
|
||||
var file = obj.path.join(path, files[a]);
|
||||
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
|
||||
translateEngine.startEx(['', '', 'minify', file]);
|
||||
}
|
||||
}
|
||||
files = obj.fs.readdirSync(path);
|
||||
for (var a in files) {
|
||||
var file = obj.path.join(path, files[a]);
|
||||
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
|
||||
translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (obj.webPublicOverridePath != null) {
|
||||
didSomething = true;
|
||||
|
|
@ -254,6 +297,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
*/
|
||||
|
||||
if (didSomething == false) { console.log("Nothing to do."); }
|
||||
console.log('Finished Translating.')
|
||||
process.exit();
|
||||
return;
|
||||
}
|
||||
|
|
@ -350,6 +394,92 @@ function CreateMeshCentralServer(config, args) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FreeBSD background service handling, MUST USE SPAWN FOR SERVICE COMMANDS!
|
||||
if (obj.platform == 'freebsd') {
|
||||
if (obj.args.install == true) {
|
||||
// Install MeshCentral in rc.d
|
||||
console.log('Installing MeshCentral as background Service...');
|
||||
var systemdConf = "/usr/local/etc/rc.d/meshcentral";
|
||||
const userinfo = require('os').userInfo();
|
||||
console.log('Writing config file...');
|
||||
require('child_process').exec('which node', {}, function (error, stdout, stderr) {
|
||||
if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; }
|
||||
const nodePath = stdout.substring(0, stdout.indexOf('\n'));
|
||||
const config = '#!/bin/sh\n# MeshCentral FreeBSD Service Script\n# PROVIDE: meshcentral\n# REQUIRE: NETWORKING\n# KEYWORD: shutdown\n. /etc/rc.subr\nname=meshcentral\nuser=' + userinfo.username + '\nrcvar=meshcentral_enable\n: \\${meshcentral_enable:=\\"NO\\"}\n: \\${meshcentral_args:=\\"\\"}\npidfile=/var/run/meshcentral/meshcentral.pid\ncommand=\\"/usr/sbin/daemon\\"\nmeshcentral_chdir=\\"' + obj.parentpath + '\\"\ncommand_args=\\"-r -u \\${user} -P \\${pidfile} -S -T meshcentral -m 3 ' + nodePath + ' ' + __dirname + ' \\${meshcentral_args}\\"\nload_rc_config \\$name\nrun_rc_command \\"\\$1\\"\n';
|
||||
require('child_process').exec('echo \"' + config + '\" | tee ' + systemdConf + ' && chmod +x ' + systemdConf, {}, function (error, stdout, stderr) {
|
||||
if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; }
|
||||
console.log('Enabling service...');
|
||||
require('child_process').exec('sysrc meshcentral_enable="YES"', {}, function (error, stdout, stderr) {
|
||||
if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; }
|
||||
if (stdout.length > 0) { console.log(stdout); }
|
||||
console.log('Starting service...');
|
||||
const service = require('child_process').spawn('service', ['meshcentral', 'start']);
|
||||
service.stdout.on('data', function (data) { console.log(data.toString()); });
|
||||
service.stderr.on('data', function (data) { console.log(data.toString()); });
|
||||
service.on('exit', function (code) {
|
||||
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service');
|
||||
process.exit(); // Must exit otherwise we just hang
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else if (obj.args.uninstall == true) {
|
||||
// Uninstall MeshCentral in rc.d
|
||||
console.log('Uninstalling MeshCentral background service...');
|
||||
var systemdConf = "/usr/local/etc/rc.d/meshcentral";
|
||||
console.log('Stopping service...');
|
||||
const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
|
||||
service.stdout.on('data', function (data) { console.log(data.toString()); });
|
||||
service.stderr.on('data', function (data) { console.log(data.toString()); });
|
||||
service.on('exit', function (code) {
|
||||
if (code !== 0) { console.log('ERROR: Unable to stop MeshCentral as a service'); }
|
||||
console.log('Disabling service...');
|
||||
require('child_process').exec('sysrc -x meshcentral_enable', {}, function (err, stdout, stderr) {
|
||||
if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); }
|
||||
if (stdout.length > 0) { console.log(stdout); }
|
||||
console.log('Removing config file...');
|
||||
require('child_process').exec('rm ' + systemdConf, {}, function (err, stdout, stderr) {
|
||||
if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); }
|
||||
console.log('Done.');
|
||||
process.exit(); // Must exit otherwise we just hang
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else if (obj.args.start == true) {
|
||||
// Start MeshCentral in rc.d
|
||||
const service = require('child_process').spawn('service', ['meshcentral', 'start']);
|
||||
service.stdout.on('data', function (data) { console.log(data.toString()); });
|
||||
service.stderr.on('data', function (data) { console.log(data.toString()); });
|
||||
service.on('exit', function (code) {
|
||||
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service: ' + error);
|
||||
process.exit(); // Must exit otherwise we just hang
|
||||
});
|
||||
return;
|
||||
} else if (obj.args.stop == true) {
|
||||
// Stop MeshCentral in rc.d
|
||||
const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
|
||||
service.stdout.on('data', function (data) { console.log(data.toString()); });
|
||||
service.stderr.on('data', function (data) { console.log(data.toString()); });
|
||||
service.on('exit', function (code) {
|
||||
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to stop MeshCentral as a service: ' + error);
|
||||
process.exit(); // Must exit otherwise we just hang
|
||||
});
|
||||
return;
|
||||
} else if (obj.args.restart == true) {
|
||||
// Restart MeshCentral in rc.d
|
||||
const service = require('child_process').spawn('service', ['meshcentral', 'restart']);
|
||||
service.stdout.on('data', function (data) { console.log(data.toString()); });
|
||||
service.stderr.on('data', function (data) { console.log(data.toString()); });
|
||||
service.on('exit', function (code) {
|
||||
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to restart MeshCentral as a service: ' + error);
|
||||
process.exit(); // Must exit otherwise we just hang
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Index a recorded file
|
||||
if (obj.args.indexmcrec != null) {
|
||||
|
|
@ -453,8 +583,11 @@ function CreateMeshCentralServer(config, args) {
|
|||
// Launch MeshCentral as a child server and monitor it.
|
||||
obj.launchChildServer = function (startArgs) {
|
||||
const child_process = require('child_process');
|
||||
const isInspectorAttached = (()=> { try { return require('node:inspector').url() !== undefined; } catch (_) { return false; } }).call();
|
||||
const logFromChildProcess = isInspectorAttached ? () => {} : console.log.bind(console);
|
||||
try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
|
||||
try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { }
|
||||
if (startArgs[0] != "--disable-proto=delete") startArgs.unshift("--disable-proto=delete")
|
||||
childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
|
||||
if (childProcess.xrestart == 1) {
|
||||
setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart.
|
||||
|
|
@ -526,12 +659,12 @@ function CreateMeshCentralServer(config, args) {
|
|||
else if (data.indexOf('Starting self upgrade to: ') >= 0) { obj.args.specificupdate = data.substring(26).split('\r')[0].split('\n')[0]; childProcess.xrestart = 3; }
|
||||
var datastr = data;
|
||||
while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); }
|
||||
console.log(datastr);
|
||||
logFromChildProcess(datastr);
|
||||
});
|
||||
childProcess.stderr.on('data', function (data) {
|
||||
var datastr = data;
|
||||
while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); }
|
||||
console.log('ERR: ' + datastr);
|
||||
logFromChildProcess('ERR: ' + datastr);
|
||||
if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock
|
||||
if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); }
|
||||
obj.logError(data);
|
||||
|
|
@ -985,7 +1118,27 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Show a list of all configuration files in the database
|
||||
if (obj.args.dblistconfigfiles) {
|
||||
obj.db.GetAllType('cfile', function (err, docs) { if (err == null) { if (docs.length == 0) { console.log("No files found."); } else { for (var i in docs) { console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' bytes.'); } } } else { console.log('Unable to read from database.'); } process.exit(); }); return;
|
||||
obj.db.GetAllType('cfile', function (err, docs) {
|
||||
if (err == null) {
|
||||
if (docs.length == 0) {
|
||||
console.log("No files found.");
|
||||
} else {
|
||||
for (var i in docs) {
|
||||
if (typeof obj.args.dblistconfigfiles == 'string') {
|
||||
const data = obj.db.decryptData(obj.args.dblistconfigfiles, docs[i].data);
|
||||
if (data == null) {
|
||||
console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' encrypted bytes - Unable to decrypt.');
|
||||
} else {
|
||||
console.log(docs[i]._id.split('/')[1] + ', ' + data.length + ' bytes, decoded correctly.');
|
||||
}
|
||||
} else {
|
||||
console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' encrypted bytes.');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { console.log('Unable to read from database.'); } process.exit();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the content of a configuration file in the database
|
||||
|
|
@ -1030,7 +1183,11 @@ function CreateMeshCentralServer(config, args) {
|
|||
const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
|
||||
console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
|
||||
lockCount++;
|
||||
obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
|
||||
if (obj.args.oldencrypt) {
|
||||
obj.db.setConfigFile(file, obj.db.oldEncryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
|
||||
} else {
|
||||
obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (--lockCount == 0) { process.exit(); }
|
||||
|
|
@ -1194,7 +1351,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
}
|
||||
|
||||
// Check if the database is capable of performing a backup
|
||||
obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
||||
// Moved behind autobackup config init in startex4: obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
||||
|
||||
// Load configuration for database if needed
|
||||
if (obj.args.loadconfigfromdb) {
|
||||
|
|
@ -1307,6 +1464,10 @@ function CreateMeshCentralServer(config, args) {
|
|||
if ((obj.config.domains[i].loginkey != null) && (obj.common.validateAlphaNumericArray(obj.config.domains[i].loginkey, 1, 128) == false)) { console.log("ERROR: Invalid login key, must be alpha-numeric string with no spaces."); process.exit(); return; }
|
||||
if (typeof obj.config.domains[i].agentkey == 'string') { obj.config.domains[i].agentkey = [obj.config.domains[i].agentkey]; }
|
||||
if ((obj.config.domains[i].agentkey != null) && (obj.common.validateAlphaNumericArray(obj.config.domains[i].agentkey, 1, 128) == false)) { console.log("ERROR: Invalid agent key, must be alpha-numeric string with no spaces."); process.exit(); return; }
|
||||
obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip = readIpListFromFile(obj.config.domains[i].userallowedip);
|
||||
obj.config.domains[i].userblockedip = obj.config.domains[i].userblockedip = readIpListFromFile(obj.config.domains[i].userblockedip);
|
||||
obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip = readIpListFromFile(obj.config.domains[i].agentallowedip);
|
||||
obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip = readIpListFromFile(obj.config.domains[i].agentblockedip);
|
||||
if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { delete obj.config.domains[i].userallowedip; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(' ').join('').split(','); } }
|
||||
if (typeof obj.config.domains[i].userblockedip == 'string') { if (obj.config.domains[i].userblockedip == '') { delete obj.config.domains[i].userblockedip; } else { obj.config.domains[i].userblockedip = obj.config.domains[i].userblockedip.split(' ').join('').split(','); } }
|
||||
if (typeof obj.config.domains[i].agentallowedip == 'string') { if (obj.config.domains[i].agentallowedip == '') { delete obj.config.domains[i].agentallowedip; } else { obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip.split(' ').join('').split(','); } }
|
||||
|
|
@ -1447,8 +1608,18 @@ function CreateMeshCentralServer(config, args) {
|
|||
if (obj.args.rediraliasport != null && (typeof obj.args.rediraliasport != 'number')) obj.args.rediraliasport = null;
|
||||
if (obj.args.redirport == null) obj.args.redirport = 80;
|
||||
if (obj.args.minifycore == null) obj.args.minifycore = false;
|
||||
if (typeof args.agentidletimeout != 'number') { args.agentidletimeout = 150000; } else { args.agentidletimeout *= 1000 } // Default agent idle timeout is 2m, 30sec.
|
||||
if ((obj.args.lanonly != true) && (obj.args.webrtconfig == null)) { obj.args.webrtconfig = { iceservers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.services.mozilla.com' }] }; } // Setup default WebRTC STUN servers
|
||||
if (typeof obj.args.agentidletimeout != 'number') { obj.args.agentidletimeout = 150000; } else { obj.args.agentidletimeout *= 1000 } // Default agent idle timeout is 2m, 30sec.
|
||||
if ((obj.args.lanonly != true) && (typeof obj.args.webrtconfig == 'object')) { // fix incase you are using an old mis-spelt webrtconfig
|
||||
obj.args.webrtcconfig = obj.args.webrtconfig;
|
||||
delete obj.args.webrtconfig;
|
||||
}
|
||||
if ((obj.args.lanonly != true) && (obj.args.webrtcconfig == null)) { obj.args.webrtcconfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.cloudflare.com:3478' }] }; } // Setup default WebRTC STUN servers
|
||||
else if ((obj.args.lanonly != true) && (typeof obj.args.webrtcconfig == 'object')) {
|
||||
if (obj.args.webrtcconfig.iceservers) { // webrtc is case-sensitive, so must rename iceservers to iceServers!
|
||||
obj.args.webrtcconfig.iceServers = obj.args.webrtcconfig.iceservers;
|
||||
delete obj.args.webrtcconfig.iceservers;
|
||||
}
|
||||
}
|
||||
if (typeof obj.args.ignoreagenthashcheck == 'string') { if (obj.args.ignoreagenthashcheck == '') { delete obj.args.ignoreagenthashcheck; } else { obj.args.ignoreagenthashcheck = obj.args.ignoreagenthashcheck.split(','); } }
|
||||
|
||||
// Setup a site administrator
|
||||
|
|
@ -1488,7 +1659,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
}
|
||||
|
||||
// Setup agent error log
|
||||
if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump != null)) {
|
||||
if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump)) {
|
||||
obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; })
|
||||
}
|
||||
|
||||
|
|
@ -1824,9 +1995,17 @@ function CreateMeshCentralServer(config, args) {
|
|||
if (typeof config.settings.webpush.gcmapi == 'string') { webpush.setGCMAPIKey(config.settings.webpush.gcmapi); }
|
||||
}
|
||||
|
||||
// Get the current node version
|
||||
const verSplit = process.version.substring(1).split('.');
|
||||
var nodeVersion = parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100);
|
||||
|
||||
// Setup Firebase
|
||||
if ((config.firebase != null) && (typeof config.firebase.senderid == 'string') && (typeof config.firebase.serverkey == 'string')) {
|
||||
obj.firebase = require('./firebase').CreateFirebase(obj, config.firebase.senderid, config.firebase.serverkey);
|
||||
addServerWarning('Firebase now requires a service account JSON file, Firebase disabled.', 27);
|
||||
} else if ((config.firebase != null) && (typeof config.firebase.serviceaccountfile == 'string')) {
|
||||
var serviceAccount;
|
||||
try { serviceAccount = JSON.parse(obj.fs.readFileSync(obj.path.join(obj.datapath, config.firebase.serviceaccountfile)).toString()); } catch (ex) { console.log(ex); }
|
||||
if (serviceAccount != null) { obj.firebase = require('./firebase').CreateFirebase(obj, serviceAccount); }
|
||||
} else if ((typeof config.firebaserelay == 'object') && (typeof config.firebaserelay.url == 'string')) {
|
||||
// Setup the push messaging relay
|
||||
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key);
|
||||
|
|
@ -1835,8 +2014,12 @@ function CreateMeshCentralServer(config, args) {
|
|||
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://alt.meshcentral.com/firebaserelay.aspx');
|
||||
}
|
||||
|
||||
// Setup monitoring
|
||||
obj.monitoring = require('./monitoring.js').CreateMonitoring(obj, obj.args);
|
||||
|
||||
// Start periodic maintenance
|
||||
obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
|
||||
//obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 10 * 1); // DEBUG: Run this more often
|
||||
|
||||
// Dispatch an event that the server is now running
|
||||
obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
|
||||
|
|
@ -1921,15 +2104,25 @@ function CreateMeshCentralServer(config, args) {
|
|||
obj.updateServerState('state', "running");
|
||||
|
||||
// Setup auto-backup defaults
|
||||
if (obj.config.settings.autobackup == null) { obj.config.settings.autobackup = { backupintervalhours: 24, keeplastdaysbackup: 10 }; }
|
||||
else if (obj.config.settings.autobackup === false) { delete obj.config.settings.autobackup; }
|
||||
|
||||
// Check that autobackup path is not within the "meshcentral-data" folder.
|
||||
if ((typeof obj.config.settings.autobackup == 'object') && (typeof obj.config.settings.autobackup.backuppath == 'string') && (obj.path.normalize(obj.config.settings.autobackup.backuppath).startsWith(obj.path.normalize(obj.datapath)))) {
|
||||
addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21);
|
||||
delete obj.config.settings.autobackup;
|
||||
if (obj.config.settings.autobackup == false || obj.config.settings.autobackup == 'false') { obj.config.settings.autobackup = {backupintervalhours: 0}; } //no schedule, but able to console autobackup
|
||||
else {
|
||||
if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
|
||||
if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
|
||||
if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
|
||||
if (obj.config.settings.autobackup.backuphour != null ) { obj.config.settings.autobackup.backupintervalhours = 24; if ((typeof obj.config.settings.autobackup.backuphour != 'number') || (obj.config.settings.autobackup.backuphour > 23 || obj.config.settings.autobackup.backuphour < 0 )) { obj.config.settings.autobackup.backuphour = 0; }}
|
||||
else {obj.config.settings.autobackup.backuphour = -1 };
|
||||
//arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !!
|
||||
if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []}
|
||||
else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); };
|
||||
if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []}
|
||||
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
|
||||
if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath };
|
||||
if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'};
|
||||
}
|
||||
|
||||
// Check if the database is capable of performing a backup
|
||||
obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
|
||||
|
||||
// Load Intel AMT passwords from the "amtactivation.log" file
|
||||
obj.loadAmtActivationLogPasswords(function (amtPasswords) {
|
||||
obj.amtPasswords = amtPasswords;
|
||||
|
|
@ -2090,14 +2283,19 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Check if we need to perform an automatic backup
|
||||
function checkAutobackup() {
|
||||
if (obj.config.settings.autobackup && (typeof obj.config.settings.autobackup.backupintervalhours == 'number')) {
|
||||
if (obj.config.settings.autobackup.backupintervalhours >= 1 ) {
|
||||
obj.db.Get('LastAutoBackupTime', function (err, docs) {
|
||||
if (err != null) return;
|
||||
if (err != null) { console.error("checkAutobackup: Error getting LastBackupTime from DB"); return}
|
||||
var lastBackup = 0;
|
||||
const now = new Date().getTime();
|
||||
const currentdate = new Date();
|
||||
let currentHour = currentdate.getHours();
|
||||
let now = currentdate.getTime();
|
||||
if (docs.length == 1) { lastBackup = docs[0].value; }
|
||||
const delta = now - lastBackup;
|
||||
if (delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) {
|
||||
//const delta = 9999999999; // DEBUG: backup always
|
||||
obj.debug ('backup', 'Entering checkAutobackup, lastAutoBackupTime: ' + new Date(lastBackup).toLocaleString('default', { dateStyle: 'medium', timeStyle: 'short' }) + ', delta: ' + (delta/(1000*60*60)).toFixed(2) + ' hours');
|
||||
//start autobackup if interval has passed or at configured hour, whichever comes first. When an hour schedule is missed, it will make a backup immediately.
|
||||
if ((delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) || ((currentHour == obj.config.settings.autobackup.backuphour) && (delta >= 2 * 60 * 60 * 1000))) {
|
||||
// A new auto-backup is required.
|
||||
obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database
|
||||
obj.db.performBackup(); // Perform the backup
|
||||
|
|
@ -2219,6 +2417,10 @@ function CreateMeshCentralServer(config, args) {
|
|||
storeEvent.links = Object.assign({}, storeEvent.links);
|
||||
for (var i in storeEvent.links) { var ue = obj.common.escapeFieldName(i); if (ue !== i) { storeEvent.links[ue] = storeEvent.links[i]; delete storeEvent.links[i]; } }
|
||||
}
|
||||
if (storeEvent.mesh) {
|
||||
// Escape "mesh" names that may have "." and/or "$"
|
||||
storeEvent.mesh = obj.common.escapeLinksFieldNameEx(storeEvent.mesh);
|
||||
}
|
||||
storeEvent.ids = ids;
|
||||
obj.db.StoreEvent(storeEvent);
|
||||
}
|
||||
|
|
@ -2309,7 +2511,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Event any changes on this server only
|
||||
if ((newConnectivity != oldPowerState) || (newPowerState != oldPowerState)) {
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: newConnectivity, pwr: newPowerState, nolog: 1, nopeers: 1 });
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: newConnectivity, pwr: newPowerState, nolog: 1, nopeers: 1, id: Math.random() });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -2490,7 +2692,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Event the node connection change
|
||||
if (eventConnectChange == 1) {
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, ct: connectTime, nolog: 1, nopeers: 1 });
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, ct: connectTime, nolog: 1, nopeers: 1, id: Math.random() });
|
||||
|
||||
// Save indication of node connection change
|
||||
const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType };
|
||||
|
|
@ -2594,7 +2796,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Event the node connection change
|
||||
if (eventConnectChange == 1) {
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, nolog: 1, nopeers: 1 });
|
||||
obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, nolog: 1, nopeers: 1, id: Math.random() });
|
||||
|
||||
// Notify any users of device disconnection
|
||||
obj.NotifyUserOfDeviceStateChange(meshid, nodeid, Date.now(), connectType, -1, serverid, false, extraInfo);
|
||||
|
|
@ -3052,13 +3254,13 @@ function CreateMeshCentralServer(config, args) {
|
|||
|
||||
// Setup the time server
|
||||
var timeStampUrl = 'http://timestamp.comodoca.com/authenticode';
|
||||
if (args.agenttimestampserver === false) { timeStampUrl = null; }
|
||||
else if (typeof args.agenttimestampserver == 'string') { timeStampUrl = args.agenttimestampserver; }
|
||||
if (obj.args.agenttimestampserver === false) { timeStampUrl = null; }
|
||||
else if (typeof obj.args.agenttimestampserver == 'string') { timeStampUrl = obj.args.agenttimestampserver; }
|
||||
|
||||
// Setup the time server proxy
|
||||
var timeStampProxy = null;
|
||||
if (typeof args.agenttimestampproxy == 'string') { timeStampProxy = args.agenttimestampproxy; }
|
||||
else if ((args.agenttimestampproxy !== false) && (typeof args.npmproxy == 'string')) { timeStampProxy = args.npmproxy; }
|
||||
if (typeof obj.args.agenttimestampproxy == 'string') { timeStampProxy = obj.args.agenttimestampproxy; }
|
||||
else if ((obj.args.agenttimestampproxy !== false) && (typeof obj.args.npmproxy == 'string')) { timeStampProxy = obj.args.npmproxy; }
|
||||
|
||||
// Setup the pending operations counter
|
||||
var pendingOperations = 1;
|
||||
|
|
@ -3543,7 +3745,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
try {
|
||||
const iv = Buffer.from(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key.slice(0, 32), iv);
|
||||
const crypted = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
|
||||
return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString(obj.args.cookieencoding ? obj.args.cookieencoding : 'base64');
|
||||
return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString(obj.args.cookieencoding ? obj.args.cookieencoding : 'base64').replace(/\+/g, '@').replace(/\//g, '$');
|
||||
} catch (ex) { return null; }
|
||||
}
|
||||
|
||||
|
|
@ -3552,7 +3754,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
if ((typeof data != 'string') || (data.length < 13)) return {};
|
||||
if (key == null) { key = obj.loginCookieEncryptionKey; }
|
||||
try {
|
||||
const buf = Buffer.from(data, 'base64');
|
||||
const buf = Buffer.from(data.replace(/\@/g, '+').replace(/\$/g, '/'), obj.args.cookieencoding ? obj.args.cookieencoding : 'base64');
|
||||
const decipher = obj.crypto.createDecipheriv('aes-256-gcm', key.slice(0, 32), buf.slice(0, 12));
|
||||
decipher.setAuthTag(buf.slice(12, 28));
|
||||
return JSON.parse(decipher.update(buf.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
|
||||
|
|
@ -3674,7 +3876,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
// Send event to log file
|
||||
if (obj.config.settings && obj.config.settings.log) {
|
||||
if (typeof obj.args.log == 'string') { obj.args.log = obj.args.log.split(','); }
|
||||
if (obj.args.log.indexOf(source) >= 0) {
|
||||
if ((obj.args.log.indexOf(source) >= 0) || (obj.args.log[0] == '*')) {
|
||||
const d = new Date();
|
||||
if (obj.xxLogFile == null) {
|
||||
try {
|
||||
|
|
@ -3686,7 +3888,8 @@ function CreateMeshCentralServer(config, args) {
|
|||
if (obj.xxLogFile != null) {
|
||||
try {
|
||||
if (obj.xxLogDateStr != d.toLocaleDateString()) { obj.xxLogDateStr = d.toLocaleDateString(); obj.fs.writeSync(obj.xxLogFile, '---- ' + d.toLocaleDateString() + ' ----\r\n'); }
|
||||
obj.fs.writeSync(obj.xxLogFile, new Date().toLocaleTimeString() + ' - ' + source + ': ' + args.join(', ') + '\r\n');
|
||||
const formattedArgs = args.map(function (arg) { return (typeof arg === 'object' && arg !== null) ? JSON.stringify(arg) : arg; });
|
||||
obj.fs.writeSync(obj.xxLogFile, new Date().toLocaleTimeString() + ' - ' + source + ': ' + formattedArgs.join(', ') + '\r\n');
|
||||
} catch (ex) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -3730,7 +3933,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
function readIpListFromFile(arg) {
|
||||
if ((typeof arg != 'string') || (!arg.startsWith('file:'))) return arg;
|
||||
var lines = null;
|
||||
try { lines = obj.fs.readFileSync(obj.path.join(obj.datapath, arg.substring(5))).toString().split('\r\n').join('\r').split('\r'); } catch (ex) { }
|
||||
try { lines = obj.fs.readFileSync(obj.path.join(obj.datapath, arg.substring(5))).toString().split(/\r?\n/).join('\r').split('\r'); } catch (ex) { }
|
||||
if (lines == null) return null;
|
||||
const validLines = [];
|
||||
for (var i in lines) { if ((lines[i].length > 0) && (((lines[i].charAt(0) > '0') && (lines[i].charAt(0) < '9')) || (lines[i].charAt(0) == ':'))) validLines.push(lines[i]); }
|
||||
|
|
@ -3743,6 +3946,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); }
|
||||
function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); }
|
||||
obj.getServerWarnings = function () { return serverWarnings; }
|
||||
// TODO: migrate from other addServerWarning function and add timestamp
|
||||
obj.addServerWarning = function (msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
||||
|
||||
// auth.log functions
|
||||
|
|
@ -3781,7 +3985,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
function checkResolveAll(names, func) {
|
||||
const dns = require('dns'), state = { func: func, count: names.length, err: null };
|
||||
for (var i in names) {
|
||||
dns.resolve(names[i], function (err, records) {
|
||||
dns.lookup(names[i], { all: true }, function (err, records) {
|
||||
if (err != null) { if (this.state.err == null) { this.state.err = [this.name]; } else { this.state.err.push(this.name); } }
|
||||
if (--this.state.count == 0) { this.state.func(this.state.err); }
|
||||
}.bind({ name: names[i], state: state }))
|
||||
|
|
@ -3840,14 +4044,31 @@ function InstallModules(modules, args, func) {
|
|||
for (var i in modules) {
|
||||
// Modules may contain a version tag (foobar@1.0.0), remove it so the module can be found using require
|
||||
const moduleNameAndVersion = modules[i];
|
||||
const moduleInfo = moduleNameAndVersion.split('@', 2);
|
||||
var moduleName = moduleInfo[0];
|
||||
var moduleVersion = moduleInfo[1];
|
||||
if (moduleName == '') { moduleName = moduleNameAndVersion; moduleVersion = null; } // If the module name starts with @, don't use @ as a version seperator.
|
||||
const moduleInfo = moduleNameAndVersion.split('@', 3);
|
||||
var moduleName = null;
|
||||
var moduleVersion = null;
|
||||
if(moduleInfo.length == 1){ // normal package without version
|
||||
moduleName = moduleInfo[0];
|
||||
} else if (moduleInfo.length == 2) { // normal package with a version OR custom repo package with no version
|
||||
moduleName = moduleInfo[0] === '' ? moduleNameAndVersion : moduleInfo[0];
|
||||
moduleVersion = moduleInfo[0] === '' ? null : moduleInfo[1];
|
||||
} else if (moduleInfo.length == 3) { // custom repo package and package with a version
|
||||
moduleName = "@" + moduleInfo[1];
|
||||
moduleVersion = moduleInfo[2];
|
||||
}
|
||||
try {
|
||||
// Does the module need a specific version?
|
||||
if (moduleVersion) {
|
||||
if (require(`${moduleName}/package.json`).version != moduleVersion) { throw new Error(); }
|
||||
var versionMatch = false;
|
||||
var modulePath = null;
|
||||
// This is the first way to test if a module is already installed.
|
||||
try { versionMatch = (require(`${moduleName}/package.json`).version == moduleVersion) } catch (ex) {
|
||||
if (ex.code == "ERR_PACKAGE_PATH_NOT_EXPORTED") { modulePath = ("" + ex).split(' ').at(-1); } else { throw new Error(); }
|
||||
}
|
||||
// If the module is not installed, but we get the ERR_PACKAGE_PATH_NOT_EXPORTED error, try a second way.
|
||||
if ((versionMatch == false) && (modulePath != null)) {
|
||||
if (JSON.parse(require('fs').readFileSync(modulePath, 'utf8')).version != moduleVersion) { throw new Error(); }
|
||||
}
|
||||
} else {
|
||||
// For all other modules, do the check here.
|
||||
// Is the module in package.json? Install exact version.
|
||||
|
|
@ -3896,6 +4117,7 @@ function InstallModuleEx(modulenames, args, func) {
|
|||
process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
|
||||
|
||||
// Add a server warning, warnings will be shown to the administrator on the web application
|
||||
// TODO: migrate to obj.addServerWarning?
|
||||
const serverWarnings = [];
|
||||
function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
|
||||
|
||||
|
|
@ -3926,7 +4148,8 @@ var ServerWarnings = {
|
|||
23: "Unable to load agent icon file: {0}.",
|
||||
24: "Unable to load agent logo file: {0}.",
|
||||
25: "This NodeJS version does not support OpenID.",
|
||||
26: "This NodeJS version does not support Discord.js."
|
||||
26: "This NodeJS version does not support Discord.js.",
|
||||
27: "Firebase now requires a service account JSON file, Firebase disabled."
|
||||
};
|
||||
*/
|
||||
|
||||
|
|
@ -3936,8 +4159,8 @@ var meshserver = null;
|
|||
var childProcess = null;
|
||||
var previouslyInstalledModules = {};
|
||||
function mainStart() {
|
||||
// Check the NodeJS is version 10 or better.
|
||||
if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 11) { console.log("MeshCentral requires Node v11 or above, current version is " + process.version + "."); return; }
|
||||
// Check the NodeJS is version 16 or better.
|
||||
if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 16) { console.log("MeshCentral requires Node v16 or above, current version is " + process.version + "."); return; }
|
||||
|
||||
// If running within the node_modules folder, move working directory to the parent of the node_modules folder.
|
||||
if (__dirname.endsWith('\\node_modules\\meshcentral') || __dirname.endsWith('/node_modules/meshcentral')) { process.chdir(require('path').join(__dirname, '..', '..')); }
|
||||
|
|
@ -3980,7 +4203,7 @@ function mainStart() {
|
|||
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
|
||||
var sspi = false;
|
||||
var ldap = false;
|
||||
var passport = null;
|
||||
var passport = [];
|
||||
var allsspi = true;
|
||||
var yubikey = false;
|
||||
var ssh = false;
|
||||
|
|
@ -4001,17 +4224,17 @@ function mainStart() {
|
|||
if (mstsc == false) { config.domains[i].mstsc = false; }
|
||||
if (config.domains[i].ssh == true) { ssh = true; }
|
||||
if ((typeof config.domains[i].authstrategies == 'object')) {
|
||||
if (passport == null) { passport = ['passport']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904
|
||||
if (passport.indexOf('passport') == -1) { passport.push('passport','connect-flash'); } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors
|
||||
if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
|
||||
if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); }
|
||||
if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); }
|
||||
if ((typeof config.domains[i].authstrategies.azure == 'object') && (typeof config.domains[i].authstrategies.azure.clientid == 'string') && (typeof config.domains[i].authstrategies.azure.clientsecret == 'string') && (typeof config.domains[i].authstrategies.azure.tenantid == 'string') && (passport.indexOf('passport-azure-oauth2') == -1)) { passport.push('passport-azure-oauth2'); passport.push('jwt-simple'); }
|
||||
if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client') == -1)) {
|
||||
if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client@5.7.1') == -1)) {
|
||||
if ((nodeVersion >= 17)
|
||||
|| ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13))
|
||||
|| ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15))
|
||||
|| ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) {
|
||||
passport.push('openid-client');
|
||||
passport.push('openid-client@5.7.1');
|
||||
} else {
|
||||
addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
|
||||
delete config.domains[i].authstrategies.oidc;
|
||||
|
|
@ -4022,45 +4245,49 @@ function mainStart() {
|
|||
if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
|
||||
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
|
||||
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
|
||||
if ((typeof config.domains[i].duo2factor == 'object') && (passport.indexOf('@duosecurity/duo_universal') == -1)) { passport.push('@duosecurity/duo_universal'); }
|
||||
}
|
||||
|
||||
// Build the list of required modules
|
||||
// NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile
|
||||
var modules = ['archiver@7.0.0', 'body-parser@1.20.2', 'cbor@5.2.0', 'compression@1.7.4', 'cookie-session@2.0.0', 'express@4.18.2', 'express-handlebars@5.3.5', 'express-ws@4.0.0', 'ipcheck@0.1.0', 'minimist@1.2.8', 'multiparty@4.2.3', '@yetzt/nedb', 'node-forge@1.3.1', 'ua-parser-js@1.0.37', 'ws@8.14.2', 'yauzl@2.10.0'];
|
||||
var modules = ['archiver@7.0.1', 'body-parser@1.20.3', 'cbor@5.2.0', 'compression@1.7.5', 'cookie-session@2.1.0', 'express@4.21.2', 'express-handlebars@7.1.3', 'express-ws@5.0.2', 'ipcheck@0.1.0', 'minimist@1.2.8', 'multiparty@4.2.3', '@seald-io/nedb', 'node-forge@1.3.1', 'ua-parser-js@1.0.39', 'ws@8.18.0', 'yauzl@2.10.0'];
|
||||
if (require('os').platform() == 'win32') { modules.push('node-windows@0.1.14'); modules.push('loadavg-windows@1.1.1'); if (sspi == true) { modules.push('node-sspi@0.2.10'); } } // Add Windows modules
|
||||
if (ldap == true) { modules.push('ldapauth-fork@5.0.5'); }
|
||||
if (ssh == true) { modules.push('ssh2@1.15.0'); }
|
||||
if (ssh == true) { modules.push('ssh2@1.16.0'); }
|
||||
if (passport != null) { modules.push(...passport); }
|
||||
if (captcha == true) { modules.push('svg-captcha@1.4.0'); }
|
||||
|
||||
if (sessionRecording == true) { modules.push('image-size@1.0.2'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
|
||||
if (sessionRecording == true) { modules.push('image-size@1.1.1'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
|
||||
if (config.letsencrypt != null) { modules.push('acme-client@4.2.5'); } // Add acme-client module. We need to force v4.2.4 or higher since olver versions using SHA-1 which is no longer supported by Let's Encrypt.
|
||||
if (config.settings.mqtt != null) { modules.push('aedes@0.39.0'); } // Add MQTT Modules
|
||||
if (config.settings.mysql != null) { modules.push('mysql2@3.6.2'); } // Add MySQL.
|
||||
if (config.settings.mysql != null) { modules.push('mysql2@3.11.4'); } // Add MySQL.
|
||||
//if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/)
|
||||
if (config.settings.mongodb != null) { modules.push('mongodb@4.13.0'); modules.push('saslprep@1.0.3'); } // Add MongoDB, official driver.
|
||||
if (config.settings.postgres != null) { modules.push('pg@8.7.1'); modules.push('pgtools@0.3.2'); } // Add Postgres, Postgres driver.
|
||||
if (config.settings.mariadb != null) { modules.push('mariadb@3.2.2'); } // Add MariaDB, official driver.
|
||||
if (config.settings.postgres != null) { modules.push('pg@8.13.1') } // Add Postgres, official driver.
|
||||
if (config.settings.mariadb != null) { modules.push('mariadb@3.4.0'); } // Add MariaDB, official driver.
|
||||
if (config.settings.acebase != null) { modules.push('acebase@1.29.5'); } // Add AceBase, official driver.
|
||||
if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.6'); } // Add sqlite3, official driver.
|
||||
if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.7'); } // Add sqlite3, official driver.
|
||||
if (config.settings.vault != null) { modules.push('node-vault@0.10.2'); } // Add official HashiCorp's Vault module.
|
||||
if (config.settings.plugins != null) { modules.push('semver@7.5.4'); } // Required for version compat testing and update checks
|
||||
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent@7.0.2'); } // Required for HTTP/HTTPS proxy support
|
||||
else if (config.settings.xmongodb != null) { modules.push('mongojs@3.1.0'); } // Add MongoJS, old driver.
|
||||
if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('nodemailer@6.9.8'); } // Add SMTP support
|
||||
if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('nodemailer@6.9.16'); } // Add SMTP support
|
||||
if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
|
||||
if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('jsdom@22.1.0'); modules.push('esprima@4.0.1'); modules.push('minify-js@0.0.4'); modules.push('html-minifier@4.0.0'); } // Translation support
|
||||
if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('jsdom@22.1.0'); modules.push('esprima@4.0.1'); modules.push('html-minifier@4.0.0'); } // Translation support
|
||||
if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer@0.1.0'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer)
|
||||
if (config.settings.prometheus != null) { modules.push('prom-client'); } // Add Prometheus Metrics support
|
||||
|
||||
if (typeof config.settings.autobackup == 'object') {
|
||||
// Setup encrypted zip support if needed
|
||||
if (config.settings.autobackup.zippassword) { modules.push('archiver-zip-encrypted@1.0.11'); }
|
||||
if (config.settings.autobackup.zippassword) { modules.push('archiver-zip-encrypted@2.0.0'); }
|
||||
// Enable Google Drive Support
|
||||
if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
|
||||
// Enable WebDAV Support
|
||||
if (typeof config.settings.autobackup.webdav == 'object') {
|
||||
if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.3'); }
|
||||
if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.4'); }
|
||||
}
|
||||
// Enable S3 Support
|
||||
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); }
|
||||
}
|
||||
|
||||
// Setup common password blocking
|
||||
|
|
@ -4074,7 +4301,7 @@ function mainStart() {
|
|||
}
|
||||
|
||||
// Desktop multiplexor support
|
||||
if (config.settings.desktopmultiplex === true) { modules.push('image-size@1.0.2'); }
|
||||
if (config.settings.desktopmultiplex === true) { modules.push('image-size@1.1.1'); }
|
||||
|
||||
// SMS support
|
||||
if (config.sms != null) {
|
||||
|
|
@ -4096,8 +4323,7 @@ function mainStart() {
|
|||
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); }
|
||||
|
||||
// Firebase Support
|
||||
// Avoid 0.1.8 due to bugs: https://github.com/guness/node-xcs/issues/43
|
||||
if (config.firebase != null) { modules.push('node-xcs@0.1.7'); }
|
||||
if ((config.firebase != null) && (typeof config.firebase.serviceaccountfile == 'string')) { modules.push('firebase-admin@12.7.0'); }
|
||||
|
||||
// Syslog support
|
||||
if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); }
|
||||
|
|
|
|||
185
meshctrl.js
|
|
@ -16,7 +16,7 @@ var settings = {};
|
|||
const crypto = require('crypto');
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
const path = require('path');
|
||||
const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage'];
|
||||
const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage', 'webrelay'];
|
||||
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
|
||||
|
||||
if (args['_'].length == 0) {
|
||||
|
|
@ -36,6 +36,8 @@ if (args['_'].length == 0) {
|
|||
console.log(" ListEvents - List server events.");
|
||||
console.log(" LoginTokens - List, create and remove login tokens.");
|
||||
console.log(" DeviceInfo - Show information about a device.");
|
||||
console.log(" AddLocalDevice - Add a local device.");
|
||||
console.log(" AddAmtDevice - Add a AMT device.");
|
||||
console.log(" EditDevice - Make changes to a device.");
|
||||
console.log(" RemoveDevice - Delete a device.");
|
||||
console.log(" Config - Perform operation on config.json file.");
|
||||
|
|
@ -63,6 +65,7 @@ if (args['_'].length == 0) {
|
|||
console.log(" Shell - Access command shell of a remote device.");
|
||||
console.log(" Upload - Upload a file to a remote device.");
|
||||
console.log(" Download - Download a file from a remote device.");
|
||||
console.log(" WebRelay - Creates a HTTP/HTTPS webrelay link for a remote device.");
|
||||
console.log(" DeviceOpenUrl - Open a URL on a remote device.");
|
||||
console.log(" DeviceMessage - Open a message box on a remote device.");
|
||||
console.log(" DeviceToast - Display a toast notification on a remote device.");
|
||||
|
|
@ -109,6 +112,22 @@ if (args['_'].length == 0) {
|
|||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
case 'addlocaldevice': {
|
||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||
else if (args.devicename == null) { console.log(winRemoveSingleQuotes("Missing devicename, use --devicename [devicename]")); }
|
||||
else if (args.hostname == null) { console.log(winRemoveSingleQuotes("Missing hostname, use --hostname [hostname]")); }
|
||||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
case 'addamtdevice': {
|
||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||
else if (args.devicename == null) { console.log(winRemoveSingleQuotes("Missing devicename, use --devicename [devicename]")); }
|
||||
else if (args.hostname == null) { console.log(winRemoveSingleQuotes("Missing hostname, use --hostname [hostname]")); }
|
||||
else if (args.user == null) { console.log(winRemoveSingleQuotes("Missing user, use --user [user]")); }
|
||||
else if (args.pass == null) { console.log(winRemoveSingleQuotes("Missing pass, use --pass [pass]")); }
|
||||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
case 'addusertodevicegroup': {
|
||||
if ((args.id == null) && (args.group == null)) { console.log(winRemoveSingleQuotes("Device group identifier missing, use --id '[groupid]' or --group [groupname]")); }
|
||||
else if (args.userid == null) { console.log("Add user to group missing useid, use --userid [userid]"); }
|
||||
|
|
@ -238,10 +257,9 @@ if (args['_'].length == 0) {
|
|||
}
|
||||
case 'agentdownload': {
|
||||
if (args.type == null) { console.log(winRemoveSingleQuotes("Missing device type, use --type [agenttype]")); }
|
||||
var at = parseInt(args.type);
|
||||
if ((at == null) || isNaN(at) || (at < 1) || (at > 11000)) { console.log(winRemoveSingleQuotes("Invalid agent type, must be a number.")); }
|
||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[meshid]'")); }
|
||||
if ((typeof args.id != 'string') || (args.id.length != 64)) { console.log(winRemoveSingleQuotes("Invalid meshid.")); }
|
||||
else if ((parseInt(args.type) == null) || isNaN(parseInt(args.type)) || (parseInt(args.type) < 1) || (parseInt(args.type) > 11000)) { console.log(winRemoveSingleQuotes("Invalid agent type, must be a number.")); }
|
||||
else if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[meshid]'")); }
|
||||
else if ((typeof args.id != 'string') || (args.id.length != 64)) { console.log(winRemoveSingleQuotes("Invalid meshid.")); }
|
||||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
|
|
@ -260,6 +278,12 @@ if (args['_'].length == 0) {
|
|||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
case 'webrelay': {
|
||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||
else if (args.type == null) { console.log(winRemoveSingleQuotes("Missing protocol type, use --type [http,https]")); }
|
||||
else { ok = true; }
|
||||
break;
|
||||
}
|
||||
case 'deviceopenurl': {
|
||||
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); }
|
||||
else if (args.openurl == null) { console.log("Remote URL, use --openurl [url] specify the link to open."); }
|
||||
|
|
@ -484,7 +508,7 @@ if (args['_'].length == 0) {
|
|||
console.log(" --realname [name] - Set the real name for this account.");
|
||||
console.log(" --phone [number] - Set the account phone number.");
|
||||
console.log(" --rights [none|full|a,b,c] - Comma separated list of server permissions. Possible values:");
|
||||
console.log(" manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents");
|
||||
console.log(" manageusers,serverbackup,serverrestore,serverupdate,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents,nonewdevices");
|
||||
break;
|
||||
}
|
||||
case 'edituser': {
|
||||
|
|
@ -501,7 +525,7 @@ if (args['_'].length == 0) {
|
|||
console.log(" --realname [name] - Set the real name for this account.");
|
||||
console.log(" --phone [number] - Set the account phone number.");
|
||||
console.log(" --rights [none|full|a,b,c] - Comma separated list of server permissions. Possible values:");
|
||||
console.log(" manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents");
|
||||
console.log(" manageusers,serverbackup,serverrestore,serverupdate,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents,nonewdevices");
|
||||
break;
|
||||
}
|
||||
case 'removeuser': {
|
||||
|
|
@ -789,6 +813,55 @@ if (args['_'].length == 0) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'addlocaldevice': {
|
||||
console.log("Add a Local Device, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AddLocalDevice --id 'meshid' --devicename 'devicename' --hostname 'hostname'"));
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AddLocalDevice --id 'meshid' --devicename 'devicename' --hostname 'hostname' --type 6"));
|
||||
console.log("\r\nRequired arguments:\r\n");
|
||||
if (process.platform == 'win32') {
|
||||
console.log(" --id [meshid] - The mesh identifier.");
|
||||
console.log(" --devicename [devicename] - The device name.");
|
||||
console.log(" --hostname [hostname] - The devices hostname or ip address.");
|
||||
} else {
|
||||
console.log(" --id '[meshid]' - The mesh identifier.");
|
||||
console.log(" --devicename '[devicename]' - The device name.");
|
||||
console.log(" --hostname '[hostname]' - The devices hostname or ip address.");
|
||||
}
|
||||
|
||||
console.log("\r\nOptional arguments:\r\n");
|
||||
console.log(" --type [TypeNumber] - With the following choices:");
|
||||
console.log(" type 4 - Default, Windows (RDP)");
|
||||
console.log(" type 6 - Linux (SSH/SCP/VNC)");
|
||||
console.log(" type 29 - macOS (SSH/SCP/VNC)");
|
||||
break;
|
||||
}
|
||||
case 'addamtdevice': {
|
||||
console.log("Add an Intel AMT Device, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AddAmtDevice --id 'meshid' --devicename 'devicename' --hostname 'hostname --user 'admin' --pass 'admin'"));
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AddAmtDevice --id 'meshid' --devicename 'devicename' --hostname 'hostname --user 'admin' --pass 'admin' --notls"));
|
||||
console.log("\r\nRequired arguments:\r\n");
|
||||
if (process.platform == 'win32') {
|
||||
console.log(" --id [meshid] - The mesh identifier.");
|
||||
console.log(" --devicename [devicename] - The device name.");
|
||||
console.log(" --hostname [hostname] - The devices hostname or ip address.");
|
||||
console.log(" --user [user] - The devices AMT username.");
|
||||
console.log(" --pass [pass] - The devices AMT password.");
|
||||
console.log("")
|
||||
} else {
|
||||
console.log(" --id '[meshid]' - The mesh identifier.");
|
||||
console.log(" --devicename '[devicename]' - The device name.");
|
||||
console.log(" --hostname '[hostname]' - The devices hostname or ip address.");
|
||||
console.log(" --user '[user]' - The devices AMT username.");
|
||||
console.log(" --pass '[pass]' - The devices AMT password.");
|
||||
}
|
||||
console.log("\r\nOptional arguments:\r\n");
|
||||
if (process.platform == 'win32') {
|
||||
console.log(" --notls - Use No TLS Security.");
|
||||
} else {
|
||||
console.log(" --notls - Use No TLS Security.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'editdevice': {
|
||||
console.log("Change information about a device, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl EditDevice --id 'deviceid' --name 'device1'"));
|
||||
|
|
@ -905,6 +978,7 @@ if (args['_'].length == 0) {
|
|||
case 'agentdownload': {
|
||||
console.log("Download an agent of a specific type for a given device group, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AgentDownload --id 'groupid' --type 3"));
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl AgentDownload --id 'groupid' --type 3 --installflags 1"));
|
||||
console.log("\r\nRequired arguments:\r\n");
|
||||
console.log(" --type [ArchitectureNumber] - Agent architecture number.");
|
||||
if (process.platform == 'win32') {
|
||||
|
|
@ -912,6 +986,11 @@ if (args['_'].length == 0) {
|
|||
} else {
|
||||
console.log(" --id '[groupid]' - The device group identifier.");
|
||||
}
|
||||
console.log("\r\nOptional arguments:\r\n");
|
||||
console.log(" --installflags [InstallFlagsNumber] - With the following choices:");
|
||||
console.log(" installflags 0 - Default, Interactive & Background, offers connect button & install/uninstall");
|
||||
console.log(" installflags 1 - Interactive only, offers only connect button, not install/uninstall");
|
||||
console.log(" installflags 2 - Background only, offers only install/uninstall, not connect");
|
||||
break;
|
||||
}
|
||||
case 'upload': {
|
||||
|
|
@ -943,6 +1022,21 @@ if (args['_'].length == 0) {
|
|||
console.log(" --target [localpath] - The local path to download the file to.");
|
||||
break;
|
||||
}
|
||||
case 'webrelay': {
|
||||
console.log("Generate a webrelay URL to access a HTTP/HTTPS service on a remote device, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type http --port 80"));
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl WebRelay --id 'deviceid' --type https --port 443"));
|
||||
console.log("\r\nRequired arguments:\r\n");
|
||||
if (process.platform == 'win32') {
|
||||
console.log(" --id [deviceid] - The device identifier.");
|
||||
} else {
|
||||
console.log(" --id '[deviceid]' - The device identifier.");
|
||||
}
|
||||
console.log(" --type [http,https] - Type of relay from remote device, http or https.");
|
||||
console.log("\r\nOptional arguments:\r\n");
|
||||
console.log(" --port [portnumber] - Set alternative port for http or https, default is 80 for http and 443 for https.");
|
||||
break;
|
||||
}
|
||||
case 'deviceopenurl': {
|
||||
console.log("Open a web page on a remote device, Example usages:\r\n");
|
||||
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceOpenUrl --id 'deviceid' --openurl http://meshcentral.com"));
|
||||
|
|
@ -1117,7 +1211,7 @@ function performConfigOperations(args) {
|
|||
if (fs.existsSync(configFile) == false) { console.log("Unable to find config.json."); return; }
|
||||
var config = null;
|
||||
try { config = fs.readFileSync(configFile).toString('utf8'); } catch (ex) { console.log("Error: Unable to read config.json"); return; }
|
||||
try { config = require(configFile); } catch (e) { console.log('ERROR: Unable to parse ' + configFilePath + '.'); return null; }
|
||||
try { config = JSON.parse(fs.readFileSync(configFile)); } catch (e) { console.log('ERROR: Unable to parse ' + configFile + '.'); return null; }
|
||||
if (args.adddomain != null) {
|
||||
didSomething++;
|
||||
if (config.domains == null) { config.domains = {}; }
|
||||
|
|
@ -1265,10 +1359,10 @@ function serverConnect() {
|
|||
var domainid = '', username = 'admin';
|
||||
if (args.logindomain != null) { domainid = args.logindomain; }
|
||||
if (args.loginuser != null) { username = args.loginuser; }
|
||||
url += '?auth=' + encodeCookie({ userid: 'user/' + domainid + '/' + username, domainid: domainid }, ckey);
|
||||
url += (url.indexOf('?key=') >= 0 ? '&auth=' : '?auth=') + encodeCookie({ userid: 'user/' + domainid + '/' + username, domainid: domainid }, ckey);
|
||||
} else {
|
||||
if (args.logindomain != null) { console.log("--logindomain can only be used along with --loginkey."); process.exit(); return; }
|
||||
if (loginCookie != null) { url += '?auth=' + loginCookie; }
|
||||
if (loginCookie != null) { url += (url.indexOf('?key=') >= 0 ? '&auth=' : '?auth=') + loginCookie; }
|
||||
}
|
||||
|
||||
const ws = new WebSocket(url, options);
|
||||
|
|
@ -1485,6 +1579,29 @@ function serverConnect() {
|
|||
ws.send(JSON.stringify(op));
|
||||
break;
|
||||
}
|
||||
case 'addamtdevice': {
|
||||
var op = { action: 'addamtdevice', amttls: 1, responseid: 'meshctrl' };
|
||||
if (args.id) { op.meshid = args.id; }
|
||||
if ((typeof args.devicename == 'string') && (args.devicename != '')) { op.devicename = args.devicename; }
|
||||
if ((typeof args.hostname == 'string') && (args.hostname != '')) { op.hostname = args.hostname; }
|
||||
if ((typeof args.user == 'string') && (args.user != '')) { op.amtusername = args.user; }
|
||||
if ((typeof args.pass == 'string') && (args.pass != '')) { op.amtpassword = args.pass; }
|
||||
if (args.notls) { op.amttls = 0; }
|
||||
ws.send(JSON.stringify(op));
|
||||
break;
|
||||
}
|
||||
case 'addlocaldevice': {
|
||||
var op = { action: 'addlocaldevice', type: 4, responseid: 'meshctrl' };
|
||||
if (args.id) { op.meshid = args.id; }
|
||||
if ((typeof args.devicename == 'string') && (args.devicename != '')) { op.devicename = args.devicename; }
|
||||
if ((typeof args.hostname == 'string') && (args.hostname != '')) { op.hostname = args.hostname; }
|
||||
if (args.type) {
|
||||
if ((typeof parseInt(args.type) != 'number') || isNaN(parseInt(args.type))) { console.log("Invalid type."); process.exit(1); return; }
|
||||
op.type = args.type;
|
||||
}
|
||||
ws.send(JSON.stringify(op));
|
||||
break;
|
||||
}
|
||||
case 'editdevicegroup': {
|
||||
var op = { action: 'editmesh', responseid: 'meshctrl' };
|
||||
if (args.id) { op.meshid = args.id; } else if (args.group) { op.meshidname = args.group; }
|
||||
|
|
@ -1668,6 +1785,10 @@ function serverConnect() {
|
|||
var u = settings.xxurl.replace('wss://', 'https://').replace('/control.ashx', '/meshagents');
|
||||
if (u.indexOf('?') > 0) { u += '&'; } else { u += '?'; }
|
||||
u += 'id=' + args.type + '&meshid=' + args.id;
|
||||
if (args.installflags) {
|
||||
if ((typeof parseInt(args.installflags) != 'number') || isNaN(parseInt(args.installflags)) || (parseInt(args.installflags) < 0) || (parseInt(args.installflags) > 2)) { console.log("Invalid Installflags."); process.exit(1); return; }
|
||||
u += '&installflags=' + args.installflags;
|
||||
}
|
||||
const options = { rejectUnauthorized: false, checkServerIdentity: onVerifyServer }
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
|
|
@ -1705,6 +1826,29 @@ function serverConnect() {
|
|||
req.end()
|
||||
break;
|
||||
}
|
||||
case 'webrelay': {
|
||||
var protocol = null;
|
||||
if (args.type != null) {
|
||||
if (args.type == 'http') {
|
||||
protocol = 1;
|
||||
} else if (args.type == 'https') {
|
||||
protocol = 2;
|
||||
} else {
|
||||
console.log("Unknown protocol type: " + args.type); process.exit(1);
|
||||
}
|
||||
}
|
||||
var port = null;
|
||||
if (typeof args.port == 'number') {
|
||||
if ((args.port < 1) || (args.port > 65535)) { console.log("Port number must be between 1 and 65535."); process.exit(1); }
|
||||
port = args.port;
|
||||
} else if (protocol == 1) {
|
||||
port = 80;
|
||||
} else if (protocol == 2) {
|
||||
port = 443;
|
||||
}
|
||||
ws.send(JSON.stringify({ action: 'webrelay', nodeid: args.id, port: port, appid: protocol, responseid: 'meshctrl' }));
|
||||
break;
|
||||
}
|
||||
case 'devicesharing': {
|
||||
if (args.add) {
|
||||
if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); }
|
||||
|
|
@ -1866,11 +2010,11 @@ function serverConnect() {
|
|||
var srights = args.rights.toLowerCase().split(',');
|
||||
if (srights.indexOf('full') != -1) { siteadmin = 0xFFFFFFFF; }
|
||||
if (srights.indexOf('none') != -1) { siteadmin = 0x00000000; }
|
||||
if (srights.indexOf('backup') != -1) { siteadmin |= 0x00000001; }
|
||||
if (srights.indexOf('backup') != -1 || srights.indexOf('serverbackup') != -1) { siteadmin |= 0x00000001; }
|
||||
if (srights.indexOf('manageusers') != -1) { siteadmin |= 0x00000002; }
|
||||
if (srights.indexOf('restore') != -1) { siteadmin |= 0x00000004; }
|
||||
if (srights.indexOf('restore') != -1 || srights.indexOf('serverrestore') != -1) { siteadmin |= 0x00000004; }
|
||||
if (srights.indexOf('fileaccess') != -1) { siteadmin |= 0x00000008; }
|
||||
if (srights.indexOf('update') != -1) { siteadmin |= 0x00000010; }
|
||||
if (srights.indexOf('update') != -1 || srights.indexOf('serverupdate') != -1) { siteadmin |= 0x00000010; }
|
||||
if (srights.indexOf('locked') != -1) { siteadmin |= 0x00000020; }
|
||||
if (srights.indexOf('nonewgroups') != -1) { siteadmin |= 0x00000040; }
|
||||
if (srights.indexOf('notools') != -1) { siteadmin |= 0x00000080; }
|
||||
|
|
@ -1878,6 +2022,7 @@ function serverConnect() {
|
|||
if (srights.indexOf('recordings') != -1) { siteadmin |= 0x00000200; }
|
||||
if (srights.indexOf('locksettings') != -1) { siteadmin |= 0x00000400; }
|
||||
if (srights.indexOf('allevents') != -1) { siteadmin |= 0x00000800; }
|
||||
if (srights.indexOf('nonewdevices') != -1) { siteadmin |= 0x00001000; }
|
||||
}
|
||||
|
||||
if (args.siteadmin) { siteadmin = 0xFFFFFFFF; }
|
||||
|
|
@ -2074,6 +2219,8 @@ function serverConnect() {
|
|||
case 'toast': // TOAST
|
||||
case 'adduser': // ADDUSER
|
||||
case 'edituser': // EDITUSER
|
||||
case 'addamtdevice': // ADDAMTDEVICE
|
||||
case 'addlocaldevice': // ADDLOCALDEVICE
|
||||
case 'removedevices': // REMOVEDEVICE
|
||||
case 'changedevice': // EDITDEVICE
|
||||
case 'deleteuser': // REMOVEUSER
|
||||
|
|
@ -2096,6 +2243,7 @@ function serverConnect() {
|
|||
case 'removeDeviceShare':
|
||||
case 'userbroadcast': { // BROADCAST
|
||||
if ((settings.cmd == 'shell') || (settings.cmd == 'upload') || (settings.cmd == 'download')) return;
|
||||
if ((data.type == 'runcommands') && (settings.cmd != 'runcommand')) return;
|
||||
if ((settings.multiresponse != null) && (settings.multiresponse > 1)) { settings.multiresponse--; break; }
|
||||
if (data.responseid == 'meshctrl') {
|
||||
if (data.meshid) { console.log(data.result, data.meshid); }
|
||||
|
|
@ -2106,6 +2254,7 @@ function serverConnect() {
|
|||
break;
|
||||
}
|
||||
case 'createDeviceShareLink':
|
||||
case 'webrelay':
|
||||
if (data.result == 'OK') {
|
||||
if (data.publicid) { console.log('ID: ' + data.publicid); }
|
||||
console.log('URL: ' + data.url);
|
||||
|
|
@ -2240,7 +2389,7 @@ function serverConnect() {
|
|||
if (args.filter != null) {
|
||||
for (var meshid in data.nodes) {
|
||||
for (var d in data.nodes[meshid]) { data.nodes[meshid][d].meshid = meshid; }
|
||||
data.nodes[meshid] = parseSearchOrInput(data.nodes[meshid], args.filter.toLowerCase());
|
||||
data.nodes[meshid] = parseSearchOrInput(data.nodes[meshid], args.filter.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2391,6 +2540,8 @@ function serverConnect() {
|
|||
if (data.cause == 'noauth') {
|
||||
if (data.msg == 'tokenrequired') {
|
||||
console.log('Authentication token required, use --token [number].');
|
||||
} else if (data.msg == 'nokey') {
|
||||
console.log('URL key is invalid or missing, please specify ?key=xxx in url');
|
||||
} else {
|
||||
if ((args.loginkeyfile != null) || (args.loginkey != null)) {
|
||||
console.log('Invalid login, check the login key and that this computer has the correct time.');
|
||||
|
|
@ -2515,8 +2666,8 @@ function getDevicesThatMatchFilter(nodes, x) {
|
|||
} else if (tagSearch != null) {
|
||||
// Tag filter
|
||||
for (var d in nodes) {
|
||||
if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(d); }
|
||||
else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(d); break; } } }
|
||||
if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(nodes[d]); }
|
||||
else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase() == tagSearch) { r.push(nodes[d]); break; } } }
|
||||
}
|
||||
} else if (agentTagSearch != null) {
|
||||
// Agent Tag filter
|
||||
|
|
@ -2754,7 +2905,7 @@ function displayDeviceInfo(sysinfo, lastconnect, network, nodes) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (node == null) {
|
||||
if ((sysinfo == null && lastconnect == null && network == null) || (node == null)) {
|
||||
console.log("Invalid device id");
|
||||
process.exit(); return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -847,7 +847,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, id, func) {
|
|||
return;
|
||||
}
|
||||
// Write the recording file header
|
||||
parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename);
|
||||
parent.parent.debug('relay', 'Relay: Started recording to file: ' + recFullFilename);
|
||||
var metadata = { magic: 'MeshCentralRelaySession', ver: 1, nodeid: obj.nodeid, meshid: obj.meshid, time: new Date().toLocaleString(), protocol: 2, devicename: obj.name, devicegroup: obj.meshname };
|
||||
var firstBlock = JSON.stringify(metadata);
|
||||
recordingEntry(fd, 1, 0, firstBlock, function () {
|
||||
|
|
@ -1347,6 +1347,8 @@ function CreateMeshRelayEx2(parent, ws, req, domain, user, cookie) {
|
|||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||
}
|
||||
if (typeof domain.notificationmessages == 'object') {
|
||||
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
|
||||
|
|
|
|||
|
|
@ -352,9 +352,9 @@ module.exports.CreateServer = function (parent) {
|
|||
push.send(domain.title ? domain.title : 'MeshCentral', msg, function (err, res) { if (func != null) { func(err == null); } });
|
||||
} else if ((to.startsWith('ntfy:')) && (obj.ntfyClient != null)) { // ntfy
|
||||
const url = 'https://' + (((typeof parent.config.messaging.ntfy == 'object') && (typeof parent.config.messaging.ntfy.host == 'string')) ? parent.config.messaging.ntfy.host : 'ntfy.sh') + '/' + encodeURIComponent(to.substring(5));
|
||||
const headers = (typeof parent.config.messaging.ntfy.authorization == 'string') ? { 'Authorization': parent.config.messaging.ntfy.authorization } : {};
|
||||
const req = require('https').request(new URL(url), { method: 'POST', headers: headers }, function (res) { if (func != null) { func(true); } });
|
||||
req.on('error', function (err) { if (func != null) { func(false); } });
|
||||
const headers = { 'User-Agent': 'MeshCentral v' + parent.currentVer };
|
||||
if (typeof parent.config.messaging.ntfy.authorization == 'string') { headers['Authorization'] = parent.config.messaging.ntfy.authorization; }
|
||||
const req = require('https').request(new URL(url), { method: 'POST', headers: headers }, function (res) { if (func != null) { func(res.statusCode == 200); } });
|
||||
req.end(msg);
|
||||
} else if ((to.startsWith('zulip:')) && (obj.zulipClient != null)) { // zulip
|
||||
obj.zulipClient.sendMessage({
|
||||
|
|
|
|||
26
meshrelay.js
|
|
@ -119,6 +119,9 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
try { sr = parseInt(req.query.slowrelay); } catch (ex) { }
|
||||
if ((typeof sr == 'number') && (sr > 0) && (sr < 1000)) { obj.ws.slowRelay = sr; }
|
||||
}
|
||||
|
||||
// Check if protocol is set in the cookie and if so replace req.query.p but only if its not already set or blank
|
||||
if ((cookie != null) && (typeof cookie.p == 'number') && (obj.req.query.p === undefined || obj.req.query.p === "")) { obj.req.query.p = cookie.p; }
|
||||
|
||||
// Mesh Rights
|
||||
const MESHRIGHT_EDITMESH = 1;
|
||||
|
|
@ -442,15 +445,15 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
relayinfo.peer1.sendPeerImage();
|
||||
} else {
|
||||
// Write the recording file header
|
||||
parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename);
|
||||
parent.parent.debug('relay', 'Relay: Started recording to file: ' + recFullFilename);
|
||||
var metadata = {
|
||||
magic: 'MeshCentralRelaySession',
|
||||
ver: 1,
|
||||
userid: sessionUser._id,
|
||||
username: sessionUser.name,
|
||||
sessionid: obj.id,
|
||||
ipaddr1: (obj.req == null) ? null : obj.req.clientIp,
|
||||
ipaddr2: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp,
|
||||
ipaddr1: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp,
|
||||
ipaddr2: (obj.req == null) ? null : obj.req.clientIp,
|
||||
time: new Date().toLocaleString(),
|
||||
protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p),
|
||||
nodeid: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.nodeid)
|
||||
|
|
@ -884,7 +887,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
|
||||
const rcookie = parent.parent.encodeCookie(rcookieData, parent.parent.loginCookieEncryptionKey);
|
||||
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
||||
const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr, soptions: {} };
|
||||
const command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr, soptions: {} };
|
||||
if (user) { command.userid = user._id; }
|
||||
if (typeof domain.consentmessages == 'object') {
|
||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||
|
|
@ -893,6 +896,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||
}
|
||||
if (typeof domain.notificationmessages == 'object') {
|
||||
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
|
||||
|
|
@ -922,7 +927,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
if (obj.id == null) { obj.id = parent.crypto.randomBytes(9).toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } // If there is no connection id, generate one.
|
||||
const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey);
|
||||
if (obj.req.query.tcpport != null) {
|
||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr), soptions: {} };
|
||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, tcpport: obj.req.query.tcpport, tcpaddr: ((obj.req.query.tcpaddr == null) ? '127.0.0.1' : obj.req.query.tcpaddr), soptions: {} };
|
||||
if (typeof domain.consentmessages == 'object') {
|
||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
||||
|
|
@ -930,6 +935,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||
}
|
||||
if (typeof domain.notificationmessages == 'object') {
|
||||
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
|
||||
|
|
@ -940,14 +947,15 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command));
|
||||
if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + obj.req.clientIp + ')'); }
|
||||
} else if (obj.req.query.udpport != null) {
|
||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr), soptions: {} };
|
||||
if (typeof domain.consentmessages == 'object') {
|
||||
const command = { nodeid: obj.req.query.nodeid, action: 'msg', type: 'tunnel', userid: user._id, value: '*/' + xdomain + 'meshrelay.ashx?' + (obj.req.query.p != null ? ('p=' + obj.req.query.p + '&') : '') + 'id=' + obj.id + '&rauth=' + rcookie, udpport: obj.req.query.udpport, udpaddr: ((obj.req.query.udpaddr == null) ? '127.0.0.1' : obj.req.query.udpaddr), soptions: {} }; if (typeof domain.consentmessages == 'object') {
|
||||
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
|
||||
if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
|
||||
if (typeof domain.consentmessages.terminal == 'string') { command.soptions.consentMsgTerminal = domain.consentmessages.terminal; }
|
||||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||
}
|
||||
if (typeof domain.notificationmessages == 'object') {
|
||||
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
|
||||
|
|
@ -999,6 +1007,8 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
|
||||
if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
|
||||
if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
|
||||
if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
|
||||
if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
|
||||
}
|
||||
if (typeof domain.notificationmessages == 'object') {
|
||||
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
|
||||
|
|
@ -1227,6 +1237,7 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
||||
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
||||
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
||||
else if (req.query.p == 14) { protocolStr = 'Web-TCP'; }
|
||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 121, msgArgs: [obj.id, protocolStr, obj.host, Math.floor((Date.now() - obj.time) / 1000)], msg: 'Ended local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host + ', ' + Math.floor((Date.now() - obj.time) / 1000) + ' second(s)', nodeid: obj.req.query.nodeid, protocol: req.query.p, in: inTraffc, out: outTraffc };
|
||||
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
||||
parent.parent.DispatchEvent(['*', user._id], obj, event);
|
||||
|
|
@ -1281,6 +1292,7 @@ function CreateLocalRelayEx(parent, ws, req, domain, user, cookie) {
|
|||
else if (req.query.p == 11) { protocolStr = 'SSH-TERM'; }
|
||||
else if (req.query.p == 12) { protocolStr = 'VNC'; }
|
||||
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
|
||||
else if (req.query.p == 14) { protocolStr = 'Web-TCP'; }
|
||||
obj.time = Date.now();
|
||||
var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: obj.user._id, username: obj.user.name, msgid: 120, msgArgs: [obj.id, protocolStr, obj.host], msg: 'Started local relay session \"' + obj.id + '\", protocol ' + protocolStr + ' to ' + obj.host, nodeid: req.query.nodeid, protocol: req.query.p };
|
||||
if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.
|
||||
|
|
|
|||
1010
meshuser.js
117
monitoring.js
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* @description MeshCentral monitoring module
|
||||
* @author Simon Smith
|
||||
* @license Apache-2.0
|
||||
* @version v0.0.1
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports.CreateMonitoring = function (parent, args) {
|
||||
var obj = {};
|
||||
obj.args = args;
|
||||
obj.parent = parent;
|
||||
obj.express = require('express');
|
||||
obj.app = obj.express();
|
||||
obj.prometheus = null;
|
||||
if (args.compression !== false) { obj.app.use(require('compression')()); }
|
||||
obj.app.disable('x-powered-by');
|
||||
obj.counterMetrics = { // Counter Metrics always start at 0 and increase but never decrease
|
||||
RelayErrors: { description: "Relay Errors" }, // parent.webserver.relaySessionErrorCount
|
||||
UnknownGroup: { description: "Unknown Group" }, // meshDoesNotExistCount
|
||||
InvalidPKCSsignature: { description: "Invalid PKCS signature" }, // invalidPkcsSignatureCount
|
||||
InvalidRSAsignature: { description: "Invalid RSA signature" }, // invalidRsaSignatureCount
|
||||
InvalidJSON: { description: "Invalid JSON" }, // invalidJsonCount
|
||||
UnknownAction: { description: "Unknown Action" }, // unknownAgentActionCount
|
||||
BadWebCertificate: { description: "Bad Web Certificate" }, // agentBadWebCertHashCount
|
||||
BadSignature: { description: "Bad Signature" }, // (agentBadSignature1Count + agentBadSignature2Count)
|
||||
MaxSessionsReached: { description: "Max Sessions Reached" }, // agentMaxSessionHoldCount
|
||||
UnknownDeviceGroup: { description: "Unknown Device Group" }, // (invalidDomainMeshCount + invalidDomainMesh2Count)
|
||||
InvalidDeviceGroupType: { description: "Invalid Device Group Type" }, // invalidMeshTypeCount
|
||||
DuplicateAgent: { description: "Duplicate Agent" }, // duplicateAgentCount
|
||||
blockedUsers: { description: "Blocked Users" }, // blockedUsers
|
||||
blockedAgents: { description: "Blocked Agents" }, // blockedAgents
|
||||
};
|
||||
obj.gaugeMetrics = { // Gauge Metrics always start at 0 and can increase and decrease
|
||||
ConnectedIntelAMT: { description: "Connected Intel AMT" }, // parent.mpsserver.ciraConnections[i].length
|
||||
UserAccounts: { description: "User Accounts" }, // Object.keys(parent.webserver.users).length
|
||||
DeviceGroups: { description: "Device Groups" }, // parent.webserver.meshes (ONLY WHERE deleted=null)
|
||||
AgentSessions: { description: "Agent Sessions" }, // Object.keys(parent.webserver.wsagents).length
|
||||
ConnectedUsers: { description: "Connected Users" }, // Object.keys(parent.webserver.wssessions).length
|
||||
UsersSessions: { description: "Users Sessions" }, // Object.keys(parent.webserver.wssessions2).length
|
||||
RelaySessions: { description: "Relay Sessions" }, // parent.webserver.relaySessionCount
|
||||
RelayCount: { description: "Relay Count" } // Object.keys(parent.webserver.wsrelays).length30bb4fb74dfb758d36be52a7
|
||||
}
|
||||
obj.collectors = [];
|
||||
if (parent.config.settings.prometheus != null) { // Create Prometheus Monitoring Endpoint
|
||||
if ((typeof parent.config.settings.prometheus == 'number') && ((parent.config.settings.prometheus < 1) || (parent.config.settings.prometheus > 65535))) {
|
||||
console.log('Promethus port number is invalid, Prometheus metrics endpoint has be disabled');
|
||||
delete parent.config.settings.prometheus;
|
||||
} else {
|
||||
const port = ((typeof parent.config.settings.prometheus == 'number') ? parent.config.settings.prometheus : 9464);
|
||||
obj.prometheus = require('prom-client');
|
||||
const collectDefaultMetrics = obj.prometheus.collectDefaultMetrics;
|
||||
collectDefaultMetrics();
|
||||
for (const key in obj.gaugeMetrics) {
|
||||
obj.gaugeMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.gaugeMetrics[key].description });
|
||||
}
|
||||
for (const key in obj.counterMetrics) {
|
||||
obj.counterMetrics[key].prometheus = new obj.prometheus.Counter({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.counterMetrics[key].description });
|
||||
}
|
||||
obj.app.get('/', function (req, res) { res.send('MeshCentral Prometheus server.'); });
|
||||
obj.app.listen(port, function () {
|
||||
console.log('MeshCentral Prometheus server running on port ' + port + '.');
|
||||
obj.parent.updateServerState('prometheus-port', port);
|
||||
});
|
||||
obj.app.get('/metrics', async (req, res) => {
|
||||
try {
|
||||
// Count the number of device groups that are not deleted
|
||||
var activeDeviceGroups = 0;
|
||||
for (var i in parent.webserver.meshes) { if (parent.webserver.meshes[i].deleted == null) { activeDeviceGroups++; } } // This is not ideal for performance, we want to dome something better.
|
||||
var gauges = {
|
||||
UserAccounts: Object.keys(parent.webserver.users).length,
|
||||
DeviceGroups: activeDeviceGroups,
|
||||
AgentSessions: Object.keys(parent.webserver.wsagents).length,
|
||||
ConnectedUsers: Object.keys(parent.webserver.wssessions).length,
|
||||
UsersSessions: Object.keys(parent.webserver.wssessions2).length,
|
||||
RelaySessions: parent.webserver.relaySessionCount,
|
||||
RelayCount: Object.keys(parent.webserver.wsrelays).length,
|
||||
ConnectedIntelAMT: 0
|
||||
};
|
||||
if (parent.mpsserver != null) {
|
||||
for (var i in parent.mpsserver.ciraConnections) {
|
||||
gauges.ConnectedIntelAMT += parent.mpsserver.ciraConnections[i].length;
|
||||
}
|
||||
}
|
||||
for (const key in gauges) { obj.gaugeMetrics[key].prometheus.set(gauges[key]); }
|
||||
// Take a look at agent errors
|
||||
var agentstats = parent.webserver.getAgentStats();
|
||||
const counters = {
|
||||
RelayErrors: parent.webserver.relaySessionErrorCount,
|
||||
UnknownGroup: agentstats.meshDoesNotExistCount,
|
||||
InvalidPKCSsignature: agentstats.invalidPkcsSignatureCount,
|
||||
InvalidRSAsignature: agentstats.invalidRsaSignatureCount,
|
||||
InvalidJSON: agentstats.invalidJsonCount,
|
||||
UnknownAction: agentstats.unknownAgentActionCount,
|
||||
BadWebCertificate: agentstats.agentBadWebCertHashCount,
|
||||
BadSignature: (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count),
|
||||
MaxSessionsReached: agentstats.agentMaxSessionHoldCount,
|
||||
UnknownDeviceGroup: (agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count),
|
||||
InvalidDeviceGroupType: (agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count),
|
||||
DuplicateAgent: agentstats.duplicateAgentCount,
|
||||
blockedUsers: parent.webserver.blockedUsers,
|
||||
blockedAgents: parent.webserver.blockedAgents
|
||||
};
|
||||
for (const key in counters) { obj.counterMetrics[key].prometheus.reset(); obj.counterMetrics[key].prometheus.inc(counters[key]); }
|
||||
res.set('Content-Type', obj.prometheus.register.contentType);
|
||||
await Promise.all(obj.collectors.map((collector) => (collector(req, res))));
|
||||
res.end(await obj.prometheus.register.metrics());
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
res.status(500).end();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
|
@ -586,6 +586,20 @@ module.exports.CreateMultiServer = function (parent, args) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'agentCommand': {
|
||||
if (msg.nodeid != null) {
|
||||
// Route this message to a connected agent
|
||||
var agent = obj.parent.webserver.wsagents[msg.nodeid];
|
||||
if (agent != null) { agent.send(JSON.stringify(msg.command)); }
|
||||
} else if (msg.meshid != null) {
|
||||
// Route this message to all connected agents of this mesh
|
||||
for (var nodeid in obj.parent.webserver.wsagents) {
|
||||
var agent = obj.parent.webserver.wsagents[nodeid];
|
||||
if (agent.dbMeshKey == msg.meshid) { try { agent.send(JSON.stringify(msg.command)); } catch (ex) { } }
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Unknown peer server command
|
||||
console.log('Unknown action from peer server ' + peerServerId + ': ' + msg.action + '.');
|
||||
|
|
@ -634,7 +648,7 @@ module.exports.CreateMultiServer = function (parent, args) {
|
|||
peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug('peer', 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); });
|
||||
|
||||
// If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?)
|
||||
peerTunnel.ws2.on('message', function (msg) { try { peerTunnel.ws2._socket.pause(); peerTunnel.ws1.send(msg, function () { peerTunnel.ws2._socket.resume(); }); } catch (e) { } });
|
||||
peerTunnel.ws2.on('message', function (msg, isBinary) { try { peerTunnel.ws2._socket.pause(); peerTunnel.ws1.send((isBinary ? msg : msg.toString('binary')), function () { peerTunnel.ws2._socket.resume(); }); } catch (e) { } });
|
||||
|
||||
// Register the connection event
|
||||
peerTunnel.ws2.on('open', function () {
|
||||
|
|
|
|||
2214
package-lock.json
generated
24
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "meshcentral",
|
||||
"version": "1.1.22",
|
||||
"version": "1.1.42",
|
||||
"keywords": [
|
||||
"Remote Device Management",
|
||||
"Remote Device Monitoring",
|
||||
|
|
@ -37,25 +37,25 @@
|
|||
"sample-config-advanced.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"archiver": "7.0.0",
|
||||
"body-parser": "1.20.2",
|
||||
"@seald-io/nedb": "4.0.4",
|
||||
"archiver": "7.0.1",
|
||||
"body-parser": "1.20.3",
|
||||
"cbor": "5.2.0",
|
||||
"compression": "1.7.4",
|
||||
"cookie-session": "2.0.0",
|
||||
"express": "4.18.2",
|
||||
"express-handlebars": "5.3.5",
|
||||
"express-ws": "4.0.0",
|
||||
"compression": "1.7.5",
|
||||
"cookie-session": "2.1.0",
|
||||
"express": "4.21.2",
|
||||
"express-handlebars": "7.1.3",
|
||||
"express-ws": "5.0.2",
|
||||
"ipcheck": "0.1.0",
|
||||
"minimist": "1.2.8",
|
||||
"multiparty": "4.2.3",
|
||||
"@yetzt/nedb": "1.8.0",
|
||||
"node-forge": "1.3.1",
|
||||
"ua-parser-js": "1.0.37",
|
||||
"ws": "8.14.2",
|
||||
"ua-parser-js": "1.0.39",
|
||||
"ws": "8.18.0",
|
||||
"yauzl": "2.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ module.exports.pluginHandler = function (parent) {
|
|||
try {
|
||||
obj.plugins[p][hookName](...args);
|
||||
} catch (e) {
|
||||
console.log("Error ocurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')');
|
||||
console.log("Error occurred while running plugin hook " + p + ':' + hookName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ module.exports.pluginHandler = function (parent) {
|
|||
panel[p].header = obj.plugins[p].on_device_header();
|
||||
panel[p].content = obj.plugins[p].on_device_page();
|
||||
} catch (e) {
|
||||
console.log("Error ocurred while getting plugin views " + p + ':' + ' (' + e + ')');
|
||||
console.log("Error occurred while getting plugin views " + p + ':' + ' (' + e + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -364,7 +364,7 @@ module.exports.pluginHandler = function (parent) {
|
|||
if (force_url != null) dl_url = force_url;
|
||||
var url = require('url');
|
||||
var q = url.parse(dl_url, true);
|
||||
var http = (q.protocol == "http") ? require('http') : require('https');
|
||||
var http = (q.protocol == "http:") ? require('http') : require('https');
|
||||
var opts = {
|
||||
path: q.pathname,
|
||||
host: q.hostname,
|
||||
|
|
@ -457,7 +457,7 @@ module.exports.pluginHandler = function (parent) {
|
|||
if (plugin.versionHistoryUrl == null) reject("No version history available for this plugin.");
|
||||
var url = require('url');
|
||||
var q = url.parse(plugin.versionHistoryUrl, true);
|
||||
var http = (q.protocol == 'http') ? require('http') : require('https');
|
||||
var http = (q.protocol == 'http:') ? require('http') : require('https');
|
||||
var opts = {
|
||||
path: q.pathname,
|
||||
host: q.hostname,
|
||||
|
|
|
|||
BIN
public/images/duo-2fa-250-disable.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/duo-2fa-250.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/login/2fa-duo-48.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/login/2fa-duo-96.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "MeshCentral",
|
||||
"short_name": "MeshCentral",
|
||||
"description": "Open source web based, remote computer management.",
|
||||
"scope": ".",
|
||||
"start_url": "/",
|
||||
"display": "fullscreen",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -180,6 +180,7 @@
|
|||
self.prevClipboardText = null;
|
||||
self.clipboardReadTimer = setInterval(function(){
|
||||
if(navigator.clipboard.readText != null){
|
||||
if (Mstsc.browser() == 'firefox') return; // this is needed because firefox pops up a PASTE option every second which is annoying
|
||||
navigator.clipboard.readText()
|
||||
.then(function(data){
|
||||
if(data != self.prevClipboard){
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
|
||||
"Connecting...": "Συνδέεται...",
|
||||
"Disconnecting...": "Aποσυνδέεται...",
|
||||
"Reconnecting...": "Επανασυνδέεται...",
|
||||
|
|
@ -7,19 +8,15 @@
|
|||
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
|
||||
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
|
||||
"Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
|
||||
"Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
|
||||
"Disconnected": "Αποσυνδέθηκε",
|
||||
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
|
||||
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
|
||||
"Password is required": "Απαιτείται ο κωδικός πρόσβασης",
|
||||
"Credentials are required": "Απαιτούνται διαπιστευτήρια",
|
||||
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
|
||||
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
|
||||
"Drag": "Σύρσιμο",
|
||||
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
|
||||
"viewport drag": "σύρσιμο θεατού πεδίου",
|
||||
"Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
|
||||
"No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
|
||||
"Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
|
||||
"Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
|
||||
"Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
|
||||
"Keyboard": "Πληκτρολόγιο",
|
||||
"Show Keyboard": "Εμφάνιση Πληκτρολογίου",
|
||||
"Extra keys": "Επιπλέον πλήκτρα",
|
||||
|
|
@ -28,6 +25,8 @@
|
|||
"Toggle Ctrl": "Εναλλαγή Ctrl",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Εναλλαγή Alt",
|
||||
"Toggle Windows": "Εναλλαγή Παράθυρων",
|
||||
"Windows": "Παράθυρα",
|
||||
"Send Tab": "Αποστολή Tab",
|
||||
"Tab": "Tab",
|
||||
"Esc": "Esc",
|
||||
|
|
@ -41,8 +40,7 @@
|
|||
"Reboot": "Επανεκκίνηση",
|
||||
"Reset": "Επαναφορά",
|
||||
"Clipboard": "Πρόχειρο",
|
||||
"Clear": "Καθάρισμα",
|
||||
"Fullscreen": "Πλήρης Οθόνη",
|
||||
"Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
|
||||
"Settings": "Ρυθμίσεις",
|
||||
"Shared Mode": "Κοινόχρηστη Λειτουργία",
|
||||
"View Only": "Μόνο Θέαση",
|
||||
|
|
@ -52,6 +50,8 @@
|
|||
"Local Scaling": "Τοπική Κλιμάκωση",
|
||||
"Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
|
||||
"Advanced": "Για προχωρημένους",
|
||||
"Quality:": "Ποιότητα:",
|
||||
"Compression level:": "Επίπεδο συμπίεσης:",
|
||||
"Repeater ID:": "Repeater ID:",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "Κρυπτογράφηση",
|
||||
|
|
@ -60,10 +60,20 @@
|
|||
"Path:": "Διαδρομή:",
|
||||
"Automatic Reconnect": "Αυτόματη επανασύνδεση",
|
||||
"Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
|
||||
"Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
|
||||
"Logging:": "Καταγραφή:",
|
||||
"Version:": "Έκδοση:",
|
||||
"Disconnect": "Αποσύνδεση",
|
||||
"Connect": "Σύνδεση",
|
||||
"Server identity": "Ταυτότητα Διακομιστή",
|
||||
"The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:",
|
||||
"Fingerprint:": "Δακτυλικό αποτύπωμα:",
|
||||
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".",
|
||||
"Approve": "Αποδοχή",
|
||||
"Reject": "Απόρριψη",
|
||||
"Credentials": "Διαπιστευτήρια",
|
||||
"Username:": "Κωδικός Χρήστη:",
|
||||
"Password:": "Κωδικός Πρόσβασης:",
|
||||
"Cancel": "Ακύρωση",
|
||||
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
|
||||
"Send Credentials": "Αποστολή Διαπιστευτηρίων",
|
||||
"Cancel": "Ακύρωση"
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"HTTPS is required for full functionality": "",
|
||||
"Connecting...": "En cours de connexion...",
|
||||
"Disconnecting...": "Déconnexion en cours...",
|
||||
"Reconnecting...": "Reconnexion en cours...",
|
||||
|
|
@ -40,7 +39,8 @@
|
|||
"Reboot": "Redémarrer",
|
||||
"Reset": "Réinitialiser",
|
||||
"Clipboard": "Presse-papiers",
|
||||
"Edit clipboard content in the textarea below.": "",
|
||||
"Clear": "Effacer",
|
||||
"Fullscreen": "Plein écran",
|
||||
"Settings": "Paramètres",
|
||||
"Shared Mode": "Mode partagé",
|
||||
"View Only": "Afficher uniquement",
|
||||
|
|
@ -65,12 +65,6 @@
|
|||
"Version:": "Version :",
|
||||
"Disconnect": "Déconnecter",
|
||||
"Connect": "Connecter",
|
||||
"Server identity": "",
|
||||
"The server has provided the following identifying information:": "",
|
||||
"Fingerprint:": "",
|
||||
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "",
|
||||
"Approve": "",
|
||||
"Reject": "",
|
||||
"Username:": "Nom d'utilisateur :",
|
||||
"Password:": "Mot de passe :",
|
||||
"Send Credentials": "Envoyer les identifiants",
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
"Credentials are required": "Le credenziali sono obbligatorie",
|
||||
"noVNC encountered an error:": "noVNC ha riscontrato un errore:",
|
||||
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
|
||||
"Drag": "",
|
||||
"Move/Drag Viewport": "",
|
||||
"Keyboard": "Tastiera",
|
||||
"Show Keyboard": "Mostra tastiera",
|
||||
"Extra keys": "Tasti Aggiuntivi",
|
||||
|
|
@ -44,7 +42,6 @@
|
|||
"Settings": "Impostazioni",
|
||||
"Shared Mode": "Modalità condivisa",
|
||||
"View Only": "Sola Visualizzazione",
|
||||
"Clip to Window": "",
|
||||
"Scaling Mode:": "Modalità di ridimensionamento:",
|
||||
"None": "Nessuna",
|
||||
"Local Scaling": "Ridimensionamento Locale",
|
||||
|
|
@ -61,7 +58,6 @@
|
|||
"Automatic Reconnect": "Riconnessione Automatica",
|
||||
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
|
||||
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
|
||||
"Logging:": "",
|
||||
"Version:": "Versione:",
|
||||
"Disconnect": "Disconnetti",
|
||||
"Connect": "Connetti",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です",
|
||||
"Connecting...": "接続しています...",
|
||||
"Disconnecting...": "切断しています...",
|
||||
"Reconnecting...": "再接続しています...",
|
||||
|
|
@ -21,10 +22,10 @@
|
|||
"Extra keys": "追加キー",
|
||||
"Show Extra Keys": "追加キーを表示",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "Ctrl キーを切り替え",
|
||||
"Toggle Ctrl": "Ctrl キーをトグル",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "Alt キーを切り替え",
|
||||
"Toggle Windows": "Windows キーを切り替え",
|
||||
"Toggle Alt": "Alt キーをトグル",
|
||||
"Toggle Windows": "Windows キーをトグル",
|
||||
"Windows": "Windows",
|
||||
"Send Tab": "Tab キーを送信",
|
||||
"Tab": "Tab",
|
||||
|
|
@ -39,11 +40,11 @@
|
|||
"Reboot": "再起動",
|
||||
"Reset": "リセット",
|
||||
"Clipboard": "クリップボード",
|
||||
"Clear": "クリア",
|
||||
"Fullscreen": "全画面表示",
|
||||
"Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
|
||||
"Full Screen": "全画面表示",
|
||||
"Settings": "設定",
|
||||
"Shared Mode": "共有モード",
|
||||
"View Only": "表示のみ",
|
||||
"View Only": "表示専用",
|
||||
"Clip to Window": "ウィンドウにクリップ",
|
||||
"Scaling Mode:": "スケーリングモード:",
|
||||
"None": "なし",
|
||||
|
|
@ -60,11 +61,18 @@
|
|||
"Path:": "パス:",
|
||||
"Automatic Reconnect": "自動再接続",
|
||||
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
|
||||
"Show Dot when No Cursor": "カーソルがないときにドットを表示",
|
||||
"Show Dot when No Cursor": "カーソルがないときにドットを表示する",
|
||||
"Logging:": "ロギング:",
|
||||
"Version:": "バージョン:",
|
||||
"Disconnect": "切断",
|
||||
"Connect": "接続",
|
||||
"Server identity": "サーバーの識別情報",
|
||||
"The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:",
|
||||
"Fingerprint:": "フィンガープリント:",
|
||||
"Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。",
|
||||
"Approve": "承認",
|
||||
"Reject": "拒否",
|
||||
"Credentials": "資格情報",
|
||||
"Username:": "ユーザー名:",
|
||||
"Password:": "パスワード:",
|
||||
"Send Credentials": "資格情報を送信",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
|
||||
"Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.",
|
||||
"Connecting...": "Ansluter...",
|
||||
"Disconnecting...": "Kopplar ner...",
|
||||
"Reconnecting...": "Återansluter...",
|
||||
"Internal error": "Internt fel",
|
||||
"Must set host": "Du måste specifiera en värd",
|
||||
"Failed to connect to server: ": "Misslyckades att ansluta till servern: ",
|
||||
"Connected (encrypted) to ": "Ansluten (krypterat) till ",
|
||||
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
|
||||
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
|
||||
|
|
|
|||
|
|
@ -1,69 +1,69 @@
|
|||
{
|
||||
"Connecting...": "连接中...",
|
||||
"Connected (encrypted) to ": "已连接(已加密)到",
|
||||
"Connected (unencrypted) to ": "已连接(未加密)到",
|
||||
"Disconnecting...": "正在断开连接...",
|
||||
"Reconnecting...": "重新连接中...",
|
||||
"Internal error": "内部错误",
|
||||
"Must set host": "请提供主机名",
|
||||
"Connected (encrypted) to ": "已连接到(加密)",
|
||||
"Connected (unencrypted) to ": "已连接到(未加密)",
|
||||
"Something went wrong, connection is closed": "发生错误,连接已关闭",
|
||||
"Failed to connect to server": "无法连接到服务器",
|
||||
"Disconnected": "已断开连接",
|
||||
"New connection has been rejected with reason: ": "连接被拒绝,原因:",
|
||||
"New connection has been rejected": "连接被拒绝",
|
||||
"Must set host": "必须设置主机",
|
||||
"Reconnecting...": "重新连接中...",
|
||||
"Password is required": "请提供密码",
|
||||
"Disconnect timeout": "超时断开",
|
||||
"noVNC encountered an error:": "noVNC 遇到一个错误:",
|
||||
"Hide/Show the control bar": "显示/隐藏控制栏",
|
||||
"Move/Drag Viewport": "拖放显示范围",
|
||||
"viewport drag": "显示范围拖放",
|
||||
"Active Mouse Button": "启动鼠标按鍵",
|
||||
"No mousebutton": "禁用鼠标按鍵",
|
||||
"Left mousebutton": "鼠标左鍵",
|
||||
"Middle mousebutton": "鼠标中鍵",
|
||||
"Right mousebutton": "鼠标右鍵",
|
||||
"Move/Drag Viewport": "移动/拖动窗口",
|
||||
"viewport drag": "窗口拖动",
|
||||
"Active Mouse Button": "启动鼠标按键",
|
||||
"No mousebutton": "禁用鼠标按键",
|
||||
"Left mousebutton": "鼠标左键",
|
||||
"Middle mousebutton": "鼠标中键",
|
||||
"Right mousebutton": "鼠标右键",
|
||||
"Keyboard": "键盘",
|
||||
"Show Keyboard": "显示键盘",
|
||||
"Extra keys": "额外按键",
|
||||
"Show Extra Keys": "显示额外按键",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Ctrl": "切换 Ctrl",
|
||||
"Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
|
||||
"Alt": "Alt",
|
||||
"Toggle Alt": "切换 Alt",
|
||||
"Send Tab": "发送 Tab 键",
|
||||
"Tab": "Tab",
|
||||
"Esc": "Esc",
|
||||
"Send Escape": "发送 Escape 键",
|
||||
"Ctrl+Alt+Del": "Ctrl-Alt-Del",
|
||||
"Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键",
|
||||
"Shutdown/Reboot": "关机/重新启动",
|
||||
"Shutdown/Reboot...": "关机/重新启动...",
|
||||
"Ctrl+Alt+Del": "Ctrl+Alt+Del",
|
||||
"Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键",
|
||||
"Shutdown/Reboot": "关机/重启",
|
||||
"Shutdown/Reboot...": "关机/重启...",
|
||||
"Power": "电源",
|
||||
"Shutdown": "关机",
|
||||
"Reboot": "重新启动",
|
||||
"Reboot": "重启",
|
||||
"Reset": "重置",
|
||||
"Clipboard": "剪贴板",
|
||||
"Clear": "清除",
|
||||
"Fullscreen": "全屏",
|
||||
"Settings": "设置",
|
||||
"Encrypt": "加密",
|
||||
"Shared Mode": "分享模式",
|
||||
"View Only": "仅查看",
|
||||
"Clip to Window": "限制/裁切窗口大小",
|
||||
"Scaling Mode:": "缩放模式:",
|
||||
"None": "无",
|
||||
"Local Scaling": "本地缩放",
|
||||
"Local Downscaling": "降低本地尺寸",
|
||||
"Remote Resizing": "远程调整大小",
|
||||
"Advanced": "高级",
|
||||
"Local Cursor": "本地光标",
|
||||
"Repeater ID:": "中继站 ID",
|
||||
"WebSocket": "WebSocket",
|
||||
"Encrypt": "加密",
|
||||
"Host:": "主机:",
|
||||
"Port:": "端口:",
|
||||
"Path:": "路径:",
|
||||
"Automatic Reconnect": "自动重新连接",
|
||||
"Reconnect Delay (ms):": "重新连接间隔 (ms):",
|
||||
"Logging:": "日志级别:",
|
||||
"Disconnect": "中断连接",
|
||||
"Disconnect": "断开连接",
|
||||
"Connect": "连接",
|
||||
"Password:": "密码:",
|
||||
"Cancel": "取消"
|
||||
"Cancel": "取消",
|
||||
"Canvas not supported.": "不支持 Canvas。"
|
||||
}
|
||||
|
|
@ -16,13 +16,19 @@ export class Localizer {
|
|||
this.language = 'en';
|
||||
|
||||
// Current dictionary of translations
|
||||
this.dictionary = undefined;
|
||||
this._dictionary = undefined;
|
||||
}
|
||||
|
||||
// Configure suitable language based on user preferences
|
||||
setup(supportedLanguages) {
|
||||
async setup(supportedLanguages, baseURL) {
|
||||
this.language = 'en'; // Default: US English
|
||||
this._dictionary = undefined;
|
||||
|
||||
this._setupLanguage(supportedLanguages);
|
||||
await this._setupDictionary(baseURL);
|
||||
}
|
||||
|
||||
_setupLanguage(supportedLanguages) {
|
||||
/*
|
||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||
* Fall back to navigator.language for other browsers
|
||||
|
|
@ -40,12 +46,6 @@ export class Localizer {
|
|||
.replace("_", "-")
|
||||
.split("-");
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en') &&
|
||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
|
|
@ -64,7 +64,12 @@ export class Localizer {
|
|||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
// Second pass: English fallback
|
||||
if (userLang[0] === 'en') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Third pass pass: other fallback
|
||||
for (let j = 0;j < supportedLanguages.length;j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
|
|
@ -84,10 +89,32 @@ export class Localizer {
|
|||
}
|
||||
}
|
||||
|
||||
async _setupDictionary(baseURL) {
|
||||
if (baseURL) {
|
||||
if (!baseURL.endsWith("/")) {
|
||||
baseURL = baseURL + "/";
|
||||
}
|
||||
} else {
|
||||
baseURL = "";
|
||||
}
|
||||
|
||||
if (this.language === "en") {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await fetch(baseURL + this.language + ".json");
|
||||
if (!response.ok) {
|
||||
throw Error("" + response.status + " " + response.statusText);
|
||||
}
|
||||
|
||||
this._dictionary = await response.json();
|
||||
}
|
||||
|
||||
// Retrieve localised text
|
||||
get(id) {
|
||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
||||
return this.dictionary[id];
|
||||
if (typeof this._dictionary !== 'undefined' &&
|
||||
this._dictionary[id]) {
|
||||
return this._dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -661,7 +661,7 @@ html {
|
|||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
line-height: 25px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
color: #fff;
|
||||
|
||||
|
|
@ -887,7 +887,7 @@ html {
|
|||
.noVNC_logo {
|
||||
color:yellow;
|
||||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||
line-height:90%;
|
||||
line-height: 0.9;
|
||||
text-shadow: 0.1em 0.1em 0 black;
|
||||
}
|
||||
.noVNC_logo span{
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ option {
|
|||
* Checkboxes
|
||||
*/
|
||||
input[type=checkbox] {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
background-image: unset;
|
||||
border: 1px solid dimgrey;
|
||||
|
|
@ -104,14 +107,11 @@ input[type=checkbox]:checked {
|
|||
input[type=checkbox]:checked::after {
|
||||
content: "";
|
||||
display: block; /* width & height doesn't work on inline elements */
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 3px;
|
||||
width: 3px;
|
||||
height: 7px;
|
||||
border: 1px solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(40deg);
|
||||
transform: rotate(40deg) translateY(-1px);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import Keyboard from "../core/input/keyboard.js";
|
|||
import RFB from "../core/rfb.js";
|
||||
import * as WebUtil from "./webutil.js";
|
||||
|
||||
const PAGE_TITLE = "noVNC";
|
||||
|
||||
// String validation
|
||||
function isAlphaNumeric(str) { return (str.match(/^[A-Za-z0-9]+$/) != null); };
|
||||
function isSafeString(str) { return ((typeof str == 'string') && (str.indexOf('<') == -1) && (str.indexOf('>') == -1) && (str.indexOf('&') == -1) && (str.indexOf('"') == -1) && (str.indexOf('\'') == -1) && (str.indexOf('+') == -1) && (str.indexOf('(') == -1) && (str.indexOf(')') == -1) && (str.indexOf('#') == -1) && (str.indexOf('%') == -1)) };
|
||||
|
|
@ -89,7 +91,7 @@ const UI = {
|
|||
// insecure context
|
||||
if (!window.isSecureContext) {
|
||||
// FIXME: This gets hidden when connecting
|
||||
UI.showStatus(_("HTTPS is required for full functionality"), 'error');
|
||||
UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error');
|
||||
}
|
||||
|
||||
// Try to fetch version number
|
||||
|
|
@ -1056,11 +1058,18 @@ const UI = {
|
|||
|
||||
|
||||
|
||||
try {
|
||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), urlargs.ws,
|
||||
{ shared: UI.getSetting('shared'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
} catch (exc) {
|
||||
Log.Error("Failed to connect to server: " + exc);
|
||||
UI.updateVisualState('disconnected');
|
||||
UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
UI.rfb = new RFB(document.getElementById('noVNC_container'), urlargs.ws,
|
||||
{ shared: UI.getSetting('shared'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||
UI.rfb.addEventListener("serververification", UI.serverVerify);
|
||||
|
|
@ -1166,6 +1175,7 @@ const UI = {
|
|||
UI.showStatus(_("Disconnected"), 'normal');
|
||||
}
|
||||
|
||||
document.title = PAGE_TITLE;
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
|
|
@ -1739,9 +1749,9 @@ const UI = {
|
|||
},
|
||||
|
||||
updateDesktopName(e) {
|
||||
// UI.desktopName = e.detail.name;
|
||||
UI.desktopName = e.detail.name;
|
||||
// Display the desktop name in the document title
|
||||
// document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||
document.title = e.detail.name + " - " + PAGE_TITLE;
|
||||
},
|
||||
|
||||
bell(e) {
|
||||
|
|
@ -1778,20 +1788,8 @@ const UI = {
|
|||
|
||||
// Set up translations
|
||||
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
|
||||
l10n.setup(LINGUAS);
|
||||
if (l10n.language === "en" || l10n.dictionary !== undefined) {
|
||||
UI.prime();
|
||||
} else {
|
||||
fetch('app/locale/' + l10n.language + '.json')
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw Error("" + response.status + " " + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((translations) => { l10n.dictionary = translations; })
|
||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||
.then(UI.prime);
|
||||
}
|
||||
l10n.setup(LINGUAS, "app/locale/")
|
||||
.catch(err => Log.Error("Failed to load translations: " + err))
|
||||
.then(UI.prime);
|
||||
|
||||
export default UI;
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@
|
|||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import { initLogging as mainInitLogging } from '../core/util/logging.js';
|
||||
import * as Log from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
export function initLogging(level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
mainInitLogging(level);
|
||||
Log.initLogging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
mainInitLogging(param || undefined);
|
||||
Log.initLogging(param || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -25,14 +25,14 @@ export function initLogging(level) {
|
|||
//
|
||||
// For privacy (Using a hastag #, the parameters will not be sent to the server)
|
||||
// the url can be requested in the following way:
|
||||
// https://www.example.com#myqueryparam=myvalue&password=secreatvalue
|
||||
// https://www.example.com#myqueryparam=myvalue&password=secretvalue
|
||||
//
|
||||
// Even Mixing public and non public parameters will work:
|
||||
// https://www.example.com?nonsecretparam=example.com#password=secreatvalue
|
||||
// https://www.example.com?nonsecretparam=example.com#password=secretvalue
|
||||
export function getQueryVar(name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = ''.concat(document.location.href, window.location.hash).match(re);
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
|
|
@ -146,7 +146,7 @@ export function writeSetting(name, value) {
|
|||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.set(settings);
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
localStorageSet(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ export function readSetting(name, defaultValue) {
|
|||
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
||||
value = settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
value = localStorageGet(name);
|
||||
settings[name] = value;
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
|
|
@ -181,6 +181,70 @@ export function eraseSetting(name) {
|
|||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
localStorageRemove(name);
|
||||
}
|
||||
}
|
||||
|
||||
let loggedMsgs = [];
|
||||
function logOnce(msg, level = "warn") {
|
||||
if (!loggedMsgs.includes(msg)) {
|
||||
switch (level) {
|
||||
case "error":
|
||||
Log.Error(msg);
|
||||
break;
|
||||
case "warn":
|
||||
Log.Warn(msg);
|
||||
break;
|
||||
case "debug":
|
||||
Log.Debug(msg);
|
||||
break;
|
||||
default:
|
||||
Log.Info(msg);
|
||||
}
|
||||
loggedMsgs.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
let cookiesMsg = "Couldn't access noVNC settings, are cookies disabled?";
|
||||
|
||||
function localStorageGet(name) {
|
||||
let r;
|
||||
try {
|
||||
r = localStorage.getItem(name);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.getItem(" + name + ")' failed: " + e,
|
||||
"debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
function localStorageSet(name, value) {
|
||||
try {
|
||||
localStorage.setItem(name, value);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.setItem(" + name + "," + value +
|
||||
")' failed: " + e, "debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
function localStorageRemove(name) {
|
||||
try {
|
||||
localStorage.removeItem(name);
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException) {
|
||||
logOnce(cookiesMsg);
|
||||
logOnce("'localStorage.removeItem(" + name + ")' failed: " + e,
|
||||
"debug");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
178
public/novnc/core/crypto/aes.js
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
export class AESECBCipher {
|
||||
constructor() {
|
||||
this._key = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "AES-ECB" };
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||
const cipher = new AESECBCipher;
|
||||
await cipher._importKey(key, extractable, keyUsages);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key, extractable, keyUsages) {
|
||||
this._key = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CBC"}, extractable, keyUsages);
|
||||
}
|
||||
|
||||
async encrypt(_algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
if (x.length % 16 !== 0 || this._key === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 16;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const y = new Uint8Array(await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: new Uint8Array(16),
|
||||
}, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);
|
||||
x.set(y, i * 16);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
export class AESEAXCipher {
|
||||
constructor() {
|
||||
this._rawKey = null;
|
||||
this._ctrKey = null;
|
||||
this._cbcKey = null;
|
||||
this._zeroBlock = new Uint8Array(16);
|
||||
this._prefixBlock0 = this._zeroBlock;
|
||||
this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "AES-EAX" };
|
||||
}
|
||||
|
||||
async _encryptBlock(block) {
|
||||
const encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: this._zeroBlock,
|
||||
}, this._cbcKey, block);
|
||||
return new Uint8Array(encrypted).slice(0, 16);
|
||||
}
|
||||
|
||||
async _initCMAC() {
|
||||
const k1 = await this._encryptBlock(this._zeroBlock);
|
||||
const k2 = new Uint8Array(16);
|
||||
const v = k1[0] >>> 6;
|
||||
for (let i = 0; i < 15; i++) {
|
||||
k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);
|
||||
k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);
|
||||
}
|
||||
const lut = [0x0, 0x87, 0x0e, 0x89];
|
||||
k2[14] ^= v >>> 1;
|
||||
k2[15] = (k1[15] << 2) ^ lut[v];
|
||||
k1[15] = (k1[15] << 1) ^ lut[v >> 1];
|
||||
this._k1 = k1;
|
||||
this._k2 = k2;
|
||||
}
|
||||
|
||||
async _encryptCTR(data, counter) {
|
||||
const encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CTR",
|
||||
counter: counter,
|
||||
length: 128
|
||||
}, this._ctrKey, data);
|
||||
return new Uint8Array(encrypted);
|
||||
}
|
||||
|
||||
async _decryptCTR(data, counter) {
|
||||
const decrypted = await window.crypto.subtle.decrypt({
|
||||
name: "AES-CTR",
|
||||
counter: counter,
|
||||
length: 128
|
||||
}, this._ctrKey, data);
|
||||
return new Uint8Array(decrypted);
|
||||
}
|
||||
|
||||
async _computeCMAC(data, prefixBlock) {
|
||||
if (prefixBlock.length !== 16) {
|
||||
return null;
|
||||
}
|
||||
const n = Math.floor(data.length / 16);
|
||||
const m = Math.ceil(data.length / 16);
|
||||
const r = data.length - n * 16;
|
||||
const cbcData = new Uint8Array((m + 1) * 16);
|
||||
cbcData.set(prefixBlock);
|
||||
cbcData.set(data, 16);
|
||||
if (r === 0) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
cbcData[n * 16 + i] ^= this._k1[i];
|
||||
}
|
||||
} else {
|
||||
cbcData[(n + 1) * 16 + r] = 0x80;
|
||||
for (let i = 0; i < 16; i++) {
|
||||
cbcData[(n + 1) * 16 + i] ^= this._k2[i];
|
||||
}
|
||||
}
|
||||
let cbcEncrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: this._zeroBlock,
|
||||
}, this._cbcKey, cbcData);
|
||||
|
||||
cbcEncrypted = new Uint8Array(cbcEncrypted);
|
||||
const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);
|
||||
return mac;
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new AESEAXCipher;
|
||||
await cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key) {
|
||||
this._rawKey = key;
|
||||
this._ctrKey = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CTR"}, false, ["encrypt", "decrypt"]);
|
||||
this._cbcKey = await window.crypto.subtle.importKey(
|
||||
"raw", key, {name: "AES-CBC"}, false, ["encrypt"]);
|
||||
await this._initCMAC();
|
||||
}
|
||||
|
||||
async encrypt(algorithm, message) {
|
||||
const ad = algorithm.additionalData;
|
||||
const nonce = algorithm.iv;
|
||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||
const encrypted = await this._encryptCTR(message, nCMAC);
|
||||
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||
const mac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
mac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||
}
|
||||
const res = new Uint8Array(16 + encrypted.length);
|
||||
res.set(encrypted);
|
||||
res.set(mac, encrypted.length);
|
||||
return res;
|
||||
}
|
||||
|
||||
async decrypt(algorithm, data) {
|
||||
const encrypted = data.slice(0, data.length - 16);
|
||||
const ad = algorithm.additionalData;
|
||||
const nonce = algorithm.iv;
|
||||
const mac = data.slice(data.length - 16);
|
||||
const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);
|
||||
const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);
|
||||
const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
computedMac[i] ^= nCMAC[i] ^ adCMAC[i];
|
||||
}
|
||||
if (computedMac.length !== mac.length) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < mac.length; i++) {
|
||||
if (computedMac[i] !== mac[i]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const res = await this._decryptCTR(encrypted, nCMAC);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
34
public/novnc/core/crypto/bigint.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
export function modPow(b, e, m) {
|
||||
let r = 1n;
|
||||
b = b % m;
|
||||
while (e > 0n) {
|
||||
if ((e & 1n) === 1n) {
|
||||
r = (r * b) % m;
|
||||
}
|
||||
e = e >> 1n;
|
||||
b = (b * b) % m;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export function bigIntToU8Array(bigint, padLength=0) {
|
||||
let hex = bigint.toString(16);
|
||||
if (padLength === 0) {
|
||||
padLength = Math.ceil(hex.length / 2);
|
||||
}
|
||||
hex = hex.padStart(padLength * 2, '0');
|
||||
const length = hex.length / 2;
|
||||
const arr = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function u8ArrayToBigInt(arr) {
|
||||
let hex = '0x';
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
hex += arr[i].toString(16).padStart(2, '0');
|
||||
}
|
||||
return BigInt(hex);
|
||||
}
|
||||
90
public/novnc/core/crypto/crypto.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { AESECBCipher, AESEAXCipher } from "./aes.js";
|
||||
import { DESCBCCipher, DESECBCipher } from "./des.js";
|
||||
import { RSACipher } from "./rsa.js";
|
||||
import { DHCipher } from "./dh.js";
|
||||
import { MD5 } from "./md5.js";
|
||||
|
||||
// A single interface for the cryptographic algorithms not supported by SubtleCrypto.
|
||||
// Both synchronous and asynchronous implmentations are allowed.
|
||||
class LegacyCrypto {
|
||||
constructor() {
|
||||
this._algorithms = {
|
||||
"AES-ECB": AESECBCipher,
|
||||
"AES-EAX": AESEAXCipher,
|
||||
"DES-ECB": DESECBCipher,
|
||||
"DES-CBC": DESCBCCipher,
|
||||
"RSA-PKCS1-v1_5": RSACipher,
|
||||
"DH": DHCipher,
|
||||
"MD5": MD5,
|
||||
};
|
||||
}
|
||||
|
||||
encrypt(algorithm, key, data) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.encrypt !== "function") {
|
||||
throw new Error("key does not support encryption");
|
||||
}
|
||||
return key.encrypt(algorithm, data);
|
||||
}
|
||||
|
||||
decrypt(algorithm, key, data) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.decrypt !== "function") {
|
||||
throw new Error("key does not support encryption");
|
||||
}
|
||||
return key.decrypt(algorithm, data);
|
||||
}
|
||||
|
||||
importKey(format, keyData, algorithm, extractable, keyUsages) {
|
||||
if (format !== "raw") {
|
||||
throw new Error("key format is not supported");
|
||||
}
|
||||
const alg = this._algorithms[algorithm.name];
|
||||
if (typeof alg === "undefined" || typeof alg.importKey !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg.importKey(keyData, algorithm, extractable, keyUsages);
|
||||
}
|
||||
|
||||
generateKey(algorithm, extractable, keyUsages) {
|
||||
const alg = this._algorithms[algorithm.name];
|
||||
if (typeof alg === "undefined" || typeof alg.generateKey !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg.generateKey(algorithm, extractable, keyUsages);
|
||||
}
|
||||
|
||||
exportKey(format, key) {
|
||||
if (format !== "raw") {
|
||||
throw new Error("key format is not supported");
|
||||
}
|
||||
if (typeof key.exportKey !== "function") {
|
||||
throw new Error("key does not support exportKey");
|
||||
}
|
||||
return key.exportKey();
|
||||
}
|
||||
|
||||
digest(algorithm, data) {
|
||||
const alg = this._algorithms[algorithm];
|
||||
if (typeof alg !== "function") {
|
||||
throw new Error("algorithm is not supported");
|
||||
}
|
||||
return alg(data);
|
||||
}
|
||||
|
||||
deriveBits(algorithm, key, length) {
|
||||
if (key.algorithm.name !== algorithm.name) {
|
||||
throw new Error("algorithm does not match");
|
||||
}
|
||||
if (typeof key.deriveBits !== "function") {
|
||||
throw new Error("key does not support deriveBits");
|
||||
}
|
||||
return key.deriveBits(algorithm, length);
|
||||
}
|
||||
}
|
||||
|
||||
export default new LegacyCrypto;
|
||||
330
public/novnc/core/crypto/des.js
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* Ported from Flashlight VNC ActionScript implementation:
|
||||
* http://www.wizhelp.com/flashlight-vnc/
|
||||
*
|
||||
* Full attribution follows:
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* This DES class has been extracted from package Acme.Crypto for use in VNC.
|
||||
* The unnecessary odd parity code has been removed.
|
||||
*
|
||||
* These changes are:
|
||||
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
|
||||
* DesCipher - the DES encryption method
|
||||
*
|
||||
* The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
|
||||
*
|
||||
* Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software
|
||||
* and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
|
||||
* without fee is hereby granted, provided that this copyright notice is kept
|
||||
* intact.
|
||||
*
|
||||
* WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
|
||||
* OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
|
||||
* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
|
||||
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
|
||||
*
|
||||
* THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
|
||||
* CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
|
||||
* PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
|
||||
* NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
|
||||
* SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
|
||||
* SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
|
||||
* PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
|
||||
* SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
|
||||
* HIGH RISK ACTIVITIES.
|
||||
*
|
||||
*
|
||||
* The rest is:
|
||||
*
|
||||
* Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. 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.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR OR CONTRIBUTORS 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.
|
||||
*
|
||||
* Visit the ACME Labs Java page for up-to-date versions of this and other
|
||||
* fine Java utilities: http://www.acme.com/java/
|
||||
*/
|
||||
|
||||
/* eslint-disable comma-spacing */
|
||||
|
||||
// Tables, permutations, S-boxes, etc.
|
||||
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
|
||||
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
|
||||
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
|
||||
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];
|
||||
|
||||
const z = 0x0;
|
||||
let a,b,c,d,e,f;
|
||||
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
|
||||
const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
|
||||
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
|
||||
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
|
||||
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
|
||||
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
|
||||
const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
|
||||
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
|
||||
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
|
||||
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
|
||||
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
|
||||
const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
|
||||
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
|
||||
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
|
||||
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
|
||||
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
|
||||
const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
|
||||
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
|
||||
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
|
||||
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
|
||||
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
|
||||
const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
|
||||
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
|
||||
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
|
||||
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
|
||||
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
|
||||
const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
|
||||
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
|
||||
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
|
||||
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
|
||||
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
|
||||
const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
|
||||
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
|
||||
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
|
||||
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
|
||||
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
|
||||
const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
|
||||
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
|
||||
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
|
||||
|
||||
/* eslint-enable comma-spacing */
|
||||
|
||||
class DES {
|
||||
constructor(password) {
|
||||
this.keys = [];
|
||||
|
||||
// Set the key.
|
||||
const pc1m = [], pcr = [], kn = [];
|
||||
|
||||
for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
|
||||
l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
|
||||
const m = l & 0x7;
|
||||
pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
const m = i << 1;
|
||||
const n = m + 1;
|
||||
kn[m] = kn[n] = 0;
|
||||
for (let o = 28; o < 59; o += 28) {
|
||||
for (let j = o - 28; j < o; ++j) {
|
||||
const l = j + totrot[i];
|
||||
pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < 24; ++j) {
|
||||
if (pcr[PC2[j]] !== 0) {
|
||||
kn[m] |= 1 << (23 - j);
|
||||
}
|
||||
if (pcr[PC2[j + 24]] !== 0) {
|
||||
kn[n] |= 1 << (23 - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cookey
|
||||
for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
|
||||
const raw0 = kn[rawi++];
|
||||
const raw1 = kn[rawi++];
|
||||
this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;
|
||||
this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
|
||||
this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
|
||||
this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
|
||||
++KnLi;
|
||||
this.keys[KnLi] = (raw0 & 0x0003f000) << 12;
|
||||
this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;
|
||||
this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
|
||||
this.keys[KnLi] |= (raw1 & 0x0000003f);
|
||||
++KnLi;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt 8 bytes of text
|
||||
enc8(text) {
|
||||
const b = text.slice();
|
||||
let i = 0, l, r, x; // left, right, accumulator
|
||||
|
||||
// Squash 8 bytes to 2 ints
|
||||
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
|
||||
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
|
||||
r ^= x;
|
||||
l ^= (x << 4);
|
||||
x = ((l >>> 16) ^ r) & 0x0000ffff;
|
||||
r ^= x;
|
||||
l ^= (x << 16);
|
||||
x = ((r >>> 2) ^ l) & 0x33333333;
|
||||
l ^= x;
|
||||
r ^= (x << 2);
|
||||
x = ((r >>> 8) ^ l) & 0x00ff00ff;
|
||||
l ^= x;
|
||||
r ^= (x << 8);
|
||||
r = (r << 1) | ((r >>> 31) & 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 1) | ((l >>> 31) & 1);
|
||||
|
||||
for (let i = 0, keysi = 0; i < 8; ++i) {
|
||||
x = (r << 28) | (r >>> 4);
|
||||
x ^= this.keys[keysi++];
|
||||
let fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = r ^ this.keys[keysi++];
|
||||
fval |= SP8[x & 0x3f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
l ^= fval;
|
||||
x = (l << 28) | (l >>> 4);
|
||||
x ^= this.keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = l ^ this.keys[keysi++];
|
||||
fval |= SP8[x & 0x0000003f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
r ^= fval;
|
||||
}
|
||||
|
||||
r = (r << 31) | (r >>> 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 31) | (l >>> 1);
|
||||
x = ((l >>> 8) ^ r) & 0x00ff00ff;
|
||||
r ^= x;
|
||||
l ^= (x << 8);
|
||||
x = ((l >>> 2) ^ r) & 0x33333333;
|
||||
r ^= x;
|
||||
l ^= (x << 2);
|
||||
x = ((r >>> 16) ^ l) & 0x0000ffff;
|
||||
l ^= x;
|
||||
r ^= (x << 16);
|
||||
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
|
||||
l ^= x;
|
||||
r ^= (x << 4);
|
||||
|
||||
// Spread ints to bytes
|
||||
x = [r, l];
|
||||
for (i = 0; i < 8; i++) {
|
||||
b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
|
||||
if (b[i] < 0) { b[i] += 256; } // unsigned
|
||||
}
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
export class DESECBCipher {
|
||||
constructor() {
|
||||
this._cipher = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DES-ECB" };
|
||||
}
|
||||
|
||||
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new DESECBCipher;
|
||||
cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
_importKey(key, _extractable, _keyUsages) {
|
||||
this._cipher = new DES(key);
|
||||
}
|
||||
|
||||
encrypt(_algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 8;
|
||||
for (let i = 0; i < n; i++) {
|
||||
x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
export class DESCBCCipher {
|
||||
constructor() {
|
||||
this._cipher = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DES-CBC" };
|
||||
}
|
||||
|
||||
static importKey(key, _algorithm, _extractable, _keyUsages) {
|
||||
const cipher = new DESCBCCipher;
|
||||
cipher._importKey(key);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
_importKey(key) {
|
||||
this._cipher = new DES(key);
|
||||
}
|
||||
|
||||
encrypt(algorithm, plaintext) {
|
||||
const x = new Uint8Array(plaintext);
|
||||
let y = new Uint8Array(algorithm.iv);
|
||||
if (x.length % 8 !== 0 || this._cipher === null) {
|
||||
return null;
|
||||
}
|
||||
const n = x.length / 8;
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
y[j] ^= plaintext[i * 8 + j];
|
||||
}
|
||||
y = this._cipher.enc8(y);
|
||||
x.set(y, i * 8);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
55
public/novnc/core/crypto/dh.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||
|
||||
class DHPublicKey {
|
||||
constructor(key) {
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DH" };
|
||||
}
|
||||
|
||||
exportKey() {
|
||||
return this._key;
|
||||
}
|
||||
}
|
||||
|
||||
export class DHCipher {
|
||||
constructor() {
|
||||
this._g = null;
|
||||
this._p = null;
|
||||
this._gBigInt = null;
|
||||
this._pBigInt = null;
|
||||
this._privateKey = null;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "DH" };
|
||||
}
|
||||
|
||||
static generateKey(algorithm, _extractable) {
|
||||
const cipher = new DHCipher;
|
||||
cipher._generateKey(algorithm);
|
||||
return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };
|
||||
}
|
||||
|
||||
_generateKey(algorithm) {
|
||||
const g = algorithm.g;
|
||||
const p = algorithm.p;
|
||||
this._keyBytes = p.length;
|
||||
this._gBigInt = u8ArrayToBigInt(g);
|
||||
this._pBigInt = u8ArrayToBigInt(p);
|
||||
this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));
|
||||
this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);
|
||||
this._publicKey = bigIntToU8Array(modPow(
|
||||
this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);
|
||||
}
|
||||
|
||||
deriveBits(algorithm, length) {
|
||||
const bytes = Math.ceil(length / 8);
|
||||
const pkey = new Uint8Array(algorithm.public);
|
||||
const len = bytes > this._keyBytes ? bytes : this._keyBytes;
|
||||
const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);
|
||||
return bigIntToU8Array(secret, len).slice(0, len);
|
||||
}
|
||||
}
|
||||
82
public/novnc/core/crypto/md5.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2021 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Performs MD5 hashing on an array of bytes, returns an array of bytes
|
||||
*/
|
||||
|
||||
export async function MD5(d) {
|
||||
let s = "";
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
s += String.fromCharCode(d[i]);
|
||||
}
|
||||
return M(V(Y(X(s), 8 * s.length)));
|
||||
}
|
||||
|
||||
function M(d) {
|
||||
let f = new Uint8Array(d.length);
|
||||
for (let i=0;i<d.length;i++) {
|
||||
f[i] = d.charCodeAt(i);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
function X(d) {
|
||||
let r = Array(d.length >> 2);
|
||||
for (let m = 0; m < r.length; m++) r[m] = 0;
|
||||
for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
|
||||
return r;
|
||||
}
|
||||
|
||||
function V(d) {
|
||||
let r = "";
|
||||
for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
|
||||
return r;
|
||||
}
|
||||
|
||||
function Y(d, g) {
|
||||
d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;
|
||||
let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;
|
||||
for (let n = 0; n < d.length; n += 16) {
|
||||
let h = m,
|
||||
t = f,
|
||||
g = r,
|
||||
e = i;
|
||||
f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);
|
||||
}
|
||||
return Array(m, f, r, i);
|
||||
}
|
||||
|
||||
function cmn(d, g, m, f, r, i) {
|
||||
return add(rol(add(add(g, d), add(f, i)), r), m);
|
||||
}
|
||||
|
||||
function ff(d, g, m, f, r, i, n) {
|
||||
return cmn(g & m | ~g & f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function gg(d, g, m, f, r, i, n) {
|
||||
return cmn(g & f | m & ~f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function hh(d, g, m, f, r, i, n) {
|
||||
return cmn(g ^ m ^ f, d, g, r, i, n);
|
||||
}
|
||||
|
||||
function ii(d, g, m, f, r, i, n) {
|
||||
return cmn(m ^ (g | ~f), d, g, r, i, n);
|
||||
}
|
||||
|
||||
function add(d, g) {
|
||||
let m = (65535 & d) + (65535 & g);
|
||||
return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;
|
||||
}
|
||||
|
||||
function rol(d, g) {
|
||||
return d << g | d >>> 32 - g;
|
||||
}
|
||||
132
public/novnc/core/crypto/rsa.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import Base64 from "../base64.js";
|
||||
import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
|
||||
|
||||
export class RSACipher {
|
||||
constructor() {
|
||||
this._keyLength = 0;
|
||||
this._keyBytes = 0;
|
||||
this._n = null;
|
||||
this._e = null;
|
||||
this._d = null;
|
||||
this._nBigInt = null;
|
||||
this._eBigInt = null;
|
||||
this._dBigInt = null;
|
||||
this._extractable = false;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
return { name: "RSA-PKCS1-v1_5" };
|
||||
}
|
||||
|
||||
_base64urlDecode(data) {
|
||||
data = data.replace(/-/g, "+").replace(/_/g, "/");
|
||||
data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
|
||||
return Base64.decode(data);
|
||||
}
|
||||
|
||||
_padArray(arr, length) {
|
||||
const res = new Uint8Array(length);
|
||||
res.set(arr, length - arr.length);
|
||||
return res;
|
||||
}
|
||||
|
||||
static async generateKey(algorithm, extractable, _keyUsages) {
|
||||
const cipher = new RSACipher;
|
||||
await cipher._generateKey(algorithm, extractable);
|
||||
return { privateKey: cipher };
|
||||
}
|
||||
|
||||
async _generateKey(algorithm, extractable) {
|
||||
this._keyLength = algorithm.modulusLength;
|
||||
this._keyBytes = Math.ceil(this._keyLength / 8);
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: algorithm.modulusLength,
|
||||
publicExponent: algorithm.publicExponent,
|
||||
hash: {name: "SHA-256"},
|
||||
},
|
||||
true, ["encrypt", "decrypt"]);
|
||||
const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
|
||||
this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
|
||||
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||
this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
|
||||
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||
this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
|
||||
this._dBigInt = u8ArrayToBigInt(this._d);
|
||||
this._extractable = extractable;
|
||||
}
|
||||
|
||||
static async importKey(key, _algorithm, extractable, keyUsages) {
|
||||
if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
|
||||
throw new Error("only support importing RSA public key");
|
||||
}
|
||||
const cipher = new RSACipher;
|
||||
await cipher._importKey(key, extractable);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
async _importKey(key, extractable) {
|
||||
const n = key.n;
|
||||
const e = key.e;
|
||||
if (n.length !== e.length) {
|
||||
throw new Error("the sizes of modulus and public exponent do not match");
|
||||
}
|
||||
this._keyBytes = n.length;
|
||||
this._keyLength = this._keyBytes * 8;
|
||||
this._n = new Uint8Array(this._keyBytes);
|
||||
this._e = new Uint8Array(this._keyBytes);
|
||||
this._n.set(n);
|
||||
this._e.set(e);
|
||||
this._nBigInt = u8ArrayToBigInt(this._n);
|
||||
this._eBigInt = u8ArrayToBigInt(this._e);
|
||||
this._extractable = extractable;
|
||||
}
|
||||
|
||||
async encrypt(_algorithm, message) {
|
||||
if (message.length > this._keyBytes - 11) {
|
||||
return null;
|
||||
}
|
||||
const ps = new Uint8Array(this._keyBytes - message.length - 3);
|
||||
window.crypto.getRandomValues(ps);
|
||||
for (let i = 0; i < ps.length; i++) {
|
||||
ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
|
||||
}
|
||||
const em = new Uint8Array(this._keyBytes);
|
||||
em[1] = 0x02;
|
||||
em.set(ps, 2);
|
||||
em.set(message, ps.length + 3);
|
||||
const emBigInt = u8ArrayToBigInt(em);
|
||||
const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
|
||||
return bigIntToU8Array(c, this._keyBytes);
|
||||
}
|
||||
|
||||
async decrypt(_algorithm, message) {
|
||||
if (message.length !== this._keyBytes) {
|
||||
return null;
|
||||
}
|
||||
const msgBigInt = u8ArrayToBigInt(message);
|
||||
const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
|
||||
const em = bigIntToU8Array(emBigInt, this._keyBytes);
|
||||
if (em[0] !== 0x00 || em[1] !== 0x02) {
|
||||
return null;
|
||||
}
|
||||
let i = 2;
|
||||
for (; i < em.length; i++) {
|
||||
if (em[i] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i === em.length) {
|
||||
return null;
|
||||
}
|
||||
return em.slice(i + 1, em.length);
|
||||
}
|
||||
|
||||
async exportKey() {
|
||||
if (!this._extractable) {
|
||||
throw new Error("key is not extractable");
|
||||
}
|
||||
return { n: this._n, e: this._e, d: this._d };
|
||||
}
|
||||
}
|
||||
|
|
@ -31,10 +31,7 @@ export default class HextileDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
let rQ = sock.rQ;
|
||||
let rQi = sock.rQi;
|
||||
|
||||
let subencoding = rQ[rQi]; // Peek
|
||||
let subencoding = sock.rQpeek8();
|
||||
if (subencoding > 30) { // Raw
|
||||
throw new Error("Illegal hextile subencoding (subencoding: " +
|
||||
subencoding + ")");
|
||||
|
|
@ -65,7 +62,7 @@ export default class HextileDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
let subrects = rQ[rQi + bytes - 1]; // Peek
|
||||
let subrects = sock.rQpeekBytes(bytes).at(-1);
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
bytes += subrects * (4 + 2);
|
||||
} else {
|
||||
|
|
@ -79,7 +76,7 @@ export default class HextileDecoder {
|
|||
}
|
||||
|
||||
// We know the encoding and have a whole tile
|
||||
rQi++;
|
||||
sock.rQshift8();
|
||||
if (subencoding === 0) {
|
||||
if (this._lastsubencoding & 0x01) {
|
||||
// Weird: ignore blanks are RAW
|
||||
|
|
@ -89,42 +86,36 @@ export default class HextileDecoder {
|
|||
}
|
||||
} else if (subencoding & 0x01) { // Raw
|
||||
let pixels = tw * th;
|
||||
let data = sock.rQshiftBytes(pixels * 4, false);
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0;i < pixels;i++) {
|
||||
rQ[rQi + i * 4 + 3] = 255;
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
display.blitImage(tx, ty, tw, th, rQ, rQi);
|
||||
rQi += bytes - 1;
|
||||
display.blitImage(tx, ty, tw, th, data, 0);
|
||||
} else {
|
||||
if (subencoding & 0x02) { // Background
|
||||
this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
this._background = new Uint8Array(sock.rQshiftBytes(4));
|
||||
}
|
||||
if (subencoding & 0x04) { // Foreground
|
||||
this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
this._foreground = new Uint8Array(sock.rQshiftBytes(4));
|
||||
}
|
||||
|
||||
this._startTile(tx, ty, tw, th, this._background);
|
||||
if (subencoding & 0x08) { // AnySubrects
|
||||
let subrects = rQ[rQi];
|
||||
rQi++;
|
||||
let subrects = sock.rQshift8();
|
||||
|
||||
for (let s = 0; s < subrects; s++) {
|
||||
let color;
|
||||
if (subencoding & 0x10) { // SubrectsColoured
|
||||
color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
|
||||
rQi += 4;
|
||||
color = sock.rQshiftBytes(4);
|
||||
} else {
|
||||
color = this._foreground;
|
||||
}
|
||||
const xy = rQ[rQi];
|
||||
rQi++;
|
||||
const xy = sock.rQshift8();
|
||||
const sx = (xy >> 4);
|
||||
const sy = (xy & 0x0f);
|
||||
|
||||
const wh = rQ[rQi];
|
||||
rQi++;
|
||||
const wh = sock.rQshift8();
|
||||
const sw = (wh >> 4) + 1;
|
||||
const sh = (wh & 0x0f) + 1;
|
||||
|
||||
|
|
@ -133,7 +124,6 @@ export default class HextileDecoder {
|
|||
}
|
||||
this._finishTile(display);
|
||||
}
|
||||
sock.rQi = rQi;
|
||||
this._lastsubencoding = subencoding;
|
||||
this._tiles--;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,131 +11,136 @@ export default class JPEGDecoder {
|
|||
constructor() {
|
||||
// RealVNC will reuse the quantization tables
|
||||
// and Huffman tables, so we need to cache them.
|
||||
this._quantTables = [];
|
||||
this._huffmanTables = [];
|
||||
this._cachedQuantTables = [];
|
||||
this._cachedHuffmanTables = [];
|
||||
|
||||
this._jpegLength = 0;
|
||||
this._segments = [];
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
// A rect of JPEG encodings is simply a JPEG file
|
||||
if (!this._parseJPEG(sock.rQslice(0))) {
|
||||
return false;
|
||||
}
|
||||
const data = sock.rQshiftBytes(this._jpegLength);
|
||||
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
|
||||
// If there are quantization tables and Huffman tables in the JPEG
|
||||
// image, we can directly render it.
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
return true;
|
||||
} else {
|
||||
// Otherwise we need to insert cached tables.
|
||||
const sofIndex = this._segments.findIndex(
|
||||
x => x[1] == 0xC0 || x[1] == 0xC2
|
||||
);
|
||||
if (sofIndex == -1) {
|
||||
throw new Error("Illegal JPEG image without SOF");
|
||||
}
|
||||
let segments = this._segments.slice(0, sofIndex);
|
||||
segments = segments.concat(this._quantTables.length ?
|
||||
this._quantTables :
|
||||
this._cachedQuantTables);
|
||||
segments.push(this._segments[sofIndex]);
|
||||
segments = segments.concat(this._huffmanTables.length ?
|
||||
this._huffmanTables :
|
||||
this._cachedHuffmanTables,
|
||||
this._segments.slice(sofIndex + 1));
|
||||
let length = 0;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
length += segments[i].length;
|
||||
}
|
||||
const data = new Uint8Array(length);
|
||||
length = 0;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
data.set(segments[i], length);
|
||||
length += segments[i].length;
|
||||
}
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_parseJPEG(buffer) {
|
||||
if (this._quantTables.length != 0) {
|
||||
this._cachedQuantTables = this._quantTables;
|
||||
}
|
||||
if (this._huffmanTables.length != 0) {
|
||||
this._cachedHuffmanTables = this._huffmanTables;
|
||||
}
|
||||
this._quantTables = [];
|
||||
this._huffmanTables = [];
|
||||
this._segments = [];
|
||||
let i = 0;
|
||||
let bufferLength = buffer.length;
|
||||
while (true) {
|
||||
let j = i;
|
||||
if (j + 2 > bufferLength) {
|
||||
let segment = this._readSegment(sock);
|
||||
if (segment === null) {
|
||||
return false;
|
||||
}
|
||||
if (buffer[j] != 0xFF) {
|
||||
throw new Error("Illegal JPEG marker received (byte: " +
|
||||
buffer[j] + ")");
|
||||
}
|
||||
const type = buffer[j+1];
|
||||
j += 2;
|
||||
if (type == 0xD9) {
|
||||
this._jpegLength = j;
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
return true;
|
||||
} else if (type == 0xDA) {
|
||||
// start of scan
|
||||
let hasFoundEndOfScan = false;
|
||||
for (let k = j + 3; k + 1 < bufferLength; k++) {
|
||||
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
|
||||
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
|
||||
j = k;
|
||||
hasFoundEndOfScan = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasFoundEndOfScan) {
|
||||
return false;
|
||||
}
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
i = j;
|
||||
continue;
|
||||
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
|
||||
// No length after marker
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
if (j + 2 > bufferLength) {
|
||||
return false;
|
||||
}
|
||||
const length = (buffer[j] << 8) + buffer[j+1] - 2;
|
||||
if (length < 0) {
|
||||
throw new Error("Illegal JPEG length received (length: " +
|
||||
length + ")");
|
||||
}
|
||||
j += 2;
|
||||
if (j + length > bufferLength) {
|
||||
return false;
|
||||
}
|
||||
j += length;
|
||||
const segment = buffer.slice(i, j);
|
||||
if (type == 0xC4) {
|
||||
// Huffman tables
|
||||
this._huffmanTables.push(segment);
|
||||
} else if (type == 0xDB) {
|
||||
// Quantization tables
|
||||
this._quantTables.push(segment);
|
||||
}
|
||||
this._segments.push(segment);
|
||||
i = j;
|
||||
// End of image?
|
||||
if (segment[1] === 0xD9) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let huffmanTables = [];
|
||||
let quantTables = [];
|
||||
for (let segment of this._segments) {
|
||||
let type = segment[1];
|
||||
if (type === 0xC4) {
|
||||
// Huffman tables
|
||||
huffmanTables.push(segment);
|
||||
} else if (type === 0xDB) {
|
||||
// Quantization tables
|
||||
quantTables.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
const sofIndex = this._segments.findIndex(
|
||||
x => x[1] == 0xC0 || x[1] == 0xC2
|
||||
);
|
||||
if (sofIndex == -1) {
|
||||
throw new Error("Illegal JPEG image without SOF");
|
||||
}
|
||||
|
||||
if (quantTables.length === 0) {
|
||||
this._segments.splice(sofIndex+1, 0,
|
||||
...this._cachedQuantTables);
|
||||
}
|
||||
if (huffmanTables.length === 0) {
|
||||
this._segments.splice(sofIndex+1, 0,
|
||||
...this._cachedHuffmanTables);
|
||||
}
|
||||
|
||||
let length = 0;
|
||||
for (let segment of this._segments) {
|
||||
length += segment.length;
|
||||
}
|
||||
|
||||
let data = new Uint8Array(length);
|
||||
length = 0;
|
||||
for (let segment of this._segments) {
|
||||
data.set(segment, length);
|
||||
length += segment.length;
|
||||
}
|
||||
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
|
||||
if (huffmanTables.length !== 0) {
|
||||
this._cachedHuffmanTables = huffmanTables;
|
||||
}
|
||||
if (quantTables.length !== 0) {
|
||||
this._cachedQuantTables = quantTables;
|
||||
}
|
||||
|
||||
this._segments = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_readSegment(sock) {
|
||||
if (sock.rQwait("JPEG", 2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let marker = sock.rQshift8();
|
||||
if (marker != 0xFF) {
|
||||
throw new Error("Illegal JPEG marker received (byte: " +
|
||||
marker + ")");
|
||||
}
|
||||
let type = sock.rQshift8();
|
||||
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
|
||||
// No length after marker
|
||||
return new Uint8Array([marker, type]);
|
||||
}
|
||||
|
||||
if (sock.rQwait("JPEG", 2, 2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let length = sock.rQshift16();
|
||||
if (length < 2) {
|
||||
throw new Error("Illegal JPEG length received (length: " +
|
||||
length + ")");
|
||||
}
|
||||
|
||||
if (sock.rQwait("JPEG", length-2, 4)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let extra = 0;
|
||||
if (type === 0xDA) {
|
||||
// start of scan
|
||||
extra += 2;
|
||||
while (true) {
|
||||
if (sock.rQwait("JPEG", length-2+extra, 4)) {
|
||||
return null;
|
||||
}
|
||||
let data = sock.rQpeekBytes(length-2+extra, false);
|
||||
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
|
||||
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
|
||||
extra -= 2;
|
||||
break;
|
||||
}
|
||||
extra++;
|
||||
}
|
||||
}
|
||||
|
||||
let segment = new Uint8Array(2 + length + extra);
|
||||
segment[0] = marker;
|
||||
segment[1] = type;
|
||||
segment[2] = length >> 8;
|
||||
segment[3] = length;
|
||||
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
|
||||
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,41 +24,34 @@ export default class RawDecoder {
|
|||
const pixelSize = depth == 8 ? 1 : 4;
|
||||
const bytesPerLine = width * pixelSize;
|
||||
|
||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const curY = y + (height - this._lines);
|
||||
const currHeight = Math.min(this._lines,
|
||||
Math.floor(sock.rQlen / bytesPerLine));
|
||||
const pixels = width * currHeight;
|
||||
|
||||
let data = sock.rQ;
|
||||
let index = sock.rQi;
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const newdata = new Uint8Array(pixels * 4);
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
while (this._lines > 0) {
|
||||
if (sock.rQwait("RAW", bytesPerLine)) {
|
||||
return false;
|
||||
}
|
||||
data = newdata;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < pixels; i++) {
|
||||
data[index + i * 4 + 3] = 255;
|
||||
}
|
||||
const curY = y + (height - this._lines);
|
||||
|
||||
display.blitImage(x, curY, width, currHeight, data, index);
|
||||
sock.rQskipBytes(currHeight * bytesPerLine);
|
||||
this._lines -= currHeight;
|
||||
if (this._lines > 0) {
|
||||
return false;
|
||||
let data = sock.rQshiftBytes(bytesPerLine, false);
|
||||
|
||||
// Convert data if needed
|
||||
if (depth == 8) {
|
||||
const newdata = new Uint8Array(width * 4);
|
||||
for (let i = 0; i < width; i++) {
|
||||
newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
|
||||
newdata[i * 4 + 3] = 255;
|
||||
}
|
||||
data = newdata;
|
||||
}
|
||||
|
||||
// Max sure the image is fully opaque
|
||||
for (let i = 0; i < width; i++) {
|
||||
data[i * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
display.blitImage(x, curY, width, 1, data, 0);
|
||||
this._lines--;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -76,12 +76,8 @@ export default class TightDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
const rQi = sock.rQi;
|
||||
const rQ = sock.rQ;
|
||||
|
||||
display.fillRect(x, y, width, height,
|
||||
[rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
|
||||
sock.rQskipBytes(3);
|
||||
let pixel = sock.rQshiftBytes(3);
|
||||
display.fillRect(x, y, width, height, pixel, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -289,7 +285,73 @@ export default class TightDecoder {
|
|||
}
|
||||
|
||||
_gradientFilter(streamId, x, y, width, height, sock, display, depth) {
|
||||
throw new Error("Gradient filter not implemented");
|
||||
// assume the TPIXEL is 3 bytes long
|
||||
const uncompressedSize = width * height * 3;
|
||||
let data;
|
||||
|
||||
if (uncompressedSize === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uncompressedSize < 12) {
|
||||
if (sock.rQwait("TIGHT", uncompressedSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = sock.rQshiftBytes(uncompressedSize);
|
||||
} else {
|
||||
data = this._readData(sock);
|
||||
if (data === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._zlibs[streamId].setInput(data);
|
||||
data = this._zlibs[streamId].inflate(uncompressedSize);
|
||||
this._zlibs[streamId].setInput(null);
|
||||
}
|
||||
|
||||
let rgbx = new Uint8Array(4 * width * height);
|
||||
|
||||
let rgbxIndex = 0, dataIndex = 0;
|
||||
let left = new Uint8Array(3);
|
||||
for (let x = 0; x < width; x++) {
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const prediction = left[c];
|
||||
const value = data[dataIndex++] + prediction;
|
||||
rgbx[rgbxIndex++] = value;
|
||||
left[c] = value;
|
||||
}
|
||||
rgbx[rgbxIndex++] = 255;
|
||||
}
|
||||
|
||||
let upperIndex = 0;
|
||||
let upper = new Uint8Array(3),
|
||||
upperleft = new Uint8Array(3);
|
||||
for (let y = 1; y < height; y++) {
|
||||
left.fill(0);
|
||||
upperleft.fill(0);
|
||||
for (let x = 0; x < width; x++) {
|
||||
for (let c = 0; c < 3; c++) {
|
||||
upper[c] = rgbx[upperIndex++];
|
||||
let prediction = left[c] + upper[c] - upperleft[c];
|
||||
if (prediction < 0) {
|
||||
prediction = 0;
|
||||
} else if (prediction > 255) {
|
||||
prediction = 255;
|
||||
}
|
||||
const value = data[dataIndex++] + prediction;
|
||||
rgbx[rgbxIndex++] = value;
|
||||
upperleft[c] = upper[c];
|
||||
left[c] = value;
|
||||
}
|
||||
rgbx[rgbxIndex++] = 255;
|
||||
upperIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
display.blitImage(x, y, width, height, rgbx, 0, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_readData(sock) {
|
||||
|
|
@ -316,7 +378,7 @@ export default class TightDecoder {
|
|||
return null;
|
||||
}
|
||||
|
||||
let data = sock.rQshiftBytes(this._len);
|
||||
let data = sock.rQshiftBytes(this._len, false);
|
||||
this._len = 0;
|
||||
|
||||
return data;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default class ZRLEDecoder {
|
|||
return false;
|
||||
}
|
||||
|
||||
const data = sock.rQshiftBytes(this._length);
|
||||
const data = sock.rQshiftBytes(this._length, false);
|
||||
|
||||
this._inflator.setInput(data);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
|
||||
export default class Deflator {
|
||||
|
|
@ -15,9 +15,8 @@ export default class Deflator {
|
|||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.outputBuffer = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
deflateInit(this.strm, this.windowBits);
|
||||
deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
|
||||
}
|
||||
|
||||
deflate(inData) {
|
||||
|
|
|
|||