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

Compare commits

...

185 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
125 changed files with 68516 additions and 12111 deletions

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" />

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

@ -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; } });

View file

@ -295,8 +295,9 @@ if (process.platform == 'win32' && require('user-sessions').isRoot()) {
// Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
try { try {
var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024); var writtenSize = 0, actualSize = Math.floor(require('fs').statSync(process.execPath).size / 1024);
try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (ex) { } var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { } } try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { }
if (writtenSize != actualSize) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { } }
} catch (ex) { } } catch (ex) { }
// Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode // Check to see if we are the Installed Mesh Agent Service, if we are, make sure we can run in Safe Mode
@ -310,6 +311,16 @@ if (process.platform == 'win32' && require('user-sessions').isRoot()) {
try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { } try { meshCheck = require('service-manager').manager.getService(svcname).isMe(); } catch (ex) { }
if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); } if (meshCheck && require('win-bcd').isSafeModeService && !require('win-bcd').isSafeModeService(svcname)) { require('win-bcd').enableSafeModeService(svcname); }
} catch (ex) { } } catch (ex) { }
// Check the Agent Uninstall MetaData for DisplayVersion and update if not the same and only on windows
if (process.platform == 'win32') {
try {
var writtenDisplayVersion = 0, actualDisplayVersion = process.versions.commitDate.toString();
var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
try { writtenDisplayVersion = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion'); } catch (ex) { }
if (writtenDisplayVersion != actualDisplayVersion) { try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'DisplayVersion', actualDisplayVersion); } catch (ex) { } }
} catch (ex) { }
}
} }
if (process.platform != 'win32') { if (process.platform != 'win32') {
@ -655,33 +666,39 @@ var meshCoreObj = { action: 'coreinfo', value: (require('MeshAgent').coreHash ?
try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { } try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; meshCoreObjChanged(); }); } catch (ex) { }
// Setup logged in user monitoring (THIS IS BROKEN IN WIN7) // Setup logged in user monitoring (THIS IS BROKEN IN WIN7)
function onUserSessionChanged(user, locked) {
userSession.enumerateUsers().then(function (users) {
if (process.platform == 'linux') {
if (userSession._startTime == null) {
userSession._startTime = Date.now();
userSession._count = users.length;
}
else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
userSession.removeAllListeners('changed');
return;
}
}
var u = [], a = users.Active;
if(meshCoreObj.lusers == null) { meshCoreObj.lusers = []; }
for (var i = 0; i < a.length; i++) {
var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
if (user && locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { if (meshCoreObj.lusers.indexOf(un) == -1) { meshCoreObj.lusers.push(un); } }
else if (user && !locked && (JSON.stringify(a[i]) === JSON.stringify(user))) { meshCoreObj.lusers.splice(meshCoreObj.lusers.indexOf(un), 1); }
if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
}
meshCoreObj.lusers = meshCoreObj.lusers;
meshCoreObj.users = u;
meshCoreObjChanged();
});
}
try { try {
var userSession = require('user-sessions'); var userSession = require('user-sessions');
userSession.on('changed', function onUserSessionChanged() { userSession.on('changed', function () { onUserSessionChanged(null, false); });
userSession.enumerateUsers().then(function (users) {
if (process.platform == 'linux') {
if (userSession._startTime == null) {
userSession._startTime = Date.now();
userSession._count = users.length;
}
else if (Date.now() - userSession._startTime < 10000 && users.length == userSession._count) {
userSession.removeAllListeners('changed');
return;
}
}
var u = [], a = users.Active;
for (var i = 0; i < a.length; i++) {
var un = a[i].Domain ? (a[i].Domain + '\\' + a[i].Username) : (a[i].Username);
if (u.indexOf(un) == -1) { u.push(un); } // Only push users in the list once.
}
meshCoreObj.users = u;
meshCoreObjChanged();
});
});
userSession.emit('changed'); userSession.emit('changed');
//userSession.on('locked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has LOCKED the desktop'); }); userSession.on('locked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, true); } });
//userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); userSession.on('unlocked', function (user) { if(user != undefined && user != null) { onUserSessionChanged(user, false); } });
} catch (ex) { } } catch (ex) { }
var meshServerConnectionState = 0; var meshServerConnectionState = 0;
@ -1158,6 +1175,7 @@ function handleServerCommand(data) {
tunnel.soptions = data.soptions; tunnel.soptions = data.soptions;
tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30; tunnel.consentTimeout = (tunnel.soptions && tunnel.soptions.consentTimeout) ? tunnel.soptions.consentTimeout : 30;
tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true)); tunnel.consentAutoAccept = (tunnel.soptions && (tunnel.soptions.consentAutoAccept === true));
tunnel.consentAutoAcceptIfNoUser = (tunnel.soptions && (tunnel.soptions.consentAutoAcceptIfNoUser === true));
tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false; tunnel.oldStyle = (tunnel.soptions && tunnel.soptions.oldStyle) ? tunnel.soptions.oldStyle : false;
tunnel.tcpaddr = data.tcpaddr; tunnel.tcpaddr = data.tcpaddr;
tunnel.tcpport = data.tcpport; tunnel.tcpport = data.tcpport;
@ -1572,7 +1590,7 @@ function handleServerCommand(data) {
mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options); mesh.cmdchild = require('child_process').execFile('/bin/sh', ['sh'], options);
mesh.cmdchild.descriptorMetadata = 'UserCommandsShell'; mesh.cmdchild.descriptorMetadata = 'UserCommandsShell';
mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); }); mesh.cmdchild.stdout.on('data', function (c) { replydata += c.toString(); });
mesh.cmdchild.stderr.on('data', function (c) { replydata + c.toString(); }); mesh.cmdchild.stderr.on('data', function (c) { replydata += c.toString(); });
mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n'); mesh.cmdchild.stdin.write(data.cmds.split('\r').join('') + '\nexit\n');
mesh.cmdchild.on('exit', function () { mesh.cmdchild.on('exit', function () {
if (data.reply) { if (data.reply) {
@ -1936,9 +1954,9 @@ function getSystemInformation(func) {
if (process.platform == 'win32') if (process.platform == 'win32')
{ {
results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot results.pendingReboot = require('win-info').pendingReboot(); // Pending reboot
if (require('computer-identifiers').volumes_promise != null) if (require('win-volumes').volumes_promise != null)
{ {
var p = require('computer-identifiers').volumes_promise(); var p = require('win-volumes').volumes_promise();
p.then(function (res) p.then(function (res)
{ {
results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res); results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(res);
@ -1946,12 +1964,6 @@ function getSystemInformation(func) {
func(results); func(results);
}); });
} }
else if (require('computer-identifiers').volumes != null)
{
results.hardware.windows.volumes = cleanGetBitLockerVolumeInfo(require('computer-identifiers').volumes());
results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
func(results);
}
else else
{ {
results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex'); results.hash = hasher.syncHash(JSON.stringify(results)).toString('hex');
@ -2303,6 +2315,59 @@ function terminal_end()
} }
function terminal_consent_ask(ws) {
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
var consentMessage = currentTranslation['terminalConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
var consentTitle = 'MeshCentral';
if (ws.httprequest.soptions != null) {
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
if (ws.httprequest.soptions.consentMsgTerminal != null) { consentMessage = ws.httprequest.soptions.consentMsgTerminal.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
}
if (process.platform == 'win32') {
var enhanced = false;
if (ws.httprequest.oldStyle === false) {
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
}
if (enhanced) {
var ipr = server_getUserImage(ws.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = ws.httprequest.consentTimeout;
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
ipr.username = ws.httprequest.realname;
ipr.tsid = ws.tsid;
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
ws.httprequest.tpromise._consent = ipr.then(function (img) {
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
} else {
ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
}
} else {
ws.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout);
}
ws.httprequest.tpromise._consent.retPromise = ws.httprequest.tpromise;
ws.httprequest.tpromise._consent.then(function (always) {
if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
// Success
MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
this.retPromise._consent = null;
this.retPromise._res();
}, function (e) {
if (this.retPromise.that) {
if(this.retPromise.that.httprequest){ // User Consent Denied
MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
} else { } // Connection was closed server side, maybe log some messages somewhere?
this.retPromise._consent = null;
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
} else { } // no websocket, maybe log some messages somewhere?
this.retPromise._rej(e.toString());
});
}
function terminal_promise_connection_rejected(e) function terminal_promise_connection_rejected(e)
{ {
// FAILED to connect terminal // FAILED to connect terminal
@ -2615,6 +2680,101 @@ function kvm_tunnel_consentpromise_closehandler()
if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
} }
function kvm_consent_ok(ws) {
// User Consent Prompt is not required because no user is present
if (ws.httprequest.consent && (ws.httprequest.consent & 1)){
// User Notifications is required
MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', ws.httprequest.realname);
var notifyTitle = "MeshCentral";
if (ws.httprequest.soptions != null) {
if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
if (ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
}
try { require('toaster').Toast(notifyTitle, notifyMessage, ws.tsid); } catch (ex) { }
} else {
MeshServerLogEx(36, null, "Started remote desktop without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
}
if (ws.httprequest.consent && (ws.httprequest.consent & 0x40)) {
// Connection Bar is required
if (ws.httprequest.desktop.kvm.connectionBar) {
ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
ws.httprequest.desktop.kvm.connectionBar.close();
}
try {
ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(ws.httprequest.privacybartext.replace('{0}', ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', ws.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
} catch (ex) {
MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
}
if (ws.httprequest.desktop.kvm.connectionBar) {
ws.httprequest.desktop.kvm.connectionBar.state = {
userid: ws.httprequest.userid,
xuserid: ws.httprequest.xuserid,
username: ws.httprequest.username,
sessionid: ws.httprequest.sessionid,
remoteaddr: ws.httprequest.remoteaddr,
guestname: ws.httprequest.guestname,
desktop: ws.httprequest.desktop
};
ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
console.info1('Connection Bar Forcefully closed');
MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
for (var i in this.state.desktop.kvm._pipedStreams) {
this.state.desktop.kvm._pipedStreams[i].end();
}
this.state.desktop.kvm.end();
});
}
}
ws.httprequest.desktop.kvm.pipe(ws, { dataTypeSkip: 1 });
if (ws.httprequest.autolock) {
destopLockHelper_pipe(ws.httprequest);
}
}
function kvm_consent_ask(ws){
// Send a console message back using the console channel, "\n" is supported.
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
var consentMessage = currentTranslation['desktopConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
var consentTitle = 'MeshCentral';
if (ws.httprequest.soptions != null) {
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
if (ws.httprequest.soptions.consentMsgDesktop != null) { consentMessage = ws.httprequest.soptions.consentMsgDesktop.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
}
var pr;
if (process.platform == 'win32') {
var enhanced = false;
if (ws.httprequest.oldStyle === false) {
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
}
if (enhanced) {
var ipr = server_getUserImage(ws.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = ws.httprequest.consentTimeout;
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
ipr.tsid = ws.tsid;
ipr.username = ws.httprequest.realname;
ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
pr = ipr.then(function (img) {
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
} else {
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
}
} else {
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null, ws.tsid);
}
pr.ws = ws;
ws.pause();
ws._consentpromise = pr;
ws.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
}
function kvm_consentpromise_rejected(e) function kvm_consentpromise_rejected(e)
{ {
if (this.ws) { if (this.ws) {
@ -2694,6 +2854,67 @@ function kvm_consentpromise_resolved(always)
this.ws = null; this.ws = null;
} }
function files_consent_ok(ws){
// User Consent Prompt is not required
if (ws.httprequest.consent && (ws.httprequest.consent & 4)) {
// User Notifications is required
MeshServerLogEx(42, null, "Started remote files with toast notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
var notifyMessage = currentTranslation['fileNotify'].replace('{0}', ws.httprequest.realname);
var notifyTitle = "MeshCentral";
if (ws.httprequest.soptions != null) {
if (ws.httprequest.soptions.notifyTitle != null) { notifyTitle = ws.httprequest.soptions.notifyTitle; }
if (ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = ws.httprequest.soptions.notifyMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
}
try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
} else {
MeshServerLogEx(43, null, "Started remote files without notification (" + ws.httprequest.remoteaddr + ")", ws.httprequest);
}
ws.resume();
}
function files_consent_ask(ws){
// Send a console message back using the console channel, "\n" is supported.
ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
var consentMessage = currentTranslation['fileConsent'].replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username);
var consentTitle = 'MeshCentral';
if (ws.httprequest.soptions != null) {
if (ws.httprequest.soptions.consentTitle != null) { consentTitle = ws.httprequest.soptions.consentTitle; }
if (ws.httprequest.soptions.consentMsgFiles != null) { consentMessage = ws.httprequest.soptions.consentMsgFiles.replace('{0}', ws.httprequest.realname).replace('{1}', ws.httprequest.username); }
}
var pr;
if (process.platform == 'win32') {
var enhanced = false;
if (ws.httprequest.oldStyle === false) {
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
}
if (enhanced) {
var ipr = server_getUserImage(ws.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = ws.httprequest.consentTimeout;
ipr.consentAutoAccept = ws.httprequest.consentAutoAccept;
ipr.username = ws.httprequest.realname;
ipr.tsid = ws.tsid;
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
pr = ipr.then(function (img) {
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
} else {
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
}
} else {
pr = require('message-box').create(consentTitle, consentMessage, ws.httprequest.consentTimeout, null);
}
pr.ws = ws;
ws.pause();
ws._consentpromise = pr;
ws.prependOnceListener('end', files_tunnel_endhandler);
pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
}
function files_consentpromise_resolved(always) function files_consentpromise_resolved(always)
{ {
if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); } if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
@ -2807,6 +3028,12 @@ function onTunnelData(data)
this.descriptorMetadata = "Remote Terminal"; this.descriptorMetadata = "Remote Terminal";
// Look for a TSID
var tsid = null;
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
require('MeshAgent')._tsid = tsid;
this.tsid = tsid;
if (process.platform == 'win32') if (process.platform == 'win32')
{ {
if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) { if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
@ -2823,76 +3050,31 @@ function onTunnelData(data)
this.end = terminal_end; this.end = terminal_end;
// Perform User-Consent if needed. // Perform User-Consent if needed.
if (this.httprequest.consent && (this.httprequest.consent & 16)) if (this.httprequest.consent && (this.httprequest.consent & 16)) {
{ // User asked for consent so now we check if we can auto accept if no user is present/loggedin
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); if (this.httprequest.consentAutoAcceptIfNoUser) {
var consentMessage = currentTranslation['terminalConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); var p = require('user-sessions').enumerateUsers();
var consentTitle = 'MeshCentral'; p.sessionid = this.httprequest.sessionid;
p.ws = this;
if (this.httprequest.soptions != null) p.then(function (u) {
{ var v = [];
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } for (var i in u) {
if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
} }
if (process.platform == 'win32') if (v.length == 0) { // No user is present, auto accept
{ this.ws.httprequest.tpromise._res();
var enhanced = false; } else {
if (this.httprequest.oldStyle === false) { // User is present so we still need consent
try { require('win-userconsent'); enhanced = true; } catch (ex) { } terminal_consent_ask(this.ws);
} }
if (enhanced)
{
var ipr = server_getUserImage(this.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = this.httprequest.consentTimeout;
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
ipr.username = this.httprequest.realname;
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
this.httprequest.tpromise._consent = ipr.then(function (img)
{
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
} else
{
this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
}
} else
{
this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
}
this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
this.httprequest.tpromise._consent.then(
function (always)
{
if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
// Success
MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
this.retPromise._consent = null;
this.retPromise._res();
},
function (e) {
if (this.retPromise.that) {
if(this.retPromise.that.httprequest){ // User Consent Denied
MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
} else { } // Connection was closed server side, maybe log some messages somewhere?
this.retPromise._consent = null;
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
} else { } // no websocket, maybe log some messages somewhere?
this.retPromise._rej(e.toString());
}); });
} } else {
else terminal_consent_ask(this);
{ }
} else {
// User-Consent is not required, so just resolve this promise // User-Consent is not required, so just resolve this promise
this.httprequest.tpromise._res(); this.httprequest.tpromise._res();
} }
this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected); this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
} }
else if (this.httprequest.protocol == 2) else if (this.httprequest.protocol == 2)
@ -2916,6 +3098,7 @@ function onTunnelData(data)
var tsid = null; var tsid = null;
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; } if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
require('MeshAgent')._tsid = tsid; require('MeshAgent')._tsid = tsid;
this.tsid = tsid;
// If MacOS, Wake up device with caffeinate // If MacOS, Wake up device with caffeinate
if(process.platform == 'darwin'){ if(process.platform == 'darwin'){
@ -2987,119 +3170,33 @@ function onTunnelData(data)
} }
// Perform notification if needed. Toast messages may not be supported on all platforms. // Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 8)) if (this.httprequest.consent && (this.httprequest.consent & 8)) {
{
// User Consent Prompt is required // User asked for consent but now we check if can auto accept if no user is present
// Send a console message back using the console channel, "\n" is supported. if (this.httprequest.consentAutoAcceptIfNoUser) {
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); // Get list of users to check if we any actual users logged in, and if users logged in, we still need consent
var consentMessage = currentTranslation['desktopConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); var p = require('user-sessions').enumerateUsers();
var consentTitle = 'MeshCentral'; p.sessionid = this.httprequest.sessionid;
if (this.httprequest.soptions != null) p.ws = this;
{ p.then(function (u) {
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } var v = [];
if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } for (var i in u) {
if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
}
if (v.length == 0) { // No user is present, auto accept
kvm_consent_ok(this.ws);
} else {
// User is present so we still need consent
kvm_consent_ask(this.ws);
}
});
} else {
// User Consent Prompt is required
kvm_consent_ask(this);
} }
var pr; } else {
if (process.platform == 'win32')
{
var enhanced = false;
if (this.httprequest.oldStyle === false) {
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
}
if (enhanced)
{
var ipr = server_getUserImage(this.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = this.httprequest.consentTimeout;
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
ipr.tsid = tsid;
ipr.username = this.httprequest.realname;
ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
pr = ipr.then(function (img)
{
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
}
else
{
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
}
}
else
{
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
}
pr.ws = this;
this.pause();
this._consentpromise = pr;
this.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
}
else
{
// User Consent Prompt is not required // User Consent Prompt is not required
if (this.httprequest.consent && (this.httprequest.consent & 1)) kvm_consent_ok(this);
{
// User Notifications is required
MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.httprequest.realname);
var notifyTitle = "MeshCentral";
if (this.httprequest.soptions != null) {
if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
}
try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
} else
{
MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
}
if (this.httprequest.consent && (this.httprequest.consent & 0x40))
{
// Connection Bar is required
if (this.httprequest.desktop.kvm.connectionBar)
{
this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
this.httprequest.desktop.kvm.connectionBar.close();
}
try
{
this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')).replace(/'/g, "\\'\\"), require('MeshAgent')._tsid, color_options);
MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
} catch (ex) {
MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
}
if (this.httprequest.desktop.kvm.connectionBar)
{
this.httprequest.desktop.kvm.connectionBar.state =
{
userid: this.httprequest.userid,
xuserid: this.httprequest.xuserid,
username: this.httprequest.username,
sessionid: this.httprequest.sessionid,
remoteaddr: this.httprequest.remoteaddr,
guestname: this.httprequest.guestname,
desktop: this.httprequest.desktop
};
this.httprequest.desktop.kvm.connectionBar.on('close', function ()
{
console.info1('Connection Bar Forcefully closed');
MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
for (var i in this.state.desktop.kvm._pipedStreams)
{
this.state.desktop.kvm._pipedStreams[i].end();
}
this.state.desktop.kvm.end();
});
}
}
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
if (this.httprequest.autolock)
{
destopLockHelper_pipe(this.httprequest);
}
} }
this.removeAllListeners('data'); this.removeAllListeners('data');
@ -3121,6 +3218,12 @@ function onTunnelData(data)
this.descriptorMetadata = "Remote Files"; this.descriptorMetadata = "Remote Files";
// Look for a TSID
var tsid = null;
if ((this.httprequest.xoptions != null) && (typeof this.httprequest.xoptions.tsid == 'number')) { tsid = this.httprequest.xoptions.tsid; }
require('MeshAgent')._tsid = tsid;
this.tsid = tsid;
// Add the files session to the count to update the server // Add the files session to the count to update the server
if (this.httprequest.userid != null) { if (this.httprequest.userid != null) {
var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest); var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
@ -3143,71 +3246,31 @@ function onTunnelData(data)
// Perform notification if needed. Toast messages may not be supported on all platforms. // Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 32)) if (this.httprequest.consent && (this.httprequest.consent & 32))
{ {
// User Consent Prompt is required // User asked for consent so now we check if we can auto accept if no user is present/loggedin
// Send a console message back using the console channel, "\n" is supported. if (this.httprequest.consentAutoAcceptIfNoUser) {
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var p = require('user-sessions').enumerateUsers();
var consentMessage = currentTranslation['fileConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); p.sessionid = this.httprequest.sessionid;
var consentTitle = 'MeshCentral'; p.ws = this;
p.then(function (u) {
if (this.httprequest.soptions != null) var v = [];
{ for (var i in u) {
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (u[i].State == 'Active') { v.push({ tsid: i, type: u[i].StationName, user: u[i].Username, domain: u[i].Domain }); }
if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } }
} if (v.length == 0) { // No user is present, auto accept
var pr; // User Consent Prompt is not required
if (process.platform == 'win32') files_consent_ok(this.ws);
{ } else {
var enhanced = false; // User is present so we still need consent
if (this.httprequest.oldStyle === false) { files_consent_ask(this.ws);
try { require('win-userconsent'); enhanced = true; } catch (ex) { } }
} });
if (enhanced)
{
var ipr = server_getUserImage(this.httprequest.userid);
ipr.consentTitle = consentTitle;
ipr.consentMessage = consentMessage;
ipr.consentTimeout = this.httprequest.consentTimeout;
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
ipr.username = this.httprequest.realname;
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
pr = ipr.then(function (img)
{
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
this.__childPromise.close = this.consent.close.bind(this.consent);
return (this.consent);
});
} else
{
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
}
}
else
{
pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
}
pr.ws = this;
this.pause();
this._consentpromise = pr;
this.prependOnceListener('end', files_tunnel_endhandler);
pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
}
else
{
// User Consent Prompt is not required
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
// User Notifications is required
MeshServerLogEx(42, null, "Started remote files with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.httprequest.realname);
var notifyTitle = "MeshCentral";
if (this.httprequest.soptions != null) {
if (this.httprequest.soptions.notifyTitle != null) { notifyTitle = this.httprequest.soptions.notifyTitle; }
if (this.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.httprequest.soptions.notifyMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
}
try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
} else { } else {
MeshServerLogEx(43, null, "Started remote files without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); // User Consent Prompt is required
files_consent_ask(this);
} }
this.resume(); } else {
// User Consent Prompt is not required
files_consent_ok(this);
} }
// Setup files // Setup files
@ -3695,7 +3758,14 @@ function onTunnelControlData(data, ws) {
{ // Desktop { // Desktop
// Switch the user input from websocket to webrtc at this point. // Switch the user input from websocket to webrtc at this point.
ws.unpipe(ws.httprequest.desktop.kvm); ws.unpipe(ws.httprequest.desktop.kvm);
try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. if ((ws.httprequest.desktopviewonly != true) && ((ws.httprequest.rights == 0xFFFFFFFF) || (((ws.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((ws.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
// If we have remote control rights, pipe the KVM input
try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
} else {
// We need to only pipe non-mouse & non-keyboard inputs.
// sendConsoleText('Warning: No Remote Desktop Input Rights.');
// TODO!!!
}
ws.resume(); // Resume the websocket to keep receiving control data ws.resume(); // Resume the websocket to keep receiving control data
} }
ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point. ws.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc2\"}'); // Indicates we will no longer get any data on websocket, switching to WebRTC at this point.
@ -3881,11 +3951,11 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
if (require('os').dns != null) { availcommands += ',dnsinfo'; } if (require('os').dns != null) { availcommands += ',dnsinfo'; }
try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { } try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
if (process.platform == 'win32') { if (process.platform == 'win32') {
availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport'; availcommands += ',bitlocker,cs,wpfhwacceleration,uac,volumes,rdpport,deskbackground';
if (bcdOK()) { availcommands += ',safemode'; } if (bcdOK()) { availcommands += ',safemode'; }
if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; } if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { } try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
try { require('win-info'); availcommands += ',installedapps'; } catch (ex) { } try { require('win-info'); availcommands += ',installedapps,qfe'; } catch (ex) { }
} }
if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; } if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
if (process.platform != 'freebsd') { availcommands += ',vm'; } if (process.platform != 'freebsd') { availcommands += ',vm'; }
@ -4044,12 +4114,9 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
break; break;
case 'bitlocker': case 'bitlocker':
if (process.platform == 'win32') { if (process.platform == 'win32') {
if (require('computer-identifiers').volumes_promise != null) { if (require('win-volumes').volumes_promise != null) {
var p = require('computer-identifiers').volumes_promise(); var p = require('win-volumes').volumes_promise();
p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); }); p.then(function (res) { sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(res), null, 1), this.session); });
response = "Please wait...";
} else if (require('computer-identifiers').volumes != null) {
sendConsoleText(JSON.stringify(cleanGetBitLockerVolumeInfo(require('computer-identifiers').volumes()), null, 1), this.session);
} }
} }
break; break;
@ -4299,7 +4366,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
} }
case 'agentmsg': { case 'agentmsg': {
if (args['_'].length == 0) { if (args['_'].length == 0) {
response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [index]\r\n agentmsg list"; // Display usage response = "Proper usage:\r\n agentmsg add \"[message]\" [iconIndex]\r\n agentmsg remove [id]\r\n agentmsg list"; // Display usage
} else { } else {
if ((args['_'][0] == 'add') && (args['_'].length > 1)) { if ((args['_'][0] == 'add') && (args['_'].length > 1)) {
var msgID, iconIndex = 0; var msgID, iconIndex = 0;
@ -4517,10 +4584,11 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
if (process.platform == 'win32') { if (process.platform == 'win32') {
// Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value // Check the Agent Uninstall MetaData for correctness, as the installer may have written an incorrect value
var writtenSize = 0; var writtenSize = 0;
try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize'); } catch (ex) { response = ex; } var serviceName = (_MSH().serviceName ? _MSH().serviceName : (require('_agentNodeId').serviceName() ? require('_agentNodeId').serviceName() : 'Mesh Agent'));
try { writtenSize = require('win-registry').QueryKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize'); } catch (ex) { response = ex; }
if (writtenSize != actualSize) { if (writtenSize != actualSize) {
response = "Size updated from: " + writtenSize + " to: " + actualSize; response = "Size updated from: " + writtenSize + " to: " + actualSize;
try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MeshCentralAgent', 'EstimatedSize', actualSize); } catch (ex) { response = ex; } try { require('win-registry').WriteKey(require('win-registry').HKEY.LocalMachine, 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + serviceName, 'EstimatedSize', actualSize); } catch (ex) { response = ex; }
} else } else
{ response = "Agent Size: " + actualSize + " kb"; } { response = "Agent Size: " + actualSize + " kb"; }
} else } else
@ -5329,6 +5397,13 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
} }
break; break;
} }
case 'qfe': {
if(process.platform == 'win32'){
var qfe = require('win-info').qfe();
sendConsoleText(JSON.stringify(qfe,null,1));
}
break;
}
default: { // This is an unknown command, return an error message default: { // This is an unknown command, return an error message
response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands."; response = "Unknown command \"" + cmd + "\", type \"help\" for list of available commands.";
break; break;
@ -5593,12 +5668,12 @@ function windows_execve(name, agentfilename, sessionid) {
sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3); sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
return; return;
} }
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 & "' + process.cwd() + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' + var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + process.cwd() + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
' "' + process.execPath + '" & copy "' + process.cwd() + agentfilename + '.update.exe" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + process.cwd() + agentfilename + '.update.exe"', { wide: true }); ' "' + process.execPath + '" & copy "' + process.cwd() + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + process.cwd() + agentfilename + '.update.exe"', { wide: true });
arg1.pointerBuffer().copy(args.toBuffer()); arg1.pointerBuffer().copy(args.toBuffer());
arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize); arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);
@ -5788,6 +5863,7 @@ function handleServerConnection(state) {
// Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings // Update the server on with basic info, logged in users and more advanced stuff, like Intel ME and Network Settings
meInfoStr = null; meInfoStr = null;
LastPeriodicServerUpdate = null;
sendPeriodicServerUpdate(null, true); sendPeriodicServerUpdate(null, true);
if (selfInfoUpdateTimer == null) { if (selfInfoUpdateTimer == null) {
selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); // 20 minutes

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

@ -422,104 +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,SizeRemaining,DriveType | ConvertTo-Csv -NoTypeInformation\r\nexit\r\n');
child.on('exit', function (c)
{
var a, i, tokens, key;
var ret = {};
a = this.stdout.str.trim().split('\r\n');
for (i = 1; i < a.length; ++i)
{
tokens = a[i].split(',');
if (tokens[0] != '' && tokens[1] != undefined)
{
ret[tokens[0].split('"')[1]] =
{
name: tokens[1].split('"')[1],
type: tokens[2].split('"')[1],
size: tokens[3].split('"')[1],
sizeremaining: tokens[4].split('"')[1],
removable: tokens[5].split('"')[1] == 'Removable',
cdrom: tokens[5].split('"')[1] == 'CD-ROM'
};
}
}
this.promise._res({ r: ret, t: tokens });
});
p1.then(function (j)
{
var ret = j.r;
var tokens = j.t;
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-']);
p2.child = child;
child.promise = p2;
child.tokens = tokens;
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stdin.write('Get-BitLockerVolume | Select-Object -Property MountPoint,VolumeStatus,ProtectionStatus | ConvertTo-Csv -NoTypeInformation\r\nexit\r\n');
child.on('exit', function ()
{
var i;
var a = this.stdout.str.trim().split('\r\n');
for (i = 1; i < a.length; ++i)
{
tokens = a[i].split(',');
key = tokens[0].split(':').shift().split('"').pop();
if (ret[key] != null)
{
ret[key].volumeStatus = tokens[1].split('"')[1];
ret[key].protectionStatus = tokens[2].split('"')[1];
try {
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ', tokens[0].split('"')[1], ' -Type recoverypassword'], {});
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
keychild.waitExit();
var lines = keychild.stdout.str.trim().split('\r\n');
for (var x = 0; x < lines.length; x++) { // Loop each line
var abc = lines[x].trim();
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
var nextline = lines[x + 1].trim();
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
foundIDMarkedLine = true;
}else if (x + 1 < lines.length && nextline !== '') {
password = nextline;
foundMarkedLine = true;
}
}
}
ret[key].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
ret[key].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
} catch(ex) { }
}
}
this.promise._res(ret);
});
});
return (p2);
}
function windows_identifiers() function windows_identifiers()
{ {
var ret = { windows: {} }; var ret = { windows: {} };
@ -800,32 +702,35 @@ function hexToAscii(hexString) {
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)
@ -988,11 +893,6 @@ 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

View file

@ -229,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);
} }

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()
{ {
@ -258,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

@ -719,7 +719,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
} }
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), 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
} }

View file

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

View file

@ -155,12 +155,12 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) {
return obj; return obj;
}; };
// Escape and unescape field 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, SSH and RDP usernames // Escape all links, SSH and RDP usernames
// This is required for databases like NeDB that don't accept "." as part of a field name. // This is required for databases like NeDB that don't accept "." or "," as part of a field name.
module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); }; module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
module.exports.escapeLinksFieldName = function (docx) { module.exports.escapeLinksFieldName = function (docx) {
var doc = Object.assign({}, docx); var doc = Object.assign({}, docx);

899
db.js

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,16 @@
"@yetzt/nedb": "1.8.0", "@seald-io/nedb": "4.0.4",
"archiver": "7.0.1", "archiver": "7.0.1",
"body-parser": "1.20.3", "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.21.0", "express": "4.21.2",
"express-handlebars": "7.1.3", "express-handlebars": "7.1.3",
"express-ws": "5.0.2", "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",
"ua-parser-js": "1.0.37", "ua-parser-js": "1.0.39",
"ws": "8.17.1", "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/
@ -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

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

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

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)
@ -1278,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.

View file

@ -32,6 +32,23 @@ OpenID Connect allows clients of all types, including Web-based, mobile, and Jav
That description was straight from [OpenID Connect Documentation](https://openid.net/connect/), but basically, OAuth2 is the foundation upon which OpenID Connect was built, allowing for wide ranging compatability and interconnection. OpenID Connect appends the secure user *authentication* OAuth2 is known for, with user *authorization* by allowing the request of additional *scopes* that provide additional *claims* or access to API's in an easily expandable way. 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 ## Basic Config
### *Introduction* ### *Introduction*
@ -651,4 +668,4 @@ https://github.com/panva/node-openid-client
https://openid.net/connect/ https://openid.net/connect/
> You just read `openidConnectStrategy.ms v1.0.1` by [@mstrhakr](https://github.com/mstrhakr) > You just read `openidConnectStrategy.ms v1.0.1` by [@mstrhakr](https://github.com/mstrhakr)

View file

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

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

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

@ -1936,8 +1936,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
change = 1; // Don't save this change as an event to the db, so no log=1. 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'); }

View file

@ -94,9 +94,41 @@
} }
}, },
"sqlite3": { "sqlite3": {
"type": "boolean", "type": [ "boolean", "string", "object" ],
"default": false, "default": false,
"description": "Set true to use SQLite3 as a local MeshCentral database." "description": "Set boolean true to use SQLite3 as a local MeshCentral database with default db filename 'meshcentral' or enter a string for a different db filename. Extension .sqlite is appended",
"properties":{
"name": {
"type": "string",
"default": "meshcentral",
"description": "Database filename. '.sqlite' is appended"
},
"journalMode": {
"type": "string",
"default": "delete",
"description": "DELETE, TRUNCATE, PERSIST, MEMORY, WAL. NONE not allowed. See: https://www.sqlite.org/pragma.html#pragma_journal_mode"
},
"journalSize": {
"type": "integer",
"default": 4096000,
"description": "Maximum size of the journal file in bytes. Can grow larger if needed, but will shrink to this size. -1 is unlimited growth. See: https://www.sqlite.org/pragma.html#pragma_journal_size_limit"
},
"autoVacuum": {
"type": "string",
"default": "incremental",
"description": "none, full, incremental. Removes unused pages and shrinks databasefile during maintenance. See: https://www.sqlite.org/pragma.html#pragma_auto_vacuum"
},
"incrementalVacuum": {
"type": "integer",
"default": 100,
"description": "Maximum amount of pages to free during maintenance. Default page size is 4k, so default frees up to 400k from the databasefile. See: https://www.sqlite.org/pragma.html#pragma_incremental_vacuum"
},
"startupVacuum": {
"type": "boolean",
"default": false,
"description": "Do a full VACUUM at startup. Shrinks the db file and optimizes it. This can take some time with a large database and can temporarily take up to double the database size on disk. See: https://www.sqlite.org/lang_vacuum.html"
}
}
}, },
"mySQL": { "mySQL": {
"type": "object", "type": "object",
@ -488,11 +520,6 @@
"default": true, "default": true,
"description": "When enabled, MeshCentral will automatically monitor and manage Intel AMT devices." "description": "When enabled, MeshCentral will automatically monitor and manage Intel AMT devices."
}, },
"orphanAgentUser": {
"type": "string",
"default": null,
"description": "If an agent attempts to connect to a unknown device group, automatically create a new device group and grant access to the specified user. Example: admin"
},
"agentIdleTimeout": { "agentIdleTimeout": {
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
@ -740,6 +767,14 @@
"default": false, "default": false,
"description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect." "description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect."
}, },
"prometheus": {
"type": [
"boolean",
"number"
],
"default": false,
"description": "When set to true, a prometheus metrics endpoint will be available \"0.0.0.0:9464/metrics\". If you specify a number instead, the prometheus metrics will listen on this port instead of the default 9464."
},
"no2FactorAuth": { "no2FactorAuth": {
"type": "boolean", "type": "boolean",
"default": false "default": false
@ -829,7 +864,7 @@
"boolean", "boolean",
"object" "object"
], ],
"description": "If set to \"true\", automatic backups of your MeshCentral data will be enabled. Alternatively, you can provide an object with additional values such as \"webdav\", \"backupPath\", \"backupIntervalHours\", and more.", "description": "Enabled by default or if set to true. Disabled if set to false. An object can be provided with additional properties to set autobackup options.",
"properties": { "properties": {
"mongoDumpPath": { "mongoDumpPath": {
"type": "string", "type": "string",
@ -841,16 +876,31 @@
"default": "mysqldump", "default": "mysqldump",
"description": "The file path of where \"mysqldump\" is located. Default is \"mysqldump\"" "description": "The file path of where \"mysqldump\" is located. Default is \"mysqldump\""
}, },
"pgDumpPath": {
"type": "string",
"default": "pg_dump",
"description": "The file path of where \"pg_dump\" is located. Default is \"pg_dump\""
},
"backupIntervalHours": { "backupIntervalHours": {
"type": "integer", "type": "integer",
"default": 24, "default": 24,
"description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours" "description": "How often should the autobackup run in hours from the second meshcentral starts up? Default is every 24 hours"
}, },
"backupHour": {
"type": "integer",
"default": 0,
"description": "At which hour the autobackup should run. This forces a daily backup, overrules a custom 'backupIntervalHours'."
},
"keepLastDaysBackup": { "keepLastDaysBackup": {
"type": "integer", "type": "integer",
"default": 10, "default": 10,
"description": "How many days of backups should the autobackup keep? Default is 10 Days worth" "description": "How many days of backups should the autobackup keep? Default is 10 Days worth"
}, },
"zipCompression" : {
"type": "integer",
"default": "5",
"description": "Set the zip compression level, 1=fast/less small file to 9=slow/smallest file."
},
"zipPassword": { "zipPassword": {
"type": "string", "type": "string",
"default": "", "default": "",
@ -862,6 +912,31 @@
"default": "meshcentral-backups", "default": "meshcentral-backups",
"description": "The file path where backup files are kept. The default is \"meshcentral-backups\" which sits next to \"meshcentral-data\"." "description": "The file path where backup files are kept. The default is \"meshcentral-backups\" which sits next to \"meshcentral-data\"."
}, },
"backupName": {
"type": "string",
"default": "meshcentral-autobackup-",
"description": "The filename of the backupfile. The default is \"meshcentral-autobackup-\", the filename is appended with the time of backup."
},
"backupWebFolders": {
"type": "boolean",
"default": false,
"description": "Add views, public and emails directories if overridden"
},
"backupOtherFolders": {
"type": "boolean",
"default": false,
"description": "Also add files and recordings folder to the backup"
},
"backupIgnoreFilesGlob": {
"type": "array",
"default": [],
"description": "Glob for ignoring files in the data directory. For example [\"**/*.log\"] !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !! Don't do string..."
},
"backupSkipFoldersGlob":{
"type": "array",
"default": [],
"description": "Glob for ignoring directories in the data directory. For example [\"**/signedagents\"]"
},
"googleDrive": { "googleDrive": {
"type": "object", "type": "object",
"description": "Enabled automated upload of the server backups to a Google Drive account, once enabled you need to go in \"My Server\" tab as administrator to associate the account.", "description": "Enabled automated upload of the server backups to a Google Drive account, once enabled you need to go in \"My Server\" tab as administrator to associate the account.",
@ -1098,6 +1173,11 @@
"default": 2, "default": 2,
"description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages." "description": "Valid numbers are 1 and 2, changes the style of the login page and some secondary pages."
}, },
"showModernUIToggle": {
"type": "boolean",
"default": false,
"description": "When set to true, the user will be able to toggle between the modern and classic UI."
},
"title": { "title": {
"type": "string", "type": "string",
"default": "MeshCentral", "default": "MeshCentral",
@ -1133,6 +1213,11 @@
"default": true, "default": true,
"description": "When set to false, this setting will disable the mobile site." "description": "When set to false, this setting will disable the mobile site."
}, },
"orphanAgentUser": {
"type": "string",
"default": null,
"description": "If an agent attempts to connect to a unknown device group, automatically create a new device group and grant access to the specified user. Example: admin"
},
"maxDeviceView": { "maxDeviceView": {
"type": "integer", "type": "integer",
"default": null, "default": null,
@ -1597,6 +1682,11 @@
"default": true, "default": true,
"description": "Set to false to disable SMS 2FA." "description": "Set to false to disable SMS 2FA."
}, },
"duo2factor": {
"type": "boolean",
"default": true,
"description": "Set to false to disable Duo 2FA."
},
"push2factor": { "push2factor": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@ -1882,6 +1972,11 @@
"default": false, "default": false,
"description": "If true, user consent is accepted after the timeout." "description": "If true, user consent is accepted after the timeout."
}, },
"autoAcceptIfNoUser": {
"type": "boolean",
"default": false,
"description": "If true, user consent is accepted if no user is logged in."
},
"oldStyle": { "oldStyle": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
@ -2085,6 +2180,11 @@
"default": null, "default": null,
"description": "When set, idle users will be disconnected after a set amounts of minutes." "description": "When set, idle users will be disconnected after a set amounts of minutes."
}, },
"logoutOnIdleSessionTimeout": {
"type": "boolean",
"default": true,
"description": "Determines whether MeshCentral should logout after the session idle timeout elapsed or should just disconnect remote desktop, terminal and files."
},
"userConsentFlags": { "userConsentFlags": {
"type": "object", "type": "object",
"description": "Use this section to require user consent for this domain.", "description": "Use this section to require user consent for this domain.",
@ -2617,6 +2717,26 @@
}, },
"description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL." "description": "This is used to create HTTP redirections. For example setting \"redirects\": { \"example\":\"https://example.com\" } will make it so that anyone accessing /example on the server will get redirected to the specified URL."
}, },
"duo2factor": {
"type": "object",
"properties": {
"integrationkey": {
"type": "string",
"default": "",
"description": "Integration key from Duo"
},
"secretkey": {
"type": "string",
"default": "",
"description": "Secret key from Duo"
},
"apihostname": {
"type": "string",
"default": "",
"description": "API Hostname from Duo"
}
}
},
"yubikey": { "yubikey": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2795,12 +2915,10 @@
}, },
"user": { "user": {
"type": "string", "type": "string",
"format": "string",
"description": "SMTP username." "description": "SMTP username."
}, },
"pass": { "pass": {
"type": "string", "type": "string",
"format": "string",
"description": "SMTP password." "description": "SMTP password."
}, },
"tls": { "tls": {
@ -3153,7 +3271,6 @@
] ]
} }
], ],
"additionalProperties": false,
"properties": { "properties": {
"newAccounts": { "newAccounts": {
"type": "boolean", "type": "boolean",
@ -3361,8 +3478,7 @@
"required": [ "required": [
"client_id", "client_id",
"client_secret" "client_secret"
], ]
"additionalProperties": false
}, },
"issuer": { "issuer": {
"type": [ "type": [
@ -3452,8 +3568,7 @@
} }
} }
} }
}, }
"additionalProperties": false
}, },
"custom": { "custom": {
"type": "object", "type": "object",
@ -3504,8 +3619,7 @@
"type": "string", "type": "string",
"description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)" "description": "REQUIRED IF USING GROUPS: Customer ID from Google Workspace Admin Console (https://admin.google.com/ac/accountsettings/profile)"
} }
}, }
"additionalProperties": false
}, },
"groups": { "groups": {
"type": "object", "type": "object",
@ -3557,8 +3671,7 @@
"default": "groups", "default": "groups",
"description": "Custom claim to use." "description": "Custom claim to use."
} }
}, }
"additionalProperties": false
} }
} }
} }
@ -3623,8 +3736,7 @@
"description": "EAB HMAC KEY", "description": "EAB HMAC KEY",
"default": "" "default": ""
} }
}, }
"additionalProperties": false
} }
}, },
"required": [ "required": [
@ -3719,12 +3831,10 @@
}, },
"user": { "user": {
"type": "string", "type": "string",
"format": "string",
"description": "SMTP username." "description": "SMTP username."
}, },
"pass": { "pass": {
"type": "string", "type": "string",
"format": "string",
"description": "SMTP password." "description": "SMTP password."
}, },
"tls": { "tls": {

View file

@ -583,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.
@ -656,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);
@ -1348,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) {
@ -1656,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; })
} }
@ -1992,9 +1995,17 @@ 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);
@ -2003,8 +2014,12 @@ function CreateMeshCentralServer(config, args) {
obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://alt.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' });
@ -2089,18 +2104,24 @@ 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 === true) { 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 {
else if (typeof obj.config.settings.autobackup == 'object'){ if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; } if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; } if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
if (obj.config.settings.autobackup.backuphour != null ) { obj.config.settings.autobackup.backupintervalhours = 24; if ((typeof obj.config.settings.autobackup.backuphour != 'number') || (obj.config.settings.autobackup.backuphour > 23 || obj.config.settings.autobackup.backuphour < 0 )) { obj.config.settings.autobackup.backuphour = 0; }}
else {obj.config.settings.autobackup.backuphour = -1 };
//arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !!
if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []}
else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); };
if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []}
else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
if (typeof obj.config.settings.autobackup.backuppath == 'string') { obj.backuppath = (obj.config.settings.autobackup.backuppath = (obj.path.resolve(obj.config.settings.autobackup.backuppath))) } else { obj.config.settings.autobackup.backuppath = obj.backuppath };
if (typeof obj.config.settings.autobackup.backupname != 'string') { obj.config.settings.autobackup.backupname = 'meshcentral-autobackup-'};
} }
// Check that autobackup path is not within the "meshcentral-data" folder. // Check if the database is capable of performing a backup
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)))) { obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21);
delete obj.config.settings.autobackup;
}
// 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) {
@ -2262,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
@ -2391,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);
} }
@ -3858,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) { }
} }
} }
@ -3915,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
@ -4027,7 +4059,16 @@ function InstallModules(modules, args, func) {
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.
@ -4076,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); } }
@ -4106,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."
}; };
*/ */
@ -4160,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;
@ -4181,17 +4224,17 @@ 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','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 (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.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') && (passport.indexOf('openid-client') == -1)) { if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client@5.7.1') == -1)) {
if ((nodeVersion >= 17) if ((nodeVersion >= 17)
|| ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13)) || ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13))
|| ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15)) || ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15))
|| ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) { || ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) {
passport.push('openid-client'); passport.push('openid-client@5.7.1');
} else { } else {
addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25); addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
delete config.domains[i].authstrategies.oidc; delete config.domains[i].authstrategies.oidc;
@ -4202,35 +4245,37 @@ function mainStart() {
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@7.0.1', 'body-parser@1.20.3', 'cbor@5.2.0', 'compression@1.7.4', 'cookie-session@2.0.0', 'express@4.21.0', 'express-handlebars@7.1.3', 'express-ws@5.0.2', '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.17.1', '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('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
@ -4239,10 +4284,10 @@ function mainStart() {
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 // Enable S3 Support
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.1'); } if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.2'); }
} }
// Setup common password blocking // Setup common password blocking
@ -4256,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) {
@ -4278,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', 'grouptoast', 'groupmessage']; const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'listevents', 'logintokens', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'removedevice', 'editdevice', 'addlocaldevice', 'addamtdevice', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup', 'devicesharing', 'devicepower', 'indexagenterrorlog', 'agentdownload', 'report', 'grouptoast', 'groupmessage', 'webrelay'];
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } } if (args.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,6 +65,7 @@ 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.");
@ -109,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]"); }
@ -259,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."); }
@ -788,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'"));
@ -948,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"));
@ -1490,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; }
@ -1714,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); }
@ -2084,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
@ -2106,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); }
@ -2116,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);
@ -2527,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

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,7 @@ 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 (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
} }
if (typeof domain.notificationmessages == 'object') { if (typeof domain.notificationmessages == 'object') {

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,7 @@ 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 (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
} }
if (typeof domain.notificationmessages == 'object') { if (typeof domain.notificationmessages == 'object') {
@ -923,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; }
@ -931,6 +935,7 @@ 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 (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
} }
if (typeof domain.notificationmessages == 'object') { if (typeof domain.notificationmessages == 'object') {
@ -942,14 +947,14 @@ 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 (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
} }
if (typeof domain.notificationmessages == 'object') { if (typeof domain.notificationmessages == 'object') {
@ -1002,6 +1007,7 @@ 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 (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
} }
if (typeof domain.notificationmessages == 'object') { if (typeof domain.notificationmessages == 'object') {
@ -1231,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);
@ -1285,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;
}

1588
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.32", "version": "1.1.42",
"keywords": [ "keywords": [
"Remote Device Management", "Remote Device Management",
"Remote Device Monitoring", "Remote Device Monitoring",
@ -37,21 +37,21 @@
"sample-config-advanced.json" "sample-config-advanced.json"
], ],
"dependencies": { "dependencies": {
"@yetzt/nedb": "1.8.0", "@seald-io/nedb": "4.0.4",
"archiver": "7.0.1", "archiver": "7.0.1",
"body-parser": "1.20.3", "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.21.0", "express": "4.21.2",
"express-handlebars": "7.1.3", "express-handlebars": "7.1.3",
"express-ws": "5.0.2", "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",
"ua-parser-js": "1.0.37", "ua-parser-js": "1.0.39",
"ws": "8.17.1", "ws": "8.18.0",
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
"engines": { "engines": {

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 + ')');
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -180,6 +180,7 @@
self.prevClipboardText = null; self.prevClipboardText = null;
self.clipboardReadTimer = setInterval(function(){ self.clipboardReadTimer = setInterval(function(){
if(navigator.clipboard.readText != null){ 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() navigator.clipboard.readText()
.then(function(data){ .then(function(data){
if(data != self.prevClipboard){ if(data != self.prevClipboard){

File diff suppressed because one or more lines are too long

View file

@ -155,7 +155,6 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
if (Msg[1] == 1) { obj.ProcessCopyRectMsg(Msg[2]); } if (Msg[1] == 1) { obj.ProcessCopyRectMsg(Msg[2]); }
else if (Msg[1] == 2) { obj.Canvas.drawImage(Msg[2], obj.rotX(Msg[3], Msg[4]), obj.rotY(Msg[3], Msg[4])); delete Msg[2]; } else if (Msg[1] == 2) { obj.Canvas.drawImage(Msg[2], obj.rotX(Msg[3], Msg[4]), obj.rotY(Msg[3], Msg[4])); delete Msg[2]; }
obj.PendingOperations.splice(i, 1); obj.PendingOperations.splice(i, 1);
delete Msg;
obj.TilesDrawn++; obj.TilesDrawn++;
if ((obj.TilesDrawn == obj.tilesReceived) && (obj.KillDraw < obj.TilesDrawn)) { obj.KillDraw = obj.TilesDrawn = obj.tilesReceived = 0; } if ((obj.TilesDrawn == obj.tilesReceived) && (obj.KillDraw < obj.TilesDrawn)) { obj.KillDraw = obj.TilesDrawn = obj.tilesReceived = 0; }
return true; return true;
@ -221,12 +220,16 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; } if ((cmd == 3) || (cmd == 4) || (cmd == 7)) { X = (view[4] << 8) + view[5]; Y = (view[6] << 8) + view[7]; }
if (obj.debugmode > 2) { console.log('CMD', cmd, cmdsize, X, Y); } if (obj.debugmode > 2) { console.log('CMD', cmd, cmdsize, X, Y); }
// Fix for view being too large for String.fromCharCode.apply()
var chunkSize = 10000;
let result = '';
for (let i = 0; i < view.length; i += chunkSize) { result += String.fromCharCode.apply(null, view.slice(i, i + chunkSize)); }
// Record the command if needed // Record the command if needed
if (obj.recordedData != null) { if (obj.recordedData != null) {
if (cmdsize > 65000) { if (cmdsize > 65000) {
obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdsize) + obj.shortToStr(cmd) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + String.fromCharCode.apply(null, view))); obj.recordedData.push(recordingEntry(2, 1, obj.shortToStr(27) + obj.shortToStr(8) + obj.intToStr(cmdsize) + obj.shortToStr(cmd) + obj.shortToStr(0) + obj.shortToStr(0) + obj.shortToStr(0) + result));
} else { } else {
obj.recordedData.push(recordingEntry(2, 1, String.fromCharCode.apply(null, view))); obj.recordedData.push(recordingEntry(2, 1, result));
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
public/scripts/bootstrap-min.js vendored Normal file

File diff suppressed because one or more lines are too long

6314
public/scripts/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -17,6 +17,7 @@ function QV(x, y) { try { QS(x).display = (y ? '' : 'none'); } catch (x) { } }
function QA(x, y) { Q(x).innerHTML += y; } // "Q" append function QA(x, y) { Q(x).innerHTML += y; } // "Q" append
function QH(x, y) { Q(x).innerHTML = y; } // "Q" html function QH(x, y) { Q(x).innerHTML = y; } // "Q" html
function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class function QC(x) { try { return Q(x).classList; } catch (x) { } } // "Q" class
function QVH(x, y) { try { y ? Q(x).classList.remove('visually-hidden') : Q(x).classList.add('visually-hidden'); } catch (x) { } } // "Q" visibility
// Move cursor to end of input box // Move cursor to end of input box
function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; } function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; }

6
public/scripts/fontawesome/all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/scripts/jquery-min.js vendored Normal file

File diff suppressed because one or more lines are too long

8617
public/scripts/jquery.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/** /**
* marked v14.0.0 - a markdown parser * marked v14.1.3 - a markdown parser
* Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed) * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
* https://github.com/markedjs/marked * https://github.com/markedjs/marked
*/ */
@ -253,7 +253,7 @@
code(src) { code(src) {
const cap = this.rules.block.code.exec(src); const cap = this.rules.block.code.exec(src);
if (cap) { if (cap) {
const text = cap[0].replace(/^ {1,4}/gm, ''); const text = cap[0].replace(/^(?: {1,4}| {0,3}\t)/gm, '');
return { return {
type: 'code', type: 'code',
raw: cap[0], raw: cap[0],
@ -437,7 +437,7 @@
itemContents = line.slice(indent); itemContents = line.slice(indent);
indent += cap[1].length; indent += cap[1].length;
} }
if (blankLine && /^ *$/.test(nextLine)) { // Items begin with at most one blank line if (blankLine && /^[ \t]*$/.test(nextLine)) { // Items begin with at most one blank line
raw += nextLine + '\n'; raw += nextLine + '\n';
src = src.substring(nextLine.length + 1); src = src.substring(nextLine.length + 1);
endEarly = true; endEarly = true;
@ -447,13 +447,19 @@
const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`); const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`); const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`); const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
const htmlBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}<[a-z].*>`, 'i');
// Check if following lines should be included in List Item // Check if following lines should be included in List Item
while (src) { while (src) {
const rawLine = src.split('\n', 1)[0]; const rawLine = src.split('\n', 1)[0];
let nextLineWithoutTabs;
nextLine = rawLine; nextLine = rawLine;
// Re-align to follow commonmark nesting rules // Re-align to follow commonmark nesting rules
if (this.options.pedantic) { if (this.options.pedantic) {
nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
nextLineWithoutTabs = nextLine;
}
else {
nextLineWithoutTabs = nextLine.replace(/\t/g, ' ');
} }
// End list item if found code fences // End list item if found code fences
if (fencesBeginRegex.test(nextLine)) { if (fencesBeginRegex.test(nextLine)) {
@ -463,16 +469,20 @@
if (headingBeginRegex.test(nextLine)) { if (headingBeginRegex.test(nextLine)) {
break; break;
} }
// End list item if found start of html block
if (htmlBeginRegex.test(nextLine)) {
break;
}
// End list item if found start of new bullet // End list item if found start of new bullet
if (nextBulletRegex.test(nextLine)) { if (nextBulletRegex.test(nextLine)) {
break; break;
} }
// Horizontal rule found // Horizontal rule found
if (hrRegex.test(src)) { if (hrRegex.test(nextLine)) {
break; break;
} }
if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible if (nextLineWithoutTabs.search(/[^ ]/) >= indent || !nextLine.trim()) { // Dedent if possible
itemContents += '\n' + nextLine.slice(indent); itemContents += '\n' + nextLineWithoutTabs.slice(indent);
} }
else { else {
// not enough indentation // not enough indentation
@ -480,7 +490,7 @@
break; break;
} }
// paragraph continuation unless last line was a different block level element // paragraph continuation unless last line was a different block level element
if (line.search(/[^ ]/) >= 4) { // indented code block if (line.replace(/\t/g, ' ').search(/[^ ]/) >= 4) { // indented code block
break; break;
} }
if (fencesBeginRegex.test(line)) { if (fencesBeginRegex.test(line)) {
@ -499,7 +509,7 @@
} }
raw += rawLine + '\n'; raw += rawLine + '\n';
src = src.substring(rawLine.length + 1); src = src.substring(rawLine.length + 1);
line = nextLine.slice(indent); line = nextLineWithoutTabs.slice(indent);
} }
} }
if (!list.loose) { if (!list.loose) {
@ -507,7 +517,7 @@
if (endsWithBlankLine) { if (endsWithBlankLine) {
list.loose = true; list.loose = true;
} }
else if (/\n *\n *$/.test(raw)) { else if (/\n[ \t]*\n[ \t]*$/.test(raw)) {
endsWithBlankLine = true; endsWithBlankLine = true;
} }
} }
@ -969,15 +979,15 @@
/** /**
* Block-Level Grammar * Block-Level Grammar
*/ */
const newline = /^(?: *(?:\n|$))+/; const newline = /^(?:[ \t]*(?:\n|$))+/;
const blockCode = /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/; const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
const bullet = /(?:[*+-]|\d{1,9}[.)])/; const bullet = /(?:[*+-]|\d{1,9}[.)])/;
const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/) const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/)
.replace(/bull/g, bullet) // lists can interrupt .replace(/bull/g, bullet) // lists can interrupt
.replace(/blockCode/g, / {4}/) // indented code blocks can interrupt .replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code blocks can interrupt
.replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt .replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
.replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt .replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
.replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt .replace(/heading/g, / {0,3}#{1,6}/) // ATX heading can interrupt
@ -986,7 +996,7 @@
const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
const blockText = /^[^\n]+/; const blockText = /^[^\n]+/;
const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/; const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
const def = edit(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/) const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/)
.replace('label', _blockLabel) .replace('label', _blockLabel)
.replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/) .replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/)
.getRegex(); .getRegex();
@ -1006,9 +1016,9 @@
+ '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
+ '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4) + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
+ '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5) + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)' // (5)
+ '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6) + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (6)
+ '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag + '|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) open tag
+ '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag + '|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) closing tag
+ ')', 'i') + ')', 'i')
.replace('comment', _comment) .replace('comment', _comment)
.replace('tag', _tag) .replace('tag', _tag)
@ -1055,7 +1065,7 @@
.replace('hr', hr) .replace('hr', hr)
.replace('heading', ' {0,3}#{1,6}(?:\\s|$)') .replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
.replace('blockquote', ' {0,3}>') .replace('blockquote', ' {0,3}>')
.replace('code', ' {4}[^\\n]') .replace('code', '(?: {4}| {0,3}\t)[^\\n]')
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n')
.replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)') .replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)')
@ -1118,7 +1128,7 @@
const punctuation = edit(/^((?![*_])[\spunctuation])/, 'u') const punctuation = edit(/^((?![*_])[\spunctuation])/, 'u')
.replace(/punctuation/g, _punctuation).getRegex(); .replace(/punctuation/g, _punctuation).getRegex();
// sequences em should skip over [title](link), `code`, <html> // sequences em should skip over [title](link), `code`, <html>
const blockSkip = /\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g; const blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, 'u') const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, 'u')
.replace(/punct/g, _punctuation) .replace(/punct/g, _punctuation)
.getRegex(); .getRegex();
@ -1335,11 +1345,6 @@
if (this.options.pedantic) { if (this.options.pedantic) {
src = src.replace(/\t/g, ' ').replace(/^ +$/gm, ''); src = src.replace(/\t/g, ' ').replace(/^ +$/gm, '');
} }
else {
src = src.replace(/^( *)(\t+)/gm, (_, leading, tabs) => {
return leading + ' '.repeat(tabs.length);
});
}
let token; let token;
let lastToken; let lastToken;
let cutSrc; let cutSrc;
@ -2082,6 +2087,7 @@
class _Hooks { class _Hooks {
options; options;
block;
constructor(options) { constructor(options) {
this.options = options || exports.defaults; this.options = options || exports.defaults;
} }
@ -2108,13 +2114,25 @@
processAllTokens(tokens) { processAllTokens(tokens) {
return tokens; return tokens;
} }
/**
* Provide function to tokenize markdown
*/
provideLexer() {
return this.block ? _Lexer.lex : _Lexer.lexInline;
}
/**
* Provide function to parse tokens
*/
provideParser() {
return this.block ? _Parser.parse : _Parser.parseInline;
}
} }
class Marked { class Marked {
defaults = _getDefaults(); defaults = _getDefaults();
options = this.setOptions; options = this.setOptions;
parse = this.parseMarkdown(_Lexer.lex, _Parser.parse); parse = this.parseMarkdown(true);
parseInline = this.parseMarkdown(_Lexer.lexInline, _Parser.parseInline); parseInline = this.parseMarkdown(false);
Parser = _Parser; Parser = _Parser;
Renderer = _Renderer; Renderer = _Renderer;
TextRenderer = _TextRenderer; TextRenderer = _TextRenderer;
@ -2287,8 +2305,8 @@
if (!(prop in hooks)) { if (!(prop in hooks)) {
throw new Error(`hook '${prop}' does not exist`); throw new Error(`hook '${prop}' does not exist`);
} }
if (prop === 'options') { if (['options', 'block'].includes(prop)) {
// ignore options property // ignore options and block properties
continue; continue;
} }
const hooksProp = prop; const hooksProp = prop;
@ -2346,7 +2364,7 @@
parser(tokens, options) { parser(tokens, options) {
return _Parser.parse(tokens, options ?? this.defaults); return _Parser.parse(tokens, options ?? this.defaults);
} }
parseMarkdown(lexer, parser) { parseMarkdown(blockType) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const parse = (src, options) => { const parse = (src, options) => {
const origOpt = { ...options }; const origOpt = { ...options };
@ -2366,7 +2384,10 @@
} }
if (opt.hooks) { if (opt.hooks) {
opt.hooks.options = opt; opt.hooks.options = opt;
opt.hooks.block = blockType;
} }
const lexer = opt.hooks ? opt.hooks.provideLexer() : (blockType ? _Lexer.lex : _Lexer.lexInline);
const parser = opt.hooks ? opt.hooks.provideParser() : (blockType ? _Parser.parse : _Parser.parseInline);
if (opt.async) { if (opt.async) {
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src) return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src)
.then(src => lexer(src, opt)) .then(src => lexer(src, opt))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/scripts/select2.full.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
public/scripts/sweetalert2.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
document.addEventListener("DOMContentLoaded", function () {
const themeStylesheet = document.getElementById("theme-stylesheet");
// Load saved theme from local storage
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
const safeTheme = ((savedTheme != 'default') ? encodeURIComponent(savedTheme) : encodeURIComponent('..'));
themeStylesheet.href = `styles/themes/${safeTheme}/bootstrap-min.css`;
}
// Initialize Select2 on all select elements with the 'select2' class
$(".select2").select2({
theme: "bootstrap-5",
width: $(this).data("width")
? $(this).data("width")
: $(this).hasClass("w-100")
? "100%"
: "style",
placeholder: $(this).data("placeholder"),
});
});

View file

@ -1 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return r=[function(e,t,r){function n(){}Object.defineProperty(t,"__esModule",{value:!0}),n.prototype.activate=function(e){this._terminal=e},n.prototype.dispose=function(){},n.prototype.fit=function(){var e,t=this.proposeDimensions();t&&this._terminal&&(e=this._terminal._core,this._terminal.rows===t.rows&&this._terminal.cols===t.cols||(e._renderService.clear(),this._terminal.resize(t.cols,t.rows)))},n.prototype.proposeDimensions=function(){var e,t,r,n;if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement)return e=this._terminal._core,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue("height")),n=Math.max(0,parseInt(n.getPropertyValue("width"))),t=window.getComputedStyle(this._terminal.element),r=r-(parseInt(t.getPropertyValue("padding-top"))+parseInt(t.getPropertyValue("padding-bottom"))),n=n-(parseInt(t.getPropertyValue("padding-right"))+parseInt(t.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth,{cols:Math.max(2,Math.floor(n/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(r/e._renderService.dimensions.actualCellHeight))}},t.FitAddon=n}],n={},o.m=r,o.c=n,o.d=function(e,t,r){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0);function o(e){var t;return(n[e]||(t=n[e]={i:e,l:!1,exports:{}},r[e].call(t.exports,t,t.exports,o),t.l=!0,t)).exports}var r,n}) ((e,t)=>{"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()})(window,function(){return r=[function(e,t,r){function n(){}Object.defineProperty(t,"__esModule",{value:!0}),n.prototype.activate=function(e){this._terminal=e},n.prototype.dispose=function(){},n.prototype.fit=function(){var e,t=this.proposeDimensions();t&&this._terminal&&(e=this._terminal._core,this._terminal.rows===t.rows&&this._terminal.cols===t.cols||(e._renderService.clear(),this._terminal.resize(t.cols,t.rows)))},n.prototype.proposeDimensions=function(){var e,t,r,n;if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement)return e=this._terminal._core,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue("height")),n=Math.max(0,parseInt(n.getPropertyValue("width"))),t=window.getComputedStyle(this._terminal.element),r=r-(parseInt(t.getPropertyValue("padding-top"))+parseInt(t.getPropertyValue("padding-bottom"))),n=n-(parseInt(t.getPropertyValue("padding-right"))+parseInt(t.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth,{cols:Math.max(2,Math.floor(n/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(r/e._renderService.dimensions.actualCellHeight))}},t.FitAddon=n}],n={},o.m=r,o.c=n,o.d=function(e,t,r){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0);function o(e){var t;return(n[e]||(t=n[e]={i:e,l:!1,exports:{}},r[e].call(t.exports,t,t.exports,o),t.l=!0,t)).exports}var r,n})

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-adler32.js"),function(){var b=65521,v=5552;ZLIB.adler32=function(r,e,o,t){if("string"==typeof e){var a,d=r,c=e,C=o,h=t,A=d>>>16&65535;if(d&=65535,1==h)d+=255&c.charCodeAt(C),b<=d&&(d-=b),b<=(A+=d)&&(A-=b);else{if(null===c)return 1;if(h<16){for(;h--;)A+=d+=255&c.charCodeAt(C++);return b<=d&&(d-=b),d|(A%=b)<<16}for(;v<=h;){for(h-=v,a=347;A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)),--a;);d%=b,A%=b}if(h){for(;16<=h;)h-=16,A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++));for(;h--;)A+=d+=255&c.charCodeAt(C++);d%=b,A%=b}}return d|A<<16}var f,n=r,i=e,u=o,l=t,s=n>>>16&65535;if(n&=65535,1==l)n+=i[u],b<=n&&(n-=b),b<=(s+=n)&&(s-=b);else{if(null===i)return 1;if(l<16){for(;l--;)s+=n+=i[u++];return b<=n&&(n-=b),n|(s%=b)<<16}for(;v<=l;){for(l-=v,f=347;s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[u++])+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]),--f;);n%=b,s%=b}if(l){for(;16<=l;)l-=16,s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[u++])+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]))+(n+=i[u++]);for(;l--;)s+=n+=i[u++];n%=b,s%=b}}return n|s<<16},ZLIB.adler32_combine=function(r,e,o){var t,a;return o<0?4294967295:(a=(o%=b)*(t=65535&r),b<=(t+=(65535&e)+b-1)&&(t-=b),b<=t&&(t-=b),b<<1<=(a=a%b+((r>>16&65535)+(e>>16&65535)+b-o))&&(a-=b<<1),b<=a&&(a-=b),t|a<<16)}}() "undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-adler32.js"),(()=>{var b=65521,v=5552;ZLIB.adler32=function(r,e,o,t){if("string"==typeof e){var a,d=r,c=e,C=o,h=t,A=d>>>16&65535;if(d&=65535,1==h)d+=255&c.charCodeAt(C),b<=d&&(d-=b),b<=(A+=d)&&(A-=b);else{if(null===c)return 1;if(h<16){for(;h--;)A+=d+=255&c.charCodeAt(C++);return b<=d&&(d-=b),d|(A%=b)<<16}for(;v<=h;){for(h-=v,a=347;A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)),--a;);d%=b,A%=b}if(h){for(;16<=h;)h-=16,A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A=(A+=d+=255&c.charCodeAt(C++))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++)))+(d+=255&c.charCodeAt(C++));for(;h--;)A+=d+=255&c.charCodeAt(C++);d%=b,A%=b}}return d|A<<16}var f,n=r,i=e,l=o,u=t,s=n>>>16&65535;if(n&=65535,1==u)n+=i[l],b<=n&&(n-=b),b<=(s+=n)&&(s-=b);else{if(null===i)return 1;if(u<16){for(;u--;)s+=n+=i[l++];return b<=n&&(n-=b),n|(s%=b)<<16}for(;v<=u;){for(u-=v,f=347;s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[l++])+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]),--f;);n%=b,s%=b}if(u){for(;16<=u;)u-=16,s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s+=n+=i[l++])+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]))+(n+=i[l++]);for(;u--;)s+=n+=i[l++];n%=b,s%=b}}return n|s<<16},ZLIB.adler32_combine=function(r,e,o){var t,a;return o<0?4294967295:(a=(o%=b)*(t=65535&r),b<=(t+=(65535&e)+b-1)&&(t-=b),b<=t&&(t-=b),b<<1<=(a=a%b+((r>>16&65535)+(e>>16&65535)+b-o))&&(a-=b<<1),b<=a&&(a-=b),t|a<<16)}})()

View file

@ -1 +1 @@
"undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-crc32.js"),function(){var C=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];ZLIB.crc32=function(r,e,o,n){if("string"==typeof e){var t=r,f=e,c=o,a=n;if(null==f)return 0;for(t^=4294967295;8<=a;)t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,a-=8;if(a)for(;t=C[255&(t^f.charCodeAt(c++))]^t>>>8,--a;);return 4294967295^t}var i=r,u=e,d=o,A=n;if(null==u)return 0;for(i^=4294967295;8<=A;)i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,i=C[255&(i^u[d++])]^i>>>8,A-=8;if(A)for(;i=C[255&(i^u[d++])]^i>>>8,--A;);return 4294967295^i};function a(r,e){for(var o=0,n=0;e;)1&e&&(n^=r[o]),e>>=1,o++;return n}function i(r,e){for(var o=0;o<32;o++)r[o]=a(e,e[o])}ZLIB.crc32_combine=function(r,e,o){var n,t,f,c;if(!(o<=0)){for(f=new Array(32),(c=new Array(32))[0]=3988292384,n=t=1;n<32;n++)c[n]=t,t<<=1;for(i(f,c),i(c,f);i(f,c),1&o&&(r=a(f,r)),0!=(o>>=1)&&(i(c,f),1&o&&(r=a(c,r)),0!=(o>>=1)););r^=e}return r}}() "undefined"==typeof ZLIB&&alert("ZLIB is not defined. SRC zlib.js before zlib-crc32.js"),(()=>{var C=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];function a(r,e){for(var o=0,n=0;e;)1&e&&(n^=r[o]),e>>=1,o++;return n}function i(r,e){for(var o=0;o<32;o++)r[o]=a(e,e[o])}ZLIB.crc32=function(r,e,o,n){if("string"==typeof e){var t=r,f=e,c=o,a=n;if(null==f)return 0;for(t^=4294967295;8<=a;)t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,t=C[255&(t^f.charCodeAt(c++))]^t>>>8,a-=8;if(a)for(;t=C[255&(t^f.charCodeAt(c++))]^t>>>8,--a;);return 4294967295^t}var i=r,d=e,u=o,A=n;if(null==d)return 0;for(i^=4294967295;8<=A;)i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,i=C[255&(i^d[u++])]^i>>>8,A-=8;if(A)for(;i=C[255&(i^d[u++])]^i>>>8,--A;);return 4294967295^i},ZLIB.crc32_combine=function(r,e,o){var n,t,f,c;if(!(o<=0)){for(f=new Array(32),(c=new Array(32))[0]=3988292384,n=t=1;n<32;n++)c[n]=t,t<<=1;for(i(f,c),i(c,f);i(f,c),1&o&&(r=a(f,r)),0!=(o>>=1)&&(i(c,f),1&o&&(r=a(c,r)),0!=(o>>=1)););r^=e}return r}})()

File diff suppressed because one or more lines are too long

6
public/styles/bootstrap-min.css vendored Normal file

File diff suppressed because one or more lines are too long

12057
public/styles/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
public/styles/fontawesome/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/styles/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -263,6 +263,16 @@ body {
right: 3px; right: 3px;
} }
.textnewui {
color: white;
font-weight: bold;
padding-top: 5px;
cursor: pointer;
position: absolute;
right: 0;
margin-right: 10px;
}
.LogoffLinkColor { .LogoffLinkColor {
color:white; color:white;
} }
@ -2896,7 +2906,7 @@ a {
width: 28px; width: 28px;
} }
.viewSelector3 { .viewSelector3, .uiSelector7 {
margin-left: 2px; margin-left: 2px;
margin-top: 2px; margin-top: 2px;
background: url(../images/views.png) -56px 0px; background: url(../images/views.png) -56px 0px;
@ -2996,6 +3006,13 @@ a {
background-color: #AAA; background-color: #AAA;
} }
.uiSelector_end {
width: 32px;
height: 32px;
float: left;
margin: 3px;
}
.uiSelectorSel { .uiSelectorSel {
background-color: #BBB; background-color: #BBB;
opacity: 0.8; opacity: 0.8;

1
public/styles/sweetalert2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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