1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-03-09 15:40:18 +00:00

Compare commits

...

551 commits

Author SHA1 Message Date
si458
88a765bb13 add agentupdate to console agent actions #6869
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-08 18:37:18 +00:00
Daniel Hammerschmidt
7ad4b917be
print stack trace on plugin error and add space for readability of error message (#6859)
* add space for readability of error message

* print stack trace on plugin error
2025-03-08 17:50:39 +00:00
si458
d10173a018 fix sharing-mobile view only having control #6764
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-08 17:20:30 +00:00
si458
0e3a6b4915 fix logoutOnIdleSessionTimeout spelling and extra translations #6851
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-08 16:48:37 +00:00
Ylian Saint-Hilaire
b949cecc5f Version 1.1.42 2025-03-07 09:06:50 -08:00
Ylian Saint-Hilaire
c7cbf2f12a Updated Windows x86-32 and 64 agents. 2025-03-07 09:05:07 -08:00
si458
d49afdd7bf report version/size correctly to control panel #6860
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-07 16:31:11 +00:00
Ylian Saint-Hilaire
133e77c8c6 Version 1.1.41 2025-03-05 18:09:42 -08:00
Ylian Saint-Hilaire
e404e86b9f Updated Windows Agents 2025-03-05 18:06:30 -08:00
si458
c6da201af8 fix duo 2fa redirect when session changes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-05 10:37:39 +00:00
Roman
9a27d7637c
Improvements to the German translation in various areas (#6849) 2025-03-04 20:44:36 +00:00
Daniel Hammerschmidt
5aa2467409
add comment about meshrelay ping-pong in docs(#6842) 2025-03-04 18:47:00 +00:00
si458
9398afd07e fix getDeviceDetails asking for all devices not including lastconnect #6833
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-03-03 14:13:34 +00:00
Ylian Saint-Hilaire
b2cd84035b Version 1.1.40 2025-03-02 10:47:32 -08:00
Ylian Saint-Hilaire
97547d72a3 Updated MeshCmd 2025-03-02 10:46:13 -08:00
si458
7faf043c35 recording not recoding
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-27 18:35:11 +00:00
Daniel Hammerschmidt
9df0330896
avoid double logging in debug console (#6827) 2025-02-27 18:23:36 +00:00
si458
42f61ea46e fix bootstrap padding #6755
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-27 18:08:12 +00:00
ijustw0rkhere
0d65080a8a
fix runcommands in a peering environment (#6825)
* fix adding meshes and user groups in a peering environment

* fix runcommands in a peering environment
2025-02-27 15:12:18 +00:00
si458
bd4d8b12d4 fix consent with oldstyle: true and rdp sessions #6816
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-26 20:51:24 +00:00
si458
18ae8bdbf4 fix relaystate for entra #6822
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-26 20:32:38 +00:00
si458
46c76f7234 smoothing not smooting #6818
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-26 15:36:23 +00:00
Ylian Saint-Hilaire
e65bf77111 Version 1.1.39 2025-02-25 19:58:15 -08:00
Ylian Saint-Hilaire
f19ad6c664 Extra argument validation. 2025-02-25 19:57:27 -08:00
Ylian Saint-Hilaire
fe2f12149d Updated French Translations. 2025-02-25 19:20:43 -08:00
si458
91bd5ae702 fix minify files and log minify errors
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-25 17:21:50 +00:00
Daniel Hammerschmidt
38f5bf2e0f
Fix parameter names in usage message (#6810) 2025-02-25 13:08:51 +00:00
Daniel Hammerschmidt
79f00bcaab
fix evaluation of config.settings.agentlogdump (#6812) 2025-02-25 12:51:52 +00:00
si458
c55065505b fix relaylog ip order again
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-23 01:13:20 +00:00
PTR
e0e8a3fcaa
Add comma to escapeFieldName for NeDBv4 (#6803) 2025-02-22 16:59:41 +00:00
Daniel Hammerschmidt
3f77cfa93a
Add collectors to monitoring (#6777)
* Add collectors to monitoring

* Pass request and response objects to collectors
2025-02-20 22:37:14 +00:00
Jakub Maruszczak
5ee9aa2410
fix meshctrl tag filter search (#6798) 2025-02-20 22:16:15 +00:00
Jakub Maruszczak
0ab3f01ca6
prevent runcommand with --reply from terminating other ws connections (#6797) 2025-02-20 22:09:02 +00:00
Daniel Hammerschmidt
4b621a01fb
Fix gauge typo (#6778) 2025-02-20 21:59:36 +00:00
si458
f2681de87d fix webrtc viewonly mode #6792
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-19 14:03:31 +00:00
PTR
c90fa55c99
Init webstate with empty object (#6788) 2025-02-18 11:35:23 +00:00
si458
edeef03f00 track/show locked active users #6782
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-14 15:42:43 +00:00
si458
d7fe87d1db fix sessionrecording ip addresses wrong way round
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-13 21:39:15 +00:00
Martin Mädler
9d4f51e970
Add support for logoutOnIdleSessionTimeout (#6773) 2025-02-12 10:04:10 +00:00
si458
0b376fe5a0 fix player for windows and terminal #6761
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-11 17:04:19 +00:00
si458
9fd40751b2 fix remote desktop consent for rdp sessions #6710
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-09 21:41:08 +00:00
si458
d246307fae translation improvements
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-09 21:03:54 +00:00
Simon Smith
711bb56a93
autoAcceptIfNoUser (#6759)
* autoAcceptIfNoUser for desktop

* autoAcceptIfNoUser for terminal

* autoAcceptIfNoUser for files

* forgot few extra files

---------

Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-09 19:54:51 +00:00
Simon Smith
5734bcc33a
UI Toggle between Classic and Modern (#6763)
* [ENH] Add toggle switch for new MeshCentral UI in settings and top header of the classic UI

* [ENH] Add toggle for new MeshCentral UI in settings and in top headbar of the modern UI

* add showModernUIToggle and store uiviewmode in db

---------

Co-authored-by: kambereBr <brunokambere@gmail.com>
2025-02-09 19:41:47 +00:00
si458
1310c57397 return more than 100 groups for azure oidc #6669
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-05 21:21:39 +00:00
si458
854d6c00d2 fix view chunksize being too big causing pixelation
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-05 11:44:23 +00:00
si458
c96d7ff1ca remove CSP for web relay as apps dont load properly with the default set #6456
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-02-05 11:32:56 +00:00
Simon Smith
712f06db3c
fix meshcentral-config-schema.json for ace editor 2025-01-27 23:02:56 +00:00
si458
cac505e2cd few more translations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-26 18:10:26 +00:00
interfect
9d962bc523
Note maximum password length in USB key dialog (#6735) 2025-01-26 18:02:42 +00:00
si458
f079692b16 change dnslytics.com to maclookup.app for mac address lookup #6704
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-26 14:57:41 +00:00
nmmclwhitehead
3ee06abfe8
Update webserver.js - allow saml relaystate in POST request (#6685)
added check for relaystate saml and regex check

added in rest of allowed params

correct formatting on regex string - now evaluates correctly

set relaystate on get request

check for ipv6
2025-01-26 14:42:48 +00:00
PTR
b46ddf2f70
Update console info command (#6722)
* Add human readable option (h) to info command

* Add option to helptext
2025-01-26 14:35:18 +00:00
PTR
f7b958d28b
Autobackup update (#6695)
* add backupHours option and many debug messages

* Cleanup debug messages, add backupinfo

* Add full path to remove log message

* Put backupcheck after config init, check proper backuppath

* Handle absolute backuppath, check access in checkBackupCapability, seperated expired files check to function, more message edits, serverwarnings

* Revert fallback to default backuppath

* Cleanup checkBackupCapability and messages

* add WebDAV messages
2025-01-26 14:24:40 +00:00
KevinBK1998
64c8d2c238
include username for amt direct connect in logs 2025-01-26 14:07:37 +00:00
si458
31f2224a93 add notransval to fix select options translate issues
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-26 13:54:04 +00:00
petervanv
de685556c8
Dutch language update 1.1.38 (#6716)
update and cleaned up some useless strings
2025-01-26 12:52:04 +00:00
si458
92375ddc93 add lastbootuptime to csv and fix spaces in csv translations #6723
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-26 12:42:30 +00:00
si458
ea80f8595e fix some functions not being called in sitestyle=3 #6733
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-26 10:59:08 +00:00
Ylian Saint-Hilaire
763f76b68f Version 1.1.38 2025-01-09 17:08:31 -08:00
nmmclwhitehead
1a02539f23
Added support for &gotodeviceip=x.x.x.x (#6672) 2025-01-08 11:27:50 +00:00
si458
90b71e924f properly fix multiple dialog popups in a row
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-07 16:52:19 +00:00
si458
73c18c4dd5 fix deviceaction multiple modals not working
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-07 16:35:13 +00:00
si458
def62075c7 fix theme switch to default bug and include google fonts in csp #6665
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-06 16:02:10 +00:00
Ylian Saint-Hilaire
998769a888 Version 1.1.37 2025-01-05 15:37:41 -08:00
si458
ca6ec5ebc9 fix delete node bootstrap ui
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 22:16:09 +00:00
si458
0dd56d5708 improve translations all around
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 21:37:56 +00:00
si458
95729d2a88 set default minify to false in docker
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 17:47:21 +00:00
si458
1ae7b9f641 fix minify in bootstrap #6662
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 17:38:03 +00:00
si458
c66a9a12ef i hate sundays, fix passport module again
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 14:48:51 +00:00
si458
2c310450cf fix passport module not installing for multiple domains
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-05 14:40:19 +00:00
Ylian Saint-Hilaire
b6e022c01e Version 1.1.36 2025-01-03 17:22:49 -08:00
si458
e66776c5da if no nodes return nothing for getDeviceDetails #6637
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-03 16:59:29 +00:00
si458
d1cb184e9e fix deleting user with no events causes page to not change
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-03 16:18:42 +00:00
si458
6aa60d556f fix reload issue with # in url
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-03 16:03:17 +00:00
Noah Zalev
8d4e9bcede
Re-enable autobackups by default (#6644) (#6653) 2025-01-03 15:54:47 +00:00
si458
eb0d24cf0b fix trafficstats with novnc
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-02 23:55:43 +00:00
si458
fe02d3158d add pwr,conn,agct,cict to getDeviceDetails #6650
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-02 23:49:02 +00:00
Simon Smith
61d3487f8a
add prometheus metrics (#6654)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2025-01-02 23:38:13 +00:00
Simon Smith
a23725eb40
fix trafficstats relaycount NaN 2025-01-02 20:13:51 +00:00
Ylian Saint-Hilaire
9b60271f31
Update index.md
Added Duo link
2025-01-01 17:22:03 -08:00
Ylian Saint-Hilaire
31f328086e
Update security.md
JSON fix
2025-01-01 17:18:59 -08:00
Ylian Saint-Hilaire
7aa4061cad
Update security.md
Added Duo installation steps.
2025-01-01 17:18:38 -08:00
Ylian Saint-Hilaire
54bb0177ee
Update index.md
Minor fixes
2025-01-01 17:03:30 -08:00
Jason N. White
ce70f4ac08
Update LICENSE, fix license year (#6646)
Signed-off-by: JasonnnW3000 <sufssl04@gmail.com>
2025-01-01 16:52:53 -08:00
Pablo Henrique Batista
f712cd9425
Fixes multi domain using default domain cert without trying to recreate for new domains (#6645) 2025-01-01 20:04:32 +00:00
Ylian Saint-Hilaire
c4592dcc4f Fixed Duo Boost UI. 2024-12-31 14:11:21 -08:00
Ylian Saint-Hilaire
2a274fe569 More Duo UI improvements. 2024-12-31 11:48:30 -08:00
Ylian Saint-Hilaire
5d0b5acdfb Duo UI improvements. 2024-12-31 11:40:08 -08:00
Ylian Saint-Hilaire
f80ba62cfc Fixed Duo 2FA security. 2024-12-31 10:37:09 -08:00
dev6699
5da849063b
fix: db.js expire server stats comment typo (#6633) 2024-12-29 12:39:50 -08:00
si458
68ac8cf86c fix duo and theme switcher
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-24 11:20:58 +00:00
Sammy Ndabo
8e70cd7187
[NEW] Add Theme Switcher with Bootstrap/Bootswatch Themes (#6622) 2024-12-24 10:15:45 +00:00
si458
5cf468159d fix duo and package.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-23 13:13:37 +00:00
Ylian Saint-Hilaire
c92b88a374 Duo changes, but not yet fully tested. 2024-12-22 19:10:35 -08:00
si458
1b01b90cd6 bootstrap3: dark mode fixes, buttons replaced, more translate fixes #6496
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-22 01:28:31 +00:00
si458
6a366fe174 bootstrap3: fix translations, fix button styling, new contextmenus, mouse disable cursor #6496
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-21 19:42:50 +00:00
Simon Smith
e2362a0547
add duo authentication support (#6609)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-21 13:52:54 +00:00
Simon Smith
59fcc0dbc6
fix bootstrap 3 record session formatting #6618 2024-12-21 11:24:12 +00:00
Simon Smith
988983b880
alpine 3.21 and node 22 in docker 2024-12-18 18:58:32 +00:00
Simon Smith
a1854fa074
fix createmesh with email as userid #6596 2024-12-18 18:31:20 +00:00
Ylian Saint-Hilaire
22fc95926a Fixed module update for modules like firebase-admin.js 2024-12-15 00:24:18 -08:00
Ylian Saint-Hilaire
c2eb1f2516 Updated to MeshCentral Firebase support, updated to using firebase-admin module. 2024-12-14 21:56:36 -08:00
si458
832d11739b fix defender not showing
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-09 13:17:35 +00:00
si458
ab7be919a0 fix meshcmd amtinfo UNCAUGHT EXCEPTION #4135
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-07 14:41:19 +00:00
si458
624d61db43 move windows_volumes and bitlocker to win-volumes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-06 13:56:15 +00:00
si458
b0d8e3fe48 package updates
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-06 13:34:24 +00:00
si458
a33047747a add OS Descripton to Details #6556
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-06 12:34:06 +00:00
PTR
39e81befe1
fix bitlocker/manage-bde cmdline (#6586) 2024-12-05 17:40:20 +00:00
Roman
8eeb96fb0d
Spelling mistake in German translation (#6585) 2024-12-05 13:45:23 +00:00
PTR
f9228ad0eb
connectinArgs.Database > connectinArgs.database typo (#6576) 2024-12-02 14:06:58 +00:00
si458
ce4217c346 fix messenger background notifications #2440
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-01 23:19:41 +00:00
si458
d9262f7c9d update translate, fix bootstrap version panel, deskbackground
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-12-01 22:26:03 +00:00
Ylian Saint-Hilaire
8e8ec4f88a Version 1.1.35 2024-12-01 10:28:35 -08:00
Ikko Eltociear Ashimine
18167499d9
chore: update pluginHandler.js (#6569)
ocurred -> occurred
2024-11-30 22:41:06 +00:00
si458
da5d03b0e7 fix annoying firefox paste option with clipboard #6571
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-30 16:14:35 +00:00
si458
c41eb72a2c fix win7+server2008r2 powershell/wmi
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-30 13:50:02 +00:00
si458
ef4d764ab4 Revert "swap powershell write to command instead" to fix win7/server2008r2
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-30 00:21:44 +00:00
Ylian Saint-Hilaire
c16ff89902 Version 1.1.34 2024-11-27 12:24:37 -08:00
Ylian Saint-Hilaire
041e8021d1 Added test Windows x86-32, x86-64, ARM-64 agents. 2024-11-27 12:08:36 -08:00
Kaiwalya Koparkar
6e31562ecb
feat: Added Elestio as one-click deploy option (#6453) 2024-11-26 10:53:02 -08:00
dependabot[bot]
874ef23c40
Bump cross-spawn from 7.0.3 to 7.0.6 (#6562)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-26 10:48:48 -08:00
Ylian Saint-Hilaire
7bd5b66ebc Disabled Firebase support on NodeJS 23 for now, added warning. 2024-11-26 10:37:27 -08:00
Simon Smith
975e49a190
use @seald-io/nedb for node23 support (#6561)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-26 10:01:12 -08:00
si458
462c383b77 update openid-client to 5.7.1
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-26 17:52:59 +00:00
si458
dbb5b4ba11 add webrelay to websocket and meshctrl #6484
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-26 16:46:14 +00:00
si458
545bf58e8d fix pwa orientation #6554
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-25 10:16:13 +00:00
si458
30b390bdbf increase usernames to 128 for oidc identifiers #6447
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-19 19:08:10 +00:00
si458
d0a51e90e9 fix mobile ui file upload #6543 #6460
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-18 16:04:56 +00:00
PTR
c773857b17
Add forgotten space in query GetNodeEventsWithLimit (#6541) 2024-11-17 23:12:57 +00:00
Ylian Saint-Hilaire
cae1f7ea14 Switched to BlueSky 2024-11-17 11:50:55 -08:00
si458
b398cb7fa9 bootstrap: more fixes and icon changes part 2
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-16 02:25:25 +00:00
PTR
5a1a97ca7e
Fix quoting of string literals from double to single (#6530)
See: https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted
2024-11-15 22:14:58 +00:00
PTR
dd21f14f4e
Mariadb/mysql: Fix ssl option on autobackup cmdline and deprecated warnings (#6537)
Fix: error: 2026: "TLS/SSL error: Server certificate validation failed. The certificate's CN name does not match the passed value. Error 0x800B010F(CERT_E_CN_NO_MATCH)"
by adding '--ssl-verify-server-cert=false'
and updating the modules.
2024-11-15 22:04:19 +00:00
ijustw0rkhere
3da60b43ac
fix adding meshes and user groups in a peering environment (#6534) 2024-11-15 21:54:28 +00:00
si458
727080ab68 bootstrap: more fixes and icon changes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-15 16:04:08 +00:00
si458
54170c44a0 bootstrap: fix files edit file feature
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-14 15:51:46 +00:00
ijustw0rkhere
8a5ad1563d
adjust removemeshuser to allow for shorter user ids (#6520) 2024-11-11 10:58:25 +00:00
si458
911d987a84 fix actions modal popup and icon changes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-10 22:27:57 +00:00
si458
9a8f4e8ebe bootstrap: fix edit node and duplicate lastbootuptime
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-10 20:23:54 +00:00
si458
badee98b71 fix badlogins naming #6516
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-10 17:50:45 +00:00
si458
d44faed29e fix filter input #6498 #6507
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-10 17:41:22 +00:00
Josiah Baldwin
01c585f7f1
Added the ability to fetch timestamp for codesigning through https (#6510) 2024-11-10 14:39:12 +00:00
PTR
777eb53476
Add sqlite config options (#6517) 2024-11-10 14:04:17 +00:00
PTR
b71c69e81d
Fix autobackup defaults and zip level for performance (#6518)
* Fix autobackup defaults and zip level for performance

* Add zipcompression configuration option
2024-11-10 13:49:11 +00:00
si458
7d59210d05 swap powershell write to command instead
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-06 15:37:26 +00:00
si458
fc387ca417 set backup vars on object for acess and fix nedb backup #6481
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-05 16:05:15 +00:00
si458
fc83211e90 open note urls in new tab #4091
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-05 15:58:51 +00:00
si458
9ebd23a518 restore login screen enter button use #6494
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-05 15:41:37 +00:00
PTR
3f8301e9d7
Put dbdumpfile back in root of zip instead of meshcentral-data subfolder and normalize some more (#6500) 2024-11-05 13:26:05 +00:00
si458
b39235643e more wmic replacements
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-04 15:09:38 +00:00
si458
0ec8b061c8 Revert "require package version from correct folder"
This reverts commit cfe9345b53.
2024-11-04 13:59:50 +00:00
PTR
e58d659fa9
Fix archiver error, add backup options and SQLite maintenance (#6487) 2024-11-03 18:44:15 +00:00
Ylian Saint-Hilaire
45169b2cfd Version 1.1.33 2024-11-03 09:40:52 -08:00
Ylian Saint-Hilaire
c09d2fad3e Can now switch to the bootstrap ui at runtime with ?sitestyle=3 in the url. 2024-11-03 09:34:26 -08:00
si458
7928f7fb30 fix ntfy missing user-agent #6488
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-11-01 11:23:45 +00:00
si458
438289b2ed few more bootstrap fixes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-31 13:31:33 +00:00
Marc Laporte
561fc67f33
Adding LinkedIn and a few fixes (#6486) 2024-10-31 12:44:28 +00:00
si458
aa7767f37c fix menu bar and swap icons in bootstrap
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-28 13:35:43 +00:00
si458
f23792881e more general web fixes and updates
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-28 11:26:30 +00:00
si458
c920b28acc more bootstrap fixes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-28 11:17:09 +00:00
Josiah Baldwin
36f1b4d5be
Added global ws error handler (#6475) 2024-10-25 11:02:53 +01:00
si458
141bec559f AddLocalDevice and AddAmtDevice to meshctrl.js #6473
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-25 10:34:16 +01:00
si458
0d885e6fa0 fix some modals not working in bootstrap
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-24 18:31:55 +01:00
si458
e10f5277e9 improve bootstrap icons and formatting
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-24 15:56:02 +01:00
si458
f33768fe32 1st bootstrap 5 cosmetic fixes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-21 18:15:05 +01:00
si458
1e565768d1 fix sitestyle for new bootstrap
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-19 19:28:23 +01:00
Sammy Ndabo
5193fef888
[BETA] Meshcentral Bootstrap 5 User Interface (#6450)
Co-authored-by: Bruno Kambere <brunokambere@gmail.com>
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Simon Smith <simonsmith5521@gmail.com>
2024-10-19 18:33:40 +01:00
trmdi
63930c4b33
Use built-in login validation (#6434)
* Use built-in login validation

* use button instead of submit to avoid duplicate submits

---------

Co-authored-by: Simon Smith <simonsmith5521@gmail.com>
2024-10-19 16:47:33 +01:00
si458
ac27034542 update packages
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-17 20:45:11 +01:00
si458
cfe9345b53 require package version from correct folder
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-17 20:18:05 +01:00
si458
1e2d736d6d pin openid-client to 5.7.0 for moment
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-17 17:36:17 +01:00
Meet50
0c825251eb
Update sysinfo.js meshcmd (#6448) 2024-10-15 11:36:01 +01:00
Daniel-Hillenbrand
ccf00b7d06
add annotation about using own IDP, CA and Docker (#6454) 2024-10-15 09:45:24 +01:00
si458
6d412a7bea show local/relay devices in online filter #6440
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-12 03:24:31 +01:00
si458
5a0d3054b8 fix reports missing multiplex desktop sessions #6445
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-12 02:53:50 +01:00
si458
6dbc6d2d07 update express to fix xss
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-12 00:12:51 +01:00
si458
ea8e1b1076 fix log.txt with json objects
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-10 18:34:52 +01:00
si458
d1368791e9 move orphanAgentUser to domain config-schema
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-08 14:01:02 +01:00
si458
590166f847 fix meshaction with foldr based domains #6436
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-08 08:51:16 +01:00
si458
19d0df7e7f always show active users for offline devices #6421
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-05 21:38:49 +01:00
Josiah Baldwin
1d87c42977
Fixed bug with agent reconnecting (#6431) 2024-10-05 17:21:48 +01:00
si458
ec7505987d fix login button disabled when autofilled #6428
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-05 16:59:55 +01:00
si458
37729269ba fix public folder sharing for domains without dns
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-01 12:10:48 +01:00
si458
952bcde25f refix publicfiles sharing for folder based domains #6406
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-10-01 11:47:41 +01:00
Ylian Saint-Hilaire
4848df4faf Version 1.1.32 2024-09-30 09:39:41 -07:00
si458
41d1f9d26f Revert "allow password resets when using allowaccountreset and reset together #6261"
This reverts commit 8e5aa35bf3.
2024-09-30 17:30:07 +01:00
si458
113adb5b85 add debug/log to schema file
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-30 14:07:34 +01:00
si458
8e5aa35bf3 allow password resets when using allowaccountreset and reset together #6261
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-30 12:20:01 +01:00
si458
1139a37338 update noVNC to 1.5.0
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-29 15:26:20 +01:00
si458
b20e51561a fix novnc missing desktopName from title #6412
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-29 13:26:11 +01:00
si458
5ff44bbae8 fix DeskTools only dragging one way #6257
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-28 14:29:17 +01:00
si458
2beeb6f644 fix agentTimeStampServer and agentTimeStampProxy not being set correctly #6409
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-27 19:11:41 +01:00
si458
3eede1bf43 update package-lock.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-27 07:05:04 +01:00
Simon Smith
9f8ea3a6b5
Update package.json 2024-09-27 06:54:26 +01:00
Ylian Saint-Hilaire
23679d119b Dependency fix 2024-09-26 21:20:43 -07:00
Ylian Saint-Hilaire
a3fd6008a0 Version 1.1.31 2024-09-26 21:19:11 -07:00
Ylian Saint-Hilaire
d0014b3f8b Removed cleanReqQuery() 2024-09-26 21:13:55 -07:00
Josiah Baldwin
04c96eb2ff
Fix/xss (#6403)
* Fixed filenames not being escaped when editing files

This allowed a possible XSS by naming a file in a particular way on your device.

* Fixed HTML generation in webserver not escaping most things from req.query

This would allow XSS through a very simple phishing attack

* Added HtmlEscape to Mobile default as well

* Added sanitization to SAML redirect and Twitter/Azure
2024-09-26 21:09:34 -07:00
Simon Smith
df64c750cc add runCommands to server peering #6404
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-09-26 22:56:13 +01:00
si458
b90b2ac0bf add RDP to device context menu #6401
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-26 19:51:06 +01:00
si458
39a1755b3d fix 404 on public files with /login url #6406
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-26 19:21:25 +01:00
si458
61fb6898c0 add ctrl+c ctrl+x and esc to mobile terminal ui
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-24 20:00:32 +01:00
si458
bc34f140c8 upgrade express-handlebars #6357
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-24 19:17:08 +01:00
si458
0bee2be3cf generate manifest.json from domain and add pwalogo
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-24 19:09:14 +01:00
si458
1d67172dd3 fix RDP canvas size #4701
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-24 13:29:57 +01:00
si458
b99a97eb48 add restart agent service to agent action
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-23 13:20:05 +01:00
si458
a1899a719f add freebsd install/uinstall/start/stop/restart commands #6040
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-19 22:33:24 +01:00
si458
5fcfa8f369 fix storage volumes on arch/busybox
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-16 11:00:33 +01:00
si458
7172d1f701 always show operating system section and LastBootUpTime fix
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-15 23:02:23 +01:00
si458
8bc760855e add raspberry pi arm/gpu memory
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-15 19:39:41 +01:00
si458
5fc3683ebb update archiver
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-15 18:34:42 +01:00
Ylian Saint-Hilaire
626416a202 Version 1.1.30 2024-09-15 10:07:28 -07:00
Ylian Saint-Hilaire
d84afb939a More dependency updates. 2024-09-15 10:02:51 -07:00
Ylian Saint-Hilaire
3cd875d6ee Dependency update. 2024-09-15 09:56:07 -07:00
Ylian Saint-Hilaire
f5e63b7cbd Updated dependencies. 2024-09-15 09:52:32 -07:00
wdlut
8b20f44dd5
Update meshuser.js (#6210)
Added a hook "uiCustomEvent" to start plug-ins from custom dialog boxes.
In the config.json:
````
     "CustomUI": {
        "deviceButtons": {
          "custom_dialog": {
            "name": "Show custom dialog",
            "action": "dialog:customDialog"
          }
        },
        "dialogs": {
          "customDialog": {
...
````

In the plug-in: ````
obj.uiCustomEvent = function(command, parent) {
    switch(command.element) {
            case 'customDialog':
                onCustomDialog(command, obj, parent.ws);
                break;
            default:
                console.log("Element "+command.element+ " not supported.");
        }
    };
````
2024-09-15 09:43:37 -07:00
Dmitry Kostenkov
59a3a22ea5
fix orphanagentuser toLowerCase() (#6317)
https://github.com/Ylianst/MeshCentral/issues/2095#issuecomment-2276360509
2024-09-15 09:42:15 -07:00
Ylian Saint-Hilaire
2f34b7e83b Improved debug documentation. 2024-09-15 09:40:36 -07:00
silversword411
4d5ec6cac1
Docs: Adding meshcentral server debug command (#6369) 2024-09-15 09:21:37 -07:00
Ylian Saint-Hilaire
7635109f6d
fix: package.json & package-lock.json to reduce vulnerabilities (#6378)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-BODYPARSER-7926860
- https://snyk.io/vuln/SNYK-JS-EXPRESS-7926867
- https://snyk.io/vuln/SNYK-JS-SEND-7926862
- https://snyk.io/vuln/SNYK-JS-SERVESTATIC-7926865

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-15 09:18:08 -07:00
si458
de60b7f952 update meshcentralrouter with fixes
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-12 19:15:45 +01:00
petervanv
405261a67e
Dutch language update 1.1.29 (#6379)
cleaned up some unused text
2024-09-12 19:09:21 +01:00
si458
4bcec73e07 forgot new lines in backupconfig
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 22:01:44 +01:00
si458
d81c00c0b0 add extra info to backupconfig
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 21:54:44 +01:00
si458
d2e4f12ea3 fix left mousebutton click with event.button #6351
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 21:06:16 +01:00
si458
f7c79166da fix dockerfile translate
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 20:14:08 +01:00
si458
2b5337329a remove minify-js use html-minifier instead #6357
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 20:01:10 +01:00
si458
21206b670c refix #6240 as switching devices keep session open by mistake
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 13:31:15 +01:00
si458
1d04a13a64 add oldStyle to consentMessages as workaround for win-userconsent crashing #6290
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-10 11:01:23 +01:00
si458
9e309584db update package-lock 1.1.29
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-09 11:47:43 +01:00
Ylian Saint-Hilaire
0d56504d96 Version 1.1.29 2024-09-08 15:07:05 -07:00
Ylian Saint-Hilaire
d17918936d express-ws update. 2024-09-08 14:59:35 -07:00
Ylian Saint-Hilaire
0a64b80654 Updated express-ws. 2024-09-08 14:48:19 -07:00
Ylian Saint-Hilaire
35e38a71c0 Re-translated & minified. 2024-09-08 14:27:10 -07:00
si458
6fe30b7730 add new line break feature to file edit #6365
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-08 20:11:06 +01:00
Simon Smith
516a14b4ca
Update readme.md 2024-09-07 23:31:03 +01:00
si458
390991123a fix pluginhandler http protocol #6362
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-06 11:00:41 +01:00
si458
d367b2ed87 few fixes and doc updates
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-05 19:19:52 +01:00
Simon Smith
5a410ccd5b properly fix peering #5714
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-09-05 18:31:37 +01:00
Simon Smith
ad1d82152a fix clonesafenode in dbNodeChange #5591
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-09-05 15:39:45 +01:00
Simon Smith
7c79dbcda1 fix peering traffic #5714
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-09-05 12:57:29 +01:00
si458
cf23a3df81 fix volumes not closing powershell
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-03 20:03:05 +01:00
si458
e8cbebaffe fix webrtc file upload maybe #6309
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-03 14:46:25 +01:00
si458
ac0d805378 fix webrtcconfig and allow stun servers #6309
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-03 13:42:06 +01:00
si458
7b48e3b5f5 update webrtc servers and examples
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-09-01 18:56:33 +01:00
si458
ea6682e06a fix passport failure url
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-30 16:31:54 +01:00
si458
4e37455471 fix agentdownload on agentonly port #3282
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-30 13:17:14 +01:00
si458
b4323223cc support markdown in notes when using showNotesPanel #4091 #6332
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-27 15:24:36 +01:00
si458
bf00de4425 full keyboard control in fullscreen using chrome in sharing #1881
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-27 15:12:05 +01:00
si458
f95dbdd404 allow full keyboard control in fullscreen using chrome #1881
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-27 14:58:29 +01:00
si458
e1e59953f4 fix remoteaddr null in consent disconnect #6290
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-27 10:46:25 +01:00
si458
d2d9f7a13e fix disconnections of new user logins with ldap #6240
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-25 17:34:44 +01:00
si458
21e196e35c dont do utf8 validation for mongodb #6340
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-24 00:12:14 +01:00
si458
0ccce62c9d dont minify charts or OL JS files #6338
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-23 13:47:34 +01:00
si458
3a78cb83c4 add ukrainian agent-translations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-23 13:14:28 +01:00
Simon Smith
8dd5545a2b add Ukrainian translation #6335
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-08-23 12:47:22 +01:00
Ylian Saint-Hilaire
2f61e3edad Spanish update. 2024-08-18 22:16:23 -07:00
si458
7caf2aa05d fix file edit box missing data #5813
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-17 16:00:06 +01:00
si458
cee181fb61 fix meshcmd os select #6327
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-16 16:14:27 +01:00
si458
fa39f8a105 fix meshctrl with key=xxx and loginkey #6328
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-16 16:02:21 +01:00
Simon Smith
c1e3354c91 fix mysql autoback zippassword
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-08-15 13:06:40 +01:00
si458
aae551dab9 autobackup improvements #6324
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-15 11:32:24 +01:00
si458
3a28e33efb use hardware identifier if no intelamt uuid
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-14 10:09:46 +01:00
wow
92385e3d73
AMT: Fix One Click Recovery support (#6301)
* add support for PXE reboot/power

* AMT: Fix support for OneClickRecovery
2024-08-13 21:22:26 +01:00
Simon Smith
adeaac1588 getLocalAmtNodes only show intelamt devices fix #6321
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-08-13 18:55:59 +01:00
Simon Smith
77f44fc308 fix local amt with sqlite/mysql/postgres #6321
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-08-13 17:46:37 +01:00
si458
3efa680361 add png and tiff to encoding in web ui #6315
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-13 10:20:31 +01:00
si458
899ff0c742 dont allow deleting yourself in my users tab in web ui
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-12 16:01:36 +01:00
Simon Smith
61f1c22c27 update mpkg with arm64 support
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-08-06 16:28:44 +01:00
si458
08877844c4 show seperate macos mpkgs in web ui #6308
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-06 15:33:49 +01:00
GraphicHealer
698b7fb056
Update apprelays.js to fix Header issue (#6306)
Add `.trim()` to the header label value (`i`) to fix a rare circumstance where there can be a space in the header name.
2024-08-05 22:18:15 +01:00
si458
fbd4533477 add intel amt identifier to ui to help find passwords in amtpasswords
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-05 13:34:00 +01:00
si458
6f2b57998f fix agentidletimeout being undefined
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-05 11:48:53 +01:00
Ylian Saint-Hilaire
a6acb35a31 Fixed typo. 2024-08-04 22:31:17 -07:00
Ylian Saint-Hilaire
fc29e60939 Improved configuration file encryption in the database, added testing. 2024-08-04 22:00:37 -07:00
Josiah Baldwin
5b76a31644
Changed database file encryption to pbkdf2 for key derivation and aes-256-gcm for encryption (#6296)
* Added pbkdf2 and aes-256-gcm options for database file encryption

* Added dbCipherAlgorithm option

* Changed pbkdf2 to default

Maintains backward compatibility, but will require a manual repush to update to the new version

* Removed dbkeyderivationiterations option, as this branch is to be more opinionated
2024-08-04 16:02:22 -07:00
si458
41e4213fc5 update translate.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-08-02 11:58:27 +01:00
wow
999ae7f67f
add support for PXE reboot/power (#6298) 2024-08-02 11:48:38 +01:00
Josiah Baldwin
44991975d3
Added ability to use environment variables as arguments (#6184) 2024-08-01 15:47:30 +01:00
Simon Smith
6da9222871
add s3 autobackup support (#6280)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-29 14:41:36 +01:00
si458
10b57dcf9e fix default linuxshell for mobile ui #6275
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-24 10:15:43 +01:00
Ylian Saint-Hilaire
1cfe0e2c31 Version 1.1.27 2024-07-23 12:59:40 -07:00
si458
7e504a28e6 fix autoRemoveInactiveDevices again #6268
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 17:28:36 +01:00
Ylian Saint-Hilaire
92869ec78b Version 1.1.26 2024-07-23 08:53:54 -07:00
si458
9264b9d281 fix autoRemoveInactiveDevices is 1 #6268
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 15:58:13 +01:00
si458
d8a91d3150 fix loading=lazy minify bug
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 14:37:40 +01:00
si458
5e71bcc634 fix minifed files
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 14:03:03 +01:00
si458
4d75d48eea add watchdog to config-schema
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 13:10:01 +01:00
si458
df6474802d fix language change event log
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-23 12:39:15 +01:00
si458
31c323583b fix message box Korean translate #1392
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-22 11:26:51 +01:00
si458
4b891c5be0 fix cancelhelp translate error #4888
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-21 20:00:53 +01:00
Ylian Saint-Hilaire
86713cacac Version 1.1.25 2024-07-20 10:45:06 -07:00
Ylian Saint-Hilaire
95d60fef13 Added Catalan language support and Italian fixes. 2024-07-20 10:34:18 -07:00
si458
b4e7e7384d show previous logins for all users and fix mobile ui account permissions
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-20 17:11:33 +01:00
si458
62cae4cf8a fix autobackup: true
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-18 10:48:40 +01:00
si458
aaad8b79cc fix autobackup not running if no backupintervalhours specified
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-18 10:03:32 +01:00
si458
b0d9b17e36 fix external auth with loginkey passthrough #4883
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-17 15:19:05 +01:00
si458
991c23c5f9 fix remote input lock button from desktop views #4542
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-17 13:43:35 +01:00
si458
87c5745594 fix invite code installation type display #4541
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-17 12:43:24 +01:00
si458
707982a71b fix Localization Settings not saving server side #2164
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-16 12:06:06 +01:00
si458
a8fc5e1187 disable open button in files for android
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-16 11:03:29 +01:00
si458
40ac6aa636 fix download button in files for android
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-16 09:30:34 +01:00
si458
1d9de2e144 add lock button to mobile ui #6251
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-15 10:16:37 +01:00
si458
ee0018e4d1 fix force2fa for files and groupactions #6247 #6246
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-10 15:33:39 +01:00
si458
721c909158 add proxmox to isVM
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-09 18:13:02 +01:00
si458
2630931eee dont allow go to folder button for android
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-09 17:41:17 +01:00
si458
13b8ca3686 fix Hungarian translate #6234
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-08 15:03:01 +01:00
mitch
a59da2fb9a
Remove link to archived user guide in new docs 2024-07-04 17:37:58 +01:00
si458
c3470f493b forgot d7encoding css
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-07-04 11:39:25 +01:00
Simon Smith
3d815824ba
Update readme.md 2024-07-04 10:24:43 +01:00
si458
9619a83ba7 update package-lock.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-28 15:18:06 +01:00
Simon Smith
f6c7761afb
update ws to 8.17.1 (#6214)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-28 15:09:34 +01:00
Joel Roth
9fd3e4c569
Check agent IP address instead of user IP address for agent file downloads. (#6155) 2024-06-28 14:50:57 +01:00
Josiah Baldwin
118b0c58dc
Added "trustedproxy" to the arguments list (#6211) 2024-06-28 14:25:49 +01:00
Simon Smith
57442e4988
fix filter refilling in chrome and safari (#6209)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-27 16:07:03 +01:00
Simon Smith
602eb3c64a
add encoding options to remote desktop (#6198)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-23 21:00:30 +01:00
si458
28c522c5bb add android version+api to dtails page
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-23 14:21:08 +01:00
si458
df91c90d33 fix ip fliters from files #3401
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-20 22:34:08 +01:00
si458
81557ab2d4 forgot user new events filter fix #6189
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-20 18:16:34 +01:00
si458
6b21bacad2 fix new events appearing when filtered #6189
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-20 18:07:50 +01:00
Simon Smith
46ebadf440
fix mac mpkg agent again (#6194)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-20 12:36:24 +01:00
si458
6c3e60e13c update translate.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-17 10:54:58 +01:00
si458
7955bc4954 include connect-flash with passport to allow displaying of errors
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-17 10:10:35 +01:00
Simon Smith
482e79f913
fix meshcentral-web-domain translate displaying (#6180)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-17 09:48:21 +01:00
Simon Smith
0a89d07937
add userSessionsSort for session sorting (#6177)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-14 09:56:02 +01:00
si458
c053c14dd0 fix star covering desc in list view #6174
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-12 14:23:37 +01:00
si458
5950b2c829 make sure to clear flash errors after display to avoid showing again #6154
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-11 20:46:45 +01:00
si458
42a07e9d74 fix passport failureRedirect for subdomain paths
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-11 20:38:09 +01:00
si458
d7341ab153 display flash errors for external auths like saml or oidc on the login screen #6154
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-11 20:06:19 +01:00
si458
74d6252699 increase uploadFile buffer to speed up file uploads #6169
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-11 17:02:20 +01:00
si458
b08f3827f5 fix obj.user._id undefined for rdp/ssh #6127
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-11 10:05:58 +01:00
si458
6976992735 fix oidc paths with aliasport #6148
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-04 10:26:29 +01:00
si458
b1c3e2a8e7 remove power-monitor server side to fix windows battery levels #6143
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-01 23:17:13 +01:00
si458
c67a76bcc2 fix oidc reauth #6132
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-01 20:31:25 +01:00
si458
62199d8057 fix handleStrategyLogin invalid token/user
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-06-01 17:13:22 +01:00
si458
52a2194116 require connect-flash for oidc #6132
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-28 20:00:33 +01:00
si458
2b3c329a54 remove comments and console.log meshctrl.js
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-28 18:26:21 +01:00
si458
17cf36edd9 add installflags to agentdownload in meshctrl.js #6133
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-28 18:24:39 +01:00
si458
a171cde2ff update package-lock.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-25 16:57:15 +01:00
Ylian Saint-Hilaire
5d5e861a4a Version 1.1.24 2024-05-25 08:38:28 -07:00
si458
26ac23c80d fix web-rdp/web-ssh save creds per user
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-24 17:27:22 +01:00
si458
5a7e3d9869 fix allowSavingDeviceCredentials description
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-24 16:25:25 +01:00
si458
abbb0fa9ee fix sharing keyboard input after Ctrl+Alt+Delete #6120
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-24 15:51:39 +01:00
si458
89b67ff999 fix sharing latency and timer
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-24 15:30:03 +01:00
si458
6c685d5557 fix realname undefined #6118
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-24 10:54:27 +01:00
Ylian Saint-Hilaire
49b561260a Updated ExpressJS to 4.19.2 2024-05-23 15:47:44 -07:00
Ylian Saint-Hilaire
aa8f45f4a9 Version 1.1.23 2024-05-23 15:32:29 -07:00
si458
7cf14a2b69 meshctrl deviceinfo error on unescaped nodeid
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-23 20:59:33 +01:00
si458
7e7361de9b add/fix iplocation
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 20:01:45 +01:00
si458
4cd7b408fa fix linux storage volumes 0kb
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 19:42:52 +01:00
si458
bc6451fee5 migrate groups.enabled in oidc #6104
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 19:04:43 +01:00
si458
f1ba76a423 fix device notifications not dismissing on other web sessions
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 17:47:39 +01:00
si458
385a4738cd forgot oidc group schema fix
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 16:56:56 +01:00
si458
5c13f178be fix oidc sync groups
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-21 16:05:00 +01:00
si458
323ef2d50a fix cookieEncoding hex for 2fa #6096
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-18 19:45:31 +01:00
Simon Smith
dd249938b3
fix keyboard shortcuts and add restore default keyboard shortcuts (#6103)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-18 18:30:26 +01:00
si458
30d958fbd9 fix auth-oidc-callback examples
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-18 12:26:27 +01:00
si458
1c8d664962 fix oidc groups.claim undefined
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-17 20:01:12 +01:00
Simon Smith
b22e56b6d2
add openidConnectStrategy to mkdocs.yml 2024-05-17 18:09:48 +01:00
Simon Smith
bc2f34b629
remove sendconsoletext from computer-identifiers.js 2024-05-17 17:13:59 +01:00
si458
e8da6a607c add nodeid to info in console #6097
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-17 14:41:51 +01:00
si458
77d268d064 listdevice filter should be string #6091
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-14 20:49:08 +01:00
si458
23ee76e26a fix mac volume detection for older os
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-14 10:11:49 +01:00
si458
be3e333b40 add macos storage volumes using df
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-14 00:13:46 +01:00
si458
e3f68226d2 add linux storage volumes using df
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-13 23:44:47 +01:00
si458
b71b4d04e6 bring power-monitor server side to fix mac battery levels
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-13 21:47:08 +01:00
Simon Smith
bf7957ebff
add zerossl acme (#6084)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-12 15:45:24 +01:00
Simon Smith
19eb1235f5
set min to node 16 (#5955)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-12 15:37:47 +01:00
Simon Smith
274bb525a5
allow msh get/set/delete from console (#6074)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-10 14:12:39 +01:00
si458
33c0e82286 fix mobile ui upload mesh agent core
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-05-07 14:16:23 +01:00
Simon Smith
56d6527bf5
add run commands to mobile ui (#6044)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-24 09:09:35 +01:00
adnan29979
3ce2fd92c0
Missing languages added to translator
All languages from source code default.handlebars added to translator.htm
2024-04-22 00:19:22 +01:00
adnan29979
eb27334b82
Doc update - Addition of 'How to Contribute' section (#6046) 2024-04-21 19:20:51 +01:00
si458
414d9b9561 undo #5452 and #6036 commits
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-19 11:48:49 +01:00
si458
1747ff7550 fix email in use meshctrl reply #6036
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-18 20:48:58 +01:00
si458
f39b6f8859 add smtp user/pass to schema and help docs
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-18 20:22:25 +01:00
Simon Smith
ca868afdd1
update translate readme.txt url #6041 2024-04-18 17:09:31 +01:00
Simon Smith
410c84c30b
add --mysql --mariadb arguments for stateless run (#6031)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-18 17:07:01 +01:00
Attocode1
18b731fd36
Updated install document - Corrected chmod command examples. (#6035) 2024-04-18 15:41:07 +01:00
si458
832e618602 forgot semicolon in a hurry meshctrl.js #6029
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-15 18:48:25 +01:00
si458
7b8cf85740 dont require, use readFileSync and phase for config.json in meshctrl #6029
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-15 18:46:40 +01:00
si458
1dca9e2235 fix missing connect-flash again #6028
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-15 14:51:18 +01:00
Simon Smith
30d570f28b
translation fixes for meshcentral-data-domain (#6027)
* dont translate min files
* translate meshcentral-web-domain folders with --translate
* also translate default views folder incase of changes

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-15 13:00:42 +01:00
si458
f854c80421 fix meshctrl configfile undefined
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-13 23:00:43 +01:00
Simon Smith
f5891f2946
fix custom public folders for dns domains (#6018)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-12 10:43:06 +01:00
Simon Smith
1da33f0ade
add nice404 to invite and fix invite with dns use #6017
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-11 18:51:54 +01:00
Simon Smith
e025e9558b
fix authStrategyFlags using wrong domain (#6015)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-11 17:43:08 +01:00
Simon Smith
ccf57bee1a
add missing rights to meshctrl and meshServerRightsArrayToNumber (#6004)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-09 13:31:00 +01:00
Simon Smith
4ba08a96f7
unEscape ssh/rdp creds from db (#6001)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-09 11:47:32 +01:00
Simon Smith
548edd13d6
add lastbootuptime to columns and device powered on event (#5999)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-07 19:12:01 +01:00
si458
31ebb21e0b fix ipv6 only letsencrypt #5988
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-06 23:47:02 +01:00
adnan29979
4a3c6db0ea
Fixing documentation of gmail smtp (#5998)
• removal of "accessToken" from documentation, since it is not in the source code.
• addition of a new step of 'changing publishing status from testing to production'
• removal of a duplicate picture in index.md and adding an appropriate pic instead.
2024-04-06 21:27:01 +01:00
si458
f9af1ffc90 fix powertimeline daylights savings on mobile ui #5997
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-06 21:11:32 +01:00
si458
95e7997e60 fix daylight savings in powertimeline #5997
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-06 21:09:43 +01:00
buckybytes
9081a6aeac
Google Workspace OAuth2 SMTP Documentation (#5939) 2024-04-05 14:35:18 +01:00
si458
afc6165827 nochecks description attempt 3 #5987
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-03 11:41:16 +01:00
si458
c9c0a6cb67 fix nocheck description again #5987
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-03 11:22:29 +01:00
si458
b46c322c41 fix nochecks description #5987
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-03 11:18:46 +01:00
si458
4ff5a5c912 add letsencrypt nochecks to schema #5987
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-03 11:14:42 +01:00
Simon Smith
65d1346e06
open files/folders on desktop with files and console with openfile (#5986)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-03 09:51:18 +01:00
Simon Smith
5d1c8ca68b
add open web link to mobile ui (#5985)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-02 23:36:05 +01:00
Simon Smith
9294488d4e
fix name display for oauth (#5980)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-01 15:48:01 +01:00
Simon Smith
d2a0946f22
add user import via csv file (#5978)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-04-01 00:21:47 +01:00
Simon Smith
3be8ec5add
add mac uninstall and fix windows uninstall (#5976)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 22:28:10 +01:00
Simon Smith
102489447d
check db exists first before creating in postgres (#5968)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 19:20:15 +01:00
si458
8e8cc4b327 rename 2x mac image
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 14:05:20 +01:00
si458
ce93c896ce fix null values in filters
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 13:55:41 +01:00
si458
7b67b992e2 fix postgres nedbtodb
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 13:52:16 +01:00
Simon Smith
95bbd7157f
add filter for events (#5975)
* add filter to node events
* add filter to my events
* add filter to user events
* improve sql querys

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-31 13:50:38 +01:00
Simon Smith
8e6cc14981
set flatpickr to 1 minute increments (#5974)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-29 18:11:29 +00:00
buckybytes
862e2ee80b
Various grammar, spelling, and clarity issues. (#5964)
* Update plugins.md

* Update faq.md

* Update debugging.md

* Update customization.md

* Update codesigning.md

* Update assistant.md
2024-03-27 11:26:38 +00:00
si458
81e98033fc fix mac memory part number
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-25 13:41:42 +00:00
Ylian Saint-Hilaire
fbae83dd9f Version 1.1.22 2024-03-24 11:43:32 -07:00
adnan29979
8498414ae9
Doc update - Agent Invitation Customization (#5937)
* Update assistant.md

* Email Invite and customization

* Update assistant.md

* Email Invitation pic upload

* point agent invitation customization to assistant.md
2024-03-24 11:32:08 -07:00
Ylian Saint-Hilaire
d33aa25e5b Updated Spanish translation. 2024-03-24 11:14:42 -07:00
Ylian Saint-Hilaire
8775b7dcf7 Set login autocomplete to off when set to false in config.json. 2024-03-24 11:03:33 -07:00
Simon Smith
e6ee2034d1
add biosSerial/biosMode to csv (#5949)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-22 14:31:47 +00:00
Simon Smith
f874e76f0c
wake mac screen with caffeinate (#5935)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-15 12:13:19 +00:00
adnan29979
105612c199
Doc update - Custom Web Icons (#5931) 2024-03-14 20:35:04 +00:00
silversword411
4027ee1b28
Fixing realname in notification for terminal sessions (#5928) 2024-03-14 14:51:33 +00:00
Simon Smith
4f11d7fdbc
allow meshctrl.js to reply with output from runcommands (#5932)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-14 14:33:02 +00:00
si458
b8238ef34d add selfupdate version to schema
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-10 16:26:52 +00:00
si458
4b6da03d2f fix crash caused my oidc merge
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-10 11:54:55 +00:00
mstrhakr
05fca6cb36
Fix formatting issues from merging (#5909)
* fix formatting issues in webserver.js

* fix formatting issues is meshcentral.js
2024-03-09 23:46:01 -08:00
Josiah Baldwin
150e2337f5
Add options for overriding TLS ciphers used (#5915)
* Add the ability to set TLS cipher suites

Added config option to set the TLS ciphers instead of relying on a hardcoded list of ciphers determined by meshcentral.

* Added option to use default node ciphers

This allows the ciphers used to be set to the recommended ciphers by nodejs, as well as allowing the user to override the ciphers using the "--tls-cipher-list" command line switch for node.

* Updated validArguments array to include "usenodedefaulttlsciphers" and "tlsciphers" as options
2024-03-09 23:45:10 -08:00
mstrhakr
dfc08b05a9
Login/logout bugfix for OIDC strategy. (#5920)
* add extra logging

* fix how strategy is saved
2024-03-09 23:44:18 -08:00
si458
fbe1445691 fix windows arm terminal
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-08 18:03:31 +00:00
si458
05ee40c591 add 'EFI Development Kit II / OVMF' as VM
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-08 17:04:08 +00:00
si458
872794e15f update showpaths with domain custom folders #5496
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-08 15:22:12 +00:00
Aaron Meese
334fee706e
Changed insure to ensure (#5913) 2024-03-07 16:08:11 +00:00
si458
e9c28d03b5 add windows arm 64bit to agentinvite page
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-07 15:21:10 +00:00
si458
2d75bbde33 add osx mpkg customized filename
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-07 10:19:31 +00:00
mstrhakr
ab0d9c188d
Update Passport version in meshcentral.js - Fix bad merge (#5908) 2024-03-06 19:00:24 +00:00
petervanv
d71ca89b58
Dutch language update 1.1.21 (#5903)
Update 3
2024-03-05 22:34:38 +00:00
wdlut
0e896fe9fe
Bugfix for plugin filemode #5865 (#5897)
Some files were created with file rights of 0o000. It was not even possible to read them.
2024-03-05 10:33:13 +00:00
si458
0a59f9dbae update agentinvite with osx universal mpkg
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-05 10:21:16 +00:00
si458
c1bec67839 allow multiple osx mpkg installs now and update uninstall.command
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-04 16:34:41 +00:00
si458
234acd3347 add displayname to macos pkg
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-04 12:50:22 +00:00
si458
473b9d0265 add osx custom filename to zip
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-04 11:17:43 +00:00
si458
6f47f2bc89 add note about apple quarantine for binarys
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-04 11:11:42 +00:00
si458
548c1b9bb4 fix mac memory and invalid smc values
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-04 10:57:09 +00:00
tschettervictor
3ffc92e917
Update common.js (#5895)
typo

Co-authored-by: Ylian Saint-Hilaire <ysainthilaire@hotmail.com>
2024-03-03 17:50:26 -08:00
Ylian Saint-Hilaire
bab35e7bca Removed Reddit auth strategy since it never worked well. 2024-03-03 16:34:01 -08:00
mstrhakr
4be5b7273e
Migrate to openid client (#5856)
* Create forksync.yml

* update oidc to use openid-client

* update oidc module requirements

* working oidc+

includes all oauth2 clients automatically migrated. azure will need some kind of fix for the uid

* update openid-client install checks

* created overarching schema for OIDC

* bug fixs for azure login

* update schema

prepare schema for unified oidc module

* update 'oidc' to strategy variable

* working azure+ groups

groups from azure are in,
you can use memberOf or transitiveMemberOf in config (Graphs API)

* clean up old config import + working google oidc

previous config map was recursive nonsense, changed to multiple IFs

* added convertStrArray

* de-expanded scope

put all other auth strategies back to normal and fixed oidc strategy

* swap back to using authlog debugger

* Update meshcentral-config-schema.json

* working google oidc + groups

* working azure+groups (again)

* init oidc docs

very incomplete but basic config is present

* add oidc

* more work on docs

* add scope and claim options

plus fixed a few bugs and faults in my logic
used logs correctly

* further cleanup debug

* more debug cleanup

* continue documentation push

fixed minor debug bugs also

* more work on docs

missing links, need to get azure preset docs, probably more.

* done with docs

its good enough for now

* minor fix + presets get correct icon

* fix google oidc not visible at login

* fix bug with emailVerified property

* fix logout bug + debug cleanup

* fix strategy logout bug +cleanup

* fixed preset login icon

* fix alert + fix schema

* terminate lines

* Dutch language update 1.0.85

line up polish translation

* Fixed guest web relay session revocation (#4667)

* Updated French translation.

* Add hook to allow adding custom api endpoints to Express routing

* Updated German translation.

* Update meshcentral-config-schema.json (change formatting)

This way it is easier to edit and maintain

* Fixed schema.

* fix meshcentral-config-schema.json

* add language selector to login (#5648)

* add language selector to login

* add showLanguageSelect to pick top or bottom boxe

* remove additionalProperties: false in schema to allow comments #5697

Signed-off-by: si458 <simonsmith5521@gmail.com>

* fix notes in docs

* Fix web relay session handling and redirection due to bad merge

* Added option to check HTTP origin.

* add links and fix typo

* move groups after strategy

* Update version split in docs

* Fix preset issuer URL in OIDC strategy

* Update clientid and clientsecret to client_id and client_secret

* Update meshcentral-config-schema.json and fix bad rebase

* Update meshcentral-config-schema.json

* fix bad rebase

* fix bad rebase

* Add 'connect-flash' to passport dependencies

* Remove unnecessary passport dependencies - fix bad rebase

* Fix auth strategy bug and remove console.log statement

* Set groupType to the preset name if it exists, otherwise use the strategy name

* remove finally block from

* Refactor authentication logging in handleStrategyLogin to include strategy name

---------

Signed-off-by: si458 <simonsmith5521@gmail.com>
Co-authored-by: petervanv <58996467+petervanv@users.noreply.github.com>
Co-authored-by: Ylian Saint-Hilaire <ysainthilaire@hotmail.com>
Co-authored-by: Martin Mädler <martin.maedler@gmail.com>
Co-authored-by: Fausto Gutierrez <28719096+faustogut@users.noreply.github.com>
Co-authored-by: Simon Smith <simonsmith5521@gmail.com>
2024-03-03 16:03:27 -08:00
Ylian Saint-Hilaire
436a3cb9be Only set fileMode if not win32, #5865 2024-03-03 12:31:32 -08:00
wdlut
e6a71d77a1
Preservation of the executable rights of the files contained in a plugin (#5865)
On file systems that support this, the executable rights of a plug-in's files are retained.
2024-03-03 12:27:28 -08:00
Ylian Saint-Hilaire
9e9cd821bf Use userid instead of username when username is null in authlog, #5870 2024-03-03 12:21:21 -08:00
Ylian Saint-Hilaire
27f7648953 Updated archiver to 7.0.0 2024-03-03 11:30:52 -08:00
Ylian Saint-Hilaire
c937764980 Merge branch 'master' of https://github.com/Ylianst/MeshCentral 2024-03-03 11:13:32 -08:00
Ylian Saint-Hilaire
e8c4f322cb Fix for saving SSH/RDP credentials when a dot is in the username, #5833 2024-03-03 11:13:16 -08:00
si458
fb62df326c pad interfaces panel for firefox scrolling #4275
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-03 00:58:15 +00:00
Simon Smith
2318a0bb32
add relay and port to devicesharing in meshctrl.js
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-02 21:56:14 +00:00
si458
636255c8af fix Unzip translations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-02 15:33:18 +00:00
Simon Smith
9241c43435
add unzip to files
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-02 15:16:06 +00:00
si458
0e055ef741 remove memory Manufacturer console.log
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-01 23:58:50 +00:00
Simon Smith
ee59e582b6
add no consent to guest sharing
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-01 22:29:59 +00:00
si458
aa87fd61bb maybe fix weird undefined user login accepted #5870
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-01 15:45:39 +00:00
Simon Smith
b4361d1c4e
fix view only setting incorrect protocol when sharing (#5879)
* fix view only setting incorrect protocol when sharing

---------

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-03-01 13:59:49 +00:00
si458
3e23741cc8 fix Chat & Notify buttons always being shown #5858
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-29 23:07:36 +00:00
petervanv
016e43c191
Dutch language update 1.1.21 V2 (#5871)
Cleaned up version !!
2024-02-29 09:55:01 +00:00
si458
5ed0e4f59e fix mac translate locations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-28 15:21:14 +00:00
Simon Smith
1b60e4dbfb
add mac memory/storage (#5869)
* add mac memory

* add macos storage

---------

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-28 15:19:14 +00:00
petervanv
269ec02dc0
Dutch language update 1.1.21 (#5862) 2024-02-26 23:35:08 +00:00
si458
fce123e971 fix few more translations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-24 00:31:57 +00:00
si458
bca1d277a4 add osx single binarys and rename tabs
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-24 00:24:04 +00:00
Simon Smith
017f777de2
update android apk to 1.0.21 2024-02-23 12:22:43 +00:00
si458
4cbb95a85f update package.lock
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-22 20:35:27 +00:00
si458
7912df307a update db sysinfo when sysinfo from console
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-22 17:57:12 +00:00
Simon Smith
82c70659b8
show cd-rom as volume
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-22 17:55:48 +00:00
Simon Smith
0b2368df9d
add Capacity Remaining
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-22 17:09:28 +00:00
si458
26bb350122 fix translations and add bitlocker in mobile ui
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-22 09:50:31 +00:00
si458
44926e16d5 update translations
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-21 15:51:02 +00:00
si458
0f23fa0ade fix linux txt translating
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-21 15:43:20 +00:00
Simon Smith
a80c0ef48c
fix translation values in details tab (#5841)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-21 15:00:20 +00:00
Marcin Wilk
79e137dbe7
Polish translation - Fix typo (#5840) 2024-02-20 23:33:52 +00:00
si458
af3e7f6186 fix rdp port translation
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-20 22:38:46 +00:00
Simon Smith
936aaa0b2b
disconnect sessions first before logout on idletimeout (#5838)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-20 21:59:30 +00:00
si458
272015483b fix formatting oops #bb93c11
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-20 20:24:20 +00:00
si458
bb93c113bd move rdpport to right side to fix translations #5834
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-20 20:20:46 +00:00
si458
9ef1cc64a4 fix translate.json
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-20 09:13:07 +00:00
Simon Smith
a14390e049
update Android apk to 1.0.20
Android 6 - 14 supported!
2024-02-19 23:46:55 +00:00
Simon Smith
a77d40505a
French bitlocker fix (#5832)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-19 23:16:31 +00:00
Ylian Saint-Hilaire
76485713df
Update SECURITY.md
Fixed PGP key format.
2024-02-19 15:12:07 -08:00
Ylian Saint-Hilaire
9ef48eac1f
Create SECURITY.md
Added a security.md file with email and PGP key for security reporting.
2024-02-19 15:10:33 -08:00
Marcin Wilk
7bc28b5dad
Update Polish translation for Meshcentral v 1..1.21 (#5822)
Also fix some mistake made with nl translation.
2024-02-19 00:12:11 -08:00
Ylian Saint-Hilaire
3e04e4d8c6 Version 1.1.21 2024-02-17 18:08:48 -08:00
Ylian Saint-Hilaire
f2bc7d5349 More BitLocker improvements. 2024-02-17 17:51:15 -08:00
Ylian Saint-Hilaire
e5e86fee19 BitLocker fixes, added drive volumes to details tab. 2024-02-17 14:01:49 -08:00
Ylian Saint-Hilaire
4637e6b3b3 Intel AMT fixes. 2024-02-17 12:33:56 -08:00
bennyc-huji
fb15d94976
update full version, and marking of the amt to check if activated (#5803) 2024-02-17 11:48:50 -08:00
si458
a7018e74bc split ips correctly with spaces for domains #5809
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-17 19:42:47 +00:00
petervanv
a3b3ecd8b1
Dutch language update 1.1.2 (#5804) 2024-02-17 11:23:14 -08:00
Ylian Saint-Hilaire
f2e43cc6da Added option to check HTTP origin. 2024-02-17 11:22:38 -08:00
si458
5c1249ccca split ips correctly with spaces #5809
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-17 15:10:47 +00:00
si458
0232056219 add extra defaults and descriptions to schema
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-14 13:44:23 +00:00
si458
6f78f9e276 fix one ping/pong function
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-12 11:46:16 +00:00
si458
4098805798 update meshcentralrouter with 2fa fix
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-12 10:23:10 +00:00
Simon Smith
980eca3765 update package lock 2024-02-11 15:07:02 +00:00
silversword411
512695df6c
Docs update for docker and meshagent msh file (#5791) 2024-02-11 14:05:19 +00:00
Simon Smith
15ff7d12a1
fix agentping (#5786)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-11 01:03:07 +00:00
silversword411
6637f08fe7
Docs - Reference docker official (#5788) 2024-02-09 18:32:06 +00:00
Simon Smith
990da39fde
add installdate to installedapps
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-02 13:00:56 +00:00
Simon Smith
ddcff9f0d2
use accelerator regardless of cpu core count (#5759)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-02 10:43:40 +00:00
Simon Smith
bfec20ac81
fix account pic transparency (#5761)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-02 10:35:10 +00:00
Simon Smith
550ee34f00
fix latency bouncing monitors (#5756)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-02 10:22:25 +00:00
si458
e6f27fca36 fix 2nd line also showing drive name #1892
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-02-01 14:40:55 +00:00
Simon Smith
b887457ec4
add bitlocker key to gui for admins (#5747)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-31 15:40:58 +00:00
Simon Smith
4648dbeb26
create folder first before copy/move #5704 2024-01-31 15:02:50 +00:00
Simon Smith
cdab553800
show latency in remote/terminal every 10 secs (#5750)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-31 14:06:31 +00:00
Simon Smith
cd2ede8369
add drive names and icons to files (#5749)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-31 14:06:02 +00:00
si458
97ed6c8285 fix coreinfo and sendPeriodicServerUpdate
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-29 15:13:24 +00:00
si458
df94b50d5d dont sendconsoletext muppet!
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-29 13:59:37 +00:00
si458
9542e77e1b update volumes and bitlocker periodically
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-29 13:16:39 +00:00
si458
3f8bbe68b0 fix blank Windows Defender
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-28 18:52:16 +00:00
si458
37911d091c fix bitlocker with multiple drives and german phrase checker
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-28 17:30:57 +00:00
Ylian Saint-Hilaire
1855dd1d56 Fixes authenticode signing if a section name starts with an underscore. 2024-01-27 10:15:33 -08:00
Ylian Saint-Hilaire
9f57c27dd1 Version 1.1.20 2024-01-27 09:54:36 -08:00
si458
8547de340c fix drive status undefined
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-27 12:42:27 +00:00
Simon Smith
cd48909dcb
add application location to info (#5740)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-26 14:59:34 +00:00
Simon Smith
ceba76cfde
fix numlock sending extended keys incorrectly (#5735)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-25 23:05:47 -08:00
Simon Smith
c331eca679
add android apk to mobile ui
* add android apk to mobile ui

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-25 18:00:03 +00:00
si458
03cab630c7 try lscpu if blank cpu_name
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-25 17:21:40 +00:00
Simon Smith
aaff3abc1a
add cloudflare issue to faq docs 2024-01-25 16:47:28 +00:00
silversword411
b02541cd02
tweaking docs loginTokeKey (#5733) 2024-01-25 16:18:14 +00:00
Simon Smith
18af676c22
add groupmessage/grouptoast to meshctrl (#5731)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-25 09:44:27 +00:00
Simon Smith
57f77057b4 fix files connect button order
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-01-24 15:28:02 +00:00
Simon Smith
b9c706cec2 fix amt powerstate again #5722
Signed-off-by: Simon Smith <simonsmith5521@gmail.com>
2024-01-24 15:27:01 +00:00
Simon Smith
b1451a1c8a
add extra services information
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-24 11:51:28 +00:00
Simon Smith
b860981925
add linux binary to agentinvite (#5717)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-22 20:57:04 -08:00
Simon Schön
327f29159e
Fixed 'deprecated' warnings in gh-workflows (#5720) 2024-01-21 20:49:31 +00:00
Simon Smith
a2aed9e772
show rdp port on button and set rdpport in console (#5708)
* add rdpport to console

Signed-off-by: si458 <simonsmith5521@gmail.com>

* add port number to rdp connect button

Signed-off-by: si458 <simonsmith5521@gmail.com>

---------

Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-18 18:56:54 -08:00
Simon Smith
aaff8232b0
fix notes in docs 2024-01-18 22:33:57 +00:00
dinger1986
ab835591db
add nginx selinux to docs (#5710)
Co-authored-by: Simon Smith <simonsmith5521@gmail.com>
2024-01-18 22:16:15 +00:00
Simon Smith
6cb05ce009
add dns servers to details tab (#5709)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-18 21:42:17 +00:00
si458
21bd171167 make add users to device group box expandable #1718
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-18 21:40:33 +00:00
si458
c546333cf7 fix MyFiles cut/copy command #5704
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-17 15:53:34 +00:00
Ylian Saint-Hilaire
1e9607ba8e Changed publicpushnotifications setting to use alt.meshcentral.com. 2024-01-16 18:57:57 -08:00
Ylian Saint-Hilaire
f93bb3a82f Version 1.1.19 2024-01-16 18:38:51 -08:00
si458
0d7564d845 fix relaying with subdomain #5394
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-16 15:57:50 +00:00
Simon Smith
6e2321b5c6
add multiple file download by check boxes (#5699)
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-16 09:35:53 +00:00
si458
757cef3f29 missed 1 onmouseup #5700
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-16 09:31:47 +00:00
si458
d02b63a4a2 remove additionalProperties: false in schema to allow comments #5697
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-15 19:45:46 +00:00
si458
1295af4677 fix config schema 2024
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-15 19:15:19 +00:00
si458
329ba43e81 add loginTokenKey to docs #4153
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-15 17:08:32 +00:00
si458
6cacec010b add subfolder to 2fa domain name #5238
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-14 21:23:49 +00:00
si458
9986567cf0 add set remote clipboard to web-rdp #4133
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-14 17:42:43 +00:00
si458
b01078bf55 add get remote clipboard to web-rdp #4133
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-14 16:56:13 +00:00
si458
0b0f2999db fix meshcentral assistant downloads
Signed-off-by: si458 <simonsmith5521@gmail.com>
2024-01-14 16:45:04 +00:00
Ylian Saint-Hilaire
db06c0c925 Version 1.1.18 2024-01-13 12:31:21 -08:00
Ylian Saint-Hilaire
ee7ceb7898 Fixed dependencies. 2024-01-13 12:30:35 -08:00
214 changed files with 91445 additions and 14812 deletions

View file

@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.

View file

@ -14,8 +14,8 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/setup-python@v2 - uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: 3.x
- run: pip install --upgrade pip - run: pip install --upgrade pip

View file

@ -5,48 +5,56 @@ on:
- master - master
release: release:
types: [published] types: [published]
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
check-token: check-token:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
token: ${{ steps.token.outputs.defined }} token_defined: ${{ steps.token_check.outputs.token_defined }}
steps: steps:
- id: token - name: Check token
id: token_check
env: env:
MY_TOKEN: ${{ secrets.MY_TOKEN }} MY_TOKEN: ${{ secrets.MY_TOKEN }}
if: "${{ env.MY_TOKEN != '' }}" if: "${{ env.MY_TOKEN != '' }}"
run: echo "::set-output name=defined::true" run: echo "token_defined=true" >> "$GITHUB_OUTPUT"
build: build:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [check-token] needs: [check-token]
if: needs.check-token.outputs.token == 'true' if: needs.check-token.outputs.token_defined == 'true'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.MY_TOKEN }} password: ${{ secrets.MY_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v3 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: docker/Dockerfile file: docker/Dockerfile

View file

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Release - name: Release

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -594,12 +594,14 @@
<Content Include="readme.md" /> <Content Include="readme.md" />
<Content Include="sample-config-advanced.json" /> <Content Include="sample-config-advanced.json" />
<Content Include="sample-config.json" /> <Content Include="sample-config.json" />
<Content Include="SECURITY.md" />
<Content Include="SourceFileList.txt" /> <Content Include="SourceFileList.txt" />
<Content Include="translate\readme.txt" /> <Content Include="translate\readme.txt" />
<Content Include="translate\translate.json" /> <Content Include="translate\translate.json" />
<Content Include="views\agentinvite.handlebars" /> <Content Include="views\agentinvite.handlebars" />
<Content Include="views\default-mobile.handlebars" /> <Content Include="views\default-mobile.handlebars" />
<Content Include="views\default.handlebars" /> <Content Include="views\default.handlebars" />
<Content Include="views\default3.handlebars" />
<Content Include="views\download.handlebars" /> <Content Include="views\download.handlebars" />
<Content Include="views\download2.handlebars" /> <Content Include="views\download2.handlebars" />
<Content Include="views\error404-mobile.handlebars" /> <Content Include="views\error404-mobile.handlebars" />

49
SECURITY.md Normal file
View file

@ -0,0 +1,49 @@
# Security Policy
## Supported Versions
Any version of MeshCentral 1.x.x is supported.
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| < 1.0 | :x: |
## Reporting a Vulnerability
Please report any concerns or security issue to Ylian Saint-Hilaire (ylianst@gmail.com). If needed, use my PGP key below.
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.56
mQMuBF2gC4sRCAClFNvMCCVW3ego3UHBQ6LhSenJfaZYhvn8gaGuemSQxqTI6bla
BTAv3aMtQnvqlSuadMMegb+FO6hnaQMlGvpVA1qpkSzgrPS5HrBD3H33J2Nj3i93
ZpDPpxdI0ehCj6IJPnl0GxGbpKIN8YpJUFl44wv1lMRFI1lgyb+dCoO60irYdNQB
PV85BI+DwPfOBFHunwR78nqMvpvsk9HaeHjEP7oXr952/7EazUowZsMlEfkYnw5S
+tLfpCoY3QWkektpJP40nMJSKQdV2NEuED99doA0X+7P1vsvFFFyMH69dnU2uSay
XCHpkAbntBy0BGmtF1RnTcOMv2V/LPXnlMdvAQCbmLQzNra3r163tcdRY0jSs+pZ
1L3w5tHNj2dzhfpa7wf/SIuds6QTr2LCN6miLoSVCRMMpT7d771b16GwQqWEXzN2
+h7dYqrssHPOa8FSUrPerz0+0eFcbMSm5/L/4KXWXoQthURv8aMP9E0iVoUYaaKB
7U+5vFEZbpoOZyZmTAjXQMSNZCft0azA82Q+G85euyicWtMv48yNVzUhkdh+M2ud
ohkXX2Aor1TqpBJoIeWke7j9D+Bo+lu61zPRx5ed9teUeLJCwqNEjlE+6gre5kxF
PoreAtn59QYcBIpzQEWVMbNFlDAR4jMyqIoKCGfBPiRw2V+kunbzqiGQEglIFfOt
6sTN/+CJh0ei976VDmE0Z1kMN+CNLgIjIw8fl02V9QgAnHcpqtVUxR4dbGOhVDq5
lWv+K75QQlWyXC2k+KboXcaCvH0WZEBACYzO0CfrZ5hP9BSkbj5usSUVGGHwEFAJ
t+/04KVY71fW281Ej5kGNaIKxeKsx6+hMo+UXb5ZM+6fANNNxs1cK95sTH6PjkyB
tsKxLoa3CV2v9mSE5JiKKt74R9nXVo7PXf6DizwAU2l30Lb6y6y0OdXdCCPAG8Ij
FrMgPu5MtjgsO5DnkZfUqDPWHhOgEPyOh3Ho+pvDhNYh5cm2eLQ8g5orzs2FHwbZ
DpAHwCdqrlcpBlKJ4W/MZdf1fg2PjqaTWm7ZFiGr91P0F6kltTLWbVKTjLdS0T+D
L7QnWWxpYW4gU2FpbnQtSGlsYWlyZSA8eWxpYW5zdEBnbWFpbC5jb20+iF4EExEI
AAYFAl2gC4sACgkQg7j/r4DH+kD/3gD+MRedlM53VzOtNOpS6mqDAxj1aWP90HN0
AqO6zuCTyGgBAJlunLFKH8IUetmQOhiohB8HVhdm/q4lKRDV7sHdplDyuMwEXaAL
ixACAJSU/sCV87he4oZUKzg2/IGl3QoDSbTCOd04dE1IjPjjHbi8t9M7Qau55aM8
ypFEsc7zMslL8Fc78EejrKmM3zsB/RU9XWFyrbQwRbaK6OHeEHC2E3AFaG0p09c6
d0kZloHuWyEsm5a/3PpbIM1eP9IESJXWCc+bQQt6DxLKHLmkKMwB/icWMg8uMJlx
aady8TEq7LH5oFVKsglnwuN1nIkecrf77TVkEqTjIxS6TiOup6zOnioFNKLYBAH0
WUnJEYFvx4OIXgQYEQgABgUCXaALiwAKCRCDuP+vgMf6QGFTAQCUj2gGwsFlN0eR
Wowv4eLcc3FwQ+lBElUctKg8vNFb0gD/ZWVWsWwKerNgNnf7RGD9mt8G2CKvdgGG
oZ2hPP2gU9w=
=roW4
-----END PGP PUBLIC KEY BLOCK-----
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -682,5 +682,67 @@
], ],
"statusDescription": "Jelenlegi agent állapota", "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." "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": "Клікнути кнопки нижче, щоб інсталювати або видалити це програмне забезпечення для віддаленого керування. Після інсталювання ця програма працює у фоновому режимі, що дозволяє віддаленому адміністратору керувати цим комп'ютером."
} }
} }

Binary file not shown.

View file

@ -588,7 +588,7 @@ function run(argv) {
} }
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } }); amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
amtMei.getProvisioningMode(function (result) { if (result) { mestate.ProvisioningMode = 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.getControlMode(function (result) { if (result) { mestate.controlmode = result; } });
amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } }); amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } });
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } });

File diff suppressed because it is too large Load diff

View file

@ -225,19 +225,14 @@ function macos_memUtilization()
function windows_thermals() function windows_thermals()
{ {
var ret = []; var ret = [];
child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', '/namespace:\\\\root\\wmi', 'PATH', 'MSAcpi_ThermalZoneTemperature', 'get', 'CurrentTemperature']); try {
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); if (ret[0]) {
child.waitExit(); for (var i = 0; i < ret.length; ++i) {
ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
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)); }
} }
} } catch (ex) { }
return (ret); return (ret);
} }
@ -285,16 +280,10 @@ function macos_thermals()
return (ret); return (ret);
} }
switch(process.platform) const platformConfig = {
{ linux: { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals },
case 'linux': win32: { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals },
module.exports = { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals }; darwin: { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_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;
}
module.exports = platformConfig[process.platform];

View file

@ -70,31 +70,38 @@ function linux_identifiers()
var values = {}; var values = {};
if (!require('fs').existsSync('/sys/class/dmi/id')) { if (!require('fs').existsSync('/sys/class/dmi/id')) {
if(require('fs').existsSync('/sys/firmware/devicetree/base/model')){ 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').readFileSync('/sys/firmware/devicetree/base/model').toString().trim().startsWith('Raspberry')) {
identifiers['board_vendor'] = 'Raspberry Pi'; identifiers['board_vendor'] = 'Raspberry Pi';
identifiers['board_name'] = require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim(); 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(); 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'); throw('Unknown board');
} }
}else { } else {
throw ('this platform does not have DMI statistics'); throw ('this platform does not have DMI statistics');
} }
} else { } else {
var entries = require('fs').readdirSync('/sys/class/dmi/id'); var entries = require('fs').readdirSync('/sys/class/dmi/id');
for(var i in entries) for (var i in entries) {
{ if (require('fs').statSync('/sys/class/dmi/id/' + entries[i]).isFile()) {
if (require('fs').statSync('/sys/class/dmi/id/' + entries[i]).isFile()) try {
{
try
{
ret[entries[i]] = require('fs').readFileSync('/sys/class/dmi/id/' + entries[i]).toString().trim(); ret[entries[i]] = require('fs').readFileSync('/sys/class/dmi/id/' + entries[i]).toString().trim();
} } catch(z) { }
catch(z) if (ret[entries[i]] == 'None') { delete ret[entries[i]]; }
{
}
if (ret[entries[i]] == 'None') { delete ret[entries[i]];}
} }
} }
entries = null; entries = null;
@ -117,9 +124,16 @@ function linux_identifiers()
var child = require('child_process').execFile('/bin/sh', ['sh']); var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', dataHandler); child.stdout.str = ''; child.stdout.on('data', dataHandler);
child.stdin.write('cat /proc/cpuinfo | grep "model name" | ' + "tr '\\n' ':' | awk -F: '{ print $2 }'\nexit\n"); child.stdin.write('cat /proc/cpuinfo | grep -i "model name" | ' + "tr '\\n' ':' | awk -F: '{ print $2 }'\nexit\n");
child.waitExit(); child.waitExit();
identifiers['cpu_name'] = child.stdout.str.trim(); identifiers['cpu_name'] = child.stdout.str.trim();
if (identifiers['cpu_name'] == "") { // CPU BLANK, check lscpu instead
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', dataHandler);
child.stdin.write('lscpu | grep -i "model name" | ' + "tr '\\n' ':' | awk -F: '{ print $2 }'\nexit\n");
child.waitExit();
identifiers['cpu_name'] = child.stdout.str.trim();
}
child = null; child = null;
@ -137,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.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(); child.waitExit();
try { identifiers['storage_devices'] = JSON.parse(child.stdout.str.trim()); } catch (xx) { } 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.identifiers = identifiers;
values.linux = ret; values.linux = ret;
trimIdentifiers(values.identifiers); trimIdentifiers(values.identifiers);
child = null;
var dmidecode = require('lib-finder').findBinary('dmidecode'); var dmidecode = require('lib-finder').findBinary('dmidecode');
if (dmidecode != null) if (dmidecode != null)
@ -336,6 +358,36 @@ function linux_identifiers()
child = null; 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); return (values);
} }
@ -370,94 +422,6 @@ function windows_wmic_results(str)
return (result); 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,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],
removable: tokens[4].split('"')[1] == 'Removable'
};
}
}
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 str = '';
var foundMarkedLine = false;
var password = '';
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ', tokens[0].split('"')[1], ' -Type recoverypassword'], {});
child.stdout.on('data', function (chunk) { str += chunk.toString(); });
child.stderr.on('data', function (chunk) { str += chunk.toString(); });
child.waitExit();
var lines = str.split(/\r?\n/);
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() !== '' && lines[i].includes('Password:') && !lines[i].includes('Numerical Password:')) {
if (i + 1 < lines.length && lines[i + 1].trim() !== '') {
password = lines[i + 1].trim();
foundMarkedLine = true;
}
if (foundMarkedLine) break;
}
}
ret[key].recoveryPassword = (foundMarkedLine ? password : '');
} catch(ex) { }
}
}
this.promise._res(ret);
});
});
return (p2);
}
function windows_identifiers() function windows_identifiers()
{ {
var ret = { windows: {} }; var ret = { windows: {} };
@ -542,11 +506,28 @@ function windows_identifiers()
} }
try { ret.identifiers.cpu_name = ret.windows.cpu[0].Name; } catch (x) { } 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); return (ret);
} }
function macos_identifiers() function macos_identifiers()
{ {
var ret = { identifiers: {} }; var ret = { identifiers: {}, darwin: {} };
var child; var child;
child = require('child_process').execFile('/bin/sh', ['sh']); child = require('child_process').execFile('/bin/sh', ['sh']);
@ -585,42 +566,171 @@ function macos_identifiers()
child.waitExit(); child.waitExit();
ret.identifiers.cpu_name = child.stdout.str.trim(); ret.identifiers.cpu_name = child.stdout.str.trim();
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stdin.write('system_profiler SPMemoryDataType\nexit\n');
child.waitExit();
var lines = child.stdout.str.trim().split('\n');
if(lines.length > 0) {
const memorySlots = [];
if(lines[2].trim().includes('Memory Slots:')) { // OLD MACS WITH SLOTS
var memorySlots1 = child.stdout.str.split(/\n{2,}/).slice(3);
memorySlots1.forEach(function(slot,index) {
var lines = slot.split('\n');
if(lines.length == 1){ // start here
if(lines[0].trim()!=''){
var slotObj = { DeviceLocator: lines[0].trim().replace(/:$/, '') }; // Initialize name as an empty string
var nextline = memorySlots1[index+1].split('\n');
nextline.forEach(function(line) {
if (line.trim() !== '') {
var parts = line.split(':');
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.replace(' ','')] = value; // Store attribute in the slot object
}
});
memorySlots.push(slotObj);
}
}
});
} else { // NEW MACS WITHOUT SLOTS
memorySlots.push({ DeviceLocator: "Onboard Memory", Size: lines[2].split(":")[1].trim(), PartNumber: lines[3].split(":")[1].trim(), Manufacturer: lines[4].split(":")[1].trim() })
}
ret.darwin.memory = memorySlots;
}
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stdin.write('diskutil info -all\nexit\n');
child.waitExit();
var sections = child.stdout.str.split('**********\n');
if(sections.length > 0){
var devices = [];
for (var i = 0; i < sections.length; i++) {
var lines = sections[i].split('\n');
var deviceInfo = {};
var wholeYes = false;
var physicalYes = false;
var oldmac = false;
for (var j = 0; j < lines.length; j++) {
var keyValue = lines[j].split(':');
var key = keyValue[0].trim();
var value = keyValue[1] ? keyValue[1].trim() : '';
if (key === 'Virtual') oldmac = true;
if (key === 'Whole' && value === 'Yes') wholeYes = true;
if (key === 'Virtual' && value === 'No') physicalYes = true;
if(value && key === 'Device / Media Name'){
deviceInfo['Caption'] = value;
}
if(value && key === 'Disk Size'){
deviceInfo['Size'] = value.split(' ')[0] + ' ' + value.split(' ')[1];
}
}
if (wholeYes) {
if (oldmac) {
if (physicalYes) devices.push(deviceInfo);
} else {
devices.push(deviceInfo);
}
}
}
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); trimIdentifiers(ret.identifiers);
child = null; child = null;
return (ret); return (ret);
} }
function hexToAscii(hexString) {
if(!hexString.startsWith('0x')) return hexString.trim();
hexString = hexString.startsWith('0x') ? hexString.slice(2) : hexString;
var str = '';
for (var i = 0; i < hexString.length; i += 2) {
var hexPair = hexString.substr(i, 2);
str += String.fromCharCode(parseInt(hexPair, 16));
}
str = str.replace(/[\u007F-\uFFFF]/g, ''); // Remove characters from 0x0080 to 0xFFFF
return str.trim();
}
function win_chassisType() 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.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.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(); child.waitExit();
try {
try return (parseInt(child.stdout.str));
{ } catch (e) {
var tok = child.stdout.str.split('{')[1].split('}')[0];
var val = tok.split(',')[0];
return (parseInt(val));
}
catch (e)
{
return (2); // unknown return (2); // unknown
} }
} }
function win_systemType() function win_systemType()
{ {
var CSV = '/FORMAT:"' + require('util-language').wmicXslPath + 'csv"'; try {
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'ComputerSystem', 'get', 'PCSystemType', CSV]); var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT PCSystemType FROM Win32_ComputerSystem', ['PCSystemType']);
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); if (tokens[0]) {
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); return (parseInt(tokens[0]['PCSystemType']));
child.waitExit(); } 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) function win_formFactor(chassistype)
@ -744,6 +854,8 @@ module.exports.isVM = function isVM()
case 'VMware, Inc.': case 'VMware, Inc.':
case 'Xen': case 'Xen':
case 'SeaBIOS': case 'SeaBIOS':
case 'EFI Development Kit II / OVMF':
case 'Proxmox distribution of EDK II':
ret = true; ret = true;
break; break;
default: default:
@ -781,15 +893,10 @@ module.exports.isVM = function isVM()
return (ret); return (ret);
}; };
if (process.platform == 'win32')
{
module.exports.volumes_promise = windows_volumes;
}
// bios_date = BIOS->ReleaseDate // bios_date = BIOS->ReleaseDate
// bios_vendor = BIOS->Manufacturer // bios_vendor = BIOS->Manufacturer
// bios_version = BIOS->SMBIOSBIOSVersion // bios_version = BIOS->SMBIOSBIOSVersion
// board_name = BASEBOARD->Product = ioreg/board-id // board_name = BASEBOARD->Product = ioreg/board-id
// board_serial = BASEBOARD->SerialNumber = ioreg/serial-number | ioreg/IOPlatformSerialNumber // board_serial = BASEBOARD->SerialNumber = ioreg/serial-number | ioreg/IOPlatformSerialNumber
// board_vendor = BASEBOARD->Manufacturer = ioreg/manufacturer // board_vendor = BASEBOARD->Manufacturer = ioreg/manufacturer
// board_version = BASEBOARD->Version // board_version = BASEBOARD->Version

View file

@ -262,5 +262,29 @@
"desktopNotify": "{0} távoli asztali munkamenetet indított.", "desktopNotify": "{0} távoli asztali munkamenetet indított.",
"fileNotify": "{0} távoli fájlmunkamenetet indított.", "fileNotify": "{0} távoli fájlmunkamenetet indított.",
"privacyBar": "Asztal megosztás aktív: {0} felhasználóval" "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}"
} }
} }

View file

@ -209,9 +209,13 @@ function macos_memUtilization()
{ {
var usage = lines[0].split(':')[1]; var usage = lines[0].split(':')[1];
var bdown = usage.split(','); var bdown = usage.split(',');
if (bdown.length > 2){ // new style - PhysMem: 5750M used (1130M wired, 634M compressor), 1918M unused.
mem.MemTotal = parseInt(bdown[0].trim().split(' ')[0]); mem.MemFree = parseInt(bdown[2].trim().split(' ')[0]);
mem.MemFree = parseInt(bdown[1].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.percentFree = ((mem.MemFree / mem.MemTotal) * 100);//.toFixed(2);
mem.percentConsumed = (((mem.MemTotal - mem.MemFree) / mem.MemTotal) * 100);//.toFixed(2); mem.percentConsumed = (((mem.MemTotal - mem.MemFree) / mem.MemTotal) * 100);//.toFixed(2);
return (mem); return (mem);
@ -225,25 +229,14 @@ function macos_memUtilization()
function windows_thermals() function windows_thermals()
{ {
var ret = []; 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']); try {
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); if (ret[0]) {
child.waitExit(); for (var i = 0; i < ret.length; ++i) {
if(child.stdout.str.trim()!='') ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
{
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]; }
} }
ret.push(obj);
} }
} } catch (ex) { }
return (ret); return (ret);
} }
@ -305,9 +298,14 @@ function macos_thermals()
} }
} }
}); });
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function (c) {
child.stdin.write('powermetrics -s smc\n'); if (c.toString().split('unable to get smc values').length > 1) { // error getting sensors so just kill
child.waitExit(5000); this.parent.kill();
return;
}
});
child.stdin.write('powermetrics -s smc -i 500 -n 1\n');
child.waitExit(2000);
} }
return (ret); return (ret);
} }

View file

@ -18,28 +18,21 @@ var promise = require('promise');
function qfe() function qfe()
{ {
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'qfe', 'list', 'full', '/FORMAT:CSV']); try {
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_QuickFixEngineering');
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); if (tokens[0]){
child.waitExit(); for (var index = 0; index < tokens.length; index++) {
for (var key in tokens[index]) {
var lines = child.stdout.str.trim().split('\r\n'); if (key.startsWith('__')) delete tokens[index][key];
var keys = lines[0].split(','); }
var i, key; }
var tokens; return (tokens);
var result = []; } else {
return ([]);
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]; }
} }
result.push(obj); } catch (ex) {
return ([]);
} }
return (result);
} }
function av() function av()
{ {
@ -228,6 +221,14 @@ function installedApps()
catch(e)\ catch(e)\
{\ {\
}\ }\
try\
{\
val.installdate = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallDate');\
if (val.installdate == '') { delete val.installdate; }\
}\
catch(e)\
{\
}\
result.push(val);\ result.push(val);\
}\ }\
console.log(JSON.stringify(result,'', 1));process.exit();"; console.log(JSON.stringify(result,'', 1));process.exit();";
@ -250,8 +251,12 @@ function defender(){
ret.child.stdin.write('exit\r\n'); ret.child.stdin.write('exit\r\n');
ret.child.on('exit', function (c) { ret.child.on('exit', function (c) {
if (this.stdout.str == '') { this.promise._resolve({}); return; } if (this.stdout.str == '') { this.promise._resolve({}); return; }
var abc = JSON.parse(this.stdout.str.trim()) try {
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected }); 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); return (ret);
} }

View file

@ -39,17 +39,90 @@ function getVolumes()
{ {
ret[v[i].DeviceID] = trimObject(v[i]); ret[v[i].DeviceID] = trimObject(v[i]);
} }
try {
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume'); v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
for (i in v) for (i in v)
{
var tmp = trimObject(v[i]);
for (var k in tmp)
{ {
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); 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
};

View file

@ -485,8 +485,8 @@ function windows_execve(name, agentfilename, sessionid) {
var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true }); var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize); var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true }); 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==' + var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + 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 }); ' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
if (name == null) if (name == null)
{ {

View file

@ -707,7 +707,15 @@ module.exports.CreateAmtManager = function (parent) {
dev.aquired.controlMode = responses['IPS_HostBasedSetupService'].response.CurrentControlMode; // 1 = CCM, 2 = ACM dev.aquired.controlMode = responses['IPS_HostBasedSetupService'].response.CurrentControlMode; // 1 = CCM, 2 = ACM
if (typeof stack.wsman.comm.amtVersion == 'string') { // Set the Intel AMT version using the HTTP header if present if (typeof stack.wsman.comm.amtVersion == 'string') { // Set the Intel AMT version using the HTTP header if present
var verSplit = stack.wsman.comm.amtVersion.split('.'); var verSplit = stack.wsman.comm.amtVersion.split('.');
if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); } if (verSplit.length >= 2) {
dev.aquired.version = verSplit[0] + '.' + verSplit[1];
dev.aquired.majorver = parseInt(verSplit[0]);
dev.aquired.minorver = parseInt(verSplit[1]);
if (verSplit.length >= 3) {
dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2];
dev.aquired.maintenancever = parseInt(verSplit[2]);
}
}
} }
dev.aquired.realm = stack.wsman.comm.digestRealm; dev.aquired.realm = stack.wsman.comm.digestRealm;
dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user; dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user;
@ -931,8 +939,8 @@ module.exports.CreateAmtManager = function (parent) {
if (response.Body.OSPowerSavingState == 2) { meshPowerState = 1; } // Fully powered (S0); if (response.Body.OSPowerSavingState == 2) { meshPowerState = 1; } // Fully powered (S0);
else if (response.Body.OSPowerSavingState == 3) { meshPowerState = 2; } // Modern standby (We are going to call this S1); else if (response.Body.OSPowerSavingState == 3) { meshPowerState = 2; } // Modern standby (We are going to call this S1);
// Set OS power state // Set OS power state - connType: 0 = CIRA, 1 = CIRA-Relay, 2 = CIRA-LMS, 3 = LAN
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 2, meshPowerState, null, { name: dev.name }); } if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), (dev.connType == 3 ? 4 : 2), meshPowerState, null, { name: dev.name }); }
}); });
} else { } else {
// Convert the power state // Convert the power state
@ -941,13 +949,13 @@ module.exports.CreateAmtManager = function (parent) {
var meshPowerState = -1, powerConversionTable = [-1, -1, 1, 2, 3, 6, 6, 5, 6]; var meshPowerState = -1, powerConversionTable = [-1, -1, 1, 2, 3, 6, 6, 5, 6];
if (powerstate < powerConversionTable.length) { meshPowerState = powerConversionTable[powerstate]; } else { powerstate = 6; } if (powerstate < powerConversionTable.length) { meshPowerState = powerConversionTable[powerstate]; } else { powerstate = 6; }
// Set power state // Set power state - connType: 0 = CIRA, 1 = CIRA-Relay, 2 = CIRA-LMS, 3 = LAN
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 2, meshPowerState, null, { name: dev.name }); } if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), (dev.connType == 3 ? 4 : 2), meshPowerState, null, { name: dev.name }); }
} }
}); });
} }
// 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) { function performPowerAction(nodeid, action) {
console.log('performPowerAction', nodeid, action); console.log('performPowerAction', nodeid, action);
var devices = obj.amtDevices[nodeid]; var devices = obj.amtDevices[nodeid];
@ -962,7 +970,7 @@ module.exports.CreateAmtManager = function (parent) {
// Action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset // Action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset
try { dev.amtstack.RequestPowerStateChange(action, performPowerActionResponse); } catch (ex) { } try { dev.amtstack.RequestPowerStateChange(action, performPowerActionResponse); } catch (ex) { }
} else { } 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); dev.amtstack.BatchEnum(null, ['*AMT_BootSettingData'], performAdvancedPowerActionResponse);
} }
} }
@ -995,8 +1003,8 @@ module.exports.CreateAmtManager = function (parent) {
// Ready boot parameters // Ready boot parameters
bootSettingData['BIOSSetup'] = ((action >= 11) && (action <= 14)); bootSettingData['BIOSSetup'] = ((action >= 11) && (action <= 14));
bootSettingData['UseSOL'] = ((action >= 13) && (action <= 14)); bootSettingData['UseSOL'] = ((action >= 13) && (action <= 14));
if ((action == 11) || (action == 13)) { dev.powerAction = 2; } // Power on if ((action == 11) || (action == 13) || (action == 15)) { dev.powerAction = 2; } // Power on
if ((action == 12) || (action == 14)) { dev.powerAction = 10; } // Reset if ((action == 12) || (action == 14) || (action == 16)) { dev.powerAction = 10; } // Reset
// Set boot parameters // Set boot parameters
dev.amtstack.Put('AMT_BootSettingData', bootSettingData, function (stack, name, response, status, tag) { dev.amtstack.Put('AMT_BootSettingData', bootSettingData, function (stack, name, response, status, tag) {
@ -1007,7 +1015,8 @@ module.exports.CreateAmtManager = function (parent) {
const dev = stack.dev; const dev = stack.dev;
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
// Set boot order // 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; const dev = stack.dev;
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
// Perform power action // Perform power action
@ -1060,7 +1069,7 @@ module.exports.CreateAmtManager = function (parent) {
if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); delete dev.ocrfile; return; } if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); delete dev.ocrfile; return; }
// Check if this Intel AMT device supports OCR // 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; dev.consoleMsg("This Intel AMT device does not support UEFI HTTPS boot (" + status + ")."); delete dev.ocrfile; return;
} }
@ -1090,11 +1099,14 @@ module.exports.CreateAmtManager = function (parent) {
// Generate the one-time URL. // Generate the one-time URL.
var cookie = obj.parent.encodeCookie({ a: 'f', f: dev.ocrfile }, obj.parent.loginCookieEncryptionKey) 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; delete dev.ocrfile;
// Generate the boot data for OCR with URL // Generate the boot data for OCR with URL
var r = response.Body; 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['UefiBootParametersArray'] = Buffer.from(makeUefiBootParam(1, url) + makeUefiBootParam(20, 1, 1) + makeUefiBootParam(30, 0, 2), 'binary').toString('base64');
r['UefiBootNumberOfParams'] = 3; r['UefiBootNumberOfParams'] = 3;
r['BootMediaIndex'] = 0; // Do not use boot media index for One Click Recovery (OCR) r['BootMediaIndex'] = 0; // Do not use boot media index for One Click Recovery (OCR)
@ -1115,8 +1127,7 @@ module.exports.CreateAmtManager = function (parent) {
dev.amtstack.SetBootConfigRole(1, function (stack, name, response, status) { dev.amtstack.SetBootConfigRole(1, function (stack, name, response, status) {
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. 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; } 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('<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) {
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) {
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
if (status != 200) { dev.consoleMsg("Failed to set boot config (" + status + ")."); return; } 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 dev.amtstack.RequestPowerStateChange(10, function (stack, name, response, status) { // 10 = Reset, 2 = Power Up
@ -2621,7 +2632,14 @@ module.exports.CreateAmtManager = function (parent) {
if (domain && domain.amtmanager && (domain.amtmanager.tlsacmactivation == true)) { TlsAcmActivation = true; } if (domain && domain.amtmanager && (domain.amtmanager.tlsacmactivation == true)) { TlsAcmActivation = true; }
// Check Intel AMT version // Check Intel AMT version
if (typeof dev.intelamt.ver == 'string') { var verSplit = dev.intelamt.ver.split('.'); if (verSplit.length >= 3) { dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); } } if (typeof dev.intelamt.ver == 'string') {
var verSplit = dev.intelamt.ver.split('.');
if (verSplit.length >= 2) {
dev.aquired.majorver = parseInt(verSplit[0]);
dev.aquired.minorver = parseInt(verSplit[1]);
if (verSplit.length >= 3) { dev.aquired.maintenancever = parseInt(verSplit[2]); }
}
}
// If this is Intel AMT 14 or better and allowed, we are going to attempt a host-based end-to-end TLS activation. // If this is Intel AMT 14 or better and allowed, we are going to attempt a host-based end-to-end TLS activation.
if (TlsAcmActivation && (dev.aquired.majorver >= 14)) { if (TlsAcmActivation && (dev.aquired.majorver >= 14)) {
@ -2677,7 +2695,15 @@ module.exports.CreateAmtManager = function (parent) {
dev.aquired.controlMode = 1; // 1 = CCM, 2 = ACM dev.aquired.controlMode = 1; // 1 = CCM, 2 = ACM
if (typeof dev.amtstack.wsman.comm.amtVersion == 'string') { if (typeof dev.amtstack.wsman.comm.amtVersion == 'string') {
var verSplit = dev.amtstack.wsman.comm.amtVersion.split('.'); var verSplit = dev.amtstack.wsman.comm.amtVersion.split('.');
if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); } if (verSplit.length >= 2) {
dev.aquired.version = verSplit[0] + '.' + verSplit[1];
dev.aquired.majorver = parseInt(verSplit[0]);
dev.aquired.minorver = parseInt(verSplit[1]);
if (verSplit.length >= 3) {
dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2];
dev.aquired.maintenancever = parseInt(verSplit[2]);
}
}
} }
if ((typeof dev.mpsConnection.tag.meiState.OsHostname == 'string') && (typeof dev.mpsConnection.tag.meiState.OsDnsSuffix == 'string')) { if ((typeof dev.mpsConnection.tag.meiState.OsHostname == 'string') && (typeof dev.mpsConnection.tag.meiState.OsDnsSuffix == 'string')) {
dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix; dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix;
@ -2812,8 +2838,10 @@ module.exports.CreateAmtManager = function (parent) {
var vs = getInstance(amtlogicalelements, 'AMT')['VersionString']; var vs = getInstance(amtlogicalelements, 'AMT')['VersionString'];
if (vs != null) { if (vs != null) {
dev.aquired.version = vs; dev.aquired.version = vs;
dev.aquired.versionmajor = parseInt(dev.aquired.version.split('.')[0]); version = dev.aquired.version.split('.')
dev.aquired.versionminor = parseInt(dev.aquired.version.split('.')[1]); dev.aquired.versionmajor = parseInt(version[0]);
dev.aquired.versionminor = parseInt(version[1]);
if (version.length > 2) { dev.aquired.versionmaintenance = parseInt(version[2]); }
} }
} }
} }
@ -2821,10 +2849,14 @@ module.exports.CreateAmtManager = function (parent) {
// Fetch the Intel AMT version from HTTP stack // Fetch the Intel AMT version from HTTP stack
if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) { if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) {
var s = stack.wsman.comm.amtVersion.split('.'); var s = stack.wsman.comm.amtVersion.split('.');
if (s.length >= 3) { if (s.length >= 2) {
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2]; dev.aquired.version = s[0] + '.' + s[1] + '.';
dev.aquired.versionmajor = parseInt(s[0]); dev.aquired.versionmajor = parseInt(s[0]);
dev.aquired.versionminor = parseInt(s[1]); dev.aquired.versionminor = parseInt(s[1]);
if (s.length >= 3) {
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
dev.aquired.versionmaintenance = parseInt(s[2]);
}
} }
} }

View file

@ -201,8 +201,10 @@ module.exports.CreateAmtProvisioningServer = function (parent, config) {
var vs = getInstance(amtlogicalelements, 'AMT')['VersionString']; var vs = getInstance(amtlogicalelements, 'AMT')['VersionString'];
if (vs != null) { if (vs != null) {
dev.aquired.version = vs; dev.aquired.version = vs;
dev.aquired.versionmajor = parseInt(dev.aquired.version.split('.')[0]); const versionSplit = parseInt(dev.aquired.version.split('.'));
dev.aquired.versionminor = parseInt(dev.aquired.version.split('.')[1]); dev.aquired.versionmajor = parseInt(versionSplit[0]);
dev.aquired.versionminor = parseInt(versionSplit[1]);
if (versionSplit.length >= 3) { dev.aquired.versionmaintenance = parseInt(versionSplit[2]); }
} }
} }
} }
@ -210,10 +212,14 @@ module.exports.CreateAmtProvisioningServer = function (parent, config) {
// Fetch the Intel AMT version from HTTP stack // Fetch the Intel AMT version from HTTP stack
if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) { if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) {
var s = stack.wsman.comm.amtVersion.split('.'); var s = stack.wsman.comm.amtVersion.split('.');
if (s.length >= 3) { if (s.length >= 2) {
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2]; dev.aquired.version = s[0] + '.' + s[1];
dev.aquired.versionmajor = parseInt(s[0]); dev.aquired.versionmajor = parseInt(s[0]);
dev.aquired.versionminor = parseInt(s[1]); dev.aquired.versionminor = parseInt(s[1]);
if (s.length >= 3) {
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
dev.aquired.versionmaintenance = parseInt(s[2]);
}
} }
} }

View file

@ -1,4 +1,4 @@
/** /**
* @description MeshCentral MSTSC & SSH relay * @description MeshCentral MSTSC & SSH relay
* @author Ylian Saint-Hilaire & Bryan Roe * @author Ylian Saint-Hilaire & Bryan Roe
* @copyright Intel Corporation 2018-2022 * @copyright Intel Corporation 2018-2022
@ -458,7 +458,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
const protocol = (args.tlsoffload) ? 'ws' : 'wss'; const protocol = (args.tlsoffload) ? 'ws' : 'wss';
var domainadd = ''; var domainadd = '';
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' } if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=14&auth=' + cookie; // Protocol 14 is Web-TCP var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=14&auth=' + cookie; // Protocol 14 is Web-TCP
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument. if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
parent.parent.parent.debug('relay', 'TCP: Connection websocket to ' + url); parent.parent.parent.debug('relay', 'TCP: Connection websocket to ' + url);
obj.wsClient = new WebSocket(url, options); obj.wsClient = new WebSocket(url, options);
@ -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('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 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
} }
@ -842,22 +843,21 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
obj.relaySocket.resume(); obj.relaySocket.resume();
} }
} else { } else {
if (typeof data == 'string') { try { // Forward any ping/pong commands to the browser
// Forward any ping/pong commands to the browser var cmd = JSON.parse(data);
var cmd = null;
try { cmd = JSON.parse(data); } catch (ex) { }
if ((cmd != null) && (cmd.ctrlChannel == '102938')) { if ((cmd != null) && (cmd.ctrlChannel == '102938')) {
if (cmd.type == 'ping') { send(['ping']); } if (cmd.type == 'ping') { send(['ping']); }
else if (cmd.type == 'pong') { send(['pong']); } else if (cmd.type == 'pong') { send(['pong']); }
} }
return; return;
} catch (ex) { // You are not JSON data so just send over relaySocket
obj.wsClient._socket.pause();
try {
obj.relaySocket.write(data, function () {
if (obj.wsClient && obj.wsClient._socket) { try { obj.wsClient._socket.resume(); } catch (ex) { console.log(ex); } }
});
} catch (ex) { console.log(ex); obj.close(); }
} }
obj.wsClient._socket.pause();
try {
obj.relaySocket.write(data, function () {
if (obj.wsClient && obj.wsClient._socket) { try { obj.wsClient._socket.resume(); } catch (ex) { console.log(ex); } }
});
} catch (ex) { console.log(ex); obj.close(); }
} }
}); });
obj.wsClient.on('close', function () { parent.parent.debug('relay', 'RDP: Relay websocket closed'); obj.close(); }); obj.wsClient.on('close', function () { parent.parent.debug('relay', 'RDP: Relay websocket closed'); obj.close(); });
@ -984,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 ((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_REMOTEVIEWONLY) != 0)) { obj.viewonly = true; }
if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0)) { obj.limitedinput = 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.mtype = node.mtype; // Store the device group type
obj.meshid = node.meshid; // Store the MeshID obj.meshid = node.meshid; // Store the MeshID
@ -1046,7 +1047,11 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
if ((k == 14) || (k == 28)) { ok = true; } // Enter and backspace if ((k == 14) || (k == 28)) { ok = true; } // Enter and backspace
if (ok == false) return; if (ok == false) return;
} }
if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventScancode(msg[1], msg[2]); } break; var extended = false;
var extendedkeys = [57419,57421,57416,57424,57426,57427,57417,57425,57372,57397,57415,57423,57373,57400,57399];
// left,right,up,down,insert,delete,pageup,pagedown,numpadenter,numpaddivide,home,end,controlright,altright,printscreen
if (extendedkeys.includes(msg[1])) extended=true;
if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventScancode(msg[1], msg[2], extended); } break;
} }
case 'unicode': { if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventUnicode(msg[1], msg[2]); } break; } case 'unicode': { if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventUnicode(msg[1], msg[2]); } break; }
case 'utype': { case 'utype': {
@ -1210,7 +1215,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
const protocol = (args.tlsoffload) ? 'ws' : 'wss'; const protocol = (args.tlsoffload) ? 'ws' : 'wss';
var domainadd = ''; var domainadd = '';
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' } if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + obj.xcookie; // Protocol 11 is Web-SSH var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + obj.xcookie; // Protocol 11 is Web-SSH
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument. if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url); parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
obj.wsClient = new WebSocket(url, options); obj.wsClient = new WebSocket(url, options);
@ -1277,16 +1282,14 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
ws._socket.resume(); ws._socket.resume();
} }
} else { } else {
if (typeof data == 'string') { try { // Forward any ping/pong commands to the browser
// Forward any ping/pong commands to the browser
var cmd = null; var cmd = null;
try { cmd = JSON.parse(data); } catch (ex) { } cmd = JSON.parse(data);
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); } if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); }
return; return;
} catch(ex) { // Relay WS --> SSH instead
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
// Relay WS --> SSH
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
}); });
obj.wsClient.on('close', function () { parent.parent.debug('relay', 'SSH: Relay websocket closed'); obj.close(); }); obj.wsClient.on('close', function () { parent.parent.debug('relay', 'SSH: Relay websocket closed'); obj.close(); });
@ -1314,7 +1317,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
// Check if we have SSH credentials for this device // Check if we have SSH credentials for this device
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
if ((err != null) || (nodes == null) || (nodes.length != 1)) return; 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'))) { 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 // Send a request for SSH authentication
try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { }
@ -1362,7 +1365,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
obj.termSize = msg; obj.termSize = msg;
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
if ((err != null) || (nodes == null) || (nodes.length != 1)) return; 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) { if (node.ssh != null) {
obj.username = node.ssh.u; obj.username = node.ssh.u;
obj.privateKey = node.ssh.k; obj.privateKey = node.ssh.k;
@ -1404,7 +1407,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
if (obj.cookie == null) return; // obj has been cleaned up, just exit. 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(); } 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.nodeid = node._id; // Store the NodeID
obj.meshid = node.meshid; // Store the MeshID obj.meshid = node.meshid; // Store the MeshID
obj.mtype = node.mtype; // Store the device group type obj.mtype = node.mtype; // Store the device group type
@ -1549,7 +1552,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
const protocol = (args.tlsoffload) ? 'ws' : 'wss'; const protocol = (args.tlsoffload) ? 'ws' : 'wss';
var domainadd = ''; var domainadd = '';
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' } if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + authCookie // Protocol 11 is Web-SSH var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + authCookie // Protocol 11 is Web-SSH
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument. if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url); parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
obj.wsClient = new WebSocket(url, options); obj.wsClient = new WebSocket(url, options);
@ -1617,16 +1620,14 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
ws._socket.resume(); ws._socket.resume();
} }
} else { } else {
if (typeof data == 'string') { try { // Forward any ping/pong commands to the browser
// Forward any ping/pong commands to the browser
var cmd = null; var cmd = null;
try { cmd = JSON.parse(data); } catch (ex) { } cmd = JSON.parse(data);
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { try { obj.ws.send(data); } catch (ex) { console.log(ex); } } if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { try { obj.ws.send(data); } catch (ex) { console.log(ex); } }
return; return;
} catch (ex) { // Relay WS --> SSH
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
// Relay WS --> SSH
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
}); });
obj.wsClient.on('close', function () { obj.wsClient.on('close', function () {
@ -1739,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 if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) { parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
if (obj.ws == null) return; // obj has been cleaned up, just exit. 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 // Check permissions
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights
@ -1903,7 +1905,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
const protocol = (args.tlsoffload) ? 'ws' : 'wss'; const protocol = (args.tlsoffload) ? 'ws' : 'wss';
var domainadd = ''; var domainadd = '';
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' } if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=13&auth=' + authCookie // Protocol 13 is Web-SSH-Files var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=13&auth=' + authCookie // Protocol 13 is Web-SSH-Files
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument. if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url); parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
obj.wsClient = new WebSocket(url, options); obj.wsClient = new WebSocket(url, options);
@ -1965,16 +1967,15 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
ws._socket.resume(); ws._socket.resume();
} }
} else { } else {
if (typeof data == 'string') { try {
// Forward any ping/pong commands to the browser // Forward any ping/pong commands to the browser
var cmd = null; var cmd = null;
try { cmd = JSON.parse(data); } catch (ex) { } cmd = JSON.parse(data);
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); } if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); }
return; return;
} catch (ex) { // Relay WS --> SSH
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
// Relay WS --> SSH
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
} }
}); });
obj.wsClient.on('close', function () { obj.wsClient.on('close', function () {
@ -2269,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 if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) { parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
if (obj.ws == null) return; // obj has been cleaned up, just exit. 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 // Check permissions
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights

View file

@ -298,7 +298,7 @@ function createAuthenticodeHandler(path) {
for (var i = 0; i < obj.header.coff.numberOfSections; i++) { for (var i = 0; i < obj.header.coff.numberOfSections; i++) {
var section = {}; var section = {};
buf = readFileSlice(obj.header.SectionHeadersPtr + (i * 40), 40); buf = readFileSlice(obj.header.SectionHeadersPtr + (i * 40), 40);
if (buf[0] != 46) { obj.close(); return false; }; // Name of the section must start with a dot. If not, something is wrong. if ((buf[0] != 46) && (buf[0] != 95)) { obj.close(); return false; }; // Name of the section must start with a dot or underscore. If not, something is wrong.
var sectionName = buf.slice(0, 8).toString().trim('\0'); var sectionName = buf.slice(0, 8).toString().trim('\0');
var j = sectionName.indexOf('\0'); var j = sectionName.indexOf('\0');
if (j >= 0) { sectionName = sectionName.substring(0, j); } // Trim any trailing zeroes if (j >= 0) { sectionName = sectionName.substring(0, j); } // Trim any trailing zeroes
@ -1566,7 +1566,11 @@ function createAuthenticodeHandler(path) {
options.protocol = timeServerUrl.protocol; options.protocol = timeServerUrl.protocol;
options.hostname = timeServerUrl.hostname; options.hostname = timeServerUrl.hostname;
options.path = timeServerUrl.pathname; 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) { if (options.proxy == null) {
// No proxy needed // No proxy needed
@ -1584,7 +1588,7 @@ function createAuthenticodeHandler(path) {
// Set up the request // Set up the request
var responseAccumulator = ''; var responseAccumulator = '';
var req = require('http').request(options, function (res) { var req = http.request(options, function (res) {
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', function (chunk) { responseAccumulator += chunk; }); res.on('data', function (chunk) { responseAccumulator += chunk; });
res.on('end', function () { func(null, responseAccumulator); }); res.on('end', function () { func(null, responseAccumulator); });
@ -1605,12 +1609,12 @@ function createAuthenticodeHandler(path) {
proxyOptions.protocol = proxyUrl.protocol; proxyOptions.protocol = proxyUrl.protocol;
proxyOptions.hostname = proxyUrl.hostname; proxyOptions.hostname = proxyUrl.hostname;
proxyOptions.path = options.hostname + ':' + options.port; 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 // Set up the proxy request
var responseAccumulator = ''; var responseAccumulator = '';
var req = require('http').request(proxyOptions); var req = http.request(proxyOptions);
req.on('error', function (err) { func('' + err); }); req.on('error', function (err) { func('' + err); });
req.on('connect', function (res, socket, head) { req.on('connect', function (res, socket, head) {
// Make a request over the HTTP tunnel // Make a request over the HTTP tunnel

View file

@ -1049,6 +1049,7 @@ module.exports.CertificateOperations = function (parent) {
config.domains[i].certs = r.dns[i]; config.domains[i].certs = r.dns[i];
} else { } else {
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly."); console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
rcountmax++;
} }
} else { } else {
// If the web certificate already exist, load it. Load both certificate and private key // If the web certificate already exist, load it. Load both certificate and private key
@ -1407,20 +1408,15 @@ module.exports.CertificateOperations = function (parent) {
// Perform any general operation // Perform any general operation
obj.acceleratorPerformOperation = function (operation, data, tag, func) { obj.acceleratorPerformOperation = function (operation, data, tag, func) {
if (acceleratorTotalCount <= 1) { var acc = obj.getAccelerator();
// No accelerators available if (acc == null) {
require(program).processMessage({ action: operation, data: data, tag: tag, func: func }); // Add to pending accelerator workload
acceleratorPerformSignaturePushFuncCall++;
pendingAccelerator.push({ action: operation, data: data, tag: tag, func: func });
} else { } else {
var acc = obj.getAccelerator(); // Send to accelerator now
if (acc == null) { acceleratorPerformSignatureRunFuncCall++;
// Add to pending accelerator workload acc.send(acc.x = { action: operation, data: data, tag: tag, func: func });
acceleratorPerformSignaturePushFuncCall++;
pendingAccelerator.push({ action: operation, data: data, tag: tag, func: func });
} else {
// Send to accelerator now
acceleratorPerformSignatureRunFuncCall++;
acc.send(acc.x = { action: operation, data: data, tag: tag, func: func });
}
} }
}; };

View file

@ -155,14 +155,26 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) {
return obj; return obj;
}; };
// Escape and unescape feild names so there are no invalid characters for MongoDB // 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)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24'); }; 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('%2E').join('.').split('%24').join('$').split('%25').join('%'); }; 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 // Escape all links, SSH and RDP usernames
module.exports.escapeLinksFieldNameEx = function (docx) { if (docx.links == null) { return docx; } var doc = Object.assign({}, docx); doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } return doc; }; // This is required for databases like NeDB that don't accept "." or "," as part of a field name.
module.exports.escapeLinksFieldName = function (docx) { var doc = Object.assign({}, docx); if (doc.links != null) { doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } } return doc; }; module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
module.exports.unEscapeLinksFieldName = function (doc) { if (doc.links != null) { for (var j in doc.links) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.links[ue] = doc.links[j]; delete doc.links[j]; } } } return doc; }; module.exports.escapeLinksFieldName = function (docx) {
var doc = Object.assign({}, docx);
if (doc.links != null) { doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } }
if (doc.ssh != null) { doc.ssh = Object.assign({}, doc.ssh); for (var i in doc.ssh) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.ssh[ue] = doc.ssh[i]; delete doc.ssh[i]; } } }
if (doc.rdp != null) { doc.rdp = Object.assign({}, doc.rdp); for (var i in doc.rdp) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.rdp[ue] = doc.rdp[i]; delete doc.rdp[i]; } } }
return doc;
};
module.exports.unEscapeLinksFieldName = function (doc) {
if (doc.links != null) { for (var j in doc.links) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.links[ue] = doc.links[j]; delete doc.links[j]; } } }
if (doc.ssh != null) { for (var j in doc.ssh) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.ssh[ue] = doc.ssh[j]; delete doc.ssh[j]; } } }
if (doc.rdp != null) { for (var j in doc.rdp) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.rdp[ue] = doc.rdp[j]; delete doc.rdp[j]; } } }
return doc;
};
//module.exports.escapeAllLinksFieldName = function (docs) { for (var i in docs) { module.exports.escapeLinksFieldName(docs[i]); } return docs; }; //module.exports.escapeAllLinksFieldName = function (docs) { for (var i in docs) { module.exports.escapeLinksFieldName(docs[i]); } return docs; };
module.exports.unEscapeAllLinksFieldName = function (docs) { for (var i in docs) { docs[i] = module.exports.unEscapeLinksFieldName(docs[i]); } return docs; }; module.exports.unEscapeAllLinksFieldName = function (docs) { for (var i in docs) { docs[i] = module.exports.unEscapeLinksFieldName(docs[i]); } return docs; };
@ -322,6 +334,11 @@ module.exports.meshServerRightsArrayToNumber = function (val) {
if (r == 'locked') { newAccRights |= 32; } if (r == 'locked') { newAccRights |= 32; }
if (r == 'nonewgroups') { newAccRights |= 64; } if (r == 'nonewgroups') { newAccRights |= 64; }
if (r == 'notools') { newAccRights |= 128; } 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; return newAccRights;
} }
@ -374,4 +391,32 @@ module.exports.moveOldFiles = function (filelist) {
for (var i in filelist) { if (fs.existsSync(filelist[i] + oldFileExt) == true) { extOk = false; } } for (var i in filelist) { if (fs.existsSync(filelist[i] + oldFileExt) == true) { extOk = false; } }
} while (extOk == false); } while (extOk == false);
for (var i in filelist) { try { fs.renameSync(filelist[i], filelist[i] + oldFileExt); } catch (ex) { } } for (var i in filelist) { try { fs.renameSync(filelist[i], filelist[i] + oldFileExt); } catch (ex) { } }
}
// Convert strArray to Array, returns array if strArray or null if any other type
module.exports.convertStrArray = function (object, split) {
if (split && typeof object === 'string') {
return object.split(split)
} else if (typeof object === 'string') {
return Array(object);
} else if (Array.isArray(object)) {
return object
} 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;
} }

1641
db.js

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,16 @@
"archiver": "5.3.2", "@seald-io/nedb": "4.0.4",
"body-parser": "1.20.2", "archiver": "7.0.1",
"body-parser": "1.20.3",
"cbor": "5.2.0", "cbor": "5.2.0",
"compression": "1.7.4", "compression": "1.7.5",
"cookie-session": "2.0.0", "cookie-session": "2.1.0",
"express": "4.18.2", "express": "4.21.2",
"express-handlebars": "5.3.5", "express-handlebars": "7.1.3",
"express-ws": "4.0.0", "express-ws": "5.0.2",
"ipcheck": "0.1.0", "ipcheck": "0.1.0",
"minimist": "1.2.8", "minimist": "1.2.8",
"multiparty": "4.2.3", "multiparty": "4.2.3",
"@yetzt/nedb": "1.8.0",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"ua-parser-js": "1.0.37", "ua-parser-js": "1.0.39",
"ws": "8.14.2", "ws": "8.18.0",
"yauzl": "2.10.0" "yauzl": "2.10.0"

View file

@ -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 RUN mkdir -p /opt/meshcentral/meshcentral
COPY ./ /opt/meshcentral/meshcentral/ COPY ./ /opt/meshcentral/meshcentral/
@ -18,7 +18,7 @@ RUN if ! [ -z "$DISABLE_TRANSLATE" ] && [ "$DISABLE_TRANSLATE" != "yes" ] && [ "
fi fi
# install translate/minify modules if need too # 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 # first extractall if need too
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral/translate && node translate.js extractall; fi 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 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 #Add non-root user, add installation directories and assign proper permissions
RUN mkdir -p /opt/meshcentral/meshcentral RUN mkdir -p /opt/meshcentral/meshcentral
@ -62,8 +62,8 @@ ENV MONGO_URL=""
ENV HOSTNAME="localhost" ENV HOSTNAME="localhost"
ENV ALLOW_NEW_ACCOUNTS="true" ENV ALLOW_NEW_ACCOUNTS="true"
ENV ALLOWPLUGINS="false" ENV ALLOWPLUGINS="false"
ENV LOCALSESSIONRECORDING="false" ENV LOCALSESSIONRECORDING="true"
ENV MINIFY="true" ENV MINIFY="false"
ENV WEBRTC="false" ENV WEBRTC="false"
ENV IFRAME="false" ENV IFRAME="false"
ENV SESSION_KEY="" ENV SESSION_KEY=""
@ -83,12 +83,12 @@ COPY --from=builder /opt/meshcentral/meshcentral /opt/meshcentral/meshcentral
COPY ./docker/startup.sh ./startup.sh COPY ./docker/startup.sh ./startup.sh
COPY ./docker/config.json.template /opt/meshcentral/config.json.template COPY ./docker/config.json.template /opt/meshcentral/config.json.template
# install dependencies from package.json and nedb # install dependencies from package.json
RUN cd meshcentral && npm install && npm install nedb RUN cd meshcentral && npm install
# NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart() # 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 "$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 EXPOSE 80 443 4433

View file

@ -21,9 +21,9 @@
"": { "": {
"_title": "MyServer", "_title": "MyServer",
"_title2": "Servername", "_title2": "Servername",
"minify": true, "minify": false,
"NewAccounts": true, "NewAccounts": true,
"localSessionRecording": false, "localSessionRecording": true,
"_userNameIsEmail": true, "_userNameIsEmail": true,
"_certUrl": "my.reverse.proxy" "_certUrl": "my.reverse.proxy"
} }

View file

@ -18,7 +18,7 @@ else
sed -i "s/\"NewAccounts\": true/\"NewAccounts\": $ALLOW_NEW_ACCOUNTS/" meshcentral-data/"${CONFIG_FILE}" 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/\"enabled\": false/\"enabled\": $ALLOWPLUGINS/" meshcentral-data/"${CONFIG_FILE}"
sed -i "s/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" 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/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}"
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}" sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}"
if [ -z "$SESSION_KEY" ]; then if [ -z "$SESSION_KEY" ]; then

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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 youve 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 projects 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.
![](images/translation-msg-output.png)
10. **Finalize Changes:** Its 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.

View file

@ -12,7 +12,7 @@ For more information, [visit MeshCentral.com](https://www.meshcentral.com/).
[Reddit](https://www.reddit.com/r/MeshCentral/) [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/) [BlogSpot](https://meshcentral2.blogspot.com/)

View file

@ -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. 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 ## Video Walkthru

View file

@ -4,6 +4,45 @@
This guide is specifically intended to help users install MeshCentral from start to finish. Once installed, you can take a look at the MeshCentral users guide for information on how to configure MeshCentral for your specific use. In this document, we will look at installing MeshCentral on AWS Linux, Raspberry Pi and Ubuntu. This guide is specifically intended to help users install MeshCentral from start to finish. Once installed, you can take a look at the MeshCentral users guide for information on how to configure MeshCentral for your specific use. In this document, we will look at installing MeshCentral on AWS Linux, Raspberry Pi and Ubuntu.
## Docker
<https://github.com/Ylianst/MeshCentral/pkgs/container/meshcentral>
```
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 ## 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: 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:
@ -40,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 dont delay and create an account right away. Once running, move on to the MeshCentrals users guide to configure your new server. 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 dont delay and create an account right away. Once running, move on to the MeshCentrals users 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.
[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/meshcentral)
## Server Security - Adding Crowdsec ## 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. 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.
@ -884,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 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: 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:
@ -901,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 servers `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: MeshCentral allows users to upload and download files stores in the servers `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 Lets Encrypt support you will need to type the following commands to make the `letsencrypt` folder in `meshcentral-data` writable. If you plan on using the increased security installation along with MeshCentral built-in Lets Encrypt support you will need to type the following commands to make the `letsencrypt` folder in `meshcentral-data` writable.
@ -909,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
sudo mkdir /opt/meshcentral/meshcentral-data/letsencrypt 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 Lets Encrypt certificate. If this is not done, the server will generate an `ACCES: permission denied` exception. This will allow the server to get and periodically update its Lets Encrypt certificate. If this is not done, the server will generate an `ACCES: permission denied` exception.

View file

@ -352,3 +352,11 @@ wpfhwacceleration (ON|OFF|STATUS)
``` ```
zip (output file name), input1 [, input n] zip (output file name), input1 [, input n]
``` ```
## Agent msh options
You can find a full list of options for the agent [here](https://github.com/Ylianst/MeshAgent?tab=readme-ov-file#msh-format)
`skipmaccheck=1`: Will not regenerate the agents nodeid and cause duplication of the agent when the MAC address changes.
You can add options to your .msh on agent install with [this](https://github.com/Ylianst/MeshCentral/blob/15ff7d12a1e4e5d78936b473ea207b7e02b8ff26/meshcentral-config-schema.json#L2504)

View file

@ -14,16 +14,28 @@
![agent invite code](images/assistant_agent_code.png) ![agent invite code](images/assistant_agent_code.png)
## Agent Invitation Link ## Agent Invitation
Click on the 'Invite' button next to the device group name to access it.
For web page customization: ### Link Invitation
For link invitation web page customization:
1. Alongside `meshcentral-data` create a folder called `meshcentral-web` 1. Alongside `meshcentral-data` create a folder called `meshcentral-web`
2. Create a `views` folder in it and copy the file `node_modules/meshcentral/views/invite.handlebars` into it. 2. Create a `views` folder in it and copy the file `node_modules/meshcentral/views/invite.handlebars` into it.
3. That copy will be served instead of the default one, you can customize as you want. 3. That copy will be served instead of the default one, so you can customize it as you want.
![agent invite code](images/assistant_invitation_link.png) ![agent invite code](images/assistant_invitation_link.png)
### Email Invitation
This option will show up if you have an SMTP email server set up with MeshCentral.
For invitation email customization:
1. Alongside `meshcentral-data` create a folder called `meshcentral-web`
2. Create an `emails` folder in it and copy the files `node_modules/meshcentral/emails/mesh-invite.txt` and `node_modules/meshcentral/emails/mesh-invite.html` into it.
3. These copies will be used instead of the default ones, so you can customize them as you want.
![email-invitation](images/email-invitation.png)
## Email notification ## Email notification
You can also get an email notification when someone clicks the "Request Help" button in the Assistant agent. You can also get an email notification when someone clicks the "Request Help" button in the Assistant agent.

View file

@ -73,7 +73,7 @@ When doing sign/unsign, you can also change resource properties of the generated
## Automatic Agent Code Signing ## 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"> <div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/qMAestNgCwc" frameborder="0" allowfullscreen></iframe> <iframe width="320" height="180" src="https://www.youtube.com/embed/qMAestNgCwc" frameborder="0" allowfullscreen></iframe>

View file

@ -1,6 +1,6 @@
# Customization # 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"> <div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/xUZ1w9RSKpQ" frameborder="0" allowfullscreen></iframe> <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 ## 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.
![](images/2022-05-19-00-38-51.png) ![](images/2022-05-19-00-38-51.png)
@ -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.
![](images/2022-05-19-00-39-35.png) ![](images/2022-05-19-00-39-35.png)
@ -51,6 +51,17 @@ If, for example, MeshCentral is running on a Raspberry Pi. You may want to put a
This is great to personalize the look of the server within the web site. This is great to personalize the look of the server within the web site.
### Customizing Web Icons
MeshCentral lets you change the icons for different devices shown in the Web User Interface. To do this the proper way, you should make a new folder called `meshcentral-web` in the main directory, where you find other folders like `meshcentral-data`, `meshcentral-backup`, `meshcentral-files`, and `node-modules`. Inside `meshcentral-web`, make another folder named `public` and copy the entire `node_modules/meshcentral/public/images` folder into this new `meshcentral-web/public` folder and then edit the files in `meshcentral-web/public/images/`. This step is suggested because if MeshCentral updates, it might delete any changes in `node_modules`. But, changes in `meshcentral-web` will stay safe, and MeshCentral will use these files instead of the originals in `node_modules`.
To update device icons, you need to edit these files: `meshcentral-web/public/images/webp/iconsXX.webp` (`icons16.webp`, `icons32.webp`, `icons50.webp`, `icons100.webp`), and `meshcentral-web/public/images/iconsXX.png` (`icons16.png`, `icons32.png`, `icons50.png`, `icons64.png`, `icons100.png`) and the corresponding `meshcentral-web/public/images/icons256-X-1.png`. Make sure to keep the resolution of these files as it is.
By following these steps, you can customize any icon in MeshCentral. Just find and change the corresponding image files in the `meshcentral-web/public/images` folder. Similarly, you can also move other folders from `node_modules/meshcentral` to `meshcentral-web` while keeping the original folder structure. This allows you to modify other parts of MeshCentral too, like the `.handlebars` templates for the web interface. Simply copy files from `node_modules/meshcentral/views` to `meshcentral-web/views` and make your changes in `meshcentral-web`. This lets you match MeshCentral's look to your company's brand or your own style.
![](images/custom-web-icons.png)
### Customizing Agent Invitation
Agents can be invited by public link or via email. [Click Here](assistant.md#agent-invitation) to see details.
## Agent Branding ## Agent Branding
You can customize the Agent to add your own logo, change the title bar, install text, the service name, or even colors! You can customize the Agent to add your own logo, change the title bar, install text, the service name, or even colors!

View file

@ -23,7 +23,7 @@ Make sure you understand how MeshCentral works with your browser using chrome de
### Understanding node and paths ### 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 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. 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 mkdir c:\meshcentral
@ -50,11 +50,11 @@ node node_modules\meshcentral
node node_modules\meshcentral --install 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 ### 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 ```json
{ {
@ -86,14 +86,59 @@ node node_modules/meshcentral --stop
### Port Troubleshooting on server ### 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. 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 ### 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 To log all database queries, change log_statement in /etc/postgresql/13/main/postgresql.conf

View file

@ -66,3 +66,18 @@ To be able to transfer files
To be able to control keyboard and mouse To be able to control keyboard and mouse
![](images/2023-11-29-12-58-36.png) ![](images/2023-11-29-12-58-36.png)
## 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!
Unfortunately, MeshCentral doesn't always work with CloudFlare's Proxy DNS Mode.
The fix is to simply set the 'Proxy Status' to OFF inside your DNS A Record, within the CloudFlare control panel.
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
There is currently a PINNED GitHub issue about this [here](https://github.com/Ylianst/MeshCentral/issues/5302)

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -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) 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)
@ -213,7 +213,7 @@ As indicated before, the settings section of the config.json is equivalent to pa
| RedirPort | This is the port for redirecting traffic in the web server. When the server is configured with HTTPS, users that uses HTTP will be redirected to HTTPS. Port 80 is the default port. So, redirection will happen from port 80 to port 443. | | RedirPort | This is the port for redirecting traffic in the web server. When the server is configured with HTTPS, users that uses HTTP will be redirected to HTTPS. Port 80 is the default port. So, redirection will happen from port 80 to port 443. |
| MpsPort | Port for Intel" AMT Management Presence Server to receive Intel" AMT CIRA (Client Initiated Remote Access) connections. The default is port 4433. This port is disabled in LAN mode. If user don"t plan on using Intel" AMT for management, this port can be left as-is. | | MpsPort | Port for Intel" AMT Management Presence Server to receive Intel" AMT CIRA (Client Initiated Remote Access) connections. The default is port 4433. This port is disabled in LAN mode. If user don"t plan on using Intel" AMT for management, this port can be left as-is. |
| TLSOffload | By default this option is set to "false". If set to "true", server will run both web port and the Intel AMT MPS port without TLS with the assumption that a TLS offloading is taking care of this task. For further details, see the "TLS Offloading" section. This option can also be set to the IP address of the reverse-proxy in order to indicate to MeshCental to only trust HTTP X-Forwarded headers coming from this IP address. See the "Reverse-Proxy Setup" section for an example. | | TLSOffload | By default this option is set to "false". If set to "true", server will run both web port and the Intel AMT MPS port without TLS with the assumption that a TLS offloading is taking care of this task. For further details, see the "TLS Offloading" section. This option can also be set to the IP address of the reverse-proxy in order to indicate to MeshCental to only trust HTTP X-Forwarded headers coming from this IP address. See the "Reverse-Proxy Setup" section for an example. |
| SelfUpdate | When set to "true" the server will check for a new version and attempt to self-update automatically a bit after midnight local time every day. For this to work, the server needs to run with sufficient permissions to overwrite its own files. If you run the server with more secure, restricted privileges, this option should not be used. If set to a specific version such as "0.2.7-g" when the server will immediately update to the specified version on startup if it"s not already at this version. | | SelfUpdate | When set to "true" the server will check for a new version and attempt to self-update automatically a bit after midnight local time every day. If set to a specific version such as "1.1.21" the server will immediately update to the specified version on startup if it's not already at this version. |
| SessionKey | This is the encryption key used to secure the user"s login session. It will encrypt the browser cookie. By default, this value is randomly generated each time the server starts. If many servers are used with a load balancer, all servers should use the same session key. In addition, one can set this key so that when the server restarts, users do not need to re-login to the server. | | SessionKey | This is the encryption key used to secure the user"s login session. It will encrypt the browser cookie. By default, this value is randomly generated each time the server starts. If many servers are used with a load balancer, all servers should use the same session key. In addition, one can set this key so that when the server restarts, users do not need to re-login to the server. |
| Minify | Default value is 0, when set to 1 the server will serve "minified" web pages, that is, web pages that have all comments, white spaces and other unused characters removed. This reduces the data size of the web pages by about half and reduced the number requests made by the browser. The source code of the web page will not be easily readable, adding "&nominify=1" at the end of the URL will override this option. | | Minify | Default value is 0, when set to 1 the server will serve "minified" web pages, that is, web pages that have all comments, white spaces and other unused characters removed. This reduces the data size of the web pages by about half and reduced the number requests made by the browser. The source code of the web page will not be easily readable, adding "&nominify=1" at the end of the URL will override this option. |
| User | Specify a username that browsers will be automatically logged in as. Useful to skip the login page and password prompts. Used heavily during development of MeshCentral. | | User | Specify a username that browsers will be automatically logged in as. Useful to skip the login page and password prompts. Used heavily during development of MeshCentral. |
@ -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. 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 accounts 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.
![](images/2022-05-19-00-00-05.png)
The password recovery flow when “Reset Account” is triggered at the login page.
![](images/2022-05-19-00-00-18.png)
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 users 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. Its in the account settings, security section:
![](images/2022-05-19-00-01-19.png)
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 cant be used, its 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"
![](images/gc-newproject.png)
Click on the "OAuth Consent Screen" link, Under "APIs and Services" from the left hand menu:
![](images/gc-oauthconsent.png)
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.
![](images/OAuth-Internal-External.png)
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:
![](images/gc-oauthscopes.png)
Once this is complete, the next step will be to add credentials.
![](images/gc-oauthcredentials.png)
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
![](images/gc-playground.webp)
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.
![](images/2022-05-19-00-01-43.png)
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.
![](images/In-production.png)
## Database ## 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. 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. | | --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. | | --dbshowconfigfile (filename) | Show the content of a specified filename from the database. --configkey is required. |
| --dbdeleteconfigfiles | Delete all configuration files from the database. | | --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. | | --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. | | --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: 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" 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 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 accounts 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.
![](images/2022-05-19-00-00-05.png)
The password recovery flow when “Reset Account” is triggered at the login page.
![](images/2022-05-19-00-00-18.png)
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 users 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. Its in the account settings, security section:
![](images/2022-05-19-00-01-19.png)
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 cant be used, its 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.
![](images/2022-05-19-00-01-43.png)
## Embedding MeshCentral ## Embedding MeshCentral
@ -827,6 +901,12 @@ In this example, we will:
- MeshCentral will read the NGINX web certificate so agents will perform correct server authentication. - MeshCentral will read the NGINX web certificate so agents will perform correct server authentication.
- NGINX will be setup with long timeouts, because agents have long standard web socket connections. - NGINX will be setup with long timeouts, because agents have long standard web socket connections.
!!!note
With SELinux, NGINX reverse proxy requires 'setsebool -P httpd_can_network_relay 1'
Caution: httpd_can_network_relay only allows certain ports
Confirm you are using ports from this subset in MeshCentral
If you want to use a different port then you will need to add it to http_port_t
Lets get started by configuring MeshCentral with the following values in config.json: Lets get started by configuring MeshCentral with the following values in config.json:
```json ```json
@ -1198,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> <iframe width="320" height="180" src="https://www.youtube.com/embed/-WKY8Wy0Huk" frameborder="0" allowfullscreen></iframe>
</div> </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 ## Server Backup & Restore
Its 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. Its 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.
@ -1652,7 +1734,40 @@ The callback URL will be of the form “https://(servername)/auth-saml-callback
Enabling SAML will require MeshCentral to install extra modules from NPM, so depending on your server configuration, you may need to run MeshCentral once manually. Enabling SAML will require MeshCentral to install extra modules from NPM, so depending on your server configuration, you may need to run MeshCentral once manually.
!!!note !!!note
MeshCentral only supports "POST". [For example Authentik's](https://github.com/Ylianst/MeshCentral/issues/4725) default setting is to use "Redirect" as a "Service Provider Binding". MeshCentral only supports "POST". [For example Authentik's](https://github.com/Ylianst/MeshCentral/issues/4725) default setting is to use "Redirect" as a "Service Provider Binding".
### Generic OpenID Connect Setup
Generally, if you are using an IdP that supports OpenID Connect (OIDC), you can use a very basic configuration to get started, and if needed, add more specific or advanced configurations later. Here is what your config file will look like with a basic, generic, configuration.
``` json
{
"settings": {
"cert": "mesh.your.domain",
"port": 443,
"sqlite3": true
},
"domains": {
"": {
"title": "Mesh",
"title2": ".Your.Domain",
"authStrategies": {
"oidc": {
"issuer": "https://sso.your.domain",
"clientid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"clientsecret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"newAccounts": true
}
}
}
}
}
```
As you can see, this is roughly the same as all the other OAuth2 based authentication strategies. These are the basics you need to get started using OpenID Connect because it's still authenticating with OAuth2. If you plan to take advantage of some of the more advanced features provided by this strategy you should consider reading the [additional strategy documentation](./openidConnectStrategy.md).
> NOTE: MeshCentral will use `https://mesh.your.domain/auth-oidc-callback` as the default redirect uri.
## Improvements to MeshCentral ## Improvements to MeshCentral
In 2007, the first version of MeshCentral was built. We will refer to it as “MeshCentral1”. When MeshCentral1 was designed, HTML5 did not exist and web sockets where not implemented in any of the major browsers. Many design decisions were made at the time that are no longer optimal today. With the advent of the latest MeshCentral, MeshCentral1 is no longer supported and MeshCentral v2 has been significantly redesigned and mostly re-written based of previous version. Here is a list of improvements made in MeshCentral when compared with MeshCentral1: In 2007, the first version of MeshCentral was built. We will refer to it as “MeshCentral1”. When MeshCentral1 was designed, HTML5 did not exist and web sockets where not implemented in any of the major browsers. Many design decisions were made at the time that are no longer optimal today. With the advent of the latest MeshCentral, MeshCentral1 is no longer supported and MeshCentral v2 has been significantly redesigned and mostly re-written based of previous version. Here is a list of improvements made in MeshCentral when compared with MeshCentral1:
@ -1790,4 +1905,4 @@ MeshCentral has built-in web-based integration of SSH in the "Terminal" tab and
<div class="video-wrapper"> <div class="video-wrapper">
<iframe width="320" height="180" src="https://www.youtube.com/embed/7qAbl2OuZEU" frameborder="0" allowfullscreen></iframe> <iframe width="320" height="180" src="https://www.youtube.com/embed/7qAbl2OuZEU" frameborder="0" allowfullscreen></iframe>
</div> </div>

View file

@ -0,0 +1,671 @@
# Using the OpenID Connect Strategy on MeshCentral
## Overview
### Introduction
There is a lot of information to go over, but first, why OpenID Connect?
Esentially its because its both based on a industry standard authorization protocol, and is becoming an industry standard authentication protocol. Put simply it's reliable and reusable, and we use OpenID Connect for exactly those reasons, almost every everyone does, and we want to be able to integrate with almost anyone. This strategy allows us to expand the potential of MeshCentral through the potential of OpenID Connect.
In this document, we will learn about the OpenID Connect specification at a high level, and then use that information to configure the OpenID Connect strategy for MeshCentral using a generic OpenID Connect compatible IdP. After that we will go over some advanced configurations and then continue by explaining how to use the new presets for popular IdPs, specifically Google or Azure. Then we will explore the configuration and usage of the groups feature.
> ATTENTION: As of MeshCentral `v1.1.22` there are multiple config options being depreciated. Using any of the old configs will only generate a warning in the authlog and will not stop you from using this strategy at this time. If there is information found in both the new and old config locations the new config location will be used. We will go over the specifics later, now lets jump in.
### Chart of Frequently Used Terms and Acronyms
| Term | AKA | Descriptions |
| --- | --- | --- |
| OAuth 2.0 | OAuth2 | OAuth 2.0 is the industry-standard protocol for user *authorization*. |
| OpenID Connect | OIDC | Identity layer built on top of OAuth2 for user *authentication*. |
| Identity Provider | IdP | The *service used* to provide authentication and authorization. |
| Preset Configs | Presets | Set of *pre-configured values* to allow some specific IdPs to connect correctly. |
| OAuth2 Scope | Scope | A flag *requesting access* to a specific resource or endpoint |
| OIDC Claim | Claim | A *returned property* in the user info provided by your IdP |
| User Authentication | AuthN | Checks if you *are who you say you are*. Example: Username and password authentication |
| User Authorization | AuthZ | Check if you have the *permissions* required to access a specific resource or endpoint |
### OpenID Connect Technology Overview
OpenID Connect is a simple identity layer built on top of the OAuth2 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an “Authorization Server”, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.
OpenID Connect allows clients of all types, including Web-based, mobile, and JavaScript clients, to request and receive information about authenticated sessions and end-users. The specification suite is extensible, allowing participants to use optional features such as encryption of identity data, discovery of OpenID Providers, and logout, when it makes sense for them.
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*
Generally, if you are using an IdP that supports OIDC, you can use a very basic configuration to get started, and if needed, add more specific or advanced configurations later. Here is what your config file will look like with a basic, generic, configuration.
### *Basic Config File Example*
``` json
{
"settings": {
"cert": "mesh.your.domain",
"port": 443,
"sqlite3": true
},
"domains": {
"": {
"title": "MeshCentral",
"title2": "Your sub-title",
"authStrategies": {
"oidc": {
"issuer": "https://sso.your.domain",
"clientid": "2d5685c5-0f32-4c1f-9f09-c60e0dbc948a",
"clientsecret": "7PiGSLSLL4e7NGi67KM229tfK7Z7TqzQ",
"newAccounts": true
}
}
}
}
}
```
As you can see, this is roughly the same as all the other OAuth2 based authentication strategies. These are the basics you need to get started, however, if you plan to take advantage of some of the more advanced features provided by this strategy, you'll need to keep reading.
In this most basic of setups, you only need the URL of the issuer, as well as a client ID and a client secret. Notice in this example that the callback URL (or client redirect uri) is not configured, thats because MeshCentral will use `https://mesh.your.domain/auth-oidc-callback` as the default. Once you've got your configuration saved, restart MeshCentral and you should see an OpenID Connect Single Sign-on button on the login screen.
> WARNING: The redirect endpoint must EXACTLY match the value provided to your IdP or your will deny the connection.
> ATTENTION: You are required to configure the cert property in the settings section for the default domain, and configure the dns property under each additional domain.
## Advanced Options
### Overview
There are plenty of options at your disposal if you need them. In fact, you can configure any property that node-openid-client supports. The openid-client module supports far more customization than I know what to do with, if you want to know more check out [node-openid-client on GitHub](https://github.com/panva/node-openid-client) for expert level configuration details. There are plenty of things you can configure with this strategy and there is a lot of decumentation behind the tools used to make this all happen. I strongly recommend you explore the [config schema](https://github.com/Ylianst/MeshCentral/blob/master/meshcentral-config-schema.json), and if you have a complicated config maybe check out the [openid-client readme](https://github.com/panva/node-openid-client/blob/main/docs/README.md). Theres a list of resources at the end if you want more information on any specific topics. In the meantime, lets take a look at an example of what your config file could look with a slightly more complicated configuration, including multiple manually defined endpoints.
#### *Advanced Config File Example*
``` json
{
"settings": {
"cert": "mesh.your.domain",
"port": 443,
"redirPort": 80,
"AgentPong": 300,
"TLSOffload": "192.168.1.50",
"SelfUpdate": false,
"AllowFraming": false,
"sqlite3": true,
"WebRTC": true
},
"domains": {
"": {
"title": "Mesh",
"title2": ".Your.Domain",
"orphanAgentUser": "~oidc:e48f8ef3-a9cb-4c84-b6d1-fb7d294e963c",
"authStrategies": {
"oidc": {
"issuer": {
"issuer": "https://sso.your.domain",
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
"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/auth-oidc-callback",
"post_logout_redirect_uri": "https://mesh.your.domain/login",
"token_endpoint_auth_method": "client_secret_post",
"response_types": "authorization_code"
},
"custom": {
"scope": [ "openid", "profile", "read.EmailAlias", "read.UserProfile" ],
"preset": null
},
"groups": {
"recursive": true,
"required": ["Group1", "Group2"],
"siteadmin": ["GroupA", "GroupB"],
"revokeAdmin": true,
"sync": {
"filter": ["Group1", "GroupB", "OtherGroup"]
},
"claim": "GroupClaim",
"scope": "read.GroupMemberships"
},
"logouturl": "https://sso.your.domain/logout?r=https://mesh.your.domain/login",
"newAccounts": true
},
{...}
}
}
}
}
```
### "Issuer" Options
#### *Introduction*
In the advanced example config above, did you notice that the issuer property has changed from a *string* to an *object* compared to the basic example? This not only allows for much a much smaller config footprint when advanced issuer options are not required, it successfully fools you in to a false sense of confidence early on in this document. If you are manually configuring the issuer endpoints, keep in mind that MeshCentral will still attempt to discover **ALL** issuer information. Obviously if you manually configure an endpoint, it will be used even if the discovered information is different from your config.
> NOTE: If you are using a preset, you dont need to define an issuer. If you do, the predefined information will be ignored.
#### *Common Config Chart*
| Name | Description | Default | Example | Required |
| --- | --- | --- | --- | --- |
| `issuer` | The primary URI that represents your Identity Providers authentication endpoints. | N/A | `"issuer": "https://sso.your.domain"`<br/>`"issuer": { "issuer": "https://sso.your.domain" }` | Unless using preset. |
#### *Advanced Config Example*
``` json
"issuer": {
"issuer": "https://sso.your.domain",
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
"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 end_session_endpoint are the two main ones you want to setup.
#### *Schema*
``` json
"issuer": {
"type": ["string","object"],
"format": "uri",
"description": "Issuer options. Requires issuer URI (issuer.issuer) to discover missing information unless using preset",
"properties": {
"issuer": { "type": "string", "format": "uri", "description": "URI of the issuer." },
"authorization_endpoint": { "type": "string", "format": "uri" },
"token_endpoint": { "type": "string", "format": "uri" },
"jwks_uri": { "type": "string", "format": "uri" },
"userinfo_endpoint": { "type": "string", "format": "uri" },
"revocation_endpoint": { "type": "string", "format": "uri" },
"introspection_endpoint": { "type": "string", "format": "uri" },
"end_session_endpoint": {
"type": "string",
"format": "uri",
"description": "URI to direct users to when logging out of MeshCentral.",
"default": "this.issuer/logout"
},
"registration_endpoint": { "type": "string", "format": "uri" },
"token_endpoint_auth_methods_supported": { "type": "string" },
"token_endpoint_auth_signing_alg_values_supported": { "type": "string" },
"introspection_endpoint_auth_methods_supported": { "type": "string" },
"introspection_endpoint_auth_signing_alg_values_supported": { "type": "string" },
"revocation_endpoint_auth_methods_supported": { "type": "string" },
"revocation_endpoint_auth_signing_alg_values_supported": { "type": "string" },
"request_object_signing_alg_values_supported": { "type": "string" },
"mtls_endpoint_aliases": {
"type":"object",
"properties": {
"token_endpoint": { "type": "string", "format": "uri" },
"userinfo_endpoint": { "type": "string", "format": "uri" },
"revocation_endpoint": { "type": "string", "format": "uri" },
"introspection_endpoint": { "type": "string", "format": "uri" }
}
}
},
"additionalProperties": false
},
```
### "Client" Options
#### *Introduction*
There are just about as many option as possible here since openid-client also provides a Client class, because of this you are able to manually configure the client how ever you need. This includes setting your redirect URI to any available path, for example, if I was using the "google" preset and wanted to have Google redirect me back to "https://mesh.your.domain/oauth2/oidc/redirect/givemebackgooglemusicyoujerks", MeshCentral will now fully support you in that. One of the other options is the post logout redirect URI, and it is exactly what it sounds like. After MeshCentral logs out a user using the IdPs end session endpoint, it send the post logout redirect URI to your IdP to forward the user back to MeshCentral or to an valid URI such as a homepage.
> NOTE: The client object is required, however an exception would be with using old configs, which will be discussed later.
#### *Common Configs*
| Name | Description | Default | Example | Required |
| --- | --- | --- | --- | --- |
| `client_id` | The client ID provided by your Identity Provider (IdP) | N/A | `bdd6aa4b-d2a2-4ceb-96d3-b3e23cd17678` | `true` |
| `client_secret` | The client secret provided by your Identity Provider (IdP) | N/A | `vUg82LJ322rp2bvdzuVRh3dPn3oVo29m` | `true` |
| `redirect_uri` | "URI your IdP sends you after successful authorization. | `https://mesh.your.domain/auth-oidc-callback` | `https://mesh.your.domain/oauth2/oidc/redirect` | `false` |
| `post_logout_redirect_uri` | URI for your IdP to send you after logging out of IdP via MeshCentral. | `https://mesh.your.domain/login` | `https://site.your.other.domain/login` | `false` |
#### *Advanced Config Example*
``` json
"client": {
"client_id": "00b3875c-8d82-4238-a8ef-25303fa7f9f2",
"client_secret": "7PP453H577xbFDCqG8nYEJg8M3u8GT8F",
"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"
},
```
#### *Required and Commonly Used Configs*
There are many available options you can configure but most of them go unused. Although there are a few *commonly used* properties. The first two properties, `client_id` and `client_secret` are required. The next one `redirect_uri` is used to setup a custom URI for the redirect back to MeshCentral after being authenicated by your IdP. The `post_logout_redirect_uri` property is used to tell your IdP where to send you after being logged out. These work in conjunction with the issuers `end_session_url` to automatically fill in any blanks in the config.
#### *Schema*
``` json
"client": {
"type": "object",
"description": "OIDC Client Options",
"properties": {
"client_id": {
"type": "string",
"description": "REQUIRED: The client ID provided by your Identity Provider (IdP)"
},
"client_secret": {
"type": "string",
"description": "REQUIRED: The client secret provided by your Identity Provider (IdP)"
},
"redirect_uri": {
"type": "string",
"format": "uri",
"description": "URI your IdP sends you after successful authorization. This must match what is listed with your IdP. (Default is https://[currentHost][currentPath]/auth-oidc-callback)"
},
"post_logout_redirect_uri": {
"type": "string",
"format": "uri",
"description": "URI for your IdP to send you after logging out of IdP via MeshCentral.",
"default": "https:[currentHost][currentPath]/login"
},
"id_token_signed_response_alg": { "type": "string", "default": "RS256" },
"id_token_encrypted_response_alg": { "type": "string" },
"id_token_encrypted_response_enc": { "type": "string" },
"userinfo_signed_response_alg": { "type": "string" },
"userinfo_encrypted_response_alg": { "type": "string" },
"userinfo_encrypted_response_enc": { "type": "string" },
"response_types": { "type": ["string", "array"], "default": ["code"] },
"default_max_age": { "type": "number" },
"require_auth_time": { "type": "boolean", "default": false },
"request_object_signing_alg": { "type": "string" },
"request_object_encryption_alg": { "type": "string" },
"request_object_encryption_enc": { "type": "string" },
"token_endpoint_auth_method": {
"type": "string",
"default": "client_secret_basic",
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
},
"introspection_endpoint_auth_method": {
"type": "string",
"default": "client_secret_basic",
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
},
"revocation_endpoint_auth_method": {
"type": "string",
"default": "client_secret_basic",
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
},
"token_endpoint_auth_signing_alg": { "type": "string" },
"introspection_endpoint_auth_signing_alg": { "type": "string" },
"revocation_endpoint_auth_signing_alg": { "type": "string" },
"tls_client_certificate_bound_access_tokens": { "type": "boolean" }
},
"required": [ "client_id", "client_secret" ],
"additionalProperties": false
},
```
### "Custom" Options
#### *Introduction*
These are all the options that dont fit with the issuer or client, including the presets. The presets define more than just the issuer URL used in discovery, they also define API endpoints, and specific ways to assemble your data. You are able to manually override most of the effects of the preset, but not all. You are able to manually configure the *scope* of the authorization request though, as well as choose which claims to use if your IdP uses something other than the defaults.
> NOTE: The scope must be a string, an array of strings, or a space separated list of scopes as a single string.
#### *Common Config Chart*
| Name | Description | Default | Example | Required |
| -------- | ------------------------------------------------ | --------------------------------------------------------- | ----------------------------------- | -------- |
| `scope` | A list of scopes to request from the issuer. | `"openid profile email"` | `["openid", "profile"]` | `false` |
| `claims` | A group of claims to use instead of the defaults | Defauts to name of property except that `uuid` used `sub` | `"claims": {"uuid": "unique_name"}` | `false` |
#### *Advanced Config Example*
``` json
"custom": {
"scope": [ "openid", "profile", "read.EmailAlias", "read.UserProfile" ],
"preset": null,
"claims": {
"name": "nameOfUser",
"email": "publicEmail"
}
},
```
> NOTE: You can `preset` to null if you want to explicitly disable presets.
#### *Required and Commonly Used Configs*
As should be apparent by the name alone, the custom property does not need to be configured and is used for optional or advanced configurations. With that said, lets look at few common options strategy will default to using the `openid`, `profile`, and `email` scopes to gather the required information about the user, if your IdP doesn't support or require all these, you can set up the scope manually. Combine that with the ability to set the group scope and you can end up with an entirely custom scope being sent to your IdP. Not to mention the claims property, which allows you to pick and choose what claims to use to gather your data in case you have issues with any of the default behaviors of OpenID Connect and your IdP. This is also where you would set the preset and any values required by the presets.
#### *Schema*
``` json
"custom": {
"type": "object",
"properties": {
"scope": {
"type": ["string", "array"],
"description": "A list of scopes to request from the issuer.",
"default": "openid profile email",
"examples": ["openid", ["openid", "profile"], "openid profile email", "openid profile email groups"]
},
"claims": {
"type": "object",
"properties": {
"email": { "type": "string" },
"name": { "type": "string" },
"uuid": { "type": "string" }
}
},
"preset": { "type": "string", "enum": ["azure", "google"]},
"tenant_id": { "type": "string", "description": "REQUIRED FOR AZURE PRESET: Tenantid for Azure"},
"customer_id": { "type": "string", "description": "REQUIRED FOR GOOGLE PRESET IF USING GROUPS: Customer ID from Google, should start with 'C'."}
},
"additionalProperties": false
},
```
### "Groups" Options
#### *Introduction*
The groups option allows you to use the groups you already have with your IdP in MeshCentral in a few ways. First you can set a group that the authorized user must be in to sign in to MeshCentral. You can also allow users with the right memberships automatic admin privlidges, and there is even an option to revoke privlidges if the user is NOT in the admin group. Besides these filters, you can filter the sync property to mirror only certain groups as MeshCentral User Groups, dynamically created as the user logs in. You can of course simply enable sync and mirror all groups from your IdP as User Groups. Additionally you can define the scope and claim of the groups for a custom setup, again allowing for a wide range of IdPs to be used, even without a preset.
#### *Common Config Chart*
| Name | Description | Default | Example | Required |
| --- | --- | --- | --- | --- |
| `sync` | Allows you to mirror user groups from your IdP. | `false` | `"sync": { "filter": ["Group1", "Group2"] }`<br/>`"sync": true` | `false` |
| `required` | Access is only granted to users who are a member<br/>of at least one of the listed required groups. | `undefined` | `"required": ["Group1", "Group2"]` | `false` |
| `siteadmin` | Full site admin priviledges will be granted to users<br/>who are a member of at least one of the listed admin groups | `undefined` | `"siteadmin": ["Group1", "Group2"]` | `false` |
| `revokeAdmin` | If true, admin privileges will be revoked from users<br/>who arent a member of at least one of the listed admin groups. | `true` | `"revokeAdmin": false` | `false` |
#### *Advanced Config Example*
``` json
"groups": {
"recursive": true,
"required": ["Group1", "Group2"],
"siteadmin": ["GroupA", "GroupB"],
"revokeAdmin": false,
"sync": {
"filter": ["Group1", "GroupB", "OtherGroup"]
},
"claim": "GroupClaim",
"scope": "read.GroupMemberships"
},
```
#### *Required and Commonly Used Configs*
As you can see in the schema below, there aren't any required properties in the groups object, however there are some commonly used ones. The first, and maybe most commonly used one, is the sync property. The sync property mirrors IdP provided groups into MeshCentral as user groups. You can then configure access as required to those groups, and as users log in, they will be added to the now existing groups if they are a member. You also have other options like using a custom *scope* or *claim* to get your IdP communicating with MeshCentral properly, without the use of preset configs. You also can set the required property if you need to limit authorization to users that are a member of at least one of the groups you set. or the siteadmin property to grant admin privilege, with the revokeAdmin property available to allow revoking admin rights also.
#### *Schema*
``` json
"groups": {
"type": "object",
"properties": {
"recursive": {
"type": "boolean",
"default": false,
"description": "When true, the group memberships will be scanned recursively."
},
"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."
},
"revokeAdmin": {
"type": "boolean",
"default": false,
"description": "If true, admin privileges will be revoked from users who are NOT a member of at least one of the listed admin groups."
},
"sync": {
"type": [ "boolean", "object" ],
"default": false,
"description": "If true, all groups found during user login are mirrored into MeshCentral user groups.",
"properties": {
"filter": {
"type": [ "string", "array" ],
"description": "Only groups listed here are mirrored into MeshCentral user groups."
}
}
},
"scope": { "type": "string", "default": "groups", "description": "Custom scope to use." },
"claim": { "type": "string", "default": "groups", "description": "Custom claim to use." }
},
"additionalProperties": false
}
```
## Preset OpenID Connect Configurations
### Overview
#### *Introduction*
Google is a blah and is used by tons of blahs as its so great. Lets move on.
#### *Common Config Chart*
> NOTE: All settings directly related to presets are in the custom section of the config.
| Name | Description | Example | Required |
| --- | --- | --- | --- |
| `preset` | Manually enable the use of a preset. | `"preset": "google"`<br/>`"preset": "azure"` | `false` |
| `customer_id` | Customer ID of the Google Workspaces instace you<br/>plan to use with the groups feature.| `"customer_id": ["Group1", "Group2"]` | If `google` preset is used with `groups` feature |
| `tenant_id` | Tenant ID from Azure AD, this is required to use<br/>the `azure` preset as it is part of the issuer url. | `"siteadmin": ["Group1", "Group2"]` | `false` |
### Google Preset
#### *Prerequisites*
> Check out this [documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect) to get ready before we start.
#### *Basic Config Example*
``` json
"oidc": {
"client": {
"client_id": "268438852161-r8xa7qxwf3rr0shp1xnpgmm70bnag21p.apps.googleusercontent.com",
"client_secret": "ETFWBX-gFEaxfPXs1tWmAOkuWDFTgoL3nwh"
}
}
```
#### *Specifics*
If you notice above I forgot to add any preset related configs, however because google tags the client ID we can detect that and automatically use the google preset. The above config is tested, the sentive data has been scrambled of course. That said, you would normally use this preset in more advaced setups, let take a look at an example.
#### *Advanced Example with Groups*
``` json
"oidc": {
"client": {
"client_id": "424555768625-k7ub3ovqs0yp7mfo0usvyyx51nfii61c.apps.googleusercontent.com",
"client_secret": "QLBCQY-nRYmjnFWv3nKyHGmwQEGLokP6ldk"
},
"custom": {
"preset": "google",
"customer_id": "C46kyhmps"
},
"groups": {
"siteadmin": ["GroupA", "GroupB"],
"revokeAdmin": true,
"sync": true
},
"callbackURL": "https://mesh.your.domain/auth-oidc-google-callback"
},
```
#### *Customer ID and Groups*
As always, the client ID and secret are required, the customer ID on the other hand is only required if you plan to take advantage of the groups function *and* the google preset. This also requires you have a customer ID, if you have do, it is available in the Google Workspace Admin Console under Profile->View. Groups work the same as they would with any other IdP but they are pulled from the Workspace groups.
#### *Schema*
```json
"custom": {
"type": "object",
"properties": {
"preset": { "type": "string", "enum": ["azure", "google"]},
"customer_id": { "type": "string", "description": "Customer ID from Google, should start with 'C'."}
},
"additionalProperties": false
},
```
### Azure Preset
#### *Prerequisites*
To configure OIDC-based SSO, you need an Azure account with an active subscription. [Create an account](https://azure.microsoft.com/free/?WT.mc_id=A261C142F) for free. The account used for setup must be of the following roles: Global Administrator, Cloud Application Administrator, Application Administrator, or owner the service principal.
> Check this [documentation](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/add-application-portal-setup-oidc-sso) for more information.
#### *Basic Config Example*
``` json
"oidc": {
"client": {
"client_id": "a1gkl04i-40g8-2h74-6v41-2jm2o2x0x27r",
"client_secret": "AxT6U5K4QtcyS6gF48gndL7Ys22BL15BWJImuq1O"
},
"custom": {
"preset": "azure",
"tenant_id": "46a6022g-4h33-1451-h1rc-08102ga3b5e4"
}
}
```
#### *Specifics*
As with all other types of configuration for the OIDC strategy, the Azure preset requires a client ID and secret.The tenant ID is used as part of the issuer URI to make even the most basic AuthN requests so it is also required for the azure preset. besides that groups are available to the Azure preset as well as the recursive feature of groups. This allows you to search user groups recursively for groups they have membership in through other groups.
> NOTE: The Azure AD preset uses the Tenant ID as part of the issuer URI:<br>`"https://login.microsoftonline.com/"` + `strategy`.custom.tenant_id + `"/v2.0"`
#### *Advanced Example with Groups*
``` json
"oidc": {
"client": {
"client_id": "a1gkl04i-40g8-2h74-6v41-2jm2o2x0x27r",
"client_secret": "AxT6U5K4QtcyS6gF48gndL7Ys22BL15BWJImuq1O"
},
"custom": {
"preset": "azure",
"tenant_id": "46a6022g-4h33-1451-h1rc-08102ga3b5e4"
},
"groups": {
"recursive": true,
"siteadmin": ["GroupA", "GroupB"],
"revokeAdmin": true,
"sync": true
},
"callbackURL": "https://mesh.your.domain/auth-oidc-azure-callback"
},
```
#### *Schema*
```json
"custom": {
"type": "object",
"properties": {
"preset": { "type": "string", "enum": ["azure", "google"]},
"tenant_id": { "type": "string", "description": "Tenant ID from Azure AD."}
},
"additionalProperties": false
},
```
## Depreciated Properties
### Overview
#### Introduction
As of MeshCentral `v1.1.22` and the writing of this documentation, the node module that handles everything was changed from [passport-openid-connect](https://github.com/jaredhanson/passport-openidconnect) to [openid-client](https://github.com/panva/node-openid-client). As a result of this change, multiple properties in the config have been depcrecated; this means some options in the strategy arent being used anymore. These are often referred to as "old configs" by this documentation.
#### *Migrating Old Configs*
We upgraded but what about all the existing users, we couldn't just invalidate every config pre `v1.1.22`. So in an effort to allow greater flexibility to all users of MeshCentral, and what futures scholars will all agree was an obvious move, all the depreciated configs will continue working as expected. Using any of the old options will just generate a warning in the authlog and will not stop you from using this the OIDC strategy with outdated configs, however if both the equivalent new and old config are set the new config will be used.
#### *Old Config Example*
```json
"oidc": {
"newAccounts": true,
"clientid": "421326444155-i1tt4bsmk3jm7dri6jldekl86rfpg07r.apps.googleusercontent.com",
"clientsecret": "GNLXOL-kEDjufOCk6pIcTHtaHFOCgbT4hoi"
}
```
This example was chosen because I wanted to highlight an advantage of supporting these old configs long term, even in a depreciated status. That is, the ability to copy your existing config from one of the related strategies without making any changes to your config by using the presets. This allows you to test out the oidc strategy without commiting to anything, since the user is always appended with the strategy used to login. In this example, the config was originally a google auth strategy config, changing the `"google"` to `"oidc"` is all that was done to the above config, besides obsfuscation of course.
#### *Advcanced Old Config Example*
``` json
"oidc": {
"authorizationURL": "https://sso.your.domain/api/oidc/authorization",
"callbackURL": "https://mesh.your.domain/oauth2/oidc/callback",
"clientid": "tZiPTMDNuSaQPapAQJtwDXVnYjjhQybc",
"clientsecret": "vrQWspJxdVAxEFJdrxvxeQwWkooVcqdU",
"issuer": "https://sso.your.domain",
"tokenURL": "https://sso.your.domain/api/oidc/token",
"userInfoURL": "https://sso.your.domain/api/oidc/userinfo",
"logoutURL": "https://sso.your.domain/logout?rd=https://mesh.your.domain/login",
"groups": {
"recursive": true,
"required": ["Group1", "Group2"],
"siteadmin": ["GroupA", "GroupB"],
"sync": {
"filter": ["Group1", "GroupB", "OtherGroup"]
}
},
"newAccounts": true
},
```
#### *Upgrading to v1.1.22*
If you were already using a meticulusly configured oidc strategy, all of your configs will still be used. You will simply see a warning in the logs if any depreciated properties were used. If you check the authLog there are additional details about the old config and provide the new place to put that information. In this advanced config, even the groups will continue to work just as they did before without any user intervention when upgrading from a version of MeshCentral pre v1.1.22. There are no step to take and no action is needed, moving the configs to the new locations is completely optional at the moment.
# Links
https://cloud.google.com/identity/docs/reference/rest/v1/groups/list
https://www.onelogin.com/learn/authentication-vs-authorization
https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
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)

View file

@ -5,7 +5,7 @@
## Use Cases ## 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 ## List of publically available plugins
@ -17,10 +17,10 @@ Certain feature requests may not be suitable for all MeshCentral users and thus
>"plugins": { >"plugins": {
> "enabled": true > "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. 2. Log into MeshCentral as full administrator.
3. Go my `My Server` -> `Plugins`, hit the Download plugin button. 3. Go my `My Server` -> `Plugins`, then hit the Download plugin button.
4. A dialog opens requesting an URL, e.g. put in: <https://github.com/ryanblenis/MeshCentral-ScriptTask> 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. 5. The plugin pops up in the plugin list below the download button, you can now configure and enable/disable it.
# Plugins - Development & Hooks # Plugins - Development & Hooks
@ -30,7 +30,7 @@ Certain feature requests may not be suitable for all MeshCentral users and thus
## Overview ## 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: ## 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 | | 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) | | downloadUrl | Yes | string | the URL to a ZIP of the project (used for installation/upgrades) |
| repository | Yes | JSON object | contains the following attributes | | 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 | | repository.url | Yes | string | the URL to the project's repository |
| versionHistoryUrl | No | string | the URL to the project's versions/tags | | 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. | | 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. 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
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. 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.

View file

@ -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.

View file

@ -8,8 +8,18 @@
## Software Integration Tokens ## Software Integration Tokens
Currently, the login tokens in the user manual section 14.1 can't be tracked, deleted or revoked. They are generated with: !!!warning
You can only have a SINGLE loginTokenKey for your meshcentral server!<br>
So if you regenerate a loginTokenKey, the old one will be revoked/deleted!
You can create/view the Login Token Key with the following:
```bash ```bash
node meshcentral --loginTokenKey node node_modules/meshcentral --loginTokenKey
```
You can then reset/revoke/renew the Login Token Key with the following to create a new one:
```bash
node node_modules/meshcentral --loginTokenKey --loginTokenGen
``` ```

View file

@ -22,6 +22,7 @@ nav:
- 'Tips n Tricks': 'meshcentral/tipsntricks.md' - 'Tips n Tricks': 'meshcentral/tipsntricks.md'
- 'Messaging': 'messaging/index.md' - 'Messaging': 'messaging/index.md'
- 'Customization': 'meshcentral/customization.md' - 'Customization': 'meshcentral/customization.md'
- 'openidConnectStrategy': 'meshcentral/openidConnectStrategy.md'
- Design and Architecture: - Design and Architecture:
- design/index.md - design/index.md
@ -38,6 +39,9 @@ nav:
- Intel AMT: - Intel AMT:
- intelamt/index.md - intelamt/index.md
- How to Contribute:
- how-to-contribute/index.md
- Other: - Other:
- other/adfs_sso_guide.md - other/adfs_sso_guide.md
- other/meshcentral_satellite.md - other/meshcentral_satellite.md

View file

@ -1,20 +1,20 @@
<html><head></head><body><div>[[[SERVERNAME]]] - "[[[DEVICENAME]]]" Solicitud de ayuda</div> <html><head></head><body><div>[[[SERVERNAME]]] - "[[[DEVICENAME]]]" Solicitud de Ayuda</div>
<div style="font-family:Arial,Helvetica,sans-serif"> <div style="font-family:Arial,Helvetica,sans-serif">
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8"> <table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
<tbody><tr> <tbody><tr>
<td> <td>
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Solicitud de ayuda</b> <b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Solicitud de Ayuda</b>
</td> </td>
</tr> </tr>
</tbody></table> </tbody></table>
<p> <p>
Device "<b>[[[DEVICENAME]]]</b>" requested help. Dispositivo "<b>[[[DEVICENAME]]]</b>" ha pedido ayuda.
</p> </p>
<p> <p>
User: <b>[[[HELPUSERNAME]]]</b><br> Usuario: <b>[[[HELPUSERNAME]]]</b><br>
Request: <b>[[[HELPREQUEST]]]</b> Solicitud: <b>[[[HELPREQUEST]]]</b>
</p> </p>
<p> <p>
<a href="[[[SERVERURL]]]?viewmode=10&amp;gotonode=[[[NODEID]]]">haz clic aquí</a> to navigate to this device. <a href="[[[SERVERURL]]]?viewmode=10&amp;gotonode=[[[NODEID]]]">haz clic aquí</a> para navegar a este dispositivo.
</p> </p>
</div></body></html> </div></body></html>

View file

@ -1,7 +1,7 @@
[[[SERVERNAME]]] - Solicitud de ayuda del dispositivo [[[SERVERNAME]]] - Solicitud de ayuda con el Dispositivo
Device "[[[DEVICENAME]]]" requested assistance. Dispositivo "[[[DEVICENAME]]]" solicito asistencia.
User: "[[[HELPUSERNAME]]]" Usuario: "[[[HELPUSERNAME]]]"
Request: "[[[HELPREQUEST]]]" Solicitud: "[[[HELPREQUEST]]]"
[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]] [[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]

View file

@ -1,7 +1,6 @@
/** /**
* @description MeshCentral Firebase communication module * @description MeshCentral Firebase communication module
* @author Ylian Saint-Hilaire * @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2022
* @license Apache-2.0 * @license Apache-2.0
* @version v0.0.1 * @version v0.0.1
*/ */
@ -14,27 +13,31 @@
/*jshint esversion: 6 */ /*jshint esversion: 6 */
"use strict"; "use strict";
// Construct the Firebase object // Initialize the Firebase Admin SDK
module.exports.CreateFirebase = function (parent, senderid, serverkey) { module.exports.CreateFirebase = function (parent, serviceAccount) {
var obj = {};
// Import the Firebase Admin SDK
const admin = require('firebase-admin');
const obj = {};
obj.messageId = 0; obj.messageId = 0;
obj.relays = {}; obj.relays = {};
obj.stats = { obj.stats = {
mode: "Real", mode: 'Real',
sent: 0, sent: 0,
sendError: 0, sendError: 0,
received: 0, received: 0,
receivedNoRoute: 0, receivedNoRoute: 0,
receivedBadArgs: 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 // Setup logging
if (parent.config.firebase && (parent.config.firebase.log === true)) { if (parent.config.firebase && (parent.config.firebase.log === true)) {
obj.logpath = parent.path.join(parent.datapath, 'firebase.txt'); obj.logpath = parent.path.join(parent.datapath, 'firebase.txt');
@ -42,155 +45,108 @@ module.exports.CreateFirebase = function (parent, senderid, serverkey) {
} else { } else {
obj.log = function () { } obj.log = function () { }
} }
// Messages received from client (excluding receipts) // Function to send notifications
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'
obj.sendToDevice = function (node, payload, options, func) { obj.sendToDevice = function (node, payload, options, func) {
if (typeof node == 'string') { 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'); } }) 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 { } else {
obj.sendToDeviceEx(node, payload, options, func); obj.sendToDeviceEx(node, payload, options, func);
} }
} };
// Send an outbound push notification // Send an outbound push notification
obj.sendToDeviceEx = function (node, payload, options, func) { obj.sendToDeviceEx = function (node, payload, options, func) {
parent.debug('email', 'Firebase-sendToDevice'); if (!node || typeof node.pmt !== 'string') {
if ((node == null) || (typeof node.pmt != 'string')) return; func(0, 'error');
return;
}
obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options)); obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
// Fill in our lookup table // Fill in our lookup table
if (node._id != null) { tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } } if (node._id) {
tokenToNodeMap[node.pmt] = {
// Built the on-screen notification nid: node._id,
var notification = null; mid: node.meshid,
if (payload.notification) { did: node.domain
var notification = new Notification('ic_message') };
.title(payload.notification.title)
.body(payload.notification.body)
.build();
} }
// Build the message const message = {
var message = new Message('msg_' + (++obj.messageId)); token: node.pmt,
if (options.priority) { message.priority(options.priority); } notification: payload.notification,
if (payload.data) { for (var i in payload.data) { message.addData(i, payload.data[i]); } } data: payload.data,
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. android: {
if (notification) { message.notification(notification) } priority: options.priority || 'high',
message.build(); ttl: options.timeToLive ? options.timeToLive * 1000 : undefined
}
// Send the message };
function callback(result) {
if (result.getError() == null) { obj.stats.sent++; obj.log('Success'); } else { obj.stats.sendError++; obj.log('Fail'); } admin.messaging().send(message).then(function (response) {
callback.func(result.getMessageId(), result.getError(), result.getErrorDescription()) obj.stats.sent++;
} obj.log('Success');
callback.func = func; func(response);
parent.debug('email', 'Firebase-sending'); }).catch(function (error) {
xcs.sendNoRetry(message, node.pmt, callback); obj.stats.sendError++;
} obj.log('Fail: ' + error);
func(0, error);
});
};
// Setup a two way relay // Setup a two way relay
obj.setupRelay = function (ws) { obj.setupRelay = function (ws) {
// Select and set a relay identifier
ws.relayId = getRandomPassword(); 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; obj.relays[ws.relayId] = ws;
// On message, parse it
ws.on('message', function (msg) { ws.on('message', function (msg) {
parent.debug('email', 'FBWS-Data(' + this.relayId + '): ' + msg); parent.debug('email', 'FBWS-Data(' + this.relayId + '): ' + msg);
if (typeof msg == 'string') { if (typeof msg === 'string') {
obj.log('Relay: ' + msg); obj.log('Relay: ' + msg);
// Parse the incoming push request let data;
var data = null; try { data = JSON.parse(msg); } catch (ex) { return; }
try { data = JSON.parse(msg) } catch (ex) { return; } if (typeof data !== 'object') return;
if (typeof data != 'object') return; if (!parent.common.validateObjectForMongo(data, 4096)) return;
if (parent.common.validateObjectForMongo(data, 4096) == false) return; // Perform sanity checking on this object. if (typeof data.pmt !== 'string' || typeof data.payload !== 'object') return;
if (typeof data.pmt != 'string') return;
if (typeof data.payload != 'object') return; data.payload.data = data.payload.data || {};
if (typeof data.payload.notification == 'object') { data.payload.data.r = ws.relayId;
if (typeof data.payload.notification.title != 'string') return;
if (typeof data.payload.notification.body != 'string') return; obj.sendToDevice({ pmt: data.pmt }, data.payload, data.options, function (id, err) {
} if (!err) {
if (typeof data.options != 'object') return; try { ws.send(JSON.stringify({ sent: true })); } catch (ex) { }
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) { }
} else { } else {
try { wsrelay.send(JSON.stringify({ sent: false })); } catch (ex) { } try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
} }
}); });
} }
}); });
// If error, close the relay // If error, close the relay
ws.on('error', function (err) { ws.on('error', function (err) {
parent.debug('email', 'FBWS-Error(' + this.relayId + '): ' + err); parent.debug('email', 'FBWS-Error(' + this.relayId + '): ' + err);
delete obj.relays[this.relayId]; delete obj.relays[this.relayId];
}); });
// Close the relay // Close the relay
ws.on('close', function () { ws.on('close', function () {
parent.debug('email', 'FBWS-Close(' + this.relayId + ')'); parent.debug('email', 'FBWS-Close(' + this.relayId + ')');
delete obj.relays[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; return obj;
}; };
@ -212,7 +168,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
const querystring = require('querystring'); const querystring = require('querystring');
const relayUrl = require('url').parse(url); const relayUrl = require('url').parse(url);
parent.debug('email', 'CreateFirebaseRelay-Setup'); parent.debug('email', 'CreateFirebaseRelay-Setup');
// Setup logging // Setup logging
if (parent.config.firebaserelay && (parent.config.firebaserelay.log === true)) { if (parent.config.firebaserelay && (parent.config.firebaserelay.log === true)) {
obj.logpath = parent.path.join(parent.datapath, 'firebaserelay.txt'); obj.logpath = parent.path.join(parent.datapath, 'firebaserelay.txt');
@ -220,7 +176,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
} else { } else {
obj.log = function () { } obj.log = function () { }
} }
obj.log('Starting relay to: ' + relayUrl.href); obj.log('Starting relay to: ' + relayUrl.href);
if (relayUrl.protocol == 'wss:') { if (relayUrl.protocol == 'wss:') {
// Setup two-way push notification channel // Setup two-way push notification channel
@ -252,7 +208,7 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
parent.debug('email', 'FBWS-Disconnected'); parent.debug('email', 'FBWS-Disconnected');
obj.wsclient = null; obj.wsclient = null;
obj.wsopen = false; obj.wsopen = false;
// Compute the backoff timer // Compute the backoff timer
if (obj.reconnectTimer == null) { if (obj.reconnectTimer == null) {
if ((obj.lastConnect != null) && ((Date.now() - obj.lastConnect) > 10000)) { obj.backoffTimer = 0; } 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) { function processMessage(messageId, from, data, category) {
// Lookup node information from the cache // Lookup node information from the cache
var ninfo = obj.tokenToNodeMap[from]; var ninfo = obj.tokenToNodeMap[from];
if (ninfo == null) { obj.stats.receivedNoRoute++; return; } if (ninfo == null) { obj.stats.receivedNoRoute++; return; }
if ((data != null) && (data.con != null) && (data.s != null)) { // Console command if ((data != null) && (data.con != null) && (data.s != null)) { // Console command
obj.stats.received++; obj.stats.received++;
parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: data.con, sessionid: data.s }, ninfo.did, ninfo.nid, ninfo.mid); 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.stats.receivedBadArgs++;
} }
} }
obj.sendToDevice = function (node, payload, options, func) { obj.sendToDevice = function (node, payload, options, func) {
if (typeof node == 'string') { 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'); } }) 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(node, payload, options, func);
} }
} }
obj.sendToDeviceEx = function (node, payload, options, func) { obj.sendToDeviceEx = function (node, payload, options, func) {
parent.debug('email', 'Firebase-sendToDevice-webSocket'); parent.debug('email', 'Firebase-sendToDevice-webSocket');
if ((node == null) || (typeof node.pmt != 'string')) { func(0, 'error'); return; } 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)); obj.log('sendToDevice, node:' + node._id + ', payload: ' + JSON.stringify(payload) + ', options: ' + JSON.stringify(options));
// Fill in our lookup table // Fill in our lookup table
if (node._id != null) { obj.tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } } if (node._id != null) { obj.tokenToNodeMap[node.pmt] = { nid: node._id, mid: node.meshid, did: node.domain } }
// Fill in the server agent cert hash // Fill in the server agent cert hash
if (payload.data == null) { payload.data = {}; } 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 (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 the web socket is open, send now
if (obj.wsopen == true) { 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; } 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:') { } else if (relayUrl.protocol == 'https:') {
// Send an outbound push notification using an HTTPS POST // Send an outbound push notification using an HTTPS POST
obj.pushOnly = true; obj.pushOnly = true;
obj.sendToDevice = function (node, payload, options, func) { obj.sendToDevice = function (node, payload, options, func) {
if (typeof node == 'string') { 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'); } }) 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(node, payload, options, func);
} }
} }
obj.sendToDeviceEx = function (node, payload, options, func) { obj.sendToDeviceEx = function (node, payload, options, func) {
parent.debug('email', 'Firebase-sendToDevice-httpPost'); parent.debug('email', 'Firebase-sendToDevice-httpPost');
if ((node == null) || (typeof node.pmt != 'string')) return; if ((node == null) || (typeof node.pmt != 'string')) return;
// Fill in the server agent cert hash // Fill in the server agent cert hash
if (payload.data == null) { payload.data = {}; } 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 (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)); 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 }) }); const querydata = querystring.stringify({ 'msg': JSON.stringify({ pmt: node.pmt, payload: payload, options: options }) });
// Send the message to the relay // Send the message to the relay
const httpOptions = { const httpOptions = {
hostname: relayUrl.hostname, hostname: relayUrl.hostname,
@ -357,6 +313,6 @@ module.exports.CreateFirebaseRelay = function (parent, url, key) {
req.end(); req.end();
} }
} }
return obj; return obj;
}; };

View file

@ -28,6 +28,8 @@ module.exports.CreateLetsEncrypt = function (parent) {
obj.challenges = {}; obj.challenges = {};
obj.runAsProduction = false; obj.runAsProduction = false;
obj.redirWebServerHooked = false; obj.redirWebServerHooked = false;
obj.zerossl = false;
obj.csr = null;
obj.configErr = null; obj.configErr = null;
obj.configOk = false; obj.configOk = false;
obj.pendingRequest = false; obj.pendingRequest = false;
@ -57,6 +59,7 @@ module.exports.CreateLetsEncrypt = function (parent) {
// Get the current certificate // Get the current certificate
obj.getCertificate = function(certs, func) { obj.getCertificate = function(certs, func) {
obj.runAsProduction = (obj.parent.config.letsencrypt.production === true); 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") + ")"); 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 (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; } 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..."); obj.log("Generating private key...");
acme.forge.createPrivateKey().then(function (accountKey) { acme.forge.createPrivateKey().then(function (accountKey) {
// TODO: ZeroSSL
// https://acme.zerossl.com/v2/DV90
// Create the ACME client // Create the ACME client
obj.log("Setting up ACME client..."); obj.log("Setting up ACME client...");
obj.client = new acme.Client({ if (obj.zerossl) {
directoryUrl: obj.runAsProduction ? acme.directory.letsencrypt.production : acme.directory.letsencrypt.staging, if (obj.zerossl.kid == "") { obj.log("EAB KID hasn't been set, invalid configuration."); return; }
accountKey: accountKey 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) // Create Certificate Request (CSR)
obj.log("Creating certificate request..."); obj.log("Creating certificate request...");
var certRequest = { commonName: obj.leDomains[0] }; var certRequest = { commonName: obj.leDomains[0] };
if (obj.leDomains.length > 1) { certRequest.altNames = obj.leDomains; } if (obj.leDomains.length > 1) { certRequest.altNames = obj.leDomains; }
acme.forge.createCsr(certRequest).then(function (r) { acme.forge.createCsr(certRequest).then(function (r) {
var csr = r[1]; obj.csr = r[1];
obj.tempPrivateKey = r[0]; 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({ obj.client.auto({
csr, csr: obj.csr,
email: obj.parent.config.letsencrypt.email, email: obj.parent.config.letsencrypt.email,
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
skipChallengeVerification: (obj.parent.config.letsencrypt.skipchallengeverification === true), skipChallengeVerification: (obj.parent.config.letsencrypt.skipchallengeverification === true),

View file

@ -321,7 +321,7 @@ function setup() { InstallModules(['image-size'], start); }
function start() { startEx(process.argv); } function start() { startEx(process.argv); }
function startEx(argv) { function startEx(argv) {
if (argv.length > 2) { indexFile(argv[2]); } else { 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("This tool will index a .mcrec file so that the player can seek thru the file.");
log(""); log("");
log(" Usage: node mcrec [file]"); log(" Usage: node mcrec [file]");

View file

@ -58,15 +58,15 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
dataAccounting(); 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 == 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) { if (arg == 2) {
try { try {
if (ws._socket._parent != null) if (ws._socket._parent != null)
ws._socket._parent.end(); ws._socket._parent.end();
else else
ws._socket.end(); ws._socket.end();
if (obj.nodeid != null) { if (obj.nodeid != null) {
parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')');
} }
} catch (e) { console.log(e); } } 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')) { 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)) { if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
// Mesh name is hex instead of base64 // Mesh name is hex instead of base64
const meshname = obj.meshid.substring(0, 18); 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) { db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
if ((iplocs != null) && (iplocs.length == 1)) { if ((iplocs != null) && (iplocs.length == 1)) {
// We have a location in the database for this remote IP // 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)) { if ((iploc != null) && (iploc.ip != null) && (iploc.loc != null)) {
x.publicip = iploc.ip; x.publicip = iploc.ip;
x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000)); 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 { } else {
// Check if we need to ask for the IP location // Check if we need to ask for the IP location
var doIpLocation = 0; var doIpLocation = 0;
if (device.iploc == null) { if (obj.iploc == null) {
doIpLocation = 1; doIpLocation = 1;
} else { } else {
const loc = device.iploc.split(','); const loc = obj.iploc.split(',');
if (loc.length < 3) { if (loc.length < 3) {
doIpLocation = 2; doIpLocation = 2;
} else { } else {
@ -1924,6 +1924,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (!device.defender) { device.defender = {}; } 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 (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 // Push Messaging Token
if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) { if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) {
@ -1932,19 +1936,17 @@ 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. 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. 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.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)) { if ((mesh.mtype == 2) && (!args.wanonly)) {
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes. // 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'); } if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
// TODO: Check that the agent has an interface that is the same as the one we got this websocket connection on. Only set if we have a match. // TODO: Check that the agent has an interface that is the same as the one we got this websocket connection on. Only set if we have a match.
} }
// Volumes and BitLocker // Remove old volumes and BitLocker data, this is part of sysinfo.
if(command.volumes != null){ delete device.volumes;
if(!device.volumes) { device.volumes = {}; }
if (JSON.stringify(device.volumes) != JSON.stringify(command.volumes)) { /*changes.push('Volumes status');*/ device.volumes = command.volumes; change = 1; log = 1; }
}
// If there are changes, event the new device // If there are changes, event the new device
if (change == 1) { if (change == 1) {

File diff suppressed because it is too large Load diff

View file

@ -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. 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 // 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', '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; } } 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.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. 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)) { if ((obj.args.help == true) || (obj.args['?'] == true)) {
@ -220,6 +234,12 @@ function CreateMeshCentralServer(config, args) {
translateEngine.startEx(['', '', 'translateall', translationFile]); translateEngine.startEx(['', '', 'translateall', translationFile]);
translateEngine.startEx(['', '', 'extractall', translationFile]); translateEngine.startEx(['', '', 'extractall', translationFile]);
didSomething = true; 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. // 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); files = obj.fs.readdirSync(obj.webViewsOverridePath);
for (var i in files) { for (var i in files) {
var file = obj.path.join(obj.webViewsOverridePath, files[i]); 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']); 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) { if (obj.webPublicOverridePath != null) {
didSomething = true; didSomething = true;
@ -254,6 +297,7 @@ function CreateMeshCentralServer(config, args) {
*/ */
if (didSomething == false) { console.log("Nothing to do."); } if (didSomething == false) { console.log("Nothing to do."); }
console.log('Finished Translating.')
process.exit(); process.exit();
return; return;
} }
@ -350,6 +394,92 @@ function CreateMeshCentralServer(config, args) {
return; 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 // Index a recorded file
if (obj.args.indexmcrec != null) { if (obj.args.indexmcrec != null) {
@ -453,8 +583,11 @@ function CreateMeshCentralServer(config, args) {
// Launch MeshCentral as a child server and monitor it. // Launch MeshCentral as a child server and monitor it.
obj.launchChildServer = function (startArgs) { obj.launchChildServer = function (startArgs) {
const child_process = require('child_process'); 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.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } 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) { childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
if (childProcess.xrestart == 1) { if (childProcess.xrestart == 1) {
setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart. 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; } 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; var datastr = data;
while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); } 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) { childProcess.stderr.on('data', function (data) {
var datastr = data; var datastr = data;
while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); } 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.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); } if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); }
obj.logError(data); obj.logError(data);
@ -618,7 +751,7 @@ function CreateMeshCentralServer(config, args) {
obj.performServerUpdate = function (version) { obj.performServerUpdate = function (version) {
if (obj.serverSelfWriteAllowed != true) return false; if (obj.serverSelfWriteAllowed != true) return false;
if ((version == null) || (version == '') || (typeof version != 'string')) { console.log('Starting self upgrade...'); } else { console.log('Starting self upgrade to: ' + version); } if ((version == null) || (version == '') || (typeof version != 'string')) { console.log('Starting self upgrade...'); } else { console.log('Starting self upgrade to: ' + version); }
process.exit(200); process.exit(200);
return true; return true;
}; };
@ -648,7 +781,7 @@ function CreateMeshCentralServer(config, args) {
// Get new instance of the client // Get new instance of the client
const vault = require("node-vault")({ endpoint: obj.args.vault.endpoint, token: obj.args.vault.token }); const vault = require("node-vault")({ endpoint: obj.args.vault.endpoint, token: obj.args.vault.token });
vault.unseal({ key: obj.args.vault.unsealkey }) vault.unseal({ key: obj.args.vault.unsealkey })
.then(function() { .then(function () {
if (obj.args.vaultdeleteconfigfiles) { if (obj.args.vaultdeleteconfigfiles) {
vault.delete('secret/data/' + obj.args.vault.name) vault.delete('secret/data/' + obj.args.vault.name)
.then(function (r) { console.log('Done.'); process.exit(); }) .then(function (r) { console.log('Done.'); process.exit(); })
@ -764,7 +897,7 @@ function CreateMeshCentralServer(config, args) {
} }
// Check top level configuration for any unrecognized values // Check top level configuration for any unrecognized values
if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'messaging', 'sendgrid', 'sendmail', 'firebase', 'firebaserelay', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".', 3, [ i ]); } } } if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'messaging', 'sendgrid', 'sendmail', 'firebase', 'firebaserelay', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".', 3, [i]); } } }
// Read IP lists from files if applicable // Read IP lists from files if applicable
config.settings.userallowedip = obj.args.userallowedip = readIpListFromFile(obj.args.userallowedip); config.settings.userallowedip = obj.args.userallowedip = readIpListFromFile(obj.args.userallowedip);
@ -774,11 +907,11 @@ function CreateMeshCentralServer(config, args) {
config.settings.swarmallowedip = obj.args.swarmallowedip = readIpListFromFile(obj.args.swarmallowedip); config.settings.swarmallowedip = obj.args.swarmallowedip = readIpListFromFile(obj.args.swarmallowedip);
// Check IP lists and ranges // Check IP lists and ranges
if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { config.settings.userallowedip = obj.args.userallowedip = null; } else { config.settings.userallowedip = obj.args.userallowedip = obj.args.userallowedip.split(','); } } if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { config.settings.userallowedip = obj.args.userallowedip = null; } else { config.settings.userallowedip = obj.args.userallowedip = obj.args.userallowedip.split(' ').join('').split(','); } }
if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { config.settings.userblockedip = obj.args.userblockedip = null; } else { config.settings.userblockedip = obj.args.userblockedip = obj.args.userblockedip.split(','); } } if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { config.settings.userblockedip = obj.args.userblockedip = null; } else { config.settings.userblockedip = obj.args.userblockedip = obj.args.userblockedip.split(' ').join('').split(','); } }
if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { config.settings.agentallowedip = obj.args.agentallowedip = null; } else { config.settings.agentallowedip = obj.args.agentallowedip = obj.args.agentallowedip.split(','); } } if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { config.settings.agentallowedip = obj.args.agentallowedip = null; } else { config.settings.agentallowedip = obj.args.agentallowedip = obj.args.agentallowedip.split(' ').join('').split(','); } }
if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { config.settings.agentblockedip = obj.args.agentblockedip = null; } else { config.settings.agentblockedip = obj.args.agentblockedip = obj.args.agentblockedip.split(','); } } if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { config.settings.agentblockedip = obj.args.agentblockedip = null; } else { config.settings.agentblockedip = obj.args.agentblockedip = obj.args.agentblockedip.split(' ').join('').split(','); } }
if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } } if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(' ').join('').split(','); } }
if ((typeof obj.args.agentupdateblocksize == 'number') && (obj.args.agentupdateblocksize >= 1024) && (obj.args.agentupdateblocksize <= 65531)) { obj.agentUpdateBlockSize = obj.args.agentupdateblocksize; } if ((typeof obj.args.agentupdateblocksize == 'number') && (obj.args.agentupdateblocksize >= 1024) && (obj.args.agentupdateblocksize <= 65531)) { obj.agentUpdateBlockSize = obj.args.agentupdateblocksize; }
if (typeof obj.args.trustedproxy == 'string') { obj.args.trustedproxy = obj.args.trustedproxy.split(' ').join('').split(','); } if (typeof obj.args.trustedproxy == 'string') { obj.args.trustedproxy = obj.args.trustedproxy.split(' ').join('').split(','); }
if (typeof obj.args.tlsoffload == 'string') { obj.args.tlsoffload = obj.args.tlsoffload.split(' ').join('').split(','); } if (typeof obj.args.tlsoffload == 'string') { obj.args.tlsoffload = obj.args.tlsoffload.split(' ').join('').split(','); }
@ -867,10 +1000,10 @@ function CreateMeshCentralServer(config, args) {
delete user.phone; delete user.otpekey; delete user.otpsecret; delete user.otpkeys; delete user.otphkeys; delete user.otpdev; delete user.otpsms; delete user.otpmsg; // Disable 2FA delete user.phone; delete user.otpekey; delete user.otpsecret; delete user.otpkeys; delete user.otphkeys; delete user.otpdev; delete user.otpsms; delete user.otpmsg; // Disable 2FA
delete user.msghandle; // Disable users 2fa messaging too delete user.msghandle; // Disable users 2fa messaging too
var config = getConfig(false); var config = getConfig(false);
if(config.domains[user.domain].auth || config.domains[user.domain].authstrategies){ if (config.domains[user.domain].auth || config.domains[user.domain].authstrategies) {
console.log('This users domain has external authentication methods enabled so the password will not be changed if you set one') console.log('This users domain has external authentication methods enabled so the password will not be changed if you set one')
obj.db.Set(user, function () { console.log("Done."); process.exit(); return; }); obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
}else{ } else {
if (obj.args.hashpass && (typeof obj.args.hashpass == 'string')) { if (obj.args.hashpass && (typeof obj.args.hashpass == 'string')) {
// Reset an account using a pre-hashed password. Use --hashpassword to pre-hash a password. // Reset an account using a pre-hashed password. Use --hashpassword to pre-hash a password.
var hashpasssplit = obj.args.hashpass.split(','); var hashpasssplit = obj.args.hashpass.split(',');
@ -878,14 +1011,14 @@ function CreateMeshCentralServer(config, args) {
user.salt = hashpasssplit[0]; user.salt = hashpasssplit[0];
user.hash = hashpasssplit[1]; user.hash = hashpasssplit[1];
obj.db.Set(user, function () { console.log("Done. This command will only work if MeshCentral is stopped."); process.exit(); return; }); obj.db.Set(user, function () { console.log("Done. This command will only work if MeshCentral is stopped."); process.exit(); return; });
} else if(obj.args.pass && (typeof obj.args.pass == 'string')) { } else if (obj.args.pass && (typeof obj.args.pass == 'string')) {
// Hash the password and reset the account. // Hash the password and reset the account.
require('./pass').hash(String(obj.args.pass), user.salt, function (err, hash, tag) { require('./pass').hash(String(obj.args.pass), user.salt, function (err, hash, tag) {
if (err) { console.log("Unable to reset password: " + err); process.exit(); return; } if (err) { console.log("Unable to reset password: " + err); process.exit(); return; }
user.hash = hash; user.hash = hash;
obj.db.Set(user, function () { console.log("Done."); process.exit(); return; }); obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
}, 0); }, 0);
}else{ } else {
console.log('Not setting a users password'); console.log('Not setting a users password');
obj.db.Set(user, function () { console.log("Done."); process.exit(); return; }); obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
} }
@ -985,7 +1118,27 @@ function CreateMeshCentralServer(config, args) {
// Show a list of all configuration files in the database // Show a list of all configuration files in the database
if (obj.args.dblistconfigfiles) { 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 // 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'); 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.'); console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
lockCount++; 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(); } if (--lockCount == 0) { process.exit(); }
@ -1107,7 +1264,7 @@ function CreateMeshCentralServer(config, args) {
for (var j in doc) { if (j.indexOf('.') >= 0) { console.log("Invalid field name (" + j + ") in document: " + json[i]); return; } } for (var j in doc) { if (j.indexOf('.') >= 0) { console.log("Invalid field name (" + j + ") in document: " + json[i]); return; } }
//if ((json[i].type == 'ifinfo') && (json[i].netif2 != null)) { for (var j in json[i].netif2) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].netif2[esc] = json[i].netif2[j]; delete json[i].netif2[j]; } } } //if ((json[i].type == 'ifinfo') && (json[i].netif2 != null)) { for (var j in json[i].netif2) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].netif2[esc] = json[i].netif2[j]; delete json[i].netif2[j]; } } }
//if ((json[i].type == 'mesh') && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } } //if ((json[i].type == 'mesh') && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } }
} }
//for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it. setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
obj.db.RemoveAll(function () { obj.db.RemoveAll(function () {
@ -1194,7 +1351,7 @@ function CreateMeshCentralServer(config, args) {
} }
// Check if the database is capable of performing a backup // 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 // Load configuration for database if needed
if (obj.args.loadconfigfromdb) { if (obj.args.loadconfigfromdb) {
@ -1307,11 +1464,16 @@ 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 ((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 (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; } 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; }
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(','); } } obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip = readIpListFromFile(obj.config.domains[i].userallowedip);
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(','); } } obj.config.domains[i].userblockedip = obj.config.domains[i].userblockedip = readIpListFromFile(obj.config.domains[i].userblockedip);
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(','); } } obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip = readIpListFromFile(obj.config.domains[i].agentallowedip);
if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { delete obj.config.domains[i].agentblockedip; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(','); } } 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(','); } }
if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { delete obj.config.domains[i].agentblockedip; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(' ').join('').split(','); } }
if (typeof obj.config.domains[i].ignoreagenthashcheck == 'string') { if (obj.config.domains[i].ignoreagenthashcheck == '') { delete obj.config.domains[i].ignoreagenthashcheck; } else { obj.config.domains[i].ignoreagenthashcheck = obj.config.domains[i].ignoreagenthashcheck.split(','); } } if (typeof obj.config.domains[i].ignoreagenthashcheck == 'string') { if (obj.config.domains[i].ignoreagenthashcheck == '') { delete obj.config.domains[i].ignoreagenthashcheck; } else { obj.config.domains[i].ignoreagenthashcheck = obj.config.domains[i].ignoreagenthashcheck.split(','); } }
if (typeof obj.config.domains[i].allowedorigin == 'string') { if (obj.config.domains[i].allowedorigin == '') { delete obj.config.domains[i].allowedorigin; } else { obj.config.domains[i].allowedorigin = obj.config.domains[i].allowedorigin.split(','); } }
if ((obj.config.domains[i].passwordrequirements != null) && (typeof obj.config.domains[i].passwordrequirements == 'object')) { if ((obj.config.domains[i].passwordrequirements != null) && (typeof obj.config.domains[i].passwordrequirements == 'object')) {
if (typeof obj.config.domains[i].passwordrequirements.skip2factor == 'string') { if (typeof obj.config.domains[i].passwordrequirements.skip2factor == 'string') {
obj.config.domains[i].passwordrequirements.skip2factor = obj.config.domains[i].passwordrequirements.skip2factor.split(','); obj.config.domains[i].passwordrequirements.skip2factor = obj.config.domains[i].passwordrequirements.skip2factor.split(',');
@ -1385,7 +1547,7 @@ function CreateMeshCentralServer(config, args) {
if (i == '') { if (i == '') {
addServerWarning("Unable to load Intel AMT TLS root certificate for default domain.", 5); addServerWarning("Unable to load Intel AMT TLS root certificate for default domain.", 5);
} else { } else {
addServerWarning("Unable to load Intel AMT TLS root certificate for domain " + i + ".", 6, [ i ]); addServerWarning("Unable to load Intel AMT TLS root certificate for domain " + i + ".", 6, [i]);
} }
} }
} }
@ -1446,8 +1608,18 @@ function CreateMeshCentralServer(config, args) {
if (obj.args.rediraliasport != null && (typeof obj.args.rediraliasport != 'number')) obj.args.rediraliasport = null; 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.redirport == null) obj.args.redirport = 80;
if (obj.args.minifycore == null) obj.args.minifycore = false; 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 (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) && (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 ((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(','); } } 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 // Setup a site administrator
@ -1487,7 +1659,7 @@ function CreateMeshCentralServer(config, args) {
} }
// Setup agent error log // 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; }) obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; })
} }
@ -1823,19 +1995,31 @@ function CreateMeshCentralServer(config, args) {
if (typeof config.settings.webpush.gcmapi == 'string') { webpush.setGCMAPIKey(config.settings.webpush.gcmapi); } 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 // Setup Firebase
if ((config.firebase != null) && (typeof config.firebase.senderid == 'string') && (typeof config.firebase.serverkey == 'string')) { 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')) { } else if ((typeof config.firebaserelay == 'object') && (typeof config.firebaserelay.url == 'string')) {
// Setup the push messaging relay // Setup the push messaging relay
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key); obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key);
} else if (obj.config.settings.publicpushnotifications === true) { } else if (obj.config.settings.publicpushnotifications === true) {
// Setup the Firebase push messaging relay using https://meshcentral.com, this is the public push notification server. // Setup the Firebase push messaging relay using https://alt.meshcentral.com, this is the public push notification server.
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://meshcentral.com/firebaserelay.aspx'); 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 // Start periodic maintenance
obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour 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 // Dispatch an event that the server is now running
obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }); obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
@ -1920,15 +2104,25 @@ function CreateMeshCentralServer(config, args) {
obj.updateServerState('state', "running"); obj.updateServerState('state', "running");
// Setup auto-backup defaults // Setup auto-backup defaults
if (obj.config.settings.autobackup == null) { obj.config.settings.autobackup = { backupintervalhours: 24, keeplastdaysbackup: 10 }; } 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 === false) { delete obj.config.settings.autobackup; } else {
if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
// Check that autobackup path is not within the "meshcentral-data" folder. if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
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)))) { if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21); 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; }}
delete obj.config.settings.autobackup; 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 // Load Intel AMT passwords from the "amtactivation.log" file
obj.loadAmtActivationLogPasswords(function (amtPasswords) { obj.loadAmtActivationLogPasswords(function (amtPasswords) {
obj.amtPasswords = amtPasswords; obj.amtPasswords = amtPasswords;
@ -2089,14 +2283,19 @@ function CreateMeshCentralServer(config, args) {
// Check if we need to perform an automatic backup // Check if we need to perform an automatic backup
function checkAutobackup() { 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) { 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; 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; } if (docs.length == 1) { lastBackup = docs[0].value; }
const delta = now - lastBackup; 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. // A new auto-backup is required.
obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database
obj.db.performBackup(); // Perform the backup obj.db.performBackup(); // Perform the backup
@ -2155,7 +2354,7 @@ function CreateMeshCentralServer(config, args) {
// Update the server state // Update the server state
obj.updateServerState('state', "stopped"); obj.updateServerState('state', "stopped");
}; };
// Event Dispatch // Event Dispatch
obj.AddEventDispatch = function (ids, target) { obj.AddEventDispatch = function (ids, target) {
obj.debug('dispatch', 'AddEventDispatch', ids); obj.debug('dispatch', 'AddEventDispatch', ids);
@ -2218,6 +2417,10 @@ function CreateMeshCentralServer(config, args) {
storeEvent.links = Object.assign({}, storeEvent.links); 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]; } } 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; storeEvent.ids = ids;
obj.db.StoreEvent(storeEvent); obj.db.StoreEvent(storeEvent);
} }
@ -2308,7 +2511,7 @@ function CreateMeshCentralServer(config, args) {
// Event any changes on this server only // Event any changes on this server only
if ((newConnectivity != oldPowerState) || (newPowerState != oldPowerState)) { 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() });
} }
} }
}; };
@ -2477,7 +2680,7 @@ function CreateMeshCentralServer(config, args) {
if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; } if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; }
var powerState = 0, oldPowerState = state.powerState; var powerState = 0, oldPowerState = state.powerState;
if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; } if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
if ((state.powerState == null)|| (state.powerState == undefined) || (state.powerState != powerState)) { if ((state.powerState == null) || (state.powerState == undefined) || (state.powerState != powerState)) {
state.powerState = powerState; state.powerState = powerState;
eventConnectChange = 1; eventConnectChange = 1;
@ -2489,7 +2692,7 @@ function CreateMeshCentralServer(config, args) {
// Event the node connection change // Event the node connection change
if (eventConnectChange == 1) { 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 // 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 }; const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType };
@ -2521,7 +2724,7 @@ function CreateMeshCentralServer(config, args) {
if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; } if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; }
var powerState = 0, oldPowerState = state.powerState; var powerState = 0, oldPowerState = state.powerState;
if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; } if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
if ((state.powerState == null)|| (state.powerState == undefined) || (state.powerState != powerState)) { if ((state.powerState == null) || (state.powerState == undefined) || (state.powerState != powerState)) {
state.powerState = powerState; state.powerState = powerState;
eventConnectChange = 1; eventConnectChange = 1;
@ -2593,7 +2796,7 @@ function CreateMeshCentralServer(config, args) {
// Event the node connection change // Event the node connection change
if (eventConnectChange == 1) { 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 // Notify any users of device disconnection
obj.NotifyUserOfDeviceStateChange(meshid, nodeid, Date.now(), connectType, -1, serverid, false, extraInfo); obj.NotifyUserOfDeviceStateChange(meshid, nodeid, Date.now(), connectType, -1, serverid, false, extraInfo);
@ -2663,7 +2866,7 @@ function CreateMeshCentralServer(config, args) {
if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) { if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) {
meshcorePath = obj.path.join(__dirname, 'agents'); meshcorePath = obj.path.join(__dirname, 'agents');
if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) { if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) {
obj.defaultMeshCores = obj.defaultMeshCoresHash = { }; if (func != null) { func(false); } // meshcore.js not found obj.defaultMeshCores = obj.defaultMeshCoresHash = {}; if (func != null) { func(false); } // meshcore.js not found
} }
} }
@ -2729,7 +2932,7 @@ function CreateMeshCentralServer(config, args) {
// We are adding a JS file to the meshcores // We are adding a JS file to the meshcores
var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3); var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3);
if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files. if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files.
const moduleData = [ 'try { addModule("', moduleName, '", "', obj.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (ex) { }\r\n' ]; const moduleData = ['try { addModule("', moduleName, '", "', obj.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (ex) { }\r\n'];
// Merge this module // Merge this module
// NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms. // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms.
@ -2958,7 +3161,7 @@ function CreateMeshCentralServer(config, args) {
} catch (ex) { } } catch (ex) { }
} }
}; };
// List of possible mesh agents // List of possible mesh agents
obj.meshAgentsArchitectureNumbers = { obj.meshAgentsArchitectureNumbers = {
0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
@ -3051,13 +3254,13 @@ function CreateMeshCentralServer(config, args) {
// Setup the time server // Setup the time server
var timeStampUrl = 'http://timestamp.comodoca.com/authenticode'; var timeStampUrl = 'http://timestamp.comodoca.com/authenticode';
if (args.agenttimestampserver === false) { timeStampUrl = null; } if (obj.args.agenttimestampserver === false) { timeStampUrl = null; }
else if (typeof args.agenttimestampserver == 'string') { timeStampUrl = args.agenttimestampserver; } else if (typeof obj.args.agenttimestampserver == 'string') { timeStampUrl = obj.args.agenttimestampserver; }
// Setup the time server proxy // Setup the time server proxy
var timeStampProxy = null; var timeStampProxy = null;
if (typeof args.agenttimestampproxy == 'string') { timeStampProxy = args.agenttimestampproxy; } if (typeof obj.args.agenttimestampproxy == 'string') { timeStampProxy = obj.args.agenttimestampproxy; }
else if ((args.agenttimestampproxy !== false) && (typeof args.npmproxy == 'string')) { timeStampProxy = args.npmproxy; } else if ((obj.args.agenttimestampproxy !== false) && (typeof obj.args.npmproxy == 'string')) { timeStampProxy = obj.args.npmproxy; }
// Setup the pending operations counter // Setup the pending operations counter
var pendingOperations = 1; var pendingOperations = 1;
@ -3204,7 +3407,7 @@ function CreateMeshCentralServer(config, args) {
console.log(obj.common.format('Code signed {0}.', agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname)); console.log(obj.common.format('Code signed {0}.', agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname));
} else { } else {
// Failed to sign agent // Failed to sign agent
addServerWarning('Failed to sign \"' + agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname + '\": ' + err, 22, [ agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname, err ]); addServerWarning('Failed to sign \"' + agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname + '\": ' + err, 22, [agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname, err]);
} }
if (--pendingOperations === 0) { agentSignedFunc.func(); } if (--pendingOperations === 0) { agentSignedFunc.func(); }
} }
@ -3542,7 +3745,7 @@ function CreateMeshCentralServer(config, args) {
try { try {
const iv = Buffer.from(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key.slice(0, 32), iv); 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()]); 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; } } catch (ex) { return null; }
} }
@ -3551,7 +3754,7 @@ function CreateMeshCentralServer(config, args) {
if ((typeof data != 'string') || (data.length < 13)) return {}; if ((typeof data != 'string') || (data.length < 13)) return {};
if (key == null) { key = obj.loginCookieEncryptionKey; } if (key == null) { key = obj.loginCookieEncryptionKey; }
try { 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)); const decipher = obj.crypto.createDecipheriv('aes-256-gcm', key.slice(0, 32), buf.slice(0, 12));
decipher.setAuthTag(buf.slice(12, 28)); decipher.setAuthTag(buf.slice(12, 28));
return JSON.parse(decipher.update(buf.slice(28), 'binary', 'utf8') + decipher.final('utf8')); return JSON.parse(decipher.update(buf.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
@ -3673,7 +3876,7 @@ function CreateMeshCentralServer(config, args) {
// Send event to log file // Send event to log file
if (obj.config.settings && obj.config.settings.log) { if (obj.config.settings && obj.config.settings.log) {
if (typeof obj.args.log == 'string') { obj.args.log = obj.args.log.split(','); } 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(); const d = new Date();
if (obj.xxLogFile == null) { if (obj.xxLogFile == null) {
try { try {
@ -3685,7 +3888,8 @@ function CreateMeshCentralServer(config, args) {
if (obj.xxLogFile != null) { if (obj.xxLogFile != null) {
try { try {
if (obj.xxLogDateStr != d.toLocaleDateString()) { obj.xxLogDateStr = d.toLocaleDateString(); obj.fs.writeSync(obj.xxLogFile, '---- ' + d.toLocaleDateString() + ' ----\r\n'); } 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) { } } catch (ex) { }
} }
} }
@ -3729,7 +3933,7 @@ function CreateMeshCentralServer(config, args) {
function readIpListFromFile(arg) { function readIpListFromFile(arg) {
if ((typeof arg != 'string') || (!arg.startsWith('file:'))) return arg; if ((typeof arg != 'string') || (!arg.startsWith('file:'))) return arg;
var lines = null; 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; if (lines == null) return null;
const validLines = []; 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]); } 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]); }
@ -3742,6 +3946,7 @@ function CreateMeshCentralServer(config, args) {
function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); } 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); } function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); }
obj.getServerWarnings = function () { return serverWarnings; } 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); } } 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 // auth.log functions
@ -3757,9 +3962,9 @@ function CreateMeshCentralServer(config, args) {
if (obj.authlogfile != null) { // Write authlog to file if (obj.authlogfile != null) { // Write authlog to file
try { try {
const d = new Date(), month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getMonth()]; const d = new Date(), month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getMonth()];
msg = month + ' ' + d.getDate() + ' ' + obj.common.zeroPad(d.getHours(), 2) + ':' + obj.common.zeroPad(d.getMinutes(), 2) + ':' + d.getSeconds() + ' meshcentral ' + server + '[' + process.pid + ']: ' + msg + ((obj.platform == 'win32') ? '\r\n' : '\n'); str = month + ' ' + d.getDate() + ' ' + obj.common.zeroPad(d.getHours(), 2) + ':' + obj.common.zeroPad(d.getMinutes(), 2) + ':' + d.getSeconds() + ' meshcentral ' + server + '[' + process.pid + ']: ' + msg + ((obj.platform == 'win32') ? '\r\n' : '\n');
obj.fs.write(obj.authlogfile, msg, function (err, written, string) { }); obj.fs.write(obj.authlogfile, str, function (err, written, string) { if (err) { console.error(err); } });
} catch (ex) { console.log(ex); } } catch (ex) { console.error(ex); }
} }
} }
@ -3780,7 +3985,7 @@ function CreateMeshCentralServer(config, args) {
function checkResolveAll(names, func) { function checkResolveAll(names, func) {
const dns = require('dns'), state = { func: func, count: names.length, err: null }; const dns = require('dns'), state = { func: func, count: names.length, err: null };
for (var i in names) { 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 (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); } if (--this.state.count == 0) { this.state.func(this.state.err); }
}.bind({ name: names[i], state: state })) }.bind({ name: names[i], state: state }))
@ -3839,14 +4044,31 @@ function InstallModules(modules, args, func) {
for (var i in modules) { 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 // 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 moduleNameAndVersion = modules[i];
const moduleInfo = moduleNameAndVersion.split('@', 2); const moduleInfo = moduleNameAndVersion.split('@', 3);
var moduleName = moduleInfo[0]; var moduleName = null;
var moduleVersion = moduleInfo[1]; var moduleVersion = null;
if (moduleName == '') { moduleName = moduleNameAndVersion; moduleVersion = null; } // If the module name starts with @, don't use @ as a version seperator. 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 { try {
// Does the module need a specific version? // Does the module need a specific version?
if (moduleVersion) { 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 { } else {
// For all other modules, do the check here. // For all other modules, do the check here.
// Is the module in package.json? Install exact version. // Is the module in package.json? Install exact version.
@ -3857,7 +4079,7 @@ function InstallModules(modules, args, func) {
missingModules.push(moduleNameAndVersion); missingModules.push(moduleNameAndVersion);
} }
} }
if (missingModules.length > 0) { if (args.debug) { console.log('Missing Modules: ' + missingModules.join(', ')); } InstallModuleEx(missingModules, args, func); } else { func(); } if (missingModules.length > 0) { if (args.debug) { console.log('Missing Modules: ' + missingModules.join(', ')); } InstallModuleEx(missingModules, args, func); } else { func(); }
} }
} }
@ -3895,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(); }); 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 // Add a server warning, warnings will be shown to the administrator on the web application
// TODO: migrate to obj.addServerWarning?
const serverWarnings = []; const serverWarnings = [];
function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } } function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
@ -3925,7 +4148,8 @@ var ServerWarnings = {
23: "Unable to load agent icon file: {0}.", 23: "Unable to load agent icon file: {0}.",
24: "Unable to load agent logo file: {0}.", 24: "Unable to load agent logo file: {0}.",
25: "This NodeJS version does not support OpenID.", 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."
}; };
*/ */
@ -3935,8 +4159,8 @@ var meshserver = null;
var childProcess = null; var childProcess = null;
var previouslyInstalledModules = {}; var previouslyInstalledModules = {};
function mainStart() { function mainStart() {
// Check the NodeJS is version 10 or better. // Check the NodeJS is version 16 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; } 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 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, '..', '..')); } if (__dirname.endsWith('\\node_modules\\meshcentral') || __dirname.endsWith('/node_modules/meshcentral')) { process.chdir(require('path').join(__dirname, '..', '..')); }
@ -3979,7 +4203,7 @@ function mainStart() {
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used // Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
var sspi = false; var sspi = false;
var ldap = false; var ldap = false;
var passport = null; var passport = [];
var allsspi = true; var allsspi = true;
var yubikey = false; var yubikey = false;
var ssh = false; var ssh = false;
@ -4000,59 +4224,70 @@ function mainStart() {
if (mstsc == false) { config.domains[i].mstsc = false; } if (mstsc == false) { config.domains[i].mstsc = false; }
if (config.domains[i].ssh == true) { ssh = true; } if (config.domains[i].ssh == true) { ssh = true; }
if ((typeof config.domains[i].authstrategies == 'object')) { 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.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.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.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.reddit == 'object') && (typeof config.domains[i].authstrategies.reddit.clientid == 'string') && (typeof config.domains[i].authstrategies.reddit.clientsecret == 'string') && (passport.indexOf('passport-reddit') == -1)) { passport.push('passport-reddit'); }
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.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') && (typeof config.domains[i].authstrategies.oidc.clientid == 'string') && (typeof config.domains[i].authstrategies.oidc.clientsecret == 'string') && (typeof config.domains[i].authstrategies.oidc.issuer == 'string') && (passport.indexOf('@mstrhakr/passport-openidconnect') == -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('@mstrhakr/passport-openidconnect'); passport.push('openid-client'); passport.push('connect-flash'); } else { addServerWarning('This NodeJS version does not support OpenID.', 25); delete config.domains[i].authstrategies.oidc; } 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@5.7.1');
} else {
addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
delete config.domains[i].authstrategies.oidc;
}
} }
if ((typeof config.domains[i].authstrategies.saml == 'object') || (typeof config.domains[i].authstrategies.jumpcloud == 'object')) { passport.push('passport-saml'); } if ((typeof config.domains[i].authstrategies.saml == 'object') || (typeof config.domains[i].authstrategies.jumpcloud == 'object')) { passport.push('passport-saml'); }
} }
if (config.domains[i].sessionrecording != null) { sessionRecording = true; } 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].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = 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 // Build the list of required modules
// NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile // NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile
var modules = ['archiver@5.3.2','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 (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 (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 (passport != null) { modules.push(...passport); }
if (captcha == true) { modules.push('svg-captcha@1.4.0'); } 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.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.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.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.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.postgres != null) { modules.push('pg@8.13.1') } // Add Postgres, official driver.
if (config.settings.mariadb != null) { modules.push('mariadb@3.2.2'); } // Add MariaDB, 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.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.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) { 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 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. 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 (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 (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') { if (typeof config.settings.autobackup == 'object') {
// Setup encrypted zip support if needed // 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 // Enable Google Drive Support
if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); } if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
// Enable WebDAV Support // Enable WebDAV Support
if (typeof config.settings.autobackup.webdav == 'object') { 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 // Setup common password blocking
@ -4066,7 +4301,7 @@ function mainStart() {
} }
// Desktop multiplexor support // 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 // SMS support
if (config.sms != null) { if (config.sms != null) {
@ -4088,8 +4323,7 @@ function mainStart() {
if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); } if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); }
// Firebase Support // Firebase Support
// Avoid 0.1.8 due to bugs: https://github.com/guness/node-xcs/issues/43 if ((config.firebase != null) && (typeof config.firebase.serviceaccountfile == 'string')) { modules.push('firebase-admin@12.7.0'); }
if (config.firebase != null) { modules.push('node-xcs@0.1.7'); }
// Syslog support // Syslog support
if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); } if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); }

View file

@ -16,7 +16,7 @@ var settings = {};
const crypto = require('crypto'); const crypto = require('crypto');
const args = require('minimist')(process.argv.slice(2)); const args = require('minimist')(process.argv.slice(2));
const path = require('path'); 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']; 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.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) { if (args['_'].length == 0) {
@ -36,6 +36,8 @@ if (args['_'].length == 0) {
console.log(" ListEvents - List server events."); console.log(" ListEvents - List server events.");
console.log(" LoginTokens - List, create and remove login tokens."); console.log(" LoginTokens - List, create and remove login tokens.");
console.log(" DeviceInfo - Show information about a device."); 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(" EditDevice - Make changes to a device.");
console.log(" RemoveDevice - Delete a device."); console.log(" RemoveDevice - Delete a device.");
console.log(" Config - Perform operation on config.json file."); console.log(" Config - Perform operation on config.json file.");
@ -63,9 +65,12 @@ if (args['_'].length == 0) {
console.log(" Shell - Access command shell of a remote device."); console.log(" Shell - Access command shell of a remote device.");
console.log(" Upload - Upload a file to 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(" 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(" DeviceOpenUrl - Open a URL on a remote device.");
console.log(" DeviceMessage - Open a message box 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."); console.log(" DeviceToast - Display a toast notification on a remote device.");
console.log(" GroupMessage - Open a message box on remote devices in a specific device group.");
console.log(" GroupToast - Display a toast notification on remote devices in a specific device group.");
console.log(" DevicePower - Perform wake/sleep/reset/off operations on remote devices."); console.log(" DevicePower - Perform wake/sleep/reset/off operations on remote devices.");
console.log(" DeviceSharing - View, add and remove sharing links for a given device."); console.log(" DeviceSharing - View, add and remove sharing links for a given device.");
console.log(" AgentDownload - Download an agent of a specific type for a device group."); console.log(" AgentDownload - Download an agent of a specific type for a device group.");
@ -107,6 +112,22 @@ if (args['_'].length == 0) {
else { ok = true; } else { ok = true; }
break; 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': { case 'addusertodevicegroup': {
if ((args.id == null) && (args.group == null)) { console.log(winRemoveSingleQuotes("Device group identifier missing, use --id '[groupid]' or --group [groupname]")); } 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]"); } else if (args.userid == null) { console.log("Add user to group missing useid, use --userid [userid]"); }
@ -236,10 +257,9 @@ if (args['_'].length == 0) {
} }
case 'agentdownload': { case 'agentdownload': {
if (args.type == null) { console.log(winRemoveSingleQuotes("Missing device type, use --type [agenttype]")); } if (args.type == null) { console.log(winRemoveSingleQuotes("Missing device type, use --type [agenttype]")); }
var at = parseInt(args.type); 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.")); }
if ((at == null) || isNaN(at) || (at < 1) || (at > 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]'")); }
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.")); }
if ((typeof args.id != 'string') || (args.id.length != 64)) { console.log(winRemoveSingleQuotes("Invalid meshid.")); }
else { ok = true; } else { ok = true; }
break; break;
} }
@ -258,6 +278,12 @@ if (args['_'].length == 0) {
else { ok = true; } else { ok = true; }
break; 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': { case 'deviceopenurl': {
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device id, use --id '[deviceid]'")); } 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."); } else if (args.openurl == null) { console.log("Remote URL, use --openurl [url] specify the link to open."); }
@ -276,6 +302,18 @@ if (args['_'].length == 0) {
else { ok = true; } else { ok = true; }
break; break;
} }
case 'groupmessage': {
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device group id, use --id '[devicegroupid]'")); }
else if (args.msg == null) { console.log("Remote message, use --msg \"[message]\" specify a remote message."); }
else { ok = true; }
break;
}
case 'grouptoast': {
if (args.id == null) { console.log(winRemoveSingleQuotes("Missing device group id, use --id '[devicegroupid]'")); }
else if (args.msg == null) { console.log("Remote message, use --msg \"[message]\" specify a remote message."); }
else { ok = true; }
break;
}
case 'report': { case 'report': {
if (args.type == null) { console.log(winRemoveSingleQuotes("Missing report type, use --type '[reporttype]'")); } if (args.type == null) { console.log(winRemoveSingleQuotes("Missing report type, use --type '[reporttype]'")); }
else { ok = true; } else { ok = true; }
@ -470,7 +508,7 @@ if (args['_'].length == 0) {
console.log(" --realname [name] - Set the real name for this account."); console.log(" --realname [name] - Set the real name for this account.");
console.log(" --phone [number] - Set the account phone number."); 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(" --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; break;
} }
case 'edituser': { case 'edituser': {
@ -487,7 +525,7 @@ if (args['_'].length == 0) {
console.log(" --realname [name] - Set the real name for this account."); console.log(" --realname [name] - Set the real name for this account.");
console.log(" --phone [number] - Set the account phone number."); 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(" --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; break;
} }
case 'removeuser': { case 'removeuser': {
@ -775,6 +813,55 @@ if (args['_'].length == 0) {
} }
break; 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': { case 'editdevice': {
console.log("Change information about a device, Example usages:\r\n"); console.log("Change information about a device, Example usages:\r\n");
console.log(winRemoveSingleQuotes(" MeshCtrl EditDevice --id 'deviceid' --name 'device1'")); console.log(winRemoveSingleQuotes(" MeshCtrl EditDevice --id 'deviceid' --name 'device1'"));
@ -806,6 +893,7 @@ if (args['_'].length == 0) {
console.log("Run a shell command on a remote device, Example usages:\r\n"); console.log("Run a shell command on a remote device, Example usages:\r\n");
console.log(winRemoveSingleQuotes(" MeshCtrl RunCommand --id 'deviceid' --run \"command\"")); console.log(winRemoveSingleQuotes(" MeshCtrl RunCommand --id 'deviceid' --run \"command\""));
console.log(winRemoveSingleQuotes(" MeshCtrl RunCommand --id 'deviceid' --run \"command\" --powershell")); console.log(winRemoveSingleQuotes(" MeshCtrl RunCommand --id 'deviceid' --run \"command\" --powershell"));
console.log(winRemoveSingleQuotes(" MeshCtrl RunCommand --id 'deviceid' --run \"command\" --reply"));
console.log("\r\nRequired arguments:\r\n"); console.log("\r\nRequired arguments:\r\n");
if (process.platform == 'win32') { if (process.platform == 'win32') {
console.log(" --id [deviceid] - The device identifier."); console.log(" --id [deviceid] - The device identifier.");
@ -817,6 +905,7 @@ if (args['_'].length == 0) {
console.log(" --powershell - Run in Windows PowerShell."); console.log(" --powershell - Run in Windows PowerShell.");
console.log(" --runasuser - Attempt to run the command as logged in user."); console.log(" --runasuser - Attempt to run the command as logged in user.");
console.log(" --runasuseronly - Only run the command as the logged in user."); console.log(" --runasuseronly - Only run the command as the logged in user.");
console.log(" --reply - Return with the output from running the command.");
break; break;
} }
case 'shell': { case 'shell': {
@ -865,6 +954,7 @@ if (args['_'].length == 0) {
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --start " + localISOTime + " --duration 30")); console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --start " + localISOTime + " --duration 30"));
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --start " + localISOTime + " --duration 30 --daily")); console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --start " + localISOTime + " --duration 30 --daily"));
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --type desktop,terminal --consent prompt")); console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --type desktop,terminal --consent prompt"));
console.log(winRemoveSingleQuotes(" MeshCtrl DeviceSharing --id 'deviceid' --add Guest --type http --port 80"));
console.log("\r\nRequired arguments:\r\n"); console.log("\r\nRequired arguments:\r\n");
if (process.platform == 'win32') { if (process.platform == 'win32') {
console.log(" --id [deviceid] - The device identifier."); console.log(" --id [deviceid] - The device identifier.");
@ -872,21 +962,23 @@ if (args['_'].length == 0) {
console.log(" --id '[deviceid]' - The device identifier."); console.log(" --id '[deviceid]' - The device identifier.");
} }
console.log("\r\nOptional arguments:\r\n"); console.log("\r\nOptional arguments:\r\n");
console.log(" --remove [shareid] - Remove a device sharing link."); console.log(" --remove [shareid] - Remove a device sharing link.");
console.log(" --add [guestname] - Add a device sharing link."); console.log(" --add [guestname] - Add a device sharing link.");
console.log(" --type [desktop,terminal,files] - Type of sharing to add, can be combined. default is desktop."); console.log(" --type [desktop,terminal,files,http,https] - Type of sharing to add, can be combined. default is desktop.");
console.log(" --viewonly - Make desktop sharing view only."); console.log(" --viewonly - Make desktop sharing view only.");
console.log(" --consent [notify,prompt] - Consent flags, default is notify."); console.log(" --consent [notify,prompt,none] - Consent flags, default is notify.");
console.log(" --start [yyyy-mm-ddThh:mm:ss] - Start time, default is now."); console.log(" --start [yyyy-mm-ddThh:mm:ss] - Start time, default is now.");
console.log(" --end [yyyy-mm-ddThh:mm:ss] - End time."); console.log(" --end [yyyy-mm-ddThh:mm:ss] - End time.");
console.log(" --duration [minutes] - Duration of the share, default is 60 minutes."); console.log(" --duration [minutes] - Duration of the share, default is 60 minutes.");
console.log(" --daily - Add recurring daily device share."); console.log(" --daily - Add recurring daily device share.");
console.log(" --weekly - Add recurring weekly device share."); console.log(" --weekly - Add recurring weekly device share.");
console.log(" --port [portnumber] - Set alternative port for http or https, default is 80 for http and 443 for https.");
break; break;
} }
case 'agentdownload': { case 'agentdownload': {
console.log("Download an agent of a specific type for a given device group, Example usages:\r\n"); 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"));
console.log(winRemoveSingleQuotes(" MeshCtrl AgentDownload --id 'groupid' --type 3 --installflags 1"));
console.log("\r\nRequired arguments:\r\n"); console.log("\r\nRequired arguments:\r\n");
console.log(" --type [ArchitectureNumber] - Agent architecture number."); console.log(" --type [ArchitectureNumber] - Agent architecture number.");
if (process.platform == 'win32') { if (process.platform == 'win32') {
@ -894,6 +986,11 @@ if (args['_'].length == 0) {
} else { } else {
console.log(" --id '[groupid]' - The device group identifier."); 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; break;
} }
case 'upload': { case 'upload': {
@ -925,6 +1022,21 @@ if (args['_'].length == 0) {
console.log(" --target [localpath] - The local path to download the file to."); console.log(" --target [localpath] - The local path to download the file to.");
break; 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': { case 'deviceopenurl': {
console.log("Open a web page on a remote device, Example usages:\r\n"); 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")); console.log(winRemoveSingleQuotes(" MeshCtrl DeviceOpenUrl --id 'deviceid' --openurl http://meshcentral.com"));
@ -969,6 +1081,38 @@ if (args['_'].length == 0) {
console.log(" --title [title] - Toast title, default is \"MeshCentral\"."); console.log(" --title [title] - Toast title, default is \"MeshCentral\".");
break; break;
} }
case 'groupmessage': {
console.log("Open a message box on remote devices in a specific device group, Example usages:\r\n");
console.log(winRemoveSingleQuotes(" MeshCtrl GroupMessage --id 'devicegroupid' --msg \"message\""));
console.log(winRemoveSingleQuotes(" MeshCtrl GroupMessage --id 'devicegroupid' --msg \"message\" --title \"title\""));
console.log(winRemoveSingleQuotes(" MeshCtrl GroupMessage --id 'devicegroupid' --msg \"message\" --title \"title\" --timeout 120000"));
console.log("\r\nRequired arguments:\r\n");
if (process.platform == 'win32') {
console.log(" --id [devicegroupid] - The device identifier.");
} else {
console.log(" --id '[devicegroupid]' - The device identifier.");
}
console.log(" --msg [message] - The message to display.");
console.log("\r\nOptional arguments:\r\n");
console.log(" --title [title] - Messagebox title, default is \"MeshCentral\".");
console.log(" --timeout [miliseconds] - After timeout messagebox vanishes, 0 keeps messagebox open until closed manually, default is 120000 (2 Minutes).");
break;
}
case 'grouptoast': {
console.log("Display a toast notification on remote devices in a specific device group, Example usages:\r\n");
console.log(winRemoveSingleQuotes(" MeshCtrl GroupToast --id 'devicegroupid' --msg \"message\""));
console.log(winRemoveSingleQuotes(" MeshCtrl GroupToast --id 'devicegroupid' --msg \"message\" --title \"title\""));
console.log("\r\nRequired arguments:\r\n");
if (process.platform == 'win32') {
console.log(" --id [devicegroupid] - The device identifier.");
} else {
console.log(" --id '[devicegroupid]' - The device identifier.");
}
console.log(" --msg [message] - The message to display.");
console.log("\r\nOptional arguments:\r\n");
console.log(" --title [title] - Toast title, default is \"MeshCentral\".");
break;
}
case 'report': { case 'report': {
console.log("Generate a CSV report, Example usages:\r\n"); console.log("Generate a CSV report, Example usages:\r\n");
console.log(" MeshCtrl Report --type sessions --devicegroup mesh//..."); console.log(" MeshCtrl Report --type sessions --devicegroup mesh//...");
@ -1067,7 +1211,7 @@ function performConfigOperations(args) {
if (fs.existsSync(configFile) == false) { console.log("Unable to find config.json."); return; } if (fs.existsSync(configFile) == false) { console.log("Unable to find config.json."); return; }
var config = null; var config = null;
try { config = fs.readFileSync(configFile).toString('utf8'); } catch (ex) { console.log("Error: Unable to read config.json"); return; } 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) { if (args.adddomain != null) {
didSomething++; didSomething++;
if (config.domains == null) { config.domains = {}; } if (config.domains == null) { config.domains = {}; }
@ -1215,10 +1359,10 @@ function serverConnect() {
var domainid = '', username = 'admin'; var domainid = '', username = 'admin';
if (args.logindomain != null) { domainid = args.logindomain; } if (args.logindomain != null) { domainid = args.logindomain; }
if (args.loginuser != null) { username = args.loginuser; } 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 { } else {
if (args.logindomain != null) { console.log("--logindomain can only be used along with --loginkey."); process.exit(); return; } 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); const ws = new WebSocket(url, options);
@ -1435,6 +1579,29 @@ function serverConnect() {
ws.send(JSON.stringify(op)); ws.send(JSON.stringify(op));
break; 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': { case 'editdevicegroup': {
var op = { action: 'editmesh', responseid: 'meshctrl' }; var op = { action: 'editmesh', responseid: 'meshctrl' };
if (args.id) { op.meshid = args.id; } else if (args.group) { op.meshidname = args.group; } if (args.id) { op.meshid = args.id; } else if (args.group) { op.meshidname = args.group; }
@ -1573,7 +1740,9 @@ function serverConnect() {
case 'runcommand': { case 'runcommand': {
var runAsUser = 0; var runAsUser = 0;
if (args.runasuser) { runAsUser = 1; } else if (args.runasuseronly) { runAsUser = 2; } if (args.runasuser) { runAsUser = 1; } else if (args.runasuseronly) { runAsUser = 2; }
ws.send(JSON.stringify({ action: 'runcommands', nodeids: [args.id], type: ((args.powershell) ? 2 : 0), cmds: args.run, responseid: 'meshctrl', runAsUser: runAsUser })); var reply = false;
if (args.reply) { reply = true; }
ws.send(JSON.stringify({ action: 'runcommands', nodeids: [args.id], type: ((args.powershell) ? 2 : 0), cmds: args.run, responseid: 'meshctrl', runAsUser: runAsUser, reply: reply }));
break; break;
} }
case 'shell': case 'shell':
@ -1616,6 +1785,10 @@ function serverConnect() {
var u = settings.xxurl.replace('wss://', 'https://').replace('/control.ashx', '/meshagents'); var u = settings.xxurl.replace('wss://', 'https://').replace('/control.ashx', '/meshagents');
if (u.indexOf('?') > 0) { u += '&'; } else { u += '?'; } if (u.indexOf('?') > 0) { u += '&'; } else { u += '?'; }
u += 'id=' + args.type + '&meshid=' + args.id; 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 options = { rejectUnauthorized: false, checkServerIdentity: onVerifyServer }
const fs = require('fs'); const fs = require('fs');
const https = require('https'); const https = require('https');
@ -1653,6 +1826,29 @@ function serverConnect() {
req.end() req.end()
break; 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': { case 'devicesharing': {
if (args.add) { if (args.add) {
if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); } if (args.add.length == 0) { console.log("Invalid guest name."); process.exit(1); }
@ -1661,10 +1857,12 @@ function serverConnect() {
var p = 0; var p = 0;
if (args.type != null) { if (args.type != null) {
var shareTypes = args.type.toLowerCase().split(','); var shareTypes = args.type.toLowerCase().split(',');
for (var i in shareTypes) { if ((shareTypes[i] != 'terminal') && (shareTypes[i] != 'desktop') && (shareTypes[i] != 'files')) { console.log("Unknown sharing type: " + shareTypes[i]); process.exit(1); } } for (var i in shareTypes) { if ((shareTypes[i] != 'terminal') && (shareTypes[i] != 'desktop') && (shareTypes[i] != 'files') && (shareTypes[i] != 'http') && (shareTypes[i] != 'https')) { console.log("Unknown sharing type: " + shareTypes[i]); process.exit(1); } }
if (shareTypes.indexOf('terminal') >= 0) { p |= 1; } if (shareTypes.indexOf('terminal') >= 0) { p |= 1; }
if (shareTypes.indexOf('desktop') >= 0) { p |= 2; } if (shareTypes.indexOf('desktop') >= 0) { p |= 2; }
if (shareTypes.indexOf('files') >= 0) { p |= 4; } if (shareTypes.indexOf('files') >= 0) { p |= 4; }
if (shareTypes.indexOf('http') >= 0) { p |= 8; }
if (shareTypes.indexOf('https') >= 0) { p |= 16; }
} }
if (p == 0) { p = 2; } // Desktop if (p == 0) { p = 2; } // Desktop
@ -1699,6 +1897,19 @@ function serverConnect() {
} }
} }
var port = null;
// Set Port Number if http or https
if ((p & 8) || (p & 16)) {
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 ((p & 8)) {
port = 80;
} else if ((p & 16)) {
port = 443;
}
}
// Start and end time // Start and end time
var start = null, end = null; var start = null, end = null;
if (args.start) { start = Math.floor(Date.parse(args.start) / 1000); end = start + (60 * 60); } if (args.start) { start = Math.floor(Date.parse(args.start) / 1000); end = start + (60 * 60); }
@ -1715,14 +1926,14 @@ function serverConnect() {
if ((typeof args.duration != 'number') || (args.duration < 1)) { console.log("Invalid duration value."); process.exit(1); return; } if ((typeof args.duration != 'number') || (args.duration < 1)) { console.log("Invalid duration value."); process.exit(1); return; }
// Recurring sharing // Recurring sharing
ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, start: start, expire: args.duration, recurring: recurring, viewOnly: viewOnly, responseid: 'meshctrl' })); ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, start: start, expire: args.duration, recurring: recurring, viewOnly: viewOnly, port: port, responseid: 'meshctrl' }));
} else { } else {
if ((start == null) && (end == null)) { if ((start == null) && (end == null)) {
// Unlimited sharing // Unlimited sharing
ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, expire: 0, viewOnly: viewOnly, responseid: 'meshctrl' })); ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, expire: 0, viewOnly: viewOnly, port: port, responseid: 'meshctrl' }));
} else { } else {
// Time limited sharing // Time limited sharing
ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, start: start, end: end, viewOnly: viewOnly, responseid: 'meshctrl' })); ws.send(JSON.stringify({ action: 'createDeviceShareLink', nodeid: args.id, guestname: args.add, p: p, consent: consent, start: start, end: end, viewOnly: viewOnly, port: port, responseid: 'meshctrl' }));
} }
} }
} else if (args.remove) { } else if (args.remove) {
@ -1744,6 +1955,14 @@ function serverConnect() {
ws.send(JSON.stringify({ action: 'toast', nodeids: [args.id], title: args.title ? args.title : "MeshCentral", msg: args.msg, responseid: 'meshctrl' })); ws.send(JSON.stringify({ action: 'toast', nodeids: [args.id], title: args.title ? args.title : "MeshCentral", msg: args.msg, responseid: 'meshctrl' }));
break; break;
} }
case 'groupmessage': {
ws.send(JSON.stringify({ action: 'nodes', meshid: args.id, responseid: 'meshctrl' }));
break;
}
case 'grouptoast': {
ws.send(JSON.stringify({ action: 'nodes', meshid: args.id, responseid: 'meshctrl' }));
break;
}
case 'report': { case 'report': {
var reporttype = 1; var reporttype = 1;
switch(args.type) { switch(args.type) {
@ -1791,11 +2010,11 @@ function serverConnect() {
var srights = args.rights.toLowerCase().split(','); var srights = args.rights.toLowerCase().split(',');
if (srights.indexOf('full') != -1) { siteadmin = 0xFFFFFFFF; } if (srights.indexOf('full') != -1) { siteadmin = 0xFFFFFFFF; }
if (srights.indexOf('none') != -1) { siteadmin = 0x00000000; } 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('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('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('locked') != -1) { siteadmin |= 0x00000020; }
if (srights.indexOf('nonewgroups') != -1) { siteadmin |= 0x00000040; } if (srights.indexOf('nonewgroups') != -1) { siteadmin |= 0x00000040; }
if (srights.indexOf('notools') != -1) { siteadmin |= 0x00000080; } if (srights.indexOf('notools') != -1) { siteadmin |= 0x00000080; }
@ -1803,6 +2022,7 @@ function serverConnect() {
if (srights.indexOf('recordings') != -1) { siteadmin |= 0x00000200; } if (srights.indexOf('recordings') != -1) { siteadmin |= 0x00000200; }
if (srights.indexOf('locksettings') != -1) { siteadmin |= 0x00000400; } if (srights.indexOf('locksettings') != -1) { siteadmin |= 0x00000400; }
if (srights.indexOf('allevents') != -1) { siteadmin |= 0x00000800; } if (srights.indexOf('allevents') != -1) { siteadmin |= 0x00000800; }
if (srights.indexOf('nonewdevices') != -1) { siteadmin |= 0x00001000; }
} }
if (args.siteadmin) { siteadmin = 0xFFFFFFFF; } if (args.siteadmin) { siteadmin = 0xFFFFFFFF; }
@ -1999,6 +2219,8 @@ function serverConnect() {
case 'toast': // TOAST case 'toast': // TOAST
case 'adduser': // ADDUSER case 'adduser': // ADDUSER
case 'edituser': // EDITUSER case 'edituser': // EDITUSER
case 'addamtdevice': // ADDAMTDEVICE
case 'addlocaldevice': // ADDLOCALDEVICE
case 'removedevices': // REMOVEDEVICE case 'removedevices': // REMOVEDEVICE
case 'changedevice': // EDITDEVICE case 'changedevice': // EDITDEVICE
case 'deleteuser': // REMOVEUSER case 'deleteuser': // REMOVEUSER
@ -2021,6 +2243,7 @@ function serverConnect() {
case 'removeDeviceShare': case 'removeDeviceShare':
case 'userbroadcast': { // BROADCAST case 'userbroadcast': { // BROADCAST
if ((settings.cmd == 'shell') || (settings.cmd == 'upload') || (settings.cmd == 'download')) return; 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 ((settings.multiresponse != null) && (settings.multiresponse > 1)) { settings.multiresponse--; break; }
if (data.responseid == 'meshctrl') { if (data.responseid == 'meshctrl') {
if (data.meshid) { console.log(data.result, data.meshid); } if (data.meshid) { console.log(data.result, data.meshid); }
@ -2031,6 +2254,7 @@ function serverConnect() {
break; break;
} }
case 'createDeviceShareLink': case 'createDeviceShareLink':
case 'webrelay':
if (data.result == 'OK') { if (data.result == 'OK') {
if (data.publicid) { console.log('ID: ' + data.publicid); } if (data.publicid) { console.log('ID: ' + data.publicid); }
console.log('URL: ' + data.url); console.log('URL: ' + data.url);
@ -2165,7 +2389,7 @@ function serverConnect() {
if (args.filter != null) { if (args.filter != null) {
for (var meshid in data.nodes) { for (var meshid in data.nodes) {
for (var d in data.nodes[meshid]) { data.nodes[meshid][d].meshid = meshid; } 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());
} }
} }
@ -2223,6 +2447,30 @@ function serverConnect() {
} }
process.exit(); process.exit();
} }
if ((settings.cmd == 'groupmessage') && (data.responseid == 'meshctrl')) {
if ((data.nodes != null)) {
for (var i in data.nodes) {
for (let index = 0; index < data.nodes[i].length; index++) {
const element = data.nodes[i][index];
ws.send(JSON.stringify({ action: 'msg', type: 'messagebox', nodeid: element._id, title: args.title ? args.title : "MeshCentral", msg: args.msg, timeout: args.timeout ? args.timeout : 120000 }));
}
}
}
setTimeout(function(){ console.log('ok'); process.exit(); }, 1000);
}
if ((settings.cmd == 'grouptoast') && (data.responseid == 'meshctrl')) {
if (data.nodes != null) {
for (var i in data.nodes) {
var nodes = [];
for (let index = 0; index < data.nodes[i].length; index++) {
const element = data.nodes[i][index];
nodes.push(element._id);
}
ws.send(JSON.stringify({ action: 'toast', nodeids: nodes, title: args.title ? args.title : "MeshCentral", msg: args.msg, responseid: 'meshctrl' }));
}
}
}
break; break;
} }
case 'meshes': { // LISTDEVICEGROUPS case 'meshes': { // LISTDEVICEGROUPS
@ -2292,6 +2540,8 @@ function serverConnect() {
if (data.cause == 'noauth') { if (data.cause == 'noauth') {
if (data.msg == 'tokenrequired') { if (data.msg == 'tokenrequired') {
console.log('Authentication token required, use --token [number].'); 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 { } else {
if ((args.loginkeyfile != null) || (args.loginkey != null)) { if ((args.loginkeyfile != null) || (args.loginkey != null)) {
console.log('Invalid login, check the login key and that this computer has the correct time.'); console.log('Invalid login, check the login key and that this computer has the correct time.');
@ -2416,8 +2666,8 @@ function getDevicesThatMatchFilter(nodes, x) {
} else if (tagSearch != null) { } else if (tagSearch != null) {
// Tag filter // Tag filter
for (var d in nodes) { for (var d in nodes) {
if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(d); } 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(d); break; } } } 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) { } else if (agentTagSearch != null) {
// Agent Tag filter // Agent Tag filter
@ -2655,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"); console.log("Invalid device id");
process.exit(); return; process.exit(); return;
} }

View file

@ -847,7 +847,7 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, id, func) {
return; return;
} }
// Write the recording file header // 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 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); var firstBlock = JSON.stringify(metadata);
recordingEntry(fd, 1, 0, firstBlock, function () { 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.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 ((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.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 == 'object') {
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; } if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }

View file

@ -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); } }); 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 } 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 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 headers = { 'User-Agent': 'MeshCentral v' + parent.currentVer };
const req = require('https').request(new URL(url), { method: 'POST', headers: headers }, function (res) { if (func != null) { func(true); } }); if (typeof parent.config.messaging.ntfy.authorization == 'string') { headers['Authorization'] = parent.config.messaging.ntfy.authorization; }
req.on('error', function (err) { if (func != null) { func(false); } }); const req = require('https').request(new URL(url), { method: 'POST', headers: headers }, function (res) { if (func != null) { func(res.statusCode == 200); } });
req.end(msg); req.end(msg);
} else if ((to.startsWith('zulip:')) && (obj.zulipClient != null)) { // zulip } else if ((to.startsWith('zulip:')) && (obj.zulipClient != null)) { // zulip
obj.zulipClient.sendMessage({ obj.zulipClient.sendMessage({

View file

@ -119,6 +119,9 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
try { sr = parseInt(req.query.slowrelay); } catch (ex) { } try { sr = parseInt(req.query.slowrelay); } catch (ex) { }
if ((typeof sr == 'number') && (sr > 0) && (sr < 1000)) { obj.ws.slowRelay = sr; } 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 // Mesh Rights
const MESHRIGHT_EDITMESH = 1; const MESHRIGHT_EDITMESH = 1;
@ -442,15 +445,15 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
relayinfo.peer1.sendPeerImage(); relayinfo.peer1.sendPeerImage();
} else { } else {
// Write the recording file header // 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 = { var metadata = {
magic: 'MeshCentralRelaySession', magic: 'MeshCentralRelaySession',
ver: 1, ver: 1,
userid: sessionUser._id, userid: sessionUser._id,
username: sessionUser.name, username: sessionUser.name,
sessionid: obj.id, sessionid: obj.id,
ipaddr1: (obj.req == null) ? null : obj.req.clientIp, ipaddr1: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp,
ipaddr2: ((obj.peer == null) || (obj.peer.req == null)) ? null : obj.peer.req.clientIp, ipaddr2: (obj.req == null) ? null : obj.req.clientIp,
time: new Date().toLocaleString(), time: new Date().toLocaleString(),
protocol: (((obj.req == null) || (obj.req.query == null)) ? null : obj.req.query.p), 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) 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; } if (user != null) { rcookieData.ruserid = user._id; } else if (obj.nouser === true) { rcookieData.nouser = 1; }
const rcookie = parent.parent.encodeCookie(rcookieData, parent.parent.loginCookieEncryptionKey); 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. 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 (user) { command.userid = user._id; }
if (typeof domain.consentmessages == 'object') { if (typeof domain.consentmessages == 'object') {
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; } 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.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 ((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.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 == 'object') {
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; } 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. 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); const rcookie = parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey);
if (obj.req.query.tcpport != null) { 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 == 'object') {
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; } 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.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.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 ((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.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 == 'object') {
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; } 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)); 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 + ')'); } 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) { } 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: {} }; 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 == 'object') {
if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; } 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.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
if (typeof domain.consentmessages.terminal == 'string') { command.soptions.consentMsgTerminal = domain.consentmessages.terminal; } 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.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 ((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.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 == 'object') {
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; } 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.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 ((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.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 == 'object') {
if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; } 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 == 11) { protocolStr = 'SSH-TERM'; }
else if (req.query.p == 12) { protocolStr = 'VNC'; } else if (req.query.p == 12) { protocolStr = 'VNC'; }
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; } 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 }; 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. 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); 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 == 11) { protocolStr = 'SSH-TERM'; }
else if (req.query.p == 12) { protocolStr = 'VNC'; } else if (req.query.p == 12) { protocolStr = 'VNC'; }
else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; } else if (req.query.p == 13) { protocolStr = 'SSH-FILES'; }
else if (req.query.p == 14) { protocolStr = 'Web-TCP'; }
obj.time = Date.now(); 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 }; 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. if (obj.guestname) { event.guestname = obj.guestname; } // If this is a sharing session, set the guest name here.

File diff suppressed because it is too large Load diff

117
monitoring.js Normal file
View 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;
}

View file

@ -586,6 +586,20 @@ module.exports.CreateMultiServer = function (parent, args) {
} }
break; 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: { default: {
// Unknown peer server command // Unknown peer server command
console.log('Unknown action from peer server ' + peerServerId + ': ' + msg.action + '.'); 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(); }); 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?) // 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 // Register the connection event
peerTunnel.ws2.on('open', function () { peerTunnel.ws2.on('open', function () {

2225
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "1.1.17", "version": "1.1.42",
"keywords": [ "keywords": [
"Remote Device Management", "Remote Device Management",
"Remote Device Monitoring", "Remote Device Monitoring",
@ -37,27 +37,25 @@
"sample-config-advanced.json" "sample-config-advanced.json"
], ],
"dependencies": { "dependencies": {
"@yetzt/nedb": "1.8.0", "@seald-io/nedb": "4.0.4",
"archiver": "5.3.2", "archiver": "7.0.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"cbor": "5.2.0", "cbor": "5.2.0",
"compression": "1.7.4", "compression": "1.7.5",
"cookie-session": "2.0.0", "cookie-session": "2.1.0",
"express": "4.18.2", "express": "4.21.2",
"express-handlebars": "5.3.5", "express-handlebars": "7.1.3",
"express-ws": "4.0.0", "express-ws": "5.0.2",
"ipcheck": "0.1.0", "ipcheck": "0.1.0",
"minimist": "1.2.8", "minimist": "1.2.8",
"multiparty": "4.2.3", "multiparty": "4.2.3",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"otplib": "10.2.3", "ua-parser-js": "1.0.39",
"ssh2": "1.15.0", "ws": "8.18.0",
"ua-parser-js": "1.0.37",
"ws": "8.14.2",
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
"engines": { "engines": {
"node": ">=11.0.0" "node": ">=16.0.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -139,7 +139,7 @@ module.exports.pluginHandler = function (parent) {
try { try {
obj.plugins[p][hookName](...args); obj.plugins[p][hookName](...args);
} catch (e) { } 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].header = obj.plugins[p].on_device_header();
panel[p].content = obj.plugins[p].on_device_page(); panel[p].content = obj.plugins[p].on_device_page();
} catch (e) { } 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; if (force_url != null) dl_url = force_url;
var url = require('url'); var url = require('url');
var q = url.parse(dl_url, true); 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 = { var opts = {
path: q.pathname, path: q.pathname,
host: q.hostname, host: q.hostname,
@ -409,7 +409,13 @@ module.exports.pluginHandler = function (parent) {
zipfile.openReadStream(entry, function (err, readStream) { zipfile.openReadStream(entry, function (err, readStream) {
if (err) throw err; if (err) throw err;
readStream.on('end', function () { zipfile.readEntry(); }); readStream.on('end', function () { zipfile.readEntry(); });
readStream.pipe(obj.fs.createWriteStream(filePath)); if (process.platform == 'win32') {
readStream.pipe(obj.fs.createWriteStream(filePath));
} else {
var fileMode = (entry.externalFileAttributes >> 16) & 0x0fff;
if( fileMode <= 0 ) fileMode = 0o644;
readStream.pipe(obj.fs.createWriteStream(filePath, { mode: fileMode }));
}
}); });
} }
}); });
@ -451,7 +457,7 @@ module.exports.pluginHandler = function (parent) {
if (plugin.versionHistoryUrl == null) reject("No version history available for this plugin."); if (plugin.versionHistoryUrl == null) reject("No version history available for this plugin.");
var url = require('url'); var url = require('url');
var q = url.parse(plugin.versionHistoryUrl, true); 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 = { var opts = {
path: q.pathname, path: q.pathname,
host: q.hostname, host: q.hostname,
@ -535,4 +541,4 @@ module.exports.pluginHandler = function (parent) {
} }
} }
return obj; return obj;
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/images/key16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -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"
}
]
}

View file

@ -16,6 +16,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
* added get clipboard from remote RDP - Simon Smith 2024
* added set clipboard to remote RDP - Simon Smith 2024
*/
(function() { (function() {
/** /**
@ -173,6 +177,20 @@
options: options, options: options,
locale: Mstsc.locale() locale: Mstsc.locale()
}])); }]));
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){
self.prevClipboard = data;
if (self.socket) { self.socket.send(JSON.stringify(['clipboard', data])); }
}
})
.catch(function(){ });
}
}, 1000);
}; };
this.socket.onmessage = function (evt) { this.socket.onmessage = function (evt) {
if (typeof evt.data == 'string') { if (typeof evt.data == 'string') {
@ -206,6 +224,14 @@
next(err); next(err);
break; break;
} }
case 'rdp-clipboard': {
if ((msg[1] != null) && (navigator.clipboard.writeText != null)) {
navigator.clipboard.writeText(msg[1]) // Put remote clipboard data into our clipboard
.then(function() { })
.catch(function(err) { console.log('clipboard.writeText Error', err); });
}
break;
}
} }
} else { } else {
// This is binary bitmap data, store it. // This is binary bitmap data, store it.
@ -215,6 +241,8 @@
this.socket.onclose = function () { this.socket.onclose = function () {
//console.log("WS-CLOSE"); //console.log("WS-CLOSE");
self.activeSession = false; self.activeSession = false;
clearInterval(self.clipboardReadTimer);
self.prevClipboardText = null;
next(null); next(null);
}; };
} }

View file

@ -1,4 +1,5 @@
{ {
"HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
"Connecting...": "Συνδέεται...", "Connecting...": "Συνδέεται...",
"Disconnecting...": "Aποσυνδέεται...", "Disconnecting...": "Aποσυνδέεται...",
"Reconnecting...": "Επανασυνδέεται...", "Reconnecting...": "Επανασυνδέεται...",
@ -7,19 +8,15 @@
"Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
"Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
"Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
"Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
"Disconnected": "Αποσυνδέθηκε", "Disconnected": "Αποσυνδέθηκε",
"New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
"New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
"Password is required": "Απαιτείται ο κωδικός πρόσβασης", "Credentials are required": "Απαιτούνται διαπιστευτήρια",
"noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
"Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
"Drag": "Σύρσιμο",
"Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
"viewport drag": "σύρσιμο θεατού πεδίου",
"Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
"No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
"Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
"Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
"Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
"Keyboard": "Πληκτρολόγιο", "Keyboard": "Πληκτρολόγιο",
"Show Keyboard": "Εμφάνιση Πληκτρολογίου", "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
"Extra keys": "Επιπλέον πλήκτρα", "Extra keys": "Επιπλέον πλήκτρα",
@ -28,6 +25,8 @@
"Toggle Ctrl": "Εναλλαγή Ctrl", "Toggle Ctrl": "Εναλλαγή Ctrl",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Εναλλαγή Alt", "Toggle Alt": "Εναλλαγή Alt",
"Toggle Windows": "Εναλλαγή Παράθυρων",
"Windows": "Παράθυρα",
"Send Tab": "Αποστολή Tab", "Send Tab": "Αποστολή Tab",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
@ -41,8 +40,7 @@
"Reboot": "Επανεκκίνηση", "Reboot": "Επανεκκίνηση",
"Reset": "Επαναφορά", "Reset": "Επαναφορά",
"Clipboard": "Πρόχειρο", "Clipboard": "Πρόχειρο",
"Clear": "Καθάρισμα", "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
"Fullscreen": "Πλήρης Οθόνη",
"Settings": "Ρυθμίσεις", "Settings": "Ρυθμίσεις",
"Shared Mode": "Κοινόχρηστη Λειτουργία", "Shared Mode": "Κοινόχρηστη Λειτουργία",
"View Only": "Μόνο Θέαση", "View Only": "Μόνο Θέαση",
@ -52,6 +50,8 @@
"Local Scaling": "Τοπική Κλιμάκωση", "Local Scaling": "Τοπική Κλιμάκωση",
"Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
"Advanced": "Για προχωρημένους", "Advanced": "Για προχωρημένους",
"Quality:": "Ποιότητα:",
"Compression level:": "Επίπεδο συμπίεσης:",
"Repeater ID:": "Repeater ID:", "Repeater ID:": "Repeater ID:",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "Κρυπτογράφηση", "Encrypt": "Κρυπτογράφηση",
@ -60,10 +60,20 @@
"Path:": "Διαδρομή:", "Path:": "Διαδρομή:",
"Automatic Reconnect": "Αυτόματη επανασύνδεση", "Automatic Reconnect": "Αυτόματη επανασύνδεση",
"Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
"Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
"Logging:": "Καταγραφή:", "Logging:": "Καταγραφή:",
"Version:": "Έκδοση:",
"Disconnect": "Αποσύνδεση", "Disconnect": "Αποσύνδεση",
"Connect": "Σύνδεση", "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:": "Κωδικός Πρόσβασης:", "Password:": "Κωδικός Πρόσβασης:",
"Cancel": "Ακύρωση", "Send Credentials": "Αποστολή Διαπιστευτηρίων",
"Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" "Cancel": "Ακύρωση"
} }

View file

@ -1,5 +1,4 @@
{ {
"HTTPS is required for full functionality": "",
"Connecting...": "En cours de connexion...", "Connecting...": "En cours de connexion...",
"Disconnecting...": "Déconnexion en cours...", "Disconnecting...": "Déconnexion en cours...",
"Reconnecting...": "Reconnexion en cours...", "Reconnecting...": "Reconnexion en cours...",
@ -40,7 +39,8 @@
"Reboot": "Redémarrer", "Reboot": "Redémarrer",
"Reset": "Réinitialiser", "Reset": "Réinitialiser",
"Clipboard": "Presse-papiers", "Clipboard": "Presse-papiers",
"Edit clipboard content in the textarea below.": "", "Clear": "Effacer",
"Fullscreen": "Plein écran",
"Settings": "Paramètres", "Settings": "Paramètres",
"Shared Mode": "Mode partagé", "Shared Mode": "Mode partagé",
"View Only": "Afficher uniquement", "View Only": "Afficher uniquement",
@ -65,12 +65,6 @@
"Version:": "Version :", "Version:": "Version :",
"Disconnect": "Déconnecter", "Disconnect": "Déconnecter",
"Connect": "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 :", "Username:": "Nom d'utilisateur :",
"Password:": "Mot de passe :", "Password:": "Mot de passe :",
"Send Credentials": "Envoyer les identifiants", "Send Credentials": "Envoyer les identifiants",

View file

@ -14,8 +14,6 @@
"Credentials are required": "Le credenziali sono obbligatorie", "Credentials are required": "Le credenziali sono obbligatorie",
"noVNC encountered an error:": "noVNC ha riscontrato un errore:", "noVNC encountered an error:": "noVNC ha riscontrato un errore:",
"Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
"Drag": "",
"Move/Drag Viewport": "",
"Keyboard": "Tastiera", "Keyboard": "Tastiera",
"Show Keyboard": "Mostra tastiera", "Show Keyboard": "Mostra tastiera",
"Extra keys": "Tasti Aggiuntivi", "Extra keys": "Tasti Aggiuntivi",
@ -44,7 +42,6 @@
"Settings": "Impostazioni", "Settings": "Impostazioni",
"Shared Mode": "Modalità condivisa", "Shared Mode": "Modalità condivisa",
"View Only": "Sola Visualizzazione", "View Only": "Sola Visualizzazione",
"Clip to Window": "",
"Scaling Mode:": "Modalità di ridimensionamento:", "Scaling Mode:": "Modalità di ridimensionamento:",
"None": "Nessuna", "None": "Nessuna",
"Local Scaling": "Ridimensionamento Locale", "Local Scaling": "Ridimensionamento Locale",
@ -61,7 +58,6 @@
"Automatic Reconnect": "Riconnessione Automatica", "Automatic Reconnect": "Riconnessione Automatica",
"Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
"Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
"Logging:": "",
"Version:": "Versione:", "Version:": "Versione:",
"Disconnect": "Disconnetti", "Disconnect": "Disconnetti",
"Connect": "Connetti", "Connect": "Connetti",

View file

@ -1,4 +1,5 @@
{ {
"HTTPS is required for full functionality": "すべての機能を使用するにはHTTPS接続が必要です",
"Connecting...": "接続しています...", "Connecting...": "接続しています...",
"Disconnecting...": "切断しています...", "Disconnecting...": "切断しています...",
"Reconnecting...": "再接続しています...", "Reconnecting...": "再接続しています...",
@ -21,10 +22,10 @@
"Extra keys": "追加キー", "Extra keys": "追加キー",
"Show Extra Keys": "追加キーを表示", "Show Extra Keys": "追加キーを表示",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "Ctrl キーを切り替え", "Toggle Ctrl": "Ctrl キーをトグル",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "Alt キーを切り替え", "Toggle Alt": "Alt キーをトグル",
"Toggle Windows": "Windows キーを切り替え", "Toggle Windows": "Windows キーをトグル",
"Windows": "Windows", "Windows": "Windows",
"Send Tab": "Tab キーを送信", "Send Tab": "Tab キーを送信",
"Tab": "Tab", "Tab": "Tab",
@ -39,11 +40,11 @@
"Reboot": "再起動", "Reboot": "再起動",
"Reset": "リセット", "Reset": "リセット",
"Clipboard": "クリップボード", "Clipboard": "クリップボード",
"Clear": "クリア", "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
"Fullscreen": "全画面表示", "Full Screen": "全画面表示",
"Settings": "設定", "Settings": "設定",
"Shared Mode": "共有モード", "Shared Mode": "共有モード",
"View Only": "表示のみ", "View Only": "表示専用",
"Clip to Window": "ウィンドウにクリップ", "Clip to Window": "ウィンドウにクリップ",
"Scaling Mode:": "スケーリングモード:", "Scaling Mode:": "スケーリングモード:",
"None": "なし", "None": "なし",
@ -60,11 +61,18 @@
"Path:": "パス:", "Path:": "パス:",
"Automatic Reconnect": "自動再接続", "Automatic Reconnect": "自動再接続",
"Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
"Show Dot when No Cursor": "カーソルがないときにドットを表示", "Show Dot when No Cursor": "カーソルがないときにドットを表示する",
"Logging:": "ロギング:", "Logging:": "ロギング:",
"Version:": "バージョン:", "Version:": "バージョン:",
"Disconnect": "切断", "Disconnect": "切断",
"Connect": "接続", "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:": "ユーザー名:", "Username:": "ユーザー名:",
"Password:": "パスワード:", "Password:": "パスワード:",
"Send Credentials": "資格情報を送信", "Send Credentials": "資格情報を送信",

View file

@ -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...", "Connecting...": "Ansluter...",
"Disconnecting...": "Kopplar ner...", "Disconnecting...": "Kopplar ner...",
"Reconnecting...": "Återansluter...", "Reconnecting...": "Återansluter...",
"Internal error": "Internt fel", "Internal error": "Internt fel",
"Must set host": "Du måste specifiera en värd", "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 (encrypted) to ": "Ansluten (krypterat) till ",
"Connected (unencrypted) to ": "Ansluten (okrypterat) till ", "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
"Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",

View file

@ -1,69 +1,69 @@
{ {
"Connecting...": "连接中...", "Connecting...": "连接中...",
"Connected (encrypted) to ": "已连接(已加密)到",
"Connected (unencrypted) to ": "已连接(未加密)到",
"Disconnecting...": "正在断开连接...", "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": "已断开连接", "Disconnected": "已断开连接",
"New connection has been rejected with reason: ": "连接被拒绝,原因:", "Must set host": "必须设置主机",
"New connection has been rejected": "连接被拒绝", "Reconnecting...": "重新连接中...",
"Password is required": "请提供密码", "Password is required": "请提供密码",
"Disconnect timeout": "超时断开",
"noVNC encountered an error:": "noVNC 遇到一个错误:", "noVNC encountered an error:": "noVNC 遇到一个错误:",
"Hide/Show the control bar": "显示/隐藏控制栏", "Hide/Show the control bar": "显示/隐藏控制栏",
"Move/Drag Viewport": "拖放显示范围", "Move/Drag Viewport": "移动/拖动窗口",
"viewport drag": "显示范围拖放", "viewport drag": "窗口拖动",
"Active Mouse Button": "启动鼠标按", "Active Mouse Button": "启动鼠标按",
"No mousebutton": "禁用鼠标按", "No mousebutton": "禁用鼠标按",
"Left mousebutton": "鼠标左", "Left mousebutton": "鼠标左",
"Middle mousebutton": "鼠标中", "Middle mousebutton": "鼠标中",
"Right mousebutton": "鼠标右", "Right mousebutton": "鼠标右",
"Keyboard": "键盘", "Keyboard": "键盘",
"Show Keyboard": "显示键盘", "Show Keyboard": "显示键盘",
"Extra keys": "额外按键", "Extra keys": "额外按键",
"Show Extra Keys": "显示额外按键", "Show Extra Keys": "显示额外按键",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Toggle Ctrl": "切换 Ctrl", "Toggle Ctrl": "切换 Ctrl",
"Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
"Alt": "Alt", "Alt": "Alt",
"Toggle Alt": "切换 Alt", "Toggle Alt": "切换 Alt",
"Send Tab": "发送 Tab 键", "Send Tab": "发送 Tab 键",
"Tab": "Tab", "Tab": "Tab",
"Esc": "Esc", "Esc": "Esc",
"Send Escape": "发送 Escape 键", "Send Escape": "发送 Escape 键",
"Ctrl+Alt+Del": "Ctrl-Alt-Del", "Ctrl+Alt+Del": "Ctrl+Alt+Del",
"Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", "Send Ctrl-Alt-Del": "发送 Ctrl+Alt+Del 键",
"Shutdown/Reboot": "关机/重新启动", "Shutdown/Reboot": "关机/重",
"Shutdown/Reboot...": "关机/重新启动...", "Shutdown/Reboot...": "关机/重...",
"Power": "电源", "Power": "电源",
"Shutdown": "关机", "Shutdown": "关机",
"Reboot": "重新启动", "Reboot": "重",
"Reset": "重置", "Reset": "重置",
"Clipboard": "剪贴板", "Clipboard": "剪贴板",
"Clear": "清除", "Clear": "清除",
"Fullscreen": "全屏", "Fullscreen": "全屏",
"Settings": "设置", "Settings": "设置",
"Encrypt": "加密",
"Shared Mode": "分享模式", "Shared Mode": "分享模式",
"View Only": "仅查看", "View Only": "仅查看",
"Clip to Window": "限制/裁切窗口大小", "Clip to Window": "限制/裁切窗口大小",
"Scaling Mode:": "缩放模式:", "Scaling Mode:": "缩放模式:",
"None": "无", "None": "无",
"Local Scaling": "本地缩放", "Local Scaling": "本地缩放",
"Local Downscaling": "降低本地尺寸",
"Remote Resizing": "远程调整大小", "Remote Resizing": "远程调整大小",
"Advanced": "高级", "Advanced": "高级",
"Local Cursor": "本地光标",
"Repeater ID:": "中继站 ID", "Repeater ID:": "中继站 ID",
"WebSocket": "WebSocket", "WebSocket": "WebSocket",
"Encrypt": "加密",
"Host:": "主机:", "Host:": "主机:",
"Port:": "端口:", "Port:": "端口:",
"Path:": "路径:", "Path:": "路径:",
"Automatic Reconnect": "自动重新连接", "Automatic Reconnect": "自动重新连接",
"Reconnect Delay (ms):": "重新连接间隔 (ms)", "Reconnect Delay (ms):": "重新连接间隔 (ms)",
"Logging:": "日志级别:", "Logging:": "日志级别:",
"Disconnect": "断连接", "Disconnect": "连接",
"Connect": "连接", "Connect": "连接",
"Password:": "密码:", "Password:": "密码:",
"Cancel": "取消" "Cancel": "取消",
"Canvas not supported.": "不支持 Canvas。"
} }

View file

@ -16,13 +16,19 @@ export class Localizer {
this.language = 'en'; this.language = 'en';
// Current dictionary of translations // Current dictionary of translations
this.dictionary = undefined; this._dictionary = undefined;
} }
// Configure suitable language based on user preferences // Configure suitable language based on user preferences
setup(supportedLanguages) { async setup(supportedLanguages, baseURL) {
this.language = 'en'; // Default: US English 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+) * Navigator.languages only available in Chrome (32+) and FireFox (32+)
* Fall back to navigator.language for other browsers * Fall back to navigator.language for other browsers
@ -40,12 +46,6 @@ export class Localizer {
.replace("_", "-") .replace("_", "-")
.split("-"); .split("-");
// Built-in default?
if ((userLang[0] === 'en') &&
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
return;
}
// First pass: perfect match // First pass: perfect match
for (let j = 0; j < supportedLanguages.length; j++) { for (let j = 0; j < supportedLanguages.length; j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
@ -64,7 +64,12 @@ export class Localizer {
return; 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++) { for (let j = 0;j < supportedLanguages.length;j++) {
const supLang = supportedLanguages[j] const supLang = supportedLanguages[j]
.toLowerCase() .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 // Retrieve localised text
get(id) { get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { if (typeof this._dictionary !== 'undefined' &&
return this.dictionary[id]; this._dictionary[id]) {
return this._dictionary[id];
} else { } else {
return id; return id;
} }

View file

@ -661,7 +661,7 @@ html {
justify-content: center; justify-content: center;
align-content: center; align-content: center;
line-height: 25px; line-height: 1.6;
word-wrap: break-word; word-wrap: break-word;
color: #fff; color: #fff;
@ -887,7 +887,7 @@ html {
.noVNC_logo { .noVNC_logo {
color:yellow; color:yellow;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif; font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%; line-height: 0.9;
text-shadow: 0.1em 0.1em 0 black; text-shadow: 0.1em 0.1em 0 black;
} }
.noVNC_logo span{ .noVNC_logo span{

View file

@ -86,6 +86,9 @@ option {
* Checkboxes * Checkboxes
*/ */
input[type=checkbox] { input[type=checkbox] {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: white; background-color: white;
background-image: unset; background-image: unset;
border: 1px solid dimgrey; border: 1px solid dimgrey;
@ -104,14 +107,11 @@ input[type=checkbox]:checked {
input[type=checkbox]:checked::after { input[type=checkbox]:checked::after {
content: ""; content: "";
display: block; /* width & height doesn't work on inline elements */ display: block; /* width & height doesn't work on inline elements */
position: relative;
top: 0;
left: 3px;
width: 3px; width: 3px;
height: 7px; height: 7px;
border: 1px solid white; border: 1px solid white;
border-width: 0 2px 2px 0; border-width: 0 2px 2px 0;
transform: rotate(40deg); transform: rotate(40deg) translateY(-1px);
} }
/* /*

View file

@ -18,6 +18,8 @@ import Keyboard from "../core/input/keyboard.js";
import RFB from "../core/rfb.js"; import RFB from "../core/rfb.js";
import * as WebUtil from "./webutil.js"; import * as WebUtil from "./webutil.js";
const PAGE_TITLE = "noVNC";
// String validation // String validation
function isAlphaNumeric(str) { return (str.match(/^[A-Za-z0-9]+$/) != null); }; 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)) }; 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 // insecure context
if (!window.isSecureContext) { if (!window.isSecureContext) {
// FIXME: This gets hidden when connecting // 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 // 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("connect", UI.connectFinished);
UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
UI.rfb.addEventListener("serververification", UI.serverVerify); UI.rfb.addEventListener("serververification", UI.serverVerify);
@ -1166,6 +1175,7 @@ const UI = {
UI.showStatus(_("Disconnected"), 'normal'); UI.showStatus(_("Disconnected"), 'normal');
} }
document.title = PAGE_TITLE;
UI.openControlbar(); UI.openControlbar();
UI.openConnectPanel(); UI.openConnectPanel();
@ -1739,9 +1749,9 @@ const UI = {
}, },
updateDesktopName(e) { updateDesktopName(e) {
// UI.desktopName = e.detail.name; UI.desktopName = e.detail.name;
// Display the desktop name in the document title // Display the desktop name in the document title
// document.title = e.detail.name + " - " + PAGE_TITLE; document.title = e.detail.name + " - " + PAGE_TITLE;
}, },
bell(e) { bell(e) {
@ -1778,20 +1788,8 @@ const UI = {
// Set up translations // Set up translations
const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"]; const LINGUAS = ["cs", "de", "el", "es", "fr", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "zh_CN", "zh_TW"];
l10n.setup(LINGUAS); l10n.setup(LINGUAS, "app/locale/")
if (l10n.language === "en" || l10n.dictionary !== undefined) { .catch(err => Log.Error("Failed to load translations: " + err))
UI.prime(); .then(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);
}
export default UI; export default UI;

Some files were not shown because too many files have changed in this diff Show more