mirror of
https://github.com/dkmstr/openuds.git
synced 2025-10-07 15:33:51 +03:00
Compare commits
634 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e7089d9d86 | ||
|
7a367d9011 | ||
|
089b8c82dc | ||
|
df82c3854c | ||
|
f158235f16 | ||
|
fe704c0ba6 | ||
|
8d635b781b | ||
|
00c48dff69 | ||
|
01269e6d3e | ||
|
9478a86b02 | ||
|
86e8c759ec | ||
|
3d421ac38c | ||
|
adec02dc3f | ||
|
43458cbf99 | ||
|
ec2645b0a2 | ||
|
9d0df6cfae | ||
|
cfbce5aef5 | ||
|
cf6820aa2b | ||
|
1a85f60f4f | ||
|
83394f0d34 | ||
|
c34fc41f56 | ||
|
90aa455586 | ||
|
bc2328a239 | ||
|
d9d3bc452c | ||
|
08f14bff57 | ||
|
653bff420f | ||
|
73a3c89e04 | ||
|
adaabf9d83 | ||
|
3cfbdc86e0 | ||
|
ba759b3652 | ||
|
1e3478314b | ||
|
89864b11c2 | ||
|
c6a40ac182 | ||
|
7d9ffca559 | ||
|
f43b9c7bfd | ||
|
972c48ddee | ||
|
118e642700 | ||
|
dfa441871b | ||
|
18c5e3a242 | ||
|
3a4d571a6c | ||
|
3cc42e1e73 | ||
|
ffe9baa9a5 | ||
|
0b05009d3f | ||
|
b34b12ec9f | ||
|
fb70524cb3 | ||
|
4c66401e4f | ||
|
364ebd6f3a | ||
|
493cbbb4e7 | ||
|
5277a74c1c | ||
|
1e01339b93 | ||
|
9343f7c263 | ||
|
7775964d62 | ||
|
a207e8f65f | ||
|
0a0f2771ae | ||
|
ceb5fd9bde | ||
|
7bfa6a6c4f | ||
|
858b79614b | ||
|
45b47ce702 | ||
|
dd98ba5653 | ||
|
0fe5b32224 | ||
|
a0adc1ded3 | ||
|
4f5cc505d3 | ||
|
8bac68b55b | ||
|
b5412e70fd | ||
|
75cd3c4845 | ||
|
e8c45b568d | ||
|
540a2b83be | ||
|
aa4d157c30 | ||
|
69ca93586a | ||
|
cf283bba0f | ||
|
9abaada7cb | ||
|
b359892454 | ||
|
927a86c835 | ||
|
2b5aa9c9a4 | ||
|
b3047e366d | ||
|
d2ef6e3704 | ||
|
5fb4461934 | ||
|
2f5f87e122 | ||
|
d9be83863c | ||
|
5fed04d64d | ||
|
8a2e2deaf1 | ||
|
86990638dc | ||
|
40b9572233 | ||
|
5836b33299 | ||
|
282495ce0f | ||
|
2b33ffc656 | ||
|
e0149900a7 | ||
|
7bed6ac171 | ||
|
0d77e86af2 | ||
|
a179522f4c | ||
|
21c2976d82 | ||
|
ee30ab4604 | ||
|
1fba4d3f9f | ||
|
5084fec43f | ||
|
04e24d406f | ||
|
f58ef9b6d3 | ||
|
18d4147d59 | ||
|
ccd429454e | ||
|
5ce7ddc3a7 | ||
|
3dd73f4723 | ||
|
c3531f3e7e | ||
|
ba7b1c0198 | ||
|
ba90dae5d6 | ||
|
f7cd474264 | ||
|
a255b52628 | ||
|
8d93144e24 | ||
|
27d158f514 | ||
|
2b4e771709 | ||
|
3ebc0dd26f | ||
|
79739bf9b8 | ||
|
f702c144fc | ||
|
ce2d2b1c2e | ||
|
790c204b6a | ||
|
d80cf4052e | ||
|
6a86b0ff04 | ||
|
0d412c4a9a | ||
|
ac9e6dafdf | ||
|
efd0ca3f88 | ||
|
edb4a32496 | ||
|
b239ff6cab | ||
|
d55d1bc619 | ||
|
917a201483 | ||
|
4809252434 | ||
|
8be0d9702a | ||
|
36acb0b0c0 | ||
|
420b78d45d | ||
|
e1ccc62dab | ||
|
6b0d98d4eb | ||
|
7bec7bd7cc | ||
|
270957fab5 | ||
|
47c6ca42f1 | ||
|
c1f6ed376b | ||
|
250ade6aee | ||
|
bde63f7b4f | ||
|
eb4be53508 | ||
|
3003066a91 | ||
|
10805ded7e | ||
|
21c221a6db | ||
|
1857134f42 | ||
|
835dc05e63 | ||
|
4cc4af5bd1 | ||
|
986a82f225 | ||
|
90b64c1721 | ||
|
f403d4ff3e | ||
|
c5071cf348 | ||
|
679956702b | ||
|
98d7a24656 | ||
|
b67771d5f3 | ||
|
672c35c903 | ||
|
6df1bc0a50 | ||
|
01119d1914 | ||
|
a4d1ecb95f | ||
|
237f7e5b77 | ||
|
edb74ab9c6 | ||
|
86eb1a9421 | ||
|
c09ea0eb63 | ||
|
ea79ccbee1 | ||
|
da82a26dd8 | ||
|
c129c83ca0 | ||
|
d8e6de8c1e | ||
|
e0d79cb590 | ||
|
59bd6c1649 | ||
|
564f0e17de | ||
|
842212f186 | ||
|
e4b609c4ce | ||
|
741855030f | ||
|
293b7f02ad | ||
|
fddd54fa99 | ||
|
cd640af37f | ||
|
6f99b63731 | ||
|
6b3355f819 | ||
|
660cfdcd0e | ||
|
47df6c58fc | ||
|
91c90766a3 | ||
|
2a834460d1 | ||
|
5bd77676ca | ||
|
8ef97a7773 | ||
|
abafa7bfac | ||
|
dcb7b3e28e | ||
|
41aa22fadd | ||
|
d02974ad87 | ||
|
b2a067300c | ||
|
afbc75bff0 | ||
|
4c453d2b1f | ||
|
26f33626c2 | ||
|
cb8284d076 | ||
|
ef3dd893d9 | ||
|
d531a1612a | ||
|
de9c06bc2c | ||
|
2400cc99cd | ||
|
7f5c3c3bbd | ||
|
710f2fb0e4 | ||
|
ede23ad793 | ||
|
9a3913cc42 | ||
|
5bf98782ea | ||
|
3a69c9205e | ||
|
3615db877e | ||
|
2286ccaca1 | ||
|
f90bf3a421 | ||
|
df815776da | ||
|
54f7fd21dc | ||
|
8e3d90e7f3 | ||
|
afa9e0aab6 | ||
|
77b0c7c8e1 | ||
|
23afd01004 | ||
|
c30a67d363 | ||
|
aa2d268453 | ||
|
de40c72d9e | ||
|
d0b30b561c | ||
|
e485374836 | ||
|
3934f2b88d | ||
|
c72bcf4200 | ||
|
1b7076e645 | ||
|
e637f208bd | ||
|
75e54618bb | ||
|
04864e3846 | ||
|
a52be141ea | ||
|
afcbd058d1 | ||
|
8285e2daad | ||
|
03bfb3efbb | ||
|
8c4b84e7db | ||
|
4f8fe793cc | ||
|
286b320257 | ||
|
68411f0726 | ||
|
1be49a6e0e | ||
|
c21c0b44ce | ||
|
46aa9139a0 | ||
|
574b19a905 | ||
|
612646bd1c | ||
|
10d9279b89 | ||
|
a8a5063083 | ||
|
29b6613c95 | ||
|
8aa7dc3c6f | ||
|
e75d373d03 | ||
|
91d2398ade | ||
|
f4e953c9c9 | ||
|
f14f36b0d0 | ||
|
d1e51c0103 | ||
|
6fd307e86e | ||
|
51407b54ee | ||
|
91f90c8630 | ||
|
ca5b54c8e2 | ||
|
8d74055357 | ||
|
8e81d51a43 | ||
|
5ff6cdaf69 | ||
|
13cbfe26c7 | ||
|
d497235eeb | ||
|
7d8bcf2168 | ||
|
5706f9d681 | ||
|
cd06597918 | ||
|
49ce5622d6 | ||
|
de5031febf | ||
|
b29baf2a29 | ||
|
aaa909fff0 | ||
|
99ee0b00fc | ||
|
f2643df05f | ||
|
2520cce429 | ||
|
962015c355 | ||
|
582ba01014 | ||
|
eec8588628 | ||
|
37f59e952d | ||
|
46bab75a92 | ||
|
8f7421ef9d | ||
|
a7584f9e8e | ||
|
fad735bb87 | ||
|
5ba704ac8a | ||
|
3c5ef5817f | ||
|
de0db84a5d | ||
|
548b6e813d | ||
|
31b513a7ef | ||
|
fa7ce3de0b | ||
|
3a7e7b8dfc | ||
|
c9488329b9 | ||
|
55c4574021 | ||
|
59179584f2 | ||
|
92de3b01dd | ||
|
c62d62dd65 | ||
|
e02318e665 | ||
|
612ae63cf2 | ||
|
cb44662134 | ||
|
a359ff2263 | ||
|
9ca3a7cdeb | ||
|
1736cae1c1 | ||
|
727ffe0365 | ||
|
b031e0aa3c | ||
|
d7886a1281 | ||
|
09e88b60f5 | ||
|
6af0617c2a | ||
|
1417a66b21 | ||
|
9ba4234313 | ||
|
e85e4c4e54 | ||
|
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 | ||
|
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 | ||
|
1bb258d9dc | ||
|
d5f29bd20f | ||
|
69fe9e0d38 | ||
|
41c94913f8 | ||
|
0dce270a9e | ||
|
594d431af7 | ||
|
71242eba10 | ||
|
2dded06dc5 | ||
|
38490e184e | ||
|
9e462478fc | ||
|
44d8b2b754 | ||
|
5884cde35c | ||
|
3b18597d8e | ||
|
f6ddc7eef1 | ||
|
ed61fbf7b8 | ||
|
fb088ecc02 | ||
|
5396d04555 | ||
|
68b3b50acf | ||
|
9d6560a56e | ||
|
25363269a6 | ||
|
8aca8ead3d | ||
|
f184fa778d | ||
|
21f6df36b0 | ||
|
394ceb9e66 | ||
|
5f8abdfa41 | ||
|
b8af381042 | ||
|
cb92be3c66 | ||
|
8fc5c759d8 | ||
|
6c936a7dfa | ||
|
0cc40198b2 | ||
|
9789a2f868 | ||
|
e8733e74d1 | ||
|
c6d281580b | ||
|
1050ada43b | ||
|
f39bc9c5ba | ||
|
c987915c41 | ||
|
12b8354a8e | ||
|
e7d1df5ba3 | ||
|
e87727b48f | ||
|
13ec1877de | ||
|
1b4060a727 | ||
|
d8e713ad51 | ||
|
cdca39779b | ||
|
265d4f5103 | ||
|
b85a702437 | ||
|
e2d7fb0790 | ||
|
a573d2d55b | ||
|
de50fef63c | ||
|
0133ddc2b5 | ||
|
cddfd735b2 | ||
|
3f6d12c89f | ||
|
98293bba75 | ||
|
07738e3dc2 | ||
|
2b5543905a | ||
|
87c2ea8add | ||
|
2a2a2b2ad0 | ||
|
47ef12ef6a | ||
|
451b8f6fb9 | ||
|
bd2b0cd171 | ||
|
18a8c81af6 | ||
|
09c65b2598 | ||
|
6e438bf4cb | ||
|
7502fe3bcc | ||
|
e9a719a2eb | ||
|
ce73d4e29f | ||
|
ffeaf9e22c | ||
|
1e184a3a34 | ||
|
74d4349266 | ||
|
26c9f0edc8 | ||
|
797a5df4a9 | ||
|
8fbdda7b56 | ||
|
9f04bdab05 | ||
|
5597af7d85 | ||
|
697c3e1c52 | ||
|
ae7f867482 | ||
|
f595219405 | ||
|
d66e59bd50 | ||
|
cc12b7d5f6 | ||
|
d67a9d6ddc | ||
|
5ff3c58149 | ||
|
9340e3c3c1 | ||
|
4357ef3be8 | ||
|
a3905c0c6c | ||
|
03fc488f33 | ||
|
2aee4e9417 | ||
|
b8494f51ac | ||
|
93a12c180e | ||
|
26aa9f6db7 | ||
|
3f881b3e17 | ||
|
0a0b4cb740 | ||
|
5df8f640d8 | ||
|
8c68da806a | ||
|
b9ba304493 | ||
|
52d3ffeac3 | ||
|
868ff2817a | ||
|
51916e0949 | ||
|
e517281c6a | ||
|
c90f9c40fd | ||
|
c6213ff37c | ||
|
3908c875d3 | ||
|
c28c6c7b98 | ||
|
fe3fd6c35b | ||
|
a035633b58 | ||
|
688acb0631 | ||
|
0bc1f72dc8 | ||
|
5d52061041 | ||
|
190079fddc | ||
|
9f44e7fd25 | ||
|
ff685119ae | ||
|
388cb2644b | ||
|
bda4057173 | ||
|
7aec9a116e | ||
|
f57fea4699 | ||
|
d52bc68015 | ||
|
8ab1342775 | ||
|
f602d641a0 | ||
|
3e07cf53e4 | ||
|
2968bc7d41 | ||
|
6a209c0836 | ||
|
9568a9b180 | ||
|
91fcbe7336 | ||
|
2fd5b40809 | ||
|
4e161b15f4 | ||
|
328d35a289 | ||
|
af52727862 | ||
|
672897f828 | ||
|
073ce3df12 | ||
|
09125bb1fa | ||
|
f3e7e21149 | ||
|
348258daf2 | ||
|
49a6e01477 | ||
|
9f2354191c | ||
|
6804982b0b | ||
|
857f8602b8 | ||
|
584dee9fcd | ||
|
e7bf7b0258 | ||
|
46d056de5d | ||
|
92e13c48de | ||
|
d93e5dc566 | ||
|
0b8a9444d1 | ||
|
cea271a2ce | ||
|
d2d190e8a4 | ||
|
5b8ff497fa | ||
|
ae6d36b86a | ||
|
600f50f203 | ||
|
caf1d5d825 | ||
|
99d3393a33 | ||
|
1d06bd02c0 | ||
|
41991590ca | ||
|
4313368f78 | ||
|
50660d92e5 | ||
|
c796f5aaac | ||
|
9e88ff5daa | ||
|
cb5a6f2430 | ||
|
0f87c022f3 | ||
|
69f1c88c3d | ||
|
6fc6fa0fe1 | ||
|
f634d4ef1a | ||
|
f933181369 | ||
|
f0b6726e19 | ||
|
8424c14052 | ||
|
97f709bf52 | ||
|
c26c8d9df9 | ||
|
9f81d0a066 | ||
|
bb626889fb | ||
|
d8fb0deef2 | ||
|
743773e256 | ||
|
4adc058e1a | ||
|
f364b283e6 | ||
|
7e4975be99 | ||
|
a2df121e45 | ||
|
f402dadb0a | ||
|
865601b3c8 | ||
|
0da51dda92 | ||
|
7c9c510ca0 | ||
|
7ae9df21a5 | ||
|
2fd1dc5fc9 | ||
|
a4986d3b4d | ||
|
40abfb6014 | ||
|
d6a8639b18 | ||
|
971e5984d9 | ||
|
e486d6708d | ||
|
f0bd3782d7 | ||
|
7e9dde66ac | ||
|
fa05d9425e | ||
|
75221a4842 | ||
|
4cc11d783a | ||
|
e5a38a65ed | ||
|
dcdea31061 | ||
|
6b3d222a12 | ||
|
8719896f62 | ||
|
57d8b01757 | ||
|
66fb59a13b | ||
|
78372e593a | ||
|
1d7c57eb2f | ||
|
501565c088 | ||
|
f2d55d6141 | ||
|
e9a4da5acc | ||
|
522a5ebfb7 | ||
|
6868e471c5 | ||
|
9e4922ba79 | ||
|
7a377b0065 | ||
|
b830b0ee0a | ||
|
2251618c69 | ||
|
a6876de0b1 | ||
|
58a70e368e | ||
|
50f3b79ee3 | ||
|
4183069cec | ||
|
905b1e7589 | ||
|
024bb5e748 | ||
|
e4345dfefa | ||
|
6c54f8e75a | ||
|
45ca92b77e | ||
|
06b0f1396f | ||
|
c698300096 | ||
|
482cc4b2ae | ||
|
2f67eacfb6 | ||
|
b8e7dc07c3 | ||
|
52f524eb61 | ||
|
637519a162 | ||
|
65b47686db | ||
|
1ab534c3aa | ||
|
8cd5437429 | ||
|
a475d45b7b | ||
|
4f3792ced5 | ||
|
3e061275b4 | ||
|
22415b98cd | ||
|
302b9a85d5 | ||
|
08038b5b90 | ||
|
6d9a6baa2a | ||
|
fb69866f89 | ||
|
74ad50d7d8 | ||
|
00dc4c5a7b | ||
|
a495f36c43 | ||
|
f69a9dbc82 | ||
|
65879f4bd3 | ||
|
a094e1ebee | ||
|
c35dc90264 | ||
|
1be48b99d4 | ||
|
cab09aea9c | ||
|
da23222f0f | ||
|
5d604bb629 | ||
|
13de581b80 | ||
|
76f4df5aa1 | ||
|
8634ce50c6 | ||
|
758f409372 | ||
|
edd6714cd0 | ||
|
a7f9880816 | ||
|
d9bbbb35eb | ||
|
5bf8e74018 | ||
|
f5b59889fc | ||
|
6ced042153 | ||
|
e9f74d9ccc | ||
|
9815e21524 | ||
|
666ae4e1d3 | ||
|
3e8a3efb75 | ||
|
4094818ccc | ||
|
43acedf7f6 | ||
|
37f06617b8 | ||
|
dd39bb4e64 | ||
|
bafd3bc6b3 | ||
|
48e0577e9f | ||
|
cb05113d88 | ||
|
afc4fd39ef | ||
|
33502140cf | ||
|
63280bf9cb | ||
|
1d90f04245 | ||
|
a486e68e39 | ||
|
76e5aede37 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,9 +32,6 @@
|
||||
/client/administration/installer/UDSAdminInstaller/MSChart.exe
|
||||
/client/administration/installer/UDSAdminInstaller/UDSAdminSetup.exe
|
||||
|
||||
# /guacamole-tunnel/
|
||||
/guacamole-tunnel/target
|
||||
|
||||
# /linuxActor/
|
||||
/linuxActor/udsactor_*
|
||||
|
||||
|
@@ -12,5 +12,4 @@ This is an Open Source Source project, initiated by Spanish Company Virtualca
|
||||
|
||||
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.
|
||||
For use, please use the latest stable branch.**
|
||||
**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.**
|
||||
|
2
actor/.env
Normal file
2
actor/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
PYTHONPATH=./src:${PYTHONPATH}
|
||||
|
@@ -1,3 +1,9 @@
|
||||
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
|
||||
|
@@ -10,7 +10,7 @@ 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.4), libxss1, xscreensaver, ${misc:Depends}
|
||||
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.
|
||||
@@ -19,7 +19,7 @@ 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.4), libxss1, xscreensaver, ${misc:Depends}
|
||||
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.
|
||||
|
@@ -1,3 +1,3 @@
|
||||
udsactor-unmanaged_3.0.0_all.deb admin optional
|
||||
udsactor_3.0.0_all.deb admin optional
|
||||
udsactor_3.0.0_amd64.buildinfo admin optional
|
||||
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
|
||||
|
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_config.py $@
|
||||
exec python3 actor_config.py -platform xcb $@
|
||||
|
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_config_unmanaged.py $@
|
||||
exec python3 actor_config_unmanaged.py -platform xcb $@
|
||||
|
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
exec python3 actor_client.py $@
|
||||
exec python3 -s actor_client.py -platform xcb $@
|
||||
|
@@ -67,9 +67,9 @@ if __name__ == "__main__":
|
||||
# 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)
|
||||
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
|
||||
|
||||
qApp.exec_()
|
||||
qApp.exec()
|
||||
|
||||
# On windows, if no window is created, this point will never be reached.
|
||||
qApp.end()
|
||||
|
@@ -187,9 +187,9 @@ if __name__ == "__main__":
|
||||
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)
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec())
|
||||
|
@@ -40,6 +40,7 @@ import PyQt5 # pylint: disable=unused-import
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
|
||||
import udsactor
|
||||
import udsactor.tools
|
||||
|
||||
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
|
||||
|
||||
@@ -49,6 +50,7 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger('actor')
|
||||
|
||||
|
||||
class UDSConfigDialog(QDialog):
|
||||
_host: str = ''
|
||||
_config: udsactor.types.ActorConfigurationType
|
||||
@@ -60,65 +62,99 @@ class UDSConfigDialog(QDialog):
|
||||
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.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.serviceToken.setText(self._config.master_token or '')
|
||||
self.ui.restrictNet.setText(self._config.restrict_net or '')
|
||||
|
||||
self.ui.testButton.setEnabled(bool(self._config.master_token and self._config.host))
|
||||
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)
|
||||
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)
|
||||
self.ui.testButton.setEnabled(
|
||||
self.ui.host.text() == self._config.host
|
||||
and self.ui.serviceToken.text() == self._config.master_token
|
||||
and self.ui.restrictNet.text() == self._config.restrict_net
|
||||
)
|
||||
|
||||
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)
|
||||
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
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'UDS Test',
|
||||
'Configuration for {} seems to be correct.'.format(self._config.host),
|
||||
QMessageBox.Ok
|
||||
'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
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
|
||||
def saveConfig(self) -> None:
|
||||
# Ensure restrict_net is empty or a valid subnet
|
||||
restrictNet = self.ui.restrictNet.text().strip()
|
||||
if restrictNet:
|
||||
try:
|
||||
subnet = udsactor.tools.strToNoIPV4Network(restrictNet)
|
||||
if not subnet:
|
||||
raise Exception('Invalid subnet')
|
||||
except Exception:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
'Invalid subnet',
|
||||
'Invalid subnet {}. Please, check it.'.format(restrictNet),
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
return
|
||||
|
||||
# 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()
|
||||
master_token=self.ui.serviceToken.text().strip(),
|
||||
restrict_net=restrictNet,
|
||||
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)
|
||||
QMessageBox.information(
|
||||
self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -127,9 +163,9 @@ if __name__ == "__main__":
|
||||
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)
|
||||
QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
@@ -153,4 +189,4 @@ if __name__ == "__main__":
|
||||
|
||||
myapp = UDSConfigDialog()
|
||||
myapp.show()
|
||||
sys.exit(app.exec_())
|
||||
sys.exit(app.exec())
|
||||
|
@@ -10,8 +10,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>595</width>
|
||||
<height>220</height>
|
||||
<width>601</width>
|
||||
<height>243</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -55,7 +55,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -83,7 +83,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>410</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>171</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -117,7 +117,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>180</y>
|
||||
<y>210</y>
|
||||
<width>181</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
@@ -144,7 +144,7 @@
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>571</width>
|
||||
<height>161</height>
|
||||
<height>191</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
@@ -221,14 +221,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_loglevel">
|
||||
<property name="text">
|
||||
<string>Log Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="logLevelComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
@@ -258,6 +258,23 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_restrictNet">
|
||||
<property name="text">
|
||||
<string>Restrict Net</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="restrictNet">
|
||||
<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>
|
||||
</layout>
|
||||
<zorder>label_host</zorder>
|
||||
<zorder>host</zorder>
|
||||
@@ -267,6 +284,8 @@
|
||||
<zorder>label_security</zorder>
|
||||
<zorder>label_loglevel</zorder>
|
||||
<zorder>logLevelComboBox</zorder>
|
||||
<zorder>label_restrictNet</zorder>
|
||||
<zorder>restrictNet</zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
@@ -353,6 +372,22 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>restrictNet</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>UdsActorSetupDialog</receiver>
|
||||
<slot>configChanged()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>139</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>295</x>
|
||||
<y>121</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>finish()</slot>
|
||||
|
@@ -65,9 +65,9 @@ class UDSClientQApp(QApplication):
|
||||
self._initialized = False
|
||||
|
||||
# This will be invoked on session close
|
||||
self.commitDataRequest.connect(self.end) # Will be invoked on session close, to gracely close app
|
||||
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)
|
||||
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)
|
||||
@@ -94,7 +94,7 @@ class UDSClientQApp(QApplication):
|
||||
self._app.join()
|
||||
|
||||
def showMessage(self, message: str) -> None:
|
||||
QMessageBox.information(None, 'Message', message)
|
||||
QMessageBox.information(None, 'Message', message) # type: ignore
|
||||
|
||||
def setMainWindow(self, mw: 'QMainWindow'):
|
||||
self._mainWindow = mw
|
||||
@@ -108,6 +108,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
_listener: client.HTTPServerThread
|
||||
_loginInfo: typing.Optional['types.LoginResultInfoType']
|
||||
_notified: bool
|
||||
_notifiedDeadline: bool
|
||||
_sessionStartTime: datetime.datetime
|
||||
api: rest.UDSClientApi
|
||||
|
||||
@@ -115,13 +116,14 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
super().__init__()
|
||||
|
||||
self.api = rest.UDSClientApi() # Self initialized
|
||||
self._qApp = qApp
|
||||
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...')
|
||||
@@ -139,8 +141,8 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
remainingTime = self._loginInfo.dead_line - (datetime.datetime.now() - self._sessionStartTime).total_seconds()
|
||||
logger.debug('Remaining time: {}'.format(remainingTime))
|
||||
|
||||
if not self._notified and remainingTime < 300: # With five minutes, show a warning message
|
||||
self._notified = True
|
||||
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
|
||||
|
||||
@@ -183,7 +185,8 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
|
||||
try:
|
||||
# Notify loging and mark it
|
||||
self._loginInfo = self.api.login(platform.operations.getCurrentUser(), platform.operations.getSessionType())
|
||||
user, sessionType = platform.operations.getCurrentUser(), platform.operations.getSessionType()
|
||||
self._loginInfo = self.api.login(user, sessionType)
|
||||
|
||||
if self._loginInfo.max_idle:
|
||||
platform.operations.initIdleDuration(self._loginInfo.max_idle)
|
||||
@@ -195,8 +198,11 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
|
||||
time.sleep(1.3) # Sleeps between loop iterations
|
||||
|
||||
self.api.logout(user + self._extraLogoff, sessionType)
|
||||
logger.info('Notified logout for %s (%s)', user, sessionType) # Log logout
|
||||
|
||||
# Clean up login info
|
||||
self._loginInfo = None
|
||||
self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff)
|
||||
except Exception as e:
|
||||
logger.error('Error on client loop: %s', e)
|
||||
|
||||
@@ -210,7 +216,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
platform.operations.loggoff()
|
||||
|
||||
def _showMessage(self, message: str) -> None:
|
||||
self._qApp.message.emit(message)
|
||||
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')
|
||||
@@ -230,13 +236,13 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att
|
||||
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)
|
||||
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()
|
||||
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
|
||||
|
||||
|
@@ -42,7 +42,7 @@ class LocalProvider(handler.Handler):
|
||||
return result._asdict()
|
||||
|
||||
def post_logout(self) -> typing.Any:
|
||||
self._service.logout(self._params['username'])
|
||||
self._service.logout(self._params['username'], self._params['session_type'])
|
||||
return 'ok'
|
||||
|
||||
def post_ping(self) -> typing.Any:
|
||||
|
@@ -38,6 +38,7 @@ 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')
|
||||
@@ -51,7 +52,9 @@ class PublicProvider(handler.Handler):
|
||||
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
|
||||
self._service._clientsPool.message(
|
||||
self._params['message']
|
||||
) # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
||||
def post_script(self) -> typing.Any:
|
||||
@@ -60,7 +63,9 @@ class PublicProvider(handler.Handler):
|
||||
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
|
||||
self._service._clientsPool.executeScript(
|
||||
self._params['script']
|
||||
) # pylint: disable=protected-access
|
||||
else:
|
||||
# Execute script at server space, that is, here
|
||||
# as a parallel thread
|
||||
@@ -72,14 +77,22 @@ class PublicProvider(handler.Handler):
|
||||
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'))
|
||||
return self._service.preConnect(
|
||||
self._params['user'],
|
||||
self._params['protocol'],
|
||||
self._params.get('ip', 'unknown'),
|
||||
self._params.get('hostname', 'unknown'),
|
||||
self._params.get('udsuser', '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
|
||||
return (
|
||||
self._service._clientsPool.screenshot()
|
||||
) # pylint: disable=protected-access
|
||||
|
||||
def get_uuid(self) -> typing.Any:
|
||||
if self._service.isManaged():
|
||||
|
@@ -159,7 +159,7 @@ class HTTPServerThread(threading.Thread):
|
||||
# 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.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)
|
||||
|
||||
|
@@ -1 +0,0 @@
|
||||
VERSION = '3.0.0'
|
@@ -37,6 +37,7 @@ import typing
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
serviceLogger = False
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
|
@@ -91,10 +91,10 @@ def _getInterfaces() -> typing.List[str]:
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
names = array.array(str('B'), b'\0' * space)
|
||||
outbytes = struct.unpack(str('iL'), fcntl.ioctl(
|
||||
outbytes = struct.unpack('iL', fcntl.ioctl(
|
||||
s.fileno(),
|
||||
0x8912, # SIOCGIFCONF
|
||||
struct.pack(str('iL'), space, names.buffer_info()[0])
|
||||
struct.pack('iL', space, names.buffer_info()[0])
|
||||
))[0]
|
||||
namestr = names.tobytes()
|
||||
# return namestr, outbytes
|
||||
@@ -155,7 +155,7 @@ def renameComputer(newName: str) -> bool:
|
||||
Returns True if reboot needed
|
||||
'''
|
||||
rename(newName)
|
||||
return True # Always reboot right now. Not much slower but much more better
|
||||
return True # Always reboot right now. Not much slower but much more convenient
|
||||
|
||||
|
||||
def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):
|
||||
@@ -186,9 +186,9 @@ def getCurrentUser() -> str:
|
||||
def getSessionType() -> str:
|
||||
'''
|
||||
Known values:
|
||||
* Unknown -> No SESSIONNAME environment variable
|
||||
* Console -> Local session
|
||||
* RDP-Tcp#[0-9]+ -> RDP Session
|
||||
* 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')
|
||||
|
||||
|
@@ -56,6 +56,7 @@ def readConfig() -> types.ActorConfigurationType:
|
||||
validateCertificate=uds.getboolean('validate', fallback=False),
|
||||
master_token=uds.get('master_token', None),
|
||||
own_token=uds.get('own_token', None),
|
||||
restrict_net=uds.get('restrict_net', None),
|
||||
pre_command=uds.get('pre_command', None),
|
||||
runonce_command=uds.get('runonce_command', None),
|
||||
post_command=uds.get('post_command', None),
|
||||
@@ -78,6 +79,7 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
writeIfValue(config.actorType, 'type')
|
||||
writeIfValue(config.master_token, 'master_token')
|
||||
writeIfValue(config.own_token, 'own_token')
|
||||
writeIfValue(config.restrict_net, 'restrict_net')
|
||||
writeIfValue(config.pre_command, 'pre_command')
|
||||
writeIfValue(config.post_command, 'post_command')
|
||||
writeIfValue(config.runonce_command, 'runonce_command')
|
||||
@@ -100,3 +102,6 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
|
||||
def useOldJoinSystem() -> bool:
|
||||
return False
|
||||
|
||||
def invokeScriptOnLogin() -> str:
|
||||
return ''
|
||||
|
@@ -37,41 +37,51 @@ import typing
|
||||
import requests
|
||||
|
||||
from . import types
|
||||
from .info import VERSION
|
||||
from .version import VERSION
|
||||
|
||||
# Default public listen port
|
||||
LISTEN_PORT = 43910
|
||||
|
||||
# Default timeout
|
||||
TIMEOUT = 5 # 5 seconds is more than enought
|
||||
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,
|
||||
}
|
||||
|
||||
UDS_BASE_URL = 'https://{}/uds/rest/'
|
||||
|
||||
#
|
||||
# Basic UDS Api
|
||||
#
|
||||
@@ -79,6 +89,7 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base for remote api accesses
|
||||
"""
|
||||
|
||||
_host: str
|
||||
_validateCert: bool
|
||||
_url: str
|
||||
@@ -86,12 +97,12 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
def __init__(self, host: str, validateCert: bool) -> None:
|
||||
self._host = host
|
||||
self._validateCert = validateCert
|
||||
self._url = "https://{}/uds/rest/".format(self._host)
|
||||
self._url = UDS_BASE_URL.format(self._host)
|
||||
# Disable logging requests messages except for errors, ...
|
||||
logging.getLogger("requests").setLevel(logging.CRITICAL)
|
||||
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
logging.getLogger('request').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
try:
|
||||
warnings.simplefilter("ignore") # Disables all warnings
|
||||
warnings.simplefilter('ignore') # Disables all warnings
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -99,19 +110,19 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
def _headers(self) -> typing.MutableMapping[str, str]:
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'UDS Actor v{}'.format(VERSION)
|
||||
'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:
|
||||
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(
|
||||
@@ -120,7 +131,9 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
headers=headers,
|
||||
verify=self._validateCert,
|
||||
timeout=TIMEOUT,
|
||||
proxies=NO_PROXY if disableProxy else None # if not proxies wanted, enforce it
|
||||
proxies=NO_PROXY # type: ignore
|
||||
if disableProxy
|
||||
else None, # if not proxies wanted, enforce it
|
||||
)
|
||||
|
||||
if result.ok:
|
||||
@@ -139,6 +152,7 @@ class UDSApi: # pylint: disable=too-few-public-methods
|
||||
|
||||
raise RESTError(data)
|
||||
|
||||
|
||||
#
|
||||
# UDS Broker API access
|
||||
#
|
||||
@@ -148,7 +162,12 @@ class UDSServerApi(UDSApi):
|
||||
|
||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||
try:
|
||||
result = requests.get(self._url + 'auth/auths', headers=self._headers, verify=self._validateCert, timeout=4)
|
||||
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(
|
||||
@@ -157,24 +176,24 @@ class UDSServerApi(UDSApi):
|
||||
auth=v['auth'],
|
||||
type=v['type'],
|
||||
priority=v['priority'],
|
||||
isCustom=v['isCustom']
|
||||
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:
|
||||
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"
|
||||
"""
|
||||
@@ -186,7 +205,7 @@ class UDSServerApi(UDSApi):
|
||||
'pre_command': preCommand,
|
||||
'run_once_command': runOnceCommand,
|
||||
'post_command': postCommand,
|
||||
'log_level': logLevel
|
||||
'log_level': logLevel,
|
||||
}
|
||||
|
||||
# First, try to login to REST api
|
||||
@@ -194,13 +213,23 @@ class UDSServerApi(UDSApi):
|
||||
# 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)
|
||||
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)
|
||||
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:
|
||||
@@ -212,13 +241,18 @@ class UDSServerApi(UDSApi):
|
||||
|
||||
raise RESTError(result.content.decode())
|
||||
|
||||
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType], actorType: typing.Optional[str]) -> types.InitializationResultType:
|
||||
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': actorType or types.MANAGED,
|
||||
'type': actor_type or types.MANAGED,
|
||||
'token': token,
|
||||
'version': VERSION,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
}
|
||||
r = self._doPost('initialize', payload)
|
||||
os = r['os']
|
||||
@@ -232,95 +266,111 @@ class UDSServerApi(UDSApi):
|
||||
password=os.get('password'),
|
||||
new_password=os.get('new_password'),
|
||||
ad=os.get('ad'),
|
||||
ou=os.get('ou')
|
||||
) if r['os'] else None
|
||||
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
|
||||
}
|
||||
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']
|
||||
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
|
||||
}
|
||||
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']
|
||||
password=result['password'],
|
||||
)
|
||||
|
||||
def notifyUnmanagedCallback(self, master_token: str, secret: str, interfaces: typing.Iterable[types.InterfaceInfoType], port: int) -> types.CertificateInfoType:
|
||||
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
|
||||
'port': port,
|
||||
}
|
||||
result = self._doPost('unmanaged', payload)
|
||||
|
||||
return types.CertificateInfoType(
|
||||
private_key=result['private_key'],
|
||||
server_certificate=result['server_certificate'],
|
||||
password=result['password']
|
||||
password=result['password'],
|
||||
)
|
||||
|
||||
|
||||
def login(self, own_token: str, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
if not own_token:
|
||||
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
|
||||
ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None
|
||||
)
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'type': actor_type or types.MANAGED,
|
||||
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
|
||||
'token': token,
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN
|
||||
'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']
|
||||
max_idle=result['max_idle'],
|
||||
)
|
||||
|
||||
def logout(self, own_token: str, username: str) -> None:
|
||||
if not own_token:
|
||||
return
|
||||
def logout(
|
||||
self,
|
||||
actor_type: typing.Optional[str],
|
||||
token: str,
|
||||
username: str,
|
||||
sessionType: str,
|
||||
interfaces: typing.Iterable[types.InterfaceInfoType],
|
||||
secret: typing.Optional[str],
|
||||
) -> typing.Optional[str]:
|
||||
if not token:
|
||||
return None
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'username': username
|
||||
'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 '',
|
||||
}
|
||||
self._doPost('logout', payload)
|
||||
|
||||
return self._doPost('logout', payload) # Can be 'ok' or 'notified'
|
||||
|
||||
def log(self, own_token: str, level: int, message: str) -> None:
|
||||
if not own_token:
|
||||
return
|
||||
payLoad = {
|
||||
'token': own_token,
|
||||
'level': level,
|
||||
'message': message
|
||||
}
|
||||
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:
|
||||
@@ -341,25 +391,23 @@ class UDSClientApi(UDSApi):
|
||||
return self._url + method
|
||||
|
||||
def post(
|
||||
self,
|
||||
method: str, # i.e. 'initialize', 'ready', ....
|
||||
payLoad: typing.MutableMapping[str, typing.Any]
|
||||
) -> typing.Any:
|
||||
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
|
||||
}
|
||||
payLoad = {'callback_url': callbackUrl}
|
||||
self.post('register', payLoad)
|
||||
|
||||
def unregister(self, callbackUrl: str) -> None:
|
||||
payLoad = {
|
||||
'callback_url': callbackUrl
|
||||
}
|
||||
payLoad = {'callback_url': callbackUrl}
|
||||
self.post('unregister', payLoad)
|
||||
|
||||
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
|
||||
def login(
|
||||
self, username: str, sessionType: typing.Optional[str] = None
|
||||
) -> types.LoginResultInfoType:
|
||||
payLoad = {
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN,
|
||||
@@ -369,12 +417,13 @@ class UDSClientApi(UDSApi):
|
||||
ip=result['ip'],
|
||||
hostname=result['hostname'],
|
||||
dead_line=result['dead_line'],
|
||||
max_idle=result['max_idle']
|
||||
max_idle=result['max_idle'],
|
||||
)
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
def logout(self, username: str, sessionType: typing.Optional[str]) -> None:
|
||||
payLoad = {
|
||||
'username': username
|
||||
'username': username,
|
||||
'session_type': sessionType or UNKNOWN
|
||||
}
|
||||
self.post('logout', payLoad)
|
||||
|
||||
|
@@ -39,6 +39,7 @@ import typing
|
||||
from . import platform
|
||||
from . import rest
|
||||
from . import types
|
||||
from . import tools
|
||||
|
||||
from .log import logger, DEBUG, INFO, ERROR, FATAL
|
||||
from .http import clients_pool, server, cert
|
||||
@@ -55,6 +56,7 @@ from .http import clients_pool, server, cert
|
||||
# else:
|
||||
# logger.setLevel(20000)
|
||||
|
||||
|
||||
class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
_isAlive: bool = True
|
||||
_rebootRequested: bool = False
|
||||
@@ -75,7 +77,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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))
|
||||
logger.error(
|
||||
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
|
||||
)
|
||||
return False
|
||||
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
|
||||
return True
|
||||
@@ -86,7 +90,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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._certificate = (
|
||||
cert.defaultCertificate
|
||||
) # For being used on "unmanaged" hosts only
|
||||
self._http = None
|
||||
|
||||
# Initialzies loglevel and serviceLogger
|
||||
@@ -112,16 +118,24 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
self._http.start()
|
||||
|
||||
def isManaged(self) -> bool:
|
||||
return self._cfg.actorType != types.UNMANAGED # Only "unmanaged" hosts are unmanaged, the rest are "managed"
|
||||
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]:
|
||||
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
|
||||
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)
|
||||
return next(
|
||||
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
|
||||
)
|
||||
except StopIteration:
|
||||
return interfaces[0]
|
||||
|
||||
@@ -152,7 +166,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
while self._isAlive:
|
||||
counter -= 1
|
||||
try:
|
||||
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
|
||||
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
|
||||
@@ -168,7 +187,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
# 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)')
|
||||
logger.error(
|
||||
'Could not locate IP address!!!. (Not registered with UDS)'
|
||||
)
|
||||
|
||||
# Do not continue if not alive...
|
||||
if not self._isAlive:
|
||||
@@ -176,7 +197,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
# Cleans sensible data
|
||||
if self._cfg.config:
|
||||
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
|
||||
self._cfg = self._cfg._replace(
|
||||
config=self._cfg.config._replace(os=None), data=None
|
||||
)
|
||||
platform.store.writeConfig(self._cfg)
|
||||
|
||||
logger.info('Service ready')
|
||||
@@ -195,10 +218,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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...
|
||||
# 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
|
||||
@@ -208,9 +231,20 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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)
|
||||
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 '')
|
||||
self.joinDomain(
|
||||
osData.name,
|
||||
osData.ad or '',
|
||||
osData.ou or '',
|
||||
osData.username or '',
|
||||
osData.password or '',
|
||||
)
|
||||
|
||||
if self._rebootRequested:
|
||||
try:
|
||||
@@ -234,7 +268,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -245,13 +284,17 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
return
|
||||
|
||||
while self._isAlive:
|
||||
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||
self._interfaces = tools.validNetworkCards(
|
||||
self._cfg.restrict_net, 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
|
||||
if (
|
||||
self._initialized or not self._cfg.host or not self._isAlive
|
||||
): # Not configured or not running
|
||||
return False
|
||||
|
||||
self._initialized = True
|
||||
@@ -268,20 +311,25 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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)
|
||||
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))
|
||||
logger.debug(
|
||||
'This host is not managed by UDS Broker (ids: {})'.format(
|
||||
self._interfaces
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
# Only removes token for managed machines
|
||||
# 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
|
||||
)
|
||||
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)
|
||||
@@ -294,17 +342,28 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
break # Initial configuration done..
|
||||
except rest.RESTConnectionError as e:
|
||||
logger.info('Trying to inititialize connection with broker (last error: {})'.format(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))
|
||||
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
|
||||
self._cfg = self._cfg._replace(
|
||||
own_token=None
|
||||
) # Ensures assigned token is cleared
|
||||
|
||||
def finish(self) -> None:
|
||||
if self._http:
|
||||
@@ -314,7 +373,14 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
if self._loggedIn and self._cfg.own_token:
|
||||
self._loggedIn = False
|
||||
try:
|
||||
self._api.logout(self._cfg.own_token, '')
|
||||
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)
|
||||
|
||||
@@ -325,19 +391,33 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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:
|
||||
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())
|
||||
currentInterfaces = tools.validNetworkCards(
|
||||
self._cfg.restrict_net, 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))
|
||||
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)
|
||||
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))
|
||||
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:
|
||||
@@ -345,29 +425,34 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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:
|
||||
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)
|
||||
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)))
|
||||
# Logs error, but continue renaming computer
|
||||
logger.error(
|
||||
'Could not change password for user {}: {}'.format(userName, e)
|
||||
)
|
||||
|
||||
if hostName.lower() == name.lower():
|
||||
logger.info('Computer name is already {}'.format(hostName))
|
||||
return
|
||||
|
||||
if platform.operations.renameComputer(name):
|
||||
self.reboot()
|
||||
@@ -380,7 +465,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
|
||||
# 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')
|
||||
self.logout('client_unavailable', '')
|
||||
except Exception as e:
|
||||
logger.error('Exception on main service loop: %s', e)
|
||||
|
||||
@@ -388,13 +473,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
# 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:
|
||||
self, name: str, domain: str, ou: str, account: str, password: str
|
||||
) -> None:
|
||||
'''
|
||||
Invoked when broker requests a "domain" action
|
||||
default does nothing
|
||||
@@ -402,22 +482,71 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
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
|
||||
def login(
|
||||
self, username: str, sessionType: typing.Optional[str] = None
|
||||
) -> types.LoginResultInfoType:
|
||||
result = types.LoginResultInfoType(
|
||||
ip='', hostname='', dead_line=None, max_idle=None
|
||||
)
|
||||
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()
|
||||
self._initialized = (
|
||||
self.initialize()
|
||||
) # Maybe it's a local login by an unmanaged host.... On real login, will execute initilize again
|
||||
# Unamanaged, need the master token
|
||||
master_token = self._cfg.master_token
|
||||
secret = self._secret
|
||||
|
||||
if self._cfg.own_token:
|
||||
result = self._api.login(self._cfg.own_token, username, sessionType)
|
||||
# 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,
|
||||
)
|
||||
|
||||
if result.logged_in:
|
||||
logger.debug('Login successful')
|
||||
self._loggedIn = True
|
||||
script = platform.store.invokeScriptOnLogin()
|
||||
if script:
|
||||
logger.info('Executing script on login: {}'.format(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
|
||||
if self._cfg.own_token:
|
||||
self._api.logout(self._cfg.own_token, username)
|
||||
def logout(self, username: str, sessionType: typing.Optional[str]) -> None:
|
||||
master_token = self._cfg.master_token
|
||||
|
||||
# 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:
|
||||
# If logout is not processed (that is, not ok result), the logout has not been processed
|
||||
if (
|
||||
self._api.logout(
|
||||
self._cfg.actorType,
|
||||
token,
|
||||
username,
|
||||
sessionType or '',
|
||||
self._interfaces,
|
||||
self._secret,
|
||||
)
|
||||
!= 'ok'
|
||||
):
|
||||
logger.info('Logout from %s ignored as required by uds broker', username)
|
||||
return
|
||||
|
||||
self._loggedIn = False
|
||||
self.onLogout(username)
|
||||
|
||||
if not self.isManaged():
|
||||
@@ -444,13 +573,25 @@ class CommonService: # pylint: disable=too-many-instance-attributes
|
||||
'''
|
||||
logger.info('Service stopped')
|
||||
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str: # pylint: disable=unused-argument
|
||||
def preConnect(
|
||||
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
|
||||
) -> str:
|
||||
'''
|
||||
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')
|
||||
self.execute(
|
||||
self._cfg.pre_command
|
||||
+ ' {} {} {} {} {}'.format(
|
||||
userName.replace('"', '%22'),
|
||||
protocol,
|
||||
ip,
|
||||
hostname,
|
||||
udsUserName.replace('"', '%22'),
|
||||
),
|
||||
'preConnect',
|
||||
)
|
||||
|
||||
return 'ok'
|
||||
|
||||
|
@@ -28,20 +28,58 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=invalid-name
|
||||
import threading
|
||||
import ipaddress
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from udsactor.types import InterfaceInfoType
|
||||
|
||||
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:
|
||||
from udsactor.log import logger
|
||||
|
||||
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()
|
||||
|
||||
|
||||
# Convert "X.X.X.X/X" to ipaddress.IPv4Network
|
||||
def strToNoIPV4Network(net: typing.Optional[str]) -> typing.Optional[ipaddress.IPv4Network]:
|
||||
if not net: # Empty or None
|
||||
return None
|
||||
try:
|
||||
return ipaddress.IPv4Interface(net).network
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def validNetworkCards(
|
||||
net: typing.Optional[str], cards: typing.Iterable['InterfaceInfoType']
|
||||
) -> typing.List['InterfaceInfoType']:
|
||||
try:
|
||||
subnet = strToNoIPV4Network(net)
|
||||
except Exception as e:
|
||||
subnet = None
|
||||
|
||||
if subnet is None:
|
||||
return list(cards)
|
||||
|
||||
def isValid(ip: str, subnet: ipaddress.IPv4Network) -> bool:
|
||||
if not ip:
|
||||
return False
|
||||
try:
|
||||
return ipaddress.IPv4Address(ip) in subnet
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return [c for c in cards if isValid(c.ip, subnet)]
|
||||
|
@@ -35,6 +35,7 @@ class ActorConfigurationType(typing.NamedTuple):
|
||||
actorType: typing.Optional[str] = None
|
||||
master_token: typing.Optional[str] = None
|
||||
own_token: typing.Optional[str] = None
|
||||
restrict_net: typing.Optional[str] = None
|
||||
|
||||
pre_command: typing.Optional[str] = None
|
||||
runonce_command: typing.Optional[str] = None
|
||||
@@ -57,6 +58,10 @@ class LoginResultInfoType(typing.NamedTuple):
|
||||
dead_line: typing.Optional[int]
|
||||
max_idle: typing.Optional[int] # Not provided by broker
|
||||
|
||||
@property
|
||||
def logged_in(self) -> bool:
|
||||
return self.hostname != '' or self.ip != ''
|
||||
|
||||
class CertificateInfoType(typing.NamedTuple):
|
||||
private_key: str
|
||||
server_certificate: str
|
||||
|
1
actor/src/udsactor/version.py
Normal file
1
actor/src/udsactor/version.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION = '3.5.0'
|
@@ -34,7 +34,7 @@ import os
|
||||
import tempfile
|
||||
import typing
|
||||
|
||||
import servicemanager # pylint: disable=import-error
|
||||
import servicemanager
|
||||
|
||||
# Valid logging levels, from UDS Broker (uds.core.utils.log).
|
||||
from .. import loglevel
|
||||
@@ -42,6 +42,7 @@ from .. import loglevel
|
||||
class LocalLogger: # pylint: disable=too-few-public-methods
|
||||
linux = False
|
||||
windows = True
|
||||
serviceLogger = False
|
||||
|
||||
logger: typing.Optional[logging.Logger]
|
||||
|
||||
|
@@ -41,6 +41,8 @@ from .service import UDSActorSvc
|
||||
def setupRecoverService():
|
||||
svc_name = UDSActorSvc._svc_name_ # pylint: disable=protected-access
|
||||
|
||||
hs = None
|
||||
hscm = None
|
||||
try:
|
||||
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
|
||||
|
||||
@@ -57,9 +59,11 @@ def setupRecoverService():
|
||||
}
|
||||
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hs)
|
||||
if hs:
|
||||
win32service.CloseServiceHandle(hs)
|
||||
finally:
|
||||
win32service.CloseServiceHandle(hscm)
|
||||
if hscm:
|
||||
win32service.CloseServiceHandle(hscm)
|
||||
|
||||
|
||||
def run() -> None:
|
||||
|
@@ -39,6 +39,7 @@ import win32net
|
||||
import win32event
|
||||
import pythoncom
|
||||
import servicemanager
|
||||
import winreg as wreg
|
||||
|
||||
from . import operations
|
||||
from . import store
|
||||
@@ -138,7 +139,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
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:
|
||||
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
|
||||
logger.debug('Pre connect invoked')
|
||||
|
||||
if protocol == 'rdp': # If connection is not using rdp, skip adding user
|
||||
@@ -167,7 +168,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
self._user = None
|
||||
logger.debug('User {} already in group'.format(userName))
|
||||
|
||||
return super().preConnect(userName, protocol, ip, hostname)
|
||||
return super().preConnect(userName, protocol, ip, hostname, udsUserName)
|
||||
|
||||
def ovLogon(self, username: str, password: str) -> str:
|
||||
"""
|
||||
@@ -197,6 +198,18 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
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
|
||||
@@ -209,6 +222,17 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
|
||||
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():
|
||||
|
@@ -76,9 +76,9 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
|
||||
except Exception:
|
||||
key = wreg.CreateKeyEx(BASEKEY, PATH, 0, wreg.KEY_ALL_ACCESS)
|
||||
|
||||
fixRegistryPermissions(key.handle)
|
||||
fixRegistryPermissions(key.handle) # type: ignore
|
||||
|
||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config))
|
||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, pickle.dumps(config)) # type: ignore
|
||||
wreg.CloseKey(key)
|
||||
|
||||
|
||||
@@ -94,3 +94,16 @@ def useOldJoinSystem() -> bool:
|
||||
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
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -14,7 +15,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
def setupUi(self, UdsActorSetupDialog):
|
||||
UdsActorSetupDialog.setObjectName("UdsActorSetupDialog")
|
||||
UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
UdsActorSetupDialog.resize(595, 220)
|
||||
UdsActorSetupDialog.resize(601, 243)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -34,12 +35,12 @@ class Ui_UdsActorSetupDialog(object):
|
||||
UdsActorSetupDialog.setModal(True)
|
||||
self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.saveButton.setEnabled(True)
|
||||
self.saveButton.setGeometry(QtCore.QRect(10, 180, 181, 23))
|
||||
self.saveButton.setGeometry(QtCore.QRect(10, 210, 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))
|
||||
self.closeButton.setGeometry(QtCore.QRect(410, 210, 171, 23))
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
@@ -49,11 +50,11 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.closeButton.setObjectName("closeButton")
|
||||
self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog)
|
||||
self.testButton.setEnabled(False)
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 180, 181, 23))
|
||||
self.testButton.setGeometry(QtCore.QRect(210, 210, 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.setGeometry(QtCore.QRect(10, 10, 571, 191))
|
||||
self.layoutWidget.setObjectName("layoutWidget")
|
||||
self.formLayout = QtWidgets.QFormLayout(self.layoutWidget)
|
||||
self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
|
||||
@@ -84,7 +85,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
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.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel)
|
||||
self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget)
|
||||
self.logLevelComboBox.setFrame(True)
|
||||
self.logLevelComboBox.setObjectName("logLevelComboBox")
|
||||
@@ -96,7 +97,13 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.logLevelComboBox.setItemText(2, "ERROR")
|
||||
self.logLevelComboBox.addItem("")
|
||||
self.logLevelComboBox.setItemText(3, "FATAL")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
self.label_restrictNet = QtWidgets.QLabel(self.layoutWidget)
|
||||
self.label_restrictNet.setObjectName("label_restrictNet")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_restrictNet)
|
||||
self.restrictNet = QtWidgets.QLineEdit(self.layoutWidget)
|
||||
self.restrictNet.setObjectName("restrictNet")
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.restrictNet)
|
||||
self.label_host.raise_()
|
||||
self.host.raise_()
|
||||
self.label_serviceToken.raise_()
|
||||
@@ -105,6 +112,8 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.label_security.raise_()
|
||||
self.label_loglevel.raise_()
|
||||
self.logLevelComboBox.raise_()
|
||||
self.label_restrictNet.raise_()
|
||||
self.restrictNet.raise_()
|
||||
|
||||
self.retranslateUi(UdsActorSetupDialog)
|
||||
self.logLevelComboBox.setCurrentIndex(1)
|
||||
@@ -113,6 +122,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig)
|
||||
self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
self.restrictNet.textChanged['QString'].connect(UdsActorSetupDialog.configChanged)
|
||||
QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog)
|
||||
|
||||
def retranslateUi(self, UdsActorSetupDialog):
|
||||
@@ -139,4 +149,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
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"))
|
||||
self.label_restrictNet.setText(_translate("UdsActorSetupDialog", "Restrict Net"))
|
||||
self.restrictNet.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)"))
|
||||
self.restrictNet.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>"))
|
||||
from ui import uds_rc
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
2
client-py3/full/.env
Normal file
2
client-py3/full/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
PYTHONPATH=./src:${PYTHONPATH}
|
||||
|
4
client-py3/full/linux/.gitignore
vendored
4
client-py3/full/linux/.gitignore
vendored
@@ -2,3 +2,7 @@
|
||||
/udsclient-[0-9]*.spec
|
||||
/debian/udsclient
|
||||
/targz
|
||||
/UDSClientDir
|
||||
/UDSClient*.AppImage
|
||||
/appimage*
|
||||
/UDSClient.desktop
|
||||
|
@@ -14,6 +14,8 @@ APPSDIR := $(DESTDIR)/usr/share/applications
|
||||
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
||||
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__')
|
||||
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
||||
install:
|
||||
@@ -46,8 +48,60 @@ endif
|
||||
ifeq ($(DISTRO),rh)
|
||||
endif
|
||||
|
||||
# chmod 0755 $(BINDIR)/udsclient
|
||||
uninstall:
|
||||
rm -rf $(LIBDIR)
|
||||
# rm -f $(BINDIR)/udsclient
|
||||
# rm -rf $(CFGDIR)
|
||||
|
||||
build-appimage:
|
||||
ifeq ($(DISTRO),x86_64)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g > appimage.recipe
|
||||
endif
|
||||
ifeq ($(DISTRO),armhf)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/armhf/g | sed -e s/x86_64/armhf/g > appimage.recipe
|
||||
endif
|
||||
ifeq ($(DISTRO),i686)
|
||||
cat udsclient-appimage-x86_64.recipe | sed -e s/"version: 0.0.0"/"version: $(VERSION)"/g | sed -e s/amd64/i386/g | sed -e s/x86_64/i686/g > appimage.recipe
|
||||
endif
|
||||
# Ensure all working folders are "clean"
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir appimage-build AppDir
|
||||
|
||||
appimage-builder --recipe appimage.recipe --appdir /tmp/UDSClientDir
|
||||
# Now create dist and move appimage
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
cp UDSClient-$(VERSION)-$(DISTRO).AppImage $(DESTDIR)
|
||||
# Generate the .desktop fixed for new path
|
||||
cat desktop/UDSClient.desktop | sed -e s/".usr.lib.UDSClient.UDSClient.py"/"\/usr\/bin\/UDSClient-$(VERSION)-$(DISTRO).AppImage"/g > $(DESTDIR)/UDSClient.desktop
|
||||
# And also, generater installer
|
||||
cat installer-appimage-template.sh | sed -e s/"0.0.0"/"$(VERSION)"/g | sed -e s/x86_64/$(DISTRO)/g > $(DESTDIR)/installer.sh
|
||||
chmod 755 $(DESTDIR)/installer.sh
|
||||
tar czvf ../udsclient3-$(DISTRO)-$(VERSION).tar.gz -C $(DESTDIR) .
|
||||
|
||||
# cleanup
|
||||
-rm -rf appimage appimage-builder-cache /tmp/UDSClientDir appimage-build AppDir
|
||||
|
||||
build-igel:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
# Calculate the size of the custom partition (15 megas more than the appimage size)
|
||||
@$(eval APPIMAGE_SIZE=$(shell du -sm UDSClient-$(VERSION)-x86_64.AppImage | cut -f1))
|
||||
@$(eval APPIMAGE_SIZE=$(shell expr $(APPIMAGE_SIZE) + 15))
|
||||
cat igel/UDSClient-Profile-template.xml | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient-Profile.xml
|
||||
cat igel/UDSClient-template.inf | sed -e s/"_SIZE_"/"$(APPIMAGE_SIZE)M"/g > $(DESTDIR)/UDSClient.inf
|
||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
||||
cp igel/UDSClient.desktop $(DESTDIR)/UDSClient.desktop
|
||||
cp igel/init.sh $(DESTDIR)/init.sh
|
||||
tar cjvf $(DESTDIR)/UDSClient.tar.bz2 -C $(DESTDIR) UDSClient UDSClient.desktop init.sh
|
||||
zip -j ../udsclient3-$(VERSION)-igel.zip $(DESTDIR)/UDSClient-Profile.xml $(DESTDIR)/UDSClient.inf $(DESTDIR)/UDSClient.tar.bz2
|
||||
cd ..
|
||||
rm -rf $(DESTDIR)
|
||||
|
||||
build-thinpro:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(DESTDIR)
|
||||
cp -r thinpro/* $(DESTDIR)
|
||||
cp UDSClient-$(VERSION)-x86_64.AppImage $(DESTDIR)/UDSClient
|
||||
tar czvf ../udsclient3-$(VERSION)-thinpro.tar.gz -C $(DESTDIR) .
|
||||
rm -rf $(DESTDIR)
|
||||
|
||||
|
@@ -12,6 +12,9 @@ cat udsclient-template.spec |
|
||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsclient-$VERSION.spec
|
||||
|
||||
cat appimage-udsclient.recipe |
|
||||
sed -e s/"version: 0.0.0"/"version: ${VERSION}"/g > appimage.recipe
|
||||
|
||||
# Now fix dependencies for opensuse
|
||||
# Note: Right now, opensuse & rh seems to have same dependencies, only 1 package needed
|
||||
# cat udsclient-template.spec |
|
||||
@@ -32,6 +35,19 @@ done
|
||||
|
||||
#rm udsclient-$VERSION
|
||||
|
||||
# Make .tar.gz with source
|
||||
make DESTDIR=targz DISTRO=targz VERSION=${VERSION} install
|
||||
|
||||
# And make FULL CLIENT .tar.gz for x86 and raspberry
|
||||
make DESTDIR=appimage DISTRO=x86_64 VERSION=${VERSION} build-appimage
|
||||
make DESTDIR=appimage DISTRO=armhf VERSION=${VERSION} build-appimage
|
||||
make DESTDIR=appimage DISTRO=i686 VERSION=${VERSION} build-appimage
|
||||
|
||||
# Now create igel version
|
||||
# we need first to create the Appimage for x86_64
|
||||
make DESTDIR=igelimage DISTRO=x86_64 VERSION=${VERSION} build-igel
|
||||
|
||||
# Create the thinpro version
|
||||
make DESTDIR=thinproimage DISTRO=x86_64 VERSION=${VERSION} build-thinpro
|
||||
|
||||
rpm --addsign ../*rpm
|
||||
|
@@ -1,3 +1,9 @@
|
||||
udsclient3 (3.5.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 3.5.0 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 23 Oct 2020 08:12:10 +0200
|
||||
|
||||
udsclient3 (3.0.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 3.0.0 release
|
||||
|
@@ -10,6 +10,6 @@ Package: udsclient3
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: python3-paramiko (>=2.0.0), python3-crypto, python3-pyqt5 (>=5.0), python3-six(>=1.1), python3 (>=3.6), freerdp2-x11 | freerdp-x11, desktop-file-utils, ${misc:Depends}
|
||||
Depends: python3-paramiko (>=2.0.0), python3-certifi, python3-cryptography, python3-psutil, python3-pyqt5 (>=5.0), python3 (>=3.6), freerdp2-x11 | freerdp-x11 | freerdp-nightly, desktop-file-utils, ${misc:Depends}
|
||||
Description: Client connector for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow this machine to connect to services provided by UDS Broker.
|
||||
|
@@ -1,26 +1,38 @@
|
||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||
Name: udsclient3
|
||||
Maintainer: Adolfo Gómez García
|
||||
Source: http://www.udsenterprise.com/
|
||||
Source: http://github.com/dkmstr/openuds/client-py3
|
||||
|
||||
Copyright: 2014 Virtual Cable S.L.U.
|
||||
License: BSD-3-clause
|
||||
Files: *
|
||||
Copyright: (c) 2014-2022, Virtual Cable S.L.U.
|
||||
License: 3-BSD
|
||||
|
||||
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'.
|
||||
License: 3-BSD
|
||||
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 pg_query 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.
|
||||
|
@@ -1,2 +1,2 @@
|
||||
udsclient3_3.0.0_all.deb admin optional
|
||||
udsclient3_3.0.0_amd64.buildinfo admin optional
|
||||
udsclient3_3.5.0_all.deb admin optional
|
||||
udsclient3_3.5.0_amd64.buildinfo admin optional
|
||||
|
@@ -2,7 +2,7 @@
|
||||
Name=UDSClient
|
||||
Comment=UDS Helper
|
||||
Keywords=uds;client;vdi;
|
||||
Exec=/usr/lib/UDSClient/UDSClient.py %u
|
||||
Exec=/usr/lib/UDSClient/UDSClient.py %u -platform xcb
|
||||
Icon=help-browser
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
|
69
client-py3/full/linux/igel/UDSClient-Profile-template.xml
Executable file
69
client-py3/full/linux/igel/UDSClient-Profile-template.xml
Executable file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<profile>
|
||||
<profile_id>1126</profile_id>
|
||||
<profilename>UDSClient</profilename>
|
||||
<firmware>
|
||||
<model>IGEL OS 11</model>
|
||||
<version>11.05.120.01</version>
|
||||
</firmware>
|
||||
<description></description>
|
||||
<overwritesessions>false</overwritesessions>
|
||||
<is_master_profile>false</is_master_profile>
|
||||
<is_igel_os>true</is_igel_os>
|
||||
<settings>
|
||||
<pclass name="custom_partition.enabled">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">true</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="system.security.apparmor">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">false</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="custom_partition.mountpoint">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">/UDSClient</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
<pclass name="custom_partition.size">
|
||||
<pvalue instancenr="-1" variableExpression="" variableSubstitutionActive="false">_SIZE_</pvalue>
|
||||
<variableSubstitutionActive>false</variableSubstitutionActive>
|
||||
</pclass>
|
||||
</settings>
|
||||
<instancesettings>
|
||||
<instance classprefix="custom_partition.source%" serialnumber="-719cadfe:17ca470644a:-7fff127.0.1.1">
|
||||
<ivalue classname="custom_partition.source%.autoupdate" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="custom_partition.source%.crypt_password" variableExpression="" variableSubstitutionActive="false">000d4317311f2c0031133c4d3e4c3d</ivalue>
|
||||
<ivalue classname="custom_partition.source%.final_action" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="custom_partition.source%.init_action" variableExpression="" variableSubstitutionActive="false">/UDSClient/init.sh</ivalue>
|
||||
<ivalue classname="custom_partition.source%.password" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="custom_partition.source%.url" variableExpression="" variableSubstitutionActive="false">https://[UMS_SERVER]:8443/ums_filetransfer/UDSClient-igel.inf</ivalue>
|
||||
<ivalue classname="custom_partition.source%.username" variableExpression="" variableSubstitutionActive="false">[UMS_USERNAME]</ivalue>
|
||||
</instance>
|
||||
<instance classprefix="sessions.chromium%" serialnumber="-6b5264e9:17ca6f65505:-8000127.0.1.1">
|
||||
<ivalue classname="sessions.chromium%.app.browser_startup_page" variableExpression="" variableSubstitutionActive="false">1</ivalue>
|
||||
<ivalue classname="sessions.chromium%.app.homepage" variableExpression="" variableSubstitutionActive="false">https://demo.udsenterprise.com</ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.applaunch_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.autostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.autostartnotify" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.desktop" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.desktop_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.hotkey" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.hotkeymodifier" variableExpression="" variableSubstitutionActive="false">None</ivalue>
|
||||
<ivalue classname="sessions.chromium%.icon" variableExpression="" variableSubstitutionActive="false">chromium</ivalue>
|
||||
<ivalue classname="sessions.chromium%.menu_path" variableExpression="" variableSubstitutionActive="false"></ivalue>
|
||||
<ivalue classname="sessions.chromium%.name" variableExpression="UDS" variableSubstitutionActive="true">###LOC_DEFAULT###</ivalue>
|
||||
<ivalue classname="sessions.chromium%.position" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
<ivalue classname="sessions.chromium%.pulldown" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.pwprotected" variableExpression="" variableSubstitutionActive="false">none</ivalue>
|
||||
<ivalue classname="sessions.chromium%.quick_start" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.scardautostart" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.snotify" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.startmenu" variableExpression="" variableSubstitutionActive="false">true</ivalue>
|
||||
<ivalue classname="sessions.chromium%.startmenu_system" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.usehotkey" variableExpression="" variableSubstitutionActive="false">false</ivalue>
|
||||
<ivalue classname="sessions.chromium%.waittime2autostart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
<ivalue classname="sessions.chromium%.waittime2restart" variableExpression="" variableSubstitutionActive="false">0</ivalue>
|
||||
</instance>
|
||||
</instancesettings>
|
||||
</profile>
|
7
client-py3/full/linux/igel/UDSClient-template.inf
Executable file
7
client-py3/full/linux/igel/UDSClient-template.inf
Executable file
@@ -0,0 +1,7 @@
|
||||
[INFO]
|
||||
[PART]
|
||||
file="UDSClient.tar.bz2"
|
||||
version="1.1_igel1"
|
||||
size="_SIZE_"
|
||||
name="UDSClient"
|
||||
minfw="11.05.120"
|
2
client/thin/thinstation/udsclient/UDSClient.desktop → client-py3/full/linux/igel/UDSClient.desktop
Normal file → Executable file
2
client/thin/thinstation/udsclient/UDSClient.desktop → client-py3/full/linux/igel/UDSClient.desktop
Normal file → Executable file
@@ -2,7 +2,7 @@
|
||||
Name=UDSClient
|
||||
Comment=UDS Helper
|
||||
Keywords=uds;client;vdi;
|
||||
Exec=/bin/udsclient %u
|
||||
Exec=/UDSClient/UDSClient %u
|
||||
Icon=help-browser
|
||||
StartupNotify=true
|
||||
Terminal=false
|
2
client-py3/full/linux/igel/init.sh
Executable file
2
client-py3/full/linux/igel/init.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
cp /UDSClient/UDSClient.desktop /usr/share/applications.mime
|
15
client-py3/full/linux/installer-appimage-template.sh
Normal file
15
client-py3/full/linux/installer-appimage-template.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check for root
|
||||
if ! [ $(id -u) = 0 ]; then
|
||||
echo "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing UDSClient Portable..."
|
||||
|
||||
cp UDSClient-0.0.0-x86_64.AppImage /usr/bin
|
||||
cp UDSClient.desktop /usr/share/applications
|
||||
update-desktop-database
|
||||
|
||||
echo "Installation process done."
|
@@ -8,6 +8,8 @@ echo "Installation process done."
|
||||
echo "Remember that the following packages must be installed on system:"
|
||||
echo "* Python3 paramiko"
|
||||
echo "* Python3 PyQt5"
|
||||
echo "* Python3 six"
|
||||
echo "* Python3 requests"
|
||||
echo "* Python3 cryptography"
|
||||
echo "Theese packages (as their names), are dependent on your platform, so you must locate and install them"
|
||||
echo "Also, ensure that a /media folder exists on your machine, that will be redirected on RDP connections"
|
||||
|
4
client-py3/full/linux/thinpro/firefox/45-uds
Normal file
4
client-py3/full/linux/thinpro/firefox/45-uds
Normal file
@@ -0,0 +1,4 @@
|
||||
# UDS handlers.json
|
||||
cp "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
||||
ffset "network.protocol-handler.external.uds" "true"
|
||||
ffset "network.protocol-handler.external.udss" "true"
|
98
client-py3/full/linux/thinpro/firefox/handlers.json
Normal file
98
client-py3/full/linux/thinpro/firefox/handlers.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"defaultHandlersVersion": {
|
||||
"en-US": 4
|
||||
},
|
||||
"mimeTypes": {
|
||||
"application/pdf": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"pdf"
|
||||
]
|
||||
},
|
||||
"application/x-ica": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"ica"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "wfica",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/citrix"
|
||||
}
|
||||
]
|
||||
},
|
||||
"application/x-rdp": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"rdp"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "HP xfreerdp",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/rdp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text/lic": {
|
||||
"action": 2,
|
||||
"extensions": [
|
||||
"lic"
|
||||
],
|
||||
"handlers": [
|
||||
{
|
||||
"name": "Copy license to ThinPro",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/copy_lic"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text/xml": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"xml"
|
||||
]
|
||||
},
|
||||
"image/svg+xml": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"svg"
|
||||
]
|
||||
},
|
||||
"image/webp": {
|
||||
"action": 3,
|
||||
"extensions": [
|
||||
"webp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemes": {
|
||||
"vmware-view": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "VMWare Horizon View",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/vmware"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uds": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "UDS Client for ThinPro (SSL)",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
||||
}
|
||||
]
|
||||
},
|
||||
"udss": {
|
||||
"action": 2,
|
||||
"handlers": [
|
||||
{
|
||||
"name": "UDS Client for ThinPro",
|
||||
"path": "/usr/share/hptc-firefox-mgr/handlers/uds"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
5
client-py3/full/linux/thinpro/firefox/uds
Normal file
5
client-py3/full/linux/thinpro/firefox/uds
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
export LD_PRELOAD=""
|
||||
/bin/udsclient $*
|
||||
exit 0
|
2
client-py3/full/linux/thinpro/firefox7.1/45-uds
Normal file
2
client-py3/full/linux/thinpro/firefox7.1/45-uds
Normal file
@@ -0,0 +1,2 @@
|
||||
# UDS handlers.json
|
||||
restore "/lib/UDSClient/firefox/handlers.json" "$FIREFOX_PROFILE_HANDLERS"
|
50
client-py3/full/linux/thinpro/firefox7.1/handlers.json
Normal file
50
client-py3/full/linux/thinpro/firefox7.1/handlers.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"defaultHandlersVersion":{
|
||||
"en-US":4
|
||||
},
|
||||
"mimeTypes":{
|
||||
"application/pdf":{
|
||||
"action":3,
|
||||
"extensions":["pdf"]
|
||||
},
|
||||
"application/x-ica":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"wfica",
|
||||
"path":"/usr/bin/hptc-firefox-run-wfica.sh"
|
||||
}],
|
||||
"extensions":["ica"]
|
||||
},
|
||||
"application/x-rdp":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"HP xfreerdp",
|
||||
"path":"/usr/bin/hptc-run-rdp-file-freerdp.sh"
|
||||
}],
|
||||
"extensions":["rdp"]
|
||||
}
|
||||
},
|
||||
"schemes":{
|
||||
"vmware-view":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"VMWare Horizon View",
|
||||
"path":"/usr/bin/vmware-view"
|
||||
}]
|
||||
},
|
||||
"udss":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"UDS Client",
|
||||
"path":"/bin/udsclient"
|
||||
}]
|
||||
},
|
||||
"uds":{
|
||||
"action":2,
|
||||
"handlers":[{
|
||||
"name":"UDS Client",
|
||||
"path":"/bin/udsclient"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
37
client-py3/full/linux/thinpro/firefox7.1/syspref.js
Normal file
37
client-py3/full/linux/thinpro/firefox7.1/syspref.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// This file can be used to configure global preferences for Firefox
|
||||
// Example: Homepage
|
||||
//pref("browser.startup.homepage", "http://www.weebls-stuff.com/wab/");
|
||||
pref("plugin.default.state", 2);
|
||||
pref("xpinstall.signatures.required", false, locked);
|
||||
pref("extensions.autoDisableScopes", 0, locked);
|
||||
pref("extensions.pocket.enabled", false, locked);
|
||||
pref("extensions.screenshots.disabled", true, locked);
|
||||
pref("datareporting.policy.dataSubmissionEnabled", false, locked);
|
||||
pref("datareporting.policy.dataSubmissionEnabled.v2", false, locked);
|
||||
|
||||
pref("app.update.auto", false, locked);
|
||||
pref("app.update.enabled", false, locked);
|
||||
pref("browser.download.manager.closeWhenDone", true, locked);
|
||||
pref("browser.helperApps.neverAsk.openFile", "application/x-rdp, application/x-java-jnlp-file", locked);
|
||||
pref("browser.EULA.3.accepted", true, locked);
|
||||
pref("browser.rights.3.shown", true, locked);
|
||||
pref("browser.safebrowsing.enabled", false, locked);
|
||||
pref("browser.search.update", false, locked);
|
||||
pref("browser.sessionstore.enabled", false, locked);
|
||||
pref("browser.sessionhistory.cache_subframes", false, locked);
|
||||
pref("datareporting.healthreport.service.enabled", false, locked);
|
||||
pref("datareporting.healthreport.uploadEnabled", false, locked);
|
||||
pref("devtools.toolbox.host", "none", locked);
|
||||
pref("extensions.autoDisableScopes", 14, locked);
|
||||
pref("extensions.blocklist.enabled", false, locked);
|
||||
pref("extensions.update.enabled", false, locked);
|
||||
pref("intl.charsetmenu.browser.cache", "UTF-8", locked);
|
||||
|
||||
pref("network.protocol-handler.external.mailto", false, locked);
|
||||
pref("network.protocol-handler.external.news", false, locked);
|
||||
pref("network.protocol-handler.external.snews", false, locked);
|
||||
pref("network.protocol-handler.external.nntp", false, locked);
|
||||
pref("network.protocol-handler.external-default", false, locked);
|
||||
pref("network.protocol-handler.external.vmware-view", true, locked);
|
||||
pref("network.protocol-handler.external.uds", true, locked);
|
||||
pref("network.protocol-handler.external.udss", true, locked);
|
38
client-py3/full/linux/thinpro/install-uds-client.sh
Executable file
38
client-py3/full/linux/thinpro/install-uds-client.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Common part
|
||||
|
||||
# unlocks so we can write on TC
|
||||
fsunlock
|
||||
|
||||
cp UDSClient /bin/udsclient
|
||||
chmod 755 /bin/udsclient
|
||||
# RDP Script for UDSClient. Launchs udsclient using the "Template_UDS" profile
|
||||
cp udsrdp /usr/bin
|
||||
|
||||
INSTALLED=0
|
||||
# Installation for 7.1.x version
|
||||
grep -q "7.1" /etc/issue
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Installing for thinpro version 7.1"
|
||||
# Allow UDS apps without asking
|
||||
cp firefox7.1/syspref.js /etc/firefox
|
||||
# Copy handlers.json for firefox
|
||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
||||
cp firefox7.1/handlers.json /lib/UDSClient/firefox/
|
||||
# and runner
|
||||
cp firefox7.1/45-uds /etc/hptc-firefox-mgr/prestart
|
||||
else
|
||||
echo "Installing for thinpro version 7.2 or later"
|
||||
# Copy handlers for firefox
|
||||
mkdir -p /lib/UDSClient/firefox/ > /dev/null 2>&1
|
||||
# Copy handlers.json for firefox
|
||||
cp firefox/handlers.json /lib/UDSClient/firefox/
|
||||
cp firefox/45-uds /etc/hptc-firefox-mgr/prestart
|
||||
# copy uds handler for firefox
|
||||
cp firefox/uds /usr/share/hptc-firefox-mgr/handlers/uds
|
||||
chmod 755 /usr/share/hptc-firefox-mgr/handlers/uds
|
||||
fi
|
||||
|
||||
# Common part
|
||||
fslock
|
390
client-py3/full/linux/thinpro/udsrdp
Executable file
390
client-py3/full/linux/thinpro/udsrdp
Executable file
@@ -0,0 +1,390 @@
|
||||
#!/bin/bash
|
||||
|
||||
function clearParams {
|
||||
mclient set $REGKEY/address ""
|
||||
mclient set $REGKEY/username ""
|
||||
mclient set $REGKEY/password ""
|
||||
mclient set $REGKEY/domain ""
|
||||
|
||||
mclient set $REGKEY/authorizations/user/execution 0
|
||||
|
||||
mclient commit
|
||||
}
|
||||
|
||||
function getRegKey {
|
||||
# Get Template_UDS
|
||||
for key in `mclient get root/ConnectionType/freerdp/connections | sed "s/dir //g"`; do
|
||||
val=`mclient get $key/label | sed "s/value //g"`
|
||||
if [ "$val" == "Template_UDS" ]; then
|
||||
REGKEY=$key
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function createUDSConnectionTemplate {
|
||||
TMPFILE=$(mktemp /tmp/udsexport.XXXXXX)
|
||||
cat > $TMPFILE << EOF
|
||||
<Profile>
|
||||
<ProfileSettings>
|
||||
<Name>UDS Template Profile</Name>
|
||||
<RegistryRoot>root/ConnectionType/freerdp/connections/{ff064bd9-047a-45ec-b70f-04ab218186ff}</RegistryRoot>
|
||||
<Target>
|
||||
<Hardware>t420</Hardware>
|
||||
<ImageId>T7X62022</ImageId>
|
||||
<Version>6.2.0</Version>
|
||||
<Config>standard</Config>
|
||||
</Target>
|
||||
</ProfileSettings>
|
||||
<ProfileRegistry>
|
||||
<NodeDir name="{ff064bd9-047a-45ec-b70f-04ab218186ff}">
|
||||
<NodeDir name="rdWebFeed">
|
||||
<NodeKey name="keepResourcesWindowOpened">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoStartSingleResource">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoDisconnectTimeout">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
<NodeDir name="loginfields">
|
||||
<NodeKey name="username">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rememberme">
|
||||
<NodeParam name="value">2</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="password">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="domain">
|
||||
<NodeParam name="value">3</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
<NodeDir name="authorizations">
|
||||
<NodeDir name="user">
|
||||
<NodeKey name="execution">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="edit">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
</NodeDir>
|
||||
<NodeKey name="address">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="username">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="password">
|
||||
<NodeParam name="value">NLCR.1</NodeParam>
|
||||
<NodeParam name="type">rc4</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="domain">
|
||||
<NodeParam name="value"/>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="label">
|
||||
<NodeParam name="value">Template_UDS</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="credentialsType">
|
||||
<NodeParam name="value">password</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayEnabled">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayPort">
|
||||
<NodeParam name="value">443</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayUsesSameCredentials">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="gatewayCredentialsType">
|
||||
<NodeParam name="value">password</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="remoteDesktopService">
|
||||
<NodeParam name="value">Remote Computer</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowMode">
|
||||
<NodeParam name="value">Remote Application</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="seamlessWindow">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowType">
|
||||
<NodeParam name="value">full</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizePercentage">
|
||||
<NodeParam name="value">70</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizeWidth">
|
||||
<NodeParam name="value">1024</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="windowSizeHeight">
|
||||
<NodeParam name="value">768</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="mouseMotionEvents">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="compression">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdpEncryption">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="offScreenBitmaps">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="attachToConsole">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="clipboardExtension">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdp6Buffering">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="rdpProgressiveCodec">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="securityLevel">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="tlsVersion">
|
||||
<NodeParam name="value">auto</NodeParam>
|
||||
<NodeParam name="type">string</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="sound">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="printerMapping">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="portMapping">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="usbStorageRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="localPartitionRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="scRedirection">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="usbMiscRedirection">
|
||||
<NodeParam name="value">2</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoWallpaper">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagFontSmoothing">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagDesktopComposition">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoWindowDrag">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoMenuAnimations">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="perfFlagNoTheming">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutsEnabled">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutWarning">
|
||||
<NodeParam name="value">6000</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutWarningDialog">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutRecovery">
|
||||
<NodeParam name="value">30000</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="timeoutError">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="showRDPDashboard">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="showConnectionGraph">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Synchronous">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Logging">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11LogAutoflush">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="x11Capture">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="SingleSignOn">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autostart">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">number</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="waitForNetwork">
|
||||
<NodeParam name="value">1</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="hasDesktopIcon">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
<NodeKey name="autoReconnect">
|
||||
<NodeParam name="value">0</NodeParam>
|
||||
<NodeParam name="type">bool</NodeParam>
|
||||
</NodeKey>
|
||||
</NodeDir>
|
||||
</ProfileRegistry>
|
||||
<ProfileFiles/>
|
||||
</Profile>
|
||||
EOF
|
||||
mclient import $TMPFILE
|
||||
rm $TMPFILE
|
||||
}
|
||||
|
||||
ADDRESS=
|
||||
USERNAME=
|
||||
PASSWORD=
|
||||
DOMAIN=
|
||||
REGKEY=
|
||||
CLEAR=0
|
||||
|
||||
# Try to locate registry key for UDS Template
|
||||
getRegKey
|
||||
|
||||
if [ "$REGKEY" == "" ]; then
|
||||
# Not found, create on based on our template
|
||||
createUDSConnectionTemplate
|
||||
getRegKey
|
||||
fi
|
||||
|
||||
for param in $@; do
|
||||
if [ "/u:" == "${param:0:3}" ]; then
|
||||
USERNAME=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/p:" == "${param:0:3}" ]; then
|
||||
PASSWORD=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/d:" == "${param:0:3}" ]; then
|
||||
DOMAIN=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
|
||||
if [ "/v:" == "${param:0:3}" ]; then
|
||||
ADDRESS=${param:3}
|
||||
CLEAR=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$CLEAR" -eq 1 ]; then
|
||||
clearParams
|
||||
fi
|
||||
|
||||
ID=`basename $REGKEY`
|
||||
RESPAWN=0
|
||||
|
||||
if [ "" != "$ADDRESS" ]; then
|
||||
mclient set $REGKEY/address "${ADDRESS}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$USERNAME" ]; then
|
||||
mclient set $REGKEY/username "${USERNAME}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$PASSWORD" ]; then
|
||||
mclient set $REGKEY/password "${PASSWORD}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "" != "$DOMAIN" ]; then
|
||||
mclient set $REGKEY/domain "${DOMAIN}"
|
||||
RESPAWN=1
|
||||
fi
|
||||
|
||||
if [ "$RESPAWN" -eq 1 ]; then
|
||||
mclient set $REGKEY/authorizations/user/execution 1
|
||||
mclient commit
|
||||
exec $0 # Restart without command line
|
||||
fi
|
||||
|
||||
process-connection $ID
|
||||
|
||||
clearParams
|
61
client-py3/full/linux/udsclient-appimage-x86_64.recipe
Normal file
61
client-py3/full/linux/udsclient-appimage-x86_64.recipe
Normal file
@@ -0,0 +1,61 @@
|
||||
version: 1
|
||||
script:
|
||||
# Remove any previous build
|
||||
- rm -rf $TARGET_APPDIR | true
|
||||
# Make usr and icons dirs
|
||||
- mkdir -p $TARGET_APPDIR/usr/src
|
||||
# Copy the python application code into the UDSClientDir
|
||||
- cp ../src/UDS*.py $TARGET_APPDIR/usr/src
|
||||
- cp -r ../src/uds $TARGET_APPDIR/usr/src
|
||||
# Remove __pycache__ and .mypy if exists
|
||||
- rm $TARGET_APPDIR/usr/src/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/uds/.mypy_cache -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/__pycache__ -rf 2>&1 > /dev/null
|
||||
- rm $TARGET_APPDIR/usr/src/uds/__pycache__ -rf 2>&1 > /dev/null
|
||||
AppDir:
|
||||
# On /tmp, that is an ext4 filesystem. On btrfs squashfs complains with "Unrecognised xattr prefix btrfs.compression"
|
||||
path: /tmp/UDSClientDir
|
||||
|
||||
app_info:
|
||||
id: com.udsenterprise.UDSClient3
|
||||
name: UDSClient
|
||||
icon: utilities-terminal
|
||||
version: 0.0.0
|
||||
# Set the python executable as entry point
|
||||
exec: usr/bin/python3
|
||||
# Set the application main script path as argument. Use '$@' to forward CLI parameters
|
||||
exec_args: "$APPDIR/usr/src/UDSClient.py $@"
|
||||
|
||||
apt:
|
||||
arch: amd64
|
||||
sources:
|
||||
- sourceline: 'deb [arch=amd64] http://ftp.de.debian.org/debian/ bullseye main contrib non-free'
|
||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x648ACFD622F3D138'
|
||||
|
||||
include:
|
||||
- python3
|
||||
- python3-pkg-resources
|
||||
- python3-pyqt5
|
||||
- python3-paramiko
|
||||
- python3-cryptography
|
||||
- python3-certifi
|
||||
- python3-psutil
|
||||
- freerdp2-x11
|
||||
- freerdp2-wayland
|
||||
- x2goclient
|
||||
- openssh-sftp-server
|
||||
exclude: []
|
||||
|
||||
runtime:
|
||||
env:
|
||||
# Set python home
|
||||
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
|
||||
PYTHONHOME: '${APPDIR}/usr'
|
||||
# Path to the site-packages dir or other modules dirs
|
||||
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|
||||
PYTHONPATH: '${APPDIR}/usr/lib/python3.9/site-packages'
|
||||
|
||||
AppImage:
|
||||
# update-information: None
|
||||
sign-key: 592AF43A64B8559137FA2458AA4ECFEE784E6BA7
|
||||
arch: x86_64
|
@@ -11,7 +11,7 @@ Release: %{release}
|
||||
Summary: Client for Universal Desktop Services (UDS) Broker
|
||||
License: BSD3
|
||||
Group: Applications/Productivity
|
||||
Requires: python3-six python3-requests python3-paramiko python3-qt5 (python3-crypto or python3-pycrypto)
|
||||
Requires: python3-paramiko python3-qt5 python3-cryptography python3-certifi python3-psutil
|
||||
Vendor: Virtual Cable S.L.U.
|
||||
URL: http://www.udsenterprise.com
|
||||
Provides: udsclient
|
||||
|
6
client-py3/full/src/.gitignore
vendored
6
client-py3/full/src/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
/build
|
||||
/dist
|
||||
UDSClient.dmg
|
||||
UDSClient.pkg
|
||||
/UDSClient*.pkg
|
||||
/UDSClient*.dist
|
||||
/UDSClient*.build
|
||||
/.eggs
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -s
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -12,7 +12,7 @@
|
||||
# * 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
|
||||
# * 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.
|
||||
#
|
||||
@@ -31,41 +31,45 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
import webbrowser
|
||||
import json
|
||||
import base64, bz2
|
||||
import threading
|
||||
import typing
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets # @UnresolvedImport
|
||||
import six
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from uds.rest import RestApi, RetryException, InvalidVersion, UDSException
|
||||
|
||||
# Just to ensure there are available on runtime
|
||||
from uds.forward import forward as ssh_forward # type: ignore
|
||||
from uds.tunnel import forward as tunnel_forwards # type: ignore
|
||||
|
||||
from uds.rest import RestRequest
|
||||
from uds.forward import forward # pylint: disable=unused-import
|
||||
from uds.log import logger
|
||||
from uds import tools
|
||||
from uds import VERSION
|
||||
|
||||
from UDSWindow import Ui_MainWindow
|
||||
|
||||
# Server before this version uses "unsigned" scripts
|
||||
OLD_METHOD_VERSION = '2.4.0'
|
||||
|
||||
class RetryException(Exception):
|
||||
pass
|
||||
|
||||
class UDSClient(QtWidgets.QMainWindow):
|
||||
|
||||
ticket = None
|
||||
scrambler = None
|
||||
ticket: str = ''
|
||||
scrambler: str = ''
|
||||
withError = False
|
||||
animTimer = None
|
||||
anim = 0
|
||||
animInverted = False
|
||||
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
|
||||
req = None
|
||||
animTimer: typing.Optional[QtCore.QTimer] = None
|
||||
anim: int = 0
|
||||
animInverted: bool = False
|
||||
api: RestApi
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, api: RestApi, ticket: str, scrambler: str):
|
||||
QtWidgets.QMainWindow.__init__(self)
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.api = api
|
||||
self.ticket = ticket
|
||||
self.scrambler = scrambler
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) # type: ignore
|
||||
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
@@ -82,34 +86,29 @@ class UDSClient(QtWidgets.QMainWindow):
|
||||
self.move(hpos, vpos)
|
||||
|
||||
self.animTimer = QtCore.QTimer()
|
||||
self.animTimer.timeout.connect(self.updateAnim)
|
||||
self.animTimer.timeout.connect(self.updateAnim) # type: ignore
|
||||
# QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
|
||||
|
||||
self.activateWindow()
|
||||
|
||||
self.startAnim()
|
||||
|
||||
|
||||
def closeWindow(self):
|
||||
self.close()
|
||||
|
||||
def processError(self, data):
|
||||
if 'error' in data:
|
||||
# QtWidgets.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtWidgets.QMessageBox.Ok)
|
||||
if data.get('retryable', '0') == '1':
|
||||
raise RetryException(data['error'])
|
||||
|
||||
raise Exception(data['error'])
|
||||
# QtWidgets.QMessageBox.critical(self, 'Request error', rest.data['error'], QtWidgets.QMessageBox.Ok)
|
||||
# self.closeWindow()
|
||||
# return
|
||||
|
||||
def showError(self, error):
|
||||
logger.error('got error: %s', error)
|
||||
self.stopAnim()
|
||||
self.ui.info.setText('UDS Plugin Error') # In fact, main window is hidden, so this is not visible... :)
|
||||
self.ui.info.setText(
|
||||
'UDS Plugin Error'
|
||||
) # In fact, main window is hidden, so this is not visible... :)
|
||||
self.closeWindow()
|
||||
QtWidgets.QMessageBox.critical(None, 'UDS Plugin Error', '{}'.format(error), QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None, # type: ignore
|
||||
'UDS Plugin Error',
|
||||
'{}'.format(error),
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
self.withError = True
|
||||
|
||||
def cancelPushed(self):
|
||||
@@ -125,165 +124,229 @@ class UDSClient(QtWidgets.QMainWindow):
|
||||
self.ui.progressBar.setValue(self.anim)
|
||||
|
||||
def startAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.ui.progressBar.invertedAppearance = False # type: ignore
|
||||
self.anim = 0
|
||||
self.animInverted = False
|
||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||
self.animTimer.start(40)
|
||||
if self.animTimer:
|
||||
self.animTimer.start(40)
|
||||
|
||||
def stopAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.animTimer.stop()
|
||||
self.ui.progressBar.invertedAppearance = False # type: ignore
|
||||
if self.animTimer:
|
||||
self.animTimer.stop()
|
||||
|
||||
def getVersion(self):
|
||||
self.req = RestRequest('', self, self.version)
|
||||
self.req.get()
|
||||
|
||||
def version(self, data):
|
||||
try:
|
||||
self.processError(data)
|
||||
self.ui.info.setText('Processing...')
|
||||
|
||||
if data['result']['requiredVersion'] > VERSION:
|
||||
QtWidgets.QMessageBox.critical(self, 'Upgrade required', 'A newer connector version is required.\nA browser will be opened to download it.', QtWidgets.QMessageBox.Ok)
|
||||
webbrowser.open(data['result']['downloadUrl'])
|
||||
self.closeWindow()
|
||||
return
|
||||
|
||||
self.serverVersion = data['result']['requiredVersion']
|
||||
self.getTransportData()
|
||||
|
||||
except RetryException as e:
|
||||
self.ui.info.setText(str(e))
|
||||
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||
|
||||
self.api.getVersion()
|
||||
except InvalidVersion as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
'Upgrade required',
|
||||
'A newer connector version is required.\nA browser will be opened to download it.',
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
webbrowser.open(e.downloadUrl)
|
||||
self.closeWindow()
|
||||
return
|
||||
except Exception as e:
|
||||
self.showError(e)
|
||||
self.closeWindow()
|
||||
return
|
||||
|
||||
self.getTransportData()
|
||||
|
||||
def getTransportData(self):
|
||||
try:
|
||||
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||
self.req.get()
|
||||
except Exception as e:
|
||||
logger.exception('Got exception on getTransportData')
|
||||
raise e
|
||||
|
||||
def transportDataReceived(self, data):
|
||||
logger.debug('Transport data received')
|
||||
try:
|
||||
self.processError(data)
|
||||
|
||||
params = None
|
||||
|
||||
if self.serverVersion <= OLD_METHOD_VERSION:
|
||||
script = bz2.decompress(base64.b64decode(data['result']))
|
||||
# This fixes uds 2.2 "write" string on binary streams on some transport
|
||||
script = script.replace(b'stdin.write("', b'stdin.write(b"')
|
||||
script = script.replace(b'version)', b'version.decode("utf-8"))')
|
||||
else:
|
||||
res = data['result']
|
||||
# We have three elements on result:
|
||||
# * Script
|
||||
# * Signature
|
||||
# * Script data
|
||||
# We test that the Script has correct signature, and them execute it with the parameters
|
||||
#script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
||||
script, signature, params = bz2.decompress(base64.b64decode(res['script'])), res['signature'], json.loads(bz2.decompress(base64.b64decode(res['params'])))
|
||||
if tools.verifySignature(script, signature) is False:
|
||||
logger.error('Signature is invalid')
|
||||
|
||||
raise Exception('Invalid UDS code signature. Please, report to administrator')
|
||||
|
||||
script, params = self.api.getScriptAndParams(self.ticket, self.scrambler)
|
||||
self.stopAnim()
|
||||
|
||||
if 'darwin' in sys.platform:
|
||||
self.showMinimized()
|
||||
|
||||
QtCore.QTimer.singleShot(3000, self.endScript)
|
||||
self.hide()
|
||||
# QtCore.QTimer.singleShot(3000, self.endScript)
|
||||
# self.hide()
|
||||
self.closeWindow()
|
||||
|
||||
six.exec_(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
||||
exec(script, globals(), {'parent': self, 'sp': params})
|
||||
|
||||
# Execute the waiting tasks...
|
||||
threading.Thread(target=endScript).start()
|
||||
|
||||
except RetryException as e:
|
||||
self.ui.info.setText(six.text_type(e) + ', retrying access...')
|
||||
self.ui.info.setText(str(e) + ', retrying access...')
|
||||
# Retry operation in ten seconds
|
||||
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
||||
|
||||
except Exception as e:
|
||||
#logger.exception('Got exception executing script:')
|
||||
self.showError(e)
|
||||
|
||||
def endScript(self):
|
||||
# After running script, wait for stuff
|
||||
try:
|
||||
tools.waitForTasks()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tools.unlinkFiles()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tools.execBeforeExit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.closeWindow()
|
||||
|
||||
def start(self):
|
||||
'''
|
||||
"""
|
||||
Starts proccess by requesting version info
|
||||
'''
|
||||
"""
|
||||
self.ui.info.setText('Initializing...')
|
||||
QtCore.QTimer.singleShot(100, self.getVersion)
|
||||
|
||||
|
||||
def done(data):
|
||||
QtWidgets.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtWidgets.QMessageBox.Ok)
|
||||
sys.exit(0)
|
||||
def endScript():
|
||||
# Wait a bit before start processing ending sequence
|
||||
time.sleep(3)
|
||||
try:
|
||||
# Remove early stage files...
|
||||
tools.unlinkFiles(early=True)
|
||||
except Exception as e:
|
||||
logger.debug('Unlinking files on early stage: %s', e)
|
||||
|
||||
# After running script, wait for stuff
|
||||
try:
|
||||
logger.debug('Wating for tasks to finish...')
|
||||
tools.waitForTasks()
|
||||
except Exception as e:
|
||||
logger.debug('Watiting for tasks to finish: %s', e)
|
||||
|
||||
try:
|
||||
logger.debug('Unlinking files')
|
||||
tools.unlinkFiles(early=False)
|
||||
except Exception as e:
|
||||
logger.debug('Unlinking files on later stage: %s', e)
|
||||
|
||||
# Removing
|
||||
try:
|
||||
logger.debug('Executing threads before exit')
|
||||
tools.execBeforeExit()
|
||||
except Exception as e:
|
||||
logger.debug('execBeforeExit: %s', e)
|
||||
|
||||
logger.debug('endScript done')
|
||||
|
||||
|
||||
# Ask user to approve endpoint
|
||||
def approveHost(hostName, parentWindow=None):
|
||||
def approveHost(hostName: str):
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup('endpoints')
|
||||
|
||||
#approved = settings.value(hostName, False).toBool()
|
||||
# approved = settings.value(hostName, False).toBool()
|
||||
approved = bool(settings.value(hostName, False))
|
||||
|
||||
errorString = '<p>The server <b>{}</b> must be approved:</p>'.format(hostName)
|
||||
errorString += '<p>Only approve UDS servers that you trust to avoid security issues.</p>'
|
||||
errorString += (
|
||||
'<p>Only approve UDS servers that you trust to avoid security issues.</p>'
|
||||
)
|
||||
|
||||
if approved or QtWidgets.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.Yes:
|
||||
settings.setValue(hostName, True)
|
||||
approved = True
|
||||
if not approved:
|
||||
if (
|
||||
QtWidgets.QMessageBox.warning(
|
||||
None, # type: ignore
|
||||
'ACCESS Warning',
|
||||
errorString,
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, # type: ignore
|
||||
)
|
||||
== QtWidgets.QMessageBox.Yes
|
||||
):
|
||||
settings.setValue(hostName, True)
|
||||
approved = True
|
||||
|
||||
settings.endGroup()
|
||||
return approved
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.debug('Initializing connector')
|
||||
|
||||
# Initialize app
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
def sslError(hostname: str, serial):
|
||||
settings = QSettings()
|
||||
settings.beginGroup('ssl')
|
||||
|
||||
approved = settings.value(serial, False)
|
||||
|
||||
if (
|
||||
approved
|
||||
or QtWidgets.QMessageBox.warning(
|
||||
None, # type: ignore
|
||||
'SSL Warning',
|
||||
f'Could not check SSL certificate for {hostname}.\nDo you trust this host?',
|
||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, # type: ignore
|
||||
)
|
||||
== QtWidgets.QMessageBox.Yes
|
||||
):
|
||||
approved = True
|
||||
settings.setValue(serial, True)
|
||||
|
||||
settings.endGroup()
|
||||
return approved
|
||||
|
||||
|
||||
# Used only if command line says so
|
||||
def minimal(api: RestApi, ticket: str, scrambler: str):
|
||||
try:
|
||||
logger.info('Minimal Execution')
|
||||
logger.debug('Getting version')
|
||||
try:
|
||||
api.getVersion()
|
||||
except InvalidVersion as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None, # type: ignore
|
||||
'Upgrade required',
|
||||
'A newer connector version is required.\nA browser will be opened to download it.',
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
webbrowser.open(e.downloadUrl)
|
||||
return 0
|
||||
logger.debug('Transport data')
|
||||
script, params = api.getScriptAndParams(ticket, scrambler)
|
||||
|
||||
# Execute UDS transport script
|
||||
exec(script, globals(), {'parent': None, 'sp': params})
|
||||
# Execute the waiting task...
|
||||
threading.Thread(target=endScript).start()
|
||||
|
||||
except RetryException as e:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
None, # type: ignore
|
||||
'Service not ready',
|
||||
'{}'.format('.\n'.join(str(e).split('.')))
|
||||
+ '\n\nPlease, retry again in a while.',
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
except Exception as e:
|
||||
# logger.exception('Got exception on getTransportData')
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None, # type: ignore
|
||||
'Error',
|
||||
'{}'.format(str(e)) + '\n\nPlease, retry again in a while.',
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def main(args: typing.List[str]):
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
logger.debug('Initializing connector for %s(%s)', sys.platform, platform.machine())
|
||||
|
||||
logger.debug('Arguments: %s', args)
|
||||
# Set several info for settings
|
||||
QtCore.QCoreApplication.setOrganizationName('Virtual Cable S.L.U.')
|
||||
QtCore.QCoreApplication.setApplicationName('UDS Connector')
|
||||
|
||||
if 'darwin' not in sys.platform:
|
||||
logger.debug('Mac OS *NOT* Detected')
|
||||
app.setStyle('plastique')
|
||||
|
||||
if six.PY3 is False:
|
||||
logger.debug('Fixing threaded execution of commands')
|
||||
import threading
|
||||
threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore # pylint: disable=protected-access
|
||||
app.setStyle('plastique') # type: ignore
|
||||
else:
|
||||
logger.debug('Platform is Mac OS, adding homebrew possible paths')
|
||||
os.environ['PATH'] += ''.join(
|
||||
os.pathsep + i
|
||||
for i in (
|
||||
'/usr/local/bin',
|
||||
'/opt/homebrew/bin',
|
||||
)
|
||||
)
|
||||
logger.debug('Now path is %s', os.environ['PATH'])
|
||||
|
||||
# First parameter must be url
|
||||
useMinimal = False
|
||||
try:
|
||||
uri = sys.argv[1]
|
||||
uri = args[1]
|
||||
|
||||
if uri == '--minimal':
|
||||
useMinimal = True
|
||||
uri = args[2] # And get URI
|
||||
|
||||
if uri == '--test':
|
||||
sys.exit(0)
|
||||
@@ -293,17 +356,28 @@ if __name__ == "__main__":
|
||||
raise Exception()
|
||||
|
||||
ssl = uri[3] == 's'
|
||||
host, UDSClient.ticket, UDSClient.scrambler = uri.split('//')[1].split('/') # type: ignore
|
||||
logger.debug('ssl:%s, host:%s, ticket:%s, scrambler:%s', ssl, host, UDSClient.ticket, UDSClient.scrambler)
|
||||
host, ticket, scrambler = uri.split('//')[1].split('/') # type: ignore
|
||||
logger.debug(
|
||||
'ssl:%s, host:%s, ticket:%s, scrambler:%s',
|
||||
ssl,
|
||||
host,
|
||||
ticket,
|
||||
scrambler,
|
||||
)
|
||||
except Exception:
|
||||
logger.debug('Detected execution without valid URI, exiting')
|
||||
QtWidgets.QMessageBox.critical(None, 'Notice', 'UDS Client Version {}'.format(VERSION), QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None, # type: ignore
|
||||
'Notice',
|
||||
'UDS Client Version {}'.format(VERSION),
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Setup REST api endpoint
|
||||
RestRequest.restApiUrl = '{}://{}/rest/client'.format(['http', 'https'][ssl], host)
|
||||
logger.debug('Setting request URL to %s', RestRequest.restApiUrl)
|
||||
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
|
||||
api = RestApi(
|
||||
'{}://{}/uds/rest/client'.format(['http', 'https'][ssl], host), sslError
|
||||
)
|
||||
|
||||
try:
|
||||
logger.debug('Starting execution')
|
||||
@@ -312,18 +386,23 @@ if __name__ == "__main__":
|
||||
if approveHost(host) is False:
|
||||
raise Exception('Host {} was not approved'.format(host))
|
||||
|
||||
win = UDSClient()
|
||||
win = UDSClient(api, ticket, scrambler)
|
||||
win.show()
|
||||
|
||||
win.start()
|
||||
|
||||
exitVal = app.exec_()
|
||||
exitVal = app.exec()
|
||||
logger.debug('Execution finished correctly')
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Got an exception executing client:')
|
||||
exitVal = 128
|
||||
QtWidgets.QMessageBox.critical(None, 'Error', six.text_type(e), QtWidgets.QMessageBox.Ok)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
None, 'Error', str(e), QtWidgets.QMessageBox.Ok # type: ignore
|
||||
)
|
||||
|
||||
logger.debug('Exiting')
|
||||
sys.exit(exitVal)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
|
75
client-py3/full/src/UDSClientLauncher.py
Normal file
75
client-py3/full/src/UDSClientLauncher.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
import typing
|
||||
|
||||
from uds.log import logger
|
||||
import UDSClient
|
||||
from UDSLauncherMac import Ui_MacLauncher
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
SCRIPT_NAME = 'UDSClientLauncher'
|
||||
|
||||
class UdsApplication(QtWidgets.QApplication):
|
||||
path: str
|
||||
tunnels: typing.List[subprocess.Popen]
|
||||
|
||||
def __init__(self, argv: typing.List[str]) -> None:
|
||||
super().__init__(argv)
|
||||
self.path = os.path.join(os.path.dirname(sys.argv[0]).replace('Resources', 'MacOS'), SCRIPT_NAME)
|
||||
self.tunnels = []
|
||||
self.lastWindowClosed.connect(self.closeTunnels) # type: ignore
|
||||
|
||||
def cleanTunnels(self) -> None:
|
||||
def isRunning(p: subprocess.Popen):
|
||||
try:
|
||||
if p.poll() is None:
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug('Got error polling subprocess: %s', e)
|
||||
return False
|
||||
|
||||
for k in [i for i, tunnel in enumerate(self.tunnels) if not isRunning(tunnel)]:
|
||||
try:
|
||||
del self.tunnels[k]
|
||||
except Exception as e:
|
||||
logger.debug('Error closing tunnel: %s', e)
|
||||
|
||||
def closeTunnels(self) -> None:
|
||||
logger.debug('Closing remaining tunnels')
|
||||
for tunnel in self.tunnels:
|
||||
logger.debug('Checking %s - "%s"', tunnel, tunnel.poll())
|
||||
if tunnel.poll() is None: # Running
|
||||
logger.info('Found running tunnel %s, closing it', tunnel.pid)
|
||||
tunnel.kill()
|
||||
|
||||
def event(self, evnt: QtCore.QEvent) -> bool:
|
||||
if evnt.type() == QtCore.QEvent.FileOpen:
|
||||
fe = typing.cast(QtGui.QFileOpenEvent, evnt)
|
||||
logger.debug('Got url: %s', fe.url().url())
|
||||
fe.accept()
|
||||
logger.debug('Spawning %s', self.path)
|
||||
# First, remove all finished tunnel processed from check queue
|
||||
self.cleanTunnels()
|
||||
# And now add a new one
|
||||
self.tunnels.append(subprocess.Popen([self.path, fe.url().url()]))
|
||||
|
||||
return super().event(evnt)
|
||||
|
||||
|
||||
def main(args: typing.List[str]):
|
||||
if len(args) > 1:
|
||||
UDSClient.main(args)
|
||||
else:
|
||||
app = UdsApplication(sys.argv)
|
||||
window = QtWidgets.QMainWindow()
|
||||
Ui_MacLauncher().setupUi(window)
|
||||
|
||||
window.showMinimized()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(args=sys.argv)
|
||||
|
75
client-py3/full/src/UDSLauncherMac.py
Normal file
75
client-py3/full/src/UDSLauncherMac.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'UDSLauncherMac.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MacLauncher(object):
|
||||
def setupUi(self, MacLauncher):
|
||||
MacLauncher.setObjectName("MacLauncher")
|
||||
MacLauncher.setWindowModality(QtCore.Qt.NonModal)
|
||||
MacLauncher.resize(235, 120)
|
||||
MacLauncher.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/images/logo-uds-small"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
MacLauncher.setWindowIcon(icon)
|
||||
MacLauncher.setWindowOpacity(1.0)
|
||||
self.centralwidget = QtWidgets.QWidget(MacLauncher)
|
||||
self.centralwidget.setAutoFillBackground(True)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
|
||||
self.verticalLayout_2.setContentsMargins(4, 4, 4, 4)
|
||||
self.verticalLayout_2.setSpacing(4)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.frame = QtWidgets.QFrame(self.centralwidget)
|
||||
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
|
||||
self.frame.setObjectName("frame")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.frame)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.topLabel = QtWidgets.QLabel(self.frame)
|
||||
self.topLabel.setTextFormat(QtCore.Qt.RichText)
|
||||
self.topLabel.setObjectName("topLabel")
|
||||
self.verticalLayout.addWidget(self.topLabel)
|
||||
self.image = QtWidgets.QLabel(self.frame)
|
||||
self.image.setMinimumSize(QtCore.QSize(0, 32))
|
||||
self.image.setAutoFillBackground(True)
|
||||
self.image.setText("")
|
||||
self.image.setPixmap(QtGui.QPixmap(":/images/logo-uds-small"))
|
||||
self.image.setScaledContents(False)
|
||||
self.image.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.image.setObjectName("image")
|
||||
self.verticalLayout.addWidget(self.image)
|
||||
self.label_2 = QtWidgets.QLabel(self.frame)
|
||||
self.label_2.setTextFormat(QtCore.Qt.RichText)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.verticalLayout.addWidget(self.label_2)
|
||||
self.verticalLayout_2.addWidget(self.frame)
|
||||
MacLauncher.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(MacLauncher)
|
||||
QtCore.QMetaObject.connectSlotsByName(MacLauncher)
|
||||
|
||||
def retranslateUi(self, MacLauncher):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MacLauncher.setWindowTitle(_translate("MacLauncher", "UDS Launcher"))
|
||||
self.topLabel.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:12pt; font-weight:600;\">UDS Launcher</span></p></body></html>"))
|
||||
self.label_2.setText(_translate("MacLauncher", "<html><head/><body><p align=\"center\"><span style=\" font-size:6pt;\">Closing this window will end all UDS tunnels</span></p></body></html>"))
|
||||
import UDSResources_rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
MacLauncher = QtWidgets.QMainWindow()
|
||||
ui = Ui_MacLauncher()
|
||||
ui.setupUi(MacLauncher)
|
||||
MacLauncher.show()
|
||||
sys.exit(app.exec_())
|
113
client-py3/full/src/UDSLauncherMac.ui
Normal file
113
client-py3/full/src/UDSLauncherMac.ui
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MacLauncher</class>
|
||||
<widget class="QMainWindow" name="MacLauncher">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::NonModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>235</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>ArrowCursor</cursorShape>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>UDS Launcher</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="UDSResources.qrc">
|
||||
<normaloff>:/images/logo-uds-small</normaloff>:/images/logo-uds-small</iconset>
|
||||
</property>
|
||||
<property name="windowOpacity">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="topLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:12pt; font-weight:600;">UDS Launcher</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="image">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="UDSResources.qrc">:/images/logo-uds-small</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="center"><span style=" font-size:6pt;">Closing this window will end all UDS tunnels</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="UDSResources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@@ -2,158 +2,13 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2)
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x08\xed\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x25\x00\x00\x00\x30\x08\x06\x00\x00\x00\x96\x85\xb3\x2b\
|
||||
\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
|
||||
\xa7\x93\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\
|
||||
\xdf\x03\x1b\x0e\x10\x3b\x1e\x53\x78\x6b\x00\x00\x08\x7a\x49\x44\
|
||||
\x41\x54\x58\xc3\xed\x98\x7b\x70\x54\xf5\x15\xc7\x3f\xbf\xfb\xd8\
|
||||
\x7b\xb3\x79\x42\x70\x09\x08\xa8\xa5\x20\x54\x4a\xaa\xa5\x6b\xad\
|
||||
\x81\xfa\x18\xf1\x3a\xad\x53\x67\xaa\x16\xad\xd1\x5a\x75\xc4\x19\
|
||||
\xa7\x63\xeb\xab\x1a\x94\x91\x91\xaa\xb5\x76\x3a\xd3\x97\xa9\x75\
|
||||
\x98\x96\xd2\x86\x19\x5b\x47\x85\x61\xd5\x1a\x35\xc1\x16\x57\xa8\
|
||||
\xe1\xfd\x0e\x04\x03\x92\x9b\x40\x4c\x76\x37\xd9\xec\x7d\xfc\xfa\
|
||||
\xc7\xde\xd0\x4d\xd8\x4d\x42\xa1\xd3\x3f\xf4\xcc\xec\x24\xfb\xdb\
|
||||
\x73\xcf\xef\xfb\x3b\xe7\x7b\xce\x3d\xbf\x03\x9f\xc9\x67\xf2\x7f\
|
||||
\x94\x58\x3c\x72\x5a\xcf\x5f\xb1\xc8\xe4\x8a\x45\xe6\xa8\x7a\x62\
|
||||
\x2c\x40\xac\xa8\x9d\xfb\xdd\x00\xa6\x07\x9f\xb3\x81\xb3\x1c\x47\
|
||||
\x96\xef\xde\xe3\x86\xf7\xb7\xba\x66\x67\x97\x27\x92\x29\xc9\xc0\
|
||||
\x80\x74\x84\xa0\x17\xf8\x18\x68\x03\xb6\x34\x36\xa4\xf7\xe4\x02\
|
||||
\x6c\x6c\x48\x8f\x1d\xd4\x20\x90\xc0\x33\xa5\xc0\xc5\xc0\x0d\xc0\
|
||||
\x95\x01\x98\xec\xc3\x02\x76\xed\x76\x59\xf7\x16\x74\xa7\x2a\xf1\
|
||||
\xb4\x08\x52\x14\x81\x00\x55\xf1\xd0\x45\x0f\xaa\xdf\x83\xf0\xd3\
|
||||
\x08\xd9\x8f\xe2\xf7\xf5\x80\xf7\x5b\x50\x7f\xd7\xd8\x90\x3e\x50\
|
||||
\x08\xd8\x88\x9e\x7a\x73\xd3\xc4\x99\x9e\x27\x7f\x13\x80\x39\x49\
|
||||
\x14\x05\x7e\xf9\x82\x4a\x4b\xea\x01\x12\xe6\xe5\xf8\x98\x20\x3d\
|
||||
\x94\xa2\x32\xb4\x8a\x89\x08\xc3\x40\x75\x93\x18\x99\x7d\x14\xa5\
|
||||
\x37\x52\x92\x7a\x9b\xe2\x54\x13\x9a\xdb\x71\x50\x0a\xf5\xa6\xc6\
|
||||
\x86\xf4\x86\x7c\xc0\x46\x02\x25\x5e\x6e\x9a\xb0\x24\x5c\xa4\x2c\
|
||||
\x93\xb2\x80\x82\x80\xf8\xf6\x49\xfc\xb4\x79\x15\xaa\xec\x47\xe0\
|
||||
\xa2\x16\x8f\x43\x1b\x37\x29\x8b\x58\x4a\x40\x20\x85\x0a\x68\x80\
|
||||
\x47\x79\xe2\x35\xaa\x3a\x1e\x46\x73\x0f\xef\x01\x65\x4e\x63\x43\
|
||||
\xda\x19\x6e\x57\x2d\x84\x68\x42\x0d\xea\x79\x53\x94\xab\xcb\x4a\
|
||||
\xd4\x1a\xd3\x28\x8c\x7d\x62\xe5\x00\x42\xfa\x6c\x6d\xaf\x46\x33\
|
||||
\x34\xb4\xca\x29\x08\x45\x19\x76\x72\x1f\x81\x8b\x40\x92\x36\x2e\
|
||||
\x20\xe4\xb4\x53\x94\x6e\xa9\x04\xd9\x74\x60\x9b\xdb\x7a\x52\x04\
|
||||
\x0a\x6d\xd6\xb5\x1e\x6f\xdb\x9e\xf4\xe6\xdd\xad\x03\x8e\xeb\x15\
|
||||
\x76\xa7\xa6\x38\x7c\xb3\xfa\xaf\x5c\x32\xbd\x09\x69\x54\x9c\x04\
|
||||
\x68\xa8\x64\x3d\x97\x36\x2f\xc0\x57\x0c\x80\x0b\xf3\xd2\x62\x24\
|
||||
\x4e\xe9\x21\x5e\xde\xba\xab\x7f\xed\xc1\xf6\xcc\x88\x19\x5a\x56\
|
||||
\xd4\x43\xed\x25\x2f\x32\x6d\xdc\xfe\xec\xbe\xa3\xa5\xbc\xf4\x72\
|
||||
\x51\x8e\x1d\x54\x6d\x9d\xce\xaa\xe5\xee\x80\xa6\xb1\xe4\x9f\x9b\
|
||||
\x52\x6d\x76\x97\x4b\x21\x27\x78\xbe\xca\xb4\xca\x83\xdc\xf1\x95\
|
||||
\x67\xa8\x30\xbb\xf0\x65\xe1\xb3\x4a\xa1\xa0\xbb\xed\x08\x99\x01\
|
||||
\xd8\x7d\x4a\xa0\x56\x2e\x77\xb2\xc0\x9e\x72\xb7\xfb\x52\xd6\x35\
|
||||
\x6f\x4c\xd2\xdd\xe3\xa1\xaa\xf9\xf9\x35\xe0\x9a\x54\x4f\xfe\x07\
|
||||
\xb7\xcf\x5d\x42\x99\xd1\x8d\xe3\x87\x0a\x96\x46\xdd\x69\x47\x48\
|
||||
\x07\x60\xc3\x29\x87\x6f\x50\xfe\xfc\xb4\xbb\xaa\xab\x3b\xb3\x74\
|
||||
\xfd\xc6\x24\x7d\x7d\x3e\x4a\x01\xde\xf7\xbb\xc5\xcc\xab\x5a\xc7\
|
||||
\x3d\x17\xde\xc7\xb9\xe5\x3b\xba\xd3\x5e\x78\xf7\x70\x40\x8a\x9f\
|
||||
\x44\x75\x6d\x84\xf4\xed\xc6\x86\x74\x67\xbe\x0a\x5f\x10\xd4\x8f\
|
||||
\x9e\x1b\xcf\xca\xe5\xd9\x6c\x3d\xd2\x7d\x83\x59\x77\xcf\x42\x31\
|
||||
\xbe\xac\x94\x0f\xb6\x26\xf0\xfc\xc2\x07\x70\xfc\x10\x73\x26\x34\
|
||||
\xf1\xf0\x57\xbf\x3b\xf0\xc3\x79\x77\x3d\x74\xf4\x58\xe9\xd9\xc0\
|
||||
\x75\xc0\x8b\x20\xfa\x34\xaf\x0b\xd5\xeb\x06\x78\xaf\x90\x8d\x82\
|
||||
\xa0\x7e\x7e\xff\x71\x00\xe5\xef\x1b\x6b\x96\x1c\xef\x49\x1d\x0b\
|
||||
\x1b\xda\xd2\x19\xb3\x6e\x61\xdf\xe1\x89\xec\x6b\xeb\x1f\xd1\xb3\
|
||||
\x12\x41\x59\xe8\x58\xd5\xc5\x93\xd7\x2e\x8d\xdd\x3a\xd1\x3c\xfa\
|
||||
\x44\xd9\x2b\xc0\x5d\x8e\x56\x3c\xc3\xc8\xec\x7b\x5b\xf5\x93\x80\
|
||||
\x7c\x17\x38\xf5\x8a\x7e\xfd\x83\x28\xba\xc2\x9a\xd9\xb3\x6a\xae\
|
||||
\x39\xcc\x6a\x8e\xf4\x4c\x26\xec\xac\xa2\x38\x75\x27\x57\x5e\x12\
|
||||
\xa6\x72\x9c\x36\x96\xe8\xaf\x05\x6e\xb5\xa2\xf6\x71\x80\x4b\x6f\
|
||||
\x9b\x7b\xa3\xee\x7c\xbc\x5a\xf1\x7b\x2e\x6c\x6c\xc8\xb4\x9c\x72\
|
||||
\xf6\xbd\xf4\x2c\xbe\xa6\xd2\xba\xa3\xd5\xc7\x4e\x4e\x26\xa4\x81\
|
||||
\x6b\x5e\x4f\x52\xb9\x8d\x0d\x2d\x7d\xa4\x07\x24\x42\x8c\xe2\x34\
|
||||
\xf8\x06\xf0\xc4\xe0\x82\x99\xde\x76\x4c\xf1\x7b\x5e\x02\xf5\x50\
|
||||
\xa1\x8e\x61\x74\xa2\x0b\xde\xd3\xfd\x96\xa4\x22\x82\xda\xa2\x18\
|
||||
\xf8\xe5\x4b\xb1\x13\xb3\x89\x6f\x4e\x8d\xb5\x0b\xb9\x37\x16\x8f\
|
||||
\xdc\x92\xad\x7d\xc6\x3b\xa0\xdc\x02\xb2\xfb\x94\xba\x84\xa1\x1e\
|
||||
\x0b\x85\x91\x99\x5d\x99\xca\x75\x53\x5d\xc3\x3a\xf1\x94\x9a\xd9\
|
||||
\x48\xa8\x73\x3e\x73\x66\xea\x44\xab\xc3\x64\x9c\x51\xab\xe6\xc7\
|
||||
\x52\x32\xe5\x9a\x8b\x6d\x7f\x34\xc5\x51\x3d\xb5\x72\x79\xa6\x0f\
|
||||
\xf8\x83\x9e\xa8\xe3\x44\x4d\x94\xe0\x85\xe6\xe1\x54\xfc\x9a\xbd\
|
||||
\xad\x09\x5a\x3f\xca\x14\xac\x5f\xb9\xaf\x53\x21\xb8\x6a\x2c\x24\
|
||||
\x1c\x53\x9d\x42\x29\x5d\x2a\x9c\x2d\x5d\x7a\x6a\xc5\x7f\x7c\x2b\
|
||||
\xc1\x2d\xfe\x3e\xfd\x7a\x2d\x5b\x76\x7e\x42\x22\xe9\x8d\xc6\x2f\
|
||||
\x01\x4c\x3a\x23\xa0\x6a\xeb\x74\x56\x3e\x99\xf0\x41\x7c\x4f\xef\
|
||||
\x7d\x0c\xc5\xd9\x3d\x04\x58\xa6\xfc\x57\x74\x25\xe7\xb2\x63\x6f\
|
||||
\x02\xcf\x1b\xd1\x94\x0b\x6c\x1a\x0b\x28\x75\x34\x85\x2d\xcd\x7e\
|
||||
\x16\xd8\x72\x67\x6f\xf5\xfc\xbe\x22\xc5\xdd\x5e\xe3\x19\x57\x83\
|
||||
\x28\x09\xce\x6f\x20\xf5\xd9\x24\x3a\xd7\x30\xa1\x22\x4d\x69\x49\
|
||||
\xc1\x32\xf1\x82\x15\xb5\x57\x9c\xb9\xf0\x9d\x48\x70\xef\x49\x25\
|
||||
\xf3\xce\x9f\x8c\x9e\x7b\x10\xb2\xf7\xc4\xb2\x6f\x7c\x9d\xfe\xd0\
|
||||
\xdd\xfc\x6b\x87\x87\xeb\xe5\x25\xfc\x0b\xc0\x03\x63\xbd\x7c\xa8\
|
||||
\x63\xc1\xb2\xa5\xd9\x0f\xfe\x92\xa9\x9e\x2f\x9b\x84\xb7\xf7\x7c\
|
||||
\x35\xf3\xde\x2c\xd7\xfc\x36\x08\x23\xeb\x30\x7d\x3a\xe9\xee\x35\
|
||||
\x14\x85\xba\x99\x58\xa9\xe5\xf6\x24\xcf\x00\x8f\x59\x51\x3b\x39\
|
||||
\xfc\x12\x72\xda\x9e\xaa\xad\xd3\xb3\xd9\xf8\x13\x8e\x01\x37\x2a\
|
||||
\x99\xf5\x7f\x2b\xea\x9c\x85\xe2\xbc\x0f\x02\x3c\x7d\x1a\x8a\x3e\
|
||||
\x89\x9d\x7b\xd3\x88\xec\x1b\x3b\x03\x3c\x02\xd4\x59\x51\xbb\x77\
|
||||
\xac\x80\xc6\x54\xa7\xf2\x12\x3f\x78\x51\xd7\xd6\xa9\x8f\x81\xff\
|
||||
\xa0\x1b\xbe\xa3\xd4\xd7\x2f\x45\x4b\x2c\xc3\x50\xdb\xb9\xf9\xda\
|
||||
\xf1\x47\x5d\x4f\x3e\x60\x45\xed\x55\xf9\xae\x69\x67\x1c\xd4\x10\
|
||||
\xaf\x2d\x77\xa8\xad\x53\xbe\x84\xf4\x1f\x15\x92\x8b\x7c\xa1\x98\
|
||||
\xe7\x4e\x29\xda\xb6\xb0\xa6\xf4\xfe\x05\x73\x8f\x6e\x1f\xe4\xcf\
|
||||
\xa9\x00\x2a\x78\xfb\x1d\x8d\x8c\xb1\x78\xe4\x24\x9d\x9b\x1f\xd1\
|
||||
\xab\x6f\x7f\x3c\xb4\x60\x73\x6b\x44\xcb\x67\xa3\x90\xcd\x11\xf7\
|
||||
\x3a\x5d\x50\x23\xd9\x1c\x66\x7b\x7c\x2c\x1e\xf9\x62\x2c\x1e\x39\
|
||||
\xa7\xd0\xfe\x62\xd8\x0f\xe7\x02\x1b\xac\xa8\x5d\x55\x60\x83\x1f\
|
||||
\x00\x33\xad\xa8\x7d\x6f\xb0\x76\x14\xd8\x09\xf4\x07\x49\x53\x01\
|
||||
\x84\x81\x57\x81\xa7\xac\xa8\x9d\xca\xb9\x6d\xcf\xc8\x36\x7a\xf4\
|
||||
\x02\xbb\x80\x09\x40\x0d\x50\x6f\x45\xed\x67\x73\xb9\xa7\xe5\xc9\
|
||||
\xc6\xca\x11\x0e\x6f\x02\xc5\x39\xdf\xcf\x02\x2e\xb5\xa2\xf6\xfe\
|
||||
\x1c\xf0\x4a\x00\xfe\xfd\x58\x3c\x72\xbd\x15\xb5\x77\x05\x3f\x6d\
|
||||
\x08\xfa\xaa\xb5\x39\xba\x26\xf0\x7a\x2c\x1e\x29\xb5\xa2\xf6\xe3\
|
||||
\x83\xdc\x53\x38\x7d\xd1\x86\xcd\x20\x7c\x2b\x6a\xff\x02\x58\x01\
|
||||
\xd4\x05\xeb\x93\x80\x72\x60\x48\x53\x67\x45\xed\x34\xb0\x18\xe8\
|
||||
\x0d\x0e\x33\xd4\xe0\x99\x90\x61\x59\xd6\x0c\x58\xb1\x78\x64\x8a\
|
||||
\x15\xb5\xdb\x63\xf1\xc8\xcf\x80\xa7\x63\xf1\x48\x0b\x90\x00\x8e\
|
||||
\x00\x6d\x56\xd4\xde\x0a\xec\x1c\xe4\x69\xbe\xf0\x89\x42\x23\xa0\
|
||||
\xff\xa2\x8c\x78\xc1\x4b\x58\x0f\x00\xff\x38\x16\x8f\x7c\x39\x98\
|
||||
\xda\x8c\x03\x66\x03\x97\xc5\xe2\x91\x69\xc0\x8b\x56\xd4\x7e\xbd\
|
||||
\x90\xa7\xbc\x02\xa7\x3e\x71\x69\x06\xfc\x31\xce\xb3\xce\x0a\x0e\
|
||||
\xd0\x11\x8b\x47\xae\x02\x34\x2b\x6a\xaf\x1b\xec\x14\x62\xf1\x48\
|
||||
\x51\x30\x66\x8a\x02\x8f\xc6\xe2\x91\xb6\x41\xfe\x29\xc3\xdc\x7f\
|
||||
\x10\xb0\x63\xf1\xc8\x75\x05\x42\x73\x25\xf0\xfe\x68\x21\x8c\xc5\
|
||||
\x23\xa5\xc0\x43\xc0\xab\x56\xd4\xee\x03\xce\x07\x9e\x1b\xa6\xd7\
|
||||
\x6f\x45\x6d\x1b\xd8\x1c\x80\x2f\x39\x29\x14\xaf\x35\xcd\xe1\xda\
|
||||
\x05\xdb\x08\x5c\xbc\x1a\x78\x12\x78\x27\x48\xf7\x69\x41\x46\xe9\
|
||||
\x56\xd4\x5e\x94\xe3\x95\x04\x70\x6f\x30\xa9\x53\x03\xc3\x17\x00\
|
||||
\xdf\x01\xfe\x68\x45\xed\xe7\x72\x74\xd7\x01\x4e\x00\x6e\x7f\x70\
|
||||
\xa9\xa8\x06\xee\x03\x5a\xac\xa8\xfd\x50\x5e\x7e\xac\x58\xf9\xb8\
|
||||
\x72\x7b\xed\x32\x7f\xcd\xfa\x99\xc5\xba\x71\xec\x56\x29\xb5\xb9\
|
||||
\x42\x78\x2a\xc8\x0e\x21\xbc\x37\x16\xce\xfb\xa4\x39\x57\x7f\x4d\
|
||||
\xf3\xac\x07\x75\xb3\xcb\x43\xf8\x02\x29\x84\x94\x6a\x1a\xe4\x81\
|
||||
\xe2\xf1\x9d\x6f\xce\xff\x3c\x27\xa6\x22\xcf\x3f\xff\xfb\xb2\xc5\
|
||||
\x8b\xef\xec\x7d\x63\x53\xf9\x65\xd2\xd7\xae\x90\x52\x9d\x0c\xd2\
|
||||
\x45\xc8\x43\x8a\x9a\x5e\xb7\xf0\xa2\xe4\x87\x00\x0d\xaf\xdc\xc4\
|
||||
\xa2\x6f\xfd\x65\x28\xa8\xfa\xfa\xfa\xf3\x84\x92\x69\xf3\xdd\x70\
|
||||
\xcd\x40\xdf\x94\x23\xbd\x1d\x0b\xba\x8c\xe2\x43\x45\xba\x69\x9b\
|
||||
\xba\xd9\xa9\xeb\x66\xa7\x16\x64\xce\x78\x84\xff\x51\x26\x35\xf5\
|
||||
\x6c\x29\x55\x0f\x29\xfc\xfe\xc4\xf4\xce\x70\xc5\xf6\x73\x14\x35\
|
||||
\x83\xa2\xa6\x3b\x15\xb5\xcf\x00\xe1\x03\xdd\xd9\x22\x29\x76\x3a\
|
||||
\xe9\x09\x5a\xdf\x27\x5f\x38\xae\x86\x7a\x8b\x9d\xfe\xaa\x94\xef\
|
||||
\x99\x99\xf2\xaa\xc6\xa9\x5a\xa8\xc7\x01\x79\xe8\xee\xbb\x17\xf7\
|
||||
\xe5\x23\xba\x2f\xfd\xd0\xd7\x84\xe2\xce\x30\x4b\x0e\x9a\x66\xc9\
|
||||
\xc1\xcf\x01\x21\x20\x02\x1c\x00\xa6\x06\xc5\xb5\x03\xa9\x74\x84\
|
||||
\xc2\x87\xcf\x0f\x6c\x54\x19\x25\x6d\xfb\x80\x99\xc0\xd1\x6c\x2f\
|
||||
\x3e\xd8\x9a\xb2\x39\x1b\x2a\x79\xb9\x6e\x76\x66\xca\xab\xde\x3d\
|
||||
\x14\x84\xd9\x05\x7a\x82\xaa\x7e\x18\x84\x07\xec\xcd\x07\xea\xa3\
|
||||
\x60\x9a\xfb\x41\x90\x65\x22\xa7\xd2\xbb\x01\x6f\xd4\x20\x4b\x1d\
|
||||
\x60\x4d\xa0\x37\xb8\xf6\x56\x4e\x06\x1b\xc1\xe7\x78\xc0\x21\x2d\
|
||||
\xd0\xcd\xb5\x2b\x81\x0f\x83\x35\xf7\x7f\x3a\x6f\xaf\xaf\xaf\xcf\
|
||||
\xfb\xff\x67\xf2\xa9\x90\x7f\x03\xbb\x9c\x9c\x9c\x32\xd8\x63\xca\
|
||||
\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\x00\x00\x8e\x8c\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
@@ -2437,6 +2292,151 @@ qt_resource_data = b"\
|
||||
\x00\x80\x10\x0c\x00\x00\x00\x10\x82\x01\x00\x00\x00\x42\x30\x00\
|
||||
\x00\x00\xe0\x96\xff\x7f\x00\x27\x97\xdb\xb5\x4d\x29\xcb\x9d\x00\
|
||||
\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\x00\x00\x08\xed\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x25\x00\x00\x00\x30\x08\x06\x00\x00\x00\x96\x85\xb3\x2b\
|
||||
\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\
|
||||
\xa7\x93\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\
|
||||
\xdf\x03\x1b\x0e\x10\x3b\x1e\x53\x78\x6b\x00\x00\x08\x7a\x49\x44\
|
||||
\x41\x54\x58\xc3\xed\x98\x7b\x70\x54\xf5\x15\xc7\x3f\xbf\xfb\xd8\
|
||||
\x7b\xb3\x79\x42\x70\x09\x08\xa8\xa5\x20\x54\x4a\xaa\xa5\x6b\xad\
|
||||
\x81\xfa\x18\xf1\x3a\xad\x53\x67\xaa\x16\xad\xd1\x5a\x75\xc4\x19\
|
||||
\xa7\x63\xeb\xab\x1a\x94\x91\x91\xaa\xb5\x76\x3a\xd3\x97\xa9\x75\
|
||||
\x98\x96\xd2\x86\x19\x5b\x47\x85\x61\xd5\x1a\x35\xc1\x16\x57\xa8\
|
||||
\xe1\xfd\x0e\x04\x03\x92\x9b\x40\x4c\x76\x37\xd9\xec\x7d\xfc\xfa\
|
||||
\xc7\xde\xd0\x4d\xd8\x4d\x42\xa1\xd3\x3f\xf4\xcc\xec\x24\xfb\xdb\
|
||||
\x73\xcf\xef\xfb\x3b\xe7\x7b\xce\x3d\xbf\x03\x9f\xc9\x67\xf2\x7f\
|
||||
\x94\x58\x3c\x72\x5a\xcf\x5f\xb1\xc8\xe4\x8a\x45\xe6\xa8\x7a\x62\
|
||||
\x2c\x40\xac\xa8\x9d\xfb\xdd\x00\xa6\x07\x9f\xb3\x81\xb3\x1c\x47\
|
||||
\x96\xef\xde\xe3\x86\xf7\xb7\xba\x66\x67\x97\x27\x92\x29\xc9\xc0\
|
||||
\x80\x74\x84\xa0\x17\xf8\x18\x68\x03\xb6\x34\x36\xa4\xf7\xe4\x02\
|
||||
\x6c\x6c\x48\x8f\x1d\xd4\x20\x90\xc0\x33\xa5\xc0\xc5\xc0\x0d\xc0\
|
||||
\x95\x01\x98\xec\xc3\x02\x76\xed\x76\x59\xf7\x16\x74\xa7\x2a\xf1\
|
||||
\xb4\x08\x52\x14\x81\x00\x55\xf1\xd0\x45\x0f\xaa\xdf\x83\xf0\xd3\
|
||||
\x08\xd9\x8f\xe2\xf7\xf5\x80\xf7\x5b\x50\x7f\xd7\xd8\x90\x3e\x50\
|
||||
\x08\xd8\x88\x9e\x7a\x73\xd3\xc4\x99\x9e\x27\x7f\x13\x80\x39\x49\
|
||||
\x14\x05\x7e\xf9\x82\x4a\x4b\xea\x01\x12\xe6\xe5\xf8\x98\x20\x3d\
|
||||
\x94\xa2\x32\xb4\x8a\x89\x08\xc3\x40\x75\x93\x18\x99\x7d\x14\xa5\
|
||||
\x37\x52\x92\x7a\x9b\xe2\x54\x13\x9a\xdb\x71\x50\x0a\xf5\xa6\xc6\
|
||||
\x86\xf4\x86\x7c\xc0\x46\x02\x25\x5e\x6e\x9a\xb0\x24\x5c\xa4\x2c\
|
||||
\x93\xb2\x80\x82\x80\xf8\xf6\x49\xfc\xb4\x79\x15\xaa\xec\x47\xe0\
|
||||
\xa2\x16\x8f\x43\x1b\x37\x29\x8b\x58\x4a\x40\x20\x85\x0a\x68\x80\
|
||||
\x47\x79\xe2\x35\xaa\x3a\x1e\x46\x73\x0f\xef\x01\x65\x4e\x63\x43\
|
||||
\xda\x19\x6e\x57\x2d\x84\x68\x42\x0d\xea\x79\x53\x94\xab\xcb\x4a\
|
||||
\xd4\x1a\xd3\x28\x8c\x7d\x62\xe5\x00\x42\xfa\x6c\x6d\xaf\x46\x33\
|
||||
\x34\xb4\xca\x29\x08\x45\x19\x76\x72\x1f\x81\x8b\x40\x92\x36\x2e\
|
||||
\x20\xe4\xb4\x53\x94\x6e\xa9\x04\xd9\x74\x60\x9b\xdb\x7a\x52\x04\
|
||||
\x0a\x6d\xd6\xb5\x1e\x6f\xdb\x9e\xf4\xe6\xdd\xad\x03\x8e\xeb\x15\
|
||||
\x76\xa7\xa6\x38\x7c\xb3\xfa\xaf\x5c\x32\xbd\x09\x69\x54\x9c\x04\
|
||||
\x68\xa8\x64\x3d\x97\x36\x2f\xc0\x57\x0c\x80\x0b\xf3\xd2\x62\x24\
|
||||
\x4e\xe9\x21\x5e\xde\xba\xab\x7f\xed\xc1\xf6\xcc\x88\x19\x5a\x56\
|
||||
\xd4\x43\xed\x25\x2f\x32\x6d\xdc\xfe\xec\xbe\xa3\xa5\xbc\xf4\x72\
|
||||
\x51\x8e\x1d\x54\x6d\x9d\xce\xaa\xe5\xee\x80\xa6\xb1\xe4\x9f\x9b\
|
||||
\x52\x6d\x76\x97\x4b\x21\x27\x78\xbe\xca\xb4\xca\x83\xdc\xf1\x95\
|
||||
\x67\xa8\x30\xbb\xf0\x65\xe1\xb3\x4a\xa1\xa0\xbb\xed\x08\x99\x01\
|
||||
\xd8\x7d\x4a\xa0\x56\x2e\x77\xb2\xc0\x9e\x72\xb7\xfb\x52\xd6\x35\
|
||||
\x6f\x4c\xd2\xdd\xe3\xa1\xaa\xf9\xf9\x35\xe0\x9a\x54\x4f\xfe\x07\
|
||||
\xb7\xcf\x5d\x42\x99\xd1\x8d\xe3\x87\x0a\x96\x46\xdd\x69\x47\x48\
|
||||
\x07\x60\xc3\x29\x87\x6f\x50\xfe\xfc\xb4\xbb\xaa\xab\x3b\xb3\x74\
|
||||
\xfd\xc6\x24\x7d\x7d\x3e\x4a\x01\xde\xf7\xbb\xc5\xcc\xab\x5a\xc7\
|
||||
\x3d\x17\xde\xc7\xb9\xe5\x3b\xba\xd3\x5e\x78\xf7\x70\x40\x8a\x9f\
|
||||
\x44\x75\x6d\x84\xf4\xed\xc6\x86\x74\x67\xbe\x0a\x5f\x10\xd4\x8f\
|
||||
\x9e\x1b\xcf\xca\xe5\xd9\x6c\x3d\xd2\x7d\x83\x59\x77\xcf\x42\x31\
|
||||
\xbe\xac\x94\x0f\xb6\x26\xf0\xfc\xc2\x07\x70\xfc\x10\x73\x26\x34\
|
||||
\xf1\xf0\x57\xbf\x3b\xf0\xc3\x79\x77\x3d\x74\xf4\x58\xe9\xd9\xc0\
|
||||
\x75\xc0\x8b\x20\xfa\x34\xaf\x0b\xd5\xeb\x06\x78\xaf\x90\x8d\x82\
|
||||
\xa0\x7e\x7e\xff\x71\x00\xe5\xef\x1b\x6b\x96\x1c\xef\x49\x1d\x0b\
|
||||
\x1b\xda\xd2\x19\xb3\x6e\x61\xdf\xe1\x89\xec\x6b\xeb\x1f\xd1\xb3\
|
||||
\x12\x41\x59\xe8\x58\xd5\xc5\x93\xd7\x2e\x8d\xdd\x3a\xd1\x3c\xfa\
|
||||
\x44\xd9\x2b\xc0\x5d\x8e\x56\x3c\xc3\xc8\xec\x7b\x5b\xf5\x93\x80\
|
||||
\x7c\x17\x38\xf5\x8a\x7e\xfd\x83\x28\xba\xc2\x9a\xd9\xb3\x6a\xae\
|
||||
\x39\xcc\x6a\x8e\xf4\x4c\x26\xec\xac\xa2\x38\x75\x27\x57\x5e\x12\
|
||||
\xa6\x72\x9c\x36\x96\xe8\xaf\x05\x6e\xb5\xa2\xf6\x71\x80\x4b\x6f\
|
||||
\x9b\x7b\xa3\xee\x7c\xbc\x5a\xf1\x7b\x2e\x6c\x6c\xc8\xb4\x9c\x72\
|
||||
\xf6\xbd\xf4\x2c\xbe\xa6\xd2\xba\xa3\xd5\xc7\x4e\x4e\x26\xa4\x81\
|
||||
\x6b\x5e\x4f\x52\xb9\x8d\x0d\x2d\x7d\xa4\x07\x24\x42\x8c\xe2\x34\
|
||||
\xf8\x06\xf0\xc4\xe0\x82\x99\xde\x76\x4c\xf1\x7b\x5e\x02\xf5\x50\
|
||||
\xa1\x8e\x61\x74\xa2\x0b\xde\xd3\xfd\x96\xa4\x22\x82\xda\xa2\x18\
|
||||
\xf8\xe5\x4b\xb1\x13\xb3\x89\x6f\x4e\x8d\xb5\x0b\xb9\x37\x16\x8f\
|
||||
\xdc\x92\xad\x7d\xc6\x3b\xa0\xdc\x02\xb2\xfb\x94\xba\x84\xa1\x1e\
|
||||
\x0b\x85\x91\x99\x5d\x99\xca\x75\x53\x5d\xc3\x3a\xf1\x94\x9a\xd9\
|
||||
\x48\xa8\x73\x3e\x73\x66\xea\x44\xab\xc3\x64\x9c\x51\xab\xe6\xc7\
|
||||
\x52\x32\xe5\x9a\x8b\x6d\x7f\x34\xc5\x51\x3d\xb5\x72\x79\xa6\x0f\
|
||||
\xf8\x83\x9e\xa8\xe3\x44\x4d\x94\xe0\x85\xe6\xe1\x54\xfc\x9a\xbd\
|
||||
\xad\x09\x5a\x3f\xca\x14\xac\x5f\xb9\xaf\x53\x21\xb8\x6a\x2c\x24\
|
||||
\x1c\x53\x9d\x42\x29\x5d\x2a\x9c\x2d\x5d\x7a\x6a\xc5\x7f\x7c\x2b\
|
||||
\xc1\x2d\xfe\x3e\xfd\x7a\x2d\x5b\x76\x7e\x42\x22\xe9\x8d\xc6\x2f\
|
||||
\x01\x4c\x3a\x23\xa0\x6a\xeb\x74\x56\x3e\x99\xf0\x41\x7c\x4f\xef\
|
||||
\x7d\x0c\xc5\xd9\x3d\x04\x58\xa6\xfc\x57\x74\x25\xe7\xb2\x63\x6f\
|
||||
\x02\xcf\x1b\xd1\x94\x0b\x6c\x1a\x0b\x28\x75\x34\x85\x2d\xcd\x7e\
|
||||
\x16\xd8\x72\x67\x6f\xf5\xfc\xbe\x22\xc5\xdd\x5e\xe3\x19\x57\x83\
|
||||
\x28\x09\xce\x6f\x20\xf5\xd9\x24\x3a\xd7\x30\xa1\x22\x4d\x69\x49\
|
||||
\xc1\x32\xf1\x82\x15\xb5\x57\x9c\xb9\xf0\x9d\x48\x70\xef\x49\x25\
|
||||
\xf3\xce\x9f\x8c\x9e\x7b\x10\xb2\xf7\xc4\xb2\x6f\x7c\x9d\xfe\xd0\
|
||||
\xdd\xfc\x6b\x87\x87\xeb\xe5\x25\xfc\x0b\xc0\x03\x63\xbd\x7c\xa8\
|
||||
\x63\xc1\xb2\xa5\xd9\x0f\xfe\x92\xa9\x9e\x2f\x9b\x84\xb7\xf7\x7c\
|
||||
\x35\xf3\xde\x2c\xd7\xfc\x36\x08\x23\xeb\x30\x7d\x3a\xe9\xee\x35\
|
||||
\x14\x85\xba\x99\x58\xa9\xe5\xf6\x24\xcf\x00\x8f\x59\x51\x3b\x39\
|
||||
\xfc\x12\x72\xda\x9e\xaa\xad\xd3\xb3\xd9\xf8\x13\x8e\x01\x37\x2a\
|
||||
\x99\xf5\x7f\x2b\xea\x9c\x85\xe2\xbc\x0f\x02\x3c\x7d\x1a\x8a\x3e\
|
||||
\x89\x9d\x7b\xd3\x88\xec\x1b\x3b\x03\x3c\x02\xd4\x59\x51\xbb\x77\
|
||||
\xac\x80\xc6\x54\xa7\xf2\x12\x3f\x78\x51\xd7\xd6\xa9\x8f\x81\xff\
|
||||
\xa0\x1b\xbe\xa3\xd4\xd7\x2f\x45\x4b\x2c\xc3\x50\xdb\xb9\xf9\xda\
|
||||
\xf1\x47\x5d\x4f\x3e\x60\x45\xed\x55\xf9\xae\x69\x67\x1c\xd4\x10\
|
||||
\xaf\x2d\x77\xa8\xad\x53\xbe\x84\xf4\x1f\x15\x92\x8b\x7c\xa1\x98\
|
||||
\xe7\x4e\x29\xda\xb6\xb0\xa6\xf4\xfe\x05\x73\x8f\x6e\x1f\xe4\xcf\
|
||||
\xa9\x00\x2a\x78\xfb\x1d\x8d\x8c\xb1\x78\xe4\x24\x9d\x9b\x1f\xd1\
|
||||
\xab\x6f\x7f\x3c\xb4\x60\x73\x6b\x44\xcb\x67\xa3\x90\xcd\x11\xf7\
|
||||
\x3a\x5d\x50\x23\xd9\x1c\x66\x7b\x7c\x2c\x1e\xf9\x62\x2c\x1e\x39\
|
||||
\xa7\xd0\xfe\x62\xd8\x0f\xe7\x02\x1b\xac\xa8\x5d\x55\x60\x83\x1f\
|
||||
\x00\x33\xad\xa8\x7d\x6f\xb0\x76\x14\xd8\x09\xf4\x07\x49\x53\x01\
|
||||
\x84\x81\x57\x81\xa7\xac\xa8\x9d\xca\xb9\x6d\xcf\xc8\x36\x7a\xf4\
|
||||
\x02\xbb\x80\x09\x40\x0d\x50\x6f\x45\xed\x67\x73\xb9\xa7\xe5\xc9\
|
||||
\xc6\xca\x11\x0e\x6f\x02\xc5\x39\xdf\xcf\x02\x2e\xb5\xa2\xf6\xfe\
|
||||
\x1c\xf0\x4a\x00\xfe\xfd\x58\x3c\x72\xbd\x15\xb5\x77\x05\x3f\x6d\
|
||||
\x08\xfa\xaa\xb5\x39\xba\x26\xf0\x7a\x2c\x1e\x29\xb5\xa2\xf6\xe3\
|
||||
\x83\xdc\x53\x38\x7d\xd1\x86\xcd\x20\x7c\x2b\x6a\xff\x02\x58\x01\
|
||||
\xd4\x05\xeb\x93\x80\x72\x60\x48\x53\x67\x45\xed\x34\xb0\x18\xe8\
|
||||
\x0d\x0e\x33\xd4\xe0\x99\x90\x61\x59\xd6\x0c\x58\xb1\x78\x64\x8a\
|
||||
\x15\xb5\xdb\x63\xf1\xc8\xcf\x80\xa7\x63\xf1\x48\x0b\x90\x00\x8e\
|
||||
\x00\x6d\x56\xd4\xde\x0a\xec\x1c\xe4\x69\xbe\xf0\x89\x42\x23\xa0\
|
||||
\xff\xa2\x8c\x78\xc1\x4b\x58\x0f\x00\xff\x38\x16\x8f\x7c\x39\x98\
|
||||
\xda\x8c\x03\x66\x03\x97\xc5\xe2\x91\x69\xc0\x8b\x56\xd4\x7e\xbd\
|
||||
\x90\xa7\xbc\x02\xa7\x3e\x71\x69\x06\xfc\x31\xce\xb3\xce\x0a\x0e\
|
||||
\xd0\x11\x8b\x47\xae\x02\x34\x2b\x6a\xaf\x1b\xec\x14\x62\xf1\x48\
|
||||
\x51\x30\x66\x8a\x02\x8f\xc6\xe2\x91\xb6\x41\xfe\x29\xc3\xdc\x7f\
|
||||
\x10\xb0\x63\xf1\xc8\x75\x05\x42\x73\x25\xf0\xfe\x68\x21\x8c\xc5\
|
||||
\x23\xa5\xc0\x43\xc0\xab\x56\xd4\xee\x03\xce\x07\x9e\x1b\xa6\xd7\
|
||||
\x6f\x45\x6d\x1b\xd8\x1c\x80\x2f\x39\x29\x14\xaf\x35\xcd\xe1\xda\
|
||||
\x05\xdb\x08\x5c\xbc\x1a\x78\x12\x78\x27\x48\xf7\x69\x41\x46\xe9\
|
||||
\x56\xd4\x5e\x94\xe3\x95\x04\x70\x6f\x30\xa9\x53\x03\xc3\x17\x00\
|
||||
\xdf\x01\xfe\x68\x45\xed\xe7\x72\x74\xd7\x01\x4e\x00\x6e\x7f\x70\
|
||||
\xa9\xa8\x06\xee\x03\x5a\xac\xa8\xfd\x50\x5e\x7e\xac\x58\xf9\xb8\
|
||||
\x72\x7b\xed\x32\x7f\xcd\xfa\x99\xc5\xba\x71\xec\x56\x29\xb5\xb9\
|
||||
\x42\x78\x2a\xc8\x0e\x21\xbc\x37\x16\xce\xfb\xa4\x39\x57\x7f\x4d\
|
||||
\xf3\xac\x07\x75\xb3\xcb\x43\xf8\x02\x29\x84\x94\x6a\x1a\xe4\x81\
|
||||
\xe2\xf1\x9d\x6f\xce\xff\x3c\x27\xa6\x22\xcf\x3f\xff\xfb\xb2\xc5\
|
||||
\x8b\xef\xec\x7d\x63\x53\xf9\x65\xd2\xd7\xae\x90\x52\x9d\x0c\xd2\
|
||||
\x45\xc8\x43\x8a\x9a\x5e\xb7\xf0\xa2\xe4\x87\x00\x0d\xaf\xdc\xc4\
|
||||
\xa2\x6f\xfd\x65\x28\xa8\xfa\xfa\xfa\xf3\x84\x92\x69\xf3\xdd\x70\
|
||||
\xcd\x40\xdf\x94\x23\xbd\x1d\x0b\xba\x8c\xe2\x43\x45\xba\x69\x9b\
|
||||
\xba\xd9\xa9\xeb\x66\xa7\x16\x64\xce\x78\x84\xff\x51\x26\x35\xf5\
|
||||
\x6c\x29\x55\x0f\x29\xfc\xfe\xc4\xf4\xce\x70\xc5\xf6\x73\x14\x35\
|
||||
\x83\xa2\xa6\x3b\x15\xb5\xcf\x00\xe1\x03\xdd\xd9\x22\x29\x76\x3a\
|
||||
\xe9\x09\x5a\xdf\x27\x5f\x38\xae\x86\x7a\x8b\x9d\xfe\xaa\x94\xef\
|
||||
\x99\x99\xf2\xaa\xc6\xa9\x5a\xa8\xc7\x01\x79\xe8\xee\xbb\x17\xf7\
|
||||
\xe5\x23\xba\x2f\xfd\xd0\xd7\x84\xe2\xce\x30\x4b\x0e\x9a\x66\xc9\
|
||||
\xc1\xcf\x01\x21\x20\x02\x1c\x00\xa6\x06\xc5\xb5\x03\xa9\x74\x84\
|
||||
\xc2\x87\xcf\x0f\x6c\x54\x19\x25\x6d\xfb\x80\x99\xc0\xd1\x6c\x2f\
|
||||
\x3e\xd8\x9a\xb2\x39\x1b\x2a\x79\xb9\x6e\x76\x66\xca\xab\xde\x3d\
|
||||
\x14\x84\xd9\x05\x7a\x82\xaa\x7e\x18\x84\x07\xec\xcd\x07\xea\xa3\
|
||||
\x60\x9a\xfb\x41\x90\x65\x22\xa7\xd2\xbb\x01\x6f\xd4\x20\x4b\x1d\
|
||||
\x60\x4d\xa0\x37\xb8\xf6\x56\x4e\x06\x1b\xc1\xe7\x78\xc0\x21\x2d\
|
||||
\xd0\xcd\xb5\x2b\x81\x0f\x83\x35\xf7\x7f\x3a\x6f\xaf\xaf\xaf\xcf\
|
||||
\xfb\xff\x67\xf2\xa9\x90\x7f\x03\xbb\x9c\x9c\x9c\x32\xd8\x63\xca\
|
||||
\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
"
|
||||
|
||||
qt_resource_name = b"\
|
||||
@@ -2444,21 +2444,21 @@ qt_resource_name = b"\
|
||||
\x07\x03\x7d\xc3\
|
||||
\x00\x69\
|
||||
\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\
|
||||
\x00\x0e\
|
||||
\x01\x8e\xbf\xec\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\
|
||||
\x00\x0c\
|
||||
\x05\xe1\xfc\x77\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x62\x00\x69\x00\x67\
|
||||
\x00\x0e\
|
||||
\x01\x8e\xbf\xec\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x67\x00\x6f\x00\x2d\x00\x75\x00\x64\x00\x73\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\
|
||||
"
|
||||
|
||||
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\x02\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x8e\x90\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x08\xf1\
|
||||
"
|
||||
|
||||
qt_resource_struct_v2 = b"\
|
||||
@@ -2466,9 +2466,9 @@ qt_resource_struct_v2 = b"\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x8e\x90\
|
||||
\x00\x00\x01\x70\xc4\x82\x24\xd0\
|
||||
\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x08\xf1\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x70\xc4\x82\x24\xd0\
|
||||
"
|
||||
|
||||
|
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'UDSWindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -31,7 +31,7 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
VERSION = '3.0.0'
|
||||
VERSION = '3.5.1'
|
||||
|
||||
__title__ = 'udclient'
|
||||
__version__ = VERSION
|
||||
|
@@ -9,6 +9,7 @@ import random
|
||||
import time
|
||||
import select
|
||||
import socketserver
|
||||
import typing
|
||||
|
||||
import paramiko
|
||||
|
||||
@@ -24,7 +25,11 @@ class CheckfingerPrints(paramiko.MissingHostKeyPolicy):
|
||||
if self.fingerPrints:
|
||||
remotefingerPrints = hexlify(key.get_fingerprint()).decode().lower()
|
||||
if remotefingerPrints not in self.fingerPrints.split(','):
|
||||
logger.error("Server {!r} has invalid fingerPrints. ({} vs {})".format(hostname, remotefingerPrints, self.fingerPrints))
|
||||
logger.error(
|
||||
"Server {!r} has invalid fingerPrints. ({} vs {})".format(
|
||||
hostname, remotefingerPrints, self.fingerPrints
|
||||
)
|
||||
)
|
||||
raise paramiko.SSHException(
|
||||
"Server {!r} has invalid fingerPrints".format(hostname)
|
||||
)
|
||||
@@ -36,26 +41,49 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
|
||||
|
||||
class Handler(socketserver.BaseRequestHandler):
|
||||
event: threading.Event
|
||||
thread: 'ForwardThread'
|
||||
ssh_transport: paramiko.Transport
|
||||
chain_host: str
|
||||
chain_port: int
|
||||
|
||||
def handle(self):
|
||||
self.thread.currentConnections += 1
|
||||
|
||||
try:
|
||||
chan = self.ssh_transport.open_channel('direct-tcpip',
|
||||
(self.chain_host, self.chain_port),
|
||||
self.request.getpeername())
|
||||
chan = self.ssh_transport.open_channel(
|
||||
'direct-tcpip',
|
||||
(self.chain_host, self.chain_port),
|
||||
self.request.getpeername(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception('Incoming request to %s:%d failed: %s', self.chain_host, self.chain_port, repr(e))
|
||||
logger.exception(
|
||||
'Incoming request to %s:%d failed: %s',
|
||||
self.chain_host,
|
||||
self.chain_port,
|
||||
repr(e),
|
||||
)
|
||||
return
|
||||
if chan is None:
|
||||
logger.error('Incoming request to %s:%d was rejected by the SSH server.', self.chain_host, self.chain_port)
|
||||
logger.error(
|
||||
'Incoming request to %s:%d was rejected by the SSH server.',
|
||||
self.chain_host,
|
||||
self.chain_port,
|
||||
)
|
||||
return
|
||||
|
||||
logger.debug('Connected! Tunnel open %r -> %r -> %r', self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port))
|
||||
logger.debug(
|
||||
'Connected! Tunnel open %r -> %r -> %r',
|
||||
self.request.getpeername(),
|
||||
chan.getpeername(),
|
||||
(self.chain_host, self.chain_port),
|
||||
)
|
||||
# self.ssh_transport.set_keepalive(10) # Keep alive every 10 seconds...
|
||||
try:
|
||||
while self.event.is_set() is False:
|
||||
r, _w, _x = select.select([self.request, chan], [], [], 1) # pylint: disable=unused-variable
|
||||
r, _w, _x = select.select(
|
||||
[self.request, chan], [], [], 1
|
||||
) # pylint: disable=unused-variable
|
||||
|
||||
if self.request in r:
|
||||
data = self.request.recv(1024)
|
||||
@@ -74,7 +102,10 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
peername = self.request.getpeername()
|
||||
chan.close()
|
||||
self.request.close()
|
||||
logger.debug('Tunnel closed from %r', peername,)
|
||||
logger.debug(
|
||||
'Tunnel closed from %r',
|
||||
peername,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -86,8 +117,21 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
|
||||
class ForwardThread(threading.Thread):
|
||||
status = 0 # Connecting
|
||||
client: typing.Optional[paramiko.SSHClient]
|
||||
fs: typing.Optional[ForwardServer]
|
||||
|
||||
def __init__(self, server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints):
|
||||
def __init__(
|
||||
self,
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
waitTime,
|
||||
fingerPrints,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.client = None
|
||||
self.fs = None
|
||||
@@ -102,7 +146,7 @@ class ForwardThread(threading.Thread):
|
||||
self.redirectPort = redirectPort
|
||||
|
||||
self.waitTime = waitTime
|
||||
|
||||
|
||||
self.fingerPrints = fingerPrints
|
||||
|
||||
self.stopEvent = threading.Event()
|
||||
@@ -116,9 +160,19 @@ class ForwardThread(threading.Thread):
|
||||
if localPort is None:
|
||||
localPort = random.randrange(33000, 53000)
|
||||
|
||||
ft = ForwardThread(self.server, self.port, self.username, self.password, localPort, redirectHost, redirectPort, self.waitTime, self.fingerPrints)
|
||||
ft = ForwardThread(
|
||||
self.server,
|
||||
self.port,
|
||||
self.username,
|
||||
self.password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
self.waitTime,
|
||||
self.fingerPrints,
|
||||
)
|
||||
ft.client = self.client
|
||||
self.client.useCount += 1 # One more using this client
|
||||
self.client.useCount += 1 # type: ignore
|
||||
ft.start()
|
||||
|
||||
while ft.status == 0:
|
||||
@@ -126,7 +180,6 @@ class ForwardThread(threading.Thread):
|
||||
|
||||
return (ft, localPort)
|
||||
|
||||
|
||||
def _timerFnc(self):
|
||||
self.timer = None
|
||||
logger.debug('Timer fnc: %s', self.currentConnections)
|
||||
@@ -138,14 +191,23 @@ class ForwardThread(threading.Thread):
|
||||
if self.client is None:
|
||||
try:
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.useCount = 1 # Custom added variable, to keep track on when to close tunnel
|
||||
self.client.useCount = 1 # type: ignore
|
||||
self.client.load_system_host_keys()
|
||||
self.client.set_missing_host_key_policy(CheckfingerPrints(self.fingerPrints))
|
||||
self.client.set_missing_host_key_policy(
|
||||
CheckfingerPrints(self.fingerPrints)
|
||||
)
|
||||
|
||||
logger.debug('Connecting to ssh host %s:%d ...', self.server, self.port)
|
||||
|
||||
# To disable ssh-ageng asking for passwords: allow_agent=False
|
||||
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5, allow_agent=False)
|
||||
self.client.connect(
|
||||
self.server,
|
||||
self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
timeout=5,
|
||||
allow_agent=False,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('Exception connecting: ')
|
||||
self.status = 2 # Error
|
||||
@@ -173,18 +235,30 @@ class ForwardThread(threading.Thread):
|
||||
self.timer.cancel()
|
||||
|
||||
self.stopEvent.set()
|
||||
self.fs.shutdown()
|
||||
|
||||
if self.fs:
|
||||
self.fs.shutdown()
|
||||
|
||||
if self.client is not None:
|
||||
self.client.useCount -= 1
|
||||
if self.client.useCount == 0:
|
||||
self.client.useCount -= 1 # type: ignore
|
||||
if self.client.useCount == 0: # type: ignore
|
||||
self.client.close()
|
||||
self.client = None # Clean up
|
||||
except Exception:
|
||||
logger.exception('Exception stopping')
|
||||
|
||||
|
||||
def forward(server, port, username, password, redirectHost, redirectPort, localPort=None, waitTime=10, fingerPrints=None):
|
||||
def forward(
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
localPort=None,
|
||||
waitTime=10,
|
||||
fingerPrints=None,
|
||||
):
|
||||
'''
|
||||
Instantiates an ssh connection to server:port
|
||||
Returns the Thread created and the local redirected port as a list: (thread, port)
|
||||
@@ -194,10 +268,28 @@ def forward(server, port, username, password, redirectHost, redirectPort, localP
|
||||
if localPort is None:
|
||||
localPort = random.randrange(40000, 50000)
|
||||
|
||||
logger.debug('Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
||||
server, port, username, password, redirectHost, redirectPort, localPort)
|
||||
logger.debug(
|
||||
'Connecting to %s:%s using %s/%s redirecting to %s:%s, listening on 127.0.0.1:%s',
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
localPort,
|
||||
)
|
||||
|
||||
ft = ForwardThread(server, port, username, password, localPort, redirectHost, redirectPort, waitTime, fingerPrints)
|
||||
ft = ForwardThread(
|
||||
server,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
localPort,
|
||||
redirectHost,
|
||||
redirectPort,
|
||||
waitTime,
|
||||
fingerPrints,
|
||||
)
|
||||
|
||||
ft.start()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -29,27 +29,35 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
from os.path import expanduser # pylint: disable=ungrouped-imports
|
||||
logFile = expanduser('~/udsclient.log')
|
||||
LOGLEVEL = logging.INFO
|
||||
DEBUG = False
|
||||
|
||||
# Update debug level if uds-debug-on exists
|
||||
if 'linux' in sys.platform or 'darwin' in sys.platform:
|
||||
logFile = os.path.expanduser('~/udsclient.log')
|
||||
if os.path.isfile(os.path.expanduser('~/uds-debug-on')):
|
||||
LOGLEVEL = logging.DEBUG
|
||||
DEBUG = True
|
||||
else:
|
||||
logFile = os.path.join(tempfile.gettempdir(), b'udsclient.log')
|
||||
logFile = os.path.join(tempfile.gettempdir(), 'udsclient.log')
|
||||
if os.path.isfile(os.path.join(tempfile.gettempdir(), 'uds-debug-on')):
|
||||
LOGLEVEL = logging.DEBUG
|
||||
DEBUG = True
|
||||
|
||||
try:
|
||||
logging.basicConfig(
|
||||
filename=logFile,
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.INFO
|
||||
level=LOGLEVEL,
|
||||
)
|
||||
except Exception:
|
||||
logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s', level=logging.INFO)
|
||||
logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s', level=LOGLEVEL)
|
||||
|
||||
logger = logging.getLogger('udsclient')
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -30,14 +30,13 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
LINUX = 'Linux'
|
||||
WINDOWS = 'Windows'
|
||||
MAC_OS_X = 'Mac os x'
|
||||
|
||||
|
||||
def getOs():
|
||||
if sys.platform.startswith('linux'):
|
||||
return LINUX
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2017-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -30,95 +29,223 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=c-extension-no-member,no-name-in-module
|
||||
|
||||
import json
|
||||
import bz2
|
||||
import base64
|
||||
import urllib
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import ssl
|
||||
import socket
|
||||
import typing
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, QUrl, QSettings
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QSslCertificate
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from . import osDetector
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from . import os_detector
|
||||
from . import tools
|
||||
from . import VERSION
|
||||
from .log import logger
|
||||
|
||||
# Server before this version uses "unsigned" scripts
|
||||
OLD_METHOD_VERSION = '2.4.0'
|
||||
|
||||
# Callback for error on cert
|
||||
# parameters are hostname, serial
|
||||
# If returns True, ignores error
|
||||
CertCallbackType = typing.Callable[[str, str], bool]
|
||||
|
||||
# Exceptions
|
||||
class UDSException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RetryException(UDSException):
|
||||
pass
|
||||
|
||||
class RestRequest(QObject):
|
||||
|
||||
restApiUrl = '' #
|
||||
class InvalidVersion(UDSException):
|
||||
downloadUrl: str
|
||||
|
||||
done = pyqtSignal(dict, name='done')
|
||||
def __init__(self, downloadUrl: str) -> None:
|
||||
super().__init__(downloadUrl)
|
||||
self.downloadUrl = downloadUrl
|
||||
|
||||
|
||||
class RestApi:
|
||||
|
||||
_restApiUrl: str # base Rest API URL
|
||||
_callbackInvalidCert: typing.Optional[CertCallbackType]
|
||||
_serverVersion: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
restApiUrl,
|
||||
callbackInvalidCert: typing.Optional[CertCallbackType] = None,
|
||||
) -> None: # parent not used
|
||||
logger.debug('Setting request URL to %s', restApiUrl)
|
||||
|
||||
self._restApiUrl = restApiUrl
|
||||
self._callbackInvalidCert = callbackInvalidCert
|
||||
self._serverVersion = ''
|
||||
|
||||
def get(
|
||||
self, url: str, params: typing.Optional[typing.Mapping[str, str]] = None
|
||||
) -> typing.Any:
|
||||
if params:
|
||||
url += '?' + '&'.join(
|
||||
'{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8')))
|
||||
for k, v in params.items()
|
||||
)
|
||||
|
||||
return json.loads(
|
||||
RestApi.getUrl(self._restApiUrl + url, self._callbackInvalidCert)
|
||||
)
|
||||
|
||||
def processError(self, data: typing.Any) -> None:
|
||||
if 'error' in data:
|
||||
if data.get('retryable', '0') == '1':
|
||||
raise RetryException(data['error'])
|
||||
|
||||
raise UDSException(data['error'])
|
||||
|
||||
def getVersion(self) -> str:
|
||||
'''Gets and stores the serverVersion.
|
||||
Also checks that the version is valid for us. If not,
|
||||
will raise an "InvalidVersion' exception'''
|
||||
|
||||
downloadUrl = ''
|
||||
if not self._serverVersion:
|
||||
data = self.get('')
|
||||
self.processError(data)
|
||||
self._serverVersion = data['result']['requiredVersion']
|
||||
downloadUrl = data['result']['downloadUrl']
|
||||
|
||||
def __init__(self, url, parentWindow, done, params=None): # parent not used
|
||||
super(RestRequest, self).__init__()
|
||||
# private
|
||||
self._manager = QNetworkAccessManager()
|
||||
try:
|
||||
if os.path.exists('/etc/ssl/certs/ca-certificates.crt'):
|
||||
pass
|
||||
# os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
|
||||
except Exception:
|
||||
pass
|
||||
if self._serverVersion > VERSION:
|
||||
raise InvalidVersion(downloadUrl)
|
||||
|
||||
|
||||
if params is not None:
|
||||
url += '?' + '&'.join('{}={}'.format(k, urllib.parse.quote(str(v).encode('utf8'))) for k, v in params.items())
|
||||
|
||||
self.url = QUrl(RestRequest.restApiUrl + url)
|
||||
|
||||
# connect asynchronous result, when a request finishes
|
||||
self._manager.finished.connect(self._finished)
|
||||
self._manager.sslErrors.connect(self._sslError)
|
||||
self._parentWindow = parentWindow
|
||||
|
||||
self.done.connect(done, Qt.QueuedConnection)
|
||||
|
||||
def _finished(self, reply):
|
||||
'''
|
||||
Handle signal 'finished'. A network request has finished.
|
||||
'''
|
||||
try:
|
||||
if reply.error() != QNetworkReply.NoError:
|
||||
raise Exception(reply.errorString())
|
||||
data = bytes(reply.readAll())
|
||||
data = json.loads(data)
|
||||
return self._serverVersion
|
||||
except InvalidVersion:
|
||||
raise
|
||||
except Exception as e:
|
||||
data = {
|
||||
'result': None,
|
||||
'error': str(e)
|
||||
}
|
||||
raise UDSException(e)
|
||||
|
||||
self.done.emit(data)
|
||||
def getScriptAndParams(
|
||||
self, ticket: str, scrambler: str
|
||||
) -> typing.Tuple[str, typing.Any]:
|
||||
'''Gets the transport script, validates it if necesary
|
||||
and returns it'''
|
||||
try:
|
||||
data = self.get(
|
||||
'/{}/{}'.format(ticket, scrambler),
|
||||
params={'hostname': tools.getHostName(), 'version': VERSION},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception('Got exception on getTransportData')
|
||||
raise e
|
||||
|
||||
reply.deleteLater() # schedule for delete from main event loop
|
||||
logger.debug('Transport data received')
|
||||
self.processError(data)
|
||||
|
||||
def _sslError(self, reply, errors):
|
||||
settings = QSettings()
|
||||
settings.beginGroup('ssl')
|
||||
cert = errors[0].certificate()
|
||||
digest = str(cert.digest().toHex())
|
||||
params = None
|
||||
|
||||
approved = settings.value(digest, False)
|
||||
if self._serverVersion <= OLD_METHOD_VERSION:
|
||||
raise Exception('Server version is too old. Please, update it.')
|
||||
else:
|
||||
res = data['result']
|
||||
# We have three elements on result:
|
||||
# * Script
|
||||
# * Signature
|
||||
# * Script data
|
||||
# We test that the Script has correct signature, and them execute it with the parameters
|
||||
# script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
||||
script, signature, params = (
|
||||
bz2.decompress(base64.b64decode(res['script'])),
|
||||
res['signature'],
|
||||
json.loads(bz2.decompress(base64.b64decode(res['params']))),
|
||||
)
|
||||
if tools.verifySignature(script, signature) is False:
|
||||
logger.error('Signature is invalid')
|
||||
|
||||
errorString = '<p>The certificate for <b>{}</b> has the following errors:</p><ul>'.format(cert.subjectInfo(QSslCertificate.CommonName))
|
||||
raise Exception(
|
||||
'Invalid UDS code signature. Please, report to administrator'
|
||||
)
|
||||
|
||||
for err in errors:
|
||||
errorString += '<li>' + err.errorString() + '</li>'
|
||||
return script.decode(), params
|
||||
|
||||
errorString += '</ul>'
|
||||
# exec(script.decode("utf-8"), globals(), {'parent': self, 'sp': params})
|
||||
|
||||
if approved or QMessageBox.warning(self._parentWindow, 'SSL Warning', errorString, QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
|
||||
settings.setValue(digest, True)
|
||||
reply.ignoreSslErrors()
|
||||
@staticmethod
|
||||
def _open(
|
||||
url: str, certErrorCallback: typing.Optional[CertCallbackType] = None
|
||||
) -> typing.Any:
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
# If we have the certificates file, we use it
|
||||
if tools.getCaCertsFile() is not None:
|
||||
ctx.load_verify_locations(tools.getCaCertsFile())
|
||||
hostname = urllib.parse.urlparse(url)[1]
|
||||
serial = ''
|
||||
|
||||
settings.endGroup()
|
||||
port = ''
|
||||
if ':' in hostname:
|
||||
hostname, port = hostname.split(':')
|
||||
|
||||
def get(self):
|
||||
request = QNetworkRequest(self.url)
|
||||
request.setRawHeader(b'User-Agent', osDetector.getOs().encode('utf-8') + b" - UDS Connector " + VERSION.encode('utf-8'))
|
||||
self._manager.get(request)
|
||||
if url.startswith('https'):
|
||||
port = port or '443'
|
||||
with ctx.wrap_socket(
|
||||
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
||||
server_hostname=hostname,
|
||||
) as s:
|
||||
s.connect((hostname, int(port)))
|
||||
# Get binary certificate
|
||||
binCert = s.getpeercert(True)
|
||||
if binCert:
|
||||
cert = x509.load_der_x509_certificate(binCert, default_backend())
|
||||
else:
|
||||
raise Exception('Certificate not found!')
|
||||
|
||||
serial = hex(cert.serial_number)[2:]
|
||||
|
||||
response = None
|
||||
ctx.verify_mode = ssl.CERT_REQUIRED
|
||||
ctx.check_hostname = True
|
||||
|
||||
def urlopen(url: str):
|
||||
# Generate the request with the headers
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
'User-Agent': os_detector.getOs() + " - UDS Connector " + VERSION
|
||||
},
|
||||
)
|
||||
return urllib.request.urlopen(req, context=ctx)
|
||||
|
||||
try:
|
||||
response = urlopen(url)
|
||||
except urllib.error.URLError as e:
|
||||
if isinstance(e.reason, ssl.SSLCertVerificationError):
|
||||
# Ask about invalid certificate
|
||||
if certErrorCallback:
|
||||
if certErrorCallback(hostname, serial):
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
response = urlopen(url)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def getUrl(
|
||||
url: str, certErrorCallback: typing.Optional[CertCallbackType] = None
|
||||
) -> bytes:
|
||||
with RestApi._open(url, certErrorCallback) as response:
|
||||
resp = response.read()
|
||||
|
||||
return resp
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2015 Virtual Cable S.L.
|
||||
# Copyright (c) 2015-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -30,31 +29,36 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
import tempfile
|
||||
import string
|
||||
import random
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import socket
|
||||
import stat
|
||||
import sys
|
||||
import time
|
||||
import base64
|
||||
import typing
|
||||
|
||||
import six
|
||||
import certifi
|
||||
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
psutil = None
|
||||
|
||||
from .log import logger
|
||||
|
||||
_unlinkFiles = []
|
||||
_tasksToWait = []
|
||||
_execBeforeExit = []
|
||||
_unlinkFiles: typing.List[typing.Tuple[str, bool]] = []
|
||||
_tasksToWait: typing.List[typing.Tuple[typing.Any, bool]] = []
|
||||
_execBeforeExit: typing.List[typing.Callable[[], None]] = []
|
||||
|
||||
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||
|
||||
# Public key for scripts
|
||||
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
|
||||
PUBLIC_KEY = b'''-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNURlGjBpqbglkTTg2lh
|
||||
dU5qPbg9Q+RofoDDucGfrbY0pjB9ULgWXUetUWDZhFG241tNeKw+aYFTEorK5P+g
|
||||
ud7h9KfyJ6huhzln9eyDu3k+kjKUIB1PLtA3lZLZnBx7nmrHRody1u5lRaLVplsb
|
||||
@@ -70,15 +74,13 @@ nVgtClKcDDlSaBsO875WDR0CAwEAAQ==
|
||||
-----END PUBLIC KEY-----'''
|
||||
|
||||
|
||||
def saveTempFile(content, filename=None):
|
||||
def saveTempFile(content: str, filename: typing.Optional[str] = None) -> str:
|
||||
if filename is None:
|
||||
filename = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
filename = ''.join(
|
||||
random.choice(string.ascii_lowercase + string.digits) for _ in range(16)
|
||||
)
|
||||
filename = filename + '.uds'
|
||||
|
||||
if 'win32' in sys.platform:
|
||||
logger.info('Fixing for win32')
|
||||
filename = filename.encode(sys_fs_enc)
|
||||
|
||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
@@ -88,10 +90,7 @@ def saveTempFile(content, filename=None):
|
||||
return filename
|
||||
|
||||
|
||||
def readTempFile(filename):
|
||||
if 'win32' in sys.platform:
|
||||
filename = filename.encode('utf-8')
|
||||
|
||||
def readTempFile(filename: str) -> typing.Optional[str]:
|
||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
@@ -100,7 +99,7 @@ def readTempFile(filename):
|
||||
return None
|
||||
|
||||
|
||||
def testServer(host, port, timeOut=4):
|
||||
def testServer(host: str, port: typing.Union[str, int], timeOut: int = 4) -> bool:
|
||||
try:
|
||||
sock = socket.create_connection((host, int(port)), timeOut)
|
||||
sock.close()
|
||||
@@ -109,11 +108,11 @@ def testServer(host, port, timeOut=4):
|
||||
return True
|
||||
|
||||
|
||||
def findApp(appName, extraPath=None):
|
||||
if 'win32' in sys.platform and isinstance(appName, six.text_type):
|
||||
appName = appName.encode(sys_fs_enc)
|
||||
def findApp(
|
||||
appName: str, extraPath: typing.Optional[str] = None
|
||||
) -> typing.Optional[str]:
|
||||
searchPath = os.environ['PATH'].split(os.pathsep)
|
||||
if extraPath is not None:
|
||||
if extraPath:
|
||||
searchPath += list(extraPath)
|
||||
|
||||
for path in searchPath:
|
||||
@@ -123,68 +122,101 @@ def findApp(appName, extraPath=None):
|
||||
return None
|
||||
|
||||
|
||||
def getHostName():
|
||||
def getHostName() -> str:
|
||||
'''
|
||||
Returns current host name
|
||||
In fact, it's a wrapper for socket.gethostname()
|
||||
'''
|
||||
hostname = socket.gethostname()
|
||||
if 'win32' in sys.platform:
|
||||
hostname = hostname.decode(sys_fs_enc)
|
||||
|
||||
hostname = six.text_type(hostname)
|
||||
logger.info('Hostname: %s', hostname)
|
||||
return hostname
|
||||
|
||||
|
||||
# Queing operations (to be executed before exit)
|
||||
|
||||
|
||||
def addFileToUnlink(filename):
|
||||
def addFileToUnlink(filename: str, early: bool = False) -> None:
|
||||
'''
|
||||
Adds a file to the wait-and-unlink list
|
||||
'''
|
||||
_unlinkFiles.append(filename)
|
||||
logger.debug(
|
||||
'Added file %s to unlink on %s stage', filename, 'early' if early else 'later'
|
||||
)
|
||||
_unlinkFiles.append((filename, early))
|
||||
|
||||
|
||||
def unlinkFiles():
|
||||
def unlinkFiles(early: bool = False) -> None:
|
||||
'''
|
||||
Removes all wait-and-unlink files
|
||||
'''
|
||||
if _unlinkFiles:
|
||||
time.sleep(5) # Wait 5 seconds before deleting anything
|
||||
logger.debug('Unlinking files on %s stage', 'early' if early else 'later')
|
||||
filesToUnlink = list(filter(lambda x: x[1] == early, _unlinkFiles))
|
||||
if filesToUnlink:
|
||||
logger.debug('Files to unlink: %s', filesToUnlink)
|
||||
# Wait 2 seconds before deleting anything on early and 5 on later stages
|
||||
time.sleep(1 + 2 * (1 + int(early)))
|
||||
|
||||
for f in _unlinkFiles:
|
||||
for f in filesToUnlink:
|
||||
try:
|
||||
os.unlink(f)
|
||||
except Exception:
|
||||
pass
|
||||
os.unlink(f[0])
|
||||
except Exception as e:
|
||||
logger.debug('File %s not deleted: %s', f[0], e)
|
||||
|
||||
|
||||
def addTaskToWait(taks):
|
||||
_tasksToWait.append(taks)
|
||||
def addTaskToWait(task: typing.Any, includeSubprocess: bool = False) -> None:
|
||||
logger.debug(
|
||||
'Added task %s to wait %s',
|
||||
task,
|
||||
'with subprocesses' if includeSubprocess else '',
|
||||
)
|
||||
_tasksToWait.append((task, includeSubprocess))
|
||||
|
||||
|
||||
def waitForTasks():
|
||||
for t in _tasksToWait:
|
||||
def waitForTasks() -> None:
|
||||
logger.debug('Started to wait %s', _tasksToWait)
|
||||
for task, waitForSubp in _tasksToWait:
|
||||
logger.debug('Waiting for task %s, subprocess wait: %s', task, waitForSubp)
|
||||
try:
|
||||
if hasattr(t, 'join'):
|
||||
t.join()
|
||||
elif hasattr(t, 'wait'):
|
||||
t.wait()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(task, 'join'):
|
||||
task.join()
|
||||
elif hasattr(task, 'wait'):
|
||||
task.wait()
|
||||
# If wait for spanwed process (look for process with task pid) and we can look for them...
|
||||
logger.debug(
|
||||
'Psutil: %s, waitForSubp: %s, hasattr: %s',
|
||||
psutil,
|
||||
waitForSubp,
|
||||
hasattr(task, 'pid'),
|
||||
)
|
||||
if psutil and waitForSubp and hasattr(task, 'pid'):
|
||||
subProcesses = list(
|
||||
filter(
|
||||
lambda x: x.ppid() == task.pid, # type: ignore
|
||||
psutil.process_iter(attrs=('ppid',)),
|
||||
)
|
||||
)
|
||||
logger.debug(
|
||||
'Waiting for subprocesses... %s, %s', task.pid, subProcesses
|
||||
)
|
||||
for i in subProcesses:
|
||||
logger.debug('Found %s', i)
|
||||
i.wait()
|
||||
except Exception as e:
|
||||
logger.error('Waiting for tasks to finish error: %s', e)
|
||||
|
||||
|
||||
def addExecBeforeExit(fnc):
|
||||
def addExecBeforeExit(fnc: typing.Callable[[], None]) -> None:
|
||||
logger.debug('Added exec before exit: %s', fnc)
|
||||
_execBeforeExit.append(fnc)
|
||||
|
||||
|
||||
def execBeforeExit():
|
||||
def execBeforeExit() -> None:
|
||||
logger.debug('Esecuting exec before exit: %s', _execBeforeExit)
|
||||
for fnc in _execBeforeExit:
|
||||
fnc.__call__()
|
||||
fnc()
|
||||
|
||||
|
||||
def verifySignature(script, signature):
|
||||
def verifySignature(script: bytes, signature: bytes) -> bool:
|
||||
'''
|
||||
Verifies with a public key from whom the data came that it was indeed
|
||||
signed by their private key
|
||||
@@ -193,13 +225,45 @@ def verifySignature(script, signature):
|
||||
return: Boolean. True if the signature is valid; False otherwise.
|
||||
'''
|
||||
# For signature checking
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
from Crypto.Hash import SHA256
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import utils, padding
|
||||
|
||||
rsakey = RSA.importKey(PUBLIC_KEY)
|
||||
signer = PKCS1_v1_5.new(rsakey)
|
||||
digest = SHA256.new(script) # Script is "binary string" here
|
||||
if signer.verify(digest, b64decode(signature)):
|
||||
return True
|
||||
return False
|
||||
public_key = serialization.load_pem_public_key(
|
||||
data=PUBLIC_KEY, backend=default_backend()
|
||||
)
|
||||
|
||||
try:
|
||||
public_key.verify( # type: ignore
|
||||
base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256() # type: ignore
|
||||
)
|
||||
except Exception: # InvalidSignature
|
||||
return False
|
||||
|
||||
# If no exception, the script was fine...
|
||||
return True
|
||||
|
||||
|
||||
def getCaCertsFile() -> typing.Optional[str]:
|
||||
# First, try certifi...
|
||||
|
||||
# If environment contains CERTIFICATE_BUNDLE_PATH, use it
|
||||
if 'CERTIFICATE_BUNDLE_PATH' in os.environ:
|
||||
return os.environ['CERTIFICATE_BUNDLE_PATH']
|
||||
|
||||
try:
|
||||
if os.path.exists(certifi.where()):
|
||||
return certifi.where()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info('Certifi file does not exists: %s', certifi.where())
|
||||
|
||||
# Check if "standard" paths are valid for linux systems
|
||||
if 'linux' in sys.platform:
|
||||
for path in ('/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/ca-bundle.pem'):
|
||||
if os.path.exists(path):
|
||||
logger.info('Found certifi path: %s', path)
|
||||
return path
|
||||
|
||||
return None
|
||||
|
289
client-py3/full/src/uds/tunnel.py
Normal file
289
client-py3/full/src/uds/tunnel.py
Normal file
@@ -0,0 +1,289 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 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. 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 socket
|
||||
import socketserver
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
import select
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from . import tools
|
||||
|
||||
HANDSHAKE_V1 = b'\x5AMGB\xA5\x01\x00'
|
||||
BUFFER_SIZE = 1024 * 16 # Max buffer length
|
||||
DEBUG = True
|
||||
LISTEN_ADDRESS = '0.0.0.0' if DEBUG else '127.0.0.1'
|
||||
|
||||
# ForwarServer states
|
||||
TUNNEL_LISTENING, TUNNEL_OPENING, TUNNEL_PROCESSING, TUNNEL_ERROR = 0, 1, 2, 3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
remote: typing.Tuple[str, int]
|
||||
ticket: str
|
||||
stop_flag: threading.Event
|
||||
can_stop: bool
|
||||
timeout: int
|
||||
timer: typing.Optional[threading.Timer]
|
||||
check_certificate: bool
|
||||
current_connections: int
|
||||
status: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
remote: typing.Tuple[str, int],
|
||||
ticket: str,
|
||||
timeout: int = 0,
|
||||
local_port: int = 0,
|
||||
check_certificate: bool = True,
|
||||
) -> None:
|
||||
|
||||
local_port = local_port or random.randrange(33000, 53000)
|
||||
|
||||
super().__init__(
|
||||
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
||||
)
|
||||
self.remote = remote
|
||||
self.ticket = ticket
|
||||
# Negative values for timeout, means "accept always connections"
|
||||
# "but if no connection is stablished on timeout (positive)"
|
||||
# "stop the listener"
|
||||
self.timeout = int(time.time()) + timeout if timeout > 0 else 0
|
||||
self.check_certificate = check_certificate
|
||||
self.stop_flag = threading.Event() # False initial
|
||||
self.current_connections = 0
|
||||
|
||||
self.status = TUNNEL_LISTENING
|
||||
self.can_stop = False
|
||||
|
||||
timeout = abs(timeout) or 60
|
||||
self.timer = threading.Timer(
|
||||
abs(timeout), ForwardServer.__checkStarted, args=(self,)
|
||||
)
|
||||
self.timer.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
if not self.stop_flag.is_set():
|
||||
logger.debug('Stopping servers')
|
||||
self.stop_flag.set()
|
||||
if self.timer:
|
||||
self.timer.cancel()
|
||||
self.timer = None
|
||||
self.shutdown()
|
||||
|
||||
def connect(self) -> ssl.SSLSocket:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as rsocket:
|
||||
logger.info('CONNECT to %s', self.remote)
|
||||
|
||||
rsocket.connect(self.remote)
|
||||
|
||||
rsocket.sendall(HANDSHAKE_V1) # No response expected, just the handshake
|
||||
|
||||
context = ssl.create_default_context()
|
||||
|
||||
# Do not "recompress" data, use only "base protocol" compression
|
||||
context.options |= ssl.OP_NO_COMPRESSION
|
||||
if tools.getCaCertsFile() is not None:
|
||||
context.load_verify_locations(
|
||||
tools.getCaCertsFile()
|
||||
) # Load certifi certificates
|
||||
|
||||
# If ignore remote certificate
|
||||
if self.check_certificate is False:
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
logger.warning('Certificate checking is disabled!')
|
||||
|
||||
return context.wrap_socket(rsocket, server_hostname=self.remote[0])
|
||||
|
||||
def check(self) -> bool:
|
||||
if self.status == TUNNEL_ERROR:
|
||||
return False
|
||||
|
||||
logger.debug('Checking tunnel availability')
|
||||
|
||||
try:
|
||||
with self.connect() as ssl_socket:
|
||||
ssl_socket.sendall(b'TEST')
|
||||
resp = ssl_socket.recv(2)
|
||||
if resp != b'OK':
|
||||
raise Exception({'Invalid tunnelresponse: {resp}'})
|
||||
logger.debug('Tunnel is available!')
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
'Error connecting to tunnel server %s: %s', self.server_address, e
|
||||
)
|
||||
return False
|
||||
|
||||
@property
|
||||
def stoppable(self) -> bool:
|
||||
logger.debug('Is stoppable: %s', self.can_stop)
|
||||
return self.can_stop or (self.timeout != 0 and int(time.time()) > self.timeout)
|
||||
|
||||
@staticmethod
|
||||
def __checkStarted(fs: 'ForwardServer') -> None:
|
||||
logger.debug('New connection limit reached')
|
||||
fs.timer = None
|
||||
fs.can_stop = True
|
||||
if fs.current_connections <= 0:
|
||||
fs.stop()
|
||||
|
||||
|
||||
class Handler(socketserver.BaseRequestHandler):
|
||||
# Override Base type
|
||||
server: ForwardServer
|
||||
|
||||
# server: ForwardServer
|
||||
def handle(self) -> None:
|
||||
self.server.status = TUNNEL_OPENING
|
||||
|
||||
# If server processing is over time
|
||||
if self.server.stoppable:
|
||||
self.server.status = TUNNEL_ERROR
|
||||
logger.info('Rejected timedout connection')
|
||||
self.request.close() # End connection without processing it
|
||||
return
|
||||
|
||||
self.server.current_connections += 1
|
||||
|
||||
# Open remote connection
|
||||
try:
|
||||
logger.debug('Ticket %s', self.server.ticket)
|
||||
with self.server.connect() as ssl_socket:
|
||||
# Send handhshake + command + ticket
|
||||
ssl_socket.sendall(b'OPEN' + self.server.ticket.encode())
|
||||
# Check response is OK
|
||||
data = ssl_socket.recv(2)
|
||||
if data != b'OK':
|
||||
data += ssl_socket.recv(128)
|
||||
raise Exception(
|
||||
f'Error received: {data.decode(errors="ignore")}'
|
||||
) # Notify error
|
||||
|
||||
# All is fine, now we can tunnel data
|
||||
self.process(remote=ssl_socket)
|
||||
except Exception as e:
|
||||
logger.error(f'Error connecting to {self.server.remote!s}: {e!s}')
|
||||
self.server.status = TUNNEL_ERROR
|
||||
self.server.stop()
|
||||
finally:
|
||||
self.server.current_connections -= 1
|
||||
|
||||
if self.server.current_connections <= 0 and self.server.stoppable:
|
||||
self.server.stop()
|
||||
|
||||
# Processes data forwarding
|
||||
def process(self, remote: ssl.SSLSocket):
|
||||
self.server.status = TUNNEL_PROCESSING
|
||||
logger.debug('Processing tunnel with ticket %s', self.server.ticket)
|
||||
# Process data until stop requested or connection closed
|
||||
try:
|
||||
while not self.server.stop_flag.is_set():
|
||||
r, _w, _x = select.select([self.request, remote], [], [], 1.0)
|
||||
if self.request in r:
|
||||
data = self.request.recv(BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
remote.sendall(data)
|
||||
if remote in r:
|
||||
data = remote.recv(BUFFER_SIZE)
|
||||
if not data:
|
||||
break
|
||||
self.request.sendall(data)
|
||||
logger.debug('Finished tunnel with ticket %s', self.server.ticket)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
def _run(server: ForwardServer) -> None:
|
||||
logger.debug(
|
||||
'Starting forwarder: %s -> %s, timeout: %d',
|
||||
server.server_address,
|
||||
server.remote,
|
||||
server.timeout,
|
||||
)
|
||||
server.serve_forever()
|
||||
logger.debug('Stoped forwarder %s -> %s', server.server_address, server.remote)
|
||||
|
||||
|
||||
def forward(
|
||||
remote: typing.Tuple[str, int],
|
||||
ticket: str,
|
||||
timeout: int = 0,
|
||||
local_port: int = 0,
|
||||
check_certificate=True,
|
||||
) -> ForwardServer:
|
||||
|
||||
fs = ForwardServer(
|
||||
remote=remote,
|
||||
ticket=ticket,
|
||||
timeout=timeout,
|
||||
local_port=local_port,
|
||||
check_certificate=check_certificate,
|
||||
)
|
||||
# Starts a new thread
|
||||
threading.Thread(target=_run, args=(fs,)).start()
|
||||
|
||||
return fs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
log = logging.getLogger()
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter(
|
||||
'%(levelname)s - %(message)s'
|
||||
) # Basic log format, nice for syslog
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
ticket = 'mffqg7q4s61fvx0ck2pe0zke6k0c5ipb34clhbkbs4dasb4g'
|
||||
|
||||
fs = forward(
|
||||
('172.27.0.1', 7777),
|
||||
ticket,
|
||||
local_port=49999,
|
||||
timeout=-20,
|
||||
check_certificate=False,
|
||||
)
|
4
client/full/.gitignore
vendored
4
client/full/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/bin
|
||||
/udsclient_*
|
||||
/udsclient-*.tar.gz
|
||||
/*.rpm
|
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>UDSclient</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
|
||||
<path>/${PROJECT_DIR_NAME}/src</path>
|
||||
|
||||
</pydev_pathproperty>
|
||||
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">system-2.7</pydev_property>
|
||||
|
||||
</pydev_project>
|
4
client/full/linux/.gitignore
vendored
4
client/full/linux/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/udsclient-opensuse-[0-9]*.spec
|
||||
/udsclient-[0-9]*.spec
|
||||
/debian/udsclient
|
||||
/targz
|
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
# Version
|
||||
# VERSION := 1.7.5
|
||||
|
||||
# Directories
|
||||
SOURCEDIR := ../src
|
||||
LIBDIR := $(DESTDIR)/usr/lib/UDSClient
|
||||
BINDIR := $(DESTDIR)/usr/bin
|
||||
SBINDIR = $(DESTDIR)/usr/sbin
|
||||
APPSDIR := $(DESTDIR)/usr/share/applications
|
||||
|
||||
PYC := $(shell find $(SOURCEDIR) -name '*.py[co]')
|
||||
CACHES := $(shell find $(SOURCEDIR) -name '__pycache__')
|
||||
|
||||
clean:
|
||||
rm -rf $(PYC) $(CACHES) $(DESTDIR)
|
||||
install:
|
||||
rm -rf $(DESTDIR)
|
||||
mkdir -p $(LIBDIR)
|
||||
#mkdir -p $(BINDIR)
|
||||
#mkdir -p $(SBINDIR)
|
||||
mkdir -p $(APPSDIR)
|
||||
|
||||
mkdir $(LIBDIR)/uds
|
||||
|
||||
# Cleans up .pyc and cache folders
|
||||
rm -f $(PYC) $(CACHES)
|
||||
|
||||
cp $(SOURCEDIR)/uds/*.py $(LIBDIR)/uds
|
||||
|
||||
cp $(SOURCEDIR)/UDS*.py $(LIBDIR)
|
||||
|
||||
|
||||
# URL Catchers elements for gnome/kde
|
||||
cp desktop/UDSClient.desktop $(APPSDIR)
|
||||
|
||||
chmod 755 $(LIBDIR)/UDSClient.py
|
||||
|
||||
ifeq ($(DISTRO),targz)
|
||||
cp installer.sh $(DESTDIR)/install.sh
|
||||
tar czvf ../udsclient-$(VERSION).tar.gz -C $(DESTDIR) .
|
||||
endif
|
||||
|
||||
|
||||
# chmod 0755 $(BINDIR)/udsclient
|
||||
uninstall:
|
||||
rm -rf $(LIBDIR)
|
||||
# rm -f $(BINDIR)/udsclient
|
||||
# rm -rf $(CFGDIR)
|
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=`cat ../../../VERSION`
|
||||
RELEASE=1
|
||||
# Debian based
|
||||
dpkg-buildpackage -b
|
||||
|
||||
# Now rpm based
|
||||
top=`pwd`
|
||||
|
||||
cat udsclient-template.spec |
|
||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||
sed -e s/"release 1"/"release ${RELEASE}"/g > udsclient-$VERSION.spec
|
||||
|
||||
# Now fix dependencies for opensuse
|
||||
cat udsclient-template.spec |
|
||||
sed -e s/"version 0.0.0"/"version ${VERSION}"/g |
|
||||
sed -e s/"name udsclient"/"name udsclient-opensuse"/g |
|
||||
sed -e s/"PyQt4"/"python-qt4"/g |
|
||||
sed -e s/"libXScrnSaver"/"libXss1"/g > udsclient-opensuse-$VERSION.spec
|
||||
|
||||
|
||||
# Right now, udsactor-xrdp-.spec is not needed
|
||||
for pkg in udsclient-$VERSION.spec udsclient-opensuse-$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
|
||||
|
||||
#rm udsclient-$VERSION
|
||||
|
||||
make DESTDIR=targz DISTRO=targz VERSION=${VERSION} install
|
3
client/full/linux/debian/.gitignore
vendored
3
client/full/linux/debian/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/udsactor/
|
||||
/udsactor-xrdp/
|
||||
/udsactor-nx/
|
@@ -1,47 +0,0 @@
|
||||
udsclient (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
|
||||
|
||||
udsclient (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
|
||||
|
||||
udsclient (2.2.0) stable; urgency=medium
|
||||
|
||||
* Updated release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 27 Aug 2017 14:18:18 +0200
|
||||
|
||||
udsclient (2.1.0) stable; urgency=medium
|
||||
|
||||
* Updated release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Sun, 23 Oct 2016 21:12:23 +0200
|
||||
|
||||
udsclient (2.0.0) stable; urgency=medium
|
||||
|
||||
* Release upgrade
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 09:33:18 +0100
|
||||
|
||||
udsclient (1.9.1) stable; urgency=medium
|
||||
|
||||
* Minor fixes & making version match UDS version
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:02:37 +0100
|
||||
|
||||
udsclient (1.9.0) stable; urgency=medium
|
||||
|
||||
* Minor fixes & making version match UDS version
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 05 May 2015 07:03:47 +0200
|
||||
|
||||
udsclient (1.7.5) stable; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Fri, 10 Apr 2015 05:32:41 +0100
|
@@ -1 +0,0 @@
|
||||
9
|
@@ -1,15 +0,0 @@
|
||||
Source: udsclient
|
||||
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: udsclient
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: python-paramiko (>=0.8.2), python-crypto, python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), freerdp2-x11 | freerdp-x11, desktop-file-utils, ${misc:Depends}
|
||||
Description: Client connector for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow this machine to connect to services provided by UDS Broker.
|
@@ -1,26 +0,0 @@
|
||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||
Name: udsclient
|
||||
Maintainer: Adolfo Gómez García
|
||||
Source: http://www.udsenterprise.com/
|
||||
|
||||
Copyright: 2014 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'.
|
@@ -1 +0,0 @@
|
||||
readme.txt
|
@@ -1,2 +0,0 @@
|
||||
udsclient_3.0.0_all.deb admin optional
|
||||
udsclient_3.0.0_amd64.buildinfo admin optional
|
@@ -1,44 +0,0 @@
|
||||
#!/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/udsclient install
|
||||
binary-arch: build install
|
||||
# emptyness
|
||||
binary-indep: build install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs
|
||||
dh_installdocs
|
||||
dh_installdebconf
|
||||
dh_installinit --no-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,17 +0,0 @@
|
||||
dh_prep
|
||||
dh_installdirs
|
||||
dh_installchangelogs
|
||||
dh_installdocs
|
||||
dh_installdebconf
|
||||
dh_installinit
|
||||
dh_compress
|
||||
dh_link
|
||||
dh_fixperms
|
||||
dh_installdeb
|
||||
dh_shlibdeps
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
dh_builddeb
|
||||
dh_builddeb
|
||||
dh_builddeb
|
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
set -e
|
||||
case "$1" in
|
||||
configure)
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
set -e
|
||||
|
@@ -1 +0,0 @@
|
||||
#! /bin/bash -e
|
@@ -1,20 +0,0 @@
|
||||
Template: udsactor/host
|
||||
Type: string
|
||||
Default:
|
||||
Description: UDS Server address:
|
||||
The actor needs the address of the server in order to communicate with it.
|
||||
Provide here full address (or i) of the UDS server
|
||||
|
||||
Template: udsactor/secure
|
||||
Type: boolean
|
||||
Default: true
|
||||
Description: Use secure (https) connection to communicate with UDS server?
|
||||
If selected, the communication will be done using https.
|
||||
If not selected, the communication will be done using http
|
||||
|
||||
Template: udsactor/masterKey
|
||||
Type: string
|
||||
Default:
|
||||
Description: Master Key:
|
||||
This key is available on UDS Administration interface.
|
||||
Look for it under configuration, on Security tab.
|
@@ -1,11 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=UDSClient
|
||||
Comment=UDS Helper
|
||||
Keywords=uds;client;vdi;
|
||||
Exec=/usr/lib/UDSClient/UDSClient.py %u
|
||||
Icon=help-browser
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
MimeType=x-scheme-handler/uds;x-scheme-handler/udss;
|
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cp -r usr/lib/UDSClient /usr/lib/UDSClient
|
||||
cp -r usr/share/applications /usr/lib/applications -R
|
||||
update-desktop-database
|
||||
|
||||
echo "Installation process done."
|
||||
echo "Remembar that the following packages must be installed on system:"
|
||||
echo "* Python paramiko"
|
||||
echo "* Python pyqt4"
|
||||
echo "Theese packages (as their names), are dependent on your platform, so you must locate and install them"
|
||||
echo "You can install them directly on any platform with pip, using this simple command: "
|
||||
echo "pip install PyQt4 paramiko"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
UDSClient is the client connector needed to get acccess to services managed by UDS Broker.
|
||||
|
||||
Please, visit http://www.udsenterprise.com for more information
|
@@ -1,52 +0,0 @@
|
||||
%define _topdir %(echo $PWD)/rpm
|
||||
%define name udsclient
|
||||
%define version 0.0.0
|
||||
%define release 1
|
||||
%define buildroot %{_topdir}/%{name}-%{version}-%{release}-root
|
||||
|
||||
BuildRoot: %{buildroot}
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Summary: Client for Universal Desktop Services (UDS) Broker
|
||||
License: BSD3
|
||||
Group: Applications/Productivity
|
||||
Requires: python-six python-paramiko PyQt4
|
||||
Vendor: Virtual Cable S.L.U.
|
||||
URL: http://www.udsenterprise.com
|
||||
Provides: udsclient
|
||||
|
||||
%define _rpmdir ../
|
||||
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
||||
|
||||
|
||||
%install
|
||||
curdir=`pwd`
|
||||
cd ../..
|
||||
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh install
|
||||
cd $curdir
|
||||
|
||||
%post
|
||||
/usr/bin/update-desktop-database
|
||||
if [ ! -d /media ]; then mkdir /media; echo "/media created for compatibility"; fi
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
curdir=`pwd`
|
||||
cd ../..
|
||||
make DESTDIR=$RPM_BUILD_ROOT DISTRO=rh clean
|
||||
cd $curdir
|
||||
|
||||
|
||||
%postun
|
||||
# And, posibly, the .pyc leaved behind on /usr/share/UDSActor
|
||||
rm -rf /usr/share/UDClient > /dev/null 2>&1
|
||||
/usr/bin/update-desktop-database
|
||||
|
||||
%description
|
||||
This package provides the required components to allow connection to services offered by UDS Broker.
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
/usr/lib/UDSClient/*
|
||||
/usr/share/applications/UDSClient.desktop
|
4
client/full/src/.gitignore
vendored
4
client/full/src/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/build
|
||||
/dist
|
||||
UDSClient.dmg
|
||||
UDSClient.pkg
|
@@ -1,338 +0,0 @@
|
||||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2017 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=c-extension-no-member
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import webbrowser
|
||||
import json
|
||||
|
||||
from PyQt4 import QtCore, QtGui # @UnresolvedImport
|
||||
import six
|
||||
|
||||
from uds.rest import RestRequest
|
||||
from uds.forward import forward # pylint: disable=unused-import
|
||||
from uds.log import logger
|
||||
from uds import tools
|
||||
from uds import VERSION
|
||||
|
||||
from UDSWindow import Ui_MainWindow
|
||||
|
||||
# Server before this version uses "unsigned" scripts
|
||||
OLD_METHOD_VERSION = '2.4.0'
|
||||
|
||||
class RetryException(Exception):
|
||||
pass
|
||||
|
||||
class UDSClient(QtGui.QMainWindow):
|
||||
|
||||
ticket = None
|
||||
scrambler = None
|
||||
withError = False
|
||||
animTimer = None
|
||||
anim = 0
|
||||
animInverted = False
|
||||
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
|
||||
req = None
|
||||
|
||||
def __init__(self):
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.progressBar.setValue(0)
|
||||
self.ui.cancelButton.clicked.connect(self.cancelPushed)
|
||||
|
||||
self.ui.info.setText('Initializing...')
|
||||
|
||||
screen = QtGui.QDesktopWidget().screenGeometry()
|
||||
mysize = self.geometry()
|
||||
hpos = (screen.width() - mysize.width()) / 2
|
||||
vpos = (screen.height() - mysize.height() - mysize.height()) / 2
|
||||
self.move(hpos, vpos)
|
||||
|
||||
self.animTimer = QtCore.QTimer()
|
||||
QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
|
||||
|
||||
self.activateWindow()
|
||||
|
||||
self.startAnim()
|
||||
|
||||
|
||||
def closeWindow(self):
|
||||
self.close()
|
||||
|
||||
def processError(self, data):
|
||||
if 'error' in data:
|
||||
# QtGui.QMessageBox.critical(self, 'Request error {}'.format(data.get('retryable', '0')), data['error'], QtGui.QMessageBox.Ok)
|
||||
if data.get('retryable', '0') == '1':
|
||||
raise RetryException(data['error'])
|
||||
|
||||
raise Exception(data['error'])
|
||||
# QtGui.QMessageBox.critical(self, 'Request error', rest.data['error'], QtGui.QMessageBox.Ok)
|
||||
# self.closeWindow()
|
||||
# return
|
||||
|
||||
def showError(self, error):
|
||||
logger.error('got error: %s', error)
|
||||
self.stopAnim()
|
||||
self.ui.info.setText('UDS Plugin Error') # In fact, main window is hidden, so this is not visible... :)
|
||||
self.closeWindow()
|
||||
QtGui.QMessageBox.critical(None, 'UDS Plugin Error', '{}'.format(error), QtGui.QMessageBox.Ok)
|
||||
self.withError = True
|
||||
|
||||
def cancelPushed(self):
|
||||
self.close()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def updateAnim(self):
|
||||
self.anim += 2
|
||||
if self.anim > 99:
|
||||
self.animInverted = not self.animInverted
|
||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||
self.anim = 0
|
||||
|
||||
self.ui.progressBar.setValue(self.anim)
|
||||
|
||||
def startAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.anim = 0
|
||||
self.animInverted = False
|
||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||
self.animTimer.start(40)
|
||||
|
||||
def stopAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.animTimer.stop()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def getVersion(self):
|
||||
self.req = RestRequest('', self, self.version)
|
||||
self.req.get()
|
||||
|
||||
@QtCore.pyqtSlot(dict)
|
||||
def version(self, data):
|
||||
try:
|
||||
self.processError(data)
|
||||
self.ui.info.setText('Processing...')
|
||||
|
||||
if data['result']['requiredVersion'] > VERSION:
|
||||
QtGui.QMessageBox.critical(self, 'Upgrade required', 'A newer connector version is required.\nA browser will be opened to download it.', QtGui.QMessageBox.Ok)
|
||||
webbrowser.open(data['result']['downloadUrl'])
|
||||
self.closeWindow()
|
||||
return
|
||||
|
||||
self.serverVersion = data['result']['requiredVersion']
|
||||
self.getTransportData()
|
||||
|
||||
except RetryException as e:
|
||||
self.ui.info.setText(six.text_type(e))
|
||||
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||
|
||||
except Exception as e:
|
||||
self.showError(e)
|
||||
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def getTransportData(self):
|
||||
try:
|
||||
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||
self.req.get()
|
||||
except Exception as e:
|
||||
logger.exception('Got exception on getTransportData')
|
||||
raise e
|
||||
|
||||
|
||||
@QtCore.pyqtSlot(dict)
|
||||
def transportDataReceived(self, data):
|
||||
logger.debug('Transport data received')
|
||||
try:
|
||||
self.processError(data)
|
||||
|
||||
params = None
|
||||
|
||||
if self.serverVersion <= OLD_METHOD_VERSION:
|
||||
script = data['result'].decode('base64').decode('bz2')
|
||||
else:
|
||||
res = data['result']
|
||||
# We have three elements on result:
|
||||
# * Script
|
||||
# * Signature
|
||||
# * Script data
|
||||
# We test that the Script has correct signature, and them execute it with the parameters
|
||||
script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
||||
if tools.verifySignature(script, signature) is False:
|
||||
logger.error('Signature is invalid')
|
||||
|
||||
raise Exception('Invalid UDS code signature. Please, report to administrator')
|
||||
|
||||
self.stopAnim()
|
||||
|
||||
if 'darwin' in sys.platform:
|
||||
self.showMinimized()
|
||||
|
||||
QtCore.QTimer.singleShot(3000, self.endScript)
|
||||
self.hide()
|
||||
|
||||
# if self.serverVersion <= OLD_METHOD_VERSION:
|
||||
# errorString = '<p>The server <b>{}</b> runs an old version of UDS:</p>'.format(host)
|
||||
# errorString += '<p>To avoid security issues, you must approve old UDS Version access.</p>'
|
||||
#
|
||||
# if QtGui.QMessageBox.warning(None, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
|
||||
# raise Exception('Server not approved. Access denied.')
|
||||
|
||||
six.exec_(script, globals(), {'parent': self, 'sp': params})
|
||||
|
||||
except RetryException as e:
|
||||
self.ui.info.setText(six.text_type(e) + ', retrying access...')
|
||||
# Retry operation in ten seconds
|
||||
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
||||
|
||||
except Exception as e:
|
||||
#logger.exception('Got exception executing script:')
|
||||
self.showError(e)
|
||||
|
||||
def endScript(self):
|
||||
# After running script, wait for stuff
|
||||
try:
|
||||
tools.waitForTasks()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tools.unlinkFiles()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
tools.execBeforeExit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.closeWindow()
|
||||
|
||||
def start(self):
|
||||
'''
|
||||
Starts proccess by requesting version info
|
||||
'''
|
||||
self.ui.info.setText('Initializing...')
|
||||
QtCore.QTimer.singleShot(100, self.getVersion)
|
||||
|
||||
|
||||
def done(data):
|
||||
QtGui.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtGui.QMessageBox.Ok)
|
||||
sys.exit(0)
|
||||
|
||||
# Ask user to approve endpoint
|
||||
def approveHost(hostName, parentWindow=None):
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup('endpoints')
|
||||
|
||||
approved = settings.value(hostName, False).toBool()
|
||||
|
||||
errorString = '<p>The server <b>{}</b> must be approved:</p>'.format(hostName)
|
||||
errorString += '<p>Only approve UDS servers that you trust to avoid security issues.</p>'
|
||||
|
||||
if approved or QtGui.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
|
||||
settings.setValue(hostName, True)
|
||||
approved = True
|
||||
|
||||
settings.endGroup()
|
||||
return approved
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.debug('Initializing connector')
|
||||
# Initialize app
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
# Set several info for settings
|
||||
QtCore.QCoreApplication.setOrganizationName('Virtual Cable S.L.U.')
|
||||
QtCore.QCoreApplication.setApplicationName('UDS Connector')
|
||||
|
||||
if 'darwin' not in sys.platform:
|
||||
logger.debug('Mac OS *NOT* Detected')
|
||||
app.setStyle('plastique')
|
||||
|
||||
if six.PY3 is False:
|
||||
logger.debug('Fixing threaded execution of commands')
|
||||
import threading
|
||||
threading._DummyThread._Thread__stop = lambda x: 42 # type: ignore, pylint: disable=protected-access
|
||||
|
||||
# First parameter must be url
|
||||
try:
|
||||
uri = sys.argv[1]
|
||||
|
||||
if uri == '--test':
|
||||
sys.exit(0)
|
||||
|
||||
logger.debug('URI: %s', uri)
|
||||
if uri[:6] != 'uds://' and uri[:7] != 'udss://':
|
||||
raise Exception()
|
||||
|
||||
ssl = uri[3] == 's'
|
||||
host, UDSClient.ticket, UDSClient.scrambler = uri.split('//')[1].split('/') # type: ignore
|
||||
logger.debug('ssl:%s, host:%s, ticket:%s, scrambler:%s', ssl, host, UDSClient.ticket, UDSClient.scrambler)
|
||||
except Exception:
|
||||
logger.debug('Detected execution without valid URI, exiting')
|
||||
QtGui.QMessageBox.critical(None, 'Notice', 'UDS Client Version {}'.format(VERSION), QtGui.QMessageBox.Ok)
|
||||
sys.exit(1)
|
||||
|
||||
# Setup REST api endpoint
|
||||
RestRequest.restApiUrl = '{}://{}/rest/client'.format(['http', 'https'][ssl], host)
|
||||
logger.debug('Setting request URL to %s', RestRequest.restApiUrl)
|
||||
# RestRequest.restApiUrl = 'https://172.27.0.1/rest/client'
|
||||
|
||||
try:
|
||||
logger.debug('Starting execution')
|
||||
|
||||
# Approbe before going on
|
||||
if approveHost(host) is False:
|
||||
raise Exception('Host {} was not approved'.format(host))
|
||||
|
||||
win = UDSClient()
|
||||
win.show()
|
||||
|
||||
win.start()
|
||||
|
||||
exitVal = app.exec_()
|
||||
logger.debug('Execution finished correctly')
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Got an exception executing client:')
|
||||
exitVal = 128
|
||||
QtGui.QMessageBox.critical(None, 'Error', six.text_type(e), QtGui.QMessageBox.Ok)
|
||||
|
||||
logger.debug('Exiting')
|
||||
sys.exit(exitVal)
|
@@ -1,6 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="images">
|
||||
<file alias="logo-uds-small">images/logo-uds-small.png</file>
|
||||
<file alias="logo-uds-big">images/logo-uds.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user