forked from shaba/openuds
Compare commits
1886 Commits
v2.1
...
nonMultiCl
Author | SHA1 | Date | |
---|---|---|---|
|
1417a66b21 | ||
|
9ba4234313 | ||
|
e85e4c4e54 | ||
|
693d14cd81 | ||
|
4be9e9ea69 | ||
|
e38cd73f30 | ||
|
43b785eb73 | ||
|
9c4a4ed35c | ||
|
02737c0e8d | ||
|
8bbd897cd0 | ||
|
c98933b6ed | ||
|
6e0292e76e | ||
|
8e6fced2ac | ||
|
c5a02686c4 | ||
|
bddb9355c8 | ||
|
25736f61b8 | ||
|
2ee4a7bcaa | ||
|
0da916b57c | ||
|
03012dbaa7 | ||
|
4ae95e1930 | ||
|
906901753e | ||
|
109783a430 | ||
|
655a6447ba | ||
|
52ac406853 | ||
|
55f9820f37 | ||
|
856d645652 | ||
|
fd789581ed | ||
|
a67ba2972b | ||
|
acd1dd4702 | ||
|
b914980793 | ||
|
eab51248cd | ||
|
2834120b35 | ||
|
83a407d350 | ||
|
4f45caa2e9 | ||
|
011145e911 | ||
|
046f5836f7 | ||
|
4424f2a497 | ||
|
97841d655b | ||
|
f20a5a33b0 | ||
|
d1fb59ab77 | ||
|
174d836f45 | ||
|
1b12aee767 | ||
|
a070f6878b | ||
|
d51b22096e | ||
|
9e0fbca339 | ||
|
3e67ef2f6b | ||
|
e7fe802b1d | ||
|
6f90a7ce83 | ||
|
25fec929a9 | ||
|
1abe95c492 | ||
|
d438fcf298 | ||
|
539e96d264 | ||
|
d30a3a5e4c | ||
|
a302541df5 | ||
|
9cdab65845 | ||
|
b9c55437ad | ||
|
0b0c72e65b | ||
|
aef8c637ec | ||
|
4ed3cbc787 | ||
|
60f69be354 | ||
|
8e815c3316 | ||
|
9180d04aaf | ||
|
6e60a66ae9 | ||
|
58cfa779d1 | ||
|
eed4bc5fb7 | ||
|
3ed3f03d25 | ||
|
21f811d995 | ||
|
985746139b | ||
|
9e4a9cc2fd | ||
|
c1d5e4b130 | ||
|
21143ab7f2 | ||
|
b62dfad922 | ||
|
0b3bcbc63d | ||
|
b4a1e6a903 | ||
|
df9cb4eb6a | ||
|
7f4e7e3309 | ||
|
741787f95b | ||
|
7b0ad08685 | ||
|
25b663e069 | ||
|
762e4154fd | ||
|
1bb258d9dc | ||
|
d5f29bd20f | ||
|
69fe9e0d38 | ||
|
41c94913f8 | ||
|
0dce270a9e | ||
|
594d431af7 | ||
|
71242eba10 | ||
|
2dded06dc5 | ||
|
38490e184e | ||
|
9e462478fc | ||
|
44d8b2b754 | ||
|
5884cde35c | ||
|
3b18597d8e | ||
|
f6ddc7eef1 | ||
|
ed61fbf7b8 | ||
|
fb088ecc02 | ||
|
5396d04555 | ||
|
610085e353 | ||
|
16d6e515c6 | ||
|
68b3b50acf | ||
|
9d6560a56e | ||
|
25363269a6 | ||
|
9731e57f01 | ||
|
8aca8ead3d | ||
|
7a030dd302 | ||
|
f184fa778d | ||
|
21f6df36b0 | ||
|
394ceb9e66 | ||
|
5f8abdfa41 | ||
|
b8af381042 | ||
|
e21267b0fd | ||
|
ee7e72cbd3 | ||
|
cb92be3c66 | ||
|
8fc5c759d8 | ||
|
7edf3094ed | ||
|
02c3ffbe75 | ||
|
041942b746 | ||
|
6c936a7dfa | ||
|
0cc40198b2 | ||
|
21ab85818e | ||
|
9789a2f868 | ||
|
e8733e74d1 | ||
|
c6d281580b | ||
|
1050ada43b | ||
|
f39bc9c5ba | ||
|
c987915c41 | ||
|
12b8354a8e | ||
|
a7d8058d09 | ||
|
e7d1df5ba3 | ||
|
e87727b48f | ||
|
13ec1877de | ||
|
1b4060a727 | ||
|
d8e713ad51 | ||
|
cdca39779b | ||
|
1345593c3a | ||
|
265d4f5103 | ||
|
b85a702437 | ||
|
e2d7fb0790 | ||
|
a573d2d55b | ||
|
de50fef63c | ||
|
a017807d2a | ||
|
0133ddc2b5 | ||
|
f8c9dd25df | ||
|
cddfd735b2 | ||
|
3f6d12c89f | ||
|
98293bba75 | ||
|
469940074c | ||
|
e920628395 | ||
|
07738e3dc2 | ||
|
2b5543905a | ||
|
87c2ea8add | ||
|
2a2a2b2ad0 | ||
|
47ef12ef6a | ||
|
451b8f6fb9 | ||
|
bd2b0cd171 | ||
|
9a7afe7839 | ||
|
18a8c81af6 | ||
|
84ca0b2e41 | ||
|
09c65b2598 | ||
|
6e438bf4cb | ||
|
7502fe3bcc | ||
|
e9a719a2eb | ||
|
ce73d4e29f | ||
|
ffeaf9e22c | ||
|
5355c7e420 | ||
|
1e184a3a34 | ||
|
74d4349266 | ||
|
26c9f0edc8 | ||
|
797a5df4a9 | ||
|
8fbdda7b56 | ||
|
d3c14520d4 | ||
|
9f04bdab05 | ||
|
5597af7d85 | ||
|
697c3e1c52 | ||
|
fa32d485c9 | ||
|
ae7f867482 | ||
|
f595219405 | ||
|
0fd24dadff | ||
|
d66e59bd50 | ||
|
cc12b7d5f6 | ||
|
d67a9d6ddc | ||
|
5ff3c58149 | ||
|
9340e3c3c1 | ||
|
4357ef3be8 | ||
|
afd36886d6 | ||
|
a3905c0c6c | ||
|
03fc488f33 | ||
|
2aee4e9417 | ||
|
b8494f51ac | ||
|
93a12c180e | ||
|
26aa9f6db7 | ||
|
3f881b3e17 | ||
|
0a0b4cb740 | ||
|
eb3638d62a | ||
|
5df8f640d8 | ||
|
8c68da806a | ||
|
b9ba304493 | ||
|
f7b8e644f9 | ||
|
034021522d | ||
|
52d3ffeac3 | ||
|
868ff2817a | ||
|
ba716be0f3 | ||
|
51916e0949 | ||
|
1505fd346b | ||
|
e517281c6a | ||
|
49ee75d986 | ||
|
c90f9c40fd | ||
|
2acc72a1f7 | ||
|
c6213ff37c | ||
|
81d8544f5f | ||
|
35b0d709f6 | ||
|
3908c875d3 | ||
|
c28c6c7b98 | ||
|
fe3fd6c35b | ||
|
d11a010562 | ||
|
a035633b58 | ||
|
ef24656e29 | ||
|
281adedd3b | ||
|
688acb0631 | ||
|
0bc1f72dc8 | ||
|
8a8df3de35 | ||
|
5d52061041 | ||
|
0e20ccc19c | ||
|
190079fddc | ||
|
52f8dc7fde | ||
|
9f44e7fd25 | ||
|
8af6cc008b | ||
|
ff685119ae | ||
|
388cb2644b | ||
|
5f30e083b1 | ||
|
bda4057173 | ||
|
017aa46403 | ||
|
7aec9a116e | ||
|
f57fea4699 | ||
|
a2a8f157e0 | ||
|
d52bc68015 | ||
|
eb8f300c0e | ||
|
8ab1342775 | ||
|
f602d641a0 | ||
|
9cd084a222 | ||
|
3e07cf53e4 | ||
|
2968bc7d41 | ||
|
6a209c0836 | ||
|
6b131681cd | ||
|
9568a9b180 | ||
|
d7a8a441ad | ||
|
91fcbe7336 | ||
|
2fd5b40809 | ||
|
4e161b15f4 | ||
|
7985c0f9d0 | ||
|
328d35a289 | ||
|
af52727862 | ||
|
d474f02baf | ||
|
672897f828 | ||
|
073ce3df12 | ||
|
09125bb1fa | ||
|
041ff932e4 | ||
|
f3e7e21149 | ||
|
348258daf2 | ||
|
d1f83e4ae4 | ||
|
49a6e01477 | ||
|
9f2354191c | ||
|
6804982b0b | ||
|
857f8602b8 | ||
|
584dee9fcd | ||
|
e7bf7b0258 | ||
|
a3ced4af30 | ||
|
46d056de5d | ||
|
b6cae240a7 | ||
|
92e13c48de | ||
|
d93e5dc566 | ||
|
99a96bf343 | ||
|
0b8a9444d1 | ||
|
cea271a2ce | ||
|
d2d190e8a4 | ||
|
5b8ff497fa | ||
|
ae6d36b86a | ||
|
600f50f203 | ||
|
04d0acb17e | ||
|
caf1d5d825 | ||
|
99d3393a33 | ||
|
80bd2c2f9c | ||
|
1d06bd02c0 | ||
|
41991590ca | ||
|
4313368f78 | ||
|
50660d92e5 | ||
|
c796f5aaac | ||
|
9e88ff5daa | ||
|
cb5a6f2430 | ||
|
0f87c022f3 | ||
|
69f1c88c3d | ||
|
6fc6fa0fe1 | ||
|
f634d4ef1a | ||
|
f933181369 | ||
|
99e6ec8090 | ||
|
f0b6726e19 | ||
|
8424c14052 | ||
|
97f709bf52 | ||
|
c26c8d9df9 | ||
|
9f81d0a066 | ||
|
bb626889fb | ||
|
d8fb0deef2 | ||
|
743773e256 | ||
|
4adc058e1a | ||
|
f364b283e6 | ||
|
7e4975be99 | ||
|
a2df121e45 | ||
|
f402dadb0a | ||
|
865601b3c8 | ||
|
20f2e4fd27 | ||
|
0da51dda92 | ||
|
7c9c510ca0 | ||
|
7ae9df21a5 | ||
|
2fd1dc5fc9 | ||
|
a4986d3b4d | ||
|
40abfb6014 | ||
|
d6a8639b18 | ||
|
971e5984d9 | ||
|
e486d6708d | ||
|
f0bd3782d7 | ||
|
7e9dde66ac | ||
|
68d7ded5bc | ||
|
fa05d9425e | ||
|
75221a4842 | ||
|
4cc11d783a | ||
|
105a6af885 | ||
|
e5a38a65ed | ||
|
dcdea31061 | ||
|
6b3d222a12 | ||
|
8719896f62 | ||
|
bb71dd1cd3 | ||
|
a95c69a77e | ||
|
57d8b01757 | ||
|
66fb59a13b | ||
|
78372e593a | ||
|
1d7c57eb2f | ||
|
b1c4385090 | ||
|
501565c088 | ||
|
f2d55d6141 | ||
|
620e8fee94 | ||
|
933ba772d9 | ||
|
e9a4da5acc | ||
|
45461844b1 | ||
|
522a5ebfb7 | ||
|
a70e7a269b | ||
|
6868e471c5 | ||
|
4866b8947c | ||
|
9e4922ba79 | ||
|
7a377b0065 | ||
|
b830b0ee0a | ||
|
dcf5ea8574 | ||
|
2251618c69 | ||
|
a6876de0b1 | ||
|
58a70e368e | ||
|
50f3b79ee3 | ||
|
38a625452b | ||
|
4183069cec | ||
|
905b1e7589 | ||
|
024bb5e748 | ||
|
e4345dfefa | ||
|
6c54f8e75a | ||
|
45ca92b77e | ||
|
06b0f1396f | ||
|
c698300096 | ||
|
482cc4b2ae | ||
|
ed15178549 | ||
|
2f67eacfb6 | ||
|
b8e7dc07c3 | ||
|
52f524eb61 | ||
|
637519a162 | ||
|
65b47686db | ||
|
1ab534c3aa | ||
|
8cd5437429 | ||
|
a475d45b7b | ||
|
4f3792ced5 | ||
|
3e061275b4 | ||
|
22415b98cd | ||
|
d5f84a4209 | ||
|
302b9a85d5 | ||
|
08038b5b90 | ||
|
6d9a6baa2a | ||
|
f3e2338cfb | ||
|
fb69866f89 | ||
|
cd4708296e | ||
|
c3915fd8fe | ||
|
74ad50d7d8 | ||
|
00dc4c5a7b | ||
|
a495f36c43 | ||
|
f69a9dbc82 | ||
|
253896e995 | ||
|
65879f4bd3 | ||
|
a094e1ebee | ||
|
c35dc90264 | ||
|
1be48b99d4 | ||
|
cab09aea9c | ||
|
da23222f0f | ||
|
5d604bb629 | ||
|
9d19aebdf6 | ||
|
13de581b80 | ||
|
76f4df5aa1 | ||
|
8634ce50c6 | ||
|
758f409372 | ||
|
edd6714cd0 | ||
|
a7f9880816 | ||
|
d9bbbb35eb | ||
|
5bf8e74018 | ||
|
32c496e88a | ||
|
f5b59889fc | ||
|
538779e3c1 | ||
|
6ced042153 | ||
|
e9f74d9ccc | ||
|
9815e21524 | ||
|
666ae4e1d3 | ||
|
3e8a3efb75 | ||
|
4094818ccc | ||
|
6f5f17326e | ||
|
43acedf7f6 | ||
|
37f06617b8 | ||
|
dd39bb4e64 | ||
|
6dc6f5954b | ||
|
bafd3bc6b3 | ||
|
48e0577e9f | ||
|
61750f2f94 | ||
|
cb05113d88 | ||
|
afc4fd39ef | ||
|
9c991fd8ef | ||
|
33502140cf | ||
|
d840066468 | ||
|
63280bf9cb | ||
|
e8698316c6 | ||
|
1d90f04245 | ||
|
a486e68e39 | ||
|
76e5aede37 | ||
|
bbae1dca44 | ||
|
1afe118481 | ||
|
35c4e90be8 | ||
|
b792c630c6 | ||
|
958165c64f | ||
|
2811521937 | ||
|
792db7ba6c | ||
|
5740ec7e3e | ||
|
3cbb0675a6 | ||
|
bfd5f12f82 | ||
|
b5b2d20c27 | ||
|
cf2c984065 | ||
|
e38c05286f | ||
|
5355cc5a51 | ||
|
1789aee1e7 | ||
|
e184961eba | ||
|
10b8f3418f | ||
|
60b1fabcaf | ||
|
b492e7674c | ||
|
3d0a9f758f | ||
|
a2aef2fba0 | ||
|
9d6fd8a1ab | ||
|
547607452a | ||
|
25d24f8102 | ||
|
9e77934136 | ||
|
78129c9569 | ||
|
2c27061534 | ||
|
d46c806ae3 | ||
|
b35ffbbf58 | ||
|
407dff41ea | ||
|
2627f96499 | ||
|
a0ba19010c | ||
|
7650f3239a | ||
|
35f932e88d | ||
|
fc188b5b9c | ||
|
bbc211ac8c | ||
|
fbd3d36461 | ||
|
a527ff326d | ||
|
6a9b764e1e | ||
|
06b7b58dd2 | ||
|
9560ee0699 | ||
|
cf88eb4c4b | ||
|
f9154b57a7 | ||
|
6e6384fb00 | ||
|
f0caabf814 | ||
|
110f1c23f1 | ||
|
0d63dabe9f | ||
|
e1b4825726 | ||
|
5e4114c719 | ||
|
5c5ddd147c | ||
|
d46eebc581 | ||
|
4216d1553a | ||
|
1e42bf6cbb | ||
|
416b5e738f | ||
|
2529039f49 | ||
|
0ba8e83588 | ||
|
e58b3e52e9 | ||
|
3e72f634d4 | ||
|
b61fd61e0e | ||
|
39cf5b1ff7 | ||
|
adfed4f89d | ||
|
27f867f71a | ||
|
73445c516b | ||
|
247bb2e549 | ||
|
4a8fdfe09d | ||
|
5a3a9e2e42 | ||
|
ff4ff7899c | ||
|
a34e8c2c9d | ||
|
9a9afae79a | ||
|
a3c31bcfb7 | ||
|
060b4fce98 | ||
|
fc090497cc | ||
|
44456213f7 | ||
|
5d76b3269b | ||
|
786242d1cd | ||
|
fc856ad7fb | ||
|
aecd933c98 | ||
|
5602cca2b5 | ||
|
50fc172fd7 | ||
|
0664968352 | ||
|
0333dbd51a | ||
|
6e19d59bdf | ||
|
fe7bbe8799 | ||
|
c53dbd6714 | ||
|
3e8757e908 | ||
|
e1d66db7ea | ||
|
c38a68f4f6 | ||
|
db373979c2 | ||
|
606e65eea4 | ||
|
4e3dce37f7 | ||
|
668c26dd8f | ||
|
735a093b0b | ||
|
8a49e7c437 | ||
|
03e3517baf | ||
|
db5ac8b12e | ||
|
78dbd966b1 | ||
|
cff8977c63 | ||
|
a648f5686e | ||
|
2fbdef1739 | ||
|
7d4243d72a | ||
|
c520a63800 | ||
|
12ce031217 | ||
|
3ead78adb9 | ||
|
b238bba7a4 | ||
|
416a11c0ed | ||
|
c286beac9f | ||
|
a31411fbf0 | ||
|
675e335aa4 | ||
|
31ee1d2858 | ||
|
19edf417ba | ||
|
510b0cb32a | ||
|
f9ada8349f | ||
|
5e177059fe | ||
|
df86e8742c | ||
|
526229b9cf | ||
|
8d39728ede | ||
|
5bb2cfdf8a | ||
|
640de2e7b7 | ||
|
2f615358e3 | ||
|
7c5cbe6604 | ||
|
7c4ebd90aa | ||
|
2d2030f8d5 | ||
|
243c89eb6d | ||
|
6de8ccb018 | ||
|
f52b224a38 | ||
|
909e706bbd | ||
|
0fcbf8dc6a | ||
|
bda2232629 | ||
|
308a9c68ab | ||
|
e7a403d60e | ||
|
14ddcfe797 | ||
|
afcb96d9b7 | ||
|
96945e7834 | ||
|
fdd241194d | ||
|
0dc78fc376 | ||
|
4b3c3cc183 | ||
|
69c4efe793 | ||
|
3c85ba692e | ||
|
7ce2ac8bd7 | ||
|
062b02a409 | ||
|
e3cb1ffaf1 | ||
|
7312c1ee6b | ||
|
d296761ada | ||
|
da8d885107 | ||
|
9d64f7a539 | ||
|
f01d4302cf | ||
|
d9a948df38 | ||
|
5df07875f9 | ||
|
c23f95e903 | ||
|
6f8b67f3b8 | ||
|
7a6476569d | ||
|
1638243d84 | ||
|
74c5643bcf | ||
|
7bc87c39a7 | ||
|
bf4a6e24d2 | ||
|
0aa65e71b4 | ||
|
4985747c97 | ||
|
8d9b8d9234 | ||
|
7b623bd986 | ||
|
72efd559c4 | ||
|
24f77d37d4 | ||
|
a982d64190 | ||
|
56eefa10a9 | ||
|
779dfe1fc4 | ||
|
d0e7dde571 | ||
|
7ee137b00e | ||
|
1a42daa8e3 | ||
|
a630f1fc14 | ||
|
9454b9f63f | ||
|
b48bea14d2 | ||
|
1959051fe4 | ||
|
6faf6ceaca | ||
|
7ec2197634 | ||
|
7a9dbc3edd | ||
|
944bc0041b | ||
|
a7b33bb992 | ||
|
06f4c933ed | ||
|
b1e1aebdf0 | ||
|
36609eaff3 | ||
|
71d6e389ea | ||
|
1983a218cb | ||
|
2fea5d1526 | ||
|
cede2c48e9 | ||
|
f306449b0e | ||
|
786e419b27 | ||
|
6ff997b054 | ||
|
e0a512eefb | ||
|
fcea278d2f | ||
|
aee16dd075 | ||
|
42b06ff688 | ||
|
158564552e | ||
|
5b9fe17353 | ||
|
5ea3949d4c | ||
|
aa3744094b | ||
|
222ed0e9b0 | ||
|
cf76d7ae76 | ||
|
4dec86fe9d | ||
|
62313ae67f | ||
|
aba2995371 | ||
|
66e3dc11ea | ||
|
f1e8011f42 | ||
|
616df1f0f2 | ||
|
65bb91c449 | ||
|
c69512bf8b | ||
|
1e03a5cf6e | ||
|
8c3ca38b3d | ||
|
cb8461b06c | ||
|
559c5c76ff | ||
|
a5b164930e | ||
|
92b4e2c898 | ||
|
eba6a7be7a | ||
|
b824c5431b | ||
|
f409b2986c | ||
|
ad4ea61613 | ||
a8d0081fd1 | |||
|
cdd3cd546f | ||
|
3d2423f0bf | ||
|
612d05a408 | ||
|
ab84330c7b | ||
|
bd28300324 | ||
|
e758ec36ff | ||
|
c1adf35c99 | ||
|
9aaab57a6a | ||
|
e38151a349 | ||
|
0b61e6096f | ||
|
2e9b7046ff | ||
|
4b3840f9c3 | ||
|
44632a497e | ||
|
e5967b7ff3 | ||
|
57bc347625 | ||
|
54bd713de6 | ||
|
2d3b3af978 | ||
|
7ea139163d | ||
|
e93f2951be | ||
|
7c37d37418 | ||
|
aee5b4501a | ||
|
c597b307b5 | ||
|
4c77d1ac78 | ||
|
40a820e770 | ||
|
1c6d58a53d | ||
|
b2727e700b | ||
|
0de8ce3000 | ||
|
9e32996fa8 | ||
|
addf24a30a | ||
|
25d36a64fa | ||
|
cc9326fc95 | ||
72a6c28549 | |||
|
d7e30d149c | ||
|
b8c37b279a | ||
|
06bd7bb736 | ||
|
c1a128335c | ||
|
f9163b4e10 | ||
|
359857b9b3 | ||
|
43fbfdab3d | ||
|
173caa250e | ||
|
5e971db73f | ||
|
aaa2ce825e | ||
|
663c8f762a | ||
|
6ead895bdf | ||
|
7f0e3f0541 | ||
|
fd90648874 | ||
|
3e67852d8a | ||
|
099a9ba3c1 | ||
|
1d807784d2 | ||
|
69085f28da | ||
|
15cfa45f69 | ||
|
f9a5b32993 | ||
|
e84965bb84 | ||
|
a8e58db3bd | ||
|
7c384409f7 | ||
|
ff64d21137 | ||
|
d6019384ef | ||
|
92b4aadfbb | ||
|
52437afcf8 | ||
|
56492ccd2d | ||
|
23bbda845c | ||
|
b3c9f07d36 | ||
|
a86cbe46d6 | ||
|
8e73d38ea3 | ||
|
841d088f6a | ||
|
f33987e62e | ||
|
273e394937 | ||
|
1df17f99ec | ||
|
8a090049c7 | ||
|
2b912b035d | ||
|
34d47bdda9 | ||
|
06b0e86a39 | ||
|
c7c685865e | ||
|
9bdaf4eab1 | ||
|
0282086065 | ||
|
5e7502f221 | ||
|
ffeabe15ee | ||
|
3bc08d6b86 | ||
|
2b4a0113a2 | ||
|
ac0be2f4aa | ||
|
e13c8a93b3 | ||
|
d33e5e3a1e | ||
|
8b991eae12 | ||
|
d11ffbd401 | ||
|
903ccf2e0a | ||
|
b0d4be83a4 | ||
|
efe7bb2a53 | ||
|
56376ecd56 | ||
|
4ecb520496 | ||
|
5b8e346788 | ||
|
211b227105 | ||
|
ac03d142d9 | ||
|
8549f3cfa8 | ||
|
cef382a1df | ||
|
f47b208c33 | ||
|
63824cdeee | ||
|
907402229e | ||
|
929903342f | ||
|
f9b284ff8e | ||
|
138f97e317 | ||
|
6385e24e18 | ||
|
dcc87b5f8c | ||
|
08c15b6f60 | ||
|
3fafef3b41 | ||
|
49f76a5def | ||
|
501ed22ae6 | ||
|
de827f6bd5 | ||
|
820ba906f0 | ||
|
9fd3ac280b | ||
|
9b2dd802d7 | ||
|
1518cc0294 | ||
|
d8826c2841 | ||
|
8678fa8f79 | ||
|
5229caead5 | ||
|
142f7122fc | ||
|
26e429019e | ||
|
dcf9e0137a | ||
|
525fa6e71c | ||
|
45429027bf | ||
|
7cc6c6d3b8 | ||
|
b3324eec91 | ||
|
099c0d9861 | ||
|
f888d1da61 | ||
|
69c4fd6325 | ||
|
a4353dde86 | ||
|
ddc2699774 | ||
|
8cded2bca7 | ||
|
fb3808991d | ||
|
c2da7211c3 | ||
|
8e76c1d733 | ||
|
c8839ed2a5 | ||
|
52208d18ae | ||
|
a66a14a63e | ||
|
7801cf85c0 | ||
|
7bfa539e70 | ||
|
cf40396211 | ||
|
faa7c37336 | ||
|
4d2f7c1b95 | ||
|
7483a6f04f | ||
|
3e905381ff | ||
|
9150e71840 | ||
|
33d2dbbc52 | ||
|
fcb252deae | ||
|
93b1e71440 | ||
|
293cb129ed | ||
|
65d4b0b409 | ||
|
12f39853e4 | ||
|
fb297d05c3 | ||
|
97e84c968a | ||
|
0ac3fa1987 | ||
|
633671d12c | ||
|
1ed4c1ad57 | ||
|
2509f41c85 | ||
|
91272fcbb8 | ||
|
0895d2feaf | ||
|
ff5601ce12 | ||
|
b78a41f0d3 | ||
|
6402106d85 | ||
|
3ed5d26245 | ||
|
72efa3221f | ||
|
aabb08cad4 | ||
|
78953ef5d9 | ||
|
a6cb350454 | ||
|
20bceadda9 | ||
|
4231eb5b9d | ||
|
d53d092483 | ||
|
371cc3d14b | ||
|
8052809e86 | ||
|
ced7226f81 | ||
|
a8b94325e5 | ||
|
79f61098af | ||
|
e5b4fb393f | ||
|
6f3f573f61 | ||
|
cf6a03b585 | ||
|
4cdbe5abae | ||
|
2c8963ba5f | ||
|
e76df6349d | ||
|
282816b4eb | ||
|
e967d994b1 | ||
|
45b827e9e9 | ||
|
9e66503f0e | ||
|
52335b6e31 | ||
|
d6691a4098 | ||
|
bef9776a6b | ||
|
07d337364d | ||
|
2cf869150f | ||
|
4df7c18ddc | ||
|
09000f8b00 | ||
|
f517f73a0b | ||
|
04d204883b | ||
|
37229e92e7 | ||
|
c7dc116980 | ||
|
ede5d4b790 | ||
|
4a5669bdef | ||
|
ef7bbebec8 | ||
|
7e034a9de1 | ||
|
8d680ad32d | ||
|
76c47f7c76 | ||
|
03e31f31b1 | ||
|
d67a1cd70c | ||
|
dc381565cf | ||
|
dc0511045b | ||
|
b78bcbd726 | ||
|
6038f93189 | ||
|
ee42653cea | ||
|
eb2ad9da19 | ||
|
928360444b | ||
|
5d26833779 | ||
|
d00057a03d | ||
|
6da8d2057e | ||
|
103ad93cdf | ||
|
ce91840622 | ||
|
99fe68608c | ||
|
2dc1634004 | ||
|
9cb9fc6de1 | ||
|
19754f7ed7 | ||
|
6706866174 | ||
|
ba40b38998 | ||
|
4e647c1d7a | ||
|
b02e8e1f30 | ||
|
7aee4efb15 | ||
|
4c9ad55d99 | ||
|
a2dd2cccc9 | ||
|
de5411d153 | ||
|
d916d9ea1f | ||
|
015cde5301 | ||
|
3f88884a82 | ||
|
fea0f6aa34 | ||
|
a393e0b5a3 | ||
|
fbff259691 | ||
|
8908d1de9d | ||
|
f53fa4a304 | ||
|
db2a43afc8 | ||
|
66f498e7ed | ||
|
a32bb87a78 | ||
|
d48e71cfd3 | ||
|
76a17b19df | ||
|
c2b2cab228 | ||
|
fdb630dc8e | ||
|
5abd8184dc | ||
|
7b4563b359 | ||
|
3c4f026a21 | ||
|
e11009332e | ||
|
a06fc617f0 | ||
|
917dd33349 | ||
|
bdc6f2efb2 | ||
|
346c3a3d33 | ||
|
99bfae10ad | ||
|
01317df1f7 | ||
|
3e51037160 | ||
|
ccea5e64d3 | ||
|
ce5251178b | ||
|
7be3838e41 | ||
|
a0ef9c8cbe | ||
|
a25d33a615 | ||
|
b955d0327d | ||
|
6ae4380cf2 | ||
|
e69497c8cb | ||
|
c736f82697 | ||
|
56367c58f4 | ||
|
ebbdbedaf6 | ||
|
b6391b10a5 | ||
|
ce71a2e6e7 | ||
|
69e456822e | ||
|
0afcdc3bd0 | ||
|
3850b2e01f | ||
|
265578409e | ||
|
bde6ac422b | ||
|
4a8a2d05ee | ||
|
85ef989a4c | ||
|
2d05062241 | ||
|
f43a8a3bf4 | ||
|
7f9b4c9274 | ||
|
061ac326df | ||
|
899df5a6a6 | ||
|
29cb8b6a39 | ||
|
18826708c8 | ||
|
726cc33320 | ||
|
ecdc4fa606 | ||
|
72df9b2275 | ||
|
145ab9b865 | ||
|
d758aecd1e | ||
|
fd6db8d618 | ||
|
6867a278bd | ||
|
572e2b9a51 | ||
|
ce04b3949d | ||
|
4cd051f50a | ||
|
6eccb757ad | ||
|
51b16ce895 | ||
|
fbcf635a60 | ||
|
da0df6d407 | ||
|
060e2cd023 | ||
|
420e491196 | ||
|
fbd2bd937c | ||
|
79f41b3e1a | ||
|
f464d78f99 | ||
|
6e7b5229d1 | ||
|
446a4a3cb4 | ||
|
f5278aa57f | ||
|
83726be209 | ||
|
bada5ed2af | ||
|
75f244be2e | ||
|
a42ccd0ca6 | ||
|
9464e1794e | ||
|
c502127aa5 | ||
|
97e5ff1b6a | ||
|
a69bda8673 | ||
|
b233e26a52 | ||
|
2b733c0e9e | ||
|
da0a2401f3 | ||
|
22b079cf53 | ||
|
23834328dd | ||
|
a8f2dcb03d | ||
|
80591c541b | ||
|
beb09115c2 | ||
|
9a9eee9d5e | ||
|
5ffc66a71e | ||
|
104eeac53c | ||
|
c980d42b1e | ||
|
909ff33851 | ||
|
be7d177bdc | ||
|
6e39c1a2dc | ||
|
d5ac86cfba | ||
|
e836a142fe | ||
|
1b3e1d9bcc | ||
|
882a8ad2c9 | ||
|
171b5f5a48 | ||
|
e2d1d17fa9 | ||
|
217a51728e | ||
|
bec85b5463 | ||
|
1110a5bd63 | ||
|
ffc3d14daf | ||
|
c9c4a8f578 | ||
|
2b06813176 | ||
|
49fd063e90 | ||
|
75b470a7d1 | ||
|
377b4542d7 | ||
|
ee34f6c624 | ||
|
4be27489ad | ||
|
e3912276f4 | ||
|
b4a09a4c10 | ||
|
77c6857973 | ||
|
c13da05baa | ||
|
d108de9720 | ||
|
1ace92083e | ||
|
fb32c8b86a | ||
|
efbca0bf92 | ||
|
1c9ba0738a | ||
|
445c890209 | ||
|
1072e1f883 | ||
|
1e789132a5 | ||
|
a6bf2c4cfc | ||
|
642009425f | ||
|
a992246203 | ||
|
52054ae45b | ||
|
064423413b | ||
|
de24aaab93 | ||
|
b55669d910 | ||
|
6fac308d80 | ||
|
e103fb02d2 | ||
|
72a93e774d | ||
|
e484c31b38 | ||
|
5157dce173 | ||
|
cc7fbbf5f1 | ||
|
4f35ba2cca | ||
|
45b2a3b66f | ||
|
bbb6792961 | ||
|
3588ea20f4 | ||
|
999a23fb6d | ||
|
47721db3f5 | ||
|
5fb7c830b2 | ||
|
82ef65a26c | ||
|
a948188187 | ||
|
54110f5425 | ||
|
7d66d92f85 | ||
|
c8831b1bba | ||
|
c6b9bd4643 | ||
|
15f4db636e | ||
|
668cd82d9e | ||
dae085dc6a | |||
|
30e041b34b | ||
|
38d6a7e6c6 | ||
|
e7fb7064e3 | ||
|
45aa2e92ac | ||
|
7cf4659f5f | ||
|
45db8592b7 | ||
|
e9f03f2075 | ||
|
59f0b1c572 | ||
|
bae405d082 | ||
|
e76695e583 | ||
|
1e74c55d2b | ||
|
70ec9ab3ea | ||
|
a5929a0af3 | ||
|
f343368da9 | ||
|
7dd57cd778 | ||
|
6309047a34 | ||
|
1b3ff5661b | ||
|
8f0da29a99 | ||
|
bdc92052ae | ||
|
1df42462fd | ||
|
0b97c15ca8 | ||
|
feeeaed1a9 | ||
|
ccdedb9126 | ||
|
64ddba25f4 | ||
|
36794ec5cf | ||
|
89515dce33 | ||
|
613f4efd31 | ||
|
2baac2f532 | ||
|
37ae7af508 | ||
|
d068bcef60 | ||
|
7540ca977f | ||
|
a3bbd00531 | ||
|
6ab3bad34b | ||
|
bb6f21248b | ||
|
39a75bfcc0 | ||
|
20ff8cafdc | ||
|
41b849c1f5 | ||
|
4c001b044f | ||
|
a9b4798155 | ||
|
7c92d8ad5c | ||
|
8a7ceb926d | ||
|
70141ae972 | ||
|
20ead2954b | ||
|
8331c886de | ||
|
14da66291e | ||
|
3415cb8604 | ||
|
66c217a988 | ||
|
d6935d0210 | ||
|
a1866fb56c | ||
|
c47501e3e6 | ||
|
a18f85b4f1 | ||
|
9a816edcc0 | ||
|
3568f43fc8 | ||
|
3b05a06336 | ||
|
dfa6fe04cc | ||
|
111660b396 | ||
|
c517e746da | ||
|
68f63a02f3 | ||
|
4dc4c27925 | ||
|
363d247c8b | ||
|
842595be95 | ||
|
b248a284bb | ||
|
31d2775f4d | ||
|
bb84b64eb2 | ||
|
a7116b9e2e | ||
|
2b9d625b5d | ||
|
d16cc757af | ||
|
1a43ce16e4 | ||
|
deff947b03 | ||
|
436fe43098 | ||
|
7a2bd7dcf5 | ||
|
3caa49705e | ||
|
6ab3fd5559 | ||
|
d6d6a4b44b | ||
|
b1a8d9b84c | ||
|
272588f306 | ||
|
87e7f1ca3c | ||
|
d655b0f41a | ||
|
5422dcf78d | ||
|
09eb130b11 | ||
|
952322556c | ||
|
194e232252 | ||
|
9fee4adcc2 | ||
|
ea11a25c52 | ||
|
f79f659711 | ||
|
9130a12e52 | ||
|
fddc69e0ea | ||
|
30c445bef0 | ||
|
47d234e1c5 | ||
|
83290f0a29 | ||
|
b795260e4d | ||
|
ea6d68ee93 | ||
|
3fdb089b48 | ||
|
ebf794cb67 | ||
|
45bf24ecdd | ||
|
66464df043 | ||
|
5d4e05309a | ||
|
a288dd4df3 | ||
|
6156f2c6f6 | ||
|
27a29d92e0 | ||
|
6a7d10bf1d | ||
|
6ec73b336d | ||
|
b1f3407299 | ||
|
2b653b8b05 | ||
|
cd7beac87e | ||
|
6d50f99ad6 | ||
|
f4f297a1e4 | ||
|
dbee05d1bc | ||
|
cbfa9516bf | ||
|
7910327a8a | ||
|
9f01f2c368 | ||
|
9e26580af5 | ||
|
f5abeb0a94 | ||
|
689b20ce2f | ||
|
bac0b9755e | ||
|
33d2ca4ece | ||
|
4e0c3b0c5d | ||
|
d4aa2cc8e8 | ||
|
c7c1103ed1 | ||
|
083b1f21fd | ||
|
d7ac1f590a | ||
|
d523ae6001 | ||
|
581b13e70e | ||
|
6889098ec8 | ||
|
683728fbc1 | ||
|
30ed88aefa | ||
|
7b30f58750 | ||
|
0155f6c7fd | ||
|
4509400605 | ||
|
e88256a892 | ||
|
64faeec13c | ||
|
b5352596e8 | ||
|
c1e8f6afe1 | ||
|
2641fbbfcc | ||
|
923dbcc45b | ||
|
0d4f2122c6 | ||
|
b55c9756e8 | ||
|
cc1d022f40 | ||
|
d501d6037f | ||
|
5353573ca3 | ||
|
26af865483 | ||
|
2bd4f3d8e5 | ||
|
efb3965d76 | ||
|
1a0c27f23c | ||
|
1b0f406de4 | ||
|
21a5f4de09 | ||
|
4778f5f931 | ||
|
29d0c242f7 | ||
|
78656ed4a2 | ||
|
c9a08cf1a1 | ||
|
278bcb87de | ||
|
690fd7c9cc | ||
|
7b51bb7985 | ||
|
6f81892324 | ||
|
f603bd6c8f | ||
|
a496fd97f1 | ||
|
77bf401bd5 | ||
|
812e863e7f | ||
|
cd2d529524 | ||
|
445bbc6ee6 | ||
|
cc9cf15c69 | ||
|
42ded5c382 | ||
|
8df513fbd3 | ||
|
164654143d | ||
|
d9ad218e41 | ||
|
8f76363b27 | ||
|
6a05403464 | ||
|
ac0eefc683 | ||
|
43398cd125 | ||
|
1da058cec6 | ||
|
c5d1702758 | ||
|
fb818f06a5 | ||
|
eae79364b8 | ||
|
49564147a3 | ||
|
481eca7d16 | ||
|
e382bfa19b | ||
|
9094547f22 | ||
|
c2bd664545 | ||
|
04b68401cf | ||
|
6300da4697 | ||
|
8b4485d533 | ||
|
94501b1106 | ||
|
696ad047a3 | ||
|
682ce422b6 | ||
|
906824cafc | ||
|
6f6633b819 | ||
|
eb6065b203 | ||
|
9451889bb3 | ||
|
8120ae5c92 | ||
|
d1bb018dff | ||
|
694d60cb88 | ||
|
a2a43fd041 | ||
|
43cb02b177 | ||
|
01cc3eb147 | ||
|
3e3ea59261 | ||
|
462e53efea | ||
|
589b12f490 | ||
|
688a91325c | ||
|
3c905e8bc4 | ||
|
97e8c42b2f | ||
|
e814c47a24 | ||
|
9fb19c6b9d | ||
|
4375ee6d50 | ||
|
de45c24bbc | ||
|
ff2f508240 | ||
|
f2ff09a882 | ||
|
68a58b7c73 | ||
|
4441286c89 | ||
|
0d97831b71 | ||
|
db7d3ef0e3 | ||
|
d0c9ca6234 | ||
|
857be90128 | ||
|
c9410fbbac | ||
|
5a26063233 | ||
|
a68152fee3 | ||
|
43157d722f | ||
|
5bade926b1 | ||
|
861258532d | ||
|
869f1d7031 | ||
|
ba45e4c6d6 | ||
|
ff3d27ad42 | ||
|
8088efa225 | ||
|
2b6a0ee449 | ||
|
300f2f3333 | ||
|
259bfc31c4 | ||
|
c1e2e12dd9 | ||
|
dc0d96c83a | ||
|
579b86c527 | ||
|
4bfcac0307 | ||
|
265d2d8702 | ||
|
95e99670ee | ||
|
e33a6e4712 | ||
|
73be005f6a | ||
|
4c30e9d45e | ||
|
68fbaf7ae0 | ||
|
78731b2e51 | ||
|
b2cda46cc0 | ||
|
cf7e8f19e2 | ||
|
879457db4e | ||
|
256a7f2584 | ||
|
22b3aa848e | ||
|
7d5ce53317 | ||
|
f995903056 | ||
|
7f1a776f69 | ||
|
0169c64354 | ||
|
d5be31f1d9 | ||
|
8968ada03b | ||
|
816dde8aca | ||
|
a47df49ca8 | ||
|
2e0972b71e | ||
|
b4e672bb43 | ||
|
ee9a062201 | ||
|
74ebf0dd30 | ||
|
8384a43699 | ||
|
8c882852db | ||
|
cbdd61cfa8 | ||
|
e642f4df18 | ||
|
b8eda0cbc4 | ||
|
ce3c1e3861 | ||
|
8c85639dfe | ||
|
5c27b521af | ||
|
33a02ee8f3 | ||
|
44feef783b | ||
|
5fbdb6834d | ||
|
39877dacd9 | ||
|
c8b1d0cb22 | ||
|
442a2cc72a | ||
|
6bcf8a832f | ||
|
f0903ee505 | ||
|
9ffced6365 | ||
|
4561e6c0fe | ||
|
aa4bbcbeb3 | ||
|
89f77dbbc6 | ||
|
5863962e8a | ||
|
afe5f0c965 | ||
|
82c362b2b6 | ||
|
58886b00fa | ||
|
966d61fda1 | ||
|
7a06279601 | ||
|
cb0150391a | ||
|
121f5a6fa6 | ||
|
c2a95711da | ||
|
bc04f9c30f | ||
|
eb3188c6c1 | ||
|
34b8902c71 | ||
|
3f76f6c1ab | ||
|
34028b99e7 | ||
|
8867ef7036 | ||
|
6cee32d680 | ||
|
cce00adc51 | ||
|
e37b929adc | ||
|
4b75be2f02 | ||
|
34046c8a7e | ||
|
f177b98c78 | ||
|
4777b7e8f9 | ||
|
fbbdc529fe | ||
|
e9f61b6a94 | ||
|
367a1779f5 | ||
|
99f834c41f | ||
|
2b868a71ba | ||
|
1a0a1d17dc | ||
|
f333f2f71f | ||
|
6c2a7ff6e6 | ||
|
1936a02cf7 | ||
|
d8eb440b34 | ||
|
4570d79645 | ||
|
6936f6994d | ||
|
d60f1d82db | ||
|
c72a6a32dc | ||
|
757151c71a | ||
|
7bdb3d77ba | ||
|
f211f3482f | ||
|
e52cec2ce7 | ||
|
ff226dd8e5 | ||
|
574de727d3 | ||
|
b0268346e5 | ||
|
db87dd101b | ||
|
9906b80702 | ||
|
be4a4a5b09 | ||
|
070088909e | ||
|
2066f4050c | ||
|
9cbb297330 | ||
|
e9d23cf170 | ||
|
9f98fc6f6d | ||
|
b5ebd1f1fb | ||
|
2ed4bf07cd | ||
|
df17aa8687 | ||
|
04e900bd8e | ||
|
0e99f53f0d | ||
|
af53a574f5 | ||
|
15e4c949d3 | ||
|
962d28b484 | ||
|
2bbc74a87c | ||
|
ac5d3ebe14 | ||
|
0e4e1e860d | ||
|
0b201eada9 | ||
|
73139094dc | ||
|
9e270f6f7e | ||
|
81a358e4e2 | ||
|
eead0d282c | ||
|
ba09bab8a1 | ||
|
ac24acc52d | ||
|
4c4a54e50b | ||
|
5f44798fe6 | ||
|
5b7d02964d | ||
|
912c4d3d05 | ||
|
0648a27daf | ||
|
496e5fb60d | ||
|
1406b036f7 | ||
|
51cc23b223 | ||
|
2ad3517bcd | ||
|
52e703f9c2 | ||
|
2ce130786a | ||
|
9b2e9000e7 | ||
|
0021fd5ff9 | ||
|
f74b0b0fe9 | ||
|
62abc85191 | ||
|
429eead46d | ||
|
6293c09ca8 | ||
|
6db1fdb86d | ||
|
af9e372d6b | ||
|
b1223f623b | ||
|
e72698e1da | ||
|
22a78d8e69 | ||
|
04f532065a | ||
|
39b6a59538 | ||
|
1d45fef879 | ||
|
772a31da37 | ||
|
cc95427e05 | ||
|
0070b1618b | ||
|
d3c5f47ac2 | ||
|
3b04e2a180 | ||
|
838cedf7e2 | ||
|
5d43c1dbff | ||
|
f3e8d98ae6 | ||
|
9c65db5c25 | ||
|
a6397e9ff6 | ||
|
187d417eec | ||
|
5909533161 | ||
|
cbaf42af26 | ||
|
f3420a50ac | ||
|
f6eab2f3b2 | ||
|
6bbd8d1321 | ||
|
1bd56debb9 | ||
|
83ad9141a8 | ||
|
a1ed512453 | ||
|
4641fde008 | ||
|
1d3d79c562 | ||
|
33600bda6e | ||
|
e4807cf648 | ||
|
b9cb82c054 | ||
|
92de41d410 | ||
|
ee72b65534 | ||
|
ca24954e8d | ||
|
b30f9334c5 | ||
|
a8e384cb43 | ||
|
5136d013c2 | ||
|
374c35ea8c | ||
|
dba5e4267a | ||
|
fb757fc1fd | ||
|
f6d78201cb | ||
|
952cb1c3f7 | ||
|
8162230f23 | ||
|
f73de2f4c3 | ||
|
bfb96b6d9f | ||
|
f9316b230b | ||
|
b37c87afb5 | ||
|
8913d152e7 | ||
|
8141d3bbdb | ||
|
05f2f816e0 | ||
|
0c1313af8f | ||
|
df98efc2da | ||
|
7320ba0e6d | ||
|
9f89cc1482 | ||
|
4271be9340 | ||
|
8904cc9bc0 | ||
|
e1efa5d5ed | ||
|
b56b3ef6d8 | ||
|
eacffbe20b | ||
|
5f74c7fca8 | ||
|
4a171b6aa5 | ||
|
0bd09e70bf | ||
|
52345d6970 | ||
|
697a07dda8 | ||
|
ccd2215cf6 | ||
|
c2e5b705a7 | ||
|
30006b90d4 | ||
|
9d2abc83bf | ||
|
9e86df21e5 | ||
|
367b9ca26f | ||
|
3be93f2dcf | ||
|
5d8a63e729 | ||
|
f9886cae3b | ||
|
f9e10b8daf | ||
|
b386839bdc | ||
|
32053627ad | ||
|
9e991eebb2 | ||
|
579092bac6 | ||
|
ab8faf3ebb | ||
|
17e573dc14 | ||
|
bd267d7cd1 | ||
|
18d6a24631 | ||
|
86a2c7c544 | ||
|
733522d574 | ||
|
92a0c840a5 | ||
|
92639b6771 | ||
|
7b6d7df38b | ||
|
1ea2054d60 | ||
|
96721487de | ||
|
c9075bfa0f | ||
|
752b84a17a | ||
|
4ffdd4b882 | ||
|
ca0fc6a61d | ||
|
54136ae077 | ||
|
8f38ae8f4a | ||
|
49654d9afe | ||
|
225bf75770 | ||
|
a9844fc8b4 | ||
|
d59661eb05 | ||
|
babcf53ea3 | ||
|
a395847b32 | ||
|
7c54eba78c | ||
|
766786715d | ||
|
c81fc17593 | ||
|
a03f86f031 | ||
|
c9bea9cf78 | ||
|
cc033105e2 | ||
|
a665c72c25 | ||
|
dcf1ea7cac | ||
|
9e293905b6 | ||
|
a1bf0b92bb | ||
|
bce764f0d3 | ||
|
a4288b991d | ||
|
c2a1e310ef | ||
|
b6e5aeac29 | ||
|
a6474101b4 | ||
|
8a06132b1a | ||
|
789084d3ed | ||
|
fab976fd8f | ||
|
3420ac44be | ||
|
42631f39da | ||
|
c6003536ae | ||
|
5c3505856a | ||
|
d0fb880880 | ||
|
e597381005 | ||
|
f43a65e026 | ||
|
0d63c25f27 | ||
|
37c65c14e6 | ||
|
6d5b154f3c | ||
|
ea0727993e | ||
|
61b8c270c4 | ||
|
b491c4a2c0 | ||
|
8d00883213 | ||
|
8ad77c736d | ||
|
f5325e47f7 | ||
|
bfb65ccdb6 | ||
|
178bb12b62 | ||
|
0801bd0cfb | ||
|
5fae347440 | ||
|
34ff0259ce | ||
|
b4ba79cad2 | ||
|
5a13f52d8f | ||
|
3e8b681dae | ||
|
0242eaaa54 | ||
|
f2149c594e | ||
|
7ec92d6005 | ||
|
1518be047c | ||
|
dbc2bc1754 | ||
|
4932fe37f7 | ||
|
18f75ecb37 | ||
|
61b147f450 | ||
|
033a6265a8 | ||
|
02ab1a8c7c | ||
|
8c6833f420 | ||
|
9726cd7858 | ||
|
a81e08d3cd | ||
|
fa1dfea181 | ||
|
b59067a20e | ||
|
fc2f8b8a37 | ||
|
89155d6db9 | ||
|
9b35224fc5 | ||
|
161c277ec6 | ||
|
a0797ac07f | ||
|
3694f10fff | ||
|
bbcc06f503 | ||
|
42ac0d6b03 | ||
|
6fbf419064 | ||
|
30ef566bef | ||
|
cabc906758 | ||
|
0b7535e76e | ||
|
85db53e619 | ||
|
f536112312 | ||
|
1a1a4e84b2 | ||
|
ab2ca6d527 | ||
|
442b78ef2e | ||
|
9cfc348ee6 | ||
|
6756142e44 | ||
|
0f1d1af736 | ||
|
6830d8db4e | ||
|
49862699f2 | ||
|
d8db218c6d | ||
|
afbbc342ba | ||
|
81dd4e3b8c | ||
|
9519c7c95b | ||
|
afa7cb8f39 | ||
|
dc640fd400 | ||
|
76c822b015 | ||
|
c7513328eb | ||
|
e5f0fcce69 | ||
|
5e0d71042d | ||
|
004acbab9a | ||
|
c566ec47a2 | ||
|
3e1a31954a | ||
|
3a42fb599c | ||
|
ec5473d99f | ||
|
4cf62de3fd | ||
|
4c87d96cd3 | ||
|
a43d6af237 | ||
|
f41f431f38 | ||
|
c5af877fce | ||
|
88dd6e7494 | ||
|
7fd7f48ee8 | ||
|
e69800ccd2 | ||
|
b1ba02c1f3 | ||
|
cb13ac1617 | ||
|
dcfc5357aa | ||
|
de051d99ac | ||
|
86b12f82bf | ||
|
fa1e6a69e8 | ||
|
5360864aed | ||
|
417ddc6d8d | ||
|
238f08b7dd | ||
|
739ee728d3 | ||
|
b8042f5aa7 | ||
|
09d6001ef1 | ||
|
c5b9233d4a | ||
|
22a8933e82 | ||
|
a185baccdd | ||
|
9e2a5d8b07 | ||
|
2521f41e76 | ||
|
3f1f2066b4 | ||
|
815de57b86 | ||
|
428ddd493c | ||
|
4de93ddf1f | ||
|
e6b75e3807 | ||
|
e835c018b4 | ||
|
6b88209a93 | ||
|
eb7773fb74 | ||
|
098620cd05 | ||
|
1519d7b8a1 | ||
|
214f9c397b | ||
|
528d1d3acc | ||
|
f1158b9d42 | ||
|
8a87c2e057 | ||
|
47ae741952 | ||
|
97dde33c48 | ||
|
907fad4a55 | ||
|
7c3e289a6b | ||
|
1f5a647ff3 | ||
|
0eebe6a0a5 | ||
|
953d4ac0c9 | ||
|
b8c09f48db | ||
|
8aa1607fee | ||
|
a890a957c8 | ||
|
64ed0a9a1f | ||
|
0aa1311836 | ||
|
1ac61edebe | ||
|
6eaa3fd450 | ||
|
81ce78bbbd | ||
|
a1478a04d8 | ||
|
6d49ae6667 | ||
|
76ff66661d | ||
|
e60a4bc8fa | ||
|
5ebd081ff3 | ||
|
6e5b73c3b7 | ||
|
71f04e46be | ||
|
cbb6d48cfb | ||
|
ae4330fc9d | ||
|
04a862840c | ||
|
b7147661e7 | ||
|
3c07d2f667 | ||
|
f54d87a295 | ||
|
9f386a8d01 | ||
|
ac8a661182 | ||
|
13f97248f6 | ||
|
786945fcbf | ||
|
1070b716a2 | ||
|
ad1bf0fe0b | ||
|
df70dd4fd8 | ||
|
3038c545ce | ||
|
8d3b28e3cb | ||
|
c431e39d4a | ||
|
770f2eef09 | ||
|
e702ff6bca | ||
|
c6cc1f2b43 | ||
|
06f5184ddd | ||
|
a071ce5e3e | ||
|
d5688116e3 | ||
|
1ec1104356 | ||
|
cd19eae182 | ||
|
234eb98f0c | ||
|
3c7d0f0e71 | ||
|
fa7ab534a3 | ||
|
ee661461a5 | ||
|
aecf6854a4 | ||
|
a3eb1f6e3f | ||
|
b9634bfd10 | ||
|
34cd90f9a1 | ||
|
5091b69219 | ||
|
201fb8ff9b | ||
|
d071f86c31 | ||
|
1c133eacd0 | ||
|
89ed019788 | ||
|
9930f4323c | ||
|
167eb1861c | ||
|
115beefa8a | ||
|
611f3590e6 | ||
|
59635839cf | ||
|
0d13156edf | ||
|
4853729d7c | ||
|
b5887ad1df | ||
|
a8b3b80f75 | ||
|
e5a6916109 | ||
|
6a3093c49d | ||
|
498154ffb1 | ||
|
9f441c6b22 | ||
|
1e96bd2c13 | ||
|
65d4e8f316 | ||
|
876e9ee3b5 | ||
|
59e670f44e | ||
|
2117433f8f | ||
|
7d39e15c18 | ||
|
77c7d71a96 | ||
|
ca877e513f | ||
|
648aad97ea | ||
|
e8ecf3d057 | ||
|
d8fa71c326 | ||
|
aa4a96663c | ||
|
8dc0239548 | ||
|
0d02dcb83b | ||
|
e5a770345b | ||
|
922659d284 | ||
|
c846bae301 | ||
|
bdefd11ade | ||
|
e8888afd22 | ||
|
58d0f10eb0 | ||
|
72eedd8978 | ||
|
b2851b3c65 | ||
|
b8e1e17735 | ||
|
06d4909447 | ||
|
498a1a51eb | ||
|
3862605e3a | ||
|
b713ac9172 | ||
|
1e96917c27 | ||
|
5adc47fab8 | ||
|
2ca3b7ba25 | ||
|
8af8c91e2e | ||
|
c7ba6e84b9 | ||
|
469fbaacda | ||
|
ba53a37d6f | ||
|
d7006264ad | ||
|
3acda2fa28 | ||
|
543e16500f | ||
|
2a0a243dd1 | ||
|
cdb3a2e8df | ||
|
e7f6d8ddeb | ||
|
f68f78f48c | ||
|
c5eb0432b3 | ||
|
d6539dc543 | ||
|
f2523fa8f2 | ||
|
eb6ac84b5b | ||
|
3999507129 | ||
|
03db789be9 | ||
|
4ce97b4e7a | ||
|
621722f5e3 | ||
|
897682eae4 | ||
|
5a3c14db2d | ||
|
f531d338a0 | ||
|
615ed11197 | ||
|
5037fd2bd4 | ||
|
9cf5291064 | ||
|
c1ac0d4734 | ||
|
d838c8c9fe | ||
|
fd056eab9f | ||
|
cbc66f4017 | ||
|
f5d1e39121 | ||
|
c981935270 | ||
|
8fb4f190e6 | ||
|
4e2e023ac6 | ||
|
9c0c3bcade | ||
|
5287bfbe9c | ||
|
2acaa74270 | ||
|
d816b141b1 | ||
|
4793667435 | ||
|
f86c5942f8 | ||
|
a5b17d3015 | ||
|
1af160608e | ||
|
f244bb7ece | ||
|
55964b7e3f | ||
|
35a3e48b89 | ||
|
9944854b7b | ||
|
6e6d7a8795 | ||
|
bf85332492 | ||
|
0b928476d1 | ||
|
88fb4ce1b7 | ||
|
1ad7385954 | ||
|
cdac261561 | ||
|
8ebc0aeaaf | ||
|
55dd16d508 | ||
|
70dc779fc4 | ||
|
240be81f26 | ||
|
c80535fabb | ||
|
a499f13e56 | ||
|
4e8b267da5 | ||
|
bb41021eee | ||
|
c7d9ed8d78 | ||
|
053eb55355 | ||
|
9084c0e8b0 | ||
|
9c3d67a34c | ||
|
0d42fbdc84 | ||
|
08f442b809 | ||
|
c3c11f3326 | ||
|
f1a7c4661e | ||
|
84d3a2d67f | ||
|
186190ddff | ||
|
9e2a1cc9f9 | ||
|
310d3ca618 | ||
|
c8d26199cd | ||
|
a601220771 | ||
|
9b0e1eb7ce | ||
|
eeaef4604a | ||
|
cd45b9359c | ||
|
1b1ec6d736 | ||
|
d9454fea63 | ||
|
3f110fb6c6 | ||
|
09a49a1cb2 | ||
|
d42482b6c3 | ||
|
83ed44b399 | ||
|
d1f4ce0884 | ||
|
d3417a1c79 | ||
|
1a2619c170 | ||
|
ad5b8dcded | ||
|
16997b6298 | ||
|
519e36e7bd | ||
|
dcc870d8e0 | ||
|
ab8cb7a7e0 | ||
|
5897cf33f2 | ||
|
4401985f3c | ||
|
4e972e0aff | ||
|
4a16707be3 | ||
|
bf131f896c | ||
|
506364d578 | ||
|
f1704787b8 | ||
|
eb3c61f0ea | ||
|
0711bf5db3 | ||
|
83a94d0ece | ||
|
5e0ba4990a | ||
|
e7b2811fa1 | ||
|
f9f1413a3e | ||
|
c11953cd03 | ||
|
148a8a1ffc | ||
|
1658e9278a | ||
|
7fc9adae8b | ||
|
780f2b4a05 | ||
|
14e42a6382 | ||
|
d439f78f11 | ||
|
610e88cf09 | ||
|
293dd2d9a1 | ||
|
57b5ae6dcf | ||
|
8c2b7bf4bb | ||
|
3113524197 | ||
|
2bf6a92de3 | ||
|
8ac5cc068d | ||
|
729b0310bd | ||
|
d229dab41e | ||
|
fb9fa08090 | ||
|
682ce5d69f | ||
|
397bd29ce6 | ||
|
cba154944d | ||
|
efacaeec7f | ||
|
05c00eb13a | ||
|
9347b3c29b | ||
|
8ba4a9827b | ||
|
14bbf56145 | ||
|
71076a1b63 | ||
|
36dc8ecfd5 | ||
|
34c6dd9cb5 | ||
|
c34d70231c | ||
|
5eb9af39f6 | ||
|
1dc58be4fa | ||
|
1e7b729164 | ||
|
4a021a4ed4 | ||
|
7b86ec97eb | ||
|
8bb9bf11d5 | ||
|
d34e80e360 | ||
|
a8853930a2 | ||
|
ac02161eb9 | ||
|
957403a23f | ||
|
c5582c3092 | ||
|
1c9bc68a66 | ||
|
73192bb515 | ||
|
d57ef0cc5e | ||
|
105df6e0fb | ||
|
cd859ca0cb | ||
|
5cb5b5f343 | ||
|
f0049cb826 | ||
|
643d8b40e1 | ||
|
e2db15ead7 | ||
|
24dbd884ad | ||
|
97fd0b8272 | ||
|
6096313cae | ||
|
6401ca3f05 | ||
|
9f37956e99 | ||
|
0b48306f3e | ||
|
83cdd9446b | ||
|
3f7fb77086 | ||
|
2683fddfc1 | ||
|
9c49158de2 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*_enterprise.*
|
||||
.settings/
|
||||
.ipynb_checkpoints
|
||||
.idea/
|
||||
|
||||
# Debian buildings
|
||||
*.debhelper*
|
||||
@@ -31,9 +32,6 @@
|
||||
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
||||
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
||||
|
||||
# /guacamole-tunnel/
|
||||
/guacamole-tunnel/target
|
||||
|
||||
# /linuxActor/
|
||||
/linuxActor/udsactor_*
|
||||
|
||||
@@ -164,3 +162,6 @@
|
||||
/udsService/udsgui/obj/Debug
|
||||
/udsService/udsgui/obj/Release
|
||||
/udsService/udsgui/obj/x86
|
||||
|
||||
.vscode
|
||||
.mypy_cache
|
||||
|
10
README.md
10
README.md
@@ -3,5 +3,13 @@
|
||||
openuds
|
||||
=======
|
||||
|
||||
OpenUDS Is an Open Source Source project, initiated by Spanish Company Virtualcable and released Open Source with the help of several Spanish Universities.
|
||||
OpenUDS (Universal Desktop Services) is a multiplatform connection broker for:
|
||||
- VDI: Windows and Linux virtual desktops administration and deployment
|
||||
- App virtualization
|
||||
- Desktop services consolidation
|
||||
|
||||
This is an Open Source Source project, initiated by Spanish Company Virtualcable and released Open Source with the help of several Spanish Universities.
|
||||
|
||||
Any help provided will be welcome.
|
||||
|
||||
**Note: Master version is always under heavy development and it is not recommended for use, it will probably have unfixed bugs. Please use the latest stable branch.**
|
||||
|
5
TODO.txt
5
TODO.txt
@@ -1,6 +1 @@
|
||||
* Add "Scheduler" to manage pools, and posibbly other operations (Expect to release an version 1.7.1??)
|
||||
* Improve stats
|
||||
* Add App Virtualization support
|
||||
* Add "Meta Pools"
|
||||
* Manage to connect several UDS in "tree", so one UDS can be provider o another UDS
|
||||
|
||||
|
2
actor/.env
Normal file
2
actor/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
PYTHONPATH=./src:${PYTHONPATH}
|
||||
|
8
actor/.gitignore
vendored
Normal file
8
actor/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Debian source builds
|
||||
udsactor_*.dsc
|
||||
udsactor_*.tar.xz
|
||||
udsactor_*.buildinfo
|
||||
udsactor_*.changes
|
||||
# And binaries
|
||||
udsactor*.deb
|
||||
udsactor*.rpm
|
4
actor/deps.txt
Normal file
4
actor/deps.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Linux:
|
||||
python3-prctl (recommended, but not required in fact)
|
||||
python3-pyqt5
|
||||
|
91
actor/linux/Makefile
Normal file
91
actor/linux/Makefile
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
.PHONY: install-udsactor install-udsactor-unmanaged
|
||||
|
||||
# Directories
|
||||
SOURCEDIR := ../src
|
||||
LIBDIR := $(DESTDIR)/usr/share/UDSActor
|
||||
BINDIR := $(DESTDIR)/usr/bin
|
||||
SBINDIR = $(DESTDIR)/usr/sbin
|
||||
APPSDIR := $(DESTDIR)/usr/share/applications
|
||||
CFGDIR := $(DESTDIR)/etc/udsactor
|
||||
SYSTEMDIR := $(DESTDIR)/etc/systemd/system
|
||||
POLKITDIR := $(DESTDIR)/usr/share/polkit-1/actions/
|
||||
XDGAUTOSTARTDIR := $(DESTDIR)/etc/xdg/autostart
|
||||
KDEAUTOSTARTDIR := $(DESTDIR)/usr/share/autostart
|
||||
|
||||
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
||||
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__' -o -name '.mypy_cache')
|
||||
|
||||
clean:
|
||||
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
||||
|
||||
install-udsactor: udsactor
|
||||
|
||||
install-udsactor-unmanaged: udsactor udsactor-unmanaged
|
||||
|
||||
udsactor-unmanaged:
|
||||
# Overwrite udsactor config is what to be done
|
||||
cp scripts/UDSActorConfig-unmanaged $(SBINDIR)/UDSActorConfig
|
||||
|
||||
udsactor:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(LIBDIR)
|
||||
mkdir -p $(BINDIR)
|
||||
mkdir -p $(SBINDIR)
|
||||
mkdir -p $(APPSDIR)
|
||||
mkdir -p $(CFGDIR)
|
||||
mkdir -p $(POLKITDIR)
|
||||
mkdir -p $(XDGAUTOSTARTDIR)
|
||||
mkdir -p $(KDEAUTOSTARTDIR)
|
||||
|
||||
mkdir $(LIBDIR)/img
|
||||
|
||||
# Cleans up .pyc and cache folders
|
||||
rm -f $(PYC) $(CACHES)
|
||||
|
||||
cp -r $(SOURCEDIR)/udsactor $(LIBDIR)/udsactor
|
||||
cp $(SOURCEDIR)/img/uds-icon.png $(LIBDIR)/img
|
||||
|
||||
cp $(SOURCEDIR)/actor_*.py $(LIBDIR)
|
||||
# QT Dialogs & resources
|
||||
cp -r $(SOURCEDIR)/ui $(LIBDIR)/ui
|
||||
|
||||
# Menu GUI app
|
||||
cp desktop/UDS_Actor_Configuration.desktop $(APPSDIR)
|
||||
|
||||
# Autostart elements for gnome/kde
|
||||
cp desktop/UDSActorTool.desktop $(XDGAUTOSTARTDIR)
|
||||
cp desktop/UDSActorTool.desktop $(KDEAUTOSTARTDIR)
|
||||
|
||||
# scripts
|
||||
cp scripts/udsactor $(BINDIR)
|
||||
cp scripts/UDSActorConfig $(SBINDIR)
|
||||
cp scripts/UDSActorConfig-pkexec $(SBINDIR)
|
||||
cp scripts/UDSActorTool $(BINDIR)
|
||||
cp scripts/UDSActorTool-startup $(BINDIR)
|
||||
cp scripts/udsvapp ${BINDIR}
|
||||
|
||||
# Policy to run as administrator
|
||||
cp policy/org.openuds.pkexec.UDSActorConfig.policy $(POLKITDIR)
|
||||
|
||||
# Fix permissions
|
||||
chmod 755 $(BINDIR)/udsactor
|
||||
chmod 755 $(BINDIR)/udsvapp
|
||||
chmod 755 $(BINDIR)/UDSActorTool-startup
|
||||
chmod 755 $(SBINDIR)/UDSActor*
|
||||
chmod 755 $(LIBDIR)/actor_*.py
|
||||
chmod 644 $(POLKITDIR)/org.openuds.pkexec.UDSActorConfig.policy
|
||||
|
||||
# If for red hat based, copy init.d
|
||||
ifeq ($(DISTRO),rh)
|
||||
mkdir -p $(SYSTEMDIR)
|
||||
cp debian/udsactor.service $(SYSTEMDIR)/
|
||||
endif
|
||||
|
||||
# chmod 0755 $(BINDIR)/udsactor
|
||||
uninstall:
|
||||
rm -rf $(LIBDIR)
|
||||
# rm -f $(BINDIR)/udsactor
|
||||
rm -rf $(CFGDIR)
|
36
actor/linux/build-packages.sh
Executable file
36
actor/linux/build-packages.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=`cat ../../VERSION`
|
||||
RELEASE=1
|
||||
|
||||
top=`pwd`
|
||||
|
||||
# Debian based
|
||||
dpkg-buildpackage -b
|
||||
|
||||
cat udsactor-template.spec |
|
||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsactor-$VERSION.spec
|
||||
|
||||
# Now fix dependencies for opensuse
|
||||
# Note that, although on opensuse the library is "libXss1" on newer,
|
||||
# the LibXscrnSaver is a "capability" and gets libXss1 installed
|
||||
# So right now, we only need 1 uds actor for both platforms.
|
||||
# cat udsactor-template.spec |
|
||||
# sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||
# sed -e s/"name udsactor"/"name udsactor-opensuse"/g |
|
||||
# sed -e s/"libXScrnSaver"/"libXss1"/g > udsactor-opensuse-$VERSION.spec
|
||||
|
||||
#for pkg in udsactor-$VERSION.spec udsactor-opensuse-$VERSION.spec; do
|
||||
for pkg in udsactor-$VERSION.spec; do
|
||||
|
||||
rm -rf rpm
|
||||
for folder in SOURCES BUILD RPMS SPECS SRPMS; do
|
||||
mkdir -p rpm/$folder
|
||||
done
|
||||
|
||||
rpmbuild -v -bb --clean --buildroot=$top/rpm/BUILD/$pkg-root --target noarch $pkg 2>&1
|
||||
done
|
||||
|
||||
rpm --addsign ../*rpm
|
||||
#rm udsactor-$VERSION
|
2
actor/linux/debian/.gitignore
vendored
Normal file
2
actor/linux/debian/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/udsactor/
|
||||
/udsactor-unmanaged/
|
59
actor/linux/debian/changelog
Normal file
59
actor/linux/debian/changelog
Normal file
@@ -0,0 +1,59 @@
|
||||
udsactor (3.5.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 3.5.0 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 23 Oct 2020 8:00:00 +0200
|
||||
|
||||
udsactor (3.0.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 3.0.0 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Wed, 10 Jul 2019 9:24:10 +0200
|
||||
|
||||
udsactor (2.2.1) stable; urgency=medium
|
||||
|
||||
* Upgraded to 2.2.1 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
|
||||
|
||||
udsactor (2.2.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 2.2.0 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 19 Oct 2017 16:44:12 +0200
|
||||
|
||||
udsactor (2.1.0) stable; urgency=medium
|
||||
|
||||
* Fixes for 2.1.0 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 19 Jan 2017 08:00:22 +0200
|
||||
|
||||
udsactor (2.0.0) stable; urgency=medium
|
||||
|
||||
* Upgrade for 2.0.0
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:39:21 +0100
|
||||
|
||||
udsactor (1.9.1) stable; urgency=medium
|
||||
|
||||
* Upgrade for 1.9.1
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:19:21 +0100
|
||||
|
||||
udsactor (1.9.0) stable; urgency=medium
|
||||
|
||||
* Upgrade for 1.9.0 (fixed package version)
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 05 May 2015 07:10:27 +0200
|
||||
|
||||
udsactor (1.7.5) stable; urgency=medium
|
||||
|
||||
* Upgrade for 1.7.5
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 23 Apr 2015 06:08:53 +0200
|
||||
|
||||
udsactor (1.7.0) stable; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Mon, 17 Nov 2014 05:32:41 +0100
|
1
actor/linux/debian/compat
Normal file
1
actor/linux/debian/compat
Normal file
@@ -0,0 +1 @@
|
||||
10
|
26
actor/linux/debian/control
Normal file
26
actor/linux/debian/control
Normal file
@@ -0,0 +1,26 @@
|
||||
Source: udsactor
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Adolfo Gómez García <agomez@virtualcable.net>
|
||||
Build-Depends: debhelper (>= 7), po-debconf, dh-systemd (>= 1.5)
|
||||
Standards-Version: 4.1.4
|
||||
Homepage: http://www.udsenterprise.com
|
||||
|
||||
Package: udsactor
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.6), libxss1, xscreensaver, ${misc:Depends}
|
||||
Recommends: python3-prctl(>=1.1.1)
|
||||
Description: Actor for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow managed machines to work on an environment managed by UDS Broker.
|
||||
|
||||
Package: udsactor-unmanaged
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt5 (>=4.9), python3-six(>=1.1), python3 (>=3.6), libxss1, xscreensaver, ${misc:Depends}
|
||||
Recommends: python3-prctl(>=1.1.1)
|
||||
Description: Actor for Universal Desktop Services (UDS) Broker Static Unmanaged machines
|
||||
This package provides the required components to allow unmanaged machines (static, independent machines) to work on an environment managed by UDS Broker.
|
||||
|
26
actor/linux/debian/copyright
Normal file
26
actor/linux/debian/copyright
Normal file
@@ -0,0 +1,26 @@
|
||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||
Name: udsactor
|
||||
Maintainer: Adolfo Gómez García
|
||||
Source: http://www.udsenterprise.com/
|
||||
|
||||
Copyright: 2014-2019 Virtual Cable S.L.U.
|
||||
License: BSD-3-clause
|
||||
|
||||
License: GPL-2+
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
.
|
||||
On Debian systems, the full text of the GNU General Public
|
||||
License version 2 can be found in the file
|
||||
`/usr/share/common-licenses/GPL-2'.
|
3
actor/linux/debian/files
Normal file
3
actor/linux/debian/files
Normal file
@@ -0,0 +1,3 @@
|
||||
udsactor-unmanaged_3.5.0_all.deb admin optional
|
||||
udsactor_3.5.0_all.deb admin optional
|
||||
udsactor_3.5.0_amd64.buildinfo admin optional
|
47
actor/linux/debian/rules
Executable file
47
actor/linux/debian/rules
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
configure: configure-stamp
|
||||
configure-stamp:
|
||||
dh_testdir
|
||||
touch configure-stamp
|
||||
build: build-arch build-indep
|
||||
build-arch: build-stamp
|
||||
build-indep: build-stamp
|
||||
build-stamp: configure-stamp
|
||||
dh_testdir
|
||||
$(MAKE)
|
||||
touch $@
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
rm -f build-stamp configure-stamp
|
||||
dh_clean
|
||||
install: build
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_prep
|
||||
dh_installdirs
|
||||
$(MAKE) DESTDIR=$(CURDIR)/debian/udsactor install-udsactor
|
||||
$(MAKE) DESTDIR=$(CURDIR)/debian/udsactor-unmanaged install-udsactor-unmanaged
|
||||
binary-arch: build install
|
||||
# emptyness
|
||||
binary-indep: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs
|
||||
dh_installdocs
|
||||
dh_installdebconf
|
||||
dh_systemd_enable
|
||||
dh_installinit --no-restart-on-upgrade --no-start --name=udsactor
|
||||
dh_systemd_start
|
||||
dh_python2=python
|
||||
dh_compress
|
||||
dh_link
|
||||
dh_fixperms
|
||||
dh_installdeb
|
||||
dh_shlibdeps
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
binary: binary-indep
|
||||
.PHONY: build clean binary-indep binary install configure
|
1
actor/linux/debian/udsactor-unmanaged.config
Symbolic link
1
actor/linux/debian/udsactor-unmanaged.config
Symbolic link
@@ -0,0 +1 @@
|
||||
udsactor.postinst
|
1
actor/linux/debian/udsactor-unmanaged.service
Symbolic link
1
actor/linux/debian/udsactor-unmanaged.service
Symbolic link
@@ -0,0 +1 @@
|
||||
udsactor.service
|
3
actor/linux/debian/udsactor.config
Executable file
3
actor/linux/debian/udsactor.config
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
exit 0
|
28
actor/linux/debian/udsactor.postinst
Executable file
28
actor/linux/debian/udsactor.postinst
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
set -e
|
||||
case "$1" in
|
||||
configure)
|
||||
/usr/bin/python3 -m compileall /usr/share/UDSActor > /dev/nul 2>&1
|
||||
|
||||
# Fix perms so only root can access "masterKey"
|
||||
chmod 0700 /etc/udsactor
|
||||
# chmod 0600 /etc/udsactor/udsactor.cfg
|
||||
chown root:root /etc/udsactor
|
||||
# chown root:root /etc/udsactor/udsactor.cfg
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
14
actor/linux/debian/udsactor.service
Normal file
14
actor/linux/debian/udsactor.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=UDS Broker actor
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/usr/share/UDSActor
|
||||
ExecStart=/usr/bin/python3 actor_service.py start-foreground
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
12
actor/linux/desktop/UDSActorTool.desktop
Normal file
12
actor/linux/desktop/UDSActorTool.desktop
Normal file
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Name=UDS Actor Tool
|
||||
Comment=UDS Actor Userspace tools
|
||||
Exec=/usr/bin/UDSActorTool-startup
|
||||
Icon=/usr/share/UDSActor/img/uds-icon.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
NoDisplay=true
|
||||
X-KDE-autostart-after=panel
|
||||
X-KDE-StartupNotify=false
|
||||
X-DBUS-StartupType=None
|
||||
X-KDE-UniqueApplet=false
|
11
actor/linux/desktop/UDS_Actor_Configuration.desktop
Normal file
11
actor/linux/desktop/UDS_Actor_Configuration.desktop
Normal file
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=UDS Actor Configuration
|
||||
Version=1.0
|
||||
Exec=/usr/sbin/UDSActorConfig-pkexec
|
||||
Comment=UDS Actor Configuration Application. (Must be executed as root)
|
||||
Icon=/usr/share/UDSActor/img/uds-icon.png
|
||||
Type=Application
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
Encoding=UTF-8
|
||||
Categories=Settings;System;
|
6
actor/linux/scripts/UDSActorConfig
Executable file
6
actor/linux/scripts/UDSActorConfig
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_config.py -platform xcb $@
|
3
actor/linux/scripts/UDSActorConfig-pkexec
Normal file
3
actor/linux/scripts/UDSActorConfig-pkexec
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# pkexec env DISPLAY=$DISPLAY QT_X11_NO_MITSHM=1 "/usr/sbin/UDSActorConfig" "$@"
|
||||
pkexec "/usr/sbin/UDSActorConfig" "$@"
|
6
actor/linux/scripts/UDSActorConfig-unmanaged
Executable file
6
actor/linux/scripts/UDSActorConfig-unmanaged
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_config_unmanaged.py -platform xcb $@
|
6
actor/linux/scripts/UDSActorTool
Executable file
6
actor/linux/scripts/UDSActorTool
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_client.py -platform xcb $@
|
3
actor/linux/scripts/UDSActorTool-startup
Normal file
3
actor/linux/scripts/UDSActorTool-startup
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec /usr/bin/UDSActorTool
|
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
python -m udsactor.linux.UDSActorService $@
|
||||
exec python3 actor_service.py $@
|
3
actor/linux/scripts/udsnxstart.sh
Normal file
3
actor/linux/scripts/udsnxstart.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec /usr/bin/udsactor login $2 &
|
3
actor/linux/scripts/udsnxstop.sh
Normal file
3
actor/linux/scripts/udsnxstop.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec /usr/bin/udsactor logout $2 &
|
5
actor/linux/scripts/udsvapp
Executable file
5
actor/linux/scripts/udsvapp
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/bin/udsactor login "$USER"
|
||||
$@
|
||||
/usr/bin/udsactor logout "$USER"
|
70
actor/linux/udsactor-template.spec
Normal file
70
actor/linux/udsactor-template.spec
Normal file
@@ -0,0 +1,70 @@
|
||||
%define _topdir %(echo $PWD)/rpm
|
||||
%define name udsactor
|
||||
%define version 0.0.0
|
||||
%define release 1
|
||||
%define buildroot %{_topdir}/%{name}-%{version}-%{release}-root
|
||||
|
||||
BuildRoot: %{buildroot}
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Summary: Actor for Universal Desktop Services (UDS) Broker
|
||||
License: BSD3
|
||||
Group: Admin
|
||||
Requires: python3-six python3-requests python3-qt5 libXScrnSaver
|
||||
Vendor: Virtual Cable S.L.U.
|
||||
URL: http://www.udsenterprise.com
|
||||
Provides: udsactor
|
||||
|
||||
%define _rpmdir ../
|
||||
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
||||
|
||||
|
||||
%install
|
||||
curdir=`pwd`
|
||||
cd ../..
|
||||
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh install-udsactor
|
||||
cd $curdir
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
curdir=`pwd`
|
||||
cd ../..
|
||||
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh clean
|
||||
cd $curdir
|
||||
|
||||
|
||||
%post
|
||||
systemctl enable udsactor.service > /dev/null 2>&1
|
||||
|
||||
%preun
|
||||
systemctl disable udsactor.service > /dev/null 2>&1
|
||||
systemctl stop udsactor.service > /dev/null 2>&1
|
||||
|
||||
%postun
|
||||
# $1 == 0 on uninstall, == 1 on upgrade for preun and postun (just a reminder for me... :) )
|
||||
if [ $1 -eq 0 ]; then
|
||||
rm -rf /etc/udsactor
|
||||
rm /var/log/udsactor.log
|
||||
fi
|
||||
# And, posibly, the .pyc leaved behind on /usr/share/UDSActor
|
||||
rm -rf /usr/share/UDSActor > /dev/null 2>&1
|
||||
|
||||
%description
|
||||
This package provides the required components to allow this machine to work on an environment managed by UDS Broker.
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
/etc/udsactor
|
||||
/etc/xdg/autostart/UDSActorTool.desktop
|
||||
/etc/systemd/system/udsactor.service
|
||||
/usr/bin/UDSActorTool-startup
|
||||
/usr/bin/udsactor
|
||||
/usr/bin/udsvapp
|
||||
/usr/bin/UDSActorTool
|
||||
/usr/sbin/UDSActorConfig
|
||||
/usr/sbin/UDSActorConfig-pkexec
|
||||
/usr/share/UDSActor/*
|
||||
/usr/share/applications/UDS_Actor_Configuration.desktop
|
||||
/usr/share/autostart/UDSActorTool.desktop
|
||||
/usr/share/polkit-1/actions/org.openuds.pkexec.UDSActorConfig.policy
|
3
actor/src/.gitignore
vendored
Normal file
3
actor/src/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
build
|
||||
*.spec
|
77
actor/src/actor_client.py
Executable file
77
actor/src/actor_client.py
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import sys
|
||||
import os
|
||||
|
||||
import PyQt5 # pylint: disable=unused-import
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
|
||||
from udsactor.log import logger, INFO
|
||||
from udsactor.client import UDSClientQApp
|
||||
from udsactor.platform import operations
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.setLevel(INFO)
|
||||
|
||||
# Ensure idle operations is initialized on start
|
||||
operations.initIdleDuration(0)
|
||||
|
||||
if 'linux' in sys.platform:
|
||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||
|
||||
UDSClientQApp.setQuitOnLastWindowClosed(False)
|
||||
|
||||
qApp = UDSClientQApp(sys.argv)
|
||||
|
||||
if 'linux' not in sys.platform:
|
||||
# The "hidden window" is only needed to process events on Windows
|
||||
# Not needed on Linux
|
||||
mw = QMainWindow()
|
||||
mw.showMinimized() # Start minimized, will be hidden (not destroyed) as soon as qApp.init is invoked
|
||||
qApp.setMainWindow(mw)
|
||||
|
||||
qApp.init()
|
||||
|
||||
# Crate a timer to a "dummy" function, so python can check signals from time to time by executing the python interpreter
|
||||
# Note: Signals are only checked on python code execution, so we create a timer to force call back to python
|
||||
timer = QTimer(qApp)
|
||||
timer.start(1000)
|
||||
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
|
||||
|
||||
qApp.exec_()
|
||||
|
||||
# On windows, if no window is created, this point will never be reached.
|
||||
qApp.end()
|
||||
|
||||
logger.debug('Exiting...')
|
195
actor/src/actor_config.py
Executable file
195
actor/src/actor_config.py
Executable file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import typing
|
||||
|
||||
import PyQt5 # pylint: disable=unused-import
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
|
||||
|
||||
import udsactor
|
||||
|
||||
from ui.setup_dialog_ui import Ui_UdsActorSetupDialog
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
|
||||
|
||||
logger = logging.getLogger('actor')
|
||||
|
||||
class UDSConfigDialog(QDialog):
|
||||
_host: str = ''
|
||||
|
||||
def __init__(self) -> None:
|
||||
QDialog.__init__(self, None)
|
||||
# Get local config config
|
||||
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
|
||||
self.ui = Ui_UdsActorSetupDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.host.setText(config.host)
|
||||
self.ui.validateCertificate.setCurrentIndex(1 if config.validateCertificate else 0)
|
||||
self.ui.postConfigCommand.setText(config.post_command or '')
|
||||
self.ui.preCommand.setText(config.pre_command or '')
|
||||
self.ui.runonceCommand.setText(config.runonce_command or '')
|
||||
self.ui.logLevelComboBox.setCurrentIndex(config.log_level)
|
||||
|
||||
if config.host:
|
||||
self.updateAuthenticators()
|
||||
|
||||
self.ui.username.setText('')
|
||||
self.ui.password.setText('')
|
||||
|
||||
self.ui.testButton.setEnabled(bool(config.master_token and config.host))
|
||||
|
||||
@property
|
||||
def api(self) -> udsactor.rest.UDSServerApi:
|
||||
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
|
||||
|
||||
def browse(self, lineEdit: 'QLineEdit', caption: str) -> None:
|
||||
name = QFileDialog.getOpenFileName(parent=self, caption=caption, directory=os.path.dirname(lineEdit.text()))[0]
|
||||
if name:
|
||||
if ' ' in name:
|
||||
name = '"' + name + '"'
|
||||
lineEdit.setText(os.path.normpath(name))
|
||||
|
||||
def browsePreconnect(self) -> None:
|
||||
self.browse(self.ui.preCommand, 'Select Preconnect command')
|
||||
|
||||
def browseRunOnce(self) -> None:
|
||||
self.browse(self.ui.runonceCommand, 'Select Runonce command')
|
||||
|
||||
def browsePostConfig(self) -> None:
|
||||
self.browse(self.ui.postConfigCommand, 'Select Postconfig command')
|
||||
|
||||
def updateAuthenticators(self) -> None:
|
||||
if self.ui.host.text() != self._host:
|
||||
self._host = self.ui.host.text()
|
||||
self.ui.authenticators.clear()
|
||||
auth: udsactor.types.AuthenticatorType
|
||||
auths = list(self.api.enumerateAuthenticators())
|
||||
if auths:
|
||||
for auth in auths:
|
||||
self.ui.authenticators.addItem(auth.auth, userData=auth)
|
||||
# Last, add "admin" authenticator (for uds root user)
|
||||
self.ui.authenticators.addItem('Administration', userData=udsactor.types.AuthenticatorType('admin', 'admin', 'admin', 'admin', 1, False))
|
||||
|
||||
def textChanged(self) -> None:
|
||||
enableButtons = bool(self.ui.host.text() and self.ui.username.text() and self.ui.password.text() and self.ui.authenticators.currentText())
|
||||
self.ui.registerButton.setEnabled(enableButtons)
|
||||
self.ui.testButton.setEnabled(False) # Only registered information can be checked
|
||||
|
||||
def finish(self) -> None:
|
||||
self.close()
|
||||
|
||||
def testUDSServer(self) -> None:
|
||||
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
|
||||
if not config.master_token or not config.host:
|
||||
self.ui.testButton.setEnabled(False)
|
||||
return
|
||||
try:
|
||||
api = udsactor.rest.UDSServerApi(config.host, config.validateCertificate)
|
||||
if not api.test(config.master_token, udsactor.types.MANAGED):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Current configured token seems to be invalid for {}. Please, request a new one.'.format(config.host),
|
||||
QMessageBox.Ok
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configuration for {} seems to be correct.'.format(config.host),
|
||||
QMessageBox.Ok
|
||||
)
|
||||
except Exception:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configured host {} seems to be inaccesible.'.format(config.host),
|
||||
QMessageBox.Ok
|
||||
)
|
||||
|
||||
def registerWithUDS(self) -> None:
|
||||
# Get network card. Will fail if no network card is available, but don't mind (not contempled)
|
||||
data: udsactor.types.InterfaceInfoType = next(udsactor.platform.operations.getNetworkInfo())
|
||||
try:
|
||||
token = self.api.register(
|
||||
self.ui.authenticators.currentData().auth,
|
||||
self.ui.username.text(),
|
||||
self.ui.password.text(),
|
||||
udsactor.platform.operations.getComputerName(),
|
||||
data.ip or '', # IP
|
||||
data.mac or '', # MAC
|
||||
self.ui.preCommand.text(),
|
||||
self.ui.runonceCommand.text(),
|
||||
self.ui.postConfigCommand.text(),
|
||||
self.ui.logLevelComboBox.currentIndex() # Loglevel
|
||||
)
|
||||
# Store parameters on register for later use, notify user of registration
|
||||
udsactor.platform.store.writeConfig(
|
||||
udsactor.types.ActorConfigurationType(
|
||||
actorType=udsactor.types.MANAGED,
|
||||
host=self.ui.host.text(),
|
||||
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
|
||||
master_token=token,
|
||||
pre_command=self.ui.preCommand.text(),
|
||||
post_command=self.ui.postConfigCommand.text(),
|
||||
runonce_command=self.ui.runonceCommand.text(),
|
||||
log_level=self.ui.logLevelComboBox.currentIndex()
|
||||
)
|
||||
)
|
||||
# Enables test button
|
||||
self.ui.testButton.setEnabled(True)
|
||||
# Informs the user
|
||||
QMessageBox.information(self, 'UDS Registration', 'Registration with UDS completed.', QMessageBox.Ok)
|
||||
except udsactor.rest.RESTError as e:
|
||||
self.ui.testButton.setEnabled(False)
|
||||
QMessageBox.critical(self, 'UDS Registration', 'UDS Registration error: {}'.format(e), QMessageBox.Ok)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# If to be run as "sudo" on linux, we will need this to avoid problems
|
||||
if 'linux' in sys.platform:
|
||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
if udsactor.platform.operations.checkPermissions() is False:
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||
sys.exit(1)
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
156
actor/src/actor_config_unmanaged.py
Executable file
156
actor/src/actor_config_unmanaged.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import sys
|
||||
import os
|
||||
import pickle
|
||||
import logging
|
||||
import typing
|
||||
|
||||
import PyQt5 # pylint: disable=unused-import
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
|
||||
import udsactor
|
||||
|
||||
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
|
||||
|
||||
logger = logging.getLogger('actor')
|
||||
|
||||
class UDSConfigDialog(QDialog):
|
||||
_host: str = ''
|
||||
_config: udsactor.types.ActorConfigurationType
|
||||
|
||||
def __init__(self) -> None:
|
||||
QDialog.__init__(self, None)
|
||||
# Get local config config
|
||||
self._config = udsactor.platform.store.readConfig()
|
||||
self.ui = Ui_UdsActorSetupDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.host.setText(self._config.host)
|
||||
self.ui.validateCertificate.setCurrentIndex(1 if self._config.validateCertificate else 0)
|
||||
self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level)
|
||||
self.ui.serviceToken.setText(self._config.master_token)
|
||||
|
||||
self.ui.testButton.setEnabled(bool(self._config.master_token and self._config.host))
|
||||
|
||||
@property
|
||||
def api(self) -> udsactor.rest.UDSServerApi:
|
||||
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
|
||||
|
||||
def finish(self) -> None:
|
||||
self.close()
|
||||
|
||||
def configChanged(self, text: str) -> None:
|
||||
self.ui.testButton.setEnabled(self.ui.host.text() == self._config.host and self.ui.serviceToken.text() == self._config.master_token)
|
||||
|
||||
def testUDSServer(self) -> None:
|
||||
if not self._config.master_token or not self._config.host:
|
||||
self.ui.testButton.setEnabled(False)
|
||||
return
|
||||
try:
|
||||
api = udsactor.rest.UDSServerApi(self._config.host, self._config.validateCertificate)
|
||||
if not api.test(self._config.master_token, udsactor.types.UNMANAGED):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Service token seems to be invalid . Please, check token validity.',
|
||||
QMessageBox.Ok
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configuration for {} seems to be correct.'.format(self._config.host),
|
||||
QMessageBox.Ok
|
||||
)
|
||||
except Exception:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configured host {} seems to be inaccesible.'.format(self._config.host),
|
||||
QMessageBox.Ok
|
||||
)
|
||||
|
||||
def saveConfig(self) -> None:
|
||||
# Store parameters on register for later use, notify user of registration
|
||||
self._config = udsactor.types.ActorConfigurationType(
|
||||
actorType=udsactor.types.UNMANAGED,
|
||||
host=self.ui.host.text(),
|
||||
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
|
||||
master_token=self.ui.serviceToken.text(),
|
||||
log_level=self.ui.logLevelComboBox.currentIndex()
|
||||
)
|
||||
|
||||
udsactor.platform.store.writeConfig(self._config)
|
||||
# Enables test button
|
||||
self.ui.testButton.setEnabled(True)
|
||||
# Informs the user
|
||||
QMessageBox.information(self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# If to be run as "sudo" on linux, we will need this to avoid problems
|
||||
if 'linux' in sys.platform:
|
||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
if udsactor.platform.operations.checkPermissions() is False:
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok)
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
if sys.argv[1] == 'export':
|
||||
try:
|
||||
with open(sys.argv[2], 'wb') as f:
|
||||
pickle.dump(udsactor.platform.store.readConfig(), f, protocol=3)
|
||||
except Exception as e:
|
||||
print('Error exporting configuration file: {}'.format(e))
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
if sys.argv[1] == 'import':
|
||||
try:
|
||||
with open(sys.argv[2], 'rb') as f:
|
||||
config = pickle.load(f)
|
||||
udsactor.platform.store.writeConfig(config)
|
||||
except Exception as e:
|
||||
print('Error importing configuration file: {}'.format(e))
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
40
actor/src/actor_service.py
Executable file
40
actor/src/actor_service.py
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from udsactor.windows import runner
|
||||
else:
|
||||
from udsactor.linux import runner
|
||||
|
||||
if __name__ == "__main__":
|
||||
runner.run()
|
363
actor/src/designer/setup-dialog-unmanaged.ui
Normal file
363
actor/src/designer/setup-dialog-unmanaged.ui
Normal file
@@ -0,0 +1,363 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>Adolfo Gómez</author>
|
||||
<class>UdsActorSetupDialog</class>
|
||||
<widget class="QDialog" name="UdsActorSetupDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>595</width>
|
||||
<height>220</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Verdana</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::DefaultContextMenu</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>UDS Actor Configuration Tool</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="uds.qrc">
|
||||
<normaloff>:/img/img/uds-icon.png</normaloff>:/img/img/uds-icon.png</iconset>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="locale">
|
||||
<locale language="English" country="UnitedStates"/>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QPushButton" name="saveButton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>180</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>181</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::DefaultContextMenu</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to register Actor with UDS Broker</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Configuration</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>410</x>
|
||||
<y>180</y>
|
||||
<width>171</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>171</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Closes UDS Actor Configuration (discard pending changes if any)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="testButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>180</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>181</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to test existing configuration (disabled if no config found)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Test configuration</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>571</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_security">
|
||||
<property name="text">
|
||||
<string>SSL Validation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="validateCertificate">
|
||||
<property name="toolTip">
|
||||
<string>Select communication security with broker</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=" font-weight:600;">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ignore certificate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Verify certificate</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_host">
|
||||
<property name="text">
|
||||
<string>UDS Server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="host">
|
||||
<property name="acceptDrops">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Uds Broker Server Addres. Use IP or FQDN</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Enter here the UDS Broker Addres using either its IP address or its FQDN address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_serviceToken">
|
||||
<property name="text">
|
||||
<string>Service Token</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="serviceToken">
|
||||
<property name="toolTip">
|
||||
<string>UDS user with administration rights (Will not be stored on template)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_loglevel">
|
||||
<property name="text">
|
||||
<string>Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="logLevelComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">DEBUG</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">INFO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">ERROR</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">FATAL</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>label_host</zorder>
|
||||
<zorder>host</zorder>
|
||||
<zorder>label_serviceToken</zorder>
|
||||
<zorder>serviceToken</zorder>
|
||||
<zorder>validateCertificate</zorder>
|
||||
<zorder>label_security</zorder>
|
||||
<zorder>label_loglevel</zorder>
|
||||
<zorder>logLevelComboBox</zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="uds.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>closeButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>finish()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>315</x>
|
||||
<y>165</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>231</x>
|
||||
<y>161</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>testButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>testUDSServer()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>300</x>
|
||||
<y>281</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>294</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>saveButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>saveConfig()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>100</x>
|
||||
<y>191</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>297</x>
|
||||
<y>109</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>host</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>configChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>61</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>297</x>
|
||||
<y>109</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>serviceToken</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>configChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>100</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>297</x>
|
||||
<y>109</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>finish()</slot>
|
||||
<slot>saveConfig()</slot>
|
||||
<slot>testUDSServer()</slot>
|
||||
<slot>configChanged()</slot>
|
||||
</slots>
|
||||
</ui>
|
660
actor/src/designer/setup-dialog.ui
Normal file
660
actor/src/designer/setup-dialog.ui
Normal file
@@ -0,0 +1,660 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>Adolfo Gómez</author>
|
||||
<class>UdsActorSetupDialog</class>
|
||||
<widget class="QDialog" name="UdsActorSetupDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>590</width>
|
||||
<height>307</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Verdana</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::DefaultContextMenu</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>UDS Actor Configuration Tool</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="uds.qrc">
|
||||
<normaloff>:/img/img/uds-icon.png</normaloff>:/img/img/uds-icon.png</iconset>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="locale">
|
||||
<locale language="English" country="UnitedStates"/>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QPushButton" name="registerButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>270</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>181</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::DefaultContextMenu</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to register Actor with UDS Broker</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Register with UDS</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>410</x>
|
||||
<y>270</y>
|
||||
<width>171</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>171</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Closes UDS Actor Configuration (discard pending changes if any)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>571</width>
|
||||
<height>241</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_uds">
|
||||
<attribute name="title">
|
||||
<string>UDS Server</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>551</width>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_host">
|
||||
<property name="text">
|
||||
<string>UDS Server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="host">
|
||||
<property name="acceptDrops">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Uds Broker Server Addres. Use IP or FQDN</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Enter here the UDS Broker Addres using either its IP address or its FQDN address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_auth">
|
||||
<property name="text">
|
||||
<string>Authenticator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="authenticators">
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Select the UDS Broker authenticator for credentials validation</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_username">
|
||||
<property name="text">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="username">
|
||||
<property name="toolTip">
|
||||
<string>UDS user with administration rights (Will not be stored on template)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_password">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="toolTip">
|
||||
<string>Password for user (Will not be stored on template)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Administrator password for the user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html></string>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="validateCertificate">
|
||||
<property name="toolTip">
|
||||
<string>Select communication security with broker</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=" font-weight:600;">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ignore certificate</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Verify certificate</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_security">
|
||||
<property name="text">
|
||||
<string>SSL Validation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>label_host</zorder>
|
||||
<zorder>host</zorder>
|
||||
<zorder>label_auth</zorder>
|
||||
<zorder>label_username</zorder>
|
||||
<zorder>username</zorder>
|
||||
<zorder>label_password</zorder>
|
||||
<zorder>password</zorder>
|
||||
<zorder>validateCertificate</zorder>
|
||||
<zorder>label_security</zorder>
|
||||
<zorder>authenticators</zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_advanced">
|
||||
<attribute name="title">
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="layoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>551</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_host_2">
|
||||
<property name="text">
|
||||
<string>Preconnect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="preCommand">
|
||||
<property name="acceptDrops">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Pre connection command. Executed just before the user is connected to machine.</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browsePreconnectButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_username_2">
|
||||
<property name="text">
|
||||
<string>Runonce</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="runonceCommand">
|
||||
<property name="toolTip">
|
||||
<string>Run once command. Executed on first boot, just before UDS does anything.</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseRunOnceButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_password_2">
|
||||
<property name="text">
|
||||
<string>Postconfig</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="postConfigCommand">
|
||||
<property name="toolTip">
|
||||
<string>Command to execute after UDS finalizes the VM configuration.</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Normal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browsePostConfigButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_loglevel">
|
||||
<property name="text">
|
||||
<string>Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="logLevelComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">DEBUG</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">INFO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">ERROR</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">FATAL</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="testButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>270</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>181</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to test existing configuration (disabled if no config found)</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Test configuration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="uds.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>closeButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>finish()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>315</x>
|
||||
<y>165</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>231</x>
|
||||
<y>161</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>registerButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>registerWithUDS()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>239</x>
|
||||
<y>132</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>285</x>
|
||||
<y>185</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>host</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>textChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>239</x>
|
||||
<y>59</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>username</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>textChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>239</x>
|
||||
<y>98</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>password</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>textChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>239</x>
|
||||
<y>137</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>browsePreconnectButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>browsePreconnect()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>430</x>
|
||||
<y>60</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>243</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>browsePostConfigButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>browsePostConfig()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>430</x>
|
||||
<y>142</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>243</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>browseRunOnceButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>browseRunOnce()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>430</x>
|
||||
<y>101</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>243</x>
|
||||
<y>150</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>host</sender>
|
||||
<signal>editingFinished()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>updateAuthenticators()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>343</x>
|
||||
<y>98</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>294</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>authenticators</sender>
|
||||
<signal>currentTextChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>textChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>343</x>
|
||||
<y>137</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>294</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>testButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>testUDSServer()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>300</x>
|
||||
<y>281</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>294</x>
|
||||
<y>153</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>textChanged()</slot>
|
||||
<slot>finish()</slot>
|
||||
<slot>registerWithUDS()</slot>
|
||||
<slot>browsePreconnect()</slot>
|
||||
<slot>browseRunOnce()</slot>
|
||||
<slot>browsePostConfig()</slot>
|
||||
<slot>updateAuthenticators()</slot>
|
||||
<slot>testUDSServer()</slot>
|
||||
</slots>
|
||||
</ui>
|
5
actor/src/designer/uds.qrc
Normal file
5
actor/src/designer/uds.qrc
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="img">
|
||||
<file>../img/uds-icon.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
16
actor/src/designer/update.sh
Executable file
16
actor/src/designer/update.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
function process {
|
||||
# pyuic4 about-dialog.ui -o about_dialog_ui.py -x
|
||||
# pyuic4 message-dialog.ui -o message_dialog_ui.py
|
||||
pyuic5 setup-dialog.ui -o ../ui/setup_dialog_ui.py --import-from=ui
|
||||
pyuic5 setup-dialog-unmanaged.ui -o ../ui/setup_dialog_unmanaged_ui.py --import-from=ui
|
||||
}
|
||||
|
||||
pyrcc5 uds.qrc -o ../ui/uds_rc.py
|
||||
|
||||
|
||||
# process current directory ui's
|
||||
process
|
||||
|
BIN
actor/src/img/uds-icon.png
Normal file
BIN
actor/src/img/uds-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
38
actor/src/udsactor/__init__.py
Normal file
38
actor/src/udsactor/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from . import types
|
||||
from . import rest
|
||||
from . import platform
|
||||
|
||||
__title__ = 'udsactor'
|
||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2020 VirtualCable S.L.U."
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -29,24 +29,23 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import os.path
|
||||
import secrets
|
||||
import typing
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
from os.path import expanduser
|
||||
logFile = expanduser('~/udsclient.log')
|
||||
else:
|
||||
logFile = os.path.join(tempfile.gettempdir(), b'udsclient.log')
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import types
|
||||
|
||||
logging.basicConfig(
|
||||
filename=logFile,
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.DEBUG
|
||||
)
|
||||
|
||||
logger = logging.getLogger('udsclient')
|
||||
def saveCertificate(certInfo: 'types.CertificateInfoType') -> typing.Tuple[str, str]:
|
||||
"""
|
||||
Returns CertificateFile, Password tuple generated from certInfo
|
||||
"""
|
||||
certFile = os.path.join(tempfile.gettempdir(), secrets.token_hex(16))
|
||||
|
||||
with open(certFile, "w") as f:
|
||||
f.write(certInfo.private_key+certInfo.server_certificate)
|
||||
|
||||
return certFile, certInfo.password
|
247
actor/src/udsactor/client.py
Normal file
247
actor/src/udsactor/client.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import threading
|
||||
import time
|
||||
import datetime
|
||||
import signal
|
||||
import typing
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
from PyQt5.QtCore import QByteArray, QBuffer, QIODevice, pyqtSignal
|
||||
|
||||
from . import rest
|
||||
from . import tools
|
||||
from . import platform
|
||||
|
||||
from .log import logger
|
||||
|
||||
from .http import client
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import types
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QMainWindow
|
||||
|
||||
class UDSClientQApp(QApplication):
|
||||
_app: 'UDSActorClient'
|
||||
_initialized: bool
|
||||
_mainWindow: typing.Optional['QMainWindow']
|
||||
|
||||
message = pyqtSignal(str, name='message')
|
||||
|
||||
def __init__(self, args) -> None:
|
||||
super().__init__(args)
|
||||
|
||||
self._mainWindow = None
|
||||
self._initialized = False
|
||||
|
||||
# This will be invoked on session close
|
||||
self.commitDataRequest.connect(self.end) # type: ignore # Will be invoked on session close, to gracely close app
|
||||
# self.aboutToQuit.connect(self.end)
|
||||
self.message.connect(self.showMessage) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
||||
|
||||
# Execute backgroup thread for actions
|
||||
self._app = UDSActorClient(self)
|
||||
|
||||
def init(self) -> None:
|
||||
# Notify loging and mark it
|
||||
logger.debug('Starting APP')
|
||||
|
||||
if self._mainWindow:
|
||||
self._mainWindow.hide()
|
||||
|
||||
self._app.start()
|
||||
self._initialized = True
|
||||
|
||||
def end(self, sessionManager=None) -> None: # pylint: disable=unused-argument
|
||||
if not self._initialized:
|
||||
return
|
||||
|
||||
self._initialized = False
|
||||
|
||||
logger.debug('Stopping app thread')
|
||||
self._app.stop()
|
||||
|
||||
self._app.join()
|
||||
|
||||
def showMessage(self, message: str) -> None:
|
||||
QMessageBox.information(None, 'Message', message) # type: ignore
|
||||
|
||||
def setMainWindow(self, mw: 'QMainWindow'):
|
||||
self._mainWindow = mw
|
||||
|
||||
|
||||
class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-attributes
|
||||
_running: bool
|
||||
_forceLogoff: bool
|
||||
_extraLogoff: str
|
||||
_qApp: UDSClientQApp
|
||||
_listener: client.HTTPServerThread
|
||||
_loginInfo: typing.Optional['types.LoginResultInfoType']
|
||||
_notified: bool
|
||||
_notifiedDeadline: bool
|
||||
_sessionStartTime: datetime.datetime
|
||||
api: rest.UDSClientApi
|
||||
|
||||
def __init__(self, qApp: QApplication):
|
||||
super().__init__()
|
||||
|
||||
self.api = rest.UDSClientApi() # Self initialized
|
||||
self._qApp = typing.cast(UDSClientQApp, qApp)
|
||||
self._running = False
|
||||
self._forceLogoff = False
|
||||
self._extraLogoff = ''
|
||||
self._listener = client.HTTPServerThread(self)
|
||||
self._loginInfo = None
|
||||
self._notified = False
|
||||
self._notifiedDeadline = False
|
||||
|
||||
# Capture stop signals..
|
||||
logger.debug('Setting signals...')
|
||||
signal.signal(signal.SIGINT, self.stopSignal)
|
||||
signal.signal(signal.SIGTERM, self.stopSignal)
|
||||
|
||||
def stopSignal(self, signum, frame) -> None: # pylint: disable=unused-argument
|
||||
logger.info('Stop signal received')
|
||||
self.stop()
|
||||
|
||||
def checkDeadLine(self):
|
||||
if self._loginInfo is None or not self._loginInfo.dead_line: # No deadline check
|
||||
return
|
||||
|
||||
remainingTime = self._loginInfo.dead_line - (datetime.datetime.now() - self._sessionStartTime).total_seconds()
|
||||
logger.debug('Remaining time: {}'.format(remainingTime))
|
||||
|
||||
if not self._notifiedDeadline and remainingTime < 300: # With five minutes, show a warning message
|
||||
self._notifiedDeadline = True
|
||||
self._showMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
|
||||
return
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.debug('Session dead line reached. Logging out')
|
||||
self._running = False
|
||||
self._forceLogoff = True
|
||||
|
||||
def checkIdle(self) -> None:
|
||||
if self._loginInfo is None or not self._loginInfo.max_idle: # No idle check
|
||||
return
|
||||
|
||||
idleTime = platform.operations.getIdleDuration()
|
||||
remainingTime = self._loginInfo.max_idle - idleTime
|
||||
|
||||
logger.debug('Idle: %s Remaining: %s', idleTime, remainingTime)
|
||||
|
||||
if remainingTime > 120: # Reset show Warning dialog if we have more than 5 minutes left
|
||||
self._notified = False
|
||||
return
|
||||
|
||||
if not self._notified and remainingTime < 120: # With two minutes, show a warning message
|
||||
self._notified = True
|
||||
self._showMessage('You have been idle for too long. The session will end if you don\'t resume operations.')
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.info('User has been idle for too long, exiting from session')
|
||||
self._extraLogoff = ' (idle: {} vs {})'.format(int(idleTime), self._loginInfo.max_idle)
|
||||
self._running = False
|
||||
self._forceLogoff = True
|
||||
|
||||
def run(self) -> None:
|
||||
logger.debug('UDS Actor thread')
|
||||
self._listener.start() # async listener for service
|
||||
self._running = True
|
||||
|
||||
self._sessionStartTime = datetime.datetime.now()
|
||||
|
||||
time.sleep(0.4) # Wait a bit before sending login
|
||||
|
||||
try:
|
||||
# Notify loging and mark it
|
||||
self._loginInfo = self.api.login(platform.operations.getCurrentUser(), platform.operations.getSessionType())
|
||||
|
||||
if self._loginInfo.max_idle:
|
||||
platform.operations.initIdleDuration(self._loginInfo.max_idle)
|
||||
|
||||
while self._running:
|
||||
# Check Idle & dead line
|
||||
self.checkIdle()
|
||||
self.checkDeadLine()
|
||||
|
||||
time.sleep(1.3) # Sleeps between loop iterations
|
||||
|
||||
self._loginInfo = None
|
||||
self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff)
|
||||
except Exception as e:
|
||||
logger.error('Error on client loop: %s', e)
|
||||
|
||||
self._listener.stop() # async listener for service
|
||||
|
||||
# Notify exit to qt
|
||||
QApplication.quit()
|
||||
|
||||
if self._forceLogoff:
|
||||
time.sleep(1.3) # Wait a bit before forcing logoff
|
||||
platform.operations.loggoff()
|
||||
|
||||
def _showMessage(self, message: str) -> None:
|
||||
self._qApp.message.emit(message) # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
||||
|
||||
def stop(self) -> None:
|
||||
logger.debug('Stopping client Service')
|
||||
self._running = False
|
||||
|
||||
def logout(self) -> typing.Any:
|
||||
self._forceLogoff = True
|
||||
self._running = False
|
||||
return 'ok'
|
||||
|
||||
def message(self, msg: str) -> typing.Any:
|
||||
threading.Thread(target=self._showMessage, args=(msg,)).start()
|
||||
return 'ok'
|
||||
|
||||
def screenshot(self) -> typing.Any:
|
||||
'''
|
||||
On windows, an RDP session with minimized screen will render "black screen"
|
||||
So only when user is using RDP connection will return an "actual" screenshot
|
||||
'''
|
||||
pixmap: 'QPixmap' = self._qApp.primaryScreen().grabWindow(0) # type: ignore
|
||||
ba = QByteArray()
|
||||
buffer = QBuffer(ba)
|
||||
buffer.open(QIODevice.WriteOnly)
|
||||
pixmap.save(buffer, 'PNG')
|
||||
buffer.close()
|
||||
scrBase64 = bytes(ba.toBase64()).decode() # type: ignore # there are problems with Pylance and connects on PyQt5... :)
|
||||
logger.debug('Screenshot length: %s', len(scrBase64))
|
||||
return scrBase64 # 'result' of JSON will contain base64 of screen
|
||||
|
||||
def script(self, script: str) -> typing.Any:
|
||||
tools.ScriptExecutorThread(script).start()
|
||||
return 'ok'
|
7
actor/src/udsactor/http/cert.py
Normal file
7
actor/src/udsactor/http/cert.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .. import types
|
||||
|
||||
defaultCertificate = types.CertificateInfoType(
|
||||
private_key='-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFHTBPBgkqhkiG9w0BBQ0wQjApBgkqhkiG9w0BBQwwHAQIfG2+iMYJBswCAggA\nMAwGCCqGSIb3DQIJBQAwFQYJKwYBBAGXVQECBAhCusU5R8ulZQSCBMgheyZ81Qkq\n+TcbPeBlUGCFllSUOo7xQ/OuwYSmzLx8LpN0hQNv4azF6MYH+I8eMSPd3A547yW3\nJE4GjIBfRvcq2X1UZ2FQfECU9UP0ShPuPrVhIh6ZZklmlRjbIF8hGfSzXAuafQb+\n4wXXsofahi/SPgqK1Gw65nRiMcoeRZchJkx8pBgKVWED6Cbh6aAkeqkVKPnsebiV\n6kE+0C7+hgNUbyRd46R+/5NXzPjg4ItfSak+PLzQ1KeRv4Cu6DdzRKJ4V9/MlNdU\nNNEkSVSEaRn4sv+eByU4uxBMaSmD1tLc/A7OmaAeRpIQvls3Zcf2+V0+anAtjbjd\n6eIb2nceey+dKFm4ewlR4mXuzj1QowRTHceOIkvKIrOODxdy9M5hNBZ7VLum29tY\nRhqtmEH2BZZJ8SpM2SsEZzPxqJFiVZbvpeOKjxlMyn1dFWn1rP8uMnfuMKqBaj5D\nd5clOPlwebYw5UpM6Vvawu4nGqxECTSWcfNlDYO5U/0Fsm9+JIrJ7Buukgv2+rhs\nD/6oUK9NB8AW9qnDr7UxbC/ujhkKQG3woaZlPbiMs5WQaS+DrTg4N49wPzS0h+ME\nF8ZzuPnd6+sMGQioCIrQAZ08rk54oCijBhFh8/EQhQKGsMFw2swi9t6+FVU5Bvil\nlhmBd3LA5EuQ5y1X0jRL/+GDiUiZw1gOJP8d/XzhUJL9AmamdqJ6/rAU7lUTNWkM\ndzmFonUO2Mh2zgEEudHsTOH8udZ2l64LIHc6fCkDmM8QzghjrEFyci6R8333DSSM\nwbM0MvyTLM7TTqZUD60EgD+Ihyr/wJcBZY7GVn7hTq7ee14zeI+dZFmTMYOnt0mA\ngof19t0naPPZU+zyl/ambNF5mmSkGOAl4IBHNvPt5ztEVbNpwW3DHbmdYW71Ax+z\nCDlr4iKZahv21o1PCesPV2IlaHZFD6aBRt0DxzMqtq9cpWsI1g7aEaAjRbSvqhMY\npUeqFXz/GfR9rjRkufr48//ll0/Q/Ogx7m1TjQ6mAEQrklI7pa2W0u3H0BpSZSis\nR6ST3ulE+wfsp8cau6q2er+BSsDhBjSn9FeCUjHzY56u9ud/kb6/jLEdgxNpj0na\n3WVqCCCL/dAFSWznBmdracZsRMXapXInHCiiOEkXXbXIXvRKiTPJXdN+w2/U2j2B\nwXZuazVSpmM+xAZTAS9dtBUQJo+5px9b6P09uagvTA32ezbpPXf+hSfmTdUwbmAY\nrmE9SW85tzX+cD17loygBBRrjOr4uQy/s/9FqLx8bM73jly05rdOmX28ECKwEA05\n8aCFkfqrl9J9doVapaUlywpJVPFtE6W6tCF+ULMfb16vEjT1du1+epEnbGGLRQxg\n3aFLyKlvFaNvR38fiQFUGtBgGOaBN3rhGpbMwjch3oReXv9X/4UCL6sVIiOH2H3c\nVSZdC3O5g6CMVe4zckUe1k9mLDb5524IHDFfptZ6Bw+uzrqIy3GHW8dJF2AK471b\nMUnCojTpdbFHaUs2u/rNKVUyY+vLf8hkyP+znBUoPxSJtty53EWNukxjjsxx0lx3\niZGqN72lXlXuSFZAIxi307+xxE21cbzDsMidyJkbKKGm/F4BOKvX9jWmAyYmBG6A\n1L3yNRouFWsYDwYAX2nZ1is=\n-----END ENCRYPTED PRIVATE KEY-----\n',
|
||||
server_certificate='-----BEGIN CERTIFICATE-----\nMIIDcTCCAlkCBDfnXU8wDQYJKoZIhvcNAQELBQAwfTELMAkGA1UEBhMCRVMxDzAN\nBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMREwDwYDVQQKDAhVRFMgQ2Vy\ndDERMA8GA1UECwwIVURTIENlcnQxEjAQBgNVBAMMCTEyNy4wLjAuMTESMBAGA1Ud\nEQwJMTI3LjAuMC4xMB4XDTIwMDIxNzExNTkzMloXDTMwMDIxNDExNTkzMlowfTEL\nMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMREw\nDwYDVQQKDAhVRFMgQ2VydDERMA8GA1UECwwIVURTIENlcnQxEjAQBgNVBAMMCTEy\nNy4wLjAuMTESMBAGA1UdEQwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA2e1cW7YtRpNLazR3f/LqLv8OB0rKh8cUPH4wuQhbBTkee8Wu\n5eMSadRCIyRbKj4b8dtVfI9QW0SrmhGuMx1KCh3CsYd9XsWiKbGkiRBHIDOn5pkF\n6PUayDJ8KjnGbfnZjp0AmxXP4r1OO8jUPqzKS9Ubf5PgwcwdFiUKVfVPwGwctwt5\nt9YpSRONw0rTsCjVHvO2dd9h6EopskLCWxpN8l9kNLwLM/6t0IqVKmn5/IYPKKN2\nCX8a7IXpxwoiUs4sBZYhUMBWikB1hKQRSYafp1Xvc5PeTFXTFqGANnqz0NoZ8tqL\n8qjQUN/PCdtzhfcP5RgT2g1qyS2RBCMYH7Zs0wIDAQABMA0GCSqGSIb3DQEBCwUA\nA4IBAQCUt+qlLA1N9VXMwDQAYG4Kt6/UlMHCXAajHQQGtjdyGJ4++m7EIjI96hMU\n3Cx2gp2ggR3JGnuSR+DdBvPl5iGku7J8KV0JiJg30gTY8JuUIy/PMLZWloYKrBHV\nlin2GujQ4OsIt3dbr4XtcKW1Wd7L6fBzHlq7Xyxh+gcTzTvTmq67Q9XKlBWsegMf\nv4FKy0lfcSFK3vTzswQtuTontG4TqLiT/4AnMt3D0cTQ6b6KoZwUUX/TDNhau06d\nQ4Ilz8X61ka+4HBkFSR5ahP9noCVhwO329h+6epO141E5Tep3OLc/GCF4oaKOlMR\nfqxf5f2bghU0fxmtEoNJTZkBsN1S\n-----END CERTIFICATE-----\n',
|
||||
password='Pw7qbatz5u-y-Z5ora2D2ZuBCm95AHnKRcpze53k8tw'
|
||||
)
|
166
actor/src/udsactor/http/client.py
Normal file
166
actor/src/udsactor/http/client.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import threading
|
||||
import http.server
|
||||
import secrets
|
||||
import json
|
||||
import typing
|
||||
|
||||
from ..log import logger
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..client import UDSActorClient
|
||||
|
||||
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.0'
|
||||
server_version = 'UDS Actor Server'
|
||||
sys_version = ''
|
||||
|
||||
_id: typing.ClassVar[str] # Random id for server
|
||||
_app: typing.ClassVar['UDSActorClient']
|
||||
|
||||
def sendJsonResponse(self, result: typing.Optional[typing.Any] = None, error: typing.Optional[str] = None, code: int = 200) -> None:
|
||||
data = json.dumps({'result': result, 'error': error})
|
||||
self.send_response(code)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.send_header('Server: ', self.server_version)
|
||||
self.end_headers()
|
||||
try:
|
||||
self.wfile.write(data.encode())
|
||||
except Exception:
|
||||
pass # Evict "broken pipe" when sending errors
|
||||
|
||||
def do_POST(self) -> None:
|
||||
# Only allows requests from localhost
|
||||
if self.client_address[0][:3] != '127':
|
||||
self.sendJsonResponse(error='Forbidden', code=403)
|
||||
|
||||
# Very simple path & params splitter
|
||||
path = self.path.split('?')[0][1:].split('/')
|
||||
if len(path) != 2 or path[0] != HTTPServerHandler._id:
|
||||
self.sendJsonResponse(error='Forbidden', code=403)
|
||||
|
||||
try:
|
||||
length = int(str(self.headers.get('content-length', '0')))
|
||||
content = self.rfile.read(length)
|
||||
params: typing.MutableMapping[str, str] = json.loads(content or '{}')
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
|
||||
self.sendJsonResponse(error='Invalid request', code=400)
|
||||
return
|
||||
|
||||
try:
|
||||
result = getattr(self, 'method_' + path[1])(params) # last part of path is method
|
||||
except AttributeError as e:
|
||||
logger.error('Invoked invalid method: %s: %s', path[1], e)
|
||||
self.sendJsonResponse(error='Invalid request', code=400)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing {}: {}'.format('/'.join(path), str(e)))
|
||||
self.sendJsonResponse(error='Internal error', code=500)
|
||||
return
|
||||
|
||||
self.sendJsonResponse(result)
|
||||
|
||||
# Internal methods
|
||||
def method_ping(self, params: typing.MutableMapping[str, str]) -> typing.Any:
|
||||
return 'pong'
|
||||
|
||||
def method_logout(self, params: typing.MutableMapping[str, str]) -> typing.Any:
|
||||
return self._app.logout()
|
||||
|
||||
def method_message(self, params: typing.MutableMapping[str, str]) -> typing.Any:
|
||||
return self._app.message(params['message'])
|
||||
|
||||
def method_screenshot(self, params: typing.MutableMapping[str, str]) -> typing.Any:
|
||||
return self._app.screenshot()
|
||||
|
||||
def method_script(self, params: typing.MutableMapping[str, str]) -> typing.Any:
|
||||
return self._app.script(params['script'])
|
||||
|
||||
def do_GET(self) -> None:
|
||||
self.sendJsonResponse(error='Forbidden', code=403)
|
||||
|
||||
def log_error(self, format: str, *args): # pylint: disable=redefined-builtin
|
||||
logger.error(format, *args)
|
||||
|
||||
def log_message(self, format: str, *args): # pylint: disable=redefined-builtin
|
||||
logger.debug(format, *args)
|
||||
|
||||
class HTTPServerThread(threading.Thread):
|
||||
_server: typing.Optional[http.server.HTTPServer]
|
||||
_app: 'UDSActorClient'
|
||||
|
||||
port: int
|
||||
id: str
|
||||
|
||||
def __init__(self, app: 'UDSActorClient'):
|
||||
super().__init__()
|
||||
|
||||
self._server = None
|
||||
self._app = app
|
||||
|
||||
self.port = -1
|
||||
self.id = secrets.token_urlsafe(16)
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return 'http://127.0.0.1:{}/{}'.format(self.port, self.id)
|
||||
|
||||
def stop(self) -> None:
|
||||
if self._server:
|
||||
logger.debug('Stopping Http-client Service')
|
||||
try:
|
||||
self._app.api.unregister(self.url)
|
||||
except Exception as e:
|
||||
logger.error('Error unregistering on actor service: %s', e)
|
||||
self._server.shutdown()
|
||||
self._server = None
|
||||
|
||||
def run(self):
|
||||
HTTPServerHandler._app = self._app # pylint: disable=protected-access
|
||||
HTTPServerHandler._id = self.id # pylint: disable=protected-access
|
||||
|
||||
self._server = http.server.HTTPServer(('127.0.0.1', 0), HTTPServerHandler)
|
||||
|
||||
self.port = self._server.socket.getsockname()[1]
|
||||
|
||||
# Register using app api
|
||||
logger.debug('Registered %s', self.url)
|
||||
try:
|
||||
self._app.api.register(self.url)
|
||||
except Exception as e:
|
||||
logger.error('Error registering on actor service: %s', e)
|
||||
|
||||
self._server.serve_forever()
|
98
actor/src/udsactor/http/clients_pool.py
Normal file
98
actor/src/udsactor/http/clients_pool.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import json
|
||||
import typing
|
||||
|
||||
import requests
|
||||
|
||||
from ..log import logger
|
||||
|
||||
# For avoid proxy on localhost connections
|
||||
NO_PROXY = {
|
||||
'http': None,
|
||||
'https': None,
|
||||
}
|
||||
|
||||
class UDSActorClientPool:
|
||||
_clientUrl: typing.List[str]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._clientUrl = []
|
||||
|
||||
def _post(self, method: str, data: typing.MutableMapping[str, str], timeout=2) -> typing.List[requests.Response]:
|
||||
removables: typing.List[str] = []
|
||||
result: typing.List[typing.Any] = []
|
||||
for clientUrl in self._clientUrl:
|
||||
try:
|
||||
result.append(requests.post(clientUrl + '/' + method, data=json.dumps(data), verify=False, timeout=timeout, proxies=NO_PROXY))
|
||||
except Exception as e:
|
||||
# If cannot request to a clientUrl, remove it from list
|
||||
logger.info('Could not connect with client %s: %s. Removed from registry.', e, clientUrl)
|
||||
removables.append(clientUrl)
|
||||
|
||||
# Remove failed connections
|
||||
for clientUrl in removables:
|
||||
self.unregister(clientUrl)
|
||||
|
||||
return result
|
||||
|
||||
def register(self, clientUrl: str) -> None:
|
||||
# Remove first if exists, to avoid duplicates
|
||||
self.unregister(clientUrl)
|
||||
# And add it again
|
||||
self._clientUrl.append(clientUrl)
|
||||
|
||||
def unregister(self, clientUrl: str) -> None:
|
||||
self._clientUrl = list((i for i in self._clientUrl if i != clientUrl))
|
||||
|
||||
def executeScript(self, script: str) -> None:
|
||||
self._post('script', {'script': script}, timeout=30)
|
||||
|
||||
def logout(self) -> None:
|
||||
self._post('logout', {})
|
||||
|
||||
def message(self, message: str) -> None:
|
||||
self._post('message', {'message': message})
|
||||
|
||||
def ping(self) -> bool:
|
||||
if not self._clientUrl:
|
||||
return True # No clients, ping ok
|
||||
self._post('ping', {}, timeout=1)
|
||||
return bool(self._clientUrl) # There was clients, but they are now lost!!!
|
||||
|
||||
def screenshot(self) -> typing.Optional[str]: # Screenshot are returned as base64
|
||||
for r in self._post('screenshot', {}, timeout=3):
|
||||
try:
|
||||
return r.json()['result']
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -29,16 +28,17 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import typing
|
||||
|
||||
try:
|
||||
import gtkui as theUI
|
||||
except Exception:
|
||||
import consoleui as theUI # @Reimport
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
|
||||
def message(title, message):
|
||||
theUI.message(title, message)
|
||||
|
||||
def question(title, message):
|
||||
return theUI.question(title, message)
|
||||
class Handler:
|
||||
_service: 'CommonService'
|
||||
_method: str
|
||||
_params: typing.MutableMapping[str, str]
|
||||
|
||||
def __init__(self, service: 'CommonService', method: str, params: typing.MutableMapping[str, str]):
|
||||
self._service = service
|
||||
self._method = method
|
||||
self._params = params
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -26,28 +25,32 @@
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import typing
|
||||
|
||||
from uds.core import services
|
||||
from . import handler
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
|
||||
class PhysicalMachinesProvider(services.ServiceProvider):
|
||||
# No extra data needed
|
||||
class LocalProvider(handler.Handler):
|
||||
|
||||
# What services do we offer?
|
||||
offers = []
|
||||
typeName = 'Static IP Machines Provider'
|
||||
typeType = 'PhysicalMachinesServiceProvider'
|
||||
typeDescription = 'Provides connection to machines by IP'
|
||||
iconFile = 'provider.png'
|
||||
def post_login(self) -> typing.Any:
|
||||
result = self._service.login(self._params['username'], self._params['session_type'])
|
||||
return result._asdict()
|
||||
|
||||
from .IPMachinesService import IPMachinesService
|
||||
from .IPSingleMachineService import IPSingleMachineService
|
||||
offers = [IPMachinesService, IPSingleMachineService]
|
||||
def post_logout(self) -> typing.Any:
|
||||
self._service.logout(self._params['username'])
|
||||
return 'ok'
|
||||
|
||||
def __unicode__(self):
|
||||
return "Physical Machines Provider"
|
||||
def post_ping(self) -> typing.Any:
|
||||
return 'pong'
|
||||
|
||||
def post_register(self) -> typing.Any:
|
||||
self._service._clientsPool.register(self._params['callback_url']) # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
||||
def post_unregister(self) -> typing.Any:
|
||||
self._service._clientsPool.unregister(self._params['callback_url']) # pylint: disable=protected-access
|
87
actor/src/udsactor/http/public.py
Normal file
87
actor/src/udsactor/http/public.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import typing
|
||||
|
||||
from .. import tools
|
||||
from . import handler
|
||||
|
||||
from ..log import logger
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
|
||||
class PublicProvider(handler.Handler):
|
||||
def post_logout(self) -> typing.Any:
|
||||
logger.debug('Sending LOGOFF to clients')
|
||||
self._service._clientsPool.logout() # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
||||
# Alias
|
||||
post_logoff = post_logout
|
||||
|
||||
def post_message(self) -> typing.Any:
|
||||
logger.debug('Sending MESSAGE to clients')
|
||||
if 'message' not in self._params:
|
||||
raise Exception('Invalid message parameters')
|
||||
self._service._clientsPool.message(self._params['message']) # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
||||
def post_script(self) -> typing.Any:
|
||||
logger.debug('Received script: {}'.format(self._params))
|
||||
if 'script' not in self._params:
|
||||
raise Exception('Invalid script parameters')
|
||||
if self._params.get('user', False):
|
||||
logger.debug('Sending SCRIPT to client')
|
||||
self._service._clientsPool.executeScript(self._params['script']) # pylint: disable=protected-access
|
||||
else:
|
||||
# Execute script at server space, that is, here
|
||||
# as a parallel thread
|
||||
th = tools.ScriptExecutorThread(self._params['script'])
|
||||
th.start()
|
||||
return 'ok'
|
||||
|
||||
def post_preConnect(self) -> typing.Any:
|
||||
logger.debug('Received Pre connection')
|
||||
if 'user' not in self._params or 'protocol' not in self._params:
|
||||
raise Exception('Invalid preConnect parameters')
|
||||
return self._service.preConnect(self._params['user'], self._params['protocol'], self._params.get('ip', 'unknown'), self._params.get('hostname', 'unknown'))
|
||||
|
||||
def get_information(self) -> typing.Any:
|
||||
# Return something useful? :)
|
||||
return 'UDS Actor Secure Server'
|
||||
|
||||
def get_screenshot(self) -> typing.Any:
|
||||
return self._service._clientsPool.screenshot() # pylint: disable=protected-access
|
||||
|
||||
def get_uuid(self) -> typing.Any:
|
||||
if self._service.isManaged():
|
||||
return self._service._cfg.own_token # pylint: disable=protected-access
|
||||
return ''
|
166
actor/src/udsactor/http/server.py
Normal file
166
actor/src/udsactor/http/server.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import os
|
||||
import threading
|
||||
import http.server
|
||||
import json
|
||||
import ssl
|
||||
import typing
|
||||
|
||||
from ..log import logger
|
||||
from .. import certs
|
||||
from .. import rest
|
||||
|
||||
from .public import PublicProvider
|
||||
from .local import LocalProvider
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
from .handler import Handler
|
||||
|
||||
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.0'
|
||||
server_version = 'UDS Actor Server'
|
||||
sys_version = ''
|
||||
|
||||
_service: typing.Optional['CommonService'] = None
|
||||
|
||||
def sendJsonResponse(self, result: typing.Optional[typing.Any] = None, error: typing.Optional[str] = None, code: int = 200) -> None:
|
||||
data = json.dumps({'result': result, 'error': error})
|
||||
self.send_response(code)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.send_header('Server: ', self.server_version)
|
||||
self.end_headers()
|
||||
self.wfile.write(data.encode())
|
||||
|
||||
def process(self, method: str, params: typing.MutableMapping[str, str]) -> None:
|
||||
if not self._service:
|
||||
self.sendJsonResponse(error='Not initialized', code=500)
|
||||
return
|
||||
|
||||
# Very simple path & params splitter
|
||||
path = self.path.split('?')[0][1:].split('/')
|
||||
|
||||
logger.debug('Path: %s, params: %s', path, params)
|
||||
|
||||
handlerType: typing.Optional[typing.Type['Handler']] = None
|
||||
|
||||
if len(path) == 3 and path[0] == 'actor' and path[1] == self._service._secret: # pylint: disable=protected-access
|
||||
# public method
|
||||
handlerType = PublicProvider
|
||||
elif len(path) == 2 and path[0] == 'ui':
|
||||
# private method, only from localhost
|
||||
if self.client_address[0][:3] == '127':
|
||||
handlerType = LocalProvider
|
||||
|
||||
if not handlerType:
|
||||
self.sendJsonResponse(error='Forbidden', code=403)
|
||||
return
|
||||
|
||||
try:
|
||||
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])() # last part of path is method
|
||||
except AttributeError:
|
||||
self.sendJsonResponse(error='Method not found', code=404)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing {} {}: {}'.format(method, '/'.join(path), str(e)))
|
||||
self.sendJsonResponse(error=str(e), code=500)
|
||||
return
|
||||
|
||||
self.sendJsonResponse(result)
|
||||
|
||||
def do_GET(self) -> None:
|
||||
try:
|
||||
params = {v.split('=')[0]: v.split('=')[1] for v in self.path.split('?')[1].split('&')}
|
||||
except Exception:
|
||||
params = {}
|
||||
|
||||
self.process('get', params)
|
||||
|
||||
def do_POST(self) -> None:
|
||||
try:
|
||||
length = int(str(self.headers.get('content-length', '0')))
|
||||
content = self.rfile.read(length)
|
||||
params: typing.MutableMapping[str, str] = json.loads(content)
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
|
||||
self.sendJsonResponse(error='Invalid parameters', code=400)
|
||||
return
|
||||
|
||||
self.process('post', params)
|
||||
|
||||
def log_error(self, format, *args): # pylint: disable=redefined-builtin
|
||||
logger.error(format, *args)
|
||||
|
||||
def log_message(self, format, *args): # pylint: disable=redefined-builtin
|
||||
logger.debug(format, *args)
|
||||
|
||||
class HTTPServerThread(threading.Thread):
|
||||
_server: typing.Optional[http.server.HTTPServer]
|
||||
_service: 'CommonService'
|
||||
_certFile: typing.Optional[str]
|
||||
|
||||
def __init__(self, service: 'CommonService'):
|
||||
super().__init__()
|
||||
|
||||
self._server = None
|
||||
self._service = service
|
||||
self._certFile = None
|
||||
|
||||
def stop(self) -> None:
|
||||
logger.debug('Stopping Http-server Service')
|
||||
if self._server:
|
||||
self._server.shutdown()
|
||||
self._server = None
|
||||
|
||||
if self._certFile:
|
||||
try:
|
||||
os.unlink(self._certFile)
|
||||
except Exception as e:
|
||||
logger.error('Error removing certificate file: %s', e)
|
||||
logger.debug('Http-server stopped')
|
||||
|
||||
def run(self):
|
||||
HTTPServerHandler._service = self._service # pylint: disable=protected-access
|
||||
|
||||
self._certFile, password = certs.saveCertificate(self._service._certificate) # pylint: disable=protected-access
|
||||
|
||||
self._server = http.server.HTTPServer(('0.0.0.0', rest.LISTEN_PORT), HTTPServerHandler)
|
||||
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
|
||||
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.options = ssl.CERT_NONE
|
||||
context.load_cert_chain(certfile=self._certFile, password=password)
|
||||
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)
|
||||
|
||||
self._server.serve_forever()
|
1
actor/src/udsactor/info.py
Normal file
1
actor/src/udsactor/info.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = '3.0.0'
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -29,5 +29,3 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
__updated__ = '2014-02-19'
|
173
actor/src/udsactor/linux/daemon.py
Normal file
173
actor/src/udsactor/linux/daemon.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import atexit
|
||||
from signal import SIGTERM, SIGKILL
|
||||
|
||||
from udsactor.log import logger
|
||||
|
||||
|
||||
class Daemon:
|
||||
"""
|
||||
A generic daemon class.
|
||||
|
||||
Usage: subclass the Daemon class and override the run() method
|
||||
"""
|
||||
|
||||
def __init__(self, pidfile: str, stdin: str = '/dev/null', stdout: str = '/dev/null', stderr: str = '/dev/null'):
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.pidfile = pidfile
|
||||
|
||||
def daemonize(self) -> None:
|
||||
"""
|
||||
do the UNIX double-fork magic, see Stevens' "Advanced
|
||||
Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||
"""
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit first parent
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
logger.error("fork #1 error: {}".format(e))
|
||||
sys.stderr.write("fork #1 failed: {}\n".format(e))
|
||||
sys.exit(1)
|
||||
|
||||
# decouple from parent environment
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# do second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit from second parent
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
logger.error("fork #2 error: {}".format(e))
|
||||
sys.stderr.write("fork #2 failed: {}\n".format(e))
|
||||
sys.exit(1)
|
||||
|
||||
# redirect standard file descriptors
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open(self.stdin, 'r')
|
||||
so = open(self.stdout, 'ab+')
|
||||
se = open(self.stderr, 'ab+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
# write pidfile
|
||||
atexit.register(self.removePidFile)
|
||||
pidStr = str(os.getpid())
|
||||
with open(self.pidfile, 'w+') as f:
|
||||
f.write("{}\n".format(pidStr))
|
||||
|
||||
def removePidFile(self) -> None:
|
||||
try:
|
||||
os.remove(self.pidfile)
|
||||
except Exception:
|
||||
# Not found/not permissions or whatever, ignore it
|
||||
pass
|
||||
|
||||
def start(self) -> None:
|
||||
"""
|
||||
Start the daemon
|
||||
"""
|
||||
logger.debug('Starting daemon')
|
||||
# Check for a pidfile to see if the daemon already runs
|
||||
if os.path.exists(self.pidfile):
|
||||
message = "pidfile {} already exist. Daemon already running?\n".format(self.pidfile)
|
||||
logger.error(message)
|
||||
sys.stderr.write(message)
|
||||
sys.exit(1)
|
||||
|
||||
# Start the daemon
|
||||
self.daemonize()
|
||||
try:
|
||||
self.run()
|
||||
except Exception as e:
|
||||
logger.error('Exception running process: {}'.format(e))
|
||||
|
||||
self.removePidFile()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Stop the daemon
|
||||
"""
|
||||
# Get the pid from the pidfile
|
||||
try:
|
||||
pf = open(self.pidfile, 'r')
|
||||
pid = int(pf.read().strip())
|
||||
pf.close()
|
||||
except IOError:
|
||||
message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile)
|
||||
logger.error(message)
|
||||
# sys.stderr.write(message)
|
||||
return # not an error in a restart
|
||||
|
||||
# Try killing the daemon process
|
||||
try:
|
||||
cnt = 10
|
||||
while cnt:
|
||||
cnt -= 1
|
||||
os.kill(pid, SIGTERM)
|
||||
time.sleep(1)
|
||||
|
||||
if not cnt:
|
||||
os.kill(pid, SIGKILL)
|
||||
except OSError as err:
|
||||
if err.errno == 3: # No such process
|
||||
if os.path.exists(self.pidfile):
|
||||
os.remove(self.pidfile)
|
||||
else:
|
||||
sys.stderr.write('Error: {}'.format(err))
|
||||
sys.exit(1)
|
||||
|
||||
def restart(self) -> None:
|
||||
"""
|
||||
Restart the daemon
|
||||
"""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
# Overridables
|
||||
def run(self) -> None:
|
||||
"""
|
||||
override this to provide your own daemon
|
||||
"""
|
73
actor/src/udsactor/linux/log.py
Normal file
73
actor/src/udsactor/linux/log.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import os
|
||||
import tempfile
|
||||
import logging
|
||||
import typing
|
||||
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
def __init__(self) -> None:
|
||||
# tempdir is different for "user application" and "service"
|
||||
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp
|
||||
# Try to open logger at /var/log path
|
||||
# If it fails (access denied normally), will try to open one at user's home folder, and if
|
||||
# agaim it fails, open it at the tmpPath
|
||||
for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()):
|
||||
try:
|
||||
fname = os.path.join(logDir, 'udsactor.log')
|
||||
logging.basicConfig(
|
||||
filename=fname,
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.DEBUG
|
||||
)
|
||||
self.logger = logging.getLogger('udsactor')
|
||||
os.chmod(fname, 0o0600)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Logger can't be set
|
||||
self.logger = None
|
||||
|
||||
def log(self, level: int, message: str) -> None:
|
||||
# Debug messages are logged to a file
|
||||
# our loglevels are 0 (other), 10000 (debug), ....
|
||||
# logging levels are 10 (debug), 20 (info)
|
||||
# OTHER = logging.NOTSET
|
||||
if self.logger:
|
||||
self.logger.log(int(level / 1000), message)
|
196
actor/src/udsactor/linux/operations.py
Normal file
196
actor/src/udsactor/linux/operations.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import configparser
|
||||
import platform
|
||||
import socket
|
||||
import fcntl # Only available on Linux. Expect complains if edited from windows
|
||||
import os
|
||||
import subprocess
|
||||
import struct
|
||||
import array
|
||||
import typing
|
||||
|
||||
from .. import types
|
||||
|
||||
from .renamer import rename
|
||||
from . import xss
|
||||
|
||||
|
||||
def _getMacAddr(ifname: str) -> typing.Optional[str]:
|
||||
'''
|
||||
Returns the mac address of an interface
|
||||
Mac is returned as unicode utf-8 encoded
|
||||
'''
|
||||
ifnameBytes = ifname.encode('utf-8')
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifnameBytes[:15])))
|
||||
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _getIpAddr(ifname: str) -> typing.Optional[str]:
|
||||
'''
|
||||
Returns the ip address of an interface
|
||||
Ip is returned as unicode utf-8 encoded
|
||||
'''
|
||||
ifnameBytes = ifname.encode('utf-8')
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return str(socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8915, # SIOCGIFADDR
|
||||
struct.pack(str('256s'), ifnameBytes[:15])
|
||||
)[20:24]))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _getInterfaces() -> typing.List[str]:
|
||||
'''
|
||||
Returns a list of interfaces names coded in utf-8
|
||||
'''
|
||||
max_possible = 128 # arbitrary. raise if needed.
|
||||
space = max_possible * 16
|
||||
if platform.architecture()[0] == '32bit':
|
||||
offset, length = 32, 32
|
||||
elif platform.architecture()[0] == '64bit':
|
||||
offset, length = 16, 40
|
||||
else:
|
||||
raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0]))
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
names = array.array(str('B'), b'\0' * space)
|
||||
outbytes = struct.unpack(str('iL'), fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8912, # SIOCGIFCONF
|
||||
struct.pack(str('iL'), space, names.buffer_info()[0])
|
||||
))[0]
|
||||
namestr = names.tostring()
|
||||
# return namestr, outbytes
|
||||
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
|
||||
|
||||
|
||||
def _getIpAndMac(ifname: str) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]:
|
||||
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
|
||||
return (ip, mac)
|
||||
|
||||
def checkPermissions() -> bool:
|
||||
return os.getuid() == 0 # getuid only available on linux. Expect "complaioins" if edited from Windows
|
||||
|
||||
def getComputerName() -> str:
|
||||
'''
|
||||
Returns computer name, with no domain
|
||||
'''
|
||||
return socket.gethostname().split('.')[0]
|
||||
|
||||
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
||||
for ifname in _getInterfaces():
|
||||
ip, mac = _getIpAndMac(ifname)
|
||||
if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs
|
||||
yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip)
|
||||
|
||||
def getDomainName() -> str:
|
||||
return ''
|
||||
|
||||
def getLinuxOs() -> str:
|
||||
try:
|
||||
with open('/etc/os-release', 'r') as f:
|
||||
data = f.read()
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read_string('[os]\n' + data)
|
||||
return cfg['os'].get('id', 'unknown').replace('"', '')
|
||||
except Exception:
|
||||
return 'unknown'
|
||||
|
||||
def reboot(flags: int = 0):
|
||||
'''
|
||||
Simple reboot using os command
|
||||
'''
|
||||
subprocess.call(['/sbin/shutdown', 'now', '-r'])
|
||||
|
||||
|
||||
def loggoff() -> None:
|
||||
'''
|
||||
Right now restarts the machine...
|
||||
'''
|
||||
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']])
|
||||
# subprocess.call(['/sbin/shutdown', 'now', '-r'])
|
||||
# subprocess.call(['/usr/bin/systemctl', 'reboot', '-i'])
|
||||
|
||||
|
||||
def renameComputer(newName: str) -> bool:
|
||||
'''
|
||||
Changes the computer name
|
||||
Returns True if reboot needed
|
||||
'''
|
||||
rename(newName)
|
||||
return True # Always reboot right now. Not much slower but much more better
|
||||
|
||||
|
||||
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):
|
||||
pass
|
||||
|
||||
|
||||
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
||||
'''
|
||||
Simple password change for user using command line
|
||||
'''
|
||||
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword))
|
||||
|
||||
|
||||
def initIdleDuration(atLeastSeconds: int) -> None:
|
||||
xss.initIdleDuration(atLeastSeconds)
|
||||
|
||||
|
||||
def getIdleDuration() -> float:
|
||||
return xss.getIdleDuration()
|
||||
|
||||
|
||||
def getCurrentUser() -> str:
|
||||
'''
|
||||
Returns current logged in user
|
||||
'''
|
||||
return os.environ['USER']
|
||||
|
||||
def getSessionType() -> str:
|
||||
'''
|
||||
Known values:
|
||||
* Unknown -> No XDG_SESSION_TYPE environment variable
|
||||
* xrdp --> xrdp session
|
||||
* other types
|
||||
'''
|
||||
return 'xrdp' if 'XRDP_SESSION' in os.environ else os.environ.get('XDG_SESSION_TYPE', 'unknown')
|
||||
|
||||
def forceTimeSync() -> None:
|
||||
return
|
35
actor/src/udsactor/linux/renamer/__init__.py
Normal file
35
actor/src/udsactor/linux/renamer/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from .common import rename
|
||||
|
||||
# Import packages
|
||||
from . import debian, opensuse, redhat, alt
|
74
actor/src/udsactor/linux/renamer/alt.py
Normal file
74
actor/src/udsactor/linux/renamer/alt.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Alexey Shabalin, shaba at altlinux dot org
|
||||
'''
|
||||
import os
|
||||
|
||||
from .common import renamers
|
||||
from ...log import logger
|
||||
|
||||
|
||||
def rename(newName: str) -> bool:
|
||||
'''
|
||||
ALT, ALTLinux, BaseALT Renamer
|
||||
Expects new host name on newName
|
||||
Host does not needs to be rebooted after renaming
|
||||
'''
|
||||
logger.debug('using ALT renamer')
|
||||
|
||||
with open('/etc/hostname', 'w') as hostname:
|
||||
hostname.write(newName)
|
||||
|
||||
# Force system new name
|
||||
os.system('/bin/hostname {}'.format(newName))
|
||||
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||
|
||||
# add name to "hosts"
|
||||
with open('/etc/hosts', 'r') as hosts:
|
||||
lines = hosts.readlines()
|
||||
with open('/etc/hosts', 'w') as hosts:
|
||||
hosts.write("127.0.1.1\t{}\n".format(newName))
|
||||
for l in lines:
|
||||
if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
|
||||
hosts.write(l)
|
||||
|
||||
with open('/etc/sysconfig/network', 'r') as net:
|
||||
lines = net.readlines()
|
||||
with open('/etc/sysconfig/network', 'w') as net:
|
||||
net.write('HOSTNAME={}\n'.format(newName))
|
||||
for l in lines:
|
||||
if l[:8] != 'HOSTNAME':
|
||||
net.write(l)
|
||||
|
||||
return True
|
||||
|
||||
# All names in lower case
|
||||
renamers['altlinux'] = rename
|
||||
renamers['alt'] = rename
|
||||
renamers['basealt'] = rename
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -26,33 +25,28 @@
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import random
|
||||
import os
|
||||
import tempfile
|
||||
import string
|
||||
import webbrowser
|
||||
import sys
|
||||
import pkgutil
|
||||
import typing
|
||||
|
||||
TEMPLATE = '''<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{title}</h1>
|
||||
<p>{message}<P>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
from .. import operations
|
||||
from ...log import logger
|
||||
|
||||
def _htmlFilename():
|
||||
return os.path.join(tempfile.gettempdir(), ''.join([random.choice(string.ascii_lowercase) for i in range(22)]) + '.html')
|
||||
renamers: typing.MutableMapping[str, typing.Callable[[str], bool]] = {}
|
||||
|
||||
def message(title, message):
|
||||
filename = _htmlFilename()
|
||||
with open(filename, 'w') as f:
|
||||
f.write(TEMPLATE.format(title=title, message=message))
|
||||
# Renamers now are for IPv4 only addresses
|
||||
def rename(newName: str) -> bool:
|
||||
distribution = operations.getLinuxOs().lower().strip()
|
||||
if distribution in renamers:
|
||||
logger.info('Renaming to {}'.format(newName))
|
||||
return renamers[distribution](newName)
|
||||
|
||||
# Try Debian renamer, simplest one
|
||||
logger.info('Renamer for platform "{0}" not found, trying debian renamer'.format(distribution))
|
||||
return renamers['debian'](newName)
|
||||
|
||||
webbrowser.open('file://' + filename, new=0, autoraise=False)
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -26,24 +25,41 @@
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import time
|
||||
from uds.log import logger
|
||||
import os
|
||||
|
||||
counter = 0
|
||||
from .common import renamers
|
||||
from ...log import logger
|
||||
|
||||
def message(title, message):
|
||||
sys.stderr.write("** {} **\n {}\n".format(title, message))
|
||||
|
||||
def question(title, message):
|
||||
global counter
|
||||
if counter > 100: # 5 minutes
|
||||
return False
|
||||
counter += 1
|
||||
sys.stderr.write("** {} **\n{}\nReturning YES in 3 seconds. (counter is {})\n".format(title, message, counter))
|
||||
time.sleep(3) # Wait 3 seconds before returning
|
||||
def rename(newName: str) -> bool:
|
||||
'''
|
||||
Debian renamer
|
||||
Expects new host name on newName
|
||||
Host does not needs to be rebooted after renaming
|
||||
'''
|
||||
with open('/etc/hostname', 'w') as hostname:
|
||||
hostname.write(newName)
|
||||
|
||||
# Force system new name
|
||||
os.system('/bin/hostname {}'.format(newName))
|
||||
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||
|
||||
# add name to "hosts"
|
||||
with open('/etc/hosts', 'r') as hosts:
|
||||
lines = hosts.readlines()
|
||||
with open('/etc/hosts', 'w') as hosts:
|
||||
hosts.write("127.0.1.1\t%s\n" % newName)
|
||||
for l in lines:
|
||||
if l[:9] == '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
|
||||
continue
|
||||
hosts.write(l)
|
||||
|
||||
return True
|
||||
|
||||
# All names in lower case
|
||||
renamers['debian'] = rename
|
||||
renamers['ubuntu'] = rename
|
66
actor/src/udsactor/linux/renamer/opensuse.py
Normal file
66
actor/src/udsactor/linux/renamer/opensuse.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import os
|
||||
|
||||
from .common import renamers
|
||||
from ...log import logger
|
||||
|
||||
|
||||
def rename(newName: str) -> bool:
|
||||
'''
|
||||
RH, Centos, Fedora Renamer
|
||||
Expects new host name on newName
|
||||
Host does not needs to be rebooted after renaming
|
||||
'''
|
||||
logger.debug('using SUSE renamer')
|
||||
|
||||
with open('/etc/hostname', 'w') as hostname:
|
||||
hostname.write(newName)
|
||||
|
||||
# Force system new name
|
||||
os.system('/bin/hostname {}'.format(newName))
|
||||
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||
|
||||
# add name to "hosts"
|
||||
with open('/etc/hosts', 'r') as hosts:
|
||||
lines = hosts.readlines()
|
||||
with open('/etc/hosts', 'w') as hosts:
|
||||
hosts.write("127.0.1.1\t{}\n".format(newName))
|
||||
for l in lines:
|
||||
if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
|
||||
hosts.write(l)
|
||||
|
||||
return True
|
||||
|
||||
# All names in lower case
|
||||
renamers['opensuse'] = rename
|
||||
renamers['suse'] = rename
|
||||
renamers['opensuse-leap'] = rename
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -28,36 +28,47 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
|
||||
__updated__ = '2015-02-28'
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
from django.shortcuts import redirect
|
||||
from django.template import RequestContext
|
||||
|
||||
from uds.core.auths.auth import webLoginRequired
|
||||
from uds.core.managers.UserPrefsManager import UserPrefsManager
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.ui import theme
|
||||
from .common import renamers
|
||||
from ...log import logger
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@webLoginRequired(admin=False)
|
||||
def prefs(request):
|
||||
def rename(newName: str) -> bool:
|
||||
'''
|
||||
Preferences form
|
||||
:param request:
|
||||
RH, Centos, Fedora Renamer
|
||||
Expects new host name on newName
|
||||
Host does not needs to be rebooted after renaming
|
||||
'''
|
||||
# Redirects to index if no preferences change allowed
|
||||
if GlobalConfig.PREFERENCES_ALLOWED.getBool(True) is False:
|
||||
return redirect('uds.web.views.index')
|
||||
if request.method == 'POST':
|
||||
UserPrefsManager.manager().processRequestForUserPreferences(request.user, request.POST)
|
||||
return redirect('uds.web.views.index')
|
||||
prefs_form = UserPrefsManager().manager().getHtmlForUserPreferences(request.user)
|
||||
return render_to_response(theme.template('prefs.html'), {'prefs_form': prefs_form}, context_instance=RequestContext(request))
|
||||
logger.debug('using RH renamer')
|
||||
|
||||
with open('/etc/hostname', 'w') as hostname:
|
||||
hostname.write(newName)
|
||||
|
||||
# Force system new name
|
||||
os.system('/bin/hostname {}'.format(newName))
|
||||
os.system('/usr/bin/hostnamectl set-hostname {}'.format(newName))
|
||||
|
||||
# add name to "hosts"
|
||||
with open('/etc/hosts', 'r') as hosts:
|
||||
lines = hosts.readlines()
|
||||
with open('/etc/hosts', 'w') as hosts:
|
||||
hosts.write("127.0.1.1\t{}\n".format(newName))
|
||||
for l in lines:
|
||||
if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists
|
||||
hosts.write(l)
|
||||
|
||||
with open('/etc/sysconfig/network', 'r') as net:
|
||||
lines = net.readlines()
|
||||
with open('/etc/sysconfig/network', 'w') as net:
|
||||
net.write('HOSTNAME={}\n'.format(newName))
|
||||
for l in lines:
|
||||
if l[:8] != 'HOSTNAME':
|
||||
net.write(l)
|
||||
|
||||
return True
|
||||
|
||||
# All names in lower case
|
||||
renamers['centos linux'] = rename
|
||||
renamers['centos'] = rename
|
||||
renamers['fedora'] = rename
|
76
actor/src/udsactor/linux/runner.py
Normal file
76
actor/src/udsactor/linux/runner.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2020 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import sys
|
||||
|
||||
from .. import rest
|
||||
from .. import platform
|
||||
from ..log import logger
|
||||
from .service import UDSActorSvc
|
||||
|
||||
def usage():
|
||||
sys.stderr.write('usage: udsactor start|stop|restart|login "username"|logout "username"\n')
|
||||
sys.exit(2)
|
||||
|
||||
def run() -> None:
|
||||
logger.setLevel(20000)
|
||||
|
||||
if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'):
|
||||
logger.debug('Running client udsactor')
|
||||
try:
|
||||
client: rest.UDSClientApi = rest.UDSClientApi()
|
||||
if sys.argv[1] == 'login':
|
||||
r = client.login(sys.argv[2], platform.operations.getSessionType())
|
||||
print('{},{},{},{}\n'.format(r.ip, r.hostname, r.max_idle, r.dead_line or ''))
|
||||
elif sys.argv[1] == 'logout':
|
||||
client.logout(sys.argv[2])
|
||||
except Exception as e:
|
||||
logger.exception()
|
||||
logger.error('Got exception while processing command: %s', e)
|
||||
sys.exit(0)
|
||||
elif len(sys.argv) != 2:
|
||||
usage()
|
||||
|
||||
daemonSvr = UDSActorSvc()
|
||||
if len(sys.argv) == 2:
|
||||
# Daemon mode...
|
||||
if sys.argv[1] == 'start':
|
||||
daemonSvr.start()
|
||||
elif sys.argv[1] == 'stop':
|
||||
daemonSvr.stop()
|
||||
elif sys.argv[1] == 'restart':
|
||||
daemonSvr.restart()
|
||||
elif sys.argv[1] == 'start-foreground':
|
||||
daemonSvr.run() # Execute in foreground
|
||||
else:
|
||||
usage()
|
||||
sys.exit(0)
|
||||
else:
|
||||
usage()
|
106
actor/src/udsactor/linux/service.py
Normal file
106
actor/src/udsactor/linux/service.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import signal
|
||||
|
||||
from . import daemon
|
||||
|
||||
from ..log import logger
|
||||
from ..service import CommonService
|
||||
|
||||
try:
|
||||
from prctl import set_proctitle # @UnresolvedImport
|
||||
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
|
||||
def set_proctitle(_):
|
||||
pass
|
||||
|
||||
class UDSActorSvc(daemon.Daemon, CommonService):
|
||||
def __init__(self) -> None:
|
||||
daemon.Daemon.__init__(self, '/run/udsactor.pid')
|
||||
CommonService.__init__(self)
|
||||
|
||||
# Captures signals so we can stop gracefully
|
||||
signal.signal(signal.SIGINT, self.markForExit)
|
||||
signal.signal(signal.SIGTERM, self.markForExit)
|
||||
|
||||
def markForExit(self, signum, frame) -> None: # pylint: disable=unused-argument
|
||||
self._isAlive = False
|
||||
|
||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
||||
self,
|
||||
name: str,
|
||||
domain: str,
|
||||
ou: str,
|
||||
account: str,
|
||||
password: str
|
||||
) -> None:
|
||||
logger.info('Join domain is not supported on linux platforms right now. Just renaming.')
|
||||
self.rename(name)
|
||||
|
||||
def run(self) -> None:
|
||||
logger.debug('Running Daemon: {}'.format(self._isAlive))
|
||||
set_proctitle('UDSActorDaemon')
|
||||
|
||||
# Linux daemon will continue running unless something is requested to
|
||||
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
|
||||
if self.isManaged():
|
||||
if not self.initialize():
|
||||
self.finish()
|
||||
return # Stop daemon if initializes told to do so
|
||||
|
||||
# logger.debug('Initialized, setting ready')
|
||||
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
|
||||
self.setReady()
|
||||
else:
|
||||
if not self.initializeUnmanaged():
|
||||
self.finish()
|
||||
return
|
||||
|
||||
# Start listening for petitions
|
||||
self.startHttpServer()
|
||||
|
||||
# *********************
|
||||
# * Main Service loop *
|
||||
# *********************
|
||||
# Counter used to check ip changes only once every 10 seconds, for
|
||||
# example
|
||||
counter = 0
|
||||
while self._isAlive:
|
||||
counter += 1
|
||||
try:
|
||||
if counter % 5 == 0:
|
||||
self.loop()
|
||||
except Exception as e:
|
||||
logger.error('Got exception on main loop: %s', e)
|
||||
# In milliseconds, will break
|
||||
self.doWait(1000)
|
||||
|
||||
self.finish()
|
105
actor/src/udsactor/linux/store.py
Normal file
105
actor/src/udsactor/linux/store.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import os
|
||||
import configparser
|
||||
import base64
|
||||
import pickle
|
||||
|
||||
from .. import types
|
||||
|
||||
CONFIGFILE = '/etc/udsactor/udsactor.cfg'
|
||||
|
||||
def readConfig() -> types.ActorConfigurationType:
|
||||
try:
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(CONFIGFILE)
|
||||
uds: configparser.SectionProxy = cfg['uds']
|
||||
# Extract data:
|
||||
base64Config = uds.get('config', None)
|
||||
config = pickle.loads(base64.b64decode(base64Config.encode())) if base64Config else None
|
||||
|
||||
base64Data = uds.get('data', None)
|
||||
data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None
|
||||
|
||||
return types.ActorConfigurationType(
|
||||
actorType=uds.get('type', types.MANAGED),
|
||||
host=uds.get('host', ''),
|
||||
validateCertificate=uds.getboolean('validate', fallback=False),
|
||||
master_token=uds.get('master_token', None),
|
||||
own_token=uds.get('own_token', None),
|
||||
pre_command=uds.get('pre_command', None),
|
||||
runonce_command=uds.get('runonce_command', None),
|
||||
post_command=uds.get('post_command', None),
|
||||
log_level=int(uds.get('log_level', '2')),
|
||||
config=config,
|
||||
data=data
|
||||
)
|
||||
except Exception:
|
||||
return types.ActorConfigurationType('', False)
|
||||
|
||||
def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.add_section('uds')
|
||||
uds: configparser.SectionProxy = cfg['uds']
|
||||
uds['host'] = config.host
|
||||
uds['validate'] = 'yes' if config.validateCertificate else 'no'
|
||||
def writeIfValue(val, name):
|
||||
if val:
|
||||
uds[name] = val
|
||||
writeIfValue(config.actorType, 'type')
|
||||
writeIfValue(config.master_token, 'master_token')
|
||||
writeIfValue(config.own_token, 'own_token')
|
||||
writeIfValue(config.pre_command, 'pre_command')
|
||||
writeIfValue(config.post_command, 'post_command')
|
||||
writeIfValue(config.runonce_command, 'runonce_command')
|
||||
uds['log_level'] = str(config.log_level)
|
||||
if config.config: # Special case, encoded & dumped
|
||||
uds['config'] = base64.b64encode(pickle.dumps(config.config)).decode()
|
||||
|
||||
if config.data: # Special case, encoded & dumped
|
||||
uds['data'] = base64.b64encode(pickle.dumps(config.data)).decode()
|
||||
|
||||
# Ensures exists destination folder
|
||||
dirname = os.path.dirname(CONFIGFILE)
|
||||
if not os.path.exists(dirname):
|
||||
os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-))
|
||||
|
||||
with open(CONFIGFILE, 'w') as f:
|
||||
cfg.write(f)
|
||||
|
||||
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
|
||||
|
||||
def useOldJoinSystem() -> bool:
|
||||
return False
|
||||
|
||||
def invokeScriptOnLogin() -> str:
|
||||
return ''
|
128
actor/src/udsactor/linux/xss.py
Normal file
128
actor/src/udsactor/linux/xss.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import subprocess
|
||||
|
||||
xlib = None
|
||||
xss = None
|
||||
display = None
|
||||
xssInfo = None
|
||||
initialized = False
|
||||
|
||||
class XScreenSaverInfo(ctypes.Structure): # pylint: disable=too-few-public-methods
|
||||
_fields_ = [('window', ctypes.c_long),
|
||||
('state', ctypes.c_int),
|
||||
('kind', ctypes.c_int),
|
||||
('til_or_since', ctypes.c_ulong),
|
||||
('idle', ctypes.c_ulong),
|
||||
('eventMask', ctypes.c_ulong)]
|
||||
|
||||
class c_ptr(ctypes.c_void_p):
|
||||
pass
|
||||
|
||||
def _ensureInitialized():
|
||||
global xlib, xss, xssInfo, display, initialized # pylint: disable=global-statement
|
||||
|
||||
if initialized:
|
||||
return
|
||||
|
||||
initialized = True
|
||||
|
||||
try:
|
||||
xlibPath = ctypes.util.find_library('X11')
|
||||
xssPath = ctypes.util.find_library('Xss')
|
||||
xlib = xss = None
|
||||
if not xlibPath or not xssPath:
|
||||
raise Exception('Library Not found!!')
|
||||
|
||||
xlib = ctypes.cdll.LoadLibrary(xlibPath)
|
||||
xss = ctypes.cdll.LoadLibrary(xssPath)
|
||||
|
||||
# Fix result type to XScreenSaverInfo Structure
|
||||
xss.XScreenSaverQueryExtension.restype = ctypes.c_int
|
||||
xss.XScreenSaverQueryExtension.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ctypes.c_int),
|
||||
ctypes.POINTER(ctypes.c_int)
|
||||
]
|
||||
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure
|
||||
xss.XScreenSaverQueryInfo.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(XScreenSaverInfo)
|
||||
]
|
||||
xlib.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||
xlib.XOpenDisplay.restype = c_ptr
|
||||
|
||||
display = xlib.XOpenDisplay(None)
|
||||
|
||||
if not display.value:
|
||||
raise Exception('Display not found!') # Invalid display, not accesible
|
||||
|
||||
xssInfo = xss.XScreenSaverAllocInfo()
|
||||
|
||||
# Ensures screen saver extension is available
|
||||
event_base = ctypes.c_int()
|
||||
error_base = ctypes.c_int()
|
||||
|
||||
available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base))
|
||||
|
||||
if available != 1:
|
||||
raise Exception('ScreenSaver not available')
|
||||
|
||||
except Exception: # Libraries not accesible, not found or whatever..
|
||||
xlib = xss = display = xssInfo = None
|
||||
|
||||
|
||||
def initIdleDuration(atLeastSeconds: int) -> None:
|
||||
_ensureInitialized()
|
||||
if atLeastSeconds:
|
||||
subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)])
|
||||
# And now reset it
|
||||
subprocess.call(['/usr/bin/xset', 's', 'reset'])
|
||||
|
||||
|
||||
def getIdleDuration() -> float:
|
||||
'''
|
||||
Returns idle duration, in seconds
|
||||
'''
|
||||
if not initialized or not xlib or not xss or not xssInfo:
|
||||
return 0 # Libraries not available
|
||||
|
||||
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
|
||||
|
||||
# States: 0 = off, 1 = On, 2 = Cycle, 3 = Disabled, ...?
|
||||
if xssInfo.contents.state == 1: # state = 1 means "active", so idle is not a valid state
|
||||
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
|
||||
|
||||
return xssInfo.contents.idle / 1000.0
|
117
actor/src/udsactor/log.py
Normal file
117
actor/src/udsactor/log.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import traceback
|
||||
import sys
|
||||
import typing
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from .windows.log import LocalLogger
|
||||
else:
|
||||
from .linux.log import LocalLogger
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import rest
|
||||
|
||||
# Valid logging levels, from UDS Broker (uds.core.utils.log)
|
||||
from .loglevel import OTHER, DEBUG, INFO, WARN, ERROR, FATAL
|
||||
|
||||
class Logger:
|
||||
remoteLogger: typing.Optional['rest.UDSServerApi']
|
||||
own_token: str
|
||||
logLevel: int
|
||||
localLogger: LocalLogger
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.logLevel = INFO
|
||||
self.localLogger = LocalLogger()
|
||||
self.remoteLogger = None
|
||||
self.own_token = ''
|
||||
|
||||
def setLevel(self, level: typing.Union[str, int]) -> None:
|
||||
'''
|
||||
Sets log level filter (minimum level required for a log message to be processed)
|
||||
:param level: Any message with a level below this will be filtered out
|
||||
'''
|
||||
self.logLevel = int(level) # Ensures level is an integer or fails
|
||||
|
||||
def setRemoteLogger(self, remoteLogger: 'rest.UDSServerApi', own_token: str) -> None:
|
||||
self.remoteLogger = remoteLogger
|
||||
self.own_token = own_token
|
||||
|
||||
def enableServiceLogger(self):
|
||||
if self.localLogger.windows:
|
||||
self.localLogger.serviceLogger = True
|
||||
|
||||
def log(self, level: typing.Union[str, int], message: str, *args) -> None:
|
||||
level = int(level)
|
||||
if level < self.logLevel: # Skip not wanted messages
|
||||
return
|
||||
|
||||
msg = message % args
|
||||
# If remote logger is available, notify message to it (except DEBUG messages OFC)
|
||||
try:
|
||||
if self.remoteLogger and level >= DEBUG:
|
||||
self.remoteLogger.log(self.own_token, level, msg)
|
||||
except Exception as e:
|
||||
self.localLogger.log(DEBUG, 'Log to broker: {}'.format(e))
|
||||
|
||||
self.localLogger.log(level, msg)
|
||||
|
||||
def debug(self, message: str, *args) -> None:
|
||||
self.log(DEBUG, message, *args)
|
||||
|
||||
def warn(self, message: str, *args) -> None:
|
||||
self.log(WARN, message, *args)
|
||||
|
||||
def info(self, message: str, *args) -> None:
|
||||
self.log(INFO, message, *args)
|
||||
|
||||
def error(self, message: str, *args) -> None:
|
||||
self.log(ERROR, message, *args)
|
||||
|
||||
def fatal(self, message: str, *args) -> None:
|
||||
self.log(FATAL, message, *args)
|
||||
|
||||
def exception(self) -> None:
|
||||
try:
|
||||
tb = traceback.format_exc()
|
||||
except Exception:
|
||||
tb = '(could not get traceback!)'
|
||||
|
||||
self.log(DEBUG, tb)
|
||||
|
||||
def flush(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
logger = Logger()
|
32
actor/src/udsactor/loglevel.py
Normal file
32
actor/src/udsactor/loglevel.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2020 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * x for x in range(6))
|
37
actor/src/udsactor/platform.py
Normal file
37
actor/src/udsactor/platform.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import sys
|
||||
|
||||
name = sys.platform
|
||||
if sys.platform == 'win32':
|
||||
from .windows import operations, store # pylint: disable=unused-import
|
||||
else:
|
||||
from .linux import operations, store # pylint: disable=unused-import
|
401
actor/src/udsactor/rest.py
Normal file
401
actor/src/udsactor/rest.py
Normal file
@@ -0,0 +1,401 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import warnings
|
||||
import json
|
||||
import logging
|
||||
import typing
|
||||
|
||||
import requests
|
||||
|
||||
from . import types
|
||||
from .info import VERSION
|
||||
|
||||
# Default public listen port
|
||||
LISTEN_PORT = 43910
|
||||
|
||||
# Default timeout
|
||||
TIMEOUT = 5 # 5 seconds is more than enought
|
||||
|
||||
# Constants
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
class RESTError(Exception):
|
||||
ERRCODE = 0
|
||||
|
||||
class RESTConnectionError(RESTError):
|
||||
ERRCODE = -1
|
||||
|
||||
# Errors ""raised"" from broker
|
||||
class RESTInvalidKeyError(RESTError):
|
||||
ERRCODE = 1
|
||||
|
||||
class RESTUnmanagedHostError(RESTError):
|
||||
ERRCODE = 2
|
||||
|
||||
class RESTUserServiceNotFoundError(RESTError):
|
||||
ERRCODE = 3
|
||||
|
||||
class RESTOsManagerError(RESTError):
|
||||
ERRCODE = 4
|
||||
|
||||
# For avoid proxy on localhost connections
|
||||
NO_PROXY = {
|
||||
'http': None,
|
||||
'https': None,
|
||||
}
|
||||
#
|
||||
# Basic UDS Api
|
||||
#
|
||||
class UDSApi: # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base for remote api accesses
|
||||
"""
|
||||
_host: str
|
||||
_validateCert: bool
|
||||
_url: str
|
||||
|
||||
def __init__(self, host: str, validateCert: bool) -> None:
|
||||
self._host = host
|
||||
self._validateCert = validateCert
|
||||
self._url = "https://{}/uds/rest/".format(self._host)
|
||||
# Disable logging requests messages except for errors, ...
|
||||
logging.getLogger("requests").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
try:
|
||||
warnings.simplefilter("ignore") # Disables all warnings
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@property
|
||||
def _headers(self) -> typing.MutableMapping[str, str]:
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'UDS Actor v{}'.format(VERSION)
|
||||
}
|
||||
|
||||
def _apiURL(self, method: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def _doPost(
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any],
|
||||
headers: typing.Optional[typing.MutableMapping[str, str]] = None,
|
||||
disableProxy: bool = False
|
||||
) -> typing.Any:
|
||||
headers = headers or self._headers
|
||||
try:
|
||||
result = requests.post(
|
||||
self._apiURL(method),
|
||||
data=json.dumps(payLoad),
|
||||
headers=headers,
|
||||
verify=self._validateCert,
|
||||
timeout=TIMEOUT,
|
||||
proxies=NO_PROXY if disableProxy else None # if not proxies wanted, enforce it
|
||||
)
|
||||
|
||||
if result.ok:
|
||||
j = result.json()
|
||||
if not j.get('error', None):
|
||||
return j['result']
|
||||
except requests.ConnectionError as e:
|
||||
raise RESTConnectionError(str(e))
|
||||
except Exception as e:
|
||||
raise RESTError(str(e))
|
||||
|
||||
try:
|
||||
data = result.json()
|
||||
except Exception:
|
||||
data = result.content.decode()
|
||||
|
||||
raise RESTError(data)
|
||||
|
||||
#
|
||||
# UDS Broker API access
|
||||
#
|
||||
class UDSServerApi(UDSApi):
|
||||
def _apiURL(self, method: str) -> str:
|
||||
return self._url + 'actor/v3/' + method
|
||||
|
||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||
try:
|
||||
result = requests.get(self._url + 'auth/auths', headers=self._headers, verify=self._validateCert, timeout=4)
|
||||
if result.ok:
|
||||
for v in sorted(result.json(), key=lambda x: x['priority']):
|
||||
yield types.AuthenticatorType(
|
||||
authId=v['authId'],
|
||||
authSmallName=v['authSmallName'],
|
||||
auth=v['auth'],
|
||||
type=v['type'],
|
||||
priority=v['priority'],
|
||||
isCustom=v['isCustom']
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def register( # pylint: disable=too-many-arguments, too-many-locals
|
||||
self,
|
||||
auth: str,
|
||||
username: str,
|
||||
password: str,
|
||||
hostname: str,
|
||||
ip: str,
|
||||
mac: str,
|
||||
preCommand: str,
|
||||
runOnceCommand: str,
|
||||
postCommand: str,
|
||||
logLevel: int
|
||||
) -> str:
|
||||
"""
|
||||
Raises an exception if could not register, or registers and returns the "authorization token"
|
||||
"""
|
||||
data = {
|
||||
'username': username + '@' + auth,
|
||||
'hostname': hostname,
|
||||
'ip': ip,
|
||||
'mac': mac,
|
||||
'pre_command': preCommand,
|
||||
'run_once_command': runOnceCommand,
|
||||
'post_command': postCommand,
|
||||
'log_level': logLevel
|
||||
}
|
||||
|
||||
# First, try to login to REST api
|
||||
try:
|
||||
# First, try to login
|
||||
authInfo = {'auth': auth, 'username': username, 'password': password}
|
||||
headers = self._headers
|
||||
result = requests.post(self._url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self._validateCert)
|
||||
if not result.ok or result.json()['result'] == 'error':
|
||||
raise Exception() # Invalid credentials
|
||||
|
||||
headers['X-Auth-Token'] = result.json()['token']
|
||||
|
||||
result = requests.post(self._apiURL('register'), data=json.dumps(data), headers=headers, verify=self._validateCert)
|
||||
if result.ok:
|
||||
return result.json()['result']
|
||||
except requests.ConnectionError as e:
|
||||
raise RESTConnectionError(e)
|
||||
except RESTError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise RESTError('Invalid credentials')
|
||||
|
||||
raise RESTError(result.content.decode())
|
||||
|
||||
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType], actor_type: typing.Optional[str]) -> types.InitializationResultType:
|
||||
# Generate id list from netork cards
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
'token': token,
|
||||
'version': VERSION,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]
|
||||
}
|
||||
r = self._doPost('initialize', payload)
|
||||
os = r['os']
|
||||
return types.InitializationResultType(
|
||||
own_token=r['own_token'],
|
||||
unique_id=r['unique_id'].lower() if r['unique_id'] else None,
|
||||
os=types.ActorOsConfigurationType(
|
||||
action=os['action'],
|
||||
name=os['name'],
|
||||
username=os.get('username'),
|
||||
password=os.get('password'),
|
||||
new_password=os.get('new_password'),
|
||||
ad=os.get('ad'),
|
||||
ou=os.get('ou')
|
||||
) if r['os'] else None
|
||||
)
|
||||
|
||||
def ready(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'secret': secret,
|
||||
'ip': ip,
|
||||
'port': port
|
||||
}
|
||||
result = self._doPost('ready', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
)
|
||||
|
||||
def notifyIpChange(self, own_token: str, secret: str, ip: str, port: int) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'secret': secret,
|
||||
'ip': ip,
|
||||
'port': port
|
||||
}
|
||||
result = self._doPost('ipchange', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
)
|
||||
|
||||
def notifyUnmanagedCallback(self, master_token: str, secret: str, interfaces: typing.Iterable[types.InterfaceInfoType], port: int) -> types.CertificateInfoType:
|
||||
payload = {
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': master_token,
|
||||
'secret': secret,
|
||||
'port': port
|
||||
}
|
||||
result = self._doPost('unmanaged', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
)
|
||||
|
||||
def login(
|
||||
self,
|
||||
actor_type: typing.Optional[str],
|
||||
token: str,
|
||||
username: str,
|
||||
sessionType: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
secret: typing.Optional[str]
|
||||
) -> types.LoginResultInfoType:
|
||||
if not token:
|
||||
return types.LoginResultInfoType(
|
||||
ip='0.0.0.0',
|
||||
hostname=UNKNOWN,
|
||||
dead_line=None,
|
||||
max_idle=None
|
||||
)
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': token,
|
||||
'username': username,
|
||||
'session_type': sessionType,
|
||||
'secret': secret or '',
|
||||
}
|
||||
result = self._doPost('login', payload)
|
||||
return types.LoginResultInfoType(
|
||||
ip=result['ip'],
|
||||
hostname=result['hostname'],
|
||||
dead_line=result['dead_line'],
|
||||
max_idle=result['max_idle']
|
||||
)
|
||||
|
||||
def logout(
|
||||
self,
|
||||
actor_type: typing.Optional[str],
|
||||
token: str,
|
||||
username: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
secret: typing.Optional[str]
|
||||
) -> None:
|
||||
if not token:
|
||||
return
|
||||
payload = {
|
||||
'type': actor_type or types.MANAGED,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': token,
|
||||
'username': username,
|
||||
'secret': secret or ''
|
||||
}
|
||||
self._doPost('logout', payload)
|
||||
|
||||
|
||||
def log(self, own_token: str, level: int, message: str) -> None:
|
||||
if not own_token:
|
||||
return
|
||||
payLoad = {
|
||||
'token': own_token,
|
||||
'level': level,
|
||||
'message': message
|
||||
}
|
||||
self._doPost('log', payLoad) # Ignores result...
|
||||
|
||||
def test(self, master_token: str, actorType: typing.Optional[str]) -> bool:
|
||||
payLoad = {
|
||||
'type': actorType or types.MANAGED,
|
||||
'token': master_token,
|
||||
}
|
||||
return self._doPost('test', payLoad) == 'ok'
|
||||
|
||||
|
||||
class UDSClientApi(UDSApi):
|
||||
def __init__(self) -> None:
|
||||
super().__init__('127.0.0.1:{}'.format(LISTEN_PORT), False)
|
||||
# Override base url
|
||||
self._url = "https://{}/ui/".format(self._host)
|
||||
|
||||
def _apiURL(self, method: str) -> str:
|
||||
return self._url + method
|
||||
def post(
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any]
|
||||
) -> typing.Any:
|
||||
return self._doPost(method=method, payLoad=payLoad, disableProxy=True)
|
||||
|
||||
def register(self, callbackUrl: str) -> None:
|
||||
payLoad = {
|
||||
'callback_url': callbackUrl
|
||||
}
|
||||
self.post('register', payLoad)
|
||||
|
||||
def unregister(self, callbackUrl: str) -> None:
|
||||
payLoad = {
|
||||
'callback_url': callbackUrl
|
||||
}
|
||||
self.post('unregister', payLoad)
|
||||
|
||||
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
payLoad = {
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN,
|
||||
}
|
||||
result = self.post('login', payLoad)
|
||||
return types.LoginResultInfoType(
|
||||
ip=result['ip'],
|
||||
hostname=result['hostname'],
|
||||
dead_line=result['dead_line'],
|
||||
max_idle=result['max_idle']
|
||||
)
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
payLoad = {
|
||||
'username': username
|
||||
}
|
||||
self.post('logout', payLoad)
|
||||
|
||||
def ping(self) -> bool:
|
||||
return self.post('ping', {}) == 'pong'
|
501
actor/src/udsactor/service.py
Normal file
501
actor/src/udsactor/service.py
Normal file
@@ -0,0 +1,501 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
import socket
|
||||
import time
|
||||
import secrets
|
||||
import subprocess
|
||||
import typing
|
||||
|
||||
from . import platform
|
||||
from . import rest
|
||||
from . import types
|
||||
|
||||
from .log import logger, DEBUG, INFO, ERROR, FATAL
|
||||
from .http import clients_pool, server, cert
|
||||
|
||||
# def setup() -> None:
|
||||
# cfg = platform.store.readConfig()
|
||||
|
||||
# if logger.logger.windows:
|
||||
# # Logs will also go to windows event log for services
|
||||
# logger.logger.serviceLogger = True
|
||||
|
||||
# if cfg.x:
|
||||
# logger.setLevel(cfg.get('logLevel', 20000))
|
||||
# else:
|
||||
# logger.setLevel(20000)
|
||||
|
||||
class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
_isAlive: bool = True
|
||||
_rebootRequested: bool = False
|
||||
_loggedIn: bool = False
|
||||
_initialized: bool = False
|
||||
|
||||
_cfg: types.ActorConfigurationType
|
||||
_api: rest.UDSServerApi
|
||||
_interfaces: typing.List[types.InterfaceInfoType]
|
||||
_secret: str
|
||||
_certificate: types.CertificateInfoType
|
||||
_clientsPool: clients_pool.UDSActorClientPool
|
||||
_http: typing.Optional[server.HTTPServerThread]
|
||||
|
||||
@staticmethod
|
||||
def execute(cmdLine: str, section: str) -> bool:
|
||||
try:
|
||||
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
|
||||
res = subprocess.check_call(cmdLine, shell=True)
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing: {} - {} - {}'.format(section, cmdLine, e))
|
||||
return False
|
||||
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
|
||||
return True
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cfg = platform.store.readConfig()
|
||||
self._interfaces = []
|
||||
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
|
||||
self._secret = secrets.token_urlsafe(33)
|
||||
self._clientsPool = clients_pool.UDSActorClientPool()
|
||||
self._certificate = cert.defaultCertificate # For being used on "unmanaged" hosts only
|
||||
self._http = None
|
||||
|
||||
# Initialzies loglevel and serviceLogger
|
||||
# 0 = DEBUG, 1 = INFO, 2 = ERROR, 3 = FATAL in combobox
|
||||
# BUT!!!:
|
||||
# 0 = OTHER, 10000 = DEBUG, 20000 = WARN, 30000 = INFO, 40000 = ERROR, 50000 = FATAL
|
||||
# So this comes:
|
||||
logger.setLevel([DEBUG, INFO, ERROR, FATAL][self._cfg.log_level])
|
||||
# If windows, enable service logger
|
||||
logger.enableServiceLogger()
|
||||
|
||||
socket.setdefaulttimeout(20)
|
||||
|
||||
def startHttpServer(self):
|
||||
# Starts the http thread
|
||||
if self._http:
|
||||
try:
|
||||
self._http.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._http = server.HTTPServerThread(self)
|
||||
self._http.start()
|
||||
|
||||
def isManaged(self) -> bool:
|
||||
return self._cfg.actorType != types.UNMANAGED # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
||||
|
||||
def serviceInterfaceInfo(self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None) -> typing.Optional[types.InterfaceInfoType]:
|
||||
"""
|
||||
returns the inteface with unique_id mac or first interface or None if no interfaces...
|
||||
"""
|
||||
interfaces = interfaces or self._interfaces # Emty interfaces is like "no ip change" because cannot be notified
|
||||
if self._cfg.config and interfaces:
|
||||
try:
|
||||
return next(x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id)
|
||||
except StopIteration:
|
||||
return interfaces[0]
|
||||
|
||||
return None
|
||||
|
||||
def reboot(self) -> None:
|
||||
# Reboot just after renaming
|
||||
logger.info('Rebooting...')
|
||||
self._rebootRequested = True
|
||||
|
||||
def setReady(self) -> None:
|
||||
if not self._isAlive or not self.isManaged():
|
||||
return
|
||||
# Unamanged actor types does not set ready never (has no osmanagers, no needing for this)
|
||||
|
||||
# First, if postconfig is available, execute it and disable it
|
||||
if self._cfg.post_command:
|
||||
self.execute(self._cfg.post_command, 'postConfig')
|
||||
self._cfg = self._cfg._replace(post_command=None)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
|
||||
if self._cfg.own_token and self._interfaces:
|
||||
srvInterface = self.serviceInterfaceInfo()
|
||||
if srvInterface:
|
||||
# Rery while RESTConnectionError (that is, cannot connect)
|
||||
counter = 60
|
||||
logged = False
|
||||
while self._isAlive:
|
||||
counter -= 1
|
||||
try:
|
||||
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
|
||||
except rest.RESTConnectionError as e:
|
||||
if not logged: # Only log connection problems ONCE
|
||||
logged = True
|
||||
logger.error('Error connecting with UDS Broker')
|
||||
self.doWait(5000)
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error('Unhandled exception while setting ready: %s', e)
|
||||
if counter > 0:
|
||||
self.doWait(10000) # A long wait on other error...
|
||||
continue
|
||||
platform.operations.reboot() # On too many errors, simply reboot
|
||||
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
|
||||
break
|
||||
else:
|
||||
logger.error('Could not locate IP address!!!. (Not registered with UDS)')
|
||||
|
||||
# Do not continue if not alive...
|
||||
if not self._isAlive:
|
||||
return
|
||||
|
||||
# Cleans sensible data
|
||||
if self._cfg.config:
|
||||
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
|
||||
logger.info('Service ready')
|
||||
|
||||
def configureMachine(self) -> bool:
|
||||
if not self._isAlive:
|
||||
return False
|
||||
|
||||
if not self.isManaged():
|
||||
return True
|
||||
|
||||
# First, if runonce is present, honor it and remove it from config
|
||||
# Return values is "True" for keep service (or daemon) running, False if Stop it.
|
||||
if self._cfg.runonce_command:
|
||||
runOnce = self._cfg.runonce_command
|
||||
self._cfg = self._cfg._replace(runonce_command=None)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
if self.execute(runOnce, "runOnce"):
|
||||
# If runonce is present, will not do anythin more
|
||||
# So we have to ensure that, when runonce command is finished, reboots the machine.
|
||||
# That is, the COMMAND itself has to restart the machine!
|
||||
return False # If the command fails, continue with the rest of the operations...
|
||||
|
||||
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
|
||||
counter = 10
|
||||
while self._isAlive:
|
||||
counter -= 1
|
||||
try:
|
||||
if self._cfg.config and self._cfg.config.os:
|
||||
osData = self._cfg.config.os
|
||||
if osData.action == 'rename':
|
||||
self.rename(osData.name, osData.username, osData.password, osData.new_password)
|
||||
elif osData.action == 'rename_ad':
|
||||
self.joinDomain(osData.name, osData.ad or '', osData.ou or '', osData.username or '', osData.password or '')
|
||||
|
||||
if self._rebootRequested:
|
||||
try:
|
||||
platform.operations.reboot()
|
||||
except Exception as e:
|
||||
logger.error('Exception on reboot: {}'.format(e))
|
||||
return False # Stops service if reboot was requested ofc
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error('Got exception operating machine: {}'.format(e))
|
||||
if counter > 0:
|
||||
self.doWait(5000)
|
||||
else:
|
||||
platform.operations.reboot()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def initializeUnmanaged(self) -> bool:
|
||||
# Notify UDS about my callback
|
||||
self.getInterfaces() # Ensure we have interfaces
|
||||
if self._cfg.master_token:
|
||||
try:
|
||||
self._certificate = self._api.notifyUnmanagedCallback(self._cfg.master_token, self._secret, self._interfaces, rest.LISTEN_PORT)
|
||||
except Exception as e:
|
||||
logger.error('Couuld not notify unmanaged callback: %s', e)
|
||||
|
||||
return True
|
||||
|
||||
def getInterfaces(self) -> None:
|
||||
if self._interfaces:
|
||||
return
|
||||
|
||||
while self._isAlive:
|
||||
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||
if self._interfaces:
|
||||
break
|
||||
self.doWait(5000)
|
||||
|
||||
def initialize(self) -> bool:
|
||||
if self._initialized or not self._cfg.host or not self._isAlive: # Not configured or not running
|
||||
return False
|
||||
|
||||
self._initialized = True
|
||||
|
||||
# Force time sync, just in case...
|
||||
if self.isManaged():
|
||||
platform.operations.forceTimeSync()
|
||||
|
||||
# Wait for Broker to be ready
|
||||
# Ensure we have intefaces...
|
||||
self.getInterfaces()
|
||||
|
||||
while self._isAlive:
|
||||
try:
|
||||
# If master token is present, initialize and get configuration data
|
||||
if self._cfg.master_token:
|
||||
initResult: types.InitializationResultType = self._api.initialize(self._cfg.master_token, self._interfaces, self._cfg.actorType)
|
||||
if not initResult.own_token: # Not managed
|
||||
logger.debug('This host is not managed by UDS Broker (ids: {})'.format(self._interfaces))
|
||||
return False
|
||||
|
||||
# Only removes master token for managed machines (will need it on next client execution)
|
||||
master_token = None if self.isManaged() else self._cfg.master_token
|
||||
self._cfg = self._cfg._replace(
|
||||
master_token=master_token,
|
||||
own_token=initResult.own_token,
|
||||
config=types.ActorDataConfigurationType(
|
||||
unique_id=initResult.unique_id,
|
||||
os=initResult.os
|
||||
)
|
||||
)
|
||||
|
||||
# On first successfull initialization request, master token will dissapear for managed hosts so it will be no more available (not needed anyway)
|
||||
if self.isManaged():
|
||||
platform.store.writeConfig(self._cfg)
|
||||
|
||||
# Setup logger now
|
||||
if self._cfg.own_token:
|
||||
logger.setRemoteLogger(self._api, self._cfg.own_token)
|
||||
|
||||
break # Initial configuration done..
|
||||
except rest.RESTConnectionError as e:
|
||||
logger.info('Trying to inititialize connection with broker (last error: {})'.format(e))
|
||||
self.doWait(5000) # Wait a bit and retry
|
||||
except rest.RESTError as e: # Invalid key?
|
||||
logger.error('Error validating with broker. (Invalid token?): {}'.format(e))
|
||||
return False
|
||||
except Exception:
|
||||
logger.exception()
|
||||
self.doWait(5000) # Wait a bit and retry...
|
||||
|
||||
return self.configureMachine()
|
||||
|
||||
def uninitialize(self):
|
||||
self._initialized = False
|
||||
self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
|
||||
|
||||
def finish(self) -> None:
|
||||
if self._http:
|
||||
self._http.stop()
|
||||
|
||||
# If logged in, notify UDS of logout (daemon stoped = no control = logout)
|
||||
if self._loggedIn and self._cfg.own_token:
|
||||
self._loggedIn = False
|
||||
try:
|
||||
self._api.logout(
|
||||
self._cfg.actorType,
|
||||
self._cfg.own_token,
|
||||
'',
|
||||
self._interfaces,
|
||||
self._secret
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Error notifying final logout to UDS: %s', e)
|
||||
|
||||
self.notifyStop()
|
||||
|
||||
def checkIpsChanged(self) -> None:
|
||||
if not self.isManaged():
|
||||
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
|
||||
|
||||
try:
|
||||
if not self._cfg.own_token or not self._cfg.config or not self._cfg.config.unique_id:
|
||||
# Not enouth data do check
|
||||
return
|
||||
currentInterfaces = list(platform.operations.getNetworkInfo())
|
||||
old = self.serviceInterfaceInfo()
|
||||
new = self.serviceInterfaceInfo(currentInterfaces)
|
||||
if not new or not old:
|
||||
raise Exception('No ip currently available for {}'.format(self._cfg.config.unique_id))
|
||||
if old.ip != new.ip:
|
||||
self._certificate = self._api.notifyIpChange(self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT)
|
||||
# Now store new addresses & interfaces...
|
||||
self._interfaces = currentInterfaces
|
||||
logger.info('Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip))
|
||||
# Stop the running HTTP Thread and start a new one, with new generated cert
|
||||
self.startHttpServer()
|
||||
except Exception as e:
|
||||
# No ip changed, log exception for info
|
||||
logger.warn('Checking ips failed: {}'.format(e))
|
||||
|
||||
def rename(
|
||||
self,
|
||||
name: str,
|
||||
userName: typing.Optional[str] = None,
|
||||
oldPassword: typing.Optional[str] = None,
|
||||
newPassword: typing.Optional[str] = None
|
||||
) -> None:
|
||||
'''
|
||||
Invoked when broker requests a rename action
|
||||
default does nothing
|
||||
'''
|
||||
hostName = platform.operations.getComputerName()
|
||||
|
||||
if hostName.lower() == name.lower():
|
||||
logger.info('Computer name is already {}'.format(hostName))
|
||||
return
|
||||
|
||||
# Check for password change request for an user
|
||||
if userName and newPassword:
|
||||
logger.info('Setting password for configured user')
|
||||
try:
|
||||
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
|
||||
except Exception as e:
|
||||
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, str(e)))
|
||||
|
||||
if platform.operations.renameComputer(name):
|
||||
self.reboot()
|
||||
|
||||
def loop(self):
|
||||
# Main common loop
|
||||
try:
|
||||
# Checks if ips has changed
|
||||
self.checkIpsChanged()
|
||||
|
||||
# Now check if every registered client is already there (if logged in OFC)
|
||||
if self._loggedIn and not self._clientsPool.ping():
|
||||
self.logout('client_unavailable')
|
||||
except Exception as e:
|
||||
logger.error('Exception on main service loop: %s', e)
|
||||
|
||||
# ******************************************************
|
||||
# Methods that can be overriden by linux & windows Actor
|
||||
# ******************************************************
|
||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
||||
self,
|
||||
name: str,
|
||||
domain: str,
|
||||
ou: str,
|
||||
account: str,
|
||||
password: str
|
||||
) -> None:
|
||||
'''
|
||||
Invoked when broker requests a "domain" action
|
||||
default does nothing
|
||||
'''
|
||||
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
||||
|
||||
# Client notifications
|
||||
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
|
||||
self._loggedIn = True
|
||||
|
||||
master_token = None
|
||||
secret = None
|
||||
# If unmanaged, do initialization now, because we don't know before this
|
||||
# Also, even if not initialized, get a "login" notification token
|
||||
if not self.isManaged():
|
||||
self.initialize()
|
||||
master_token = self._cfg.master_token
|
||||
secret = self._secret
|
||||
|
||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
||||
# In that case, take master token (if machine is Unamanaged version)
|
||||
token = self._cfg.own_token or master_token
|
||||
if token:
|
||||
result = self._api.login(
|
||||
self._cfg.actorType,
|
||||
token,
|
||||
username,
|
||||
sessionType or '',
|
||||
self._interfaces,
|
||||
secret
|
||||
)
|
||||
|
||||
script = platform.store.invokeScriptOnLogin()
|
||||
if script:
|
||||
script += f'{username} {sessionType or "unknown"} {self._cfg.actorType}'
|
||||
self.execute(script, 'Logon')
|
||||
|
||||
return result
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
self._loggedIn = False
|
||||
|
||||
master_token = self._cfg.master_token if self.isManaged() else None
|
||||
|
||||
# Own token will not be set if UDS did not assigned the initialized VM to an user
|
||||
# In that case, take master token (if machine is Unamanaged version)
|
||||
token = self._cfg.own_token or master_token
|
||||
if token:
|
||||
self._api.logout(
|
||||
self._cfg.actorType,
|
||||
token,
|
||||
username,
|
||||
self._interfaces,
|
||||
self._secret
|
||||
)
|
||||
|
||||
self.onLogout(username)
|
||||
|
||||
if not self.isManaged():
|
||||
self.uninitialize()
|
||||
|
||||
# ****************************************
|
||||
# Methods that CAN BE overriden by actors
|
||||
# ****************************************
|
||||
def doWait(self, miliseconds: int) -> None:
|
||||
'''
|
||||
Invoked to wait a bit
|
||||
CAN be OVERRIDEN
|
||||
'''
|
||||
seconds = miliseconds / 1000.0
|
||||
# So it can be broken by "stop"
|
||||
while self._isAlive and seconds > 1:
|
||||
time.sleep(1)
|
||||
seconds -= 1
|
||||
time.sleep(seconds)
|
||||
|
||||
def notifyStop(self) -> None:
|
||||
'''
|
||||
Overriden to log stop (on windows, notify to service manager)
|
||||
'''
|
||||
logger.info('Service stopped')
|
||||
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str: # pylint: disable=unused-argument
|
||||
'''
|
||||
Invoked when received a PRE Connection request via REST
|
||||
Base preconnect executes the preconnect command
|
||||
'''
|
||||
if self._cfg.pre_command:
|
||||
self.execute(self._cfg.pre_command + ' {} {} {} {}'.format(userName.replace('"', '%22'), protocol, ip, hostname), 'preConnect')
|
||||
|
||||
return 'ok'
|
||||
|
||||
def onLogout(self, userName: str) -> None:
|
||||
logger.debug('On logout invoked for {}'.format(userName))
|
47
actor/src/udsactor/tools.py
Normal file
47
actor/src/udsactor/tools.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import threading
|
||||
|
||||
from udsactor.log import logger
|
||||
|
||||
class ScriptExecutorThread(threading.Thread):
|
||||
def __init__(self, script: str) -> None:
|
||||
super(ScriptExecutorThread, self).__init__()
|
||||
self.script = script
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
logger.debug('Executing script: {}'.format(self.script))
|
||||
exec(self.script, globals(), None) # pylint: disable=exec-used
|
||||
except Exception as e:
|
||||
logger.error('Error executing script: {}'.format(e))
|
||||
logger.exception()
|
63
actor/src/udsactor/types.py
Normal file
63
actor/src/udsactor/types.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import typing
|
||||
|
||||
MANAGED = 'managed'
|
||||
UNMANAGED = 'unmanaged'
|
||||
|
||||
class InterfaceInfoType(typing.NamedTuple):
|
||||
name: str
|
||||
mac: str
|
||||
ip: str
|
||||
|
||||
class AuthenticatorType(typing.NamedTuple):
|
||||
authId: str
|
||||
authSmallName: str
|
||||
auth: str
|
||||
type: str
|
||||
priority: int
|
||||
isCustom: bool
|
||||
|
||||
class ActorOsConfigurationType(typing.NamedTuple):
|
||||
action: str
|
||||
name: str
|
||||
username: typing.Optional[str] = None
|
||||
password: typing.Optional[str] = None
|
||||
new_password: typing.Optional[str] = None
|
||||
ad: typing.Optional[str] = None
|
||||
ou: typing.Optional[str] = None
|
||||
|
||||
class ActorDataConfigurationType(typing.NamedTuple):
|
||||
unique_id: typing.Optional[str] = None
|
||||
os: typing.Optional[ActorOsConfigurationType] = None
|
||||
|
||||
class ActorConfigurationType(typing.NamedTuple):
|
||||
host: str
|
||||
validateCertificate: bool
|
||||
actorType: typing.Optional[str] = None
|
||||
master_token: typing.Optional[str] = None
|
||||
own_token: typing.Optional[str] = None
|
||||
|
||||
pre_command: typing.Optional[str] = None
|
||||
runonce_command: typing.Optional[str] = None
|
||||
post_command: typing.Optional[str] = None
|
||||
|
||||
log_level: int = 2
|
||||
|
||||
config: typing.Optional[ActorDataConfigurationType] = None
|
||||
|
||||
data: typing.Optional[typing.Dict[str, typing.Any]] = None
|
||||
|
||||
class InitializationResultType(typing.NamedTuple):
|
||||
own_token: typing.Optional[str] = None
|
||||
unique_id: typing.Optional[str] = None
|
||||
os: typing.Optional[ActorOsConfigurationType] = None
|
||||
|
||||
class LoginResultInfoType(typing.NamedTuple):
|
||||
ip: str
|
||||
hostname: str
|
||||
dead_line: typing.Optional[int]
|
||||
max_idle: typing.Optional[int] # Not provided by broker
|
||||
|
||||
class CertificateInfoType(typing.NamedTuple):
|
||||
private_key: str
|
||||
server_certificate: str
|
||||
password: str
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -25,10 +25,7 @@
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .usage import usage
|
||||
from .users import users
|
82
actor/src/udsactor/windows/log.py
Normal file
82
actor/src/udsactor/windows/log.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import typing
|
||||
|
||||
import servicemanager # pylint: disable=import-error
|
||||
|
||||
# Valid logging levels, from UDS Broker (uds.core.utils.log).
|
||||
from .. import loglevel
|
||||
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
def __init__(self):
|
||||
# tempdir is different for "user application" and "service"
|
||||
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp
|
||||
try:
|
||||
logging.basicConfig(
|
||||
filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'),
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.DEBUG
|
||||
)
|
||||
except Exception:
|
||||
logging.basicConfig() # basic init
|
||||
|
||||
self.logger = logging.getLogger('udsactor')
|
||||
self.serviceLogger = False
|
||||
|
||||
def log(self, level: int, message: str) -> None:
|
||||
# Debug messages are logged to a file
|
||||
# our loglevels are 0 (other), 10000 (debug), ....
|
||||
# logging levels are 10 (debug), 20 (info)
|
||||
# OTHER = logging.NOTSET
|
||||
if self.logger:
|
||||
self.logger.log(int(level / 1000), message)
|
||||
|
||||
if level < loglevel.ERROR or self.serviceLogger is False: # Only information and above will be on event log
|
||||
return
|
||||
|
||||
# In fact, we have restricted level in windows event log to ERROR or FATAL
|
||||
# but left the code for just a case in the future...
|
||||
if level < loglevel.WARN: # Info
|
||||
servicemanager.LogInfoMsg(message)
|
||||
elif level < loglevel.ERROR: # WARN
|
||||
servicemanager.LogWarningMsg(message)
|
||||
else: # Error & Fatal
|
||||
servicemanager.LogErrorMsg(message)
|
251
actor/src/udsactor/windows/operations.py
Normal file
251
actor/src/udsactor/windows/operations.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import os
|
||||
import subprocess
|
||||
import ctypes
|
||||
from ctypes.wintypes import DWORD, LPCWSTR
|
||||
import typing
|
||||
|
||||
import win32com.client
|
||||
from win32com.shell import shell # pylint: disable=no-name-in-module,import-error
|
||||
import win32net
|
||||
import win32security
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
from .. import types
|
||||
from ..log import logger
|
||||
|
||||
def checkPermissions() -> bool:
|
||||
return shell.IsUserAnAdmin()
|
||||
|
||||
def getErrorMessage(resultCode: int = 0) -> str:
|
||||
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||
msg = win32api.FormatMessage(resultCode)
|
||||
return msg
|
||||
|
||||
def getComputerName() -> str:
|
||||
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
||||
|
||||
def getNetworkInfo() -> typing.Iterator[types.InterfaceInfoType]:
|
||||
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
||||
wmobj = obj.ConnectServer("localhost", "root\\cimv2")
|
||||
adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True")
|
||||
try:
|
||||
for obj in adapters:
|
||||
for ip in obj.IPAddress:
|
||||
if ':' in ip: # Is IPV6, skip this
|
||||
continue
|
||||
if ip is None or ip == '' or ip.startswith('169.254') or ip.startswith('0.'): # If single link ip, or no ip
|
||||
continue
|
||||
yield types.InterfaceInfoType(name=obj.Caption, mac=obj.MACAddress, ip=ip)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
def getDomainName() -> str:
|
||||
'''
|
||||
Will return the domain name if we belong a domain, else None
|
||||
(if part of a network group, will also return None)
|
||||
'''
|
||||
# Status:
|
||||
# 0 = Unknown
|
||||
# 1 = Unjoined
|
||||
# 2 = Workgroup
|
||||
# 3 = Domain
|
||||
domain, status = win32net.NetGetJoinInformation()
|
||||
if status != 3:
|
||||
domain = None
|
||||
|
||||
return domain
|
||||
|
||||
def getWindowsVersion() -> typing.Tuple[int, int, int, int, str]:
|
||||
return win32api.GetVersionEx()
|
||||
|
||||
EWX_LOGOFF = 0x00000000
|
||||
EWX_SHUTDOWN = 0x00000001
|
||||
EWX_REBOOT = 0x00000002
|
||||
EWX_FORCE = 0x00000004
|
||||
EWX_POWEROFF = 0x00000008
|
||||
EWX_FORCEIFHUNG = 0x00000010
|
||||
|
||||
def reboot(flags: int = EWX_FORCEIFHUNG | EWX_REBOOT) -> None:
|
||||
hproc = win32api.GetCurrentProcess()
|
||||
htok = win32security.OpenProcessToken(hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
|
||||
privs = ((win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME), win32security.SE_PRIVILEGE_ENABLED),)
|
||||
win32security.AdjustTokenPrivileges(htok, 0, privs)
|
||||
win32api.ExitWindowsEx(flags, 0)
|
||||
|
||||
def loggoff() -> None:
|
||||
win32api.ExitWindowsEx(EWX_LOGOFF)
|
||||
|
||||
def renameComputer(newName: str) -> bool:
|
||||
'''
|
||||
Changes the computer name
|
||||
Returns True if reboot needed
|
||||
'''
|
||||
# Needs admin privileges to work
|
||||
if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: # @UndefinedVariable
|
||||
# win32api.FormatMessage -> returns error string
|
||||
# win32api.GetLastError -> returns error code
|
||||
# (just put this comment here to remember to log this when logger is available)
|
||||
error = getErrorMessage()
|
||||
computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
||||
raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error))
|
||||
return True
|
||||
|
||||
NETSETUP_JOIN_DOMAIN = 0x00000001
|
||||
NETSETUP_ACCT_CREATE = 0x00000002
|
||||
NETSETUP_ACCT_DELETE = 0x00000004
|
||||
NETSETUP_WIN9X_UPGRADE = 0x00000010
|
||||
NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020
|
||||
NETSETUP_JOIN_UNSECURE = 0x00000040
|
||||
NETSETUP_MACHINE_PWD_PASSED = 0x00000080
|
||||
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
|
||||
NETSETUP_DEFER_SPN_SET = 0x1000000
|
||||
|
||||
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False) -> None:
|
||||
'''
|
||||
Joins machine to a windows domain
|
||||
:param domain: Domain to join to
|
||||
:param ou: Ou that will hold machine
|
||||
:param account: Account used to join domain
|
||||
:param password: Password of account used to join domain
|
||||
:param executeInOneStep: If true, means that this machine has been renamed and wants to add NETSETUP_JOIN_WITH_NEW_NAME to request so we can do rename/join in one step.
|
||||
'''
|
||||
# If account do not have domain, include it
|
||||
if '@' not in account and '\\' not in account:
|
||||
if '.' in domain:
|
||||
account = account + '@' + domain
|
||||
else:
|
||||
account = domain + '\\' + account
|
||||
|
||||
# Do log
|
||||
flags: typing.Any = NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN
|
||||
|
||||
if executeInOneStep:
|
||||
flags |= NETSETUP_JOIN_WITH_NEW_NAME
|
||||
|
||||
flags = DWORD(flags)
|
||||
|
||||
lpDomain = LPCWSTR(domain)
|
||||
|
||||
# Must be in format "ou=.., ..., dc=...,"
|
||||
lpOu = LPCWSTR(ou) if ou is not None and ou != '' else None
|
||||
lpAccount = LPCWSTR(account)
|
||||
lpPassword = LPCWSTR(password)
|
||||
|
||||
res = ctypes.windll.netapi32.NetJoinDomain(None, lpDomain, lpOu, lpAccount, lpPassword, flags)
|
||||
# Machine found in another ou, use it and warn this on log
|
||||
if res == 2224:
|
||||
flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN)
|
||||
res = ctypes.windll.netapi32.NetJoinDomain(None, lpDomain, None, lpAccount, lpPassword, flags)
|
||||
if res:
|
||||
# Log the error
|
||||
error = getErrorMessage(res)
|
||||
if res == 1355:
|
||||
error = "DC Is not reachable"
|
||||
logger.error('Error joining domain: {}, {}'.format(error, res))
|
||||
raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain, account, ', under OU {}'.format(ou) if ou is not None else '', res, error))
|
||||
|
||||
def changeUserPassword(user: str, oldPassword: str, newPassword: str) -> None:
|
||||
# lpUser = LPCWSTR(user)
|
||||
# lpOldPassword = LPCWSTR(oldPassword)
|
||||
# lpNewPassword = LPCWSTR(newPassword)
|
||||
|
||||
# res = ctypes.windll.netapi32.NetUserChangePassword(None, lpUser, lpOldPassword, lpNewPassword)
|
||||
# Try to set new password "a las bravas", ignoring old one. This will not work with domain users
|
||||
res = win32net.NetUserSetInfo(None, user, 1003, {'password': newPassword})
|
||||
|
||||
if res:
|
||||
# Log the error, and raise exception to parent
|
||||
error = getErrorMessage(res)
|
||||
raise Exception('Error changing password for user {}: {} {}'.format(user, res, error))
|
||||
|
||||
class LASTINPUTINFO(ctypes.Structure): # pylint: disable=too-few-public-methods
|
||||
_fields_ = [
|
||||
('cbSize', ctypes.c_uint),
|
||||
('dwTime', ctypes.c_uint),
|
||||
]
|
||||
|
||||
def initIdleDuration(atLeastSeconds: int): # pylint: disable=unused-argument
|
||||
'''
|
||||
In windows, there is no need to set screensaver
|
||||
'''
|
||||
return
|
||||
|
||||
def getIdleDuration() -> float:
|
||||
try:
|
||||
lastInputInfo = LASTINPUTINFO()
|
||||
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) # pylint: disable=attribute-defined-outside-init
|
||||
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
|
||||
return 0
|
||||
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount()).value
|
||||
if current < lastInputInfo.dwTime:
|
||||
current += 4294967296 # If current has "rolled" to zero, adjust it so it is greater than lastInputInfo
|
||||
millis = current - lastInputInfo.dwTime # @UndefinedVariable
|
||||
return millis / 1000.0
|
||||
except Exception as e:
|
||||
logger.error('Getting idle duration: {}'.format(e))
|
||||
return 0
|
||||
|
||||
def getCurrentUser() -> str:
|
||||
'''
|
||||
Returns current logged in username
|
||||
'''
|
||||
return os.environ['USERNAME']
|
||||
|
||||
def getSessionType() -> str:
|
||||
'''
|
||||
Known values:
|
||||
* Unknown -> No SESSIONNAME environment variable
|
||||
* Console -> Local session
|
||||
* RDP-Tcp#[0-9]+ -> RDP Session
|
||||
'''
|
||||
return os.environ.get('SESSIONNAME', 'unknown')
|
||||
|
||||
def writeToPipe(pipeName: str, bytesPayload: bytes, waitForResponse: bool) -> typing.Optional[bytes]:
|
||||
# (str, bytes, bool) -> Optional[bytes]
|
||||
try:
|
||||
with open(pipeName, 'r+b', 0) as f:
|
||||
f.write(bytesPayload)
|
||||
# f.seek(0) # As recommended on intenet, but seems to work fin without thos
|
||||
if waitForResponse:
|
||||
return f.read()
|
||||
return b'ok'
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def forceTimeSync() -> None:
|
||||
try:
|
||||
subprocess.call([r'c:\WINDOWS\System32\w32tm.exe', ' /resync']) # , '/rediscover'])
|
||||
except Exception as e:
|
||||
logger.error('Error invoking time sync command: %s', e)
|
73
actor/src/udsactor/windows/runner.py
Normal file
73
actor/src/udsactor/windows/runner.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import sys
|
||||
import win32service
|
||||
import win32serviceutil
|
||||
import servicemanager
|
||||
|
||||
import win32timezone # pylint: disable=unused-import
|
||||
|
||||
from .service import UDSActorSvc
|
||||
|
||||
def setupRecoverService():
|
||||
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
|
||||
|
||||
try:
|
||||
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
|
||||
|
||||
try:
|
||||
hs = win32serviceutil.SmartOpenService(hscm, svc_name, win32service.SERVICE_ALL_ACCESS)
|
||||
service_failure_actions = {
|
||||
'ResetPeriod': 864000, # Time in ms after which to reset the failure count to zero.
|
||||
'RebootMsg': u'', # Not using reboot option
|
||||
'Command': u'', # Not using run-command option
|
||||
'Actions': [
|
||||
(win32service.SC_ACTION_RESTART, 5000), # action, delay in ms
|
||||
(win32service.SC_ACTION_RESTART, 5000)
|
||||
]
|
||||
}
|
||||
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hs)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hscm)
|
||||
|
||||
|
||||
def run() -> None:
|
||||
if len(sys.argv) == 1:
|
||||
servicemanager.Initialize()
|
||||
servicemanager.PrepareToHostSingle(UDSActorSvc)
|
||||
servicemanager.StartServiceCtrlDispatcher()
|
||||
elif sys.argv[1] == '--setup-recovery':
|
||||
setupRecoverService()
|
||||
else:
|
||||
win32serviceutil.HandleCommandLine(UDSActorSvc)
|
276
actor/src/udsactor/windows/service.py
Normal file
276
actor/src/udsactor/windows/service.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import struct
|
||||
import typing
|
||||
|
||||
import win32serviceutil
|
||||
import win32service
|
||||
import win32security
|
||||
import win32net
|
||||
import win32event
|
||||
import pythoncom
|
||||
import servicemanager
|
||||
import winreg as wreg
|
||||
|
||||
from . import operations
|
||||
from . import store
|
||||
from ..service import CommonService
|
||||
|
||||
from ..log import logger
|
||||
|
||||
REMOTE_USERS_SID = 'S-1-5-32-555' # Well nown sid for remote desktop users
|
||||
|
||||
class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
'''
|
||||
This class represents a Windows Service for managing actor interactions
|
||||
with UDS Broker and Machine
|
||||
'''
|
||||
# ServiceeFramework related
|
||||
_svc_name_ = "UDSActorNG"
|
||||
_svc_display_name_ = "UDS Actor Service"
|
||||
_svc_description_ = "UDS Actor Management Service"
|
||||
_svc_deps_ = ['EventLog']
|
||||
|
||||
_user: typing.Optional[str]
|
||||
_hWaitStop: typing.Any
|
||||
|
||||
def __init__(self, args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
CommonService.__init__(self)
|
||||
|
||||
self._hWaitStop = win32event.CreateEvent(None, 1, 0, None)
|
||||
self._user = None
|
||||
|
||||
def SvcStop(self) -> None:
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
self._isAlive = False
|
||||
win32event.SetEvent(self._hWaitStop)
|
||||
|
||||
SvcShutdown = SvcStop
|
||||
|
||||
def notifyStop(self) -> None:
|
||||
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STOPPED, (self._svc_name_, ''))
|
||||
super().notifyStop()
|
||||
|
||||
def doWait(self, miliseconds: int) -> None:
|
||||
win32event.WaitForSingleObject(self._hWaitStop, miliseconds)
|
||||
# On windows, and while on tasks, ensure that our app processes waiting messages on "wait times"
|
||||
pythoncom.PumpWaitingMessages() # pylint: disable=no-member
|
||||
|
||||
def oneStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments
|
||||
'''
|
||||
Ejecutes the join domain in exactly one step
|
||||
'''
|
||||
currName = operations.getComputerName()
|
||||
# If name is desired, simply execute multiStepJoin, because computer
|
||||
# name will not change
|
||||
if currName.lower() == name.lower():
|
||||
self.multiStepJoin(name, domain, ou, account, password)
|
||||
return
|
||||
|
||||
operations.renameComputer(name)
|
||||
logger.debug('Computer renamed to {} without reboot'.format(name))
|
||||
operations.joinDomain(domain, ou, account, password, executeInOneStep=True)
|
||||
logger.debug('Requested join domain {} without errors'.format(domain))
|
||||
self.reboot()
|
||||
|
||||
def multiStepJoin(self, name: str, domain: str, ou: str, account: str, password: str) -> None: # pylint: disable=too-many-arguments
|
||||
currName = operations.getComputerName()
|
||||
if currName.lower() == name.lower():
|
||||
currDomain = operations.getDomainName()
|
||||
if currDomain:
|
||||
# logger.debug('Name: "{}" vs "{}", Domain: "{}" vs "{}"'.format(currName.lower(), name.lower(), currDomain.lower(), domain.lower()))
|
||||
logger.debug('Machine {} is part of domain {}'.format(name, domain))
|
||||
self.setReady()
|
||||
else:
|
||||
operations.joinDomain(domain, ou, account, password, executeInOneStep=False)
|
||||
self.reboot()
|
||||
else:
|
||||
operations.renameComputer(name)
|
||||
logger.info('Rebooting computer for activating new name {}'.format(name))
|
||||
self.reboot()
|
||||
|
||||
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
|
||||
self,
|
||||
name: str,
|
||||
domain: str,
|
||||
ou: str,
|
||||
account: str,
|
||||
password: str
|
||||
) -> None:
|
||||
versionData = operations.getWindowsVersion()
|
||||
versionInt = versionData[0] * 10 + versionData[1]
|
||||
logger.debug('Starting joining domain {} with name {} (detected operating version: {})'.format(domain, name, versionData))
|
||||
# Accepts one step joinDomain, also remember XP is no more supported by
|
||||
# microsoft, but this also must works with it because will do a "multi
|
||||
# step" join
|
||||
if versionInt >= 60 and not store.useOldJoinSystem():
|
||||
self.oneStepJoin(name, domain, ou, account, password)
|
||||
else:
|
||||
logger.info('Using multiple step join because configuration requests to do so')
|
||||
self.multiStepJoin(name, domain, ou, account, password)
|
||||
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str:
|
||||
logger.debug('Pre connect invoked')
|
||||
|
||||
if protocol == 'rdp': # If connection is not using rdp, skip adding user
|
||||
# Well known SSID for Remote Desktop Users
|
||||
groupName = win32security.LookupAccountSid(None, win32security.GetBinarySid(REMOTE_USERS_SID))[0]
|
||||
|
||||
useraAlreadyInGroup = False
|
||||
resumeHandle = 0
|
||||
while True:
|
||||
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768)
|
||||
if userName.lower() in [u['name'].lower() for u in users]:
|
||||
useraAlreadyInGroup = True
|
||||
break
|
||||
if resumeHandle == 0:
|
||||
break
|
||||
|
||||
if not useraAlreadyInGroup:
|
||||
logger.debug('User not in group, adding it')
|
||||
self._user = userName
|
||||
try:
|
||||
userSSID = win32security.LookupAccountName(None, userName)[0]
|
||||
win32net.NetLocalGroupAddMembers(None, groupName, 0, [{'sid': userSSID}])
|
||||
except Exception as e:
|
||||
logger.error('Exception adding user to Remote Desktop Users: {}'.format(e))
|
||||
else:
|
||||
self._user = None
|
||||
logger.debug('User {} already in group'.format(userName))
|
||||
|
||||
return super().preConnect(userName, protocol, ip, hostname)
|
||||
|
||||
def ovLogon(self, username: str, password: str) -> str:
|
||||
"""
|
||||
Logon on oVirt agent
|
||||
currently not used.
|
||||
"""
|
||||
# Compose packet for ov
|
||||
usernameBytes = username.encode()
|
||||
passwordBytes = password.encode()
|
||||
packet = struct.pack('!I', len(usernameBytes)) + usernameBytes + struct.pack('!I', len(passwordBytes)) + passwordBytes
|
||||
# Send packet with username/password to ov pipe
|
||||
operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
|
||||
return 'done'
|
||||
|
||||
def onLogout(self, userName) -> None:
|
||||
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
|
||||
try:
|
||||
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
||||
groupName = win32security.LookupAccountSid(None, p)[0]
|
||||
except Exception:
|
||||
logger.error('Exception getting Windows Group')
|
||||
return
|
||||
|
||||
if self._user:
|
||||
try:
|
||||
win32net.NetLocalGroupDelMembers(None, groupName, [self._user])
|
||||
except Exception as e:
|
||||
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
|
||||
|
||||
def isInstallationRunning(self):
|
||||
'''
|
||||
Detect if windows is installing anything, so we can delay the execution of Service
|
||||
'''
|
||||
try:
|
||||
key = wreg.OpenKey(wreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\State')
|
||||
data, _ = wreg.QueryValueEx(key, 'ImageState')
|
||||
logger.debug('State: %s', data)
|
||||
return data != 'IMAGE_STATE_COMPLETE' # If ImageState is different of ImageStateComplete, there is something running on installation
|
||||
except Exception: # If not found, means that no installation is running
|
||||
return False
|
||||
|
||||
def SvcDoRun(self) -> None: # pylint: disable=too-many-statements, too-many-branches
|
||||
'''
|
||||
Main service loop
|
||||
'''
|
||||
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
|
||||
|
||||
# call the CoInitialize to allow the registration to run in an other
|
||||
# thread
|
||||
logger.debug('Initializing coms')
|
||||
|
||||
pythoncom.CoInitialize() # pylint: disable=no-member
|
||||
|
||||
# Check if some install is running on windows before proceeding
|
||||
while self._isAlive:
|
||||
if self.isInstallationRunning():
|
||||
win32event.WaitForSingleObject(self._hWaitStop, 1000) # Wait a bit, and check again
|
||||
continue
|
||||
break
|
||||
|
||||
if not self._isAlive: # Has been stopped while waiting windows installations
|
||||
self.finish()
|
||||
return
|
||||
|
||||
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
|
||||
if self.isManaged():
|
||||
if not self.initialize():
|
||||
logger.info('Service stopped due to init')
|
||||
self.finish()
|
||||
win32event.WaitForSingleObject(self._hWaitStop, 5000)
|
||||
return # Stop daemon if initializes told to do so
|
||||
|
||||
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
|
||||
self.setReady()
|
||||
else:
|
||||
if not self.initializeUnmanaged():
|
||||
self.finish()
|
||||
return
|
||||
|
||||
# Start listening for petitions
|
||||
self.startHttpServer()
|
||||
|
||||
# *********************
|
||||
# * Main Service loop *
|
||||
# *********************
|
||||
# Counter used to check ip changes only once every 10 seconds
|
||||
counter = 0
|
||||
while self._isAlive:
|
||||
counter += 1
|
||||
try:
|
||||
pythoncom.PumpWaitingMessages() # pylint: disable=no-member
|
||||
|
||||
if counter % 5 == 0: # Once every 5 seconds
|
||||
self.loop()
|
||||
|
||||
except Exception as e:
|
||||
logger.error('Got exception on main loop: %s', e)
|
||||
# Continue after a while...
|
||||
|
||||
# In milliseconds, will break if event hWaitStop is set
|
||||
win32event.WaitForSingleObject(self._hWaitStop, 1000)
|
||||
|
||||
logger.debug('Exited main loop')
|
||||
|
||||
self.finish()
|
109
actor/src/udsactor/windows/store.py
Normal file
109
actor/src/udsactor/windows/store.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import pickle
|
||||
|
||||
import winreg as wreg
|
||||
import win32security
|
||||
|
||||
from .. import types
|
||||
|
||||
PATH = 'Software\\UDSActor'
|
||||
BASEKEY = wreg.HKEY_LOCAL_MACHINE
|
||||
|
||||
def fixRegistryPermissions(handle) -> None:
|
||||
# Fix permissions so users can't read this key
|
||||
v = win32security.GetSecurityInfo(handle, win32security.SE_REGISTRY_KEY, win32security.DACL_SECURITY_INFORMATION)
|
||||
dacl = v.GetSecurityDescriptorDacl()
|
||||
n = 0
|
||||
# Remove all normal users access permissions to the registry key
|
||||
while n < dacl.GetAceCount():
|
||||
if str(dacl.GetAce(n)[2]) == 'PySID:S-1-5-32-545': # Whell known Users SID
|
||||
dacl.DeleteAce(n)
|
||||
else:
|
||||
n += 1
|
||||
win32security.SetSecurityInfo(
|
||||
handle,
|
||||
win32security.SE_REGISTRY_KEY,
|
||||
win32security.DACL_SECURITY_INFORMATION | win32security.PROTECTED_DACL_SECURITY_INFORMATION,
|
||||
None,
|
||||
None,
|
||||
dacl,
|
||||
None
|
||||
)
|
||||
|
||||
def readConfig() -> types.ActorConfigurationType:
|
||||
try:
|
||||
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
|
||||
data, _ = wreg.QueryValueEx(key, '')
|
||||
wreg.CloseKey(key)
|
||||
return pickle.loads(data)
|
||||
except Exception:
|
||||
return types.ActorConfigurationType('', False)
|
||||
|
||||
|
||||
def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
try:
|
||||
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
|
||||
except Exception:
|
||||
key = wreg.CreateKeyEx(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
|
||||
|
||||
fixRegistryPermissions(key.handle) # type: ignore
|
||||
|
||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config)) # type: ignore
|
||||
wreg.CloseKey(key)
|
||||
|
||||
|
||||
def useOldJoinSystem() -> bool:
|
||||
try:
|
||||
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
|
||||
try:
|
||||
data, _ = wreg.QueryValueEx(key, 'join')
|
||||
except Exception:
|
||||
data = ''
|
||||
wreg.CloseKey(key)
|
||||
except Exception:
|
||||
data = ''
|
||||
|
||||
return data == 'old'
|
||||
|
||||
def invokeScriptOnLogin() -> str:
|
||||
try:
|
||||
key = wreg.OpenKey(BASEKEY, PATH, 0, wreg.KEY_QUERY_VALUE)
|
||||
try:
|
||||
data, _ = wreg.QueryValueEx(key, 'logonScript')
|
||||
except Exception:
|
||||
data = ''
|
||||
wreg.CloseKey(key)
|
||||
except Exception:
|
||||
data = ''
|
||||
|
||||
return data
|
248
actor/src/ui/setup_dialog_ui.py
Normal file
248
actor/src/ui/setup_dialog_ui.py
Normal file
@@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_UdsActorSetupDialog(object):
|
||||
def setupUi(self, UdsActorSetupDialog):
|
||||
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
||||
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
UdsActorSetupDialog.resize(590, 307)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(UdsActorSetupDialog.sizePolicy().hasHeightForWidth())
|
||||
UdsActorSetupDialog.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Verdana")
|
||||
font.setPointSize(9)
|
||||
UdsActorSetupDialog.setFont(font)
|
||||
UdsActorSetupDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/img/img/uds-icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
UdsActorSetupDialog.setWindowIcon(icon)
|
||||
UdsActorSetupDialog.setAutoFillBackground(False)
|
||||
UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
||||
UdsActorSetupDialog.setSizeGripEnabled(False)
|
||||
UdsActorSetupDialog.setModal(True)
|
||||
self.registerButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.registerButton.setEnabled(False)
|
||||
self.registerButton.setGeometry(QtCore.QRect(10, 270, 181, 23))
|
||||
self.registerButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.registerButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
self.registerButton.setObjectName("registerButton")
|
||||
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.closeButton.setGeometry(QtCore.QRect(410, 270, 171, 23))
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.closeButton.sizePolicy().hasHeightForWidth())
|
||||
self.closeButton.setSizePolicy(sizePolicy)
|
||||
self.closeButton.setMinimumSize(QtCore.QSize(171, 0))
|
||||
self.closeButton.setObjectName("closeButton")
|
||||
self.tabWidget = QtWidgets.QTabWidget(UdsActorSetupDialog)
|
||||
self.tabWidget.setGeometry(QtCore.QRect(10, 10, 571, 241))
|
||||
self.tabWidget.setObjectName("tabWidget")
|
||||
self.tab_uds = QtWidgets.QWidget()
|
||||
self.tab_uds.setObjectName("tab_uds")
|
||||
self.layoutWidget = QtWidgets.QWidget(self.tab_uds)
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 551, 191))
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
||||
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
||||
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||||
self.formLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.formLayout.setVerticalSpacing(16)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_host = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_host.setObjectName("label_host")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_host)
|
||||
self.host = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.host.setAcceptDrops(False)
|
||||
self.host.setObjectName("host")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.host)
|
||||
self.label_auth = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_auth.setObjectName("label_auth")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_auth)
|
||||
self.authenticators = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.authenticators.setObjectName("authenticators")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.authenticators)
|
||||
self.label_username = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_username.setObjectName("label_username")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_username)
|
||||
self.username = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.username.setObjectName("username")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.username)
|
||||
self.label_password = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_password.setObjectName("label_password")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_password)
|
||||
self.password = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
self.password.setObjectName("password")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.password)
|
||||
self.validateCertificate = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.validateCertificate.setObjectName("validateCertificate")
|
||||
self.validateCertificate.addItem("")
|
||||
self.validateCertificate.addItem("")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.validateCertificate)
|
||||
self.label_security = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_security.setObjectName("label_security")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_security)
|
||||
self.label_host.raise_()
|
||||
self.host.raise_()
|
||||
self.label_auth.raise_()
|
||||
self.label_username.raise_()
|
||||
self.username.raise_()
|
||||
self.label_password.raise_()
|
||||
self.password.raise_()
|
||||
self.validateCertificate.raise_()
|
||||
self.label_security.raise_()
|
||||
self.authenticators.raise_()
|
||||
self.tabWidget.addTab(self.tab_uds, "")
|
||||
self.tab_advanced = QtWidgets.QWidget()
|
||||
self.tab_advanced.setObjectName("tab_advanced")
|
||||
self.layoutWidget_2 = QtWidgets.QWidget(self.tab_advanced)
|
||||
self.layoutWidget_2.setGeometry(QtCore.QRect(10, 10, 551, 161))
|
||||
self.layoutWidget_2.setObjectName("layoutWidget_2")
|
||||
self.formLayout_2 = QtWidgets.QFormLayout(self.layoutWidget_2)
|
||||
self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||||
self.formLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.formLayout_2.setVerticalSpacing(16)
|
||||
self.formLayout_2.setObjectName("formLayout_2")
|
||||
self.label_host_2 = QtWidgets.QLabel(self.layoutWidget_2)
|
||||
self.label_host_2.setObjectName("label_host_2")
|
||||
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_host_2)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout.setSpacing(4)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.preCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
|
||||
self.preCommand.setAcceptDrops(False)
|
||||
self.preCommand.setWhatsThis("")
|
||||
self.preCommand.setObjectName("preCommand")
|
||||
self.horizontalLayout.addWidget(self.preCommand)
|
||||
self.browsePreconnectButton = QtWidgets.QPushButton(self.layoutWidget_2)
|
||||
self.browsePreconnectButton.setAutoDefault(False)
|
||||
self.browsePreconnectButton.setFlat(False)
|
||||
self.browsePreconnectButton.setObjectName("browsePreconnectButton")
|
||||
self.horizontalLayout.addWidget(self.browsePreconnectButton)
|
||||
self.formLayout_2.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
|
||||
self.label_username_2 = QtWidgets.QLabel(self.layoutWidget_2)
|
||||
self.label_username_2.setObjectName("label_username_2")
|
||||
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_username_2)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout_2.setSpacing(4)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.runonceCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
|
||||
self.runonceCommand.setWhatsThis("")
|
||||
self.runonceCommand.setObjectName("runonceCommand")
|
||||
self.horizontalLayout_2.addWidget(self.runonceCommand)
|
||||
self.browseRunOnceButton = QtWidgets.QPushButton(self.layoutWidget_2)
|
||||
self.browseRunOnceButton.setAutoDefault(False)
|
||||
self.browseRunOnceButton.setObjectName("browseRunOnceButton")
|
||||
self.horizontalLayout_2.addWidget(self.browseRunOnceButton)
|
||||
self.formLayout_2.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
|
||||
self.label_password_2 = QtWidgets.QLabel(self.layoutWidget_2)
|
||||
self.label_password_2.setObjectName("label_password_2")
|
||||
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_password_2)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout_3.setSpacing(4)
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.postConfigCommand = QtWidgets.QLineEdit(self.layoutWidget_2)
|
||||
self.postConfigCommand.setWhatsThis("")
|
||||
self.postConfigCommand.setEchoMode(QtWidgets.QLineEdit.Normal)
|
||||
self.postConfigCommand.setObjectName("postConfigCommand")
|
||||
self.horizontalLayout_3.addWidget(self.postConfigCommand)
|
||||
self.browsePostConfigButton = QtWidgets.QPushButton(self.layoutWidget_2)
|
||||
self.browsePostConfigButton.setAutoDefault(False)
|
||||
self.browsePostConfigButton.setObjectName("browsePostConfigButton")
|
||||
self.horizontalLayout_3.addWidget(self.browsePostConfigButton)
|
||||
self.formLayout_2.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
|
||||
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget_2)
|
||||
self.label_loglevel.setObjectName("label_loglevel")
|
||||
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget_2)
|
||||
self.logLevelComboBox.setFrame(True)
|
||||
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(0, "DEBUG")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(1, "INFO")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(2, "ERROR")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(3, "FATAL")
|
||||
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.tabWidget.addTab(self.tab_advanced, "")
|
||||
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.testButton.setEnabled(False)
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 270, 181, 23))
|
||||
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.testButton.setObjectName("testButton")
|
||||
|
||||
self.retranslateUi(UdsActorSetupDialog)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.logLevelComboBox.setCurrentIndex(1)
|
||||
self.closeButton.clicked.connect(UdsActorSetupDialog.finish)
|
||||
self.registerButton.clicked.connect(UdsActorSetupDialog.registerWithUDS)
|
||||
self.host.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
|
||||
self.username.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
|
||||
self.password.textChanged['QString'].connect(UdsActorSetupDialog.textChanged)
|
||||
self.browsePreconnectButton.clicked.connect(UdsActorSetupDialog.browsePreconnect)
|
||||
self.browsePostConfigButton.clicked.connect(UdsActorSetupDialog.browsePostConfig)
|
||||
self.browseRunOnceButton.clicked.connect(UdsActorSetupDialog.browseRunOnce)
|
||||
self.host.editingFinished.connect(UdsActorSetupDialog.updateAuthenticators)
|
||||
self.authenticators.currentTextChanged['QString'].connect(UdsActorSetupDialog.textChanged)
|
||||
self.testButton.clicked.connect(UdsActorSetupDialog.testUDSServer)
|
||||
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
||||
|
||||
def retranslateUi(self, UdsActorSetupDialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration Tool"))
|
||||
self.registerButton.setToolTip(_translate("UdsActorSetupDialog", "Click to register Actor with UDS Broker"))
|
||||
self.registerButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html>"))
|
||||
self.registerButton.setText(_translate("UdsActorSetupDialog", "Register with UDS"))
|
||||
self.closeButton.setToolTip(_translate("UdsActorSetupDialog", "Closes UDS Actor Configuration (discard pending changes if any)"))
|
||||
self.closeButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html>"))
|
||||
self.closeButton.setText(_translate("UdsActorSetupDialog", "Close"))
|
||||
self.label_host.setText(_translate("UdsActorSetupDialog", "UDS Server"))
|
||||
self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN"))
|
||||
self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address"))
|
||||
self.label_auth.setText(_translate("UdsActorSetupDialog", "Authenticator"))
|
||||
self.authenticators.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the UDS Broker authenticator for credentials validation</p></body></html>"))
|
||||
self.label_username.setText(_translate("UdsActorSetupDialog", "Username"))
|
||||
self.username.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||
self.username.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
||||
self.label_password.setText(_translate("UdsActorSetupDialog", "Password"))
|
||||
self.password.setToolTip(_translate("UdsActorSetupDialog", "Password for user (Will not be stored on template)"))
|
||||
self.password.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator password for the user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique key for this image.</p></body></html>"))
|
||||
self.validateCertificate.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker"))
|
||||
self.validateCertificate.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=\" font-weight:600;\">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>"))
|
||||
self.validateCertificate.setItemText(0, _translate("UdsActorSetupDialog", "Ignore certificate"))
|
||||
self.validateCertificate.setItemText(1, _translate("UdsActorSetupDialog", "Verify certificate"))
|
||||
self.label_security.setText(_translate("UdsActorSetupDialog", "SSL Validation"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_uds), _translate("UdsActorSetupDialog", "UDS Server"))
|
||||
self.label_host_2.setText(_translate("UdsActorSetupDialog", "Preconnect"))
|
||||
self.preCommand.setToolTip(_translate("UdsActorSetupDialog", "Pre connection command. Executed just before the user is connected to machine."))
|
||||
self.browsePreconnectButton.setText(_translate("UdsActorSetupDialog", "Browse"))
|
||||
self.label_username_2.setText(_translate("UdsActorSetupDialog", "Runonce"))
|
||||
self.runonceCommand.setToolTip(_translate("UdsActorSetupDialog", "Run once command. Executed on first boot, just before UDS does anything."))
|
||||
self.browseRunOnceButton.setText(_translate("UdsActorSetupDialog", "Browse"))
|
||||
self.label_password_2.setText(_translate("UdsActorSetupDialog", "Postconfig"))
|
||||
self.postConfigCommand.setToolTip(_translate("UdsActorSetupDialog", "Command to execute after UDS finalizes the VM configuration."))
|
||||
self.browsePostConfigButton.setText(_translate("UdsActorSetupDialog", "Browse"))
|
||||
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_advanced), _translate("UdsActorSetupDialog", "Advanced"))
|
||||
self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test existing configuration (disabled if no config found)"))
|
||||
self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html>"))
|
||||
self.testButton.setText(_translate("UdsActorSetupDialog", "Test configuration"))
|
||||
from ui import uds_rc
|
142
actor/src/ui/setup_dialog_unmanaged_ui.py
Normal file
142
actor/src/ui/setup_dialog_unmanaged_ui.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_UdsActorSetupDialog(object):
|
||||
def setupUi(self, UdsActorSetupDialog):
|
||||
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
||||
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
UdsActorSetupDialog.resize(595, 220)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(UdsActorSetupDialog.sizePolicy().hasHeightForWidth())
|
||||
UdsActorSetupDialog.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Verdana")
|
||||
font.setPointSize(9)
|
||||
UdsActorSetupDialog.setFont(font)
|
||||
UdsActorSetupDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/img/img/uds-icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
UdsActorSetupDialog.setWindowIcon(icon)
|
||||
UdsActorSetupDialog.setAutoFillBackground(False)
|
||||
UdsActorSetupDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
||||
UdsActorSetupDialog.setSizeGripEnabled(False)
|
||||
UdsActorSetupDialog.setModal(True)
|
||||
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.saveButton.setEnabled(True)
|
||||
self.saveButton.setGeometry(QtCore.QRect(10, 180, 181, 23))
|
||||
self.saveButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
self.saveButton.setObjectName("saveButton")
|
||||
self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.closeButton.setGeometry(QtCore.QRect(410, 180, 171, 23))
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.closeButton.sizePolicy().hasHeightForWidth())
|
||||
self.closeButton.setSizePolicy(sizePolicy)
|
||||
self.closeButton.setMinimumSize(QtCore.QSize(171, 0))
|
||||
self.closeButton.setObjectName("closeButton")
|
||||
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.testButton.setEnabled(False)
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 180, 181, 23))
|
||||
self.testButton.setMinimumSize(QtCore.QSize(181, 0))
|
||||
self.testButton.setObjectName("testButton")
|
||||
self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog)
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 161))
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
||||
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
||||
self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||||
self.formLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.formLayout.setVerticalSpacing(16)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.label_security = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_security.setObjectName("label_security")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_security)
|
||||
self.validateCertificate = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.validateCertificate.setObjectName("validateCertificate")
|
||||
self.validateCertificate.addItem("")
|
||||
self.validateCertificate.addItem("")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.validateCertificate)
|
||||
self.label_host = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_host.setObjectName("label_host")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_host)
|
||||
self.host = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.host.setAcceptDrops(False)
|
||||
self.host.setObjectName("host")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.host)
|
||||
self.label_serviceToken = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_serviceToken.setObjectName("label_serviceToken")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_serviceToken)
|
||||
self.serviceToken = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.serviceToken.setObjectName("serviceToken")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken)
|
||||
self.label_loglevel = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_loglevel.setObjectName("label_loglevel")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.logLevelComboBox.setFrame(True)
|
||||
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(0, "DEBUG")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(1, "INFO")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(2, "ERROR")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(3, "FATAL")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.label_host.raise_()
|
||||
self.host.raise_()
|
||||
self.label_serviceToken.raise_()
|
||||
self.serviceToken.raise_()
|
||||
self.validateCertificate.raise_()
|
||||
self.label_security.raise_()
|
||||
self.label_loglevel.raise_()
|
||||
self.logLevelComboBox.raise_()
|
||||
|
||||
self.retranslateUi(UdsActorSetupDialog)
|
||||
self.logLevelComboBox.setCurrentIndex(1)
|
||||
self.closeButton.clicked.connect(UdsActorSetupDialog.finish)
|
||||
self.testButton.clicked.connect(UdsActorSetupDialog.testUDSServer)
|
||||
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
|
||||
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
||||
|
||||
def retranslateUi(self, UdsActorSetupDialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
UdsActorSetupDialog.setWindowTitle(_translate("UdsActorSetupDialog", "UDS Actor Configuration Tool"))
|
||||
self.saveButton.setToolTip(_translate("UdsActorSetupDialog", "Click to register Actor with UDS Broker"))
|
||||
self.saveButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to register Actor with UDS Broker.</p></body></html>"))
|
||||
self.saveButton.setText(_translate("UdsActorSetupDialog", "Save Configuration"))
|
||||
self.closeButton.setToolTip(_translate("UdsActorSetupDialog", "Closes UDS Actor Configuration (discard pending changes if any)"))
|
||||
self.closeButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Exits the UDS Actor Configuration Tool</p></body></html>"))
|
||||
self.closeButton.setText(_translate("UdsActorSetupDialog", "Close"))
|
||||
self.testButton.setToolTip(_translate("UdsActorSetupDialog", "Click to test existing configuration (disabled if no config found)"))
|
||||
self.testButton.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Click on this button to test the server host and assigned toen.</p></body></html>"))
|
||||
self.testButton.setText(_translate("UdsActorSetupDialog", "Test configuration"))
|
||||
self.label_security.setText(_translate("UdsActorSetupDialog", "SSL Validation"))
|
||||
self.validateCertificate.setToolTip(_translate("UdsActorSetupDialog", "Select communication security with broker"))
|
||||
self.validateCertificate.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Select the security for communications with UDS Broker.</p><p>The recommended method of communication is <span style=\" font-weight:600;\">Use SSL</span>, but selection needs to be acording to your broker configuration.</p></body></html>"))
|
||||
self.validateCertificate.setItemText(0, _translate("UdsActorSetupDialog", "Ignore certificate"))
|
||||
self.validateCertificate.setItemText(1, _translate("UdsActorSetupDialog", "Verify certificate"))
|
||||
self.label_host.setText(_translate("UdsActorSetupDialog", "UDS Server"))
|
||||
self.host.setToolTip(_translate("UdsActorSetupDialog", "Uds Broker Server Addres. Use IP or FQDN"))
|
||||
self.host.setWhatsThis(_translate("UdsActorSetupDialog", "Enter here the UDS Broker Addres using either its IP address or its FQDN address"))
|
||||
self.label_serviceToken.setText(_translate("UdsActorSetupDialog", "Service Token"))
|
||||
self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||
self.serviceToken.setWhatsThis(_translate("UdsActorSetupDialog", "<html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html>"))
|
||||
self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level"))
|
||||
from ui import uds_rc
|
195
actor/src/ui/uds_rc.py
Normal file
195
actor/src/ui/uds_rc.py
Normal file
@@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x08\x7c\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\
|
||||
\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\
|
||||
\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x0a\
|
||||
\x04\x07\x27\x0a\x6d\xd5\xd2\x21\x00\x00\x08\x1b\x49\x44\x41\x54\
|
||||
\x68\xde\xed\x9a\x6d\x8c\x54\xd5\x19\xc7\x7f\xe7\xbe\xee\x2e\xfb\
|
||||
\xbe\x3a\x60\x2d\x52\xd6\x62\xac\x52\x23\x68\xc7\x97\xb4\x46\x8c\
|
||||
\xca\x04\x6c\x2d\xad\x56\x4d\x5d\x5a\x9b\x5a\x8d\xfd\x60\xd2\x12\
|
||||
\x9b\x94\x56\xb4\x76\xc4\xa6\x68\xda\x68\xb5\x04\x62\x90\xd1\xb8\
|
||||
\xc1\x12\xab\x06\x98\x6a\x33\x60\x41\x83\x53\x17\x50\x16\x10\xa5\
|
||||
\xb2\x14\xd4\x65\x16\x96\x65\x67\x77\xe6\xce\x7d\x3b\xfd\xb0\xb3\
|
||||
\xeb\xec\xee\xec\xb0\xb3\x77\xfa\xa1\x49\xff\xc9\x64\x66\xce\xb9\
|
||||
\xf7\x9e\xe7\xbf\xcf\xcb\x79\xce\x7f\x16\xfe\xc7\x21\x2a\xfd\xc0\
|
||||
\x78\x32\xa4\x01\x9a\xa2\xa0\x9d\xdf\xaa\x29\x5f\x68\xd2\x94\x23\
|
||||
\xdd\x8e\x78\x27\xe9\xf0\xc1\x87\x0e\xdd\x3d\x3e\x5d\x47\x1c\x5f\
|
||||
\x0c\xad\xec\x00\xd9\x44\xbb\x25\xff\xeb\x04\xe2\xc9\x90\x02\xa8\
|
||||
\x80\x96\x7f\x3f\x07\x58\x04\x2c\x00\xae\x04\xa6\x0f\x5f\xeb\x79\
|
||||
\xe0\x38\x92\x5d\x7b\x6c\xf6\x76\x3a\x9c\xea\xf3\xc9\x1b\x3c\xf2\
|
||||
\x5e\x04\x27\x80\x38\x10\x03\x76\x00\x1e\x60\x9f\x89\x9c\x98\x84\
|
||||
\xe1\x17\x00\xeb\x81\x56\xa0\x2e\x4f\x40\x2b\x76\xad\x94\xb0\x7a\
|
||||
\x6d\x9a\xf4\x80\x44\x4a\x80\xe1\xb5\x25\x82\xcf\xed\x90\x88\x31\
|
||||
\x4b\x17\x35\xc3\x06\xfa\x81\x87\x13\xed\xd6\x53\x65\x11\x88\x27\
|
||||
\x43\x73\x81\x35\xc0\x45\x40\xfd\x64\x3c\x34\x38\x28\xf9\xd3\xea\
|
||||
\x34\x00\x86\xb4\x38\x6a\xdc\x43\xbf\x3a\x1f\x57\x34\xe2\x63\x22\
|
||||
\x45\x01\x67\xcf\xc6\x6c\xa8\x47\xaf\x56\x51\xdc\x5e\x0c\xb7\x9b\
|
||||
\xba\x81\xd7\x69\xe8\x7f\x05\x09\x78\xaa\x39\xd6\xb4\xd3\xc0\xef\
|
||||
\x12\xed\xd6\xca\x33\x12\x88\x27\x43\x6b\x80\x1f\x97\x1b\x8b\x07\
|
||||
\x3e\x70\x78\x6d\xd3\x20\xbd\xfa\xcd\x74\x99\x0f\x20\x51\x11\xb8\
|
||||
\x05\x5e\x18\x0e\x21\x81\x7e\xf6\x2c\x14\x73\x1a\x79\x37\x21\x51\
|
||||
\x90\x8a\x81\x14\x1a\xd5\xd6\x01\x6a\x32\x6f\x51\x3b\x98\xa0\xb9\
|
||||
\xef\x45\xdc\xd1\x64\xfe\x96\x68\xb7\x22\x13\x12\x88\x27\x43\xcf\
|
||||
\x02\x77\x4d\x25\x99\x8e\xa7\x3c\x9e\x59\x57\xc5\xfe\x86\x2d\x28\
|
||||
\x64\x8b\xbb\x5b\xd3\x30\x67\xcc\x99\x54\x6a\x4a\x61\x20\xa4\xc7\
|
||||
\x79\x9f\xdc\x49\x5d\xfa\x15\xa4\xd0\x87\x27\xb7\x27\xda\xad\x6b\
|
||||
\x86\xbf\xa8\xc3\x1f\x36\x6e\x3b\xcb\x30\x74\xf1\x62\xe1\x58\x39\
|
||||
\x68\x6e\x52\x39\xd1\xdf\xc4\xbe\x53\xdf\x42\x88\xf1\x79\x27\x84\
|
||||
\x82\x79\xee\x85\xe3\x3c\x32\x31\x05\x0f\xf0\xe9\x6b\xb8\x15\x44\
|
||||
\x15\xb5\x99\x04\xa0\x00\xcc\x9a\x3d\x57\x7b\xfe\x70\xa7\x7b\x8a\
|
||||
\xe1\x11\x80\xf5\x2f\x9f\x52\xb3\x96\xaf\x4c\xb5\x9c\xd9\xb6\xe4\
|
||||
\x96\x85\x29\x66\x36\x1d\x2d\x3a\x6f\x9c\x7b\x01\x48\xbf\xfc\x32\
|
||||
\x29\x1d\x52\x67\xfd\x9c\xbe\x86\xa5\x85\xc3\xef\x0f\x7f\x18\x31\
|
||||
\xb8\xbe\x56\x91\x1b\xe3\xa7\xa5\xe7\x4d\xb9\x24\xe3\x49\x85\x55\
|
||||
\xb7\xdd\x4b\x8d\x6e\x15\xd6\x26\xb4\xda\x66\x44\x80\x2d\x47\xc8\
|
||||
\x1c\x3d\x2d\xcb\x50\xbd\x91\xe7\xd6\x5c\x77\x7b\x55\xf3\x28\x02\
|
||||
\x40\x4e\x08\x78\x7d\xc7\x00\x9a\x16\x64\x7f\x93\xac\xfe\xc1\x9d\
|
||||
\x05\x61\x24\xd0\x1a\xa7\x07\xde\x6f\x2d\xf3\x2b\xf8\x8a\x39\x2a\
|
||||
\x6a\x47\x11\x88\x45\x1d\x09\x2c\x3c\x79\xca\x65\x57\x67\x06\x3d\
|
||||
\x00\x09\x5d\xcd\xb1\x72\xc9\x32\x6c\xd7\x44\x28\x0a\x28\x0a\xc1\
|
||||
\xe1\x63\x9b\x5f\x2e\x1c\x30\xc7\x7a\x80\x58\xd4\xd9\x2a\x04\xbb\
|
||||
\x3a\x3a\xb3\x7c\x7c\xd4\x0e\xb4\xdc\xec\xd0\x41\xee\xbf\x7e\x15\
|
||||
\x96\x63\x80\x2f\xa9\x04\x14\x3f\x33\x7e\x6c\xec\x40\x2c\xea\x5c\
|
||||
\x66\xe8\x22\xb7\x75\x67\x1a\xcf\x0b\x10\x48\x52\x70\xc3\xc5\xaf\
|
||||
\x71\xfb\xfc\x17\xf0\x5c\xb7\x12\xe6\x53\x65\x1d\x2e\x1c\xc8\x16\
|
||||
\x25\x90\xc7\x52\x55\x11\xbc\xfc\x7a\x1f\x6a\x00\xef\xdb\xae\x49\
|
||||
\xdb\xd7\x57\x73\x51\x55\x3c\xb0\xf9\x86\x7b\x6c\x6c\x01\xee\x9f\
|
||||
\x90\x40\x2c\xea\x6c\x00\x7e\x9f\xb3\x25\x7f\xd9\xd2\x17\x68\x61\
|
||||
\xdb\x35\xf9\xe5\xc2\xfb\x69\xa9\x3e\x1e\xa8\x30\xe8\xf6\x11\x0a\
|
||||
\x8a\xbc\x5f\x92\x40\x9e\xc4\x03\xc0\xa7\x19\x4b\xf2\xee\xde\x6c\
|
||||
\xa0\x3c\x94\xc0\x13\xd7\x5d\x4d\xad\xde\x3f\x75\x0f\xd8\x87\x0a\
|
||||
\xab\x50\x6f\xa2\xdd\xb2\x4b\x12\xc8\xe3\x72\x80\x03\x87\x2c\x3e\
|
||||
\x3d\xee\xa0\x88\x20\x24\x04\x4f\x2f\xbc\x94\x46\xf3\x64\xbe\x1b\
|
||||
\x2d\xe7\x5e\x0d\xd3\x3e\x54\xd8\xf9\xfc\x71\xc2\x24\x1e\xe3\x85\
|
||||
\xcf\x80\x9b\x14\x05\x36\x6f\x4b\xd3\x3f\xe0\x05\x2b\x84\x52\xe5\
|
||||
\xa9\x1b\x2f\xa3\xb5\x7e\x3f\xae\xaf\x3b\x93\xdf\x06\x34\x4c\xfb\
|
||||
\xa3\xc2\x91\xc9\x11\xc8\x93\xd8\x54\x6d\xea\x47\x4c\x43\xb0\xf9\
|
||||
\xcd\x34\xaa\x4a\xa0\x58\x76\x7d\x8d\xe8\xb5\xd7\xf3\xdc\xa2\xd6\
|
||||
\x27\xbb\x0f\xd6\x4d\xcb\x9f\x33\x6e\x03\x36\x03\x5d\x45\xef\x12\
|
||||
\x2a\xc6\xe7\x04\x9c\xfc\xab\xf4\x81\x66\xdf\x91\xc5\x0a\x30\x0f\
|
||||
\x78\xd1\x30\xb4\x39\xab\xd6\xfe\x9d\x8c\x65\x13\x6a\xd1\x58\x78\
|
||||
\x4d\x1d\xbe\x4f\x25\xf0\x78\x24\x9c\x5a\x36\x76\x70\xc6\x8a\xfe\
|
||||
\x5b\x80\x47\x81\x39\x00\xbe\x52\xcb\xdc\x03\xf5\x80\x0b\x30\x00\
|
||||
\x34\x24\xda\x2d\x7f\x42\x02\xfb\x8e\x2c\x5e\x01\xfc\xaa\xf0\xe4\
|
||||
\x65\xdb\x69\x1e\x5b\xf3\x36\xba\x26\x68\x9d\x69\x70\xf5\x65\xd3\
|
||||
\x2a\x75\x8c\xfe\x75\x24\x9c\xfa\x6d\xb1\x89\x19\x2b\xfa\xeb\x80\
|
||||
\xcf\x84\x94\xd3\xbe\xba\xbf\x01\x57\xab\x02\xd8\x9d\x68\xb7\xe6\
|
||||
\x4f\x18\x42\x3f\x7c\xd0\x4c\xbe\xd5\xf1\xf1\x43\x9a\xaa\x68\x02\
|
||||
\x1f\x01\x6c\xda\x7b\x1f\x8f\xbe\x91\x40\x34\xaf\x44\x08\x87\x8f\
|
||||
\x8f\xda\x9c\xec\xf3\x2a\x45\xe0\x91\x78\x32\x74\x47\xb1\x89\xee\
|
||||
\x87\xeb\xd3\x9a\x7b\xac\xb9\x36\xb3\xd5\x2d\x28\xa1\xb1\x92\x27\
|
||||
\xb2\xb6\xe5\xfa\x33\x9e\xe7\xdf\x5b\x53\x05\xad\x97\x6e\xa0\xe3\
|
||||
\xd8\x77\xf0\x25\x28\x02\xa4\x80\xaa\x93\xb7\xa2\xe6\x5e\x45\x4a\
|
||||
\xc9\x1d\xdf\x6c\x44\x55\x2b\x26\x6c\xcc\x8f\x84\x53\xbb\x8b\x4d\
|
||||
\x5c\x75\xd7\x37\xb6\x54\x67\xdf\x89\x80\x42\xa2\xdd\x12\x25\x5b\
|
||||
\x09\x60\xad\xaa\x2a\xe4\x6c\x8f\xdd\x5d\xf3\x86\x2e\x1a\x56\x14\
|
||||
\x24\xe4\x9a\xd6\x01\x43\x2a\xc3\xc6\xf8\xe9\x4a\x2a\x32\xbb\xe2\
|
||||
\xc9\xd0\xf4\x78\x32\x34\xee\x2f\x52\x9d\x4d\xda\x79\x53\x8f\x4d\
|
||||
\xa6\x17\xea\x18\x99\x74\xde\x2a\x52\xd2\xa6\x91\x0d\x75\x81\x74\
|
||||
\x70\x5c\xc9\x3b\x7b\x32\x01\x2b\xd3\x28\xec\x8d\x84\x53\xb2\x68\
|
||||
\x1d\x85\x34\xb0\x7d\x52\xaa\x44\xdb\x72\xfd\x3d\xf0\x2f\xf1\xaa\
|
||||
\x96\x90\x6b\x7a\xa9\xa8\x96\xa1\xa7\x9f\x40\x4f\xff\x02\x5f\x0a\
|
||||
\xae\x9a\x57\xc3\x9c\x59\x26\x95\xe9\x39\xd9\x10\x09\xa7\x6e\x9b\
|
||||
\x7c\x8b\x57\x1c\x3b\x41\x41\x78\x5d\x13\xf6\x06\x4e\xed\xcf\x90\
|
||||
\xea\x6c\x14\x21\xf9\x47\x72\x90\x8c\xe5\x57\xca\x0b\xdf\x8b\x27\
|
||||
\x43\x37\x04\x25\xf0\x18\x80\xe2\x1e\x2a\x29\x7d\x65\xa7\x7f\x08\
|
||||
\xa2\x1e\xd3\x10\x6c\xde\xd6\x1f\xe8\x10\x34\x06\x7f\x0e\x44\x20\
|
||||
\x16\x75\x0e\x03\x5b\xf1\xfb\xd0\x06\xd7\x95\xec\xd2\xac\x96\x37\
|
||||
\x11\xd2\x21\x6b\x49\x36\x6f\xeb\xaf\xcc\xe1\x0b\x5a\xe3\xc9\xd0\
|
||||
\x45\x41\x3c\x00\xf0\x07\x84\x8e\x71\xfa\xbe\xbc\x4c\x39\x41\x7f\
|
||||
\xa3\xcf\xc5\xae\x7b\x08\x21\x1c\x8e\x9f\x70\xf9\xa8\xcb\xae\x94\
|
||||
\x17\xa2\x81\x08\xc4\xa2\xce\xab\x40\x0f\x38\xa8\xb9\x1d\xa5\x7b\
|
||||
\xfe\x86\x15\xf8\xfa\xd7\x10\x02\xde\xee\x18\xc0\xb6\x2b\x92\xce\
|
||||
\x97\x07\xf5\x00\xc0\xb5\x20\x30\x4f\x5c\x8b\x90\x13\xf7\xf2\xc2\
|
||||
\x87\x5c\xcb\x1b\x20\x1d\x14\x45\xf0\xd2\x96\xbe\x52\x2a\xf4\x64\
|
||||
\x51\x5f\x6c\x4f\x28\x8b\x40\x2c\xea\xec\x07\xb6\xa2\xe8\x18\x7d\
|
||||
\x3f\x2d\x99\xd0\x52\x34\x90\x0d\x75\x0e\x37\x5c\x74\x74\x66\x83\
|
||||
\xe6\xc3\xbf\x8a\xef\x09\xe5\x79\x00\xe0\x46\xc0\x57\xb3\xcf\xa3\
|
||||
\x65\x5e\x28\xdd\x2c\x6b\x17\xe3\xd6\x3e\x0a\x48\x3a\x0f\x66\xe9\
|
||||
\x4e\x05\x3a\xcc\x3f\x3d\x39\xc5\x68\x12\x68\x5b\xae\x5f\x02\xbc\
|
||||
\x07\x0e\x56\xcb\x4e\x7c\x23\x3c\xe1\xad\x52\x40\xcd\xf1\xf3\x11\
|
||||
\xde\xbf\x51\x55\x85\x3b\xbf\xdd\x84\xeb\x96\x9d\x13\x27\x23\xe1\
|
||||
\xd4\x59\x95\xc8\x81\xe1\x50\x7a\x1f\xf8\x0d\x18\x54\xf5\x46\x10\
|
||||
\xde\x67\x25\x64\x40\xb0\xce\xfe\x10\x30\x70\x5d\x49\xe2\xed\xf4\
|
||||
\x54\x42\x29\xf0\x46\x56\x8c\xc4\x0a\x90\x1b\x90\x19\xaa\x7b\x2e\
|
||||
\x44\xc8\x74\x89\x7c\x50\xc9\x35\xc5\x10\x38\x74\x7d\xe2\xd0\x7b\
|
||||
\xba\xac\xd6\x7b\x29\xb0\x67\xf2\xa2\x63\x99\x68\x5b\xae\xef\x04\
|
||||
\xae\x40\xba\x64\xcf\xe9\x47\x8a\xda\x09\xaf\x35\x7b\xbf\x8b\x9a\
|
||||
\x7b\x0d\x5d\x17\xdc\x71\x53\x23\xde\x99\xbb\x8d\x45\x91\x70\x6a\
|
||||
\x4b\x79\x72\x57\x99\x88\x45\x9d\x2b\x81\x83\x08\x8d\xea\xee\x46\
|
||||
\x84\xdf\x3d\xe1\xb5\x4e\xc3\xe3\x20\x1d\x6c\x5b\x72\xf8\xcc\x52\
|
||||
\xe5\xc5\xe5\x1a\x3f\x25\x02\x79\x12\x17\x0e\x1d\xc2\x15\xaa\x8f\
|
||||
\xb7\xa2\x59\x7f\x2d\xea\x4b\x4f\xff\x12\xd2\x18\xda\xe0\xb6\xbf\
|
||||
\x3b\x58\xca\xdd\x33\x23\xe1\xd4\xfe\xa9\x09\x8e\x53\x44\x2c\xea\
|
||||
\x2c\x06\x1e\x01\x17\xa3\x77\x09\x46\xdf\xdd\xe3\x9e\x26\x7c\x70\
|
||||
\xcd\xdb\x47\xbe\x0f\x64\xc6\xc5\xd0\x1e\xa0\x36\x12\x4e\x1d\x9b\
|
||||
\xba\x62\x1a\x00\xb1\xa8\xf3\x20\xf0\x13\x84\xee\x68\x99\x67\xa9\
|
||||
\xf9\xa4\x0e\xd5\xda\x34\xe2\x0d\xa9\x80\xe2\x6c\xfd\x5c\xcc\x1c\
|
||||
\xad\x2b\xad\x8b\x84\x53\xf3\x22\xe1\xd4\x60\x10\x1b\x2a\xd2\xff\
|
||||
\xb6\x2d\xd7\x67\x00\xff\x04\xbe\x38\x24\xd9\xd4\x90\x6b\x8c\xa1\
|
||||
\x59\xaf\xa2\x5a\xcf\x01\x43\x3f\xd0\x2d\x5e\x50\x4f\x63\xbd\x6a\
|
||||
\x03\x77\x47\xc2\xa9\xf5\x95\x58\xbb\xa2\xff\x6a\xd0\xb6\x5c\x5f\
|
||||
\x0a\xac\x1d\xb1\x78\xcc\x42\xdf\xbf\xb9\xd9\xf1\xa4\xdf\xb8\xe8\
|
||||
\x8a\x9e\x4c\xa5\xd6\x54\x2a\x49\x20\x16\x75\xd6\x33\xf4\x6b\xfe\
|
||||
\x8f\x46\x19\x2f\x60\xf1\x82\xfa\x65\xaa\x46\x75\x25\x8d\xaf\xb8\
|
||||
\x07\xc6\x78\x43\x07\xce\x93\x12\xf3\xfc\x59\x46\xcf\xc3\xf7\x0c\
|
||||
\xf6\xf0\x7f\x8c\xc7\x7f\x00\xf4\xc5\x17\xbc\x3a\x13\xef\x97\x00\
|
||||
\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
"
|
||||
|
||||
qt_resource_name = b"\
|
||||
\x00\x03\
|
||||
\x00\x00\x70\x37\
|
||||
\x00\x69\
|
||||
\x00\x6d\x00\x67\
|
||||
\x00\x0c\
|
||||
\x09\x57\x90\xa7\
|
||||
\x00\x75\
|
||||
\x00\x64\x00\x73\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
|
||||
"
|
||||
|
||||
qt_resource_struct_v1 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
"
|
||||
|
||||
qt_resource_struct_v2 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x6e\x86\x31\xef\xa3\
|
||||
"
|
||||
|
||||
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
|
||||
if qt_version < [5, 8, 0]:
|
||||
rcc_version = 1
|
||||
qt_resource_struct = qt_resource_struct_v1
|
||||
else:
|
||||
rcc_version = 2
|
||||
qt_resource_struct = qt_resource_struct_v2
|
||||
|
||||
def qInitResources():
|
||||
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
|
||||
def qCleanupResources():
|
||||
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
||||
|
||||
qInitResources()
|
@@ -1,31 +0,0 @@
|
||||
Source: udsactor
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Adolfo Gómez García <agomez@virtualcable.es>
|
||||
Build-Depends: debhelper (>= 7), po-debconf
|
||||
Standards-Version: 3.9.2
|
||||
Homepage: http://www.virtualcable.es
|
||||
|
||||
Package: udsactor
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: policykit-1(>=0.100), python-requests (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python-prctl(>=1.1.1), python (>=2.7), libxss1, ${misc:Depends}
|
||||
Description: Actor for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow this machine to work on an environment managed by UDS Broker.
|
||||
|
||||
Package: udsactor-xrdp
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: xrdp (>= 0.5.0), udsactor (>= ${binary:Version}), libpam-modules-bin (>=1.0), ${misc:Depends}
|
||||
Description: UDS Actor component for xrdp
|
||||
This package provides connection between uds actor and xrdp
|
||||
|
||||
Package: udsactor-nx
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: nxnode (>= 3.5.0), udsactor (>= ${binary:Version}), ${misc:Depends}
|
||||
Description: UDS Actor component for nx
|
||||
This package provides connection between uds actor and nx
|
@@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
pkexec "/usr/sbin/UDSActorConfig" "$@"
|
2
client-py3/full/.env
Normal file
2
client-py3/full/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
PYTHONPATH=./src:${PYTHONPATH}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
/bin
|
||||
/udsclient_*
|
||||
/udsclient*
|
||||
/udsclient-*.tar.gz
|
||||
/*.rpm
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user