Compare commits
900 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a765bb13 | ||
|
|
7ad4b917be | ||
|
|
d10173a018 | ||
|
|
0e3a6b4915 | ||
|
|
b949cecc5f | ||
|
|
c7cbf2f12a | ||
|
|
d49afdd7bf | ||
|
|
133e77c8c6 | ||
|
|
e404e86b9f | ||
|
|
c6da201af8 | ||
|
|
9a27d7637c | ||
|
|
5aa2467409 | ||
|
|
9398afd07e | ||
|
|
b2cd84035b | ||
|
|
97547d72a3 | ||
|
|
7faf043c35 | ||
|
|
9df0330896 | ||
|
|
42f61ea46e | ||
|
|
0d65080a8a | ||
|
|
bd4d8b12d4 | ||
|
|
18ae8bdbf4 | ||
|
|
46c76f7234 | ||
|
|
e65bf77111 | ||
|
|
f19ad6c664 | ||
|
|
fe2f12149d | ||
|
|
91bd5ae702 | ||
|
|
38f5bf2e0f | ||
|
|
79f00bcaab | ||
|
|
c55065505b | ||
|
|
e0e8a3fcaa | ||
|
|
3f77cfa93a | ||
|
|
5ee9aa2410 | ||
|
|
0ab3f01ca6 | ||
|
|
4b621a01fb | ||
|
|
f2681de87d | ||
|
|
c90fa55c99 | ||
|
|
edeef03f00 | ||
|
|
d7fe87d1db | ||
|
|
9d4f51e970 | ||
|
|
0b376fe5a0 | ||
|
|
9fd40751b2 | ||
|
|
d246307fae | ||
|
|
711bb56a93 | ||
|
|
5734bcc33a | ||
|
|
1310c57397 | ||
|
|
854d6c00d2 | ||
|
|
c96d7ff1ca | ||
|
|
712f06db3c | ||
|
|
cac505e2cd | ||
|
|
9d962bc523 | ||
|
|
f079692b16 | ||
|
|
3ee06abfe8 | ||
|
|
b46ddf2f70 | ||
|
|
f7b958d28b | ||
|
|
64c8d2c238 | ||
|
|
31f2224a93 | ||
|
|
de685556c8 | ||
|
|
92375ddc93 | ||
|
|
ea80f8595e | ||
|
|
763f76b68f | ||
|
|
1a02539f23 | ||
|
|
90b71e924f | ||
|
|
73c18c4dd5 | ||
|
|
def62075c7 | ||
|
|
998769a888 | ||
|
|
ca6ec5ebc9 | ||
|
|
0dd56d5708 | ||
|
|
95729d2a88 | ||
|
|
1ae7b9f641 | ||
|
|
c66a9a12ef | ||
|
|
2c310450cf | ||
|
|
b6e022c01e | ||
|
|
e66776c5da | ||
|
|
d1cb184e9e | ||
|
|
6aa60d556f | ||
|
|
8d4e9bcede | ||
|
|
eb0d24cf0b | ||
|
|
fe02d3158d | ||
|
|
61d3487f8a | ||
|
|
a23725eb40 | ||
|
|
9b60271f31 | ||
|
|
31f328086e | ||
|
|
7aa4061cad | ||
|
|
54bb0177ee | ||
|
|
ce70f4ac08 | ||
|
|
f712cd9425 | ||
|
|
c4592dcc4f | ||
|
|
2a274fe569 | ||
|
|
5d0b5acdfb | ||
|
|
f80ba62cfc | ||
|
|
5da849063b | ||
|
|
68ac8cf86c | ||
|
|
8e70cd7187 | ||
|
|
5cf468159d | ||
|
|
c92b88a374 | ||
|
|
1b01b90cd6 | ||
|
|
6a366fe174 | ||
|
|
e2362a0547 | ||
|
|
59fcc0dbc6 | ||
|
|
988983b880 | ||
|
|
a1854fa074 | ||
|
|
22fc95926a | ||
|
|
c2eb1f2516 | ||
|
|
832d11739b | ||
|
|
ab7be919a0 | ||
|
|
624d61db43 | ||
|
|
b0d8e3fe48 | ||
|
|
a33047747a | ||
|
|
39e81befe1 | ||
|
|
8eeb96fb0d | ||
|
|
f9228ad0eb | ||
|
|
ce4217c346 | ||
|
|
d9262f7c9d | ||
|
|
8e8ec4f88a | ||
|
|
18167499d9 | ||
|
|
da5d03b0e7 | ||
|
|
c41eb72a2c | ||
|
|
ef4d764ab4 | ||
|
|
c16ff89902 | ||
|
|
041e8021d1 | ||
|
|
6e31562ecb | ||
|
|
874ef23c40 | ||
|
|
7bd5b66ebc | ||
|
|
975e49a190 | ||
|
|
462c383b77 | ||
|
|
dbb5b4ba11 | ||
|
|
545bf58e8d | ||
|
|
30b390bdbf | ||
|
|
d0a51e90e9 | ||
|
|
c773857b17 | ||
|
|
cae1f7ea14 | ||
|
|
b398cb7fa9 | ||
|
|
5a1a97ca7e | ||
|
|
dd21f14f4e | ||
|
|
3da60b43ac | ||
|
|
727080ab68 | ||
|
|
54170c44a0 | ||
|
|
8a5ad1563d | ||
|
|
911d987a84 | ||
|
|
9a8f4e8ebe | ||
|
|
badee98b71 | ||
|
|
d44faed29e | ||
|
|
01c585f7f1 | ||
|
|
777eb53476 | ||
|
|
b71c69e81d | ||
|
|
7d59210d05 | ||
|
|
fc387ca417 | ||
|
|
fc83211e90 | ||
|
|
9ebd23a518 | ||
|
|
3f8301e9d7 | ||
|
|
b39235643e | ||
|
|
0ec8b061c8 | ||
|
|
e58d659fa9 | ||
|
|
45169b2cfd | ||
|
|
c09d2fad3e | ||
|
|
7928f7fb30 | ||
|
|
438289b2ed | ||
|
|
561fc67f33 | ||
|
|
aa7767f37c | ||
|
|
f23792881e | ||
|
|
c920b28acc | ||
|
|
36f1b4d5be | ||
|
|
141bec559f | ||
|
|
0d885e6fa0 | ||
|
|
e10f5277e9 | ||
|
|
f33768fe32 | ||
|
|
1e565768d1 | ||
|
|
5193fef888 | ||
|
|
63930c4b33 | ||
|
|
ac27034542 | ||
|
|
cfe9345b53 | ||
|
|
1e2d736d6d | ||
|
|
0c825251eb | ||
|
|
ccf00b7d06 | ||
|
|
6d412a7bea | ||
|
|
5a0d3054b8 | ||
|
|
6dbc6d2d07 | ||
|
|
ea8e1b1076 | ||
|
|
d1368791e9 | ||
|
|
590166f847 | ||
|
|
19d0df7e7f | ||
|
|
1d87c42977 | ||
|
|
ec7505987d | ||
|
|
37729269ba | ||
|
|
952bcde25f | ||
|
|
4848df4faf | ||
|
|
41d1f9d26f | ||
|
|
113adb5b85 | ||
|
|
8e5aa35bf3 | ||
|
|
1139a37338 | ||
|
|
b20e51561a | ||
|
|
5ff44bbae8 | ||
|
|
2beeb6f644 | ||
|
|
3eede1bf43 | ||
|
|
9f8ea3a6b5 | ||
|
|
23679d119b | ||
|
|
a3fd6008a0 | ||
|
|
d0014b3f8b | ||
|
|
04c96eb2ff | ||
|
|
df64c750cc | ||
|
|
b90b2ac0bf | ||
|
|
39a1755b3d | ||
|
|
61fb6898c0 | ||
|
|
bc34f140c8 | ||
|
|
0bee2be3cf | ||
|
|
1d67172dd3 | ||
|
|
b99a97eb48 | ||
|
|
a1899a719f | ||
|
|
5fcfa8f369 | ||
|
|
7172d1f701 | ||
|
|
8bc760855e | ||
|
|
5fc3683ebb | ||
|
|
626416a202 | ||
|
|
d84afb939a | ||
|
|
3cd875d6ee | ||
|
|
f5e63b7cbd | ||
|
|
8b20f44dd5 | ||
|
|
59a3a22ea5 | ||
|
|
2f34b7e83b | ||
|
|
4d5ec6cac1 | ||
|
|
7635109f6d | ||
|
|
de60b7f952 | ||
|
|
405261a67e | ||
|
|
4bcec73e07 | ||
|
|
d81c00c0b0 | ||
|
|
d2e4f12ea3 | ||
|
|
f7c79166da | ||
|
|
2b5337329a | ||
|
|
21206b670c | ||
|
|
1d04a13a64 | ||
|
|
9e309584db | ||
|
|
0d56504d96 | ||
|
|
d17918936d | ||
|
|
0a64b80654 | ||
|
|
35e38a71c0 | ||
|
|
6fe30b7730 | ||
|
|
516a14b4ca | ||
|
|
390991123a | ||
|
|
d367b2ed87 | ||
|
|
5a410ccd5b | ||
|
|
ad1d82152a | ||
|
|
7c79dbcda1 | ||
|
|
cf23a3df81 | ||
|
|
e8cbebaffe | ||
|
|
ac0d805378 | ||
|
|
7b48e3b5f5 | ||
|
|
ea6682e06a | ||
|
|
4e37455471 | ||
|
|
b4323223cc | ||
|
|
bf00de4425 | ||
|
|
f95dbdd404 | ||
|
|
e1e59953f4 | ||
|
|
d2d9f7a13e | ||
|
|
21e196e35c | ||
|
|
0ccce62c9d | ||
|
|
3a78cb83c4 | ||
|
|
8dd5545a2b | ||
|
|
2f61e3edad | ||
|
|
7caf2aa05d | ||
|
|
cee181fb61 | ||
|
|
fa39f8a105 | ||
|
|
c1e3354c91 | ||
|
|
aae551dab9 | ||
|
|
3a28e33efb | ||
|
|
92385e3d73 | ||
|
|
adeaac1588 | ||
|
|
77f44fc308 | ||
|
|
3efa680361 | ||
|
|
899ff0c742 | ||
|
|
61f1c22c27 | ||
|
|
08877844c4 | ||
|
|
698b7fb056 | ||
|
|
fbd4533477 | ||
|
|
6f2b57998f | ||
|
|
a6acb35a31 | ||
|
|
fc29e60939 | ||
|
|
5b76a31644 | ||
|
|
41e4213fc5 | ||
|
|
999ae7f67f | ||
|
|
44991975d3 | ||
|
|
6da9222871 | ||
|
|
10b57dcf9e | ||
|
|
1cfe0e2c31 | ||
|
|
7e504a28e6 | ||
|
|
92869ec78b | ||
|
|
9264b9d281 | ||
|
|
d8a91d3150 | ||
|
|
5e71bcc634 | ||
|
|
4d75d48eea | ||
|
|
df6474802d | ||
|
|
31c323583b | ||
|
|
4b891c5be0 | ||
|
|
86713cacac | ||
|
|
95d60fef13 | ||
|
|
b4e7e7384d | ||
|
|
62cae4cf8a | ||
|
|
aaad8b79cc | ||
|
|
b0d9b17e36 | ||
|
|
991c23c5f9 | ||
|
|
87c5745594 | ||
|
|
707982a71b | ||
|
|
a8fc5e1187 | ||
|
|
40ac6aa636 | ||
|
|
1d9de2e144 | ||
|
|
ee0018e4d1 | ||
|
|
721c909158 | ||
|
|
2630931eee | ||
|
|
13b8ca3686 | ||
|
|
a59da2fb9a | ||
|
|
c3470f493b | ||
|
|
3d815824ba | ||
|
|
9619a83ba7 | ||
|
|
f6c7761afb | ||
|
|
9fd3e4c569 | ||
|
|
118b0c58dc | ||
|
|
57442e4988 | ||
|
|
602eb3c64a | ||
|
|
28c522c5bb | ||
|
|
df91c90d33 | ||
|
|
81557ab2d4 | ||
|
|
6b21bacad2 | ||
|
|
46ebadf440 | ||
|
|
6c3e60e13c | ||
|
|
7955bc4954 | ||
|
|
482e79f913 | ||
|
|
0a89d07937 | ||
|
|
c053c14dd0 | ||
|
|
5950b2c829 | ||
|
|
42a07e9d74 | ||
|
|
d7341ab153 | ||
|
|
74d6252699 | ||
|
|
b08f3827f5 | ||
|
|
6976992735 | ||
|
|
b1c3e2a8e7 | ||
|
|
c67a76bcc2 | ||
|
|
62199d8057 | ||
|
|
52a2194116 | ||
|
|
2b3c329a54 | ||
|
|
17cf36edd9 | ||
|
|
a171cde2ff | ||
|
|
5d5e861a4a | ||
|
|
26ac23c80d | ||
|
|
5a7e3d9869 | ||
|
|
abbb0fa9ee | ||
|
|
89b67ff999 | ||
|
|
6c685d5557 | ||
|
|
49b561260a | ||
|
|
aa8f45f4a9 | ||
|
|
7cf14a2b69 | ||
|
|
7e7361de9b | ||
|
|
4cd7b408fa | ||
|
|
bc6451fee5 | ||
|
|
f1ba76a423 | ||
|
|
385a4738cd | ||
|
|
5c13f178be | ||
|
|
323ef2d50a | ||
|
|
dd249938b3 | ||
|
|
30d958fbd9 | ||
|
|
1c8d664962 | ||
|
|
b22e56b6d2 | ||
|
|
bc2f34b629 | ||
|
|
e8da6a607c | ||
|
|
77d268d064 | ||
|
|
23ee76e26a | ||
|
|
be3e333b40 | ||
|
|
e3f68226d2 | ||
|
|
b71b4d04e6 | ||
|
|
bf7957ebff | ||
|
|
19eb1235f5 | ||
|
|
274bb525a5 | ||
|
|
33c0e82286 | ||
|
|
56d6527bf5 | ||
|
|
3ce2fd92c0 | ||
|
|
eb27334b82 | ||
|
|
414d9b9561 | ||
|
|
1747ff7550 | ||
|
|
f39b6f8859 | ||
|
|
ca868afdd1 | ||
|
|
410c84c30b | ||
|
|
18b731fd36 | ||
|
|
832e618602 | ||
|
|
7b8cf85740 | ||
|
|
1dca9e2235 | ||
|
|
30d570f28b | ||
|
|
f854c80421 | ||
|
|
f5891f2946 | ||
|
|
1da33f0ade | ||
|
|
e025e9558b | ||
|
|
ccf57bee1a | ||
|
|
4ba08a96f7 | ||
|
|
548edd13d6 | ||
|
|
31ebb21e0b | ||
|
|
4a3c6db0ea | ||
|
|
f9af1ffc90 | ||
|
|
95e7997e60 | ||
|
|
9081a6aeac | ||
|
|
afc6165827 | ||
|
|
c9c0a6cb67 | ||
|
|
b46c322c41 | ||
|
|
4ff5a5c912 | ||
|
|
65d1346e06 | ||
|
|
5d1c8ca68b | ||
|
|
9294488d4e | ||
|
|
d2a0946f22 | ||
|
|
3be8ec5add | ||
|
|
102489447d | ||
|
|
8e8cc4b327 | ||
|
|
ce93c896ce | ||
|
|
7b67b992e2 | ||
|
|
95bbd7157f | ||
|
|
8e6cc14981 | ||
|
|
862e2ee80b | ||
|
|
81e98033fc | ||
|
|
fbae83dd9f | ||
|
|
8498414ae9 | ||
|
|
d33aa25e5b | ||
|
|
8775b7dcf7 | ||
|
|
e6ee2034d1 | ||
|
|
f874e76f0c | ||
|
|
105612c199 | ||
|
|
4027ee1b28 | ||
|
|
4f11d7fdbc | ||
|
|
b8238ef34d | ||
|
|
4b6da03d2f | ||
|
|
05fca6cb36 | ||
|
|
150e2337f5 | ||
|
|
dfc08b05a9 | ||
|
|
fbe1445691 | ||
|
|
05ee40c591 | ||
|
|
872794e15f | ||
|
|
334fee706e | ||
|
|
e9c28d03b5 | ||
|
|
2d75bbde33 | ||
|
|
ab0d9c188d | ||
|
|
d71ca89b58 | ||
|
|
0e896fe9fe | ||
|
|
0a59f9dbae | ||
|
|
c1bec67839 | ||
|
|
234acd3347 | ||
|
|
473b9d0265 | ||
|
|
6f47f2bc89 | ||
|
|
548c1b9bb4 | ||
|
|
3ffc92e917 | ||
|
|
bab35e7bca | ||
|
|
4be5b7273e | ||
|
|
436a3cb9be | ||
|
|
e6a71d77a1 | ||
|
|
9e9cd821bf | ||
|
|
27f7648953 | ||
|
|
c937764980 | ||
|
|
e8c4f322cb | ||
|
|
fb62df326c | ||
|
|
2318a0bb32 | ||
|
|
636255c8af | ||
|
|
9241c43435 | ||
|
|
0e055ef741 | ||
|
|
ee59e582b6 | ||
|
|
aa87fd61bb | ||
|
|
b4361d1c4e | ||
|
|
3e23741cc8 | ||
|
|
016e43c191 | ||
|
|
5ed0e4f59e | ||
|
|
1b60e4dbfb | ||
|
|
269ec02dc0 | ||
|
|
fce123e971 | ||
|
|
bca1d277a4 | ||
|
|
017f777de2 | ||
|
|
4cbb95a85f | ||
|
|
7912df307a | ||
|
|
82c70659b8 | ||
|
|
0b2368df9d | ||
|
|
26bb350122 | ||
|
|
44926e16d5 | ||
|
|
0f23fa0ade | ||
|
|
a80c0ef48c | ||
|
|
79e137dbe7 | ||
|
|
af3e7f6186 | ||
|
|
936aaa0b2b | ||
|
|
272015483b | ||
|
|
bb93c113bd | ||
|
|
9ef1cc64a4 | ||
|
|
a14390e049 | ||
|
|
a77d40505a | ||
|
|
76485713df | ||
|
|
9ef48eac1f | ||
|
|
7bc28b5dad | ||
|
|
3e04e4d8c6 | ||
|
|
f2bc7d5349 | ||
|
|
e5e86fee19 | ||
|
|
4637e6b3b3 | ||
|
|
fb15d94976 | ||
|
|
a7018e74bc | ||
|
|
a3b3ecd8b1 | ||
|
|
f2e43cc6da | ||
|
|
5c1249ccca | ||
|
|
0232056219 | ||
|
|
6f78f9e276 | ||
|
|
4098805798 | ||
|
|
980eca3765 | ||
|
|
512695df6c | ||
|
|
15ff7d12a1 | ||
|
|
6637f08fe7 | ||
|
|
990da39fde | ||
|
|
ddcff9f0d2 | ||
|
|
bfec20ac81 | ||
|
|
550ee34f00 | ||
|
|
e6f27fca36 | ||
|
|
b887457ec4 | ||
|
|
4648dbeb26 | ||
|
|
cdab553800 | ||
|
|
cd2ede8369 | ||
|
|
97ed6c8285 | ||
|
|
df94b50d5d | ||
|
|
9542e77e1b | ||
|
|
3f8bbe68b0 | ||
|
|
37911d091c | ||
|
|
1855dd1d56 | ||
|
|
9f57c27dd1 | ||
|
|
8547de340c | ||
|
|
cd48909dcb | ||
|
|
ceba76cfde | ||
|
|
c331eca679 | ||
|
|
03cab630c7 | ||
|
|
aaff3abc1a | ||
|
|
b02541cd02 | ||
|
|
18af676c22 | ||
|
|
57f77057b4 | ||
|
|
b9c706cec2 | ||
|
|
b1451a1c8a | ||
|
|
b860981925 | ||
|
|
327f29159e | ||
|
|
a2aed9e772 | ||
|
|
aaff8232b0 | ||
|
|
ab835591db | ||
|
|
6cb05ce009 | ||
|
|
21bd171167 | ||
|
|
c546333cf7 | ||
|
|
1e9607ba8e | ||
|
|
f93bb3a82f | ||
|
|
0d7564d845 | ||
|
|
6e2321b5c6 | ||
|
|
757cef3f29 | ||
|
|
d02b63a4a2 | ||
|
|
1295af4677 | ||
|
|
329ba43e81 | ||
|
|
6cacec010b | ||
|
|
9986567cf0 | ||
|
|
b01078bf55 | ||
|
|
0b0f2999db | ||
|
|
db06c0c925 | ||
|
|
ee7ceb7898 | ||
|
|
0de32f9fee | ||
|
|
a822b88756 | ||
|
|
ef6fd23a4f | ||
|
|
7c2eea68b6 | ||
|
|
775ed95de4 | ||
|
|
227b4c68e7 | ||
|
|
82f10f0193 | ||
|
|
00db0a3a39 | ||
|
|
cb0b02152a | ||
|
|
78a49c504a | ||
|
|
23666fed1f | ||
|
|
64bef74317 | ||
|
|
a57ab97bb1 | ||
|
|
7e2bc8422f | ||
|
|
a5efc5e899 | ||
|
|
275119fc7d | ||
|
|
c248eada46 | ||
|
|
4e580bb74d | ||
|
|
6c4a4e6966 | ||
|
|
b490d6bc15 | ||
|
|
36741c767f | ||
|
|
e967f00977 | ||
|
|
bb616903ee | ||
|
|
c38cb3d46c | ||
|
|
c48447d3e6 | ||
|
|
e4001e67ef | ||
|
|
f33dbd31e5 | ||
|
|
45722c70d4 | ||
|
|
b0bb66fee8 | ||
|
|
97a624e2fc | ||
|
|
10b68408ca | ||
|
|
bfae0d6c6e | ||
|
|
a17fd2f268 | ||
|
|
e488a002da | ||
|
|
4cad780613 | ||
|
|
947d84316b | ||
|
|
91c83e2622 | ||
|
|
6e4c8b0d16 | ||
|
|
532992b03f | ||
|
|
a6640d533c | ||
|
|
0ba8b534e7 | ||
|
|
b17d04ddc5 | ||
|
|
906a42219d | ||
|
|
7e1657d632 | ||
|
|
5581f3ace8 | ||
|
|
0a01c5d4e4 | ||
|
|
712277eb9e | ||
|
|
2cb9d1484e | ||
|
|
409d1d6248 | ||
|
|
d5b79a92c4 | ||
|
|
fb8883a9d9 | ||
|
|
dd92aa6628 | ||
|
|
4a6703b63a | ||
|
|
bc0550a791 | ||
|
|
a3717095e7 | ||
|
|
ed56213d1b | ||
|
|
08d5aef9c9 | ||
|
|
d80a74b915 | ||
|
|
c05cbeae88 | ||
|
|
0ce305ebcf | ||
|
|
a7f996678f | ||
|
|
27b750813e | ||
|
|
c97e9f4f27 | ||
|
|
5c527c4632 | ||
|
|
12c3e4ee53 | ||
|
|
ca64d11552 | ||
|
|
86485cf19f | ||
|
|
55d81e0bf6 | ||
|
|
4f65f88f43 | ||
|
|
eadf6fbaf0 | ||
|
|
c286c21fb6 | ||
|
|
b7385e382c | ||
|
|
6ba4bf7202 | ||
|
|
5c99db9edb | ||
|
|
d3e9616908 | ||
|
|
4790f40179 | ||
|
|
7b016eac58 | ||
|
|
a8aa294199 | ||
|
|
e62bfadb76 | ||
|
|
b4887b7766 | ||
|
|
e04659a63d | ||
|
|
23b78960da | ||
|
|
cb87cc8172 | ||
|
|
13ead1ddeb | ||
|
|
fed8f021f2 | ||
|
|
e0d73b2fcd | ||
|
|
2173921f07 | ||
|
|
6c15bcbe05 | ||
|
|
b52da08c84 | ||
|
|
c6ebcc6e8e | ||
|
|
2117c19717 | ||
|
|
16bb4346e3 | ||
|
|
7c263872b1 | ||
|
|
71e3d788e0 | ||
|
|
b4e0dbcd80 | ||
|
|
aadfbafc33 | ||
|
|
cdde9b5d67 | ||
|
|
25b0ac3a6d | ||
|
|
a60b4cbbea | ||
|
|
e20a585b3a | ||
|
|
58580beb96 | ||
|
|
47767e86a1 | ||
|
|
e35af29ad0 | ||
|
|
1002bbb952 | ||
|
|
0f91268547 | ||
|
|
19e03eaf29 | ||
|
|
c9b47edc75 | ||
|
|
2791e03338 | ||
|
|
4b5b9250f1 | ||
|
|
ad14c83400 | ||
|
|
d095831b6d | ||
|
|
556d72dfad | ||
|
|
9884db3ce0 | ||
|
|
bb7a5e9cce | ||
|
|
b57bb1ad27 | ||
|
|
548d76efed | ||
|
|
a39f874c71 | ||
|
|
5ad481a2e0 | ||
|
|
7c4312f0c6 | ||
|
|
66284e2813 | ||
|
|
467a30ba66 | ||
|
|
6e1138ee5b | ||
|
|
ae55285493 | ||
|
|
553bcc9b8d | ||
|
|
1a34cba9fb | ||
|
|
78ef26ec3e | ||
|
|
d2e52fbbff | ||
|
|
dd172a2b24 | ||
|
|
e1b435fb0c | ||
|
|
484146fb09 | ||
|
|
a0b7280893 | ||
|
|
87382465aa | ||
|
|
932584982c | ||
|
|
62c2ee08b1 | ||
|
|
4171a0766f | ||
|
|
5126c5a1ef | ||
|
|
82c94cdf9d | ||
|
|
8b8ec48430 | ||
|
|
4702926773 | ||
|
|
a397d9a79a | ||
|
|
75a92ce485 | ||
|
|
14c3816fc9 | ||
|
|
2b5e0e23be | ||
|
|
6d27ff5969 | ||
|
|
d8d13bda42 | ||
|
|
31bb6d7864 | ||
|
|
12a248f102 | ||
|
|
49fa88fb69 | ||
|
|
3a3663bb55 | ||
|
|
0e671bbe81 | ||
|
|
2045946eb3 | ||
|
|
a4c0400ba5 | ||
|
|
622d8c2fc7 | ||
|
|
128919b851 | ||
|
|
3d5fc3ae2e | ||
|
|
eb8df307ec | ||
|
|
489f1aa57a | ||
|
|
d19e456efb | ||
|
|
2d8c3cfc64 | ||
|
|
55f16ab0ac | ||
|
|
2547a1fea7 | ||
|
|
7209093767 | ||
|
|
b99fb68675 | ||
|
|
4fb8e4713d | ||
|
|
f057633e14 | ||
|
|
9fc7fd2c5a | ||
|
|
f5c056fdeb | ||
|
|
434bfe1845 | ||
|
|
014e89ea8b | ||
|
|
ea79be0a8f | ||
|
|
972acd5718 | ||
|
|
140b7f3dea | ||
|
|
8bf905e1c2 | ||
|
|
dd279d3ea2 | ||
|
|
1e93057c52 | ||
|
|
b59b76a4f2 | ||
|
|
aed96a84e3 | ||
|
|
43d57597ab | ||
|
|
352b5d4d64 | ||
|
|
dabd0e3808 | ||
|
|
604d50585b | ||
|
|
7c56b722c9 | ||
|
|
ed3f9b65bf | ||
|
|
94812f53b2 | ||
|
|
37b14a6b0b | ||
|
|
b087ed0f50 | ||
|
|
28e3b18256 | ||
|
|
955c6e1888 | ||
|
|
e35e621743 | ||
|
|
4a0a86b83a | ||
|
|
d97e0b1f62 | ||
|
|
86375207cc | ||
|
|
4c6696c0f7 | ||
|
|
0a147a18d4 | ||
|
|
bdc3ef56d1 | ||
|
|
b67b90f197 | ||
|
|
69dfcaf140 | ||
|
|
6e2466046e | ||
|
|
e32cceb1b4 | ||
|
|
545c029752 | ||
|
|
ec25d91544 | ||
|
|
5c2da83cc1 | ||
|
|
04a71800fa | ||
|
|
cf8f4b682a | ||
|
|
42f275ac93 | ||
|
|
2bb65110f8 | ||
|
|
9ffa2bc116 | ||
|
|
fcdac82f53 | ||
|
|
f7f59483a5 | ||
|
|
551a9cb672 | ||
|
|
bca4f1c217 | ||
|
|
53a96fb26b | ||
|
|
3a6e01d08e | ||
|
|
3ce50b5cc1 | ||
|
|
3de12093c6 | ||
|
|
f1c2e307dc | ||
|
|
d5d782094b | ||
|
|
3c6e6fc4bf | ||
|
|
d6a77e5c19 | ||
|
|
277333724b | ||
|
|
14a1639a4f | ||
|
|
62f52e91c7 | ||
|
|
0ee671ac74 | ||
|
|
81b35baa61 | ||
|
|
95dfb608e3 | ||
|
|
f856b96aa5 | ||
|
|
4e6e4fa67e | ||
|
|
a3dfc30607 | ||
|
|
5160926477 | ||
|
|
5824ccd3b4 | ||
|
|
5deceb1a21 | ||
|
|
86ad4bc926 | ||
|
|
819ca16ccd | ||
|
|
93ea293a23 | ||
|
|
23c2627b3b | ||
|
|
7ea744fdfe | ||
|
|
b0dbbf4b5a | ||
|
|
403c313771 | ||
|
|
bed9d8cf33 | ||
|
|
5fa27a0339 | ||
|
|
749d5b0ea5 | ||
|
|
f297857d70 | ||
|
|
200212796a | ||
|
|
a84557ef0c | ||
|
|
68763137e9 | ||
|
|
de46556df7 | ||
|
|
f3cd0a4dda | ||
|
|
b1d2d1aea9 | ||
|
|
ef2f111eb0 | ||
|
|
89cf7d8c1e | ||
|
|
cde1d3c80b | ||
|
|
3e57d2dfa4 | ||
|
|
adbfa6122a | ||
|
|
ecc6ce5617 | ||
|
|
bd26ec32cf | ||
|
|
c19d2c5dd0 | ||
|
|
163f8f80f1 | ||
|
|
be5bfddc04 | ||
|
|
77b0c8ea23 | ||
|
|
d5c662fb74 | ||
|
|
a7aacb8802 | ||
|
|
258d7d1d12 | ||
|
|
94b5f8ced9 | ||
|
|
022ebd7311 | ||
|
|
45411d2e90 | ||
|
|
f67d59cc83 | ||
|
|
11007a8a24 | ||
|
|
6adb037a50 | ||
|
|
a98302e058 | ||
|
|
44071e93e9 | ||
|
|
f715a54354 | ||
|
|
ccf027afe5 | ||
|
|
437011524c | ||
|
|
3971b26797 | ||
|
|
66ced6df27 | ||
|
|
05aeb8a244 | ||
|
|
c244b12ec6 | ||
|
|
1ead77ef8d | ||
|
|
2305323734 | ||
|
|
3ea83158fb | ||
|
|
3f0d9484b7 | ||
|
|
fefce68687 | ||
|
|
43c16d02f9 | ||
|
|
53e2c5cf69 | ||
|
|
89942e0c8c | ||
|
|
f0ada3fed0 | ||
|
|
e4d8c04afe | ||
|
|
02ab92195a | ||
|
|
d7dd9c5fcc | ||
|
|
9d322746e6 | ||
|
|
dc6c815cec | ||
|
|
7087504841 | ||
|
|
0205e4f733 | ||
|
|
5e85eb0bb0 | ||
|
|
2267b8311c | ||
|
|
84bcfc73a9 | ||
|
|
c65bf86305 | ||
|
|
1501c9c483 | ||
|
|
181d4db0fe | ||
|
|
e168db19f3 | ||
|
|
19be5817a9 | ||
|
|
c1ecfc823a | ||
|
|
3dd469f1a0 | ||
|
|
4e67575bdf | ||
|
|
18eaebc217 | ||
|
|
7a38b70506 | ||
|
|
775568c7a7 | ||
|
|
328c7a1ba6 | ||
|
|
97eefa3b4c | ||
|
|
f2ca5c6555 | ||
|
|
19c95070b3 | ||
|
|
6789dda69f | ||
|
|
1fcb9124b6 | ||
|
|
f3d93c882f | ||
|
|
7c011b138a | ||
|
|
b76499c907 | ||
|
|
8918208f89 | ||
|
|
c3a9b67070 | ||
|
|
d2d6e95dd9 | ||
|
|
b818ba3548 | ||
|
|
aca0e7aab4 | ||
|
|
0d97c62ed8 | ||
|
|
618ab19693 | ||
|
|
250f2e1b85 | ||
|
|
f079170b70 | ||
|
|
d9481c4537 | ||
|
|
70a2a6a28c | ||
|
|
9064ebb71e | ||
|
|
b2a55fcfac | ||
|
|
376c4d53ac | ||
|
|
dc4116cff5 | ||
|
|
3b0d2da7e7 | ||
|
|
269d4dfbed | ||
|
|
d6b3d8b53b | ||
|
|
281a0787b7 | ||
|
|
136a805c70 | ||
|
|
e56149f162 | ||
|
|
d698760d30 | ||
|
|
7a5649510d | ||
|
|
cd0c564e9f | ||
|
|
a8f89e1068 | ||
|
|
ab5c1f8f55 | ||
|
|
f5db131693 | ||
|
|
b6d73f3d81 | ||
|
|
783b1f6480 | ||
|
|
bd57601cd8 | ||
|
|
c359254aaf | ||
|
|
209eaeb476 | ||
|
|
27c0a14ec6 |
3
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,5 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/Ylianst/MeshCentral/discussions
|
||||
about: Ask a question in discussions.
|
||||
- name: Unofficial Discord Server
|
||||
url: https://discord.gg/8wHC6ASWAc
|
||||
about: Please ask here for support questions.
|
||||
|
|
|
|||
68
.github/ISSUE_TEMPLATE/question.md
vendored
|
|
@ -1,68 +0,0 @@
|
|||
---
|
||||
name: Question
|
||||
about: Create a question for community help
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your issue**
|
||||
A clear and concise description of what your issue is.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Server Software (please complete the following information):**
|
||||
- OS: [e.g. Ubuntu]
|
||||
- Virtualization: [e.g. Docker]
|
||||
- Network: [e.g. LAN/WAN, reverse proxy, cloudflare, ssl offload, etc...]
|
||||
- Version: [e.g. 1.0.43]
|
||||
- Node: [e.g. 18.4.0]
|
||||
|
||||
**Client Device (please complete the following information):**
|
||||
- Device: [e.g. Laptop]
|
||||
- OS: [e.g. Ubuntu]
|
||||
- Network: [e.g. Local to Meshcentral, Remote over WAN]
|
||||
- Browser: [e.g. Google Chrome]
|
||||
- MeshCentralRouter Version: [if applicable]
|
||||
|
||||
**Remote Device (please complete the following information):**
|
||||
- Device: [e.g. Laptop]
|
||||
- OS: [e.g. Windows 10 21H2]
|
||||
- Network: [e.g. Local to Meshcentral, Remote over WAN]
|
||||
- Current Core Version (if known): [**HINT**: Go to a device then `console` Tab then type `info`]
|
||||
|
||||
**Your config.json file**
|
||||
```
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json",
|
||||
"__comment1__": "This is a simple configuration file, all values and sections that start with underscore (_) are ignored. Edit a section and remove the _ in front of the name. Refer to the user's guide for details.",
|
||||
"__comment2__": "See node_modules/meshcentral/sample-config-advanced.json for a more advanced example.",
|
||||
"settings": {
|
||||
"_cert": "myserver.mydomain.com",
|
||||
"_WANonly": true,
|
||||
"_LANonly": true,
|
||||
"_sessionKey": "MyReallySecretPassword1",
|
||||
"_port": 443,
|
||||
"_aliasPort": 443,
|
||||
"_redirPort": 80,
|
||||
"_redirAliasPort": 80
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"_title": "MyServer",
|
||||
"_title2": "Servername",
|
||||
"_minify": true,
|
||||
"_newAccounts": true,
|
||||
"_userNameIsEmail": true
|
||||
}
|
||||
},
|
||||
"_letsencrypt": {
|
||||
"__comment__": "Requires NodeJS 8.x or better, Go to https://letsdebug.net/ first before trying Let's Encrypt.",
|
||||
"email": "myemail@mydomain.com",
|
||||
"names": "myserver.mydomain.com",
|
||||
"production": false
|
||||
}
|
||||
}
|
||||
```
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
|
@ -48,4 +48,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
|
|||
4
.github/workflows/deploy-docs.yml
vendored
|
|
@ -14,8 +14,8 @@ jobs:
|
|||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install --upgrade pip
|
||||
|
|
|
|||
33
.github/workflows/docker.yml
vendored
|
|
@ -1,49 +1,60 @@
|
|||
name: Docker
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
check-token:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
token: ${{ steps.token.outputs.defined }}
|
||||
token_defined: ${{ steps.token_check.outputs.token_defined }}
|
||||
steps:
|
||||
- id: token
|
||||
- name: Check token
|
||||
id: token_check
|
||||
env:
|
||||
MY_TOKEN: ${{ secrets.MY_TOKEN }}
|
||||
MY_TOKEN: ${{ secrets.MY_TOKEN }}
|
||||
if: "${{ env.MY_TOKEN != '' }}"
|
||||
run: echo "::set-output name=defined::true"
|
||||
run: echo "token_defined=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check-token]
|
||||
if: needs.check-token.outputs.token == 'true'
|
||||
if: needs.check-token.outputs.token_defined == 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.MY_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
|
|
|
|||
4
.github/workflows/release.yml
vendored
|
|
@ -3,6 +3,8 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'package.json'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -10,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Release
|
||||
|
|
|
|||
2
.gitignore
vendored
|
|
@ -10,12 +10,12 @@
|
|||
[Aa]gents/meshcore.min.js
|
||||
[Pp]ublic/translations/
|
||||
[Vv]iews/translations/
|
||||
[Ee]mails/translations/
|
||||
[Pp]ublic/*-min.htm
|
||||
[Vv]iews/*-min.handlebars
|
||||
meshcentral.db
|
||||
meshcentral.db.json
|
||||
mesherrors.txt
|
||||
package-lock.json
|
||||
bob.json
|
||||
.greenlockrc
|
||||
|
||||
|
|
|
|||
2
LICENSE
|
|
@ -186,7 +186,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-2021 Intel Corporation
|
||||
Copyright 2017-2025 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
|||
|
|
@ -241,7 +241,6 @@
|
|||
<Compile Include="redirserver.js" />
|
||||
<Compile Include="taskmanager.js" />
|
||||
<Compile Include="translate\translate.js" />
|
||||
<Compile Include="ua-parser.js" />
|
||||
<Compile Include="webauthn.js" />
|
||||
<Compile Include="webrelayserver.js" />
|
||||
<Compile Include="webserver.js" />
|
||||
|
|
@ -441,7 +440,6 @@
|
|||
<Content Include="LICENSE" />
|
||||
<Content Include="meshcentral-config-schema.json" />
|
||||
<Content Include="package.json" />
|
||||
<Content Include="plugin_development.md" />
|
||||
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.application" />
|
||||
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.config.deploy" />
|
||||
<Content Include="public\clickonce\minirouter\Application Files\MeshMiniRouter_1_0_0_70\MeshMiniRouter.exe.deploy" />
|
||||
|
|
@ -596,12 +594,14 @@
|
|||
<Content Include="readme.md" />
|
||||
<Content Include="sample-config-advanced.json" />
|
||||
<Content Include="sample-config.json" />
|
||||
<Content Include="SECURITY.md" />
|
||||
<Content Include="SourceFileList.txt" />
|
||||
<Content Include="translate\readme.txt" />
|
||||
<Content Include="translate\translate.json" />
|
||||
<Content Include="views\agentinvite.handlebars" />
|
||||
<Content Include="views\default-mobile.handlebars" />
|
||||
<Content Include="views\default.handlebars" />
|
||||
<Content Include="views\default3.handlebars" />
|
||||
<Content Include="views\download.handlebars" />
|
||||
<Content Include="views\download2.handlebars" />
|
||||
<Content Include="views\error404-mobile.handlebars" />
|
||||
|
|
@ -718,6 +718,7 @@
|
|||
<Folder Include="typings\globals\sprintf-js\" />
|
||||
<Folder Include="typings\globals\sqlite3\" />
|
||||
<Folder Include="typings\globals\type-check\" />
|
||||
<Folder Include="typings\globals\ua-parser-js\" />
|
||||
<Folder Include="typings\globals\underscore\" />
|
||||
<Folder Include="typings\globals\uuid\" />
|
||||
<Folder Include="typings\globals\window-size\" />
|
||||
|
|
@ -764,6 +765,7 @@
|
|||
<TypeScriptCompile Include="typings\globals\sprintf-js\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\sqlite3\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\type-check\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\ua-parser-js\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\underscore\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\uuid\index.d.ts" />
|
||||
<TypeScriptCompile Include="typings\globals\window-size\index.d.ts" />
|
||||
|
|
|
|||
49
SECURITY.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Any version of MeshCentral 1.x.x is supported.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.x.x | :white_check_mark: |
|
||||
| < 1.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any concerns or security issue to Ylian Saint-Hilaire (ylianst@gmail.com). If needed, use my PGP key below.
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: BCPG v1.56
|
||||
|
||||
mQMuBF2gC4sRCAClFNvMCCVW3ego3UHBQ6LhSenJfaZYhvn8gaGuemSQxqTI6bla
|
||||
BTAv3aMtQnvqlSuadMMegb+FO6hnaQMlGvpVA1qpkSzgrPS5HrBD3H33J2Nj3i93
|
||||
ZpDPpxdI0ehCj6IJPnl0GxGbpKIN8YpJUFl44wv1lMRFI1lgyb+dCoO60irYdNQB
|
||||
PV85BI+DwPfOBFHunwR78nqMvpvsk9HaeHjEP7oXr952/7EazUowZsMlEfkYnw5S
|
||||
+tLfpCoY3QWkektpJP40nMJSKQdV2NEuED99doA0X+7P1vsvFFFyMH69dnU2uSay
|
||||
XCHpkAbntBy0BGmtF1RnTcOMv2V/LPXnlMdvAQCbmLQzNra3r163tcdRY0jSs+pZ
|
||||
1L3w5tHNj2dzhfpa7wf/SIuds6QTr2LCN6miLoSVCRMMpT7d771b16GwQqWEXzN2
|
||||
+h7dYqrssHPOa8FSUrPerz0+0eFcbMSm5/L/4KXWXoQthURv8aMP9E0iVoUYaaKB
|
||||
7U+5vFEZbpoOZyZmTAjXQMSNZCft0azA82Q+G85euyicWtMv48yNVzUhkdh+M2ud
|
||||
ohkXX2Aor1TqpBJoIeWke7j9D+Bo+lu61zPRx5ed9teUeLJCwqNEjlE+6gre5kxF
|
||||
PoreAtn59QYcBIpzQEWVMbNFlDAR4jMyqIoKCGfBPiRw2V+kunbzqiGQEglIFfOt
|
||||
6sTN/+CJh0ei976VDmE0Z1kMN+CNLgIjIw8fl02V9QgAnHcpqtVUxR4dbGOhVDq5
|
||||
lWv+K75QQlWyXC2k+KboXcaCvH0WZEBACYzO0CfrZ5hP9BSkbj5usSUVGGHwEFAJ
|
||||
t+/04KVY71fW281Ej5kGNaIKxeKsx6+hMo+UXb5ZM+6fANNNxs1cK95sTH6PjkyB
|
||||
tsKxLoa3CV2v9mSE5JiKKt74R9nXVo7PXf6DizwAU2l30Lb6y6y0OdXdCCPAG8Ij
|
||||
FrMgPu5MtjgsO5DnkZfUqDPWHhOgEPyOh3Ho+pvDhNYh5cm2eLQ8g5orzs2FHwbZ
|
||||
DpAHwCdqrlcpBlKJ4W/MZdf1fg2PjqaTWm7ZFiGr91P0F6kltTLWbVKTjLdS0T+D
|
||||
L7QnWWxpYW4gU2FpbnQtSGlsYWlyZSA8eWxpYW5zdEBnbWFpbC5jb20+iF4EExEI
|
||||
AAYFAl2gC4sACgkQg7j/r4DH+kD/3gD+MRedlM53VzOtNOpS6mqDAxj1aWP90HN0
|
||||
AqO6zuCTyGgBAJlunLFKH8IUetmQOhiohB8HVhdm/q4lKRDV7sHdplDyuMwEXaAL
|
||||
ixACAJSU/sCV87he4oZUKzg2/IGl3QoDSbTCOd04dE1IjPjjHbi8t9M7Qau55aM8
|
||||
ypFEsc7zMslL8Fc78EejrKmM3zsB/RU9XWFyrbQwRbaK6OHeEHC2E3AFaG0p09c6
|
||||
d0kZloHuWyEsm5a/3PpbIM1eP9IESJXWCc+bQQt6DxLKHLmkKMwB/icWMg8uMJlx
|
||||
aady8TEq7LH5oFVKsglnwuN1nIkecrf77TVkEqTjIxS6TiOup6zOnioFNKLYBAH0
|
||||
WUnJEYFvx4OIXgQYEQgABgUCXaALiwAKCRCDuP+vgMf6QGFTAQCUj2gGwsFlN0eR
|
||||
Wowv4eLcc3FwQ+lBElUctKg8vNFb0gD/ZWVWsWwKerNgNnf7RGD9mt8G2CKvdgGG
|
||||
oZ2hPP2gU9w=
|
||||
=roW4
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
BIN
agents/MeshCmdARM64.exe
Normal file
BIN
agents/MeshServiceARM64.exe
Normal file
|
|
@ -52,7 +52,7 @@
|
|||
"zenity": "Zkuste nainstalovat / aktualizovat Zenity a spustit znovu",
|
||||
"status": [
|
||||
"NENÍ INSTALOVÁN",
|
||||
"BĚH",
|
||||
"SPUŠTĚNO",
|
||||
"NEFUNGUJE"
|
||||
],
|
||||
"statusDescription": "Aktuální stav agenta",
|
||||
|
|
@ -651,5 +651,98 @@
|
|||
],
|
||||
"statusDescription": "Trenutni status agenta",
|
||||
"description": "Kliknite na dugmad ispod da instalirate ili deinstalirate ovaj softver za daljinsko upravljanje. Kada je instaliran, ovaj softver radi u pozadini, što omogućava da ovim računarom upravlja i kontroliše udaljeni administrator."
|
||||
},
|
||||
"hu": {
|
||||
"agent": "Agent",
|
||||
"agentVersion": "Új verzió",
|
||||
"group": "Eszköz csoport",
|
||||
"url": "Kiszolgáló URL",
|
||||
"meshName": "Csoport név",
|
||||
"meshId": "Csoport azonosító",
|
||||
"serverId": "Kiszolgáló azonosító",
|
||||
"setup": "Beállítás",
|
||||
"update": "Frissítés",
|
||||
"install": "Telepítés",
|
||||
"uninstall": "Eltávolítás",
|
||||
"connect": "Kapcsolódás",
|
||||
"disconnect": "Lekapcsolódás",
|
||||
"cancel": "Mégse",
|
||||
"close": "Bezár",
|
||||
"pressok": "Press OK to disconnect",
|
||||
"elevation": "A szoftver telepítéséhez/eltávolításához megnövelt jogosultságok szükségesek.",
|
||||
"sudo": "Kérjük, próbálja meg újra a sudo használatával.",
|
||||
"ctrlc": "A kilépéshez nyomja meg a Ctrl-C billentyűt.",
|
||||
"commands": "A szöveges változatot a parancssorból futtathatja a következő parancs(okk)al",
|
||||
"graphicalerror": "A telepítő grafikus verziója nem futtatható ezen a rendszeren.",
|
||||
"zenity": "Próbálja meg telepíteni/frissíteni a Zenity-t, és indítsa újra",
|
||||
"status": [
|
||||
"NINCS TELEPÍTVE",
|
||||
"FUT",
|
||||
"NEM FUT"
|
||||
],
|
||||
"statusDescription": "Jelenlegi agent állapota",
|
||||
"description": "Kattintson a Telepítés vagy Eltávolítás gombokra a Távfelügyeleti alkalmazás telepítéséhez vagy eltávolításához. Telepítés után ez az alkalmazás a háttérben fut, lehetővé téve, hogy a számítógépet egy távoli rendszergazda kezelje."
|
||||
},
|
||||
"ca": {
|
||||
"agent": "Agent",
|
||||
"agentVersion": "Nova versió",
|
||||
"group": "Grup de dispositius",
|
||||
"url": "URL del servidor",
|
||||
"meshName": "Nom del grup",
|
||||
"meshId": "Identificador de grup",
|
||||
"serverId": "Identificador del servidor",
|
||||
"setup": "Configuració",
|
||||
"update": "Actualització",
|
||||
"install": "Instal·lar",
|
||||
"uninstall": "Desinstal·la",
|
||||
"connect": "Connecta't",
|
||||
"disconnect": "Desconnecta",
|
||||
"cancel": "Cancel · lar",
|
||||
"close": "Tanca",
|
||||
"pressok": "Premeu D'acord per desconnectar",
|
||||
"elevation": "Es necessiten permisos elevats per instal·lar/desinstal·lar aquest programari.",
|
||||
"sudo": "Si us plau, torna-ho a provar amb sudo.",
|
||||
"ctrlc": "Premeu Ctrl-C per sortir.",
|
||||
"commands": "Podeu executar la versió de text des de la línia d'ordres amb les següents ordres",
|
||||
"graphicalerror": "La versió gràfica d'aquest instal·lador no pot executar-se en aquest sistema",
|
||||
"zenity": "Proveu d'instal·lar/actualitzar Zenity i torneu a executar-lo",
|
||||
"status": [
|
||||
"NO ESTÀ INSTAL · LAT",
|
||||
"CÓRRER",
|
||||
"NO CORRE"
|
||||
],
|
||||
"statusDescription": "Estat actual de l'agent",
|
||||
"description": "Feu clic als botons següents per instal·lar o desinstal·lar aquest programari de gestió remota. Quan s'instal·la, aquest programari s'executa en segon pla i permet que aquest ordinador sigui gestionat i controlat per un administrador remot."
|
||||
},
|
||||
"uk": {
|
||||
"agent": "Агент",
|
||||
"agentVersion": "Нова Версія",
|
||||
"group": "Група Пристроїв",
|
||||
"url": "URL Сервера",
|
||||
"meshName": "Ім'я Групи",
|
||||
"meshId": "Ідентифікатор групи",
|
||||
"serverId": "Ідентифікатор серверу",
|
||||
"setup": "Налаштувати",
|
||||
"update": "Оновлення",
|
||||
"install": "Інсталювати",
|
||||
"uninstall": "Видалити",
|
||||
"connect": "Підключитися",
|
||||
"disconnect": "Відключити",
|
||||
"cancel": "Скасувати",
|
||||
"close": "Закрити",
|
||||
"pressok": "Натисніть OK, щоб від'єднатися",
|
||||
"elevation": "Для інсталяції/деінсталяції цього програмного забезпечення потрібні підвищені дозволи.",
|
||||
"sudo": "Будь ласка, спробуйте ще раз за допомогою sudo.",
|
||||
"ctrlc": "Натисніть Ctrl-C, щоб вийти",
|
||||
"commands": "Ви можете запустити текстову версію з командного рядка за допомогою таких команд",
|
||||
"graphicalerror": "Графічна версія цього інсталятора не може працювати в цій системі",
|
||||
"zenity": "Спробуйте встановити/оновити Zenity та запустіть наново",
|
||||
"status": [
|
||||
"НЕ ВСТАНОВЛЕНО",
|
||||
"ВИКОНУЄТЬСЯ",
|
||||
"НЕ ПРАЦЮЄ"
|
||||
],
|
||||
"statusDescription": "Поточний Статус Агента",
|
||||
"description": "Клікнути кнопки нижче, щоб інсталювати або видалити це програмне забезпечення для віддаленого керування. Після інсталювання ця програма працює у фоновому режимі, що дозволяє віддаленому адміністратору керувати цим комп'ютером."
|
||||
}
|
||||
}
|
||||
BIN
agents/meshagent_android.apk
Normal file
|
|
@ -588,7 +588,7 @@ function run(argv) {
|
|||
}
|
||||
amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } });
|
||||
amtMei.getProvisioningMode(function (result) { if (result) { mestate.ProvisioningMode = result; } });
|
||||
amtMei.getEHBCState(function (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); });
|
||||
amtMei.getEHBCState(function (result) { if (result) { mestate.ehbc = ((result === true) || (typeof result == 'object') && (result.EHBC === true)); } });
|
||||
amtMei.getControlMode(function (result) { if (result) { mestate.controlmode = result; } });
|
||||
amtMei.getMACAddresses(function (result) { if (result) { mestate.mac = result; } });
|
||||
amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } });
|
||||
|
|
|
|||
1151
agents/meshcore.js
|
|
@ -225,19 +225,14 @@ function macos_memUtilization()
|
|||
function windows_thermals()
|
||||
{
|
||||
var ret = [];
|
||||
child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', '/namespace:\\\\root\\wmi', 'PATH', 'MSAcpi_ThermalZoneTemperature', 'get', 'CurrentTemperature']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
|
||||
if(child.stdout.str.trim!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
for (var i = 1; i < lines.length; ++i)
|
||||
{
|
||||
if (lines[i].trim() != '') { ret.push(((parseFloat(lines[i]) / 10) - 273.15).toFixed(2)); }
|
||||
try {
|
||||
ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
|
||||
if (ret[0]) {
|
||||
for (var i = 0; i < ret.length; ++i) {
|
||||
ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
|
@ -285,16 +280,10 @@ function macos_thermals()
|
|||
return (ret);
|
||||
}
|
||||
|
||||
switch(process.platform)
|
||||
{
|
||||
case 'linux':
|
||||
module.exports = { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals };
|
||||
break;
|
||||
case 'win32':
|
||||
module.exports = { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals };
|
||||
break;
|
||||
case 'darwin':
|
||||
module.exports = { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_thermals };
|
||||
break;
|
||||
}
|
||||
const platformConfig = {
|
||||
linux: { cpuUtilization: linux_cpuUtilization, memUtilization: linux_memUtilization, thermals: linux_thermals },
|
||||
win32: { cpuUtilization: windows_cpuUtilization, memUtilization: windows_memUtilization, thermals: windows_thermals },
|
||||
darwin: { cpuUtilization: macos_cpuUtilization, memUtilization: macos_memUtilization, thermals: macos_thermals }
|
||||
};
|
||||
|
||||
module.exports = platformConfig[process.platform];
|
||||
|
|
|
|||
902
agents/modules_meshcore/computer-identifiers.js
Normal file
|
|
@ -0,0 +1,902 @@
|
|||
/*
|
||||
Copyright 2019-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
function trimIdentifiers(val)
|
||||
{
|
||||
for(var v in val)
|
||||
{
|
||||
if (!val[v] || val[v] == 'None' || val[v] == '') { delete val[v]; }
|
||||
}
|
||||
}
|
||||
function trimResults(val)
|
||||
{
|
||||
var i, x;
|
||||
for (i = 0; i < val.length; ++i)
|
||||
{
|
||||
for (x in val[i])
|
||||
{
|
||||
if (x.startsWith('_'))
|
||||
{
|
||||
delete val[i][x];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val[i][x] == null || val[i][x] == 0)
|
||||
{
|
||||
delete val[i][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function brief(headers, obj)
|
||||
{
|
||||
var i, x;
|
||||
for (x = 0; x < obj.length; ++x)
|
||||
{
|
||||
for (i in obj[x])
|
||||
{
|
||||
if (!headers.includes(i))
|
||||
{
|
||||
delete obj[x][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return (obj);
|
||||
}
|
||||
|
||||
function dataHandler(c)
|
||||
{
|
||||
this.str += c.toString();
|
||||
}
|
||||
|
||||
function linux_identifiers()
|
||||
{
|
||||
var identifiers = {};
|
||||
var ret = {};
|
||||
var values = {};
|
||||
|
||||
if (!require('fs').existsSync('/sys/class/dmi/id')) {
|
||||
if (require('fs').existsSync('/sys/firmware/devicetree/base/model')) {
|
||||
if (require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim().startsWith('Raspberry')) {
|
||||
identifiers['board_vendor'] = 'Raspberry Pi';
|
||||
identifiers['board_name'] = require('fs').readFileSync('/sys/firmware/devicetree/base/model').toString().trim();
|
||||
identifiers['board_serial'] = require('fs').readFileSync('/sys/firmware/devicetree/base/serial-number').toString().trim();
|
||||
const memorySlots = [];
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('vcgencmd get_mem arm && vcgencmd get_mem gpu\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
const lines = child.stdout.str.trim().split('\n');
|
||||
if (lines.length == 2) {
|
||||
memorySlots.push({ Locator: "ARM Memory", Size: lines[0].split('=')[1].trim() })
|
||||
memorySlots.push({ Locator: "GPU Memory", Size: lines[1].split('=')[1].trim() })
|
||||
ret.memory = { Memory_Device: memorySlots };
|
||||
}
|
||||
} catch (xx) { }
|
||||
} else {
|
||||
throw('Unknown board');
|
||||
}
|
||||
} else {
|
||||
throw ('this platform does not have DMI statistics');
|
||||
}
|
||||
} else {
|
||||
var entries = require('fs').readdirSync('/sys/class/dmi/id');
|
||||
for (var i in entries) {
|
||||
if (require('fs').statSync('/sys/class/dmi/id/' + entries[i]).isFile()) {
|
||||
try {
|
||||
ret[entries[i]] = require('fs').readFileSync('/sys/class/dmi/id/' + entries[i]).toString().trim();
|
||||
} catch(z) { }
|
||||
if (ret[entries[i]] == 'None') { delete ret[entries[i]]; }
|
||||
}
|
||||
}
|
||||
entries = null;
|
||||
|
||||
identifiers['bios_date'] = ret['bios_date'];
|
||||
identifiers['bios_vendor'] = ret['bios_vendor'];
|
||||
identifiers['bios_version'] = ret['bios_version'];
|
||||
identifiers['bios_serial'] = ret['product_serial'];
|
||||
identifiers['board_name'] = ret['board_name'];
|
||||
identifiers['board_serial'] = ret['board_serial'];
|
||||
identifiers['board_vendor'] = ret['board_vendor'];
|
||||
identifiers['board_version'] = ret['board_version'];
|
||||
identifiers['product_uuid'] = ret['product_uuid'];
|
||||
identifiers['product_name'] = ret['product_name'];
|
||||
}
|
||||
|
||||
try {
|
||||
identifiers['bios_mode'] = (require('fs').statSync('/sys/firmware/efi').isDirectory() ? 'UEFI': 'Legacy');
|
||||
} catch (ex) { identifiers['bios_mode'] = 'Legacy'; }
|
||||
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('cat /proc/cpuinfo | grep -i "model name" | ' + "tr '\\n' ':' | awk -F: '{ print $2 }'\nexit\n");
|
||||
child.waitExit();
|
||||
identifiers['cpu_name'] = child.stdout.str.trim();
|
||||
if (identifiers['cpu_name'] == "") { // CPU BLANK, check lscpu instead
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('lscpu | grep -i "model name" | ' + "tr '\\n' ':' | awk -F: '{ print $2 }'\nexit\n");
|
||||
child.waitExit();
|
||||
identifiers['cpu_name'] = child.stdout.str.trim();
|
||||
}
|
||||
child = null;
|
||||
|
||||
|
||||
// Fetch GPU info
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write("lspci | grep ' VGA ' | tr '\\n' '`' | awk '{ a=split($0,lines" + ',"`"); printf "["; for(i=1;i<a;++i) { split(lines[i],gpu,"r: "); printf "%s\\"%s\\"", (i==1?"":","),gpu[2]; } printf "]"; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
try { identifiers['gpu_name'] = JSON.parse(child.stdout.str.trim()); } catch (xx) { }
|
||||
child = null;
|
||||
|
||||
// Fetch Storage Info
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write("lshw -class disk | tr '\\n' '`' | awk '" + '{ len=split($0,lines,"*"); printf "["; for(i=2;i<=len;++i) { model=""; caption=""; size=""; clen=split(lines[i],item,"`"); for(j=2;j<clen;++j) { split(item[j],tokens,":"); split(tokens[1],key," "); if(key[1]=="description") { caption=substr(tokens[2],2); } if(key[1]=="product") { model=substr(tokens[2],2); } if(key[1]=="size") { size=substr(tokens[2],2); } } if(model=="") { model=caption; } if(caption!="" || model!="") { printf "%s{\\"Caption\\":\\"%s\\",\\"Model\\":\\"%s\\",\\"Size\\":\\"%s\\"}",(i==2?"":","),caption,model,size; } } printf "]"; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
try { identifiers['storage_devices'] = JSON.parse(child.stdout.str.trim()); } catch (xx) { }
|
||||
child = null;
|
||||
|
||||
// Fetch storage volumes using df
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -T | awk \'NR==1 || $1 ~ ".+"{print $3, $4, $5, $7, $2}\' | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\",\\"type\\":\\"%s\\"},", $1, $2, $3, $4, $5}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try { ret.volumes = JSON.parse(child.stdout.str.trim()); } catch (xx) { }
|
||||
child = null;
|
||||
|
||||
values.identifiers = identifiers;
|
||||
values.linux = ret;
|
||||
trimIdentifiers(values.identifiers);
|
||||
|
||||
var dmidecode = require('lib-finder').findBinary('dmidecode');
|
||||
if (dmidecode != null)
|
||||
{
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stderr.str = ''; child.stderr.on('data', dataHandler);
|
||||
child.stdin.write(dmidecode + " -t memory | tr '\\n' '`' | ");
|
||||
child.stdin.write(" awk '{ ");
|
||||
child.stdin.write(' printf("[");');
|
||||
child.stdin.write(' comma="";');
|
||||
child.stdin.write(' c=split($0, lines, "``");');
|
||||
child.stdin.write(' for(i=1;i<=c;++i)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' d=split(lines[i], val, "`");');
|
||||
child.stdin.write(' split(val[1], tokens, ",");');
|
||||
child.stdin.write(' split(tokens[2], dmitype, " ");');
|
||||
child.stdin.write(' dmi = dmitype[3]+0; ');
|
||||
child.stdin.write(' if(dmi == 5 || dmi == 6 || dmi == 16 || dmi == 17)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' ccx="";');
|
||||
child.stdin.write(' printf("%s{\\"%s\\": {", comma, val[2]);');
|
||||
child.stdin.write(' for(j=3;j<d;++j)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' sub(/^[ \\t]*/,"",val[j]);');
|
||||
child.stdin.write(' if(split(val[j],tmp,":")>1)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' sub(/^[ \\t]*/,"",tmp[2]);');
|
||||
child.stdin.write(' gsub(/ /,"",tmp[1]);');
|
||||
child.stdin.write(' printf("%s\\"%s\\": \\"%s\\"", ccx, tmp[1], tmp[2]);');
|
||||
child.stdin.write(' ccx=",";');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("}}");');
|
||||
child.stdin.write(' comma=",";');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("]");');
|
||||
child.stdin.write("}'\nexit\n");
|
||||
child.waitExit();
|
||||
|
||||
try
|
||||
{
|
||||
var j = JSON.parse(child.stdout.str);
|
||||
var i, key, key2;
|
||||
for (i = 0; i < j.length; ++i)
|
||||
{
|
||||
for (key in j[i])
|
||||
{
|
||||
delete j[i][key]['ArrayHandle'];
|
||||
delete j[i][key]['ErrorInformationHandle'];
|
||||
for (key2 in j[i][key])
|
||||
{
|
||||
if (j[i][key][key2] == 'Unknown' || j[i][key][key2] == 'Not Specified' || j[i][key][key2] == '')
|
||||
{
|
||||
delete j[i][key][key2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(j.length > 0){
|
||||
var mem = {};
|
||||
for (i = 0; i < j.length; ++i)
|
||||
{
|
||||
for (key in j[i])
|
||||
{
|
||||
if (mem[key] == null) { mem[key] = []; }
|
||||
mem[key].push(j[i][key]);
|
||||
}
|
||||
}
|
||||
values.linux.memory = mem;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{ }
|
||||
child = null;
|
||||
}
|
||||
|
||||
var usbdevices = require('lib-finder').findBinary('usb-devices');
|
||||
if (usbdevices != null)
|
||||
{
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stderr.str = ''; child.stderr.on('data', dataHandler);
|
||||
child.stdin.write(usbdevices + " | tr '\\n' '`' | ");
|
||||
child.stdin.write(" awk '");
|
||||
child.stdin.write('{');
|
||||
child.stdin.write(' comma="";');
|
||||
child.stdin.write(' printf("[");');
|
||||
child.stdin.write(' len=split($0, group, "``");');
|
||||
child.stdin.write(' for(i=1;i<=len;++i)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' comma2="";');
|
||||
child.stdin.write(' xlen=split(group[i], line, "`");');
|
||||
child.stdin.write(' scount=0;');
|
||||
child.stdin.write(' for(x=1;x<xlen;++x)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' if(line[x] ~ "^S:")');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' ++scount;');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' if(scount>0)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' printf("%s{", comma); comma=",";');
|
||||
child.stdin.write(' for(x=1;x<xlen;++x)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' if(line[x] ~ "^T:")');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' comma3="";');
|
||||
child.stdin.write(' printf("%s\\"hardware\\": {", comma2); comma2=",";');
|
||||
child.stdin.write(' sub(/^T:[ \\t]*/, "", line[x]);');
|
||||
child.stdin.write(' gsub(/= */, "=", line[x]);');
|
||||
child.stdin.write(' blen=split(line[x], tokens, " ");');
|
||||
child.stdin.write(' for(y=1;y<blen;++y)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' match(tokens[y],/=/);');
|
||||
child.stdin.write(' h=substr(tokens[y],1,RSTART-1);');
|
||||
child.stdin.write(' v=substr(tokens[y],RSTART+1);');
|
||||
child.stdin.write(' sub(/#/, "", h);');
|
||||
child.stdin.write(' printf("%s\\"%s\\": \\"%s\\"", comma3, h, v); comma3=",";');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("}");');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' if(line[x] ~ "^S:")');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' sub(/^S:[ \\t]*/, "", line[x]);');
|
||||
child.stdin.write(' match(line[x], /=/);');
|
||||
child.stdin.write(' h=substr(line[x],1,RSTART-1);');
|
||||
child.stdin.write(' v=substr(line[x],RSTART+1);');
|
||||
child.stdin.write(' printf("%s\\"%s\\": \\"%s\\"", comma2, h,v); comma2=",";');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("}");');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("]");');
|
||||
child.stdin.write("}'\nexit\n");
|
||||
child.waitExit();
|
||||
|
||||
try
|
||||
{
|
||||
values.linux.usb = JSON.parse(child.stdout.str);
|
||||
}
|
||||
catch(x)
|
||||
{ }
|
||||
child = null;
|
||||
}
|
||||
|
||||
var pcidevices = require('lib-finder').findBinary('lspci');
|
||||
if (pcidevices != null)
|
||||
{
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stderr.str = ''; child.stderr.on('data', dataHandler);
|
||||
child.stdin.write(pcidevices + " -m | tr '\\n' '`' | ");
|
||||
child.stdin.write(" awk '");
|
||||
child.stdin.write('{');
|
||||
child.stdin.write(' printf("[");');
|
||||
child.stdin.write(' comma="";');
|
||||
child.stdin.write(' alen=split($0, lines, "`");');
|
||||
child.stdin.write(' for(a=1;a<alen;++a)');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' match(lines[a], / /);');
|
||||
child.stdin.write(' blen=split(lines[a], meta, "\\"");');
|
||||
child.stdin.write(' bus=substr(lines[a], 1, RSTART);');
|
||||
child.stdin.write(' gsub(/ /, "", bus);');
|
||||
child.stdin.write(' printf("%s{\\"bus\\": \\"%s\\"", comma, bus); comma=",";');
|
||||
child.stdin.write(' printf(", \\"device\\": \\"%s\\"", meta[2]);');
|
||||
child.stdin.write(' printf(", \\"manufacturer\\": \\"%s\\"", meta[4]);');
|
||||
child.stdin.write(' printf(", \\"description\\": \\"%s\\"", meta[6]);');
|
||||
child.stdin.write(' if(meta[8] != "")');
|
||||
child.stdin.write(' {');
|
||||
child.stdin.write(' printf(", \\"subsystem\\": {");');
|
||||
child.stdin.write(' printf("\\"manufacturer\\": \\"%s\\"", meta[8]);');
|
||||
child.stdin.write(' printf(", \\"description\\": \\"%s\\"", meta[10]);');
|
||||
child.stdin.write(' printf("}");');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("}");');
|
||||
child.stdin.write(' }');
|
||||
child.stdin.write(' printf("]");');
|
||||
child.stdin.write("}'\nexit\n");
|
||||
child.waitExit();
|
||||
|
||||
try
|
||||
{
|
||||
values.linux.pci = JSON.parse(child.stdout.str);
|
||||
}
|
||||
catch (x)
|
||||
{ }
|
||||
child = null;
|
||||
}
|
||||
|
||||
// Linux Last Boot Up Time
|
||||
try {
|
||||
child = require('child_process').execFile('/usr/bin/uptime', ['', '-s']); // must include blank value at begining for some reason?
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.on('data', function () { });
|
||||
child.waitExit();
|
||||
var regex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||
if (regex.test(child.stdout.str.trim())) {
|
||||
values.linux.LastBootUpTime = child.stdout.str.trim();
|
||||
} else {
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('date -d "@$(( $(date +%s) - $(awk \'{print int($1)}\' /proc/uptime) ))" "+%Y-%m-%d %H:%M:%S"\nexit\n');
|
||||
child.waitExit();
|
||||
if (regex.test(child.stdout.str.trim())) {
|
||||
values.linux.LastBootUpTime = child.stdout.str.trim();
|
||||
}
|
||||
}
|
||||
child = null;
|
||||
} catch (ex) { }
|
||||
|
||||
// Linux TPM
|
||||
try {
|
||||
if (require('fs').statSync('/sys/class/tpm/tpm0').isDirectory()){
|
||||
values.tpm = {
|
||||
SpecVersion: require('fs').readFileSync('/sys/class/tpm/tpm0/tpm_version_major').toString().trim()
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
return (values);
|
||||
}
|
||||
|
||||
function windows_wmic_results(str)
|
||||
{
|
||||
var lines = str.trim().split('\r\n');
|
||||
var keys = lines[0].split(',');
|
||||
var i, key, keyval;
|
||||
var tokens;
|
||||
var result = [];
|
||||
|
||||
console.log('Lines: ' + lines.length, 'Keys: ' + keys.length);
|
||||
|
||||
for (i = 1; i < lines.length; ++i)
|
||||
{
|
||||
var obj = {};
|
||||
console.log('i: ' + i);
|
||||
tokens = lines[i].split(',');
|
||||
for (key = 0; key < keys.length; ++key)
|
||||
{
|
||||
var tmp = Buffer.from(tokens[key], 'binary').toString();
|
||||
console.log(tokens[key], tmp);
|
||||
tokens[key] = tmp == null ? '' : tmp;
|
||||
if (tokens[key].trim())
|
||||
{
|
||||
obj[keys[key].trim()] = tokens[key].trim();
|
||||
}
|
||||
}
|
||||
delete obj.Node;
|
||||
result.push(obj);
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
function windows_identifiers()
|
||||
{
|
||||
var ret = { windows: {} };
|
||||
var items, item, i;
|
||||
|
||||
ret['identifiers'] = {};
|
||||
|
||||
var values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_Bios", ['ReleaseDate', 'Manufacturer', 'SMBIOSBIOSVersion', 'SerialNumber']);
|
||||
if(values[0]){
|
||||
ret['identifiers']['bios_date'] = values[0]['ReleaseDate'];
|
||||
ret['identifiers']['bios_vendor'] = values[0]['Manufacturer'];
|
||||
ret['identifiers']['bios_version'] = values[0]['SMBIOSBIOSVersion'];
|
||||
ret['identifiers']['bios_serial'] = values[0]['SerialNumber'];
|
||||
}
|
||||
ret['identifiers']['bios_mode'] = 'Legacy';
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_BaseBoard", ['Product', 'SerialNumber', 'Manufacturer', 'Version']);
|
||||
if(values[0]){
|
||||
ret['identifiers']['board_name'] = values[0]['Product'];
|
||||
ret['identifiers']['board_serial'] = values[0]['SerialNumber'];
|
||||
ret['identifiers']['board_vendor'] = values[0]['Manufacturer'];
|
||||
ret['identifiers']['board_version'] = values[0]['Version'];
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_ComputerSystemProduct", ['UUID', 'Name']);
|
||||
if(values[0]){
|
||||
ret['identifiers']['product_uuid'] = values[0]['UUID'];
|
||||
ret['identifiers']['product_name'] = values[0]['Name'];
|
||||
trimIdentifiers(ret.identifiers);
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_PhysicalMemory");
|
||||
if(values[0]){
|
||||
trimResults(values);
|
||||
ret.windows.memory = values;
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_OperatingSystem");
|
||||
if(values[0]){
|
||||
trimResults(values);
|
||||
ret.windows.osinfo = values[0];
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_DiskPartition");
|
||||
if(values[0]){
|
||||
trimResults(values);
|
||||
ret.windows.partitions = values;
|
||||
for (var i in values) {
|
||||
if (values[i].Description=='GPT: System') {
|
||||
ret['identifiers']['bios_mode'] = 'UEFI';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_Processor", ['Caption', 'DeviceID', 'Manufacturer', 'MaxClockSpeed', 'Name', 'SocketDesignation']);
|
||||
if(values[0]){
|
||||
ret.windows.cpu = values;
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_VideoController", ['Name', 'CurrentHorizontalResolution', 'CurrentVerticalResolution']);
|
||||
if(values[0]){
|
||||
ret.windows.gpu = values;
|
||||
}
|
||||
|
||||
values = require('win-wmi').query('ROOT\\CIMV2', "SELECT * FROM Win32_DiskDrive", ['Caption', 'DeviceID', 'Model', 'Partitions', 'Size', 'Status']);
|
||||
if(values[0]){
|
||||
ret.windows.drives = values;
|
||||
}
|
||||
|
||||
// Insert GPU names
|
||||
ret.identifiers.gpu_name = [];
|
||||
for (var gpuinfo in ret.windows.gpu)
|
||||
{
|
||||
if (ret.windows.gpu[gpuinfo].Name) { ret.identifiers.gpu_name.push(ret.windows.gpu[gpuinfo].Name); }
|
||||
}
|
||||
|
||||
// Insert Storage Devices
|
||||
ret.identifiers.storage_devices = [];
|
||||
for (var dv in ret.windows.drives)
|
||||
{
|
||||
ret.identifiers.storage_devices.push({ Caption: ret.windows.drives[dv].Caption, Model: ret.windows.drives[dv].Model, Size: ret.windows.drives[dv].Size });
|
||||
}
|
||||
|
||||
try { ret.identifiers.cpu_name = ret.windows.cpu[0].Name; } catch (x) { }
|
||||
|
||||
// Windows TPM
|
||||
IntToStr = function (v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); };
|
||||
try {
|
||||
values = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftTpm', "SELECT * FROM Win32_Tpm", ['IsActivated_InitialValue','IsEnabled_InitialValue','IsOwned_InitialValue','ManufacturerId','ManufacturerVersion','SpecVersion']);
|
||||
if(values[0]) {
|
||||
ret.tpm = {
|
||||
SpecVersion: values[0].SpecVersion.split(",")[0],
|
||||
ManufacturerId: IntToStr(values[0].ManufacturerId).replace(/[^\x00-\x7F]/g, ""),
|
||||
ManufacturerVersion: values[0].ManufacturerVersion,
|
||||
IsActivated: values[0].IsActivated_InitialValue,
|
||||
IsEnabled: values[0].IsEnabled_InitialValue,
|
||||
IsOwned: values[0].IsOwned_InitialValue,
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
return (ret);
|
||||
}
|
||||
function macos_identifiers()
|
||||
{
|
||||
var ret = { identifiers: {}, darwin: {} };
|
||||
var child;
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('ioreg -d2 -c IOPlatformExpertDevice | grep board-id | awk -F= \'{ split($2, res, "\\""); print res[2]; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.board_name = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformSerialNumber | awk -F= \'{ split($2, res, "\\""); print res[2]; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.board_serial = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('ioreg -d2 -c IOPlatformExpertDevice | grep manufacturer | awk -F= \'{ split($2, res, "\\""); print res[2]; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.board_vendor = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('ioreg -d2 -c IOPlatformExpertDevice | grep version | awk -F= \'{ split($2, res, "\\""); print res[2]; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.board_version = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk -F= \'{ split($2, res, "\\""); print res[2]; }\'\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.product_uuid = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('sysctl -n machdep.cpu.brand_string\nexit\n');
|
||||
child.waitExit();
|
||||
ret.identifiers.cpu_name = child.stdout.str.trim();
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('system_profiler SPMemoryDataType\nexit\n');
|
||||
child.waitExit();
|
||||
var lines = child.stdout.str.trim().split('\n');
|
||||
if(lines.length > 0) {
|
||||
const memorySlots = [];
|
||||
if(lines[2].trim().includes('Memory Slots:')) { // OLD MACS WITH SLOTS
|
||||
var memorySlots1 = child.stdout.str.split(/\n{2,}/).slice(3);
|
||||
memorySlots1.forEach(function(slot,index) {
|
||||
var lines = slot.split('\n');
|
||||
if(lines.length == 1){ // start here
|
||||
if(lines[0].trim()!=''){
|
||||
var slotObj = { DeviceLocator: lines[0].trim().replace(/:$/, '') }; // Initialize name as an empty string
|
||||
var nextline = memorySlots1[index+1].split('\n');
|
||||
nextline.forEach(function(line) {
|
||||
if (line.trim() !== '') {
|
||||
var parts = line.split(':');
|
||||
var key = parts[0].trim();
|
||||
var value = parts[1].trim();
|
||||
value = (key == 'Part Number' || key == 'Manufacturer') ? hexToAscii(parts[1].trim()) : parts[1].trim();
|
||||
slotObj[key.replace(' ','')] = value; // Store attribute in the slot object
|
||||
}
|
||||
});
|
||||
memorySlots.push(slotObj);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else { // NEW MACS WITHOUT SLOTS
|
||||
memorySlots.push({ DeviceLocator: "Onboard Memory", Size: lines[2].split(":")[1].trim(), PartNumber: lines[3].split(":")[1].trim(), Manufacturer: lines[4].split(":")[1].trim() })
|
||||
}
|
||||
ret.darwin.memory = memorySlots;
|
||||
}
|
||||
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('diskutil info -all\nexit\n');
|
||||
child.waitExit();
|
||||
var sections = child.stdout.str.split('**********\n');
|
||||
if(sections.length > 0){
|
||||
var devices = [];
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
var lines = sections[i].split('\n');
|
||||
var deviceInfo = {};
|
||||
var wholeYes = false;
|
||||
var physicalYes = false;
|
||||
var oldmac = false;
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
var keyValue = lines[j].split(':');
|
||||
var key = keyValue[0].trim();
|
||||
var value = keyValue[1] ? keyValue[1].trim() : '';
|
||||
if (key === 'Virtual') oldmac = true;
|
||||
if (key === 'Whole' && value === 'Yes') wholeYes = true;
|
||||
if (key === 'Virtual' && value === 'No') physicalYes = true;
|
||||
if(value && key === 'Device / Media Name'){
|
||||
deviceInfo['Caption'] = value;
|
||||
}
|
||||
if(value && key === 'Disk Size'){
|
||||
deviceInfo['Size'] = value.split(' ')[0] + ' ' + value.split(' ')[1];
|
||||
}
|
||||
}
|
||||
if (wholeYes) {
|
||||
if (oldmac) {
|
||||
if (physicalYes) devices.push(deviceInfo);
|
||||
} else {
|
||||
devices.push(deviceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.identifiers.storage_devices = devices;
|
||||
}
|
||||
|
||||
// Fetch storage volumes using df
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -aHY | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\",\\"type\\":\\"%s\\"},", $3, $4, $5, $10, $2}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
ret.darwin.volumes = JSON.parse(child.stdout.str.trim());
|
||||
for (var index = 0; index < ret.darwin.volumes.length; index++) {
|
||||
if (ret.darwin.volumes[index].type == 'auto_home'){
|
||||
ret.darwin.volumes.splice(index,1);
|
||||
}
|
||||
}
|
||||
if (ret.darwin.volumes.length == 0) { // not sonima OS so dont show type for now
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', dataHandler);
|
||||
child.stdin.write('df -aH | awk \'NR>1 {printf "{\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"available\\":\\"%s\\",\\"mount_point\\":\\"%s\\"},", $2, $3, $4, $9}\' | sed \'$ s/,$//\' | awk \'BEGIN {printf "["} {printf "%s", $0} END {printf "]"}\'\nexit\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
ret.darwin.volumes = JSON.parse(child.stdout.str.trim());
|
||||
for (var index = 0; index < ret.darwin.volumes.length; index++) {
|
||||
if (ret.darwin.volumes[index].size == 'auto_home'){
|
||||
ret.darwin.volumes.splice(index,1);
|
||||
}
|
||||
}
|
||||
} catch (xx) { }
|
||||
}
|
||||
} catch (xx) { }
|
||||
child = null;
|
||||
|
||||
// MacOS Last Boot Up Time
|
||||
try {
|
||||
child = require('child_process').execFile('/usr/sbin/sysctl', ['', 'kern.boottime']); // must include blank value at begining for some reason?
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.on('data', function () { });
|
||||
child.waitExit();
|
||||
const timestampMatch = /\{ sec = (\d+), usec = \d+ \}/.exec(child.stdout.str.trim());
|
||||
if (!ret.darwin) {
|
||||
ret.darwin = { LastBootUpTime: parseInt(timestampMatch[1]) };
|
||||
} else {
|
||||
ret.darwin.LastBootUpTime = parseInt(timestampMatch[1]);
|
||||
}
|
||||
child = null;
|
||||
} catch (ex) { }
|
||||
|
||||
trimIdentifiers(ret.identifiers);
|
||||
|
||||
child = null;
|
||||
return (ret);
|
||||
}
|
||||
|
||||
function hexToAscii(hexString) {
|
||||
if(!hexString.startsWith('0x')) return hexString.trim();
|
||||
hexString = hexString.startsWith('0x') ? hexString.slice(2) : hexString;
|
||||
var str = '';
|
||||
for (var i = 0; i < hexString.length; i += 2) {
|
||||
var hexPair = hexString.substr(i, 2);
|
||||
str += String.fromCharCode(parseInt(hexPair, 16));
|
||||
}
|
||||
str = str.replace(/[\u007F-\uFFFF]/g, ''); // Remove characters from 0x0080 to 0xFFFF
|
||||
return str.trim();
|
||||
}
|
||||
|
||||
function win_chassisType()
|
||||
{
|
||||
// needs to be replaced with win-wmi but due to bug in win-wmi it doesnt handle arrays correctly
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
|
||||
if (child == null) { return ([]); }
|
||||
child.descriptorMetadata = 'process-manager';
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('Get-WmiObject Win32_SystemEnclosure | Select-Object -ExpandProperty ChassisTypes\r\n');
|
||||
child.stdin.write('exit\r\n');
|
||||
child.waitExit();
|
||||
try {
|
||||
return (parseInt(child.stdout.str));
|
||||
} catch (e) {
|
||||
return (2); // unknown
|
||||
}
|
||||
}
|
||||
|
||||
function win_systemType()
|
||||
{
|
||||
try {
|
||||
var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT PCSystemType FROM Win32_ComputerSystem', ['PCSystemType']);
|
||||
if (tokens[0]) {
|
||||
return (parseInt(tokens[0]['PCSystemType']));
|
||||
} else {
|
||||
return (parseInt(1)); // default is desktop
|
||||
}
|
||||
} catch (ex) {
|
||||
return (parseInt(1)); // default is desktop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function win_formFactor(chassistype)
|
||||
{
|
||||
var ret = 'DESKTOP';
|
||||
switch (chassistype)
|
||||
{
|
||||
case 11: // Handheld
|
||||
case 30: // Tablet
|
||||
case 31: // Convertible
|
||||
case 32: // Detachable
|
||||
ret = 'TABLET';
|
||||
break;
|
||||
case 9: // Laptop
|
||||
case 10: // Notebook
|
||||
case 14: // Sub Notebook
|
||||
ret = 'LAPTOP';
|
||||
break;
|
||||
default:
|
||||
ret = win_systemType() == 2 ? 'MOBILE' : 'DESKTOP';
|
||||
break;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
switch(process.platform)
|
||||
{
|
||||
case 'linux':
|
||||
module.exports = { _ObjectID: 'identifiers', get: linux_identifiers };
|
||||
break;
|
||||
case 'win32':
|
||||
module.exports = { _ObjectID: 'identifiers', get: windows_identifiers, chassisType: win_chassisType, formFactor: win_formFactor, systemType: win_systemType };
|
||||
break;
|
||||
case 'darwin':
|
||||
module.exports = { _ObjectID: 'identifiers', get: macos_identifiers };
|
||||
break;
|
||||
default:
|
||||
module.exports = { get: function () { throw ('Unsupported Platform'); } };
|
||||
break;
|
||||
}
|
||||
module.exports.isDocker = function isDocker()
|
||||
{
|
||||
if (process.platform != 'linux') { return (false); }
|
||||
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write("cat /proc/self/cgroup | tr '\n' '`' | awk -F'`' '{ split($1, res, " + '"/"); if(res[2]=="docker"){print "1";} }\'\nexit\n');
|
||||
child.waitExit();
|
||||
return (child.stdout.str != '');
|
||||
};
|
||||
module.exports.isBatteryPowered = function isBatteryOperated()
|
||||
{
|
||||
var ret = false;
|
||||
switch(process.platform)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case 'linux':
|
||||
var devices = require('fs').readdirSync('/sys/class/power_supply');
|
||||
for (var i in devices)
|
||||
{
|
||||
if (require('fs').readFileSync('/sys/class/power_supply/' + devices[i] + '/type').toString().trim() == 'Battery')
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'win32':
|
||||
var GM = require('_GenericMarshal');
|
||||
var stats = GM.CreateVariable(12);
|
||||
var kernel32 = GM.CreateNativeProxy('Kernel32.dll');
|
||||
kernel32.CreateMethod('GetSystemPowerStatus');
|
||||
if (kernel32.GetSystemPowerStatus(stats).Val != 0)
|
||||
{
|
||||
if(stats.toBuffer()[1] != 128 && stats.toBuffer()[1] != 255)
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No Battery detected, so lets check if there is supposed to be one
|
||||
var formFactor = win_formFactor(win_chassisType());
|
||||
return (formFactor == 'LAPTOP' || formFactor == 'TABLET' || formFactor == 'MOBILE');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'darwin':
|
||||
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function(c){ this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function(c){ this.str += c.toString(); });
|
||||
child.stdin.write("pmset -g batt | tr '\\n' '`' | awk -F'`' '{ if(NF>2) { print \"true\"; }}'\nexit\n");
|
||||
child.waitExit();
|
||||
if(child.stdout.str.trim() != '') { ret = true; }
|
||||
break;
|
||||
}
|
||||
return (ret);
|
||||
};
|
||||
module.exports.isVM = function isVM()
|
||||
{
|
||||
var ret = false;
|
||||
var id = this.get();
|
||||
if (id.linux && id.linux.sys_vendor)
|
||||
{
|
||||
switch (id.linux.sys_vendor)
|
||||
{
|
||||
case 'VMware, Inc.':
|
||||
case 'QEMU':
|
||||
case 'Xen':
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id.identifiers.bios_vendor)
|
||||
{
|
||||
switch(id.identifiers.bios_vendor)
|
||||
{
|
||||
case 'VMware, Inc.':
|
||||
case 'Xen':
|
||||
case 'SeaBIOS':
|
||||
case 'EFI Development Kit II / OVMF':
|
||||
case 'Proxmox distribution of EDK II':
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id.identifiers.board_vendor && id.identifiers.board_vendor == 'VMware, Inc.') { ret = true; }
|
||||
if (id.identifiers.board_name)
|
||||
{
|
||||
switch (id.identifiers.board_name)
|
||||
{
|
||||
case 'VirtualBox':
|
||||
case 'Virtual Machine':
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform == 'win32' && !ret)
|
||||
{
|
||||
for(var i in id.identifiers.gpu_name)
|
||||
{
|
||||
if(id.identifiers.gpu_name[i].startsWith('VMware '))
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!ret) { ret = this.isDocker(); }
|
||||
return (ret);
|
||||
};
|
||||
|
||||
// bios_date = BIOS->ReleaseDate
|
||||
// bios_vendor = BIOS->Manufacturer
|
||||
// bios_version = BIOS->SMBIOSBIOSVersion
|
||||
// board_name = BASEBOARD->Product = ioreg/board-id
|
||||
// board_serial = BASEBOARD->SerialNumber = ioreg/serial-number | ioreg/IOPlatformSerialNumber
|
||||
// board_vendor = BASEBOARD->Manufacturer = ioreg/manufacturer
|
||||
// board_version = BASEBOARD->Version
|
||||
|
|
@ -135,12 +135,12 @@
|
|||
"allow": "Permitir",
|
||||
"deny": "Negar",
|
||||
"autoAllowForFive": "Aceita automaticamente todas as conexões pelos próximos 5 minutos",
|
||||
"terminalConsent": "{0} solicitando acesso ao terminal remoto. Garantir acesso?",
|
||||
"desktopConsent": "{0} solicitando acesso à área de trabalho remota. Garantir acesso?",
|
||||
"fileConsent": "{0} solicitando acesso remoto ao arquivo. Garantir acesso?",
|
||||
"terminalConsent": "{0} está a pedir acesso ao terminal remoto. Conceder acesso?",
|
||||
"desktopConsent": "{0} está a pedir acesso à área de trabalho remota. Conceder acesso?",
|
||||
"fileConsent": "{0} está a pedir acesso remoto aos ficheiros. Conceder acesso?",
|
||||
"terminalNotify": "{0} iniciou uma sessão de terminal remoto.",
|
||||
"desktopNotify": "{0} iniciou uma sessão de área de trabalho remota.",
|
||||
"fileNotify": "{0} iniciou uma sessão de arquivo remoto.",
|
||||
"fileNotify": "{0} iniciou uma sessão de ficheiro remoto.",
|
||||
"privacyBar": "Compartilhando área de trabalho com: {0}"
|
||||
},
|
||||
"ru": {
|
||||
|
|
@ -250,5 +250,41 @@
|
|||
"desktopNotify": "{0} je započeo sesiju udaljene radne površine.",
|
||||
"fileNotify": "{0} je započeo sesiju udaljenog fajla.",
|
||||
"privacyBar": "Dijeljenje radne površine sa: {0}"
|
||||
},
|
||||
"hu": {
|
||||
"allow": "Engedélyezés",
|
||||
"deny": "Elutasítás",
|
||||
"autoAllowForFive": "Csatlakozások automatikus elfogadása a következő 5 percben",
|
||||
"terminalConsent": "{0} távoli parancssor,terminál hozzáférést kér. Engedélyezi a hozzáférést?",
|
||||
"desktopConsent": "{0} távoli asztali hozzáférést kér. Engedélyezi a hozzáférést?",
|
||||
"fileConsent": "{0} távoli fájlhozzáférést kér. Engedélyezi a hozzáférést?",
|
||||
"terminalNotify": "{0} távoli parancssor munkamenetet indított.",
|
||||
"desktopNotify": "{0} távoli asztali munkamenetet indított.",
|
||||
"fileNotify": "{0} távoli fájlmunkamenetet indított.",
|
||||
"privacyBar": "Asztal megosztás aktív: {0} felhasználóval"
|
||||
},
|
||||
"ca": {
|
||||
"allow": "Permetre",
|
||||
"deny": "Negar",
|
||||
"autoAllowForFive": "Accepta automàticament totes les connexions durant els propers 5 minuts",
|
||||
"terminalConsent": "{0} sol·licitant accés al terminal remot. Accés garantit?",
|
||||
"desktopConsent": "{0} sol·licitant accés a l'escriptori remot. Accés garantit?",
|
||||
"fileConsent": "{0} sol·licitant accés remot al fitxer. Accés garantit?",
|
||||
"terminalNotify": "{0} va iniciar una sessió de terminal remota.",
|
||||
"desktopNotify": "{0} va iniciar una sessió d'escriptori remot.",
|
||||
"fileNotify": "{0} va iniciar una sessió de fitxer remota.",
|
||||
"privacyBar": "Compartint escriptori amb: {0}"
|
||||
},
|
||||
"uk": {
|
||||
"allow": "Дозволити",
|
||||
"deny": "Відмовити",
|
||||
"autoAllowForFive": "Автоматично приймати всі підключення впродовж наступних 5 хвилин",
|
||||
"terminalConsent": "{0} запитує доступ до віддаленого терміналу. Надати доступ?",
|
||||
"desktopConsent": "{0} запитує віддалений доступ до стільниці. Надати доступ?",
|
||||
"fileConsent": "{0} запитує віддалений доступ до файлу. Надати доступ?",
|
||||
"terminalNotify": "{0} почав сеанс віддаленого терміналу.",
|
||||
"desktopNotify": "{0} розпочав сеанс віддаленої стільниці.",
|
||||
"fileNotify": "{0} розпочав віддалений файловий сеанс.",
|
||||
"privacyBar": "Поширити доступ до стільниці з: {0}"
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ function linux_memUtilization()
|
|||
case 'MemTotal:':
|
||||
ret.total = parseInt(tokens[tokens.length - 2]);
|
||||
break;
|
||||
case 'MemFree:':
|
||||
case 'MemAvailable:':
|
||||
ret.free = parseInt(tokens[tokens.length - 2]);
|
||||
break;
|
||||
}
|
||||
|
|
@ -209,9 +209,13 @@ function macos_memUtilization()
|
|||
{
|
||||
var usage = lines[0].split(':')[1];
|
||||
var bdown = usage.split(',');
|
||||
|
||||
mem.MemTotal = parseInt(bdown[0].trim().split(' ')[0]);
|
||||
mem.MemFree = parseInt(bdown[1].trim().split(' ')[0]);
|
||||
if (bdown.length > 2){ // new style - PhysMem: 5750M used (1130M wired, 634M compressor), 1918M unused.
|
||||
mem.MemFree = parseInt(bdown[2].trim().split(' ')[0]);
|
||||
} else { // old style - PhysMem: 6683M used (1606M wired), 9699M unused.
|
||||
mem.MemFree = parseInt(bdown[1].trim().split(' ')[0]);
|
||||
}
|
||||
mem.MemUsed = parseInt(bdown[0].trim().split(' ')[0]);
|
||||
mem.MemTotal = (mem.MemFree + mem.MemUsed);
|
||||
mem.percentFree = ((mem.MemFree / mem.MemTotal) * 100);//.toFixed(2);
|
||||
mem.percentConsumed = (((mem.MemTotal - mem.MemFree) / mem.MemTotal) * 100);//.toFixed(2);
|
||||
return (mem);
|
||||
|
|
@ -225,31 +229,48 @@ function macos_memUtilization()
|
|||
function windows_thermals()
|
||||
{
|
||||
var ret = [];
|
||||
child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', '/namespace:\\\\root\\wmi', 'PATH', 'MSAcpi_ThermalZoneTemperature', 'get', 'CurrentTemperature']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
|
||||
if(child.stdout.str.trim!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
for (var i = 1; i < lines.length; ++i)
|
||||
{
|
||||
if (lines[i].trim() != '') { ret.push(((parseFloat(lines[i]) / 10) - 273.15).toFixed(2)); }
|
||||
try {
|
||||
ret = require('win-wmi').query('ROOT\\WMI', 'SELECT CurrentTemperature,InstanceName FROM MSAcpi_ThermalZoneTemperature',['CurrentTemperature','InstanceName']);
|
||||
if (ret[0]) {
|
||||
for (var i = 0; i < ret.length; ++i) {
|
||||
ret[i]['CurrentTemperature'] = ((parseFloat(ret[i]['CurrentTemperature']) / 10) - 273.15).toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
function linux_thermals()
|
||||
{
|
||||
var ret = [];
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write("cat /sys/class/thermal/thermal_zone*/temp | awk '{ print $0 / 1000 }'\nexit\n");
|
||||
child.stdin.write("for folder in /sys/class/thermal/thermal_zone*/; do [ -e \"$folder/temp\" ] && echo \"$(cat \"$folder/temp\"),$(cat \"$folder/type\")\"; done\nexit\n");
|
||||
child.waitExit();
|
||||
var ret = child.stdout.str.trim().split('\n');
|
||||
if (ret.length == 1 && ret[0] == '') { ret = []; }
|
||||
if(child.stdout.str.trim()!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\n');
|
||||
for (var i = 0; i < lines.length; ++i)
|
||||
{
|
||||
var line = lines[i].trim().split(',');
|
||||
ret.push({CurrentTemperature: (parseFloat(line[0])/1000), InstanceName: line[1]});
|
||||
}
|
||||
}
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write("for mon in /sys/class/hwmon/hwmon*; do for label in \"$mon\"/temp*_label; do if [ -f $label ]; then echo $(cat \"$label\")___$(cat \"${label%_*}_input\"); fi; done; done;\nexit\n");
|
||||
child.waitExit();
|
||||
if(child.stdout.str.trim()!='')
|
||||
{
|
||||
var lines = child.stdout.str.trim().split('\n');
|
||||
for (var i = 0; i < lines.length; ++i)
|
||||
{
|
||||
var line = lines[i].trim().split('___');
|
||||
ret.push({ CurrentTemperature: (parseFloat(line[1])/1000), InstanceName: line[0] });
|
||||
}
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +282,6 @@ function macos_thermals()
|
|||
child.stderr.on('data', function () { });
|
||||
child.stdin.write('powermetrics --help | grep SMC\nexit\n');
|
||||
child.waitExit();
|
||||
|
||||
if (child.stdout.str.trim() != '')
|
||||
{
|
||||
child = require('child_process').execFile('/bin/sh', ['sh']);
|
||||
|
|
@ -273,14 +293,19 @@ function macos_thermals()
|
|||
{
|
||||
if (tokens[i].split(' die temperature: ').length > 1)
|
||||
{
|
||||
ret.push(tokens[i].split(' ')[3]);
|
||||
ret.push({CurrentTemperature: tokens[i].split(' ')[3], InstanceName: tokens[i].split(' ')[0]});
|
||||
this.parent.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stdin.write('powermetrics -s smc\n');
|
||||
child.waitExit(5000);
|
||||
child.stderr.on('data', function (c) {
|
||||
if (c.toString().split('unable to get smc values').length > 1) { // error getting sensors so just kill
|
||||
this.parent.kill();
|
||||
return;
|
||||
}
|
||||
});
|
||||
child.stdin.write('powermetrics -s smc -i 500 -n 1\n');
|
||||
child.waitExit(2000);
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,28 +18,21 @@ var promise = require('promise');
|
|||
|
||||
function qfe()
|
||||
{
|
||||
var child = require('child_process').execFile(process.env['windir'] + '\\System32\\wbem\\wmic.exe', ['wmic', 'qfe', 'list', 'full', '/FORMAT:CSV']);
|
||||
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
child.waitExit();
|
||||
|
||||
var lines = child.stdout.str.trim().split('\r\n');
|
||||
var keys = lines[0].split(',');
|
||||
var i, key;
|
||||
var tokens;
|
||||
var result = [];
|
||||
|
||||
for (i = 1; i < lines.length; ++i)
|
||||
{
|
||||
var obj = {};
|
||||
tokens = lines[i].split(',');
|
||||
for (key = 0; key < keys.length; ++key)
|
||||
{
|
||||
if (tokens[key]) { obj[keys[key]] = tokens[key]; }
|
||||
try {
|
||||
var tokens = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_QuickFixEngineering');
|
||||
if (tokens[0]){
|
||||
for (var index = 0; index < tokens.length; index++) {
|
||||
for (var key in tokens[index]) {
|
||||
if (key.startsWith('__')) delete tokens[index][key];
|
||||
}
|
||||
}
|
||||
return (tokens);
|
||||
} else {
|
||||
return ([]);
|
||||
}
|
||||
result.push(obj);
|
||||
} catch (ex) {
|
||||
return ([]);
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
function av()
|
||||
{
|
||||
|
|
@ -53,9 +46,23 @@ function av()
|
|||
child.stdin.write('[reflection.Assembly]::LoadWithPartialName("system.core")\r\n');
|
||||
child.stdin.write('Get-WmiObject -Namespace "root/SecurityCenter2" -Class AntiVirusProduct | ');
|
||||
child.stdin.write('ForEach-Object -Process { ');
|
||||
child.stdin.write('$matches = [regex]::Matches($_.pathToSignedProductExe, "%(.*?)%"); ');
|
||||
child.stdin.write('$modifiedPath = $_.pathToSignedProductExe; ');
|
||||
child.stdin.write('foreach ($match in $matches) { ');
|
||||
child.stdin.write('$modifiedPath = $modifiedPath -replace [regex]::Escape($match.Value), [System.Environment]::GetEnvironmentVariable($match.Groups[1].Value, "Process") ');
|
||||
child.stdin.write('} ');
|
||||
child.stdin.write('$flag = $true; ');
|
||||
child.stdin.write('if ($modifiedPath -ne "windowsdefender://"){ ');
|
||||
child.stdin.write('if (-not (Test-Path -Path $modifiedPath -PathType Leaf)) { ');
|
||||
child.stdin.write('$flag = $false; ');
|
||||
child.stdin.write('} ');
|
||||
child.stdin.write('} ');
|
||||
child.stdin.write('if ($flag -eq $true) { ')
|
||||
child.stdin.write('$Bytes = [System.Text.Encoding]::UTF8.GetBytes($_.displayName); ');
|
||||
child.stdin.write('$EncodedText =[Convert]::ToBase64String($Bytes); ');
|
||||
child.stdin.write('Write-Host ("{0},{1}" -f $_.productState,$EncodedText); }\r\n');
|
||||
child.stdin.write('Write-Output ("{0},{1}" -f $_.productState,$EncodedText); ');
|
||||
child.stdin.write('} ');
|
||||
child.stdin.write('}\r\n ');
|
||||
child.stdin.write('exit\r\n');
|
||||
child.waitExit();
|
||||
|
||||
|
|
@ -214,6 +221,14 @@ function installedApps()
|
|||
catch(e)\
|
||||
{\
|
||||
}\
|
||||
try\
|
||||
{\
|
||||
val.installdate = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallDate');\
|
||||
if (val.installdate == '') { delete val.installdate; }\
|
||||
}\
|
||||
catch(e)\
|
||||
{\
|
||||
}\
|
||||
result.push(val);\
|
||||
}\
|
||||
console.log(JSON.stringify(result,'', 1));process.exit();";
|
||||
|
|
@ -225,12 +240,33 @@ function installedApps()
|
|||
return (ret);
|
||||
}
|
||||
|
||||
function defender(){
|
||||
var promise = require('promise');
|
||||
var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
|
||||
ret.child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], {});
|
||||
ret.child.promise = ret;
|
||||
ret.child.stdout.str = ''; ret.child.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
ret.child.stderr.str = ''; ret.child.stderr.on('data', function (c) { this.str += c.toString(); });
|
||||
ret.child.stdin.write('Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled,IsTamperProtected | ConvertTo-JSON\r\n');
|
||||
ret.child.stdin.write('exit\r\n');
|
||||
ret.child.on('exit', function (c) {
|
||||
if (this.stdout.str == '') { this.promise._resolve({}); return; }
|
||||
try {
|
||||
var abc = JSON.parse(this.stdout.str.trim());
|
||||
this.promise._resolve({ RealTimeProtection: abc.RealTimeProtectionEnabled, TamperProtected: abc.IsTamperProtected });
|
||||
} catch (ex) {
|
||||
this.promise._resolve({}); return;
|
||||
}
|
||||
});
|
||||
return (ret);
|
||||
}
|
||||
|
||||
if (process.platform == 'win32')
|
||||
{
|
||||
module.exports = { qfe: qfe, av: av, defrag: defrag, pendingReboot: pendingReboot, installedApps: installedApps };
|
||||
module.exports = { qfe: qfe, av: av, defrag: defrag, pendingReboot: pendingReboot, installedApps: installedApps, defender: defender };
|
||||
}
|
||||
else
|
||||
{
|
||||
var not_supported = function () { throw (process.platform + ' not supported'); };
|
||||
module.exports = { qfe: not_supported, av: not_supported, defrag: not_supported, pendingReboot: not_supported, installedApps: not_supported };
|
||||
module.exports = { qfe: not_supported, av: not_supported, defrag: not_supported, pendingReboot: not_supported, installedApps: not_supported, defender: not_supported };
|
||||
}
|
||||
|
|
@ -39,17 +39,90 @@ function getVolumes()
|
|||
{
|
||||
ret[v[i].DeviceID] = trimObject(v[i]);
|
||||
}
|
||||
|
||||
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
||||
for (i in v)
|
||||
{
|
||||
var tmp = trimObject(v[i]);
|
||||
for (var k in tmp)
|
||||
try {
|
||||
v = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume');
|
||||
for (i in v)
|
||||
{
|
||||
ret[tmp.DeviceID][k] = tmp[k];
|
||||
var tmp = trimObject(v[i]);
|
||||
for (var k in tmp)
|
||||
{
|
||||
ret[tmp.DeviceID][k] = tmp[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) { }
|
||||
return (ret);
|
||||
}
|
||||
|
||||
module.exports = { getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } } };
|
||||
function windows_volumes()
|
||||
{
|
||||
var promise = require('promise');
|
||||
var p1 = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
||||
var ret = {};
|
||||
var values = require('win-wmi').query('ROOT\\CIMV2', 'SELECT * FROM Win32_LogicalDisk', ['DeviceID', 'VolumeName', 'FileSystem', 'Size', 'FreeSpace', 'DriveType']);
|
||||
if(values[0]){
|
||||
for (var i = 0; i < values.length; ++i) {
|
||||
var drive = values[i]['DeviceID'].slice(0,-1);
|
||||
ret[drive] = {
|
||||
name: (values[i]['VolumeName'] ? values[i]['VolumeName'] : ""),
|
||||
type: (values[i]['FileSystem'] ? values[i]['FileSystem'] : "Unknown"),
|
||||
size: (values[i]['Size'] ? values[i]['Size'] : 0),
|
||||
sizeremaining: (values[i]['FreeSpace'] ? values[i]['FreeSpace'] : 0),
|
||||
removable: (values[i]['DriveType'] == 2),
|
||||
cdrom: (values[i]['DriveType'] == 5)
|
||||
};
|
||||
}
|
||||
}
|
||||
try {
|
||||
values = require('win-wmi').query('ROOT\\CIMV2\\Security\\MicrosoftVolumeEncryption', 'SELECT * FROM Win32_EncryptableVolume', ['DriveLetter','ConversionStatus','ProtectionStatus']);
|
||||
if(values[0]){
|
||||
for (var i = 0; i < values.length; ++i) {
|
||||
var drive = values[i]['DriveLetter'].slice(0,-1);
|
||||
var statuses = {
|
||||
0: 'FullyDecrypted',
|
||||
1: 'FullyEncrypted',
|
||||
2: 'EncryptionInProgress',
|
||||
3: 'DecryptionInProgress',
|
||||
4: 'EncryptionPaused',
|
||||
5: 'DecryptionPaused'
|
||||
};
|
||||
ret[drive].volumeStatus = statuses.hasOwnProperty(values[i].ConversionStatus) ? statuses[values[i].ConversionStatus] : 'FullyDecrypted';
|
||||
ret[drive].protectionStatus = (values[i].ProtectionStatus == 0 ? 'Off' : (values[i].ProtectionStatus == 1 ? 'On' : 'Unknown'));
|
||||
try {
|
||||
var foundIDMarkedLine = false, foundMarkedLine = false, identifier = '', password = '';
|
||||
var keychild = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'manage-bde -protectors -get ' + drive + ': -Type recoverypassword'], {});
|
||||
keychild.stdout.str = ''; keychild.stdout.on('data', function (c) { this.str += c.toString(); });
|
||||
keychild.waitExit();
|
||||
var lines = keychild.stdout.str.trim().split('\r\n');
|
||||
for (var x = 0; x < lines.length; x++) { // Loop each line
|
||||
var abc = lines[x].trim();
|
||||
var englishidpass = (abc !== '' && abc.includes('Numerical Password:')); // English ID
|
||||
var germanidpass = (abc !== '' && abc.includes('Numerisches Kennwort:')); // German ID
|
||||
var frenchidpass = (abc !== '' && abc.includes('Mot de passe num')); // French ID
|
||||
var englishpass = (abc !== '' && abc.includes('Password:') && !abc.includes('Numerical Password:')); // English Password
|
||||
var germanpass = (abc !== '' && abc.includes('Kennwort:') && !abc.includes('Numerisches Kennwort:')); // German Password
|
||||
var frenchpass = (abc !== '' && abc.includes('Mot de passe :') && !abc.includes('Mot de passe num')); // French Password
|
||||
if (englishidpass || germanidpass || frenchidpass|| englishpass || germanpass || frenchpass) {
|
||||
var nextline = lines[x + 1].trim();
|
||||
if (x + 1 < lines.length && (nextline !== '' && (nextline.startsWith('ID:') || nextline.startsWith('ID :')) )) {
|
||||
identifier = nextline.replace('ID:','').replace('ID :', '').trim();
|
||||
foundIDMarkedLine = true;
|
||||
}else if (x + 1 < lines.length && nextline !== '') {
|
||||
password = nextline;
|
||||
foundMarkedLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret[drive].identifier = (foundIDMarkedLine ? identifier : ''); // Set Bitlocker Identifier
|
||||
ret[drive].recoveryPassword = (foundMarkedLine ? password : ''); // Set Bitlocker Password
|
||||
} catch(ex) { } // just carry on as we cant get bitlocker key
|
||||
}
|
||||
}
|
||||
p1._res(ret);
|
||||
} catch (ex) { p1._res(ret); } // just return volumes as cant get encryption/bitlocker
|
||||
return (p1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVolumes: function () { try { return (getVolumes()); } catch (x) { return ({}); } },
|
||||
volumes_promise: windows_volumes
|
||||
};
|
||||
|
|
@ -485,8 +485,8 @@ function windows_execve(name, agentfilename, sessionid) {
|
|||
var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
|
||||
var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
|
||||
var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
|
||||
var arg2 = require('_GenericMarshal').CreateVariable('/C wmic service "' + name + '" call stopservice & "' + cwd + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
|
||||
' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
|
||||
var arg2 = require('_GenericMarshal').CreateVariable('/C net stop "' + name + '" & "' + cwd + agentfilename + '.update.exe" -b64exec ' + 'dHJ5CnsKICAgIHZhciBzZXJ2aWNlTG9jYXRpb24gPSBwcm9jZXNzLmFyZ3YucG9wKCkudG9Mb3dlckNhc2UoKTsKICAgIHJlcXVpcmUoJ3Byb2Nlc3MtbWFuYWdlcicpLmVudW1lcmF0ZVByb2Nlc3NlcygpLnRoZW4oZnVuY3Rpb24gKHByb2MpCiAgICB7CiAgICAgICAgZm9yICh2YXIgcCBpbiBwcm9jKQogICAgICAgIHsKICAgICAgICAgICAgaWYgKHByb2NbcF0ucGF0aCAmJiAocHJvY1twXS5wYXRoLnRvTG93ZXJDYXNlKCkgPT0gc2VydmljZUxvY2F0aW9uKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcHJvY2Vzcy5raWxsKHByb2NbcF0ucGlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBwcm9jZXNzLmV4aXQoKTsKICAgIH0pOwp9CmNhdGNoIChlKQp7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQ==' +
|
||||
' "' + process.execPath + '" & copy "' + cwd + agentfilename + '.update.exe" "' + process.execPath + '" & net start "' + name + '" & erase "' + cwd + agentfilename + '.update.exe"', { wide: true });
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -707,7 +707,15 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
dev.aquired.controlMode = responses['IPS_HostBasedSetupService'].response.CurrentControlMode; // 1 = CCM, 2 = ACM
|
||||
if (typeof stack.wsman.comm.amtVersion == 'string') { // Set the Intel AMT version using the HTTP header if present
|
||||
var verSplit = stack.wsman.comm.amtVersion.split('.');
|
||||
if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); }
|
||||
if (verSplit.length >= 2) {
|
||||
dev.aquired.version = verSplit[0] + '.' + verSplit[1];
|
||||
dev.aquired.majorver = parseInt(verSplit[0]);
|
||||
dev.aquired.minorver = parseInt(verSplit[1]);
|
||||
if (verSplit.length >= 3) {
|
||||
dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2];
|
||||
dev.aquired.maintenancever = parseInt(verSplit[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
dev.aquired.realm = stack.wsman.comm.digestRealm;
|
||||
dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user;
|
||||
|
|
@ -750,7 +758,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Start power polling if not connected to LMS
|
||||
var ppfunc = function powerPoleFunction() { fetchPowerState(powerPoleFunction.dev); }
|
||||
ppfunc.dev = dev;
|
||||
dev.polltimer = new setTimeout(ppfunc, 290000); // Poll for power state every 4 minutes 50 seconds.
|
||||
if(dev.polltimer){ clearInterval(dev.polltimer); delete dev.polltimer; }
|
||||
dev.polltimer = new setInterval(ppfunc, 290000); // Poll for power state every 4 minutes 50 seconds.
|
||||
fetchPowerState(dev);
|
||||
} else {
|
||||
// For LMS connections, close now.
|
||||
|
|
@ -930,8 +939,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
if (response.Body.OSPowerSavingState == 2) { meshPowerState = 1; } // Fully powered (S0);
|
||||
else if (response.Body.OSPowerSavingState == 3) { meshPowerState = 2; } // Modern standby (We are going to call this S1);
|
||||
|
||||
// Set OS power state
|
||||
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 4, meshPowerState, null, { name: dev.name }); }
|
||||
// Set OS power state - connType: 0 = CIRA, 1 = CIRA-Relay, 2 = CIRA-LMS, 3 = LAN
|
||||
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), (dev.connType == 3 ? 4 : 2), meshPowerState, null, { name: dev.name }); }
|
||||
});
|
||||
} else {
|
||||
// Convert the power state
|
||||
|
|
@ -940,13 +949,13 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
var meshPowerState = -1, powerConversionTable = [-1, -1, 1, 2, 3, 6, 6, 5, 6];
|
||||
if (powerstate < powerConversionTable.length) { meshPowerState = powerConversionTable[powerstate]; } else { powerstate = 6; }
|
||||
|
||||
// Set power state
|
||||
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 4, meshPowerState, null, { name: dev.name }); }
|
||||
// Set power state - connType: 0 = CIRA, 1 = CIRA-Relay, 2 = CIRA-LMS, 3 = LAN
|
||||
if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), (dev.connType == 3 ? 4 : 2), meshPowerState, null, { name: dev.name }); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL
|
||||
// Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL, 15 = Power on to PXE, 16 = Reset to PXE
|
||||
function performPowerAction(nodeid, action) {
|
||||
console.log('performPowerAction', nodeid, action);
|
||||
var devices = obj.amtDevices[nodeid];
|
||||
|
|
@ -961,7 +970,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset
|
||||
try { dev.amtstack.RequestPowerStateChange(action, performPowerActionResponse); } catch (ex) { }
|
||||
} else {
|
||||
// 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL
|
||||
// 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL, 15 = Power on to PXE, 16 = Reset to PXE
|
||||
dev.amtstack.BatchEnum(null, ['*AMT_BootSettingData'], performAdvancedPowerActionResponse);
|
||||
}
|
||||
}
|
||||
|
|
@ -994,8 +1003,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Ready boot parameters
|
||||
bootSettingData['BIOSSetup'] = ((action >= 11) && (action <= 14));
|
||||
bootSettingData['UseSOL'] = ((action >= 13) && (action <= 14));
|
||||
if ((action == 11) || (action == 13)) { dev.powerAction = 2; } // Power on
|
||||
if ((action == 12) || (action == 14)) { dev.powerAction = 10; } // Reset
|
||||
if ((action == 11) || (action == 13) || (action == 15)) { dev.powerAction = 2; } // Power on
|
||||
if ((action == 12) || (action == 14) || (action == 16)) { dev.powerAction = 10; } // Reset
|
||||
|
||||
// Set boot parameters
|
||||
dev.amtstack.Put('AMT_BootSettingData', bootSettingData, function (stack, name, response, status, tag) {
|
||||
|
|
@ -1006,7 +1015,8 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
const dev = stack.dev;
|
||||
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
|
||||
// Set boot order
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder(null, function (stack, name, response, status) {
|
||||
var bootDevice = (action === 15 || action === 16) ? '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: Force PXE Boot</Selector></SelectorSet></ReferenceParameters>' : null;
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder(bootDevice, function (stack, name, response, status) {
|
||||
const dev = stack.dev;
|
||||
if ((obj.amtDevices[dev.nodeid] == null) || (status != 200)) return; // Device no longer exists or error
|
||||
// Perform power action
|
||||
|
|
@ -1059,7 +1069,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); delete dev.ocrfile; return; }
|
||||
|
||||
// Check if this Intel AMT device supports OCR
|
||||
if (responses['AMT_PublicKeyCertificate'].responses['ForceUEFIHTTPSBoot'] !== true) {
|
||||
if (responses['AMT_BootCapabilities'].response['ForceUEFIHTTPSBoot'] !== true) {
|
||||
dev.consoleMsg("This Intel AMT device does not support UEFI HTTPS boot (" + status + ")."); delete dev.ocrfile; return;
|
||||
}
|
||||
|
||||
|
|
@ -1089,11 +1099,14 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
|
||||
// Generate the one-time URL.
|
||||
var cookie = obj.parent.encodeCookie({ a: 'f', f: dev.ocrfile }, obj.parent.loginCookieEncryptionKey)
|
||||
var url = 'https://' + parent.webserver.certificates.AmtMpsName + ':' + ((parent.args.mpsaliasport != null) ? parent.args.mpsaliasport : parent.args.mpsport) + '/c/' + cookie + '.iso';
|
||||
var url = 'https://' + parent.webserver.certificates.AmtMpsName + ':' + ((parent.args.mpsaliasport != null) ? parent.args.mpsaliasport : parent.args.mpsport) + '/c/' + cookie + '.efi';
|
||||
delete dev.ocrfile;
|
||||
|
||||
// Generate the boot data for OCR with URL
|
||||
var r = response.Body;
|
||||
r['BIOSPause'] = false;
|
||||
r['BIOSSetup'] = false;
|
||||
r['EnforceSecureBoot'] = false;
|
||||
r['UefiBootParametersArray'] = Buffer.from(makeUefiBootParam(1, url) + makeUefiBootParam(20, 1, 1) + makeUefiBootParam(30, 0, 2), 'binary').toString('base64');
|
||||
r['UefiBootNumberOfParams'] = 3;
|
||||
r['BootMediaIndex'] = 0; // Do not use boot media index for One Click Recovery (OCR)
|
||||
|
|
@ -1114,8 +1127,7 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
dev.amtstack.SetBootConfigRole(1, function (stack, name, response, status) {
|
||||
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
|
||||
if (status != 200) { dev.consoleMsg("Failed to set boot config role (" + status + ")."); return; }
|
||||
var bootSource = 'Force OCR UEFI HTTPS Boot';
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder((bootSource == null) ? bootSource : '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: ' + bootSource + '</Selector></SelectorSet></ReferenceParameters>', function (stack, name, response, status) {
|
||||
dev.amtstack.CIM_BootConfigSetting_ChangeBootOrder('<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: Force OCR UEFI HTTPS Boot</Selector></SelectorSet></ReferenceParameters>', function (stack, name, response, status) {
|
||||
if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
|
||||
if (status != 200) { dev.consoleMsg("Failed to set boot config (" + status + ")."); return; }
|
||||
dev.amtstack.RequestPowerStateChange(10, function (stack, name, response, status) { // 10 = Reset, 2 = Power Up
|
||||
|
|
@ -2620,7 +2632,14 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
if (domain && domain.amtmanager && (domain.amtmanager.tlsacmactivation == true)) { TlsAcmActivation = true; }
|
||||
|
||||
// Check Intel AMT version
|
||||
if (typeof dev.intelamt.ver == 'string') { var verSplit = dev.intelamt.ver.split('.'); if (verSplit.length >= 3) { dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); } }
|
||||
if (typeof dev.intelamt.ver == 'string') {
|
||||
var verSplit = dev.intelamt.ver.split('.');
|
||||
if (verSplit.length >= 2) {
|
||||
dev.aquired.majorver = parseInt(verSplit[0]);
|
||||
dev.aquired.minorver = parseInt(verSplit[1]);
|
||||
if (verSplit.length >= 3) { dev.aquired.maintenancever = parseInt(verSplit[2]); }
|
||||
}
|
||||
}
|
||||
|
||||
// If this is Intel AMT 14 or better and allowed, we are going to attempt a host-based end-to-end TLS activation.
|
||||
if (TlsAcmActivation && (dev.aquired.majorver >= 14)) {
|
||||
|
|
@ -2676,7 +2695,15 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
dev.aquired.controlMode = 1; // 1 = CCM, 2 = ACM
|
||||
if (typeof dev.amtstack.wsman.comm.amtVersion == 'string') {
|
||||
var verSplit = dev.amtstack.wsman.comm.amtVersion.split('.');
|
||||
if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); }
|
||||
if (verSplit.length >= 2) {
|
||||
dev.aquired.version = verSplit[0] + '.' + verSplit[1];
|
||||
dev.aquired.majorver = parseInt(verSplit[0]);
|
||||
dev.aquired.minorver = parseInt(verSplit[1]);
|
||||
if (verSplit.length >= 3) {
|
||||
dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2];
|
||||
dev.aquired.maintenancever = parseInt(verSplit[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((typeof dev.mpsConnection.tag.meiState.OsHostname == 'string') && (typeof dev.mpsConnection.tag.meiState.OsDnsSuffix == 'string')) {
|
||||
dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix;
|
||||
|
|
@ -2811,8 +2838,10 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
var vs = getInstance(amtlogicalelements, 'AMT')['VersionString'];
|
||||
if (vs != null) {
|
||||
dev.aquired.version = vs;
|
||||
dev.aquired.versionmajor = parseInt(dev.aquired.version.split('.')[0]);
|
||||
dev.aquired.versionminor = parseInt(dev.aquired.version.split('.')[1]);
|
||||
version = dev.aquired.version.split('.')
|
||||
dev.aquired.versionmajor = parseInt(version[0]);
|
||||
dev.aquired.versionminor = parseInt(version[1]);
|
||||
if (version.length > 2) { dev.aquired.versionmaintenance = parseInt(version[2]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2820,10 +2849,14 @@ module.exports.CreateAmtManager = function (parent) {
|
|||
// Fetch the Intel AMT version from HTTP stack
|
||||
if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) {
|
||||
var s = stack.wsman.comm.amtVersion.split('.');
|
||||
if (s.length >= 3) {
|
||||
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
|
||||
if (s.length >= 2) {
|
||||
dev.aquired.version = s[0] + '.' + s[1] + '.';
|
||||
dev.aquired.versionmajor = parseInt(s[0]);
|
||||
dev.aquired.versionminor = parseInt(s[1]);
|
||||
if (s.length >= 3) {
|
||||
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
|
||||
dev.aquired.versionmaintenance = parseInt(s[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,8 +201,10 @@ module.exports.CreateAmtProvisioningServer = function (parent, config) {
|
|||
var vs = getInstance(amtlogicalelements, 'AMT')['VersionString'];
|
||||
if (vs != null) {
|
||||
dev.aquired.version = vs;
|
||||
dev.aquired.versionmajor = parseInt(dev.aquired.version.split('.')[0]);
|
||||
dev.aquired.versionminor = parseInt(dev.aquired.version.split('.')[1]);
|
||||
const versionSplit = parseInt(dev.aquired.version.split('.'));
|
||||
dev.aquired.versionmajor = parseInt(versionSplit[0]);
|
||||
dev.aquired.versionminor = parseInt(versionSplit[1]);
|
||||
if (versionSplit.length >= 3) { dev.aquired.versionmaintenance = parseInt(versionSplit[2]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,10 +212,14 @@ module.exports.CreateAmtProvisioningServer = function (parent, config) {
|
|||
// Fetch the Intel AMT version from HTTP stack
|
||||
if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) {
|
||||
var s = stack.wsman.comm.amtVersion.split('.');
|
||||
if (s.length >= 3) {
|
||||
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
|
||||
if (s.length >= 2) {
|
||||
dev.aquired.version = s[0] + '.' + s[1];
|
||||
dev.aquired.versionmajor = parseInt(s[0]);
|
||||
dev.aquired.versionminor = parseInt(s[1]);
|
||||
if (s.length >= 3) {
|
||||
dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2];
|
||||
dev.aquired.versionmaintenance = parseInt(s[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
78
apprelays.js
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @description MeshCentral MSTSC & SSH relay
|
||||
* @author Ylian Saint-Hilaire & Bryan Roe
|
||||
* @copyright Intel Corporation 2018-2022
|
||||
|
|
@ -458,7 +458,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
|
|||
const protocol = (args.tlsoffload) ? 'ws' : 'wss';
|
||||
var domainadd = '';
|
||||
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
|
||||
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=14&auth=' + cookie; // Protocol 14 is Web-TCP
|
||||
var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=14&auth=' + cookie; // Protocol 14 is Web-TCP
|
||||
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
|
||||
parent.parent.parent.debug('relay', 'TCP: Connection websocket to ' + url);
|
||||
obj.wsClient = new WebSocket(url, options);
|
||||
|
|
@ -717,9 +717,10 @@ module.exports.CreateWebRelay = function (parent, db, args, domain, mtype) {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i, header[i]); } // Set the headers if not blocked
|
||||
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i.trim(), header[i]); } // Set the headers if not blocked
|
||||
}
|
||||
obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
// Dont set any Content-Security-Policy at all because some applications like Node-Red, access external websites from there javascript which would be forbidden by the below CSP
|
||||
//obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
//obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future
|
||||
obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays
|
||||
}
|
||||
|
|
@ -842,22 +843,21 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
|
|||
obj.relaySocket.resume();
|
||||
}
|
||||
} else {
|
||||
if (typeof data == 'string') {
|
||||
// Forward any ping/pong commands to the browser
|
||||
var cmd = null;
|
||||
try { cmd = JSON.parse(data); } catch (ex) { }
|
||||
try { // Forward any ping/pong commands to the browser
|
||||
var cmd = JSON.parse(data);
|
||||
if ((cmd != null) && (cmd.ctrlChannel == '102938')) {
|
||||
if (cmd.type == 'ping') { send(['ping']); }
|
||||
else if (cmd.type == 'pong') { send(['pong']); }
|
||||
}
|
||||
return;
|
||||
} catch (ex) { // You are not JSON data so just send over relaySocket
|
||||
obj.wsClient._socket.pause();
|
||||
try {
|
||||
obj.relaySocket.write(data, function () {
|
||||
if (obj.wsClient && obj.wsClient._socket) { try { obj.wsClient._socket.resume(); } catch (ex) { console.log(ex); } }
|
||||
});
|
||||
} catch (ex) { console.log(ex); obj.close(); }
|
||||
}
|
||||
obj.wsClient._socket.pause();
|
||||
try {
|
||||
obj.relaySocket.write(data, function () {
|
||||
if (obj.wsClient && obj.wsClient._socket) { try { obj.wsClient._socket.resume(); } catch (ex) { console.log(ex); } }
|
||||
});
|
||||
} catch (ex) { console.log(ex); obj.close(); }
|
||||
}
|
||||
});
|
||||
obj.wsClient.on('close', function () { parent.parent.debug('relay', 'RDP: Relay websocket closed'); obj.close(); });
|
||||
|
|
@ -984,6 +984,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
|
|||
if ((node == null) || (visible == false) || ((rights & MESHRIGHT_REMOTECONTROL) == 0)) { obj.close(); return; }
|
||||
if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_REMOTEVIEWONLY) != 0)) { obj.viewonly = true; }
|
||||
if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0)) { obj.limitedinput = true; }
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
obj.mtype = node.mtype; // Store the device group type
|
||||
obj.meshid = node.meshid; // Store the MeshID
|
||||
|
||||
|
|
@ -1046,7 +1047,11 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
|
|||
if ((k == 14) || (k == 28)) { ok = true; } // Enter and backspace
|
||||
if (ok == false) return;
|
||||
}
|
||||
if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventScancode(msg[1], msg[2]); } break;
|
||||
var extended = false;
|
||||
var extendedkeys = [57419,57421,57416,57424,57426,57427,57417,57425,57372,57397,57415,57423,57373,57400,57399];
|
||||
// left,right,up,down,insert,delete,pageup,pagedown,numpadenter,numpaddivide,home,end,controlright,altright,printscreen
|
||||
if (extendedkeys.includes(msg[1])) extended=true;
|
||||
if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventScancode(msg[1], msg[2], extended); } break;
|
||||
}
|
||||
case 'unicode': { if (rdpClient && (obj.viewonly != true)) { rdpClient.sendKeyEventUnicode(msg[1], msg[2]); } break; }
|
||||
case 'utype': {
|
||||
|
|
@ -1210,7 +1215,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
const protocol = (args.tlsoffload) ? 'ws' : 'wss';
|
||||
var domainadd = '';
|
||||
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
|
||||
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + obj.xcookie; // Protocol 11 is Web-SSH
|
||||
var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + obj.xcookie; // Protocol 11 is Web-SSH
|
||||
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
|
||||
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
|
||||
obj.wsClient = new WebSocket(url, options);
|
||||
|
|
@ -1277,16 +1282,14 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
ws._socket.resume();
|
||||
}
|
||||
} else {
|
||||
if (typeof data == 'string') {
|
||||
// Forward any ping/pong commands to the browser
|
||||
try { // Forward any ping/pong commands to the browser
|
||||
var cmd = null;
|
||||
try { cmd = JSON.parse(data); } catch (ex) { }
|
||||
cmd = JSON.parse(data);
|
||||
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); }
|
||||
return;
|
||||
} catch(ex) { // Relay WS --> SSH instead
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
|
||||
// Relay WS --> SSH
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
});
|
||||
obj.wsClient.on('close', function () { parent.parent.debug('relay', 'SSH: Relay websocket closed'); obj.close(); });
|
||||
|
|
@ -1314,7 +1317,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
// Check if we have SSH credentials for this device
|
||||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (node.ssh[obj.userid] == null) || (typeof node.ssh[obj.userid].u != 'string') || ((typeof node.ssh[obj.userid].p != 'string') && (typeof node.ssh[obj.userid].k != 'string'))) {
|
||||
// Send a request for SSH authentication
|
||||
try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { }
|
||||
|
|
@ -1362,7 +1365,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
obj.termSize = msg;
|
||||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
if (node.ssh != null) {
|
||||
obj.username = node.ssh.u;
|
||||
obj.privateKey = node.ssh.k;
|
||||
|
|
@ -1404,7 +1407,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
|
|||
parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) {
|
||||
if (obj.cookie == null) return; // obj has been cleaned up, just exit.
|
||||
if ((err != null) || (nodes == null) || (nodes.length != 1)) { parent.parent.debug('relay', 'SSH: Invalid device'); obj.close(); }
|
||||
const node = nodes[0];
|
||||
const node = parent.common.unEscapeLinksFieldName(nodes[0]); // unEscape node data for rdp/ssh credentials
|
||||
obj.nodeid = node._id; // Store the NodeID
|
||||
obj.meshid = node.meshid; // Store the MeshID
|
||||
obj.mtype = node.mtype; // Store the device group type
|
||||
|
|
@ -1549,7 +1552,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
|
|||
const protocol = (args.tlsoffload) ? 'ws' : 'wss';
|
||||
var domainadd = '';
|
||||
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
|
||||
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + authCookie // Protocol 11 is Web-SSH
|
||||
var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=11&auth=' + authCookie // Protocol 11 is Web-SSH
|
||||
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
|
||||
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
|
||||
obj.wsClient = new WebSocket(url, options);
|
||||
|
|
@ -1617,16 +1620,14 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
|
|||
ws._socket.resume();
|
||||
}
|
||||
} else {
|
||||
if (typeof data == 'string') {
|
||||
// Forward any ping/pong commands to the browser
|
||||
try { // Forward any ping/pong commands to the browser
|
||||
var cmd = null;
|
||||
try { cmd = JSON.parse(data); } catch (ex) { }
|
||||
cmd = JSON.parse(data);
|
||||
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { try { obj.ws.send(data); } catch (ex) { console.log(ex); } }
|
||||
return;
|
||||
} catch (ex) { // Relay WS --> SSH
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
|
||||
// Relay WS --> SSH
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
});
|
||||
obj.wsClient.on('close', function () {
|
||||
|
|
@ -1739,6 +1740,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
|
|||
if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
|
||||
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
|
||||
if (obj.ws == null) return; // obj has been cleaned up, just exit.
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
|
||||
// Check permissions
|
||||
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights
|
||||
|
|
@ -1903,7 +1905,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
|
|||
const protocol = (args.tlsoffload) ? 'ws' : 'wss';
|
||||
var domainadd = '';
|
||||
if ((domain.dns == null) && (domain.id != '')) { domainadd = domain.id + '/' }
|
||||
const url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=13&auth=' + authCookie // Protocol 13 is Web-SSH-Files
|
||||
var url = protocol + '://localhost:' + args.port + '/' + domainadd + (((obj.mtype == 3) && (obj.relaynodeid == null)) ? 'local' : 'mesh') + 'relay.ashx?p=13&auth=' + authCookie // Protocol 13 is Web-SSH-Files
|
||||
if (domain.id != '') { url += '&domainid=' + domain.id; } // Since we are using "localhost", we are going to signal what domain we are on using a URL argument.
|
||||
parent.parent.debug('relay', 'SSH: Connection websocket to ' + url);
|
||||
obj.wsClient = new WebSocket(url, options);
|
||||
|
|
@ -1965,16 +1967,15 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
|
|||
ws._socket.resume();
|
||||
}
|
||||
} else {
|
||||
if (typeof data == 'string') {
|
||||
try {
|
||||
// Forward any ping/pong commands to the browser
|
||||
var cmd = null;
|
||||
try { cmd = JSON.parse(data); } catch (ex) { }
|
||||
cmd = JSON.parse(data);
|
||||
if ((cmd != null) && (cmd.ctrlChannel == '102938') && ((cmd.type == 'ping') || (cmd.type == 'pong'))) { obj.ws.send(data); }
|
||||
return;
|
||||
} catch (ex) { // Relay WS --> SSH
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
|
||||
// Relay WS --> SSH
|
||||
if ((data.length > 0) && (obj.ser != null)) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
|
||||
}
|
||||
});
|
||||
obj.wsClient.on('close', function () {
|
||||
|
|
@ -2269,6 +2270,7 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user
|
|||
if ((user == null) || (req.query.nodeid == null)) { obj.close(); return; } // Invalid nodeid
|
||||
parent.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
|
||||
if (obj.ws == null) return; // obj has been cleaned up, just exit.
|
||||
node = parent.common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
|
||||
|
||||
// Check permissions
|
||||
if ((rights & 8) == 0) { obj.close(); return; } // No MESHRIGHT_REMOTECONTROL rights
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ function createAuthenticodeHandler(path) {
|
|||
for (var i = 0; i < obj.header.coff.numberOfSections; i++) {
|
||||
var section = {};
|
||||
buf = readFileSlice(obj.header.SectionHeadersPtr + (i * 40), 40);
|
||||
if (buf[0] != 46) { obj.close(); return false; }; // Name of the section must start with a dot. If not, something is wrong.
|
||||
if ((buf[0] != 46) && (buf[0] != 95)) { obj.close(); return false; }; // Name of the section must start with a dot or underscore. If not, something is wrong.
|
||||
var sectionName = buf.slice(0, 8).toString().trim('\0');
|
||||
var j = sectionName.indexOf('\0');
|
||||
if (j >= 0) { sectionName = sectionName.substring(0, j); } // Trim any trailing zeroes
|
||||
|
|
@ -1566,7 +1566,11 @@ function createAuthenticodeHandler(path) {
|
|||
options.protocol = timeServerUrl.protocol;
|
||||
options.hostname = timeServerUrl.hostname;
|
||||
options.path = timeServerUrl.pathname;
|
||||
options.port = ((timeServerUrl.port == '') ? 80 : parseInt(timeServerUrl.port));
|
||||
let http = require("http")
|
||||
if (options.protocol === "https:"){
|
||||
http = require("https")
|
||||
}
|
||||
options.port = ((timeServerUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(timeServerUrl.port));
|
||||
|
||||
if (options.proxy == null) {
|
||||
// No proxy needed
|
||||
|
|
@ -1584,7 +1588,7 @@ function createAuthenticodeHandler(path) {
|
|||
|
||||
// Set up the request
|
||||
var responseAccumulator = '';
|
||||
var req = require('http').request(options, function (res) {
|
||||
var req = http.request(options, function (res) {
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function (chunk) { responseAccumulator += chunk; });
|
||||
res.on('end', function () { func(null, responseAccumulator); });
|
||||
|
|
@ -1605,12 +1609,12 @@ function createAuthenticodeHandler(path) {
|
|||
proxyOptions.protocol = proxyUrl.protocol;
|
||||
proxyOptions.hostname = proxyUrl.hostname;
|
||||
proxyOptions.path = options.hostname + ':' + options.port;
|
||||
proxyOptions.port = ((proxyUrl.port == '') ? 80 : parseInt(proxyUrl.port));
|
||||
proxyOptions.port = ((proxyUrl.port == '') ? (options.protocol === "https:" ? 443 : 80) : parseInt(proxyUrl.port));
|
||||
}
|
||||
|
||||
// Set up the proxy request
|
||||
var responseAccumulator = '';
|
||||
var req = require('http').request(proxyOptions);
|
||||
var req = http.request(proxyOptions);
|
||||
req.on('error', function (err) { func('' + err); });
|
||||
req.on('connect', function (res, socket, head) {
|
||||
// Make a request over the HTTP tunnel
|
||||
|
|
|
|||
|
|
@ -262,7 +262,10 @@ module.exports.CertificateOperations = function (parent) {
|
|||
acmconfig.cn = certCommonName.value;
|
||||
}
|
||||
}
|
||||
|
||||
if(r.certs[0].md){
|
||||
acmconfig.hashAlgorithm = r.certs[0].md.algorithm;
|
||||
}
|
||||
|
||||
delete acmconfig.cert;
|
||||
delete acmconfig.certpass;
|
||||
acmconfig.certs = orderedCerts;
|
||||
|
|
@ -633,9 +636,16 @@ module.exports.CertificateOperations = function (parent) {
|
|||
};
|
||||
|
||||
// Return the SHA384 hash of the certificate public key
|
||||
obj.getPublicKeyHashBinary = function (cert) {
|
||||
var publickey = obj.pki.certificateFromPem(cert).publicKey;
|
||||
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
|
||||
obj.getPublicKeyHashBinary = function (pem) {
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
return obj.pki.getPublicKeyFingerprint(obj.pki.certificateFromPem(pem).publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
var cert = new X509Certificate(pem);
|
||||
return obj.crypto.createHash('sha384').update(cert.publicKey.export({ type: ((cert.publicKey.asymmetricKeyType == 'rsa') ? 'pkcs1' : 'spki'), format: 'der' })).digest('binary');
|
||||
}
|
||||
};
|
||||
|
||||
// Return the SHA384 hash of the certificate, return binary
|
||||
|
|
@ -740,7 +750,7 @@ module.exports.CertificateOperations = function (parent) {
|
|||
}
|
||||
|
||||
// Return true if the name is found in the certificates names, we support wildcard certificates
|
||||
obj.compareCertificateNames = function(certNames, name) {
|
||||
obj.compareCertificateNames = function (certNames, name) {
|
||||
if (certNames == null) return false;
|
||||
name = name.toLowerCase();
|
||||
var xcertNames = [];
|
||||
|
|
@ -758,12 +768,102 @@ module.exports.CertificateOperations = function (parent) {
|
|||
|
||||
// Return true if the certificate is valid
|
||||
obj.checkCertificate = function (pem, key) {
|
||||
var cert = null;
|
||||
try { cert = obj.pki.certificateFromPem(pem); } catch (ex) { return false; } // Unable to decode certificate
|
||||
if (cert.serialNumber == '') return false; // Empty serial number is not allowed.
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
var cert = null;
|
||||
try { cert = obj.pki.certificateFromPem(pem); } catch (ex) { return false; } // Unable to decode certificate
|
||||
if (cert.serialNumber == '') return false; // Empty serial number is not allowed.
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
try {
|
||||
const cert = new X509Certificate(pem);
|
||||
if ((cert.serialNumber == '') || (cert.serialNumber == null)) return false; // Empty serial number is not allowed.
|
||||
} catch (ex) { return false; } // Unable to decode certificate
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the Common Name from a certificate
|
||||
obj.getCertificateCommonName = function (pem, field) {
|
||||
if (field == null) { field = 'CN'; }
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
var cert = obj.pki.certificateFromPem(pem);
|
||||
if (cert.subject.getField(field) != null) return cert.subject.getField(field).value;
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
const subjects = new X509Certificate(pem).subject.split('\n');
|
||||
for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the Issuer Common Name from a certificate
|
||||
obj.getCertificateIssuerCommonName = function (pem, field) {
|
||||
if (field == null) { field = 'CN'; }
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
var cert = obj.pki.certificateFromPem(pem);
|
||||
if (cert.issuer.getField(field) != null) return cert.issuer.getField(field).value;
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
const subjects = new X509Certificate(pem).issuer.split('\n');
|
||||
for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the Common Name and alternate names from a certificate
|
||||
obj.getCertificateAltNames = function (pem) {
|
||||
const altNamesResults = [];
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
var cert = obj.pki.certificateFromPem(pem);
|
||||
if (cert.subject.getField('CN') != null) { altNamesResults.push(cert.subject.getField('CN').value); }
|
||||
var altNames = cert.getExtension('subjectAltName');
|
||||
if (altNames) {
|
||||
for (i = 0; i < altNames.altNames.length; i++) {
|
||||
if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
|
||||
var acn = altNames.altNames[i].value.toLowerCase();
|
||||
if (altNamesResults.indexOf(acn) == -1) { altNamesResults.push(acn); }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
const cert = new X509Certificate(pem);
|
||||
const subjects = cert.subject.split('\n');
|
||||
for (var i in subjects) { if (subjects[i].startsWith('CN=')) { altNamesResults.push(subjects[i].substring(3)); } }
|
||||
var subjectAltNames = cert.subjectAltName;
|
||||
if (subjectAltNames != null) {
|
||||
subjectAltNames = subjectAltNames.split(', ');
|
||||
for (var i = 0; i < subjectAltNames.length; i++) {
|
||||
if (subjectAltNames[i].startsWith('DNS:') && altNamesResults.indexOf(subjectAltNames[i].substring(4)) == -1) {
|
||||
altNamesResults.push(subjectAltNames[i].substring(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return altNamesResults;
|
||||
}
|
||||
|
||||
// Get the expiration time from a certificate
|
||||
obj.getCertificateExpire = function (pem) {
|
||||
const altNamesResults = [];
|
||||
const { X509Certificate } = require('crypto');
|
||||
if (X509Certificate == null) {
|
||||
// This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
|
||||
return Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter);
|
||||
} else {
|
||||
// This version of NodeJS supports x509 certificates
|
||||
return Date.parse(new X509Certificate(pem).validTo);
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt private key if needed
|
||||
obj.decryptPrivateKey = function (key) {
|
||||
if (typeof key != 'string') return key;
|
||||
|
|
@ -920,22 +1020,11 @@ module.exports.CertificateOperations = function (parent) {
|
|||
|
||||
if (rcount === rcountmax) {
|
||||
// Fetch the certificates names for the main certificate
|
||||
r.AmtMpsName = obj.pki.certificateFromPem(r.mps.cert).subject.getField('CN').value;
|
||||
var webCertificate = obj.pki.certificateFromPem(r.web.cert);
|
||||
if (webCertificate.issuer.getField('CN') != null) { r.WebIssuer = webCertificate.issuer.getField('CN').value; } else { r.WebIssuer = null; }
|
||||
r.CommonName = webCertificate.subject.getField('CN').value;
|
||||
r.CommonNames = [ r.CommonName ];
|
||||
var altNames = webCertificate.getExtension('subjectAltName');
|
||||
if (altNames) {
|
||||
for (i = 0; i < altNames.altNames.length; i++) {
|
||||
if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
|
||||
var acn = altNames.altNames[i].value.toLowerCase();
|
||||
if (r.CommonNames.indexOf(acn) == -1) { r.CommonNames.push(acn); }
|
||||
}
|
||||
}
|
||||
}
|
||||
var rootCertificate = obj.pki.certificateFromPem(r.root.cert);
|
||||
r.RootName = rootCertificate.subject.getField('CN').value;
|
||||
r.AmtMpsName = obj.getCertificateCommonName(r.mps.cert);
|
||||
r.WebIssuer = obj.getCertificateIssuerCommonName(r.web.cert);
|
||||
r.CommonName = obj.getCertificateCommonName(r.web.cert);
|
||||
r.CommonNames = obj.getCertificateAltNames(r.web.cert);
|
||||
r.RootName = obj.getCertificateCommonName(r.root.cert);
|
||||
|
||||
// If the "cert" name is not set, try to use the certificate CN instead (ok if the certificate is not wildcard).
|
||||
if (commonName == 'un-configured') {
|
||||
|
|
@ -960,6 +1049,7 @@ module.exports.CertificateOperations = function (parent) {
|
|||
config.domains[i].certs = r.dns[i];
|
||||
} else {
|
||||
console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
|
||||
rcountmax++;
|
||||
}
|
||||
} else {
|
||||
// If the web certificate already exist, load it. Load both certificate and private key
|
||||
|
|
@ -988,10 +1078,8 @@ module.exports.CertificateOperations = function (parent) {
|
|||
// If we have all the certificates we need, stop here.
|
||||
if (rcount === rcountmax) {
|
||||
if ((certargs == null) && (mpscertargs == null)) { if (func != undefined) { func(r); } return r; } // If no certificate arguments are given, keep the certificate
|
||||
var xcountry, xcountryField = webCertificate.subject.getField('C');
|
||||
if (xcountryField != null) { xcountry = xcountryField.value; }
|
||||
var xorganization, xorganizationField = webCertificate.subject.getField('O');
|
||||
if (xorganizationField != null) { xorganization = xorganizationField.value; }
|
||||
const xcountry = obj.getCertificateCommonName(r.web.cert, 'C');
|
||||
const xorganization = obj.getCertificateCommonName(r.web.cert, 'O');
|
||||
if (certargs == null) { commonName = r.CommonName; country = xcountry; organization = xorganization; }
|
||||
|
||||
// Check if we have correct certificates.
|
||||
|
|
@ -1320,20 +1408,15 @@ module.exports.CertificateOperations = function (parent) {
|
|||
|
||||
// Perform any general operation
|
||||
obj.acceleratorPerformOperation = function (operation, data, tag, func) {
|
||||
if (acceleratorTotalCount <= 1) {
|
||||
// No accelerators available
|
||||
require(program).processMessage({ action: operation, data: data, tag: tag, func: func });
|
||||
var acc = obj.getAccelerator();
|
||||
if (acc == null) {
|
||||
// Add to pending accelerator workload
|
||||
acceleratorPerformSignaturePushFuncCall++;
|
||||
pendingAccelerator.push({ action: operation, data: data, tag: tag, func: func });
|
||||
} else {
|
||||
var acc = obj.getAccelerator();
|
||||
if (acc == null) {
|
||||
// Add to pending accelerator workload
|
||||
acceleratorPerformSignaturePushFuncCall++;
|
||||
pendingAccelerator.push({ action: operation, data: data, tag: tag, func: func });
|
||||
} else {
|
||||
// Send to accelerator now
|
||||
acceleratorPerformSignatureRunFuncCall++;
|
||||
acc.send(acc.x = { action: operation, data: data, tag: tag, func: func });
|
||||
}
|
||||
// Send to accelerator now
|
||||
acceleratorPerformSignatureRunFuncCall++;
|
||||
acc.send(acc.x = { action: operation, data: data, tag: tag, func: func });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
59
common.js
|
|
@ -155,14 +155,26 @@ module.exports.objKeysToLower = function (obj, exceptions, parent) {
|
|||
return obj;
|
||||
};
|
||||
|
||||
// Escape and unescape feild names so there are no invalid characters for MongoDB
|
||||
module.exports.escapeFieldName = function (name) { if ((name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24'); };
|
||||
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
||||
// Escape and unescape field names so there are no invalid characters for MongoDB/NeDB ("$", ",", ".", see https://github.com/seald/nedb/tree/master?tab=readme-ov-file#inserting-documents)
|
||||
module.exports.escapeFieldName = function (name) { if ((name.indexOf(',') == -1) && (name.indexOf('%') == -1) && (name.indexOf('.') == -1) && (name.indexOf('$') == -1)) return name; return name.split('%').join('%25').split('.').join('%2E').split('$').join('%24').split(',').join('%2C'); };
|
||||
module.exports.unEscapeFieldName = function (name) { if (name.indexOf('%') == -1) return name; return name.split('%2C').join(',').split('%2E').join('.').split('%24').join('$').split('%25').join('%'); };
|
||||
|
||||
// Escape all links
|
||||
module.exports.escapeLinksFieldNameEx = function (docx) { if (docx.links == null) { return docx; } var doc = Object.assign({}, docx); doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } return doc; };
|
||||
module.exports.escapeLinksFieldName = function (docx) { var doc = Object.assign({}, docx); if (doc.links != null) { doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } } return doc; };
|
||||
module.exports.unEscapeLinksFieldName = function (doc) { if (doc.links != null) { for (var j in doc.links) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.links[ue] = doc.links[j]; delete doc.links[j]; } } } return doc; };
|
||||
// Escape all links, SSH and RDP usernames
|
||||
// This is required for databases like NeDB that don't accept "." or "," as part of a field name.
|
||||
module.exports.escapeLinksFieldNameEx = function (docx) { if ((docx.links == null) && (docx.ssh == null) && (docx.rdp == null)) { return docx; } return module.exports.escapeLinksFieldName(docx); };
|
||||
module.exports.escapeLinksFieldName = function (docx) {
|
||||
var doc = Object.assign({}, docx);
|
||||
if (doc.links != null) { doc.links = Object.assign({}, doc.links); for (var i in doc.links) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.links[ue] = doc.links[i]; delete doc.links[i]; } } }
|
||||
if (doc.ssh != null) { doc.ssh = Object.assign({}, doc.ssh); for (var i in doc.ssh) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.ssh[ue] = doc.ssh[i]; delete doc.ssh[i]; } } }
|
||||
if (doc.rdp != null) { doc.rdp = Object.assign({}, doc.rdp); for (var i in doc.rdp) { var ue = module.exports.escapeFieldName(i); if (ue !== i) { doc.rdp[ue] = doc.rdp[i]; delete doc.rdp[i]; } } }
|
||||
return doc;
|
||||
};
|
||||
module.exports.unEscapeLinksFieldName = function (doc) {
|
||||
if (doc.links != null) { for (var j in doc.links) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.links[ue] = doc.links[j]; delete doc.links[j]; } } }
|
||||
if (doc.ssh != null) { for (var j in doc.ssh) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.ssh[ue] = doc.ssh[j]; delete doc.ssh[j]; } } }
|
||||
if (doc.rdp != null) { for (var j in doc.rdp) { var ue = module.exports.unEscapeFieldName(j); if (ue !== j) { doc.rdp[ue] = doc.rdp[j]; delete doc.rdp[j]; } } }
|
||||
return doc;
|
||||
};
|
||||
//module.exports.escapeAllLinksFieldName = function (docs) { for (var i in docs) { module.exports.escapeLinksFieldName(docs[i]); } return docs; };
|
||||
module.exports.unEscapeAllLinksFieldName = function (docs) { for (var i in docs) { docs[i] = module.exports.unEscapeLinksFieldName(docs[i]); } return docs; };
|
||||
|
||||
|
|
@ -322,6 +334,11 @@ module.exports.meshServerRightsArrayToNumber = function (val) {
|
|||
if (r == 'locked') { newAccRights |= 32; }
|
||||
if (r == 'nonewgroups') { newAccRights |= 64; }
|
||||
if (r == 'notools') { newAccRights |= 128; }
|
||||
if (r == 'usergroups') { newAccRights |= 256; }
|
||||
if (r == 'recordings') { newAccRights |= 512; }
|
||||
if (r == 'locksettings') { newAccRights |= 1024; }
|
||||
if (r == 'allevents') { newAccRights |= 2048; }
|
||||
if (r == 'nonewdevices') { newAccRights |= 4096; }
|
||||
}
|
||||
return newAccRights;
|
||||
}
|
||||
|
|
@ -374,4 +391,32 @@ module.exports.moveOldFiles = function (filelist) {
|
|||
for (var i in filelist) { if (fs.existsSync(filelist[i] + oldFileExt) == true) { extOk = false; } }
|
||||
} while (extOk == false);
|
||||
for (var i in filelist) { try { fs.renameSync(filelist[i], filelist[i] + oldFileExt); } catch (ex) { } }
|
||||
}
|
||||
|
||||
// Convert strArray to Array, returns array if strArray or null if any other type
|
||||
module.exports.convertStrArray = function (object, split) {
|
||||
if (split && typeof object === 'string') {
|
||||
return object.split(split)
|
||||
} else if (typeof object === 'string') {
|
||||
return Array(object);
|
||||
} else if (Array.isArray(object)) {
|
||||
return object
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.uniqueArray = function (a) {
|
||||
var seen = {};
|
||||
var out = [];
|
||||
var len = a.length;
|
||||
var j = 0;
|
||||
for(var i = 0; i < len; i++) {
|
||||
var item = a[i];
|
||||
if(seen[item] !== 1) {
|
||||
seen[item] = 1;
|
||||
out[j++] = item;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
"archiver": "^5.3.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"cbor": "~5.2.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-session": "^1.4.0",
|
||||
"express": "^4.17.0",
|
||||
"express-handlebars": "^5.3.5",
|
||||
"express-ws": "^4.0.0",
|
||||
"ipcheck": "^0.1.0",
|
||||
"minimist": "^1.2.5",
|
||||
"multiparty": "^4.2.1",
|
||||
"@yetzt/nedb": "^1.8.0",
|
||||
"node-forge": "^1.0.0",
|
||||
"ws": "^5.2.3",
|
||||
"yauzl": "^2.10.0"
|
||||
"@seald-io/nedb": "4.0.4",
|
||||
"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",
|
||||
"node-forge": "1.3.1",
|
||||
"ua-parser-js": "1.0.39",
|
||||
"ws": "8.18.0",
|
||||
"yauzl": "2.10.0"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM node:current-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-alpine AS builder
|
||||
|
||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||
COPY ./ /opt/meshcentral/meshcentral/
|
||||
|
|
@ -18,7 +18,7 @@ RUN if ! [ -z "$DISABLE_TRANSLATE" ] && [ "$DISABLE_TRANSLATE" != "yes" ] && [ "
|
|||
fi
|
||||
|
||||
# install translate/minify modules if need too
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral && npm install html-minifier jsdom minify-js; fi
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral && npm install html-minifier@4.0.0 jsdom@22.1.0 esprima@4.0.1; fi
|
||||
|
||||
# first extractall if need too
|
||||
RUN if [ -z "$DISABLE_MINIFY" ] || [ -z "$DISABLE_TRANSLATE" ]; then cd meshcentral/translate && node translate.js extractall; fi
|
||||
|
|
@ -34,7 +34,7 @@ RUN rm -rf /opt/meshcentral/meshcentral/docker
|
|||
RUN rm -rf /opt/meshcentral/meshcentral/node_modules
|
||||
|
||||
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest
|
||||
FROM --platform=$TARGETPLATFORM alpine:3.21
|
||||
|
||||
#Add non-root user, add installation directories and assign proper permissions
|
||||
RUN mkdir -p /opt/meshcentral/meshcentral
|
||||
|
|
@ -43,7 +43,7 @@ RUN mkdir -p /opt/meshcentral/meshcentral
|
|||
WORKDIR /opt/meshcentral
|
||||
|
||||
RUN apk update \
|
||||
&& apk add --no-cache --update tzdata nodejs npm bash \
|
||||
&& apk add --no-cache --update tzdata nodejs npm bash python3 make gcc g++ \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
RUN npm install -g npm@latest
|
||||
|
||||
|
|
@ -58,16 +58,18 @@ ENV CONFIG_FILE="config.json"
|
|||
ENV USE_MONGODB="false"
|
||||
ENV MONGO_INITDB_ROOT_USERNAME="root"
|
||||
ENV MONGO_INITDB_ROOT_PASSWORD="pass"
|
||||
ENV MONGO_URL=""
|
||||
ENV HOSTNAME="localhost"
|
||||
ENV ALLOW_NEW_ACCOUNTS="true"
|
||||
ENV ALLOWPLUGINS="false"
|
||||
ENV LOCALSESSIONRECORDING="false"
|
||||
ENV MINIFY="true"
|
||||
ENV LOCALSESSIONRECORDING="true"
|
||||
ENV MINIFY="false"
|
||||
ENV WEBRTC="false"
|
||||
ENV IFRAME="false"
|
||||
ENV SESSION_KEY=""
|
||||
ENV REVERSE_PROXY="false"
|
||||
ENV REVERSE_PROXY_TLS_PORT=""
|
||||
ENV ARGS=""
|
||||
|
||||
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ] && [ "$INCLUDE_MONGODBTOOLS" != "yes" ] && [ "$INCLUDE_MONGODBTOOLS" != "YES" ] \
|
||||
&& [ "$INCLUDE_MONGODBTOOLS" != "true" ] && [ "$INCLUDE_MONGODBTOOLS" != "TRUE" ]; then \
|
||||
|
|
@ -81,11 +83,12 @@ COPY --from=builder /opt/meshcentral/meshcentral /opt/meshcentral/meshcentral
|
|||
COPY ./docker/startup.sh ./startup.sh
|
||||
COPY ./docker/config.json.template /opt/meshcentral/config.json.template
|
||||
|
||||
# install dependencies from package.json and nedb
|
||||
RUN cd meshcentral && npm install && npm install nedb
|
||||
# install dependencies from package.json
|
||||
RUN cd meshcentral && npm install
|
||||
|
||||
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.9.1; fi
|
||||
RUN if ! [ -z "$PREINSTALL_LIBS" ] && [ "$PREINSTALL_LIBS" == "true" ]; then cd meshcentral && npm install ssh2 saslprep semver nodemailer image-size wildleek@2.0.0 otplib@10.2.3 yubikeyotp; fi
|
||||
# NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN meshcentral.js mainStart()
|
||||
RUN if ! [ -z "$INCLUDE_MONGODBTOOLS" ]; then cd meshcentral && npm install mongodb@4.13.0 saslprep@1.0.3; fi
|
||||
RUN if ! [ -z "$PREINSTALL_LIBS" ] && [ "$PREINSTALL_LIBS" == "true" ]; then cd meshcentral && npm install ssh2@1.16.0 semver@7.5.4 nodemailer@6.9.15 image-size@1.1.1 wildleek@2.0.0 otplib@10.2.3 yubikeyotp@0.2.0; fi
|
||||
|
||||
EXPOSE 80 443 4433
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@
|
|||
"": {
|
||||
"_title": "MyServer",
|
||||
"_title2": "Servername",
|
||||
"minify": true,
|
||||
"minify": false,
|
||||
"NewAccounts": true,
|
||||
"localSessionRecording": false,
|
||||
"localSessionRecording": true,
|
||||
"_userNameIsEmail": true,
|
||||
"_certUrl": "my.reverse.proxy"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@
|
|||
# Templates
|
||||
|
||||
## .env
|
||||
You can place the `config.json` file directly under `./meshcentral/data/`, or use the following `.env` file instead.
|
||||
|
||||
```ini
|
||||
NODE_ENV=production
|
||||
|
||||
# initial mongodb-variables
|
||||
USE_MONGODB=false
|
||||
# set already exist mongo connection string url here
|
||||
MONGO_URL=
|
||||
# or set following init params for new mongodb, use it with docker-compose file with mongodb version
|
||||
MONGO_INITDB_ROOT_USERNAME=mongodbadmin
|
||||
MONGO_INITDB_ROOT_PASSWORD=mongodbpasswd
|
||||
|
||||
|
|
@ -26,8 +30,7 @@ MONGO_INITDB_ROOT_PASSWORD=mongodbpasswd
|
|||
|
||||
# your hostname
|
||||
HOSTNAME=my.domain.com
|
||||
USE_MONGODB=false
|
||||
# set to your reverse proxy IP if you want to put meshcentral behind a reverse proxy
|
||||
# set to your reverse proxy IP if you want to put meshcentral behind a reverse proxy
|
||||
REVERSE_PROXY=false
|
||||
REVERSE_PROXY_TLS_PORT=
|
||||
# set to true if you wish to enable iframe support
|
||||
|
|
@ -42,6 +45,8 @@ ALLOWPLUGINS=false
|
|||
LOCALSESSIONRECORDING=false
|
||||
# set to enable or disable minification of json, reduces traffic
|
||||
MINIFY=true
|
||||
# set this value to add extra arguments to meshcentral on startup (e.g --debug ldap)
|
||||
ARGS=
|
||||
```
|
||||
|
||||
## docker-compose.yml
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ -f "meshcentral-data/${CONFIG_FILE}" ]
|
||||
then
|
||||
node meshcentral/meshcentral --configfile ${CONFIG_FILE}
|
||||
else
|
||||
cp config.json.template meshcentral-data/${CONFIG_FILE}
|
||||
if ! [ -z "$USE_MONGODB" ] && [ "$USE_MONGODB" == "true" ]; then
|
||||
sed -i "s/\"_mongoDb\": null/\"mongoDb\": \"mongodb:\/\/$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@mongodb:27017\"/" meshcentral-data/${CONFIG_FILE}
|
||||
if [ -f "meshcentral-data/${CONFIG_FILE}" ]; then
|
||||
node meshcentral/meshcentral --configfile "${CONFIG_FILE}" ${ARGS}
|
||||
else
|
||||
cp config.json.template meshcentral-data/"${CONFIG_FILE}"
|
||||
if [ -n "$USE_MONGODB" ] && [ "$USE_MONGODB" == "true" ]; then
|
||||
if [ -z "$MONGO_URL" ]; then
|
||||
prefix=""
|
||||
if [ -n "$MONGO_INITDB_ROOT_USERNAME" ] && [ -n "$MONGO_INITDB_ROOT_PASSWORD" ]; then
|
||||
prefix="$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@"
|
||||
fi
|
||||
MONGO_URL="${prefix}mongodb:27017"
|
||||
fi
|
||||
sed -i "s/\"cert\": \"myserver.mydomain.com\"/\"cert\": \"$HOSTNAME\"/" 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/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/${CONFIG_FILE}
|
||||
sed -i "s/\"minify\": true/\"minify\": $MINIFY/" meshcentral-data/${CONFIG_FILE}
|
||||
sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/${CONFIG_FILE}
|
||||
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/${CONFIG_FILE}
|
||||
if [ -z "$SESSION_KEY" ]; then
|
||||
SESSION_KEY="$(cat /dev/urandom | tr -dc 'A-Za-z0-9!#$%&()*+,-./:;<=>?@[\]^_`{|}~' | fold -w 32 | head -n 1)";
|
||||
fi
|
||||
sed -i "s/\"_sessionKey\": \"MyReallySecretPassword1\"/\"sessionKey\": \"$SESSION_KEY\"/" meshcentral-data/${CONFIG_FILE}
|
||||
if [ "$REVERSE_PROXY" != "false" ]; then
|
||||
sed -i "s/\"_certUrl\": \"my\.reverse\.proxy\"/\"certUrl\": \"https:\/\/$REVERSE_PROXY:$REVERSE_PROXY_TLS_PORT\"/" meshcentral-data/${CONFIG_FILE}
|
||||
node meshcentral/meshcentral --configfile ${CONFIG_FILE}
|
||||
exit
|
||||
fi
|
||||
node meshcentral/meshcentral --configfile ${CONFIG_FILE} --cert "$HOSTNAME"
|
||||
sed -i "s/\"_mongoDb\": null/\"mongoDb\": \"mongodb:\/\/$MONGO_URL\"/" meshcentral-data/"${CONFIG_FILE}"
|
||||
fi
|
||||
sed -i "s/\"cert\": \"myserver.mydomain.com\"/\"cert\": \"$HOSTNAME\"/" 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/\"localSessionRecording\": false/\"localSessionRecording\": $LOCALSESSIONRECORDING/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"minify\": false/\"minify\": $MINIFY/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"WebRTC\": false/\"WebRTC\": $WEBRTC/" meshcentral-data/"${CONFIG_FILE}"
|
||||
sed -i "s/\"AllowFraming\": false/\"AllowFraming\": $IFRAME/" meshcentral-data/"${CONFIG_FILE}"
|
||||
if [ -z "$SESSION_KEY" ]; then
|
||||
SESSION_KEY="$(cat /dev/urandom | tr -dc 'A-Z0-9' | fold -w 48 | head -n 1)"
|
||||
fi
|
||||
sed -i "s/\"_sessionKey\": \"MyReallySecretPassword1\"/\"sessionKey\": \"$SESSION_KEY\"/" meshcentral-data/"${CONFIG_FILE}"
|
||||
if [ "$REVERSE_PROXY" != "false" ]; then
|
||||
sed -i "s/\"_certUrl\": \"my\.reverse\.proxy\"/\"certUrl\": \"https:\/\/$REVERSE_PROXY:$REVERSE_PROXY_TLS_PORT\"/" meshcentral-data/"${CONFIG_FILE}"
|
||||
node meshcentral/meshcentral --configfile "${CONFIG_FILE}" ${ARGS}
|
||||
exit
|
||||
fi
|
||||
node meshcentral/meshcentral --configfile "${CONFIG_FILE}" --cert "$HOSTNAME" ${ARGS}
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||

|
||||
|
||||
Design and Architecture Guide [as .pdf](https://meshcentral.com/info/docs/MeshCentral2DesignArchitecture.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral Design & Architecture v0.0.4.odt?raw=true)
|
||||
Design and Architecture Guide [as .pdf](https://meshcentral.com/docs/MeshCentral2DesignArchitecture.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral Design & Architecture v0.0.4.odt?raw=true)
|
||||
## Video Walkthru
|
||||
|
||||
<div class="video-wrapper">
|
||||
|
|
@ -108,29 +108,46 @@ Someone would think the server is rather simple when taking a look at the MeshCe
|
|||
### Code files
|
||||
|
||||
```
|
||||
amtevents.js | Used to scan a local network for Intel AMT machines.
|
||||
amtscanner.js | Used to run Intel AMT scripts from MeshCommander.
|
||||
amtscript.js | Used to generate and perform certificate operations.
|
||||
certoperations.js | Various commonly used methods.
|
||||
common.js | Used to access the MongoDB or NeDB database.
|
||||
db.js | Used to modify windows executables.
|
||||
exeHandler.js | Used to insert credentials in an HTTP stream.
|
||||
interceptor.js | Used to obtain and use a Let’s Encrypt certificate.
|
||||
letsencrypt.js | Used to offload RSA sign to other CPU cores.
|
||||
meshaccelerator.js | Used to communicate to agents.
|
||||
meshagent.js | The is the main module, gets the server started.
|
||||
meshcentral.js | Used to send SMTP mails.
|
||||
meshmail.js | Used to relay agent and browser web socket connections.
|
||||
meshrelay.js | MeshCentral server discovery when in LAN mode.
|
||||
meshscanner.js | Used to communicate with browsers.
|
||||
meshuser.js | Used to communicate to Intel® AMT CIRA.
|
||||
mpsserver.js | Used for server-to-server communication.
|
||||
multiserver.js | Performs password hash + salt.
|
||||
pass.js | Used to handle HTTP traffic.
|
||||
redirserver.js | Used to upgrade legacy MeshCentralv1 agents.
|
||||
swarmserver.js | Handles HTTPS traffic.
|
||||
webserver.js | Server background install on Windows.
|
||||
winservice.js | Server background install on Windows.
|
||||
amtevents.js | Used to decode Intel AMT WSMAN events.
|
||||
amtmanager.js | Used to handle Intel AMT/CIRA things.
|
||||
amtprovisioningserver.js | Used to Provision Intel AMT on a Local Network.
|
||||
amtscanner.js | Used to scan a local network for Intel AMT machines.
|
||||
amtscript.js | Used to run Intel AMT scripts from MeshCommander.
|
||||
certoperations.js | Used to generate and perform certificate operations.
|
||||
common.js | Various commonly used methods.
|
||||
crowdsec.js | Used to handle all crowdsec security features
|
||||
db.js | Used to access the MongoDB or NeDB database.
|
||||
exeHandler.js | Used to modify windows executables.
|
||||
firebase.js | Used to handle Google Firebase things.
|
||||
interceptor.js | Used to insert credentials in an HTTP stream.
|
||||
letsencrypt.js | Used to obtain and use a Let’s Encrypt certificate.
|
||||
mcrec.js | Standalone Session Recording Indexer.
|
||||
meshaccelerator.js | Used to offload RSA sign to other CPU cores.
|
||||
meshagent.js | Used to communicate to agents.
|
||||
meshbot.js | Sample bot to connect to meshcentral and preform various tasks
|
||||
meshcentral.js | The is the main module, gets the server started.
|
||||
meshctrl.js | MeshCtrl performs command line actions on a MeshCentral server.
|
||||
meshcore.js | Main Agent Code that runs on your remote devices.
|
||||
meshdesktopmultiplex.js | Used to handle remote desktop multiplexing.
|
||||
meshdevicefile.js | Used to handle file download requests.
|
||||
meshipkvm.js | Used to handle IP KVM integration
|
||||
meshmail.js | Used to send SMTP mails.
|
||||
meshmessaging.js | Used to handle all users messaging methods like 2FA.
|
||||
meshrelay.js | Used to relay agent and browser web socket connections.
|
||||
meshscanner.js | MeshCentral server discovery when in LAN mode.
|
||||
meshsms.js | Used to handle all users sms methods.
|
||||
meshuser.js | Used to communicate with browsers.
|
||||
mpsserver.js | Used to communicate to Intel® AMT CIRA.
|
||||
mqttbroker.js | Used to create/handle an MQTT broker (beta)
|
||||
multiserver.js | Used for server-to-server communication.
|
||||
pass.js | Performs password hash + salt.
|
||||
redirserver.js | Used to handle HTTP traffic.
|
||||
swarmserver.js | Used to upgrade legacy MeshCentralv1 agents.
|
||||
webauthn.js | Handles all WebAuthN things.
|
||||
webrelayserver.js | Used for all HTTP/HTTPS web relaying from agents.
|
||||
webserver.js | Handles HTTPS traffic.
|
||||
winservice.js | Server background install on Windows.
|
||||
|
||||
```
|
||||
|
||||
At a high level, the MeshCentral.js file will get the server started. By default, it will start the webserver.js on port 443, redirectserver.js on port 80 and mpssrver.js on port 4433. The webserver.js file will create a meshuser.js or meshagent.js instance each time a user or agent connects. The other files support various usages, but this is the basic working on the server.
|
||||
|
|
|
|||
BIN
docs/docs/how-to-contribute/images/translation-msg-output.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
56
docs/docs/how-to-contribute/index.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Contribute to MeshCentral
|
||||
|
||||
## Contributing to MeshCentral via GitHub Pull Request
|
||||
|
||||
If you're looking to contribute beyond translations, such as updating documentation or enhancing the software by adding features or fixing bugs, the process involves several key steps:
|
||||
|
||||
1. **Fork the Repository:** Start by forking the [MeshCentral](https://github.com/Ylianst/MeshCentral) repository on GitHub. This creates a copy of the repository under your own GitHub account, allowing you to make changes without affecting the original project.
|
||||
|
||||
2. **Make Your Changes**
|
||||
- In your forked repository, create a new branch to keep your changes organized. This helps in managing different contributions separately.
|
||||
- Make the necessary changes in your repository. This could involve updating documentation files or modifying code to add new features or fix bugs.
|
||||
|
||||
3. **Review Your Changes:** Before submitting your work, carefully review the changes you’ve made. Check the "Files Changed" section on GitHub to ensure that all modifications are intended and correctly implemented.
|
||||
|
||||
4. **Submit a Pull Request**
|
||||
- Once your changes are ready and reviewed, submit a pull request (PR) from your branch to the `master` branch of the main MeshCentral repository.
|
||||
- When creating the pull request, provide a clear and detailed description of what changes have been made and why. This helps maintainers understand the purpose of your contributions.
|
||||
|
||||
5. **Wait for Review:** After submitting your pull request, wait for a project maintainer to review your contribution. Review time can vary depending on the complexity of the changes and the availability of the maintainers.
|
||||
|
||||
6. **Respond to Feedback:** The maintainer may request further modifications or provide feedback on your pull request. Be prepared to make additional changes based on their suggestions to ensure that your contribution meets the project’s standards and requirements.
|
||||
|
||||
7. **Final Steps:** Once your pull request is approved and merged by a maintainer, your contributions will be incorporated into the MeshCentral project. Congratulations, and thank you for helping improve MeshCentral!
|
||||
|
||||
---
|
||||
|
||||
## Contribute to MeshCentral's Multilingual Support
|
||||
|
||||
To make MeshCentral multilingual, your contributions are crucial. Follow these steps to translate the interface into various languages.
|
||||
|
||||
1. **Remove Local Translations:** Delete `translate.json` from your `meshcentral-data` folder. This file contains your local copy of translations, which may become outdated as new features and texts are added.
|
||||
|
||||
2. **Access MeshCentral:** Ensure you are logged into MeshCentral.
|
||||
3. **Open Translation Tool:** Visit `https://YOURMESHCENTRALSERVER.COM/translator.htm` to access the translation interface.
|
||||
4. **Choose a Language:** Select the language you wish to translate from the list provided.
|
||||
|
||||
5. **Translate Text:** Use the search function or scroll through the list to find text segments you want to translate. Utilize the "show no translations only" checkbox to filter untranslated texts.
|
||||
6. **Enter Translations:** For each text segment, enter your translation in the bottom box (not the top one) and click `SET (F1)`.
|
||||
7. **Repeat Translation:** Continue translating by repeating steps 5 and 6 for other texts as desired.
|
||||
|
||||
8. **Save and Apply Translations**
|
||||
- Click `SAVE TO SERVER (F3)` to save your translations to `meshcentral-data/translate.json` locally in your MeshCentral server.
|
||||
- Optionally, click `SAVE TO FILE (F4)` to download the `translate.json` file for offline review or sharing.
|
||||
|
||||
9. **Deploy Translations:** Click `TRANSLATE SERVER` and allow some time for the process to complete (approximately 5-15 minutes depending on server specifications). This command line output will indicate when the translation is complete.
|
||||

|
||||
|
||||
10. **Finalize Changes:** It’s crucial to restart MeshCentral to ensure that the translated files are picked up correctly.
|
||||
11. **Share your translations:** Once a language translation is complete, take the latest `translation.json` and share it by emailing it to the maintainer (Ylianst, `ylianst@gmail.com`) or by submitting it to the MeshCentral GitHub repository via a pull request.
|
||||
|
||||
---
|
||||
|
||||
#### Additional Information:
|
||||
- If you make any changes to `default.handlebars`, run the translate server to propagate these modifications to the language-specific handlebar files located in `node_modules/meshcentral/views/translations`.
|
||||
|
||||
By following these steps, you help MeshCentral support any language you choose, making it more accessible worldwide. By sharing your translations with us, you also help make these languages available to other users, improving the community and extending the software's reach.
|
||||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
MeshCentral is a full computer management web site. With MeshCentral, you can run your own web server to remotely manage and control computers on a local network or anywhere on the internet. Once you get the server started, create device group and download and install an agent on each computer you want to manage. A minute later, the new computer will show up on the web site and you can take control of it. MeshCentral includes full web-based remote desktop, terminal and file management capability.
|
||||
|
||||
To try out this software on the public server, please visit [MeshCentral.com/login](https://meshcentral.com/login). Be mindful that the public MeshCentral server comes with no guaranties, most should setup their own server.
|
||||
|
||||
For more information, [visit MeshCentral.com](https://www.meshcentral.com/).
|
||||
|
||||
## Social Media
|
||||
|
|
@ -14,17 +12,17 @@ For more information, [visit MeshCentral.com](https://www.meshcentral.com/).
|
|||
|
||||
[Reddit](https://www.reddit.com/r/MeshCentral/)
|
||||
|
||||
[Twitter](https://twitter.com/MeshCentral)
|
||||
[BlueSky](https://bsky.app/profile/meshcentral.bsky.social)
|
||||
|
||||
[BlogSpot](https://meshcentral2.blogspot.com/)
|
||||
|
||||
## Documentation
|
||||
|
||||
The [User's Guide](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf) contains information every adminstrator should know including usage, the server configuration file, databases, TLS offloading, Lets Encrypt, IP Filtering, Email setup, embedding, server port aliasing, reverse proxy setup, multi factor authentication, branding & terms of use, HashiCorp Vault support, and SSO.
|
||||
The [User's Guide](meshcentral) contains information every administrator should know including usage, the server configuration file, databases, TLS offloading, Lets Encrypt, IP Filtering, Email setup, embedding, server port aliasing, reverse proxy setup, multi factor authentication, branding & terms of use, HashiCorp Vault support, and SSO.
|
||||
|
||||
The [Installation Guide](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2InstallGuide.pdf) has detailed instructions for installing the MeshCentral Server on Windows 8.1, Windows 10, Windows 2012 R2, Amazon Linux 2, Raspberry Pi, Microsoft Azure, Google Cloud, Ubuntu 18, Ubuntu 16 and OpenBSD.
|
||||
The [Installation Guide](install/install2.md) has detailed instructions for installing the MeshCentral Server on Windows 8.1, Windows 10, Windows 2012 R2, Amazon Linux 2, Raspberry Pi, Microsoft Azure, Google Cloud, Ubuntu 18, Ubuntu 16 and OpenBSD.
|
||||
|
||||
The [Design and Architecture Guide](https://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2DesignArchitecture.pdf) is a short document that includes information on the design overview, dependencies, source code descriptions of each file, certificates, TLS security, the agent to server handshake, browser to agent relay and WebRTC and the messenger service.
|
||||
The [Design and Architecture Guide](design) is a short document that includes information on the design overview, dependencies, source code descriptions of each file, certificates, TLS security, the agent to server handshake, browser to agent relay and WebRTC and the messenger service.
|
||||
|
||||
## Video Tutorials
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ That's it. MeshCentral will set itself up and start managing computers on your l
|
|||
|
||||
For Windows users, you can download the MeshCentral Installer that will automate installation of NodeJS and provide basic configuration of the server. This option is not recommended for advanced users.
|
||||
|
||||
[Win32 MeshCentral Installer](https://meshcentral.com/info/tools/MeshCentralInstaller.exe)
|
||||
[Win32 MeshCentral Installer](https://meshcentral.com/tools/MeshCentralInstaller.exe)
|
||||
|
||||
By default, MeshCentral will use NeDB as this is the built-in database. For more advanced users, it's recommended to switch to using MongoDB. MeshCentral can be installed on a very small server. A [Raspberry Pi](https://www.raspberrypi.org/) or [AWS t3.nano running Amazon Linux 2 instance](https://aws.amazon.com/ec2/pricing/on-demand/) for 5$ a month will do just fine for managing up to a few hundred devices.
|
||||
|
||||
|
|
@ -23,10 +23,10 @@ You can run the MeshCentral Server with --help to get options for background ins
|
|||
|
||||
Once you get MeshCentral installed, the first user account that is created will be the server administrator. So, don't delay and navigate to the login page and create a new account. You can then start using your server right away. A lot of the fun with MeshCentral is the 100's of configuration options that are available in the config.json file. You can put your own branding on the web pages, setup a SMTP email server, SMS services and much more.
|
||||
|
||||
You can look [here for simple config.json](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config.json), [here for a more advanced configuration](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config-advanced.json) and [here for all possible configuration options](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json). You can also take a look at the [MeshCentral User's Guide](https://meshcentral.com/info/docs/MeshCentral2InstallGuide.pdf) and [tutorial videos](https://meshcentral.com/info/tutorials.html) for additional help.
|
||||
You can look [here for simple config.json](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config.json), [here for a more advanced configuration](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/sample-config-advanced.json) and [here for all possible configuration options](https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json). You can also take a look at the [tutorial videos](https://www.youtube.com/@MeshCentral/videos) for additional help.
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/LSiWuu71k_U" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,45 @@
|
|||
|
||||
This guide is specifically intended to help users install MeshCentral from start to finish. Once installed, you can take a look at the MeshCentral user’s guide for information on how to configure MeshCentral for your specific use. In this document, we will look at installing MeshCentral on AWS Linux, Raspberry Pi and Ubuntu.
|
||||
|
||||
## Docker
|
||||
|
||||
<https://github.com/Ylianst/MeshCentral/pkgs/container/meshcentral>
|
||||
|
||||
```
|
||||
docker pull ghcr.io/ylianst/meshcentral:master
|
||||
```
|
||||
|
||||
!!!warning
|
||||
Do not use the built in mesh update function. Update docker the docker way.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```
|
||||
version: '3'
|
||||
services:
|
||||
meshcentral:
|
||||
restart: unless-stopped # always restart the container unless you stop it
|
||||
image: ghcr.io/ylianst/meshcentral:1.1.27 # 1.1.27 is a version number OR use master for the master branch of bug fixes
|
||||
ports:
|
||||
- 80:80 # HTTP
|
||||
- 443:443 # HTTPS
|
||||
- 4433:4433 # AMT (Optional)
|
||||
volumes:
|
||||
- data:/opt/meshcentral/meshcentral-data # config.json and other important files live here
|
||||
- user_files:/opt/meshcentral/meshcentral-files # where file uploads for users live
|
||||
- backup:/opt/meshcentral/meshcentral-backups # location for the meshcentral backups - this should be mounted to an external storage
|
||||
- web:/opt/meshcentral/meshcentral-web # location for site customization files
|
||||
volumes:
|
||||
data:
|
||||
driver: local
|
||||
user_files:
|
||||
driver: local
|
||||
backup:
|
||||
driver: local
|
||||
web:
|
||||
driver: local
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
For some who want to skip this document entirely, there are quick install scripts that will get a MeshCentral2 instance up and running on Linux in a few minutes. These scripts will pretty much do what this document explains very rapidly. Right now, there are two such scripts available:
|
||||
|
|
@ -13,7 +52,7 @@ For some who want to skip this document entirely, there are quick install script
|
|||
For Amazon EC2 users, that want to manage 100 devices or less. Launch a t3.nano or t3.micro EC2 instance with Amazon Linux 2 with TCP ports 22 (SSH), 80 (HTTP), 443 (HTTPS) and 4433 (CIRA) open. Then login as `ec2-user` and enter the following commands:
|
||||
|
||||
```
|
||||
wget http://info.meshcentral.com/scripts/mc-aws-linux2.sh
|
||||
wget https://meshcentral.com/scripts/mc-aws-linux2.sh
|
||||
chmod 755 mc-aws-linux2.sh
|
||||
./mc-aws-linux2.sh
|
||||
```
|
||||
|
|
@ -21,7 +60,7 @@ chmod 755 mc-aws-linux2.sh
|
|||
This will download the fast install script and once run, will install nodejs, meshcentral, setup systemd and start the server. For a larger instance like a t3.small, t3.medium or larger you can run the following that does the same but also installs MongoDB.
|
||||
|
||||
```
|
||||
wget http://info.meshcentral.com/scripts/mc-aws-linux2-mongo.sh
|
||||
wget https://meshcentral.com/scripts/mc-aws-linux2-mongo.sh
|
||||
chmod 755 mc-aws-linux2-mongo.sh
|
||||
./mc-aws-linux2-mongo.sh
|
||||
```
|
||||
|
|
@ -33,13 +72,19 @@ After these scripts are run, try accessing the server using a browser. MeshCentr
|
|||
For 100 devices or less, launch an instance of Ubuntu 18.04 using a small B1s instance. Set the username to `default` in all lower case and open ports 22, 80, 443 and 3389 using the basic network profile. Then start the instance and run the following lines.
|
||||
|
||||
```
|
||||
wget http://info.meshcentral.com/scripts/mc-azure-ubuntu1804.sh
|
||||
wget https://meshcentral.com/scripts/mc-azure-ubuntu1804.sh
|
||||
chmod 755 mc-azure-ubuntu1804.sh
|
||||
./mc-azure-ubuntu1804.sh
|
||||
```
|
||||
|
||||
In this situation, port 3389 will be used to receive Intel AMT CIRA connections instead of port 4433. After these scripts are run, try accessing the server using a browser. MeshCentral will take a minute or two to create certificates after that, the server will be up. The first account to be created will be the site administrator – so don’t delay and create an account right away. Once running, move on to the MeshCentral’s user’s guide to configure your new server.
|
||||
|
||||
### Elestio
|
||||
|
||||
You can deploy MeshCentral on Elestio using one-click deployment. Elestio handles version updates, maintenance, securtiy, backups, etc. Additionally, Elestio supports MeshCentral by providing revenue share so go ahead and click below to deploy and start using.
|
||||
|
||||
[](https://elest.io/open-source/meshcentral)
|
||||
|
||||
## Server Security - Adding Crowdsec
|
||||
|
||||
MeshCentral has built-in support for a CrowdSec bouncer. This allows MeshCentral to get threat signals from the community and block or CAPTCHA requests coming from known bad IP addresses.
|
||||
|
|
@ -884,7 +929,7 @@ The last line will run MeshCentral manually and allow it to install any missing
|
|||
|
||||
```
|
||||
sudo chown -R meshcentral:meshcentral /opt/meshcentral
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-*
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-*
|
||||
```
|
||||
|
||||
To make this work, you will need to make MeshCentral work with MongoDB because the /meshcentral-data folder will be read-only. In addition, MeshCentral will not be able to update itself since the account does not have write access to the /node_modules files, so the update will have to be manual. First used systemctl to stop the MeshCentral server process, than use this:
|
||||
|
|
@ -901,7 +946,7 @@ This will perform the update to the latest server on NPM and re-set the permissi
|
|||
MeshCentral allows users to upload and download files stores in the server’s `meshcentral-files` folder. In an increased security setup, we still want the server to be able to read and write files to this folder and we can allow this with:
|
||||
|
||||
```
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-files
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-files
|
||||
```
|
||||
|
||||
If you plan on using the increased security installation along with MeshCentral built-in Let’s Encrypt support you will need to type the following commands to make the `letsencrypt` folder in `meshcentral-data` writable.
|
||||
|
|
@ -909,11 +954,19 @@ If you plan on using the increased security installation along with MeshCentral
|
|||
```
|
||||
sudo mkdir /opt/meshcentral/meshcentral-data
|
||||
sudo mkdir /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
sudo chmod 755 –R /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
sudo chmod -R 755 /opt/meshcentral/meshcentral-data/letsencrypt
|
||||
```
|
||||
|
||||
This will allow the server to get and periodically update its Let’s Encrypt certificate. If this is not done, the server will generate an `ACCES: permission denied` exception.
|
||||
|
||||
### Restore backup in Ubuntu
|
||||
|
||||
- Stop Meshcentral service `sudo systemctl stop meshcentral.service`
|
||||
- In your old server, get your backup : meshcentral-data folder, and mongodump-xxxx.archive
|
||||
- In the new server, replace the actual meshcentral-data with your backup (it will handle your LestEncrypt cert also)
|
||||
- Restore mongodb : mongorestore --archive=mongodump-xxxx.archive
|
||||
- Restart meshcentral.service `sudo systemctl start meshcentral.service`
|
||||
|
||||
## Microsoft Azure
|
||||
|
||||
In this section, we will look installing MeshCentral on Microsoft Azure. Microsoft Azure offers many operating system options and we will be selecting `Ubuntu Server` as our choice. From the Azure portal, we select `Virtual machines` on the left and `Add`.
|
||||
|
|
|
|||
|
|
@ -195,6 +195,12 @@ Once Intel AMT is in a situation where ACM activation can occur, the activation
|
|||
|
||||
The best way to test this feature is to create an “Intel AMT only” device group and run the MeshCMD command on the remote system to perform activation. If there is a problem, this process should clearly display why ACM activation fails.
|
||||
|
||||
!!!note
|
||||
Activation over wifi has some additional issues.<br>
|
||||
First you need to add your WiFi access point to that wifi configuration to allow CSME to take over WiFi when OS is not functioning. Then it should work.<br>
|
||||
Please also make sure you install Intel WiFi driver and Intel LMS package. It should work. You can detach the ethernet and then try connecting to that device using the IP address acquired by WiFi interface.
|
||||
See [Open AMT Cloud Toolkit](https://www.intel.com/content/www/us/en/developer/topic-technology/edge-5g/tools/open-amt-cloud-toolkit.html) project - a close relative to this project. It has an AMT activation component and newer remote provisioning client can activate locally and also can manage Wi-Fi profile.
|
||||
|
||||
## Intel AMT MEI and LMS
|
||||
|
||||
Intel Active Management Technology (Intel AMT) can communicate to the local platform using the Management Engine Interface (MEI). We show how your can use that to get Intel AMT information. For more advanced usages, you need to connect using TCP and TLS which requires Intel Local Manageability Service (LMS). We show how MeshCentral's Mesh Agent and MeshCMD have a small version of LMS built-in and how it works
|
||||
|
|
|
|||
|
|
@ -352,3 +352,11 @@ wpfhwacceleration (ON|OFF|STATUS)
|
|||
```
|
||||
zip (output file name), input1 [, input n]
|
||||
```
|
||||
|
||||
## Agent msh options
|
||||
|
||||
You can find a full list of options for the agent [here](https://github.com/Ylianst/MeshAgent?tab=readme-ov-file#msh-format)
|
||||
|
||||
`skipmaccheck=1`: Will not regenerate the agents nodeid and cause duplication of the agent when the MAC address changes.
|
||||
|
||||
You can add options to your .msh on agent install with [this](https://github.com/Ylianst/MeshCentral/blob/15ff7d12a1e4e5d78936b473ea207b7e02b8ff26/meshcentral-config-schema.json#L2504)
|
||||
|
|
|
|||
|
|
@ -14,16 +14,28 @@
|
|||
|
||||

|
||||
|
||||
## Agent Invitation Link
|
||||
|
||||
For web page customization:
|
||||
## Agent Invitation
|
||||
Click on the 'Invite' button next to the device group name to access it.
|
||||
### Link Invitation
|
||||
For link invitation web page customization:
|
||||
|
||||
1. Alongside `meshcentral-data` create a folder called `meshcentral-web`
|
||||
2. Create a `views` folder in it and copy the file `node_modules/meshcentral/views/invite.handlebars` into it.
|
||||
3. That copy will be served instead of the default one, you can customize as you want.
|
||||
3. That copy will be served instead of the default one, so you can customize it as you want.
|
||||
|
||||

|
||||
|
||||
### Email Invitation
|
||||
This option will show up if you have an SMTP email server set up with MeshCentral.
|
||||
|
||||
For invitation email customization:
|
||||
|
||||
1. Alongside `meshcentral-data` create a folder called `meshcentral-web`
|
||||
2. Create an `emails` folder in it and copy the files `node_modules/meshcentral/emails/mesh-invite.txt` and `node_modules/meshcentral/emails/mesh-invite.html` into it.
|
||||
3. These copies will be used instead of the default ones, so you can customize them as you want.
|
||||
|
||||

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

|
||||
|
||||
Once done, edit the config.json file and set one or all of the following values:
|
||||
|
||||
```json
|
||||
"domains": {
|
||||
"": {
|
||||
"Title": "",
|
||||
"Title2": "",
|
||||
"TitlePicture": "title-sample.png",
|
||||
"loginPicture": "logintitle-sample.png",
|
||||
"welcomeText": "This is sample text",
|
||||
"welcomePicture": "mainwelcome-04.jpg",
|
||||
"welcomePictureFullScreen": true,
|
||||
"siteStyle": "1",
|
||||
"nightMode": "1",
|
||||
"meshMessengerTitle": "Mesh Chat",
|
||||
"meshMessengerPicture": "chatimage.png",
|
||||
"footer": "This is a HTML string displayed at the bottom of the web page when a user is logged in.",
|
||||
"loginfooter": "This is a HTML string displayed at the bottom of the web page when a user is not logged in."
|
||||
},
|
||||
```
|
||||
|
||||
This will set the title and sub-title text to empty and set the background image to the new title picture file. You can now restart the server and take a look at the web page. Both the desktop and mobile sites will change.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The title image must a PNG image of size 450 x 66.
|
||||
|
||||
You can also customize the server icon in the “My Server” tab. By default, it’s a picture of a desktop with a padlock.
|
||||
|
||||

|
||||
|
||||
If, for example, MeshCentral is running on a Raspberry Pi. You may want to put a different picture at this location. Just put a “server.jpg” file that is 200 x 200 pixels in the “meshcentral-data” folder. The time MeshCentral page is loaded, you will see the new image.
|
||||
|
||||

|
||||
|
||||
This is great to personalize the look of the server within the web site.
|
||||
|
||||
### Customizing Web Icons
|
||||
MeshCentral lets you change the icons for different devices shown in the Web User Interface. To do this the proper way, you should make a new folder called `meshcentral-web` in the main directory, where you find other folders like `meshcentral-data`, `meshcentral-backup`, `meshcentral-files`, and `node-modules`. Inside `meshcentral-web`, make another folder named `public` and copy the entire `node_modules/meshcentral/public/images` folder into this new `meshcentral-web/public` folder and then edit the files in `meshcentral-web/public/images/`. This step is suggested because if MeshCentral updates, it might delete any changes in `node_modules`. But, changes in `meshcentral-web` will stay safe, and MeshCentral will use these files instead of the originals in `node_modules`.
|
||||
|
||||
To update device icons, you need to edit these files: `meshcentral-web/public/images/webp/iconsXX.webp` (`icons16.webp`, `icons32.webp`, `icons50.webp`, `icons100.webp`), and `meshcentral-web/public/images/iconsXX.png` (`icons16.png`, `icons32.png`, `icons50.png`, `icons64.png`, `icons100.png`) and the corresponding `meshcentral-web/public/images/icons256-X-1.png`. Make sure to keep the resolution of these files as it is.
|
||||
|
||||
By following these steps, you can customize any icon in MeshCentral. Just find and change the corresponding image files in the `meshcentral-web/public/images` folder. Similarly, you can also move other folders from `node_modules/meshcentral` to `meshcentral-web` while keeping the original folder structure. This allows you to modify other parts of MeshCentral too, like the `.handlebars` templates for the web interface. Simply copy files from `node_modules/meshcentral/views` to `meshcentral-web/views` and make your changes in `meshcentral-web`. This lets you match MeshCentral's look to your company's brand or your own style.
|
||||

|
||||
|
||||
### Customizing Agent Invitation
|
||||
Agents can be invited by public link or via email. [Click Here](assistant.md#agent-invitation) to see details.
|
||||
|
||||
## Agent Branding
|
||||
|
||||
You can customize the Agent to add your own logo, change the title bar, install text, the service name, or even colors!
|
||||
|
||||
!!!note
|
||||
The Customization must be done FIRST and BEFORE you deploy your agents! Once the agents have been deployed, any customization made afterwards, will not sync! This is because the setup files are customized on the fly, then when you install the agents, the exe and .msh file with the customizations in are copied over to the required folder, so you will need to reinstall the agent for agent customizations to take effect.
|
||||
|
||||

|
||||
|
||||
```json
|
||||
"domains": {
|
||||
"": {
|
||||
"agentCustomization": {
|
||||
"displayName": "MeshCentral Agent",
|
||||
"description": "Mesh Agent background service",
|
||||
"companyName": "Mesh Agent Company",
|
||||
"serviceName": "Mesh Agent Service",
|
||||
"installText": "Text string to show in the agent installation dialog box",
|
||||
"image": "mylogo.png",
|
||||
"fileName": "meshagent",
|
||||
"foregroundColor": "#FFA500",
|
||||
"backgroundColor": "#EE82EE"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Terms of use
|
||||
|
||||
You can change the terms of use of the web site by adding a “terms.txt” file in the “meshcentral-data” folder. The file can include HTML markup. Once set, the server does not need to be restarted, the updated terms.txt file will get used the next time it’s requested.
|
||||
|
||||
For example, placing this in “terms.txt”
|
||||
|
||||
```
|
||||
<br />
|
||||
This is a <b>test file</b>.
|
||||
```
|
||||
|
||||
Will show this on the terms of use web page.
|
||||
|
|
@ -23,7 +23,7 @@ Make sure you understand how MeshCentral works with your browser using chrome de
|
|||
|
||||
### Understanding node and paths
|
||||
|
||||
Note that when running MeshCentral, you should always run like from the path that is parent to node_modules, so you do this:
|
||||
Note that when running MeshCentral, you should always run from the path that is parent to node_modules, so you do this:
|
||||
|
||||
```
|
||||
cd C:\Program Files\Open Source\MeshCentral
|
||||
|
|
@ -39,7 +39,7 @@ node meshcentral
|
|||
|
||||
The problem with the second command is that NPM may install missing modules in the incorrect location.
|
||||
|
||||
Also, in general I recommend not using the MeshCentral MSI Installer and just install manually unless you are very much scared of the command prompt. Anyone that knows about bit about the shell should install MeshCentral like this:
|
||||
Also, in general I recommend not using the MeshCentral MSI Installer and just install manually unless you are very scared of the command prompt. Anyone that knows a bit about the shell should install MeshCentral like this:
|
||||
|
||||
```
|
||||
mkdir c:\meshcentral
|
||||
|
|
@ -50,11 +50,11 @@ node node_modules\meshcentral
|
|||
node node_modules\meshcentral --install
|
||||
```
|
||||
|
||||
This way, just have a lot more control over what is going on. Just my opinion, the MSI installer basically does the same thing and installs NodeJS for you.
|
||||
This way, you have a lot more control over what is going on. In my opinion, the MSI installer basically does the same thing and installs NodeJS for you.
|
||||
|
||||
### Unable to update server
|
||||
|
||||
Generally the problem is that MeshCentral can't find the npm tool and so, can't run it to see if there is a new version. You can fix this by setting the path to npm in the config.json like this:
|
||||
Generally the problem is that MeshCentral can't find the npm tool and therefore, can't run it to see if there is a new version. You can fix this by setting the path to npm in the config.json like this:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -86,14 +86,59 @@ node node_modules/meshcentral --stop
|
|||
|
||||
### Port Troubleshooting on server
|
||||
|
||||
If you're getting a `port 4433 is not available` error, this is because someone else is using this port, very likely another instance of MeshCentral. If your MeshCentral server is bound to ports 81/444 MeshCentral could not get port 80/443 and got the next available ones.
|
||||
If you're getting a `port 4433 is not available` error, this is because another process is using this port, very likely another instance of MeshCentral. If your MeshCentral server is bound to ports 81/444 MeshCentral could not get port 80/443 and got the next available ones.
|
||||
|
||||
In general the problem is that you are running two MeshCentral instances at the same time. Probably one as a background Windows Service and one in the command line. Which ever instance can grab port 4433 will have a running MPS and CIRA should work, but the second instance will not have port 4433 and CIRA will not work.
|
||||
|
||||
### Running Meshcentral server in debug mode
|
||||
|
||||
Debug more will cause MeshCentral to output a lot of debug messages to the console. To display all debug messages, run MeshCentral like this:
|
||||
|
||||
```bash
|
||||
node node_modules/meshcentral --debug
|
||||
```
|
||||
|
||||
A more practical way to run the debug command it to specify what messages you want printed out using a comma seperated list, for example:
|
||||
|
||||
```bash
|
||||
node node_modules/meshcentral --debug web,amt,mps
|
||||
```
|
||||
|
||||
Here is the list of all debug options:
|
||||
|
||||
```
|
||||
cookie - Cookie encoder
|
||||
dispatch - Message Dispatcher
|
||||
main - Main Server Messages
|
||||
peer - MeshCentral Server Peering
|
||||
agent - MeshAgent traffic
|
||||
agentupdate - MeshAgent update
|
||||
cert - Server Certificate
|
||||
db - Server Database
|
||||
email - Email/SMS/Push Traffic
|
||||
web - Web Server
|
||||
webrequest - Web Server Requests
|
||||
relay - Web Socket Relay
|
||||
httpheaders - Web Server HTTP Headers
|
||||
authlog - User Authentication Log
|
||||
amt - Intel AMT
|
||||
webrelay - Connection Relay
|
||||
mps - CIRA Server
|
||||
mpscmd - CIRA Server Commands
|
||||
```
|
||||
|
||||
You can also specify the `debug` option in the config.json file in the `settings` section. For example:
|
||||
|
||||
```
|
||||
"settings": {
|
||||
"debug": "web,amt,mps"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Enabling trace in your browser Dev Tools
|
||||
|
||||
`Trace=1` as a parameter in chrome dev tools for debugging
|
||||
|
||||
You can enable browser console tracing by adding `trace=1` as a parameter to the URL of the MeshCentral main web page. For example `https://myserver.com/?trace=1`. Once present, open the browser's console window to see all web client tracing messages.
|
||||
|
||||
To log all database queries, change log_statement in /etc/postgresql/13/main/postgresql.conf
|
||||
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ Chat
|
|||
|
||||
Legend
|
||||
|
||||
* **_Black color_**: Device is powered on (Intel AMT & agents)
|
||||
* **_Purple color_**: Device is in sleep state (Intel AMT agents only)
|
||||
* **_Teal color_**: Device is connected through AMT/CIRA, but not powered on (Intel AMT agents only)
|
||||
* **_Grey color_**: Device is powered off (Intel AMT & agents)
|
||||
* **_Black color_**: Device is Powered On on (Intel AMT & agents)
|
||||
* **_Purple color_**: Device is in sleep state such as Hibernating (Intel AMT agents only)
|
||||
* **_Teal color_**: Device is connected through AMT/CIRA, but the Power State is UNKNOWN (Intel AMT agents only)
|
||||
* **_DarkGreen color_**: Device is connected through AMT/CIRA and is in Soft-Off Power State (Intel AMT agents only)
|
||||
* **_Grey color_**: Device is Powered Off/Not Connected To MeshCentral (Intel AMT & agents)
|
||||
|
||||
### Text Links
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ No, you haven't.
|
|||
|
||||
6. Here are some examples of what that looks like.
|
||||
|
||||
# Can't login on server after first setup
|
||||
## Can't login on server after first setup
|
||||
|
||||
You're sure you're typing in everything right, giving it 2FA code and can't login
|
||||
|
||||
|
|
@ -44,9 +44,40 @@ You're sure you're typing in everything right, giving it 2FA code and can't logi
|
|||
|
||||

|
||||
|
||||
# Branding and Customization
|
||||
## Branding and Customization
|
||||
|
||||
You can brand and customize MeshCentral almost as much as you like without delving into the code, a few changes in the config.json file and uploading images can change the way your system looks. Read more [here](https://ylianst.github.io/MeshCentral/meshcentral/#branding-terms-of-use)
|
||||
|
||||
!!!note
|
||||
You will need to reinstall the agent for agent customizations to take effect.
|
||||
|
||||
## Mac Clients
|
||||
|
||||
You have to manually grant Mac permissions outside of the agent install process due to the MacOS security system under Security & Privacy > Privacy
|
||||
|
||||
To see the screen (otherwise you just see the menu bar, and otherwise blank)
|
||||
|
||||

|
||||
|
||||
To be able to transfer files
|
||||
|
||||

|
||||
|
||||
To be able to control keyboard and mouse
|
||||
|
||||

|
||||
|
||||
## I'm using CloudFlare and I'm getting a black screen but the mouse moves?
|
||||
|
||||
If you are using CloudFlare for your DNS hosting and your remote screen is black, DONT PANIC!
|
||||
|
||||
Unfortunately, MeshCentral doesn't always work with CloudFlare's Proxy DNS Mode.
|
||||
|
||||
The fix is to simply set the 'Proxy Status' to OFF inside your DNS A Record, within the CloudFlare control panel.
|
||||
|
||||
Simply follow the steps [here](https://developers.cloudflare.com/fundamentals/setup/manage-domains/pause-cloudflare/#disable-proxy-on-dns-records)
|
||||
|
||||
Once done, open your firewall for the `port` and `agentPort` ports of where your meshcentral is hosted, then restart your MeshCentral Server
|
||||
|
||||
There is currently a PINNED GitHub issue about this [here](https://github.com/Ylianst/MeshCentral/issues/5302)
|
||||
|
||||
|
|
|
|||
BIN
docs/docs/meshcentral/images/2023-02-24vscodejsonediting.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
docs/docs/meshcentral/images/2023-11-29-12-57-15.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/docs/meshcentral/images/2023-11-29-12-58-05.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/docs/meshcentral/images/2023-11-29-12-58-36.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/docs/meshcentral/images/2023-11-29_140845 - mesh json1.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
docs/docs/meshcentral/images/2023-11-29_140845 - mesh json2.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
docs/docs/meshcentral/images/In-production.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/docs/meshcentral/images/OAuth-Internal-External.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/docs/meshcentral/images/custom-web-icons.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
docs/docs/meshcentral/images/email-invitation.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
docs/docs/meshcentral/images/gc-newproject.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthconsent.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthconsent2.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthcredentials.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
docs/docs/meshcentral/images/gc-oauthscopes.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/docs/meshcentral/images/gc-playground.webp
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
# Meshcentral2 Guide
|
||||
# MeshCentral Guide
|
||||
|
||||
[MeshCentral2 Guide](https://meshcentral.com/info/docs/MeshCentral2UserGuide.pdf)
|
||||
[MeshCentral Guide](https://meshcentral.com/docs/MeshCentral2UserGuide.pdf)
|
||||
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/info/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)
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ As indicated before, the settings section of the config.json is equivalent to pa
|
|||
| RedirPort | This is the port for redirecting traffic in the web server. When the server is configured with HTTPS, users that uses HTTP will be redirected to HTTPS. Port 80 is the default port. So, redirection will happen from port 80 to port 443. |
|
||||
| MpsPort | Port for Intel" AMT Management Presence Server to receive Intel" AMT CIRA (Client Initiated Remote Access) connections. The default is port 4433. This port is disabled in LAN mode. If user don"t plan on using Intel" AMT for management, this port can be left as-is. |
|
||||
| TLSOffload | By default this option is set to "false". If set to "true", server will run both web port and the Intel AMT MPS port without TLS with the assumption that a TLS offloading is taking care of this task. For further details, see the "TLS Offloading" section. This option can also be set to the IP address of the reverse-proxy in order to indicate to MeshCental to only trust HTTP X-Forwarded headers coming from this IP address. See the "Reverse-Proxy Setup" section for an example. |
|
||||
| SelfUpdate | When set to "true" the server will check for a new version and attempt to self-update automatically a bit after midnight local time every day. For this to work, the server needs to run with sufficient permissions to overwrite its own files. If you run the server with more secure, restricted privileges, this option should not be used. If set to a specific version such as "0.2.7-g" when the server will immediately update to the specified version on startup if it"s not already at this version. |
|
||||
| SelfUpdate | When set to "true" the server will check for a new version and attempt to self-update automatically a bit after midnight local time every day. If set to a specific version such as "1.1.21" the server will immediately update to the specified version on startup if it's not already at this version. |
|
||||
| SessionKey | This is the encryption key used to secure the user"s login session. It will encrypt the browser cookie. By default, this value is randomly generated each time the server starts. If many servers are used with a load balancer, all servers should use the same session key. In addition, one can set this key so that when the server restarts, users do not need to re-login to the server. |
|
||||
| Minify | Default value is 0, when set to 1 the server will serve "minified" web pages, that is, web pages that have all comments, white spaces and other unused characters removed. This reduces the data size of the web pages by about half and reduced the number requests made by the browser. The source code of the web page will not be easily readable, adding "&nominify=1" at the end of the URL will override this option. |
|
||||
| User | Specify a username that browsers will be automatically logged in as. Useful to skip the login page and password prompts. Used heavily during development of MeshCentral. |
|
||||
|
|
@ -305,6 +305,120 @@ When the MongoDB is setup for the first time, a unique identifier is generated a
|
|||
|
||||
Once peered, all of the servers should act like one single host, no matter which server the user(s) are connected to.
|
||||
|
||||
## Email Setup
|
||||
|
||||
We highly recommend the use of an email server (SMTP) because we could allow MeshCentral to verify user account’s email address by sending a confirmation request to the user to complete the account registration and for password recovery, should a user forget account password as illustrated below
|
||||
|
||||
A verification email is sent when a new account is created or if the user requests it in the “My Account” tab.
|
||||
|
||||

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

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

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

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
## Embedding MeshCentral
|
||||
|
||||
|
|
@ -827,6 +901,12 @@ In this example, we will:
|
|||
- MeshCentral will read the NGINX web certificate so agents will perform correct server authentication.
|
||||
- NGINX will be setup with long timeouts, because agents have long standard web socket connections.
|
||||
|
||||
!!!note
|
||||
With SELinux, NGINX reverse proxy requires 'setsebool -P httpd_can_network_relay 1'
|
||||
Caution: httpd_can_network_relay only allows certain ports
|
||||
Confirm you are using ports from this subset in MeshCentral
|
||||
If you want to use a different port then you will need to add it to http_port_t
|
||||
|
||||
Let’s get started by configuring MeshCentral with the following values in config.json:
|
||||
|
||||
```json
|
||||
|
|
@ -1014,8 +1094,7 @@ First we will start with the MeshCentral configuration, here is a minimal config
|
|||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"certUrl": "https://127.0.0.1:443/",
|
||||
"agentConfig": [ "webSocketMaskOverride=1" ],
|
||||
"certUrl": "https://127.0.0.1:443/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1199,132 +1278,7 @@ And taking authentication to the next step is removing the login page entirely.
|
|||
<iframe width="320" height="180" src="https://www.youtube.com/embed/-WKY8Wy0Huk" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
## Branding & Terms of use
|
||||
|
||||
Whitelabeling your MeshCentral installation to personalize it to your companies brand, as well as having your own terms of use is one of the first things many people do after installation.
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/xUZ1w9RSKpQ" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
### Branding
|
||||
|
||||
You can put you own logo on the top of the web page. To get started, get the file “logoback.png” from the folder “node_modules/meshcentral/public/images” and copy it to your “meshcentral-data” folder. In this example, we will change the name of the file “logoback.png” to “title-mycompany.png”. Then use any image editor to change the image and place your logo.
|
||||
|
||||

|
||||
|
||||
Once done, edit the config.json file and set one or all of the following values:
|
||||
|
||||
```json
|
||||
"domains": {
|
||||
"": {
|
||||
"Title": "",
|
||||
"Title2": "",
|
||||
"TitlePicture": "title-sample.png",
|
||||
"loginPicture": "logintitle-sample.png",
|
||||
"welcomeText": "This is sample text",
|
||||
"welcomePicture": "mainwelcome-04.jpg",
|
||||
"welcomePictureFullScreen": true,
|
||||
"siteStyle": "1",
|
||||
"nightMode": "1",
|
||||
"meshMessengerTitle": "Mesh Chat",
|
||||
"meshMessengerPicture": "chatimage.png",
|
||||
"footer": "This is a HTML string displayed at the bottom of the web page when a user is logged in.",
|
||||
"loginfooter": "This is a HTML string displayed at the bottom of the web page when a user is not logged in."
|
||||
},
|
||||
```
|
||||
|
||||
This will set the title and sub-title text to empty and set the background image to the new title picture file. You can now restart the serve and take a look at the web page. Both the desktop and mobile sites will change.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The title image must a PNG image of size 450 x 66.
|
||||
|
||||
You can also customize the server icon in the “My Server” tab. By default, it’s a picture of a desktop with a padlock.
|
||||
|
||||

|
||||
|
||||
If, for example, MeshCentral is running on a Raspberry Pi. You may want to put a different picture at this location. Just put a “server.jpg” file that is 200 x 200 pixels in the “meshcentral-data” folder. The time MeshCentral page is loaded, you will see the new image.
|
||||
|
||||

|
||||
|
||||
This is great to personalize the look of the server within the web site.
|
||||
|
||||
### Agent Branding
|
||||
|
||||
You can also customize the Agent to add your own logo.
|
||||
|
||||

|
||||
|
||||
```json
|
||||
"agentCustomization": {
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"default": "MeshCentral Agent",
|
||||
"description": "The name of the agent as displayed to the user."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"default": "Mesh Agent background service",
|
||||
"description": "The description of the agent as displayed to the user."
|
||||
},
|
||||
"companyName": {
|
||||
"type": "string",
|
||||
"default": "Mesh Agent",
|
||||
"description": "This will be used as the path to install the agent, by default this is 'Mesh Agent' in Windows and 'meshagent' in other OS's."
|
||||
},
|
||||
"serviceName": {
|
||||
"type": "string",
|
||||
"default": "Mesh Agent",
|
||||
"description": "The name of the background service, by default this is 'Mesh Agent' in Windows and 'meshagent' in other OS's but should be set to an all lower case, no space string."
|
||||
},
|
||||
"installText": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Text string to show in the agent installation dialog box."
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "The filename of a image file in .png format located in meshcentral-data to display in the MeshCentral Agent installation dialog, image should be square and from 64x64 to 200x200."
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string",
|
||||
"default": "meshagent",
|
||||
"description": "The agent filename."
|
||||
},
|
||||
"foregroundColor": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Foreground text color, valid values are RBG in format 0,0,0 to 255,255,255 or format #000000 to #FFFFFF."
|
||||
},
|
||||
"backgroundColor": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Background color, valid values are RBG in format 0,0,0 to 255,255,255 or format #000000 to #FFFFFF."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
!!!note
|
||||
You will need to reinstall the agent for agent customizations to take effect.
|
||||
|
||||
### Terms of use
|
||||
|
||||
You can change the terms of use of the web site by adding a “terms.txt” file in the “meshcentral-data” folder. The file can include HTML markup. Once set, the server does not need to be restarted, the updated terms.txt file will get used the next time it’s requested.
|
||||
|
||||
For example, placing this in “terms.txt”
|
||||
|
||||
```
|
||||
<br />
|
||||
This is a <b>test file</b>.
|
||||
```
|
||||
|
||||
Will show this on the terms of use web page.
|
||||
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
|
||||
|
||||
|
|
@ -1780,7 +1734,40 @@ The callback URL will be of the form “https://(servername)/auth-saml-callback
|
|||
Enabling SAML will require MeshCentral to install extra modules from NPM, so depending on your server configuration, you may need to run MeshCentral once manually.
|
||||
|
||||
!!!note
|
||||
MeshCentral only supports "POST". [For example Authentik's](https://github.com/Ylianst/MeshCentral/issues/4725) default setting is to use "Redirect" as a "Service Provider Binding".
|
||||
MeshCentral only supports "POST". [For example Authentik's](https://github.com/Ylianst/MeshCentral/issues/4725) default setting is to use "Redirect" as a "Service Provider Binding".
|
||||
|
||||
### Generic OpenID Connect Setup
|
||||
|
||||
Generally, if you are using an IdP that supports OpenID Connect (OIDC), you can use a very basic configuration to get started, and if needed, add more specific or advanced configurations later. Here is what your config file will look like with a basic, generic, configuration.
|
||||
|
||||
``` json
|
||||
{
|
||||
"settings": {
|
||||
"cert": "mesh.your.domain",
|
||||
"port": 443,
|
||||
"sqlite3": true
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"title": "Mesh",
|
||||
"title2": ".Your.Domain",
|
||||
"authStrategies": {
|
||||
"oidc": {
|
||||
"issuer": "https://sso.your.domain",
|
||||
"clientid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
||||
"clientsecret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"newAccounts": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this is roughly the same as all the other OAuth2 based authentication strategies. These are the basics you need to get started using OpenID Connect because it's still authenticating with OAuth2. If you plan to take advantage of some of the more advanced features provided by this strategy you should consider reading the [additional strategy documentation](./openidConnectStrategy.md).
|
||||
|
||||
> NOTE: MeshCentral will use `https://mesh.your.domain/auth-oidc-callback` as the default redirect uri.
|
||||
|
||||
## Improvements to MeshCentral
|
||||
|
||||
In 2007, the first version of MeshCentral was built. We will refer to it as “MeshCentral1”. When MeshCentral1 was designed, HTML5 did not exist and web sockets where not implemented in any of the major browsers. Many design decisions were made at the time that are no longer optimal today. With the advent of the latest MeshCentral, MeshCentral1 is no longer supported and MeshCentral v2 has been significantly redesigned and mostly re-written based of previous version. Here is a list of improvements made in MeshCentral when compared with MeshCentral1:
|
||||
|
|
@ -1918,4 +1905,4 @@ MeshCentral has built-in web-based integration of SSH in the "Terminal" tab and
|
|||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="320" height="180" src="https://www.youtube.com/embed/7qAbl2OuZEU" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
671
docs/docs/meshcentral/openidConnectStrategy.md
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
# Using the OpenID Connect Strategy on MeshCentral
|
||||
|
||||
## Overview
|
||||
|
||||
### Introduction
|
||||
|
||||
There is a lot of information to go over, but first, why OpenID Connect?
|
||||
|
||||
Esentially its because its both based on a industry standard authorization protocol, and is becoming an industry standard authentication protocol. Put simply it's reliable and reusable, and we use OpenID Connect for exactly those reasons, almost every everyone does, and we want to be able to integrate with almost anyone. This strategy allows us to expand the potential of MeshCentral through the potential of OpenID Connect.
|
||||
|
||||
In this document, we will learn about the OpenID Connect specification at a high level, and then use that information to configure the OpenID Connect strategy for MeshCentral using a generic OpenID Connect compatible IdP. After that we will go over some advanced configurations and then continue by explaining how to use the new presets for popular IdPs, specifically Google or Azure. Then we will explore the configuration and usage of the groups feature.
|
||||
|
||||
> ATTENTION: As of MeshCentral `v1.1.22` there are multiple config options being depreciated. Using any of the old configs will only generate a warning in the authlog and will not stop you from using this strategy at this time. If there is information found in both the new and old config locations the new config location will be used. We will go over the specifics later, now lets jump in.
|
||||
|
||||
### Chart of Frequently Used Terms and Acronyms
|
||||
| Term | AKA | Descriptions |
|
||||
| --- | --- | --- |
|
||||
| OAuth 2.0 | OAuth2 | OAuth 2.0 is the industry-standard protocol for user *authorization*. |
|
||||
| OpenID Connect | OIDC | Identity layer built on top of OAuth2 for user *authentication*. |
|
||||
| Identity Provider | IdP | The *service used* to provide authentication and authorization. |
|
||||
| Preset Configs | Presets | Set of *pre-configured values* to allow some specific IdPs to connect correctly. |
|
||||
| OAuth2 Scope | Scope | A flag *requesting access* to a specific resource or endpoint |
|
||||
| OIDC Claim | Claim | A *returned property* in the user info provided by your IdP |
|
||||
| User Authentication | AuthN | Checks if you *are who you say you are*. Example: Username and password authentication |
|
||||
| User Authorization | AuthZ | Check if you have the *permissions* required to access a specific resource or endpoint |
|
||||
|
||||
### OpenID Connect Technology Overview
|
||||
|
||||
OpenID Connect is a simple identity layer built on top of the OAuth2 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an “Authorization Server”, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.
|
||||
|
||||
OpenID Connect allows clients of all types, including Web-based, mobile, and JavaScript clients, to request and receive information about authenticated sessions and end-users. The specification suite is extensible, allowing participants to use optional features such as encryption of identity data, discovery of OpenID Providers, and logout, when it makes sense for them.
|
||||
|
||||
That description was straight from [OpenID Connect Documentation](https://openid.net/connect/), but basically, OAuth2 is the foundation upon which OpenID Connect was built, allowing for wide ranging compatability and interconnection. OpenID Connect appends the secure user *authentication* OAuth2 is known for, with user *authorization* by allowing the request of additional *scopes* that provide additional *claims* or access to API's in an easily expandable way.
|
||||
|
||||
### Annotations
|
||||
|
||||
#### Own IDP, CA and Docker
|
||||
|
||||
If you operate your own identity provider, your own certification authority and MeshCentral via Docker, it is necessary to provide the complete certificate chain, otherwise NodeJS (in particular the openid-client module) will refuse the connection to the IDP server.
|
||||
|
||||
The following errors can be found in the log file:
|
||||
> OIDC: Discovery failed.
|
||||
|
||||
> UNABLE_TO_GET_ISSUER_CERT_LOCALLY
|
||||
|
||||
To solve this problem, the certificate chain in PEM format must be placed in the data directory and the following entry must be added to the docker-compose.yml file in the “environment” section:
|
||||
```
|
||||
environment:
|
||||
- NODE_EXTRA_CA_CERTS=/opt/meshcentral/meshcentral-data/chain.pem
|
||||
```
|
||||
|
||||
## Basic Config
|
||||
|
||||
### *Introduction*
|
||||
|
||||
Generally, if you are using an IdP that supports OIDC, you can use a very basic configuration to get started, and if needed, add more specific or advanced configurations later. Here is what your config file will look like with a basic, generic, configuration.
|
||||
|
||||
### *Basic Config File Example*
|
||||
|
||||
``` json
|
||||
{
|
||||
"settings": {
|
||||
"cert": "mesh.your.domain",
|
||||
"port": 443,
|
||||
"sqlite3": true
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"title": "MeshCentral",
|
||||
"title2": "Your sub-title",
|
||||
"authStrategies": {
|
||||
"oidc": {
|
||||
"issuer": "https://sso.your.domain",
|
||||
"clientid": "2d5685c5-0f32-4c1f-9f09-c60e0dbc948a",
|
||||
"clientsecret": "7PiGSLSLL4e7NGi67KM229tfK7Z7TqzQ",
|
||||
"newAccounts": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this is roughly the same as all the other OAuth2 based authentication strategies. These are the basics you need to get started, however, if you plan to take advantage of some of the more advanced features provided by this strategy, you'll need to keep reading.
|
||||
|
||||
In this most basic of setups, you only need the URL of the issuer, as well as a client ID and a client secret. Notice in this example that the callback URL (or client redirect uri) is not configured, thats because MeshCentral will use `https://mesh.your.domain/auth-oidc-callback` as the default. Once you've got your configuration saved, restart MeshCentral and you should see an OpenID Connect Single Sign-on button on the login screen.
|
||||
|
||||
> WARNING: The redirect endpoint must EXACTLY match the value provided to your IdP or your will deny the connection.
|
||||
|
||||
> ATTENTION: You are required to configure the cert property in the settings section for the default domain, and configure the dns property under each additional domain.
|
||||
|
||||
## Advanced Options
|
||||
|
||||
### Overview
|
||||
|
||||
There are plenty of options at your disposal if you need them. In fact, you can configure any property that node-openid-client supports. The openid-client module supports far more customization than I know what to do with, if you want to know more check out [node-openid-client on GitHub](https://github.com/panva/node-openid-client) for expert level configuration details. There are plenty of things you can configure with this strategy and there is a lot of decumentation behind the tools used to make this all happen. I strongly recommend you explore the [config schema](https://github.com/Ylianst/MeshCentral/blob/master/meshcentral-config-schema.json), and if you have a complicated config maybe check out the [openid-client readme](https://github.com/panva/node-openid-client/blob/main/docs/README.md). Theres a list of resources at the end if you want more information on any specific topics. In the meantime, let’s take a look at an example of what your config file could look with a slightly more complicated configuration, including multiple manually defined endpoints.
|
||||
|
||||
#### *Advanced Config File Example*
|
||||
|
||||
``` json
|
||||
{
|
||||
"settings": {
|
||||
"cert": "mesh.your.domain",
|
||||
"port": 443,
|
||||
"redirPort": 80,
|
||||
"AgentPong": 300,
|
||||
"TLSOffload": "192.168.1.50",
|
||||
"SelfUpdate": false,
|
||||
"AllowFraming": false,
|
||||
"sqlite3": true,
|
||||
"WebRTC": true
|
||||
},
|
||||
"domains": {
|
||||
"": {
|
||||
"title": "Mesh",
|
||||
"title2": ".Your.Domain",
|
||||
"orphanAgentUser": "~oidc:e48f8ef3-a9cb-4c84-b6d1-fb7d294e963c",
|
||||
"authStrategies": {
|
||||
"oidc": {
|
||||
"issuer": {
|
||||
"issuer": "https://sso.your.domain",
|
||||
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
|
||||
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
|
||||
"end_session_endpoint": "https://sso.your.domain/logout",
|
||||
"jwks_uri": "https://sso.your.domain/jwks-uri"
|
||||
},
|
||||
"client": {
|
||||
"client_id": "110d5612-0822-4449-a057-8a0dbe26eca5",
|
||||
"client_secret": "4TqST46K53o3Z2Q88p39YwR6YwJb7Cka",
|
||||
"redirect_uri": "https://mesh.your.domain/auth-oidc-callback",
|
||||
"post_logout_redirect_uri": "https://mesh.your.domain/login",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"response_types": "authorization_code"
|
||||
},
|
||||
"custom": {
|
||||
"scope": [ "openid", "profile", "read.EmailAlias", "read.UserProfile" ],
|
||||
"preset": null
|
||||
},
|
||||
"groups": {
|
||||
"recursive": true,
|
||||
"required": ["Group1", "Group2"],
|
||||
"siteadmin": ["GroupA", "GroupB"],
|
||||
"revokeAdmin": true,
|
||||
"sync": {
|
||||
"filter": ["Group1", "GroupB", "OtherGroup"]
|
||||
},
|
||||
"claim": "GroupClaim",
|
||||
"scope": "read.GroupMemberships"
|
||||
},
|
||||
"logouturl": "https://sso.your.domain/logout?r=https://mesh.your.domain/login",
|
||||
"newAccounts": true
|
||||
},
|
||||
{...}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### "Issuer" Options
|
||||
|
||||
#### *Introduction*
|
||||
|
||||
In the advanced example config above, did you notice that the issuer property has changed from a *string* to an *object* compared to the basic example? This not only allows for much a much smaller config footprint when advanced issuer options are not required, it successfully fools you in to a false sense of confidence early on in this document. If you are manually configuring the issuer endpoints, keep in mind that MeshCentral will still attempt to discover **ALL** issuer information. Obviously if you manually configure an endpoint, it will be used even if the discovered information is different from your config.
|
||||
|
||||
> NOTE: If you are using a preset, you dont need to define an issuer. If you do, the predefined information will be ignored.
|
||||
|
||||
#### *Common Config Chart*
|
||||
|
||||
| Name | Description | Default | Example | Required |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `issuer` | The primary URI that represents your Identity Providers authentication endpoints. | N/A | `"issuer": "https://sso.your.domain"`<br/>`"issuer": { "issuer": "https://sso.your.domain" }` | Unless using preset. |
|
||||
|
||||
#### *Advanced Config Example*
|
||||
|
||||
``` json
|
||||
"issuer": {
|
||||
"issuer": "https://sso.your.domain",
|
||||
"authorization_endpoint": "https://auth.your.domain/auth-endpoint",
|
||||
"token_endpoint": "https://tokens.sso.your.domain/token-endpoint",
|
||||
"end_session_endpoint": "https://sso.your.domain/logout",
|
||||
"jwks_uri": "https://sso.your.domain/jwks-uri"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Required and Commonly Used Configs*
|
||||
|
||||
The `issuer` property in the `issuer` object is the only one required, and its only required if you aren't using a preset. Besides the issuer, these are mostly options related to the endpoints and their configuration. The schema below looks intimidating but it comes down to being able to support any IdP. Setting the issuer, and end_session_endpoint are the two main ones you want to setup.
|
||||
|
||||
#### *Schema*
|
||||
|
||||
``` json
|
||||
"issuer": {
|
||||
"type": ["string","object"],
|
||||
"format": "uri",
|
||||
"description": "Issuer options. Requires issuer URI (issuer.issuer) to discover missing information unless using preset",
|
||||
"properties": {
|
||||
"issuer": { "type": "string", "format": "uri", "description": "URI of the issuer." },
|
||||
"authorization_endpoint": { "type": "string", "format": "uri" },
|
||||
"token_endpoint": { "type": "string", "format": "uri" },
|
||||
"jwks_uri": { "type": "string", "format": "uri" },
|
||||
"userinfo_endpoint": { "type": "string", "format": "uri" },
|
||||
"revocation_endpoint": { "type": "string", "format": "uri" },
|
||||
"introspection_endpoint": { "type": "string", "format": "uri" },
|
||||
"end_session_endpoint": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "URI to direct users to when logging out of MeshCentral.",
|
||||
"default": "this.issuer/logout"
|
||||
},
|
||||
"registration_endpoint": { "type": "string", "format": "uri" },
|
||||
"token_endpoint_auth_methods_supported": { "type": "string" },
|
||||
"token_endpoint_auth_signing_alg_values_supported": { "type": "string" },
|
||||
"introspection_endpoint_auth_methods_supported": { "type": "string" },
|
||||
"introspection_endpoint_auth_signing_alg_values_supported": { "type": "string" },
|
||||
"revocation_endpoint_auth_methods_supported": { "type": "string" },
|
||||
"revocation_endpoint_auth_signing_alg_values_supported": { "type": "string" },
|
||||
"request_object_signing_alg_values_supported": { "type": "string" },
|
||||
"mtls_endpoint_aliases": {
|
||||
"type":"object",
|
||||
"properties": {
|
||||
"token_endpoint": { "type": "string", "format": "uri" },
|
||||
"userinfo_endpoint": { "type": "string", "format": "uri" },
|
||||
"revocation_endpoint": { "type": "string", "format": "uri" },
|
||||
"introspection_endpoint": { "type": "string", "format": "uri" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
```
|
||||
|
||||
### "Client" Options
|
||||
|
||||
#### *Introduction*
|
||||
|
||||
There are just about as many option as possible here since openid-client also provides a Client class, because of this you are able to manually configure the client how ever you need. This includes setting your redirect URI to any available path, for example, if I was using the "google" preset and wanted to have Google redirect me back to "https://mesh.your.domain/oauth2/oidc/redirect/givemebackgooglemusicyoujerks", MeshCentral will now fully support you in that. One of the other options is the post logout redirect URI, and it is exactly what it sounds like. After MeshCentral logs out a user using the IdPs end session endpoint, it send the post logout redirect URI to your IdP to forward the user back to MeshCentral or to an valid URI such as a homepage.
|
||||
|
||||
> NOTE: The client object is required, however an exception would be with using old configs, which will be discussed later.
|
||||
|
||||
#### *Common Configs*
|
||||
|
||||
| Name | Description | Default | Example | Required |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `client_id` | The client ID provided by your Identity Provider (IdP) | N/A | `bdd6aa4b-d2a2-4ceb-96d3-b3e23cd17678` | `true` |
|
||||
| `client_secret` | The client secret provided by your Identity Provider (IdP) | N/A | `vUg82LJ322rp2bvdzuVRh3dPn3oVo29m` | `true` |
|
||||
| `redirect_uri` | "URI your IdP sends you after successful authorization. | `https://mesh.your.domain/auth-oidc-callback` | `https://mesh.your.domain/oauth2/oidc/redirect` | `false` |
|
||||
| `post_logout_redirect_uri` | URI for your IdP to send you after logging out of IdP via MeshCentral. | `https://mesh.your.domain/login` | `https://site.your.other.domain/login` | `false` |
|
||||
|
||||
#### *Advanced Config Example*
|
||||
|
||||
``` json
|
||||
"client": {
|
||||
"client_id": "00b3875c-8d82-4238-a8ef-25303fa7f9f2",
|
||||
"client_secret": "7PP453H577xbFDCqG8nYEJg8M3u8GT8F",
|
||||
"redirect_uri": "https://mesh.your.domain/auth-oidc-callback",
|
||||
"post_logout_redirect_uri": "https://mesh.your.domain/login",
|
||||
"token_endpoint_auth_method": "client_secret_post",
|
||||
"response_types": "authorization_code"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Required and Commonly Used Configs*
|
||||
|
||||
There are many available options you can configure but most of them go unused. Although there are a few *commonly used* properties. The first two properties, `client_id` and `client_secret` are required. The next one `redirect_uri` is used to setup a custom URI for the redirect back to MeshCentral after being authenicated by your IdP. The `post_logout_redirect_uri` property is used to tell your IdP where to send you after being logged out. These work in conjunction with the issuers `end_session_url` to automatically fill in any blanks in the config.
|
||||
|
||||
#### *Schema*
|
||||
``` json
|
||||
"client": {
|
||||
"type": "object",
|
||||
"description": "OIDC Client Options",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string",
|
||||
"description": "REQUIRED: The client ID provided by your Identity Provider (IdP)"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string",
|
||||
"description": "REQUIRED: The client secret provided by your Identity Provider (IdP)"
|
||||
},
|
||||
"redirect_uri": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "URI your IdP sends you after successful authorization. This must match what is listed with your IdP. (Default is https://[currentHost][currentPath]/auth-oidc-callback)"
|
||||
},
|
||||
"post_logout_redirect_uri": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "URI for your IdP to send you after logging out of IdP via MeshCentral.",
|
||||
"default": "https:[currentHost][currentPath]/login"
|
||||
},
|
||||
"id_token_signed_response_alg": { "type": "string", "default": "RS256" },
|
||||
"id_token_encrypted_response_alg": { "type": "string" },
|
||||
"id_token_encrypted_response_enc": { "type": "string" },
|
||||
"userinfo_signed_response_alg": { "type": "string" },
|
||||
"userinfo_encrypted_response_alg": { "type": "string" },
|
||||
"userinfo_encrypted_response_enc": { "type": "string" },
|
||||
"response_types": { "type": ["string", "array"], "default": ["code"] },
|
||||
"default_max_age": { "type": "number" },
|
||||
"require_auth_time": { "type": "boolean", "default": false },
|
||||
"request_object_signing_alg": { "type": "string" },
|
||||
"request_object_encryption_alg": { "type": "string" },
|
||||
"request_object_encryption_enc": { "type": "string" },
|
||||
"token_endpoint_auth_method": {
|
||||
"type": "string",
|
||||
"default": "client_secret_basic",
|
||||
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
|
||||
},
|
||||
"introspection_endpoint_auth_method": {
|
||||
"type": "string",
|
||||
"default": "client_secret_basic",
|
||||
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
|
||||
},
|
||||
"revocation_endpoint_auth_method": {
|
||||
"type": "string",
|
||||
"default": "client_secret_basic",
|
||||
"enum": [ "none", "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ]
|
||||
},
|
||||
"token_endpoint_auth_signing_alg": { "type": "string" },
|
||||
"introspection_endpoint_auth_signing_alg": { "type": "string" },
|
||||
"revocation_endpoint_auth_signing_alg": { "type": "string" },
|
||||
"tls_client_certificate_bound_access_tokens": { "type": "boolean" }
|
||||
},
|
||||
"required": [ "client_id", "client_secret" ],
|
||||
"additionalProperties": false
|
||||
},
|
||||
```
|
||||
|
||||
### "Custom" Options
|
||||
|
||||
#### *Introduction*
|
||||
|
||||
These are all the options that dont fit with the issuer or client, including the presets. The presets define more than just the issuer URL used in discovery, they also define API endpoints, and specific ways to assemble your data. You are able to manually override most of the effects of the preset, but not all. You are able to manually configure the *scope* of the authorization request though, as well as choose which claims to use if your IdP uses something other than the defaults.
|
||||
|
||||
> NOTE: The scope must be a string, an array of strings, or a space separated list of scopes as a single string.
|
||||
|
||||
#### *Common Config Chart*
|
||||
|
||||
| Name | Description | Default | Example | Required |
|
||||
| -------- | ------------------------------------------------ | --------------------------------------------------------- | ----------------------------------- | -------- |
|
||||
| `scope` | A list of scopes to request from the issuer. | `"openid profile email"` | `["openid", "profile"]` | `false` |
|
||||
| `claims` | A group of claims to use instead of the defaults | Defauts to name of property except that `uuid` used `sub` | `"claims": {"uuid": "unique_name"}` | `false` |
|
||||
|
||||
#### *Advanced Config Example*
|
||||
|
||||
``` json
|
||||
"custom": {
|
||||
"scope": [ "openid", "profile", "read.EmailAlias", "read.UserProfile" ],
|
||||
"preset": null,
|
||||
"claims": {
|
||||
"name": "nameOfUser",
|
||||
"email": "publicEmail"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
> NOTE: You can `preset` to null if you want to explicitly disable presets.
|
||||
|
||||
#### *Required and Commonly Used Configs*
|
||||
|
||||
As should be apparent by the name alone, the custom property does not need to be configured and is used for optional or advanced configurations. With that said, lets look at few common options strategy will default to using the `openid`, `profile`, and `email` scopes to gather the required information about the user, if your IdP doesn't support or require all these, you can set up the scope manually. Combine that with the ability to set the group scope and you can end up with an entirely custom scope being sent to your IdP. Not to mention the claims property, which allows you to pick and choose what claims to use to gather your data in case you have issues with any of the default behaviors of OpenID Connect and your IdP. This is also where you would set the preset and any values required by the presets.
|
||||
|
||||
#### *Schema*
|
||||
``` json
|
||||
"custom": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope": {
|
||||
"type": ["string", "array"],
|
||||
"description": "A list of scopes to request from the issuer.",
|
||||
"default": "openid profile email",
|
||||
"examples": ["openid", ["openid", "profile"], "openid profile email", "openid profile email groups"]
|
||||
},
|
||||
"claims": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"uuid": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"preset": { "type": "string", "enum": ["azure", "google"]},
|
||||
"tenant_id": { "type": "string", "description": "REQUIRED FOR AZURE PRESET: Tenantid for Azure"},
|
||||
"customer_id": { "type": "string", "description": "REQUIRED FOR GOOGLE PRESET IF USING GROUPS: Customer ID from Google, should start with 'C'."}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
```
|
||||
|
||||
### "Groups" Options
|
||||
|
||||
#### *Introduction*
|
||||
|
||||
The groups option allows you to use the groups you already have with your IdP in MeshCentral in a few ways. First you can set a group that the authorized user must be in to sign in to MeshCentral. You can also allow users with the right memberships automatic admin privlidges, and there is even an option to revoke privlidges if the user is NOT in the admin group. Besides these filters, you can filter the sync property to mirror only certain groups as MeshCentral User Groups, dynamically created as the user logs in. You can of course simply enable sync and mirror all groups from your IdP as User Groups. Additionally you can define the scope and claim of the groups for a custom setup, again allowing for a wide range of IdPs to be used, even without a preset.
|
||||
|
||||
#### *Common Config Chart*
|
||||
|
||||
| Name | Description | Default | Example | Required |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `sync` | Allows you to mirror user groups from your IdP. | `false` | `"sync": { "filter": ["Group1", "Group2"] }`<br/>`"sync": true` | `false` |
|
||||
| `required` | Access is only granted to users who are a member<br/>of at least one of the listed required groups. | `undefined` | `"required": ["Group1", "Group2"]` | `false` |
|
||||
| `siteadmin` | Full site admin priviledges will be granted to users<br/>who are a member of at least one of the listed admin groups | `undefined` | `"siteadmin": ["Group1", "Group2"]` | `false` |
|
||||
| `revokeAdmin` | If true, admin privileges will be revoked from users<br/>who arent a member of at least one of the listed admin groups. | `true` | `"revokeAdmin": false` | `false` |
|
||||
|
||||
#### *Advanced Config Example*
|
||||
|
||||
``` json
|
||||
"groups": {
|
||||
"recursive": true,
|
||||
"required": ["Group1", "Group2"],
|
||||
"siteadmin": ["GroupA", "GroupB"],
|
||||
"revokeAdmin": false,
|
||||
"sync": {
|
||||
"filter": ["Group1", "GroupB", "OtherGroup"]
|
||||
},
|
||||
"claim": "GroupClaim",
|
||||
"scope": "read.GroupMemberships"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Required and Commonly Used Configs*
|
||||
|
||||
As you can see in the schema below, there aren't any required properties in the groups object, however there are some commonly used ones. The first, and maybe most commonly used one, is the sync property. The sync property mirrors IdP provided groups into MeshCentral as user groups. You can then configure access as required to those groups, and as users log in, they will be added to the now existing groups if they are a member. You also have other options like using a custom *scope* or *claim* to get your IdP communicating with MeshCentral properly, without the use of preset configs. You also can set the required property if you need to limit authorization to users that are a member of at least one of the groups you set. or the siteadmin property to grant admin privilege, with the revokeAdmin property available to allow revoking admin rights also.
|
||||
|
||||
#### *Schema*
|
||||
|
||||
``` json
|
||||
"groups": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recursive": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When true, the group memberships will be scanned recursively."
|
||||
},
|
||||
"required": {
|
||||
"type": [ "string", "array" ],
|
||||
"description": "Access is only granted to users who are a member of at least one of the listed required groups."
|
||||
},
|
||||
"siteadmin": {
|
||||
"type": [ "string", "array" ],
|
||||
"description": "Full site admin priviledges will be granted to users who are a member of at least one of the listed admin groups."
|
||||
},
|
||||
"revokeAdmin": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, admin privileges will be revoked from users who are NOT a member of at least one of the listed admin groups."
|
||||
},
|
||||
"sync": {
|
||||
"type": [ "boolean", "object" ],
|
||||
"default": false,
|
||||
"description": "If true, all groups found during user login are mirrored into MeshCentral user groups.",
|
||||
"properties": {
|
||||
"filter": {
|
||||
"type": [ "string", "array" ],
|
||||
"description": "Only groups listed here are mirrored into MeshCentral user groups."
|
||||
}
|
||||
}
|
||||
},
|
||||
"scope": { "type": "string", "default": "groups", "description": "Custom scope to use." },
|
||||
"claim": { "type": "string", "default": "groups", "description": "Custom claim to use." }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
```
|
||||
|
||||
## Preset OpenID Connect Configurations
|
||||
|
||||
### Overview
|
||||
|
||||
#### *Introduction*
|
||||
|
||||
Google is a blah and is used by tons of blahs as its so great. Lets move on.
|
||||
|
||||
#### *Common Config Chart*
|
||||
|
||||
> NOTE: All settings directly related to presets are in the custom section of the config.
|
||||
|
||||
| Name | Description | Example | Required |
|
||||
| --- | --- | --- | --- |
|
||||
| `preset` | Manually enable the use of a preset. | `"preset": "google"`<br/>`"preset": "azure"` | `false` |
|
||||
| `customer_id` | Customer ID of the Google Workspaces instace you<br/>plan to use with the groups feature.| `"customer_id": ["Group1", "Group2"]` | If `google` preset is used with `groups` feature |
|
||||
| `tenant_id` | Tenant ID from Azure AD, this is required to use<br/>the `azure` preset as it is part of the issuer url. | `"siteadmin": ["Group1", "Group2"]` | `false` |
|
||||
|
||||
### Google Preset
|
||||
|
||||
#### *Prerequisites*
|
||||
|
||||
> Check out this [documentation](https://developers.google.com/identity/protocols/oauth2/openid-connect) to get ready before we start.
|
||||
|
||||
#### *Basic Config Example*
|
||||
|
||||
``` json
|
||||
"oidc": {
|
||||
"client": {
|
||||
"client_id": "268438852161-r8xa7qxwf3rr0shp1xnpgmm70bnag21p.apps.googleusercontent.com",
|
||||
"client_secret": "ETFWBX-gFEaxfPXs1tWmAOkuWDFTgoL3nwh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### *Specifics*
|
||||
|
||||
If you notice above I forgot to add any preset related configs, however because google tags the client ID we can detect that and automatically use the google preset. The above config is tested, the sentive data has been scrambled of course. That said, you would normally use this preset in more advaced setups, let take a look at an example.
|
||||
|
||||
#### *Advanced Example with Groups*
|
||||
|
||||
``` json
|
||||
"oidc": {
|
||||
"client": {
|
||||
"client_id": "424555768625-k7ub3ovqs0yp7mfo0usvyyx51nfii61c.apps.googleusercontent.com",
|
||||
"client_secret": "QLBCQY-nRYmjnFWv3nKyHGmwQEGLokP6ldk"
|
||||
},
|
||||
"custom": {
|
||||
"preset": "google",
|
||||
"customer_id": "C46kyhmps"
|
||||
},
|
||||
"groups": {
|
||||
"siteadmin": ["GroupA", "GroupB"],
|
||||
"revokeAdmin": true,
|
||||
"sync": true
|
||||
},
|
||||
"callbackURL": "https://mesh.your.domain/auth-oidc-google-callback"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Customer ID and Groups*
|
||||
|
||||
As always, the client ID and secret are required, the customer ID on the other hand is only required if you plan to take advantage of the groups function *and* the google preset. This also requires you have a customer ID, if you have do, it is available in the Google Workspace Admin Console under Profile->View. Groups work the same as they would with any other IdP but they are pulled from the Workspace groups.
|
||||
|
||||
#### *Schema*
|
||||
|
||||
```json
|
||||
"custom": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preset": { "type": "string", "enum": ["azure", "google"]},
|
||||
"customer_id": { "type": "string", "description": "Customer ID from Google, should start with 'C'."}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
```
|
||||
|
||||
### Azure Preset
|
||||
|
||||
#### *Prerequisites*
|
||||
|
||||
To configure OIDC-based SSO, you need an Azure account with an active subscription. [Create an account](https://azure.microsoft.com/free/?WT.mc_id=A261C142F) for free. The account used for setup must be of the following roles: Global Administrator, Cloud Application Administrator, Application Administrator, or owner the service principal.
|
||||
|
||||
> Check this [documentation](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/add-application-portal-setup-oidc-sso) for more information.
|
||||
|
||||
#### *Basic Config Example*
|
||||
|
||||
``` json
|
||||
"oidc": {
|
||||
"client": {
|
||||
"client_id": "a1gkl04i-40g8-2h74-6v41-2jm2o2x0x27r",
|
||||
"client_secret": "AxT6U5K4QtcyS6gF48gndL7Ys22BL15BWJImuq1O"
|
||||
},
|
||||
"custom": {
|
||||
"preset": "azure",
|
||||
"tenant_id": "46a6022g-4h33-1451-h1rc-08102ga3b5e4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### *Specifics*
|
||||
|
||||
As with all other types of configuration for the OIDC strategy, the Azure preset requires a client ID and secret.The tenant ID is used as part of the issuer URI to make even the most basic AuthN requests so it is also required for the azure preset. besides that groups are available to the Azure preset as well as the recursive feature of groups. This allows you to search user groups recursively for groups they have membership in through other groups.
|
||||
|
||||
> NOTE: The Azure AD preset uses the Tenant ID as part of the issuer URI:<br>`"https://login.microsoftonline.com/"` + `strategy`.custom.tenant_id + `"/v2.0"`
|
||||
|
||||
#### *Advanced Example with Groups*
|
||||
|
||||
``` json
|
||||
"oidc": {
|
||||
"client": {
|
||||
"client_id": "a1gkl04i-40g8-2h74-6v41-2jm2o2x0x27r",
|
||||
"client_secret": "AxT6U5K4QtcyS6gF48gndL7Ys22BL15BWJImuq1O"
|
||||
},
|
||||
"custom": {
|
||||
"preset": "azure",
|
||||
"tenant_id": "46a6022g-4h33-1451-h1rc-08102ga3b5e4"
|
||||
},
|
||||
"groups": {
|
||||
"recursive": true,
|
||||
"siteadmin": ["GroupA", "GroupB"],
|
||||
"revokeAdmin": true,
|
||||
"sync": true
|
||||
},
|
||||
"callbackURL": "https://mesh.your.domain/auth-oidc-azure-callback"
|
||||
},
|
||||
```
|
||||
|
||||
#### *Schema*
|
||||
|
||||
```json
|
||||
"custom": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preset": { "type": "string", "enum": ["azure", "google"]},
|
||||
"tenant_id": { "type": "string", "description": "Tenant ID from Azure AD."}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
```
|
||||
|
||||
## Depreciated Properties
|
||||
|
||||
### Overview
|
||||
|
||||
#### Introduction
|
||||
|
||||
As of MeshCentral `v1.1.22` and the writing of this documentation, the node module that handles everything was changed from [passport-openid-connect](https://github.com/jaredhanson/passport-openidconnect) to [openid-client](https://github.com/panva/node-openid-client). As a result of this change, multiple properties in the config have been depcrecated; this means some options in the strategy arent being used anymore. These are often referred to as "old configs" by this documentation.
|
||||
|
||||
#### *Migrating Old Configs*
|
||||
|
||||
We upgraded but what about all the existing users, we couldn't just invalidate every config pre `v1.1.22`. So in an effort to allow greater flexibility to all users of MeshCentral, and what futures scholars will all agree was an obvious move, all the depreciated configs will continue working as expected. Using any of the old options will just generate a warning in the authlog and will not stop you from using this the OIDC strategy with outdated configs, however if both the equivalent new and old config are set the new config will be used.
|
||||
|
||||
#### *Old Config Example*
|
||||
```json
|
||||
"oidc": {
|
||||
"newAccounts": true,
|
||||
"clientid": "421326444155-i1tt4bsmk3jm7dri6jldekl86rfpg07r.apps.googleusercontent.com",
|
||||
"clientsecret": "GNLXOL-kEDjufOCk6pIcTHtaHFOCgbT4hoi"
|
||||
}
|
||||
```
|
||||
|
||||
This example was chosen because I wanted to highlight an advantage of supporting these old configs long term, even in a depreciated status. That is, the ability to copy your existing config from one of the related strategies without making any changes to your config by using the presets. This allows you to test out the oidc strategy without commiting to anything, since the user is always appended with the strategy used to login. In this example, the config was originally a google auth strategy config, changing the `"google"` to `"oidc"` is all that was done to the above config, besides obsfuscation of course.
|
||||
|
||||
#### *Advcanced Old Config Example*
|
||||
|
||||
``` json
|
||||
"oidc": {
|
||||
"authorizationURL": "https://sso.your.domain/api/oidc/authorization",
|
||||
"callbackURL": "https://mesh.your.domain/oauth2/oidc/callback",
|
||||
"clientid": "tZiPTMDNuSaQPapAQJtwDXVnYjjhQybc",
|
||||
"clientsecret": "vrQWspJxdVAxEFJdrxvxeQwWkooVcqdU",
|
||||
"issuer": "https://sso.your.domain",
|
||||
"tokenURL": "https://sso.your.domain/api/oidc/token",
|
||||
"userInfoURL": "https://sso.your.domain/api/oidc/userinfo",
|
||||
"logoutURL": "https://sso.your.domain/logout?rd=https://mesh.your.domain/login",
|
||||
"groups": {
|
||||
"recursive": true,
|
||||
"required": ["Group1", "Group2"],
|
||||
"siteadmin": ["GroupA", "GroupB"],
|
||||
"sync": {
|
||||
"filter": ["Group1", "GroupB", "OtherGroup"]
|
||||
}
|
||||
},
|
||||
"newAccounts": true
|
||||
},
|
||||
```
|
||||
|
||||
#### *Upgrading to v1.1.22*
|
||||
|
||||
If you were already using a meticulusly configured oidc strategy, all of your configs will still be used. You will simply see a warning in the logs if any depreciated properties were used. If you check the authLog there are additional details about the old config and provide the new place to put that information. In this advanced config, even the groups will continue to work just as they did before without any user intervention when upgrading from a version of MeshCentral pre v1.1.22. There are no step to take and no action is needed, moving the configs to the new locations is completely optional at the moment.
|
||||
|
||||
# Links
|
||||
|
||||
https://cloud.google.com/identity/docs/reference/rest/v1/groups/list
|
||||
|
||||
https://www.onelogin.com/learn/authentication-vs-authorization
|
||||
|
||||
https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
|
||||
|
||||
https://github.com/panva/node-openid-client
|
||||
|
||||
https://openid.net/connect/
|
||||
|
||||
> You just read `openidConnectStrategy.ms v1.0.1` by [@mstrhakr](https://github.com/mstrhakr)
|
||||
|
|
@ -1,16 +1,140 @@
|
|||
# Plugins
|
||||
# Plugins - Installation & Usage
|
||||
|
||||
!!!note
|
||||
Plugins are not supported, if you have problems with MeshCentral please disable all plugins before further troubleshooting.
|
||||
Plugins as such receive **no support** by the main developers of MeshCentral. If you experience problems with MeshCentral please make sure to **disable all plugins before further troubleshooting**!
|
||||
|
||||
## Installation
|
||||
## Use Cases
|
||||
|
||||
1. Enable plugins in the configuration and restart MC as described.
|
||||
2. Log into MC as full administrator.
|
||||
3. Go my `My Server` -> `Plugins`, hit the Download plugin button.
|
||||
4. A dialog opens requesting an URL, put in: <https://github.com/ryanblenis/MeshCentral-ScriptTask>
|
||||
5. The plugin pops up in the plugin list below the download button, you can now configure and enable/disable it.
|
||||
Certain feature requests may not be suitable for all MeshCentral users and thus are available as a plugin. Furthermore users can develop their own plugins - as described further below - to extend functionality or benefit from integrating MeshCentral into their existing application environment.
|
||||
|
||||
## List of plugins
|
||||
## List of publically available plugins
|
||||
|
||||
<https://github.com/topics/meshcentral-plugin>
|
||||
|
||||
## Installation of a plugin
|
||||
|
||||
1. First please make sure that you enable plugins in the configuration
|
||||
>"plugins": {
|
||||
> "enabled": true
|
||||
>},
|
||||
2. Restart MeshCentral if you needed to change the configuration.
|
||||
2. Log into MeshCentral as full administrator.
|
||||
3. Go my `My Server` -> `Plugins`, then hit the Download plugin button.
|
||||
4. A dialog opens requesting a URL, e.g. put in: <https://github.com/ryanblenis/MeshCentral-ScriptTask>
|
||||
5. The plugin pops up in the plugin list below the download button, you can now configure and enable/disable it.
|
||||
|
||||
# Plugins - Development & Hooks
|
||||
|
||||
!!!note
|
||||
Plugins as such receive **no support** by the main developers of MeshCentral. If you experience problems with MeshCentral please make sure to **disable all plugins before further troubleshooting**!
|
||||
|
||||
## Overview
|
||||
|
||||
Not all feature requests may be suitable for all MeshCentral users and thus can't be integrated into MeshCentral directly. Hwoever, Instead of maintaining a complete fork of MeshCentral it is much easier to extend MeshCentral's functionality using hooks and writing plugins for it.
|
||||
|
||||
## Anatomy of a plugin:
|
||||
|
||||
- plugin_name/
|
||||
-- config.json
|
||||
-- plugin_name.js
|
||||
-- modules_meshcore/ // optional
|
||||
--- plugin_name.js // optional
|
||||
|
||||
## Plugin Configuration File
|
||||
|
||||
A valid JSON object within a file named `config.json` in the root folder of your project. An example:
|
||||
|
||||
{
|
||||
"name": "Plugin Name",
|
||||
"shortName": "plugin_name",
|
||||
"version": "0.0.0",
|
||||
"author": "Author Name",
|
||||
"description": "Short Description of the plugin",
|
||||
"hasAdminPanel": false,
|
||||
"homepage": "https://www.example.com",
|
||||
"changelogUrl": "https://raw.githubusercontent.com/User/Project/master/changelog.md",
|
||||
"configUrl": "https://raw.githubusercontent.com/User/Project/master/config.json",
|
||||
"downloadUrl": "https://github.com/User/Project/archive/master.zip",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/User/Project.git"
|
||||
},
|
||||
"versionHistoryUrl": "https://api.github.com/repos/User/Project/tags",
|
||||
"meshCentralCompat": ">0.4.3"
|
||||
}
|
||||
|
||||
## Configuration File Properties
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
| ----------------- | -------- | ----------- | ------------------------------------------------------------ |
|
||||
| name | Yes | string | a human-readable name for the plugin |
|
||||
| shortName | Yes | string | an alphanumeric, unique short identifier for the plugin (will be used to access your functions throughout the project |
|
||||
| version | Yes | string | the current version of the plugin |
|
||||
| author | No | string | the author's name |
|
||||
| description | Yes | string | a short, human-readable description of what the plugin does |
|
||||
| hasAdminPanel | Yes | boolean | `true` or `false`, indicates whether or not the plugin will offer its own administrative interface |
|
||||
| homepage | Yes | string | the URL of the projects homepage |
|
||||
| changelogUrl | Yes | string | the URL to the changelog of the project |
|
||||
| configUrl | Yes | string | the URL to the config.json of the project |
|
||||
| downloadUrl | Yes | string | the URL to a ZIP of the project (used for installation/upgrades) |
|
||||
| repository | Yes | JSON object | contains the following attributes |
|
||||
| repository.type | Yes | string | valid values are `git` and in the future, `npm` will also be supported. |
|
||||
| repository.url | Yes | string | the URL to the project's repository |
|
||||
| versionHistoryUrl | No | string | the URL to the project's versions/tags |
|
||||
| meshCentralCompat | Yes | string | the minimum version string of required compatibility with the MeshCentral server, can be formatted as "0.1.2-c" or ">=0.1.2-c". Currently only supports minimum version, not full semantic checking. |
|
||||
|
||||
## Plugin Hooks
|
||||
|
||||
In essence, hooks are locations in the code which enable developers to tap into a module to either provide alternative behavior or to respond to an event.
|
||||
|
||||
These are separated into the following categories depending on the type of functionality the plugin should offer.
|
||||
|
||||
- Web UI, to modify the MeshCentral admin interface
|
||||
- Back End, to modify core functionality of the server and communicate with the Web UI layer as well as the Mesh Agent (Node) layer to send commands and data
|
||||
- Mesh Agent (Node), to introduce functionality to each agent
|
||||
|
||||
### Web UI Hooks
|
||||
|
||||
- `onDeviceRefreshEnd`: called when a device is selected in the MeshCentral web interface
|
||||
- `registerPluginTab`: callable when a device is selected in the MeshCentral web interface to register a new tab for plugin data, if required. Accepts an object, or function that returns an object, with the following properties: { tabId: "yourShortNameHere", tabTitle: "Your Display Name"}. A tab and div with the associated ID and title will be created for your use
|
||||
- `onDesktopDisconnect`: called when a remote desktop session is disconnected
|
||||
- `onWebUIStartupEnd`: called when the page has loaded for the first time after a login / refresh
|
||||
- `goPageStart`: called before page changes take effect. Passes 2 arguments (<page number> : int, <event> : Event)
|
||||
- `goPageEnd`: called after page changes take effect. Passes 2 arguments (<page number> : int, <event> : Event)
|
||||
|
||||
#### Exports
|
||||
|
||||
Any function can be exported to the Web UI layer by adding the name of the function to an `exports` array in the plugin object.
|
||||
|
||||
### Back End Hooks
|
||||
|
||||
- `server_startup`: called once when the server starts (or when the plugin is first installed)
|
||||
- `hook_agentCoreIsStable`: called once when an agent initially checks in
|
||||
- `hook_processAgentData`: called each time an agent transmits data back to the server
|
||||
- `hook_userLoggedIn`: called when a user has logged into the web interface
|
||||
- `hook_setupHttpHandlers`: called before all http handlers are setup
|
||||
|
||||
### Mesh Agent
|
||||
|
||||
Use of the optional file `plugin_name.js` in the optional folder `modules_meshcore` will include the file in the default meshcore file sent to each endpoint. This is useful to add functionality on each of the endpoints.
|
||||
|
||||
## Structure
|
||||
|
||||
Much of MeshCentral revolves around returning objects for your structures, and plugins are no different. Within your plugin you can traverse all the way up to the web server and MeshCentral Server classes to access all the functionality those layers provide. This is done by passing the current object to newly created objects, and assigning that reference to a `parent` variable within that object.
|
||||
|
||||
|
||||
## Ping-Pong
|
||||
|
||||
If you build a plugin which makes use of `meshrelay.ashx`, keep in mind to either handle ping-pong messages (`serverPing`, `serverPong`) on the control channel or to request MeshCentral to not send such messages through sending the `noping=1` parameter in the connection URL. For a deeper sight search for "PING/PONG" in `meshrelay.js`.
|
||||
|
||||
## Versioning
|
||||
|
||||
Versioning your plugin correctly and consistently is essential to ensure users of your plugin are prompted to upgrade when it is available. Semantic versioning is recommended.
|
||||
|
||||
## Changelog
|
||||
|
||||
A changelog is highly recommended so that your users know what's changed since their last version.
|
||||
|
||||
## Sample Plugin
|
||||
|
||||
[MeshCentral-Sample](https://github.com/ryanblenis/MeshCentral-Sample) is a simple plugin that, upon disconnecting from remote desktop, prompts the user to enter a manual event (note), pre-filled in with the date and timestamp.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -7,3 +7,39 @@ The SSH terminal does support color. The issue is going to be the terminal confi
|
|||
```bash
|
||||
ls -al --color /tmp
|
||||
```
|
||||
|
||||
## Fancy config editing with VS Code
|
||||
|
||||
A common problem in the issues is an incorrect config.json. What makes a config incorrect? How can you verify your config is correct?
|
||||
|
||||
Easy! Use Visual Studio Code to edit your config.json and add the schema at the top.
|
||||
|
||||
If you haven't already, download VS code.
|
||||
Download or copy the config.json to your computer.
|
||||
Open config.json in code and add the schema as the top line. This schema is the raw JSON file in the MeshCentral repo.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/Ylianst/MeshCentral/master/meshcentral-config-schema.json",
|
||||
"settings": {
|
||||
"your settings go here": "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you have autocomplete, auto-format and validation for your config.json! If you start typing, Code will show the values that are valid for the location you are editing. Words with a red squiggle line are errors. Words with a orange squiggle line are warnings. Hover over both to see the error message and possible fixes. Code can even format your config.
|
||||
|
||||
While this is a huge step up, it's not perfect. If you notice, there are some invalid keys in the screenshot. This is perfectly valid JSON and MeshCentral will ignore them (maybe?). If you paste some configs into the wrong section, code will not tell you it's in the wrong section. Autocomplete will tell you what keys are valid and the type of the value (i.e. string, number, boolean).
|
||||
|
||||
Hopefully this will help verify your config is syntactically correct and prevent needless formatting errors, misspellings, etc.
|
||||
|
||||

|
||||
|
||||
## Downloading Folders
|
||||
|
||||
If you would like to download folders via Files simply select folder/files then use the zip and download the zip file by clicking on it.
|
||||
|
||||
## Share device groups with AD logins
|
||||
If you would like to share device groups with different AD users.
|
||||
|
||||
In the config.json set "ldapuserkey" to "sAMAccountName".
|
||||
|
|
|
|||
|
|
@ -8,8 +8,18 @@
|
|||
|
||||
## Software Integration Tokens
|
||||
|
||||
Currently, the login tokens in the user manual section 14.1 can't be tracked, deleted or revoked. They are generated with:
|
||||
!!!warning
|
||||
You can only have a SINGLE loginTokenKey for your meshcentral server!<br>
|
||||
So if you regenerate a loginTokenKey, the old one will be revoked/deleted!
|
||||
|
||||
You can create/view the Login Token Key with the following:
|
||||
|
||||
```bash
|
||||
node meshcentral --loginTokenKey
|
||||
node node_modules/meshcentral --loginTokenKey
|
||||
```
|
||||
|
||||
You can then reset/revoke/renew the Login Token Key with the following to create a new one:
|
||||
|
||||
```bash
|
||||
node node_modules/meshcentral --loginTokenKey --loginTokenGen
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# MeshCmd
|
||||
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/info/docs/MeshCmdUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCmd User's Guide v0.0.3.odt?raw=true)
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/docs/MeshCmdUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCmd User's Guide v0.0.3.odt?raw=true)
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# MeshCtrl
|
||||
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/info/docs/MeshCtrlUsersGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCtrl User's Guide v0.0.1.odt?raw=true)
|
||||
MeshCmd Guide [as .pdf](https://meshcentral.com/docs/MeshCtrlUsersGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCtrl User's Guide v0.0.1.odt?raw=true)
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
|
|
@ -274,6 +274,7 @@ devicesharing
|
|||
devicepower
|
||||
indexagenterrorlog
|
||||
agentdownload
|
||||
report
|
||||
```
|
||||
|
||||
You can get this list by just running MeshCtrl without any argument and can get more information on each action by typing “meshctrl help [action]”
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||

|
||||
|
||||
MeshCentral Router Guide [as .pdf](https://meshcentral.com/info/docs/MeshCentral2RouterUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral%20Router%20User%20Guide%20v0.0.2.odt?raw=true)
|
||||
MeshCentral Router Guide [as .pdf](https://meshcentral.com/docs/MeshCentral2RouterUserGuide.pdf) [as .odt](https://github.com/Ylianst/MeshCentral/blob/master/docs/MeshCentral%20Router%20User%20Guide%20v0.0.2.odt?raw=true)
|
||||
|
||||
## Video Walkthru
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ MeshCentral is a remote management web site that connects users to remote comput
|
|||
## Downloading
|
||||
|
||||
MeshCentral router is a Windows application that comes built-into the MeshCentral server or can
|
||||
be downloaded at: <http://info.meshcentral.com/downloads/MeshCentral2/MeshCentralRouter.exe>
|
||||
be downloaded at: <https://meshcentral.com/tools/MeshCentralRouter.exe>
|
||||
|
||||
It’s probably best to use the MeshCentral router that comes with your version of the MeshCentral
|
||||
server as the two will likely be most compatible. A given MeshCentral Router version may not
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ Once enabled, users see the new "CallMeBot" option when trying to enable messagi
|
|||
- [Signal Messenger](https://www.callmebot.com/blog/free-api-signal-send-messages/)
|
||||
- [Whatsapp](https://www.callmebot.com/blog/free-api-whatsapp-messages/)
|
||||
- [Facebook Messenger](https://www.callmebot.com/blog/free-api-facebook-messenger/)
|
||||
- [Telegram](https://www.callmebot.com/blog/telegram-text-messages/)
|
||||
|
||||
Once the user has enabled their account, they can cut & paste the CallMeBot URI into MeshCentral to validate their account.
|
||||
|
||||
|
|
@ -197,12 +198,16 @@ Once setup, the server will offer users the "Pushover" option when setting up me
|
|||
No setup is reqired to enable this. When using ntfy, make sure you use a suffisently random topic name so that others can't guess the name and subscribe to it to receive your messages.
|
||||
|
||||
For [self-hosting your own ntfy server](https://ntfy.sh/docs/install/) with [ACL support](https://ntfy.sh/docs/config/#access-control)
|
||||
|
||||
You can set `host` to the DNS name of your server, `userurl` to the url to provide to users to setup access to your server and `authorization` to the Basic base64 User+Pass authenttication for your server
|
||||
|
||||
```json
|
||||
{
|
||||
"messaging": {
|
||||
"ntfy": {
|
||||
"host": "https://[my]ntfy.sh",
|
||||
"userurl": "https://[my]ntfy.sh/userhelp"
|
||||
"host": "myntfyserver.com",
|
||||
"userurl": "https://myntfyserver.com/userhelp",
|
||||
"authorization": "Basic cGhpbDpteXBhc3M="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,6 +231,21 @@ You can enable the MeshCentral [Zulip](https://zulip.com/) integration with the
|
|||
}
|
||||
```
|
||||
|
||||
## Slack setup
|
||||
|
||||
[Slack](https://slack.com/) integration is achieved by the use of Incoming Webhooks.
|
||||
You can get started by following the Slack guide [here](https://api.slack.com/messaging/webhooks) and getting your URL
|
||||
|
||||
Once you have your incoming webhooks url, You can enable the [Slack](https://slack.com/) integration with the following config.json section
|
||||
|
||||
```json
|
||||
{
|
||||
"messaging": {
|
||||
"slack": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## User Setup
|
||||
|
||||
Once one or more messaging systems are setup with MeshCentral, users will be able to register their handle and verify that they own that account by typing in a 6 digit code.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ nav:
|
|||
- 'Tokens': 'meshcentral/tokens.md'
|
||||
- 'FAQ': 'meshcentral/faq.md'
|
||||
- 'Tips n Tricks': 'meshcentral/tipsntricks.md'
|
||||
- 'Messaging': 'messaging/index.md'
|
||||
- 'Customization': 'meshcentral/customization.md'
|
||||
- 'openidConnectStrategy': 'meshcentral/openidConnectStrategy.md'
|
||||
|
||||
- Design and Architecture:
|
||||
- design/index.md
|
||||
|
|
@ -36,13 +39,16 @@ nav:
|
|||
- Intel AMT:
|
||||
- intelamt/index.md
|
||||
|
||||
- How to Contribute:
|
||||
- how-to-contribute/index.md
|
||||
|
||||
- Other:
|
||||
- other/adfs_sso_guide.md
|
||||
- other/meshcentral_satellite.md
|
||||
|
||||
site_description: "A remote monitoring and management tool"
|
||||
site_author: "Ylianst"
|
||||
site_url: "https://git.meshcentral.com/"
|
||||
site_url: "https://ylianst.github.io/MeshCentral/"
|
||||
|
||||
dev_addr: "0.0.0.0:8010"
|
||||
|
||||
|
|
|
|||
15
emails/translations/account-check_hu.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<html><head></head><body><div>[[[SERVERNAME]]] - Email megerősítés</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Megerősítés</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Tisztelt [[[USERNAME]]], <a href="[[[SERVERURL]]][[[URLARGS1]]]">[[[SERVERNAME]]]</a> e-mail megerősítést kér, kattintson az alábbi linkre a folyamat befejezéséhez.</p>
|
||||
<p style="margin-left:30px">
|
||||
<a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]">Kattintson ide az e-mail címének megerősítéséhez.</a>
|
||||
</p>
|
||||
Ha nem Ön kezdeményezte ezt a kérést, kérjük, hagyja figyelmen kívül ezt a levelet.
|
||||
</div></body></html>
|
||||
6
emails/translations/account-check_hu.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[[[SERVERNAME]]] - Email megerősítés
|
||||
Tisztelt [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]][[[URLARGS1]]]) e-mail ellenőrzést végez. A folyamat befejezéséhez lépjen a következő linkre:
|
||||
~
|
||||
~[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]
|
||||
~
|
||||
Ha nem Ön kezdeményezte ezt a kérést, kérjük, hagyja figyelmen kívül ezt a levelet.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<html><head></head><body><div>更改邮件地址</div>
|
||||
<html><head></head><body><div>[[[SERVERNAME]]] - 邮件验证</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
|
|
@ -11,5 +11,5 @@
|
|||
<p style="margin-left:30px">
|
||||
<a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]">单击此处以验证您的邮件地址。</a>
|
||||
</p>
|
||||
如果您没有发起此请求,请不理此邮件。
|
||||
如果您没有发起此请求,请忽略此邮件。
|
||||
</div></body></html>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
更改邮件地址
|
||||
[[[SERVERNAME]]] - 邮件验证
|
||||
嗨[[[USERNAME]]],[[[SERVERNAME]]] ([[[SERVERURL]]][[[URLARGS1]]]) 正在执行邮件验证。转到以下链接以完成该过程:
|
||||
~
|
||||
~[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]
|
||||
~
|
||||
如果您没有发起此请求,请不理此邮件。
|
||||
如果您没有发起此请求,请忽略此邮件。
|
||||
19
emails/translations/account-invite_hu.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<html><head></head><body><div>[[[SERVERNAME]]] - Fiók meghívó</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Fiók meghívó</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Létrehozásra került Önnek egy fiók a kiszolgálón <a href="[[[SERVERURL]]][[[URLARGS1]]]" notrans="1">[[[SERVERNAME]]]</a>, most a következőkkel érheti el:</p>
|
||||
<p>
|
||||
Felhasználó név: <b notrans="1">[[[ACCOUNTNAME]]]</b><br>
|
||||
Jelszó: <b notrans="1">[[[PASSWORD]]]</b>
|
||||
</p>
|
||||
Üdvözlettel,
|
||||
<br>
|
||||
[[[USERNAME]]]
|
||||
<br>
|
||||
</div></body></html>
|
||||
5
emails/translations/account-invite_hu.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[[[SERVERNAME]]] - Fiók meghívó
|
||||
A [[[SERVERNAME]]] szerveren ([[[SERVERURL]]]/[[[URLARGS1]]]) létrehozásra került egy fiókot az Ön számára, amelyhez most már hozzáférhet a "[[[[ACCOUNTNAME]]]" felhasználónévvel és a "[[[[PASSWORD]]]" jelszóval.
|
||||
~
|
||||
Üdvözlettel,
|
||||
~[[[USERNAME]]]
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Uma conta foi criada para você no servidor <a href="[[[SERVERURL]]][[[URLARGS1]]]" notrans="1">[[[SERVERNAME]]]</a>, você pode acessá-lo agora com:</p>
|
||||
<p>Uma conta foi criada para você no servidor <a href="[[[SERVERURL]]][[[URLARGS1]]]" notrans="1">[[[SERVERNAME]]]</a>, pode aceder agora com:</p>
|
||||
<p>
|
||||
Nome de usuário: <b notrans="1">[[[ACCOUNTNAME]]]</b><br>
|
||||
Senha: <b notrans="1">[[[PASSWORD]]]</b>
|
||||
|
|
|
|||
12
emails/translations/account-login_hu.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<html><head></head><body><div>[[[SERVERNAME]]] - Bejelentkezés a fiókba</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Bejelentkezés a fiókba</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Az Ön bejelentkezési tokenje: [[[TOKEN]]]</p>
|
||||
<p>Ez a token csak egyszer használható, és 5 percig érvényes.</p>
|
||||
</div></body></html>
|
||||
4
emails/translations/account-login_hu.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[[[SERVERNAME]]] - Bejelentkezés a fiókba
|
||||
Az Ön bejelentkezési tokenje: [[[TOKEN]]]
|
||||
~
|
||||
Ez a token csak egyszer használható, és 5 percig érvényes.
|
||||
15
emails/translations/account-reset_hu.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<html><head></head><body><div>[[[SERVERNAME]]] - Fiók visszaállítás</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Megerősítés</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Tisztelt [[[USERNAME]]], <a href="[[[SERVERURL]]][[[URLARGS1]]]">[[[SERVERNAME]]]</a> fiókhoz tartozó jelszó visszaállítási igényt kapott, kattintson a következő linkre a folyamat befejezéséhez.</p>
|
||||
<p style="margin-left:30px">
|
||||
<a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]">Kattintson ide fiókja jelszaváknak visszaállításához.</a>
|
||||
</p>
|
||||
Ha nem Ön kezdeményezte ezt a kérést, kérjük, hagyja figyelmen kívül ezt a levelet.
|
||||
</div></body></html>
|
||||
6
emails/translations/account-reset_hu.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[[[SERVERNAME]]] - Fiók visszaállítás
|
||||
Tisztelt [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]][[[URLARGS1]]]) a fiók jelszavának visszaállítását kéri. A folyamat befejezéséhez lépjen a következő linkre:
|
||||
~
|
||||
~[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]
|
||||
~
|
||||
Ha nem Ön kezdeményezte ezt a kérést, kérjük, hagyja figyelmen kívül ezt a levelet.
|
||||
|
|
@ -11,5 +11,5 @@
|
|||
<p style="margin-left:30px">
|
||||
<a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]">单击此处重置您的帐户密码。</a>
|
||||
</p>
|
||||
如果您没有发起此请求,请不理此邮件。
|
||||
如果您没有发起此请求,请忽略此邮件。
|
||||
</div></body></html>
|
||||
|
|
@ -3,4 +3,4 @@
|
|||
~
|
||||
~[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]][[[URLARGS2]]]
|
||||
~
|
||||
如果您没有发起此请求,请不理此邮件。
|
||||
如果您没有发起此请求,请忽略此邮件。
|
||||
|
|
@ -8,13 +8,13 @@
|
|||
</tr>
|
||||
</tbody></table>
|
||||
<p>
|
||||
Device "<b>[[[DEVICENAME]]]</b>" requested help.
|
||||
Gerät "<b>[[[DEVICENAME]]]</b>" requested help.
|
||||
</p>
|
||||
<p>
|
||||
User: <b>[[[HELPUSERNAME]]]</b><br>
|
||||
Request: <b>[[[HELPREQUEST]]]</b>
|
||||
Benutzer: <b>[[[HELPUSERNAME]]]</b><br>
|
||||
Anforderung: <b>[[[HELPREQUEST]]]</b>
|
||||
</p>
|
||||
<p>
|
||||
<a href="[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]">hier klicken</a> to navigate to this device.
|
||||
<a href="[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]">hier klicken</a> um zu diesem Gerät zu navigieren.
|
||||
</p>
|
||||
</div></body></html>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
[[[SERVERNAME]]] - Device Help Request
|
||||
Device "[[[DEVICENAME]]]" requested assistance.
|
||||
Gerät "[[[DEVICENAME]]]" hat Unterstützung angefordert.
|
||||
|
||||
User: "[[[HELPUSERNAME]]]"
|
||||
Request: "[[[HELPREQUEST]]]"
|
||||
Benutzer: "[[[HELPUSERNAME]]]"
|
||||
Anforderung: "[[[HELPREQUEST]]]"
|
||||
|
||||
[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
<html><head></head><body><div>[[[SERVERNAME]]] - "[[[DEVICENAME]]]" Help Request</div>
|
||||
<html><head></head><body><div>[[[SERVERNAME]]] - "[[[DEVICENAME]]]" Solicitud de Ayuda</div>
|
||||
<div style="font-family:Arial,Helvetica,sans-serif">
|
||||
<table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Help Request</b>
|
||||
<b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Solicitud de Ayuda</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>
|
||||
Device "<b>[[[DEVICENAME]]]</b>" requested help.
|
||||
Dispositivo "<b>[[[DEVICENAME]]]</b>" ha pedido ayuda.
|
||||
</p>
|
||||
<p>
|
||||
User: <b>[[[HELPUSERNAME]]]</b><br>
|
||||
Request: <b>[[[HELPREQUEST]]]</b>
|
||||
Usuario: <b>[[[HELPUSERNAME]]]</b><br>
|
||||
Solicitud: <b>[[[HELPREQUEST]]]</b>
|
||||
</p>
|
||||
<p>
|
||||
<a href="[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]">haz clic aquí</a> to navigate to this device.
|
||||
<a href="[[[SERVERURL]]]?viewmode=10&gotonode=[[[NODEID]]]">haz clic aquí</a> para navegar a este dispositivo.
|
||||
</p>
|
||||
</div></body></html>
|
||||