mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-17 16:26:08 +03:00
Compare commits
2494 Commits
1.2
...
patch/user
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76dfad9462 | ||
|
|
6da26057a1 | ||
|
|
f45624c023 | ||
|
|
bfb8d3b45d | ||
|
|
33f1658ade | ||
|
|
9fbea76b74 | ||
|
|
b3ff120bcf | ||
|
|
9dea98f020 | ||
|
|
c4701d4e7a | ||
|
|
48903ca3a1 | ||
|
|
0c9fd4aef4 | ||
|
|
b2af2e46ac | ||
|
|
efc76a0683 | ||
|
|
c4a553c166 | ||
|
|
69a00b0252 | ||
|
|
4257c08b43 | ||
|
|
c9e5b92f79 | ||
|
|
99818c2ad8 | ||
|
|
99e3afabad | ||
|
|
d3339a7f3a | ||
|
|
678bfffe49 | ||
|
|
728b48044c | ||
|
|
7ccbfa48bc | ||
|
|
83460bc29b | ||
|
|
c28e1b468a | ||
|
|
abd7fdd19c | ||
|
|
2b1ec9c693 | ||
|
|
19fcddfdaf | ||
|
|
0bca78eca9 | ||
|
|
68046a0b7c | ||
|
|
d19017f87b | ||
|
|
46536bc60a | ||
|
|
6a424e9858 | ||
|
|
8afe50cd87 | ||
|
|
48980c486e | ||
|
|
5f6cd282d3 | ||
|
|
95121c06e2 | ||
|
|
c2b17c128d | ||
|
|
eda24765e7 | ||
|
|
35e0e146e6 | ||
|
|
a5254ac238 | ||
|
|
517b5e5ca6 | ||
|
|
cfeb6cbffd | ||
|
|
c128ba981c | ||
|
|
a1ca994c8b | ||
|
|
52c12940c4 | ||
|
|
25d759374c | ||
|
|
e9250afd2b | ||
|
|
eb83086d5c | ||
|
|
9398e0e695 | ||
|
|
915c8f46c5 | ||
|
|
ec132ac96c | ||
|
|
101838404e | ||
|
|
db3164223a | ||
|
|
5a7b5d34fb | ||
|
|
9420333c76 | ||
|
|
f6403fe82e | ||
|
|
c55b025eee | ||
|
|
fc6fc26148 | ||
|
|
48b43ee102 | ||
|
|
e091020692 | ||
|
|
07baf0ed65 | ||
|
|
42d3d9b98a | ||
|
|
389c1f5327 | ||
|
|
703b9137e0 | ||
|
|
b183a3b232 | ||
|
|
f163f0fc1d | ||
|
|
3b49d5ca59 | ||
|
|
236e5ca2e3 | ||
|
|
2f6e28b980 | ||
|
|
46d96a8887 | ||
|
|
56221881da | ||
|
|
3f55f6a629 | ||
|
|
7c8ae9c311 | ||
|
|
b173dcaa17 | ||
|
|
da5fe1d766 | ||
|
|
a15ea0e8a1 | ||
|
|
fbbba648c4 | ||
|
|
f79bfa9d2e | ||
|
|
3011a0e306 | ||
|
|
76640311ab | ||
|
|
e707471b04 | ||
|
|
6425700d1c | ||
|
|
36045c6694 | ||
|
|
52ecd6899b | ||
|
|
49a6a9ed76 | ||
|
|
4869429eb6 | ||
|
|
956dd6e37a | ||
|
|
665a2911be | ||
|
|
1cfa4e0630 | ||
|
|
5bda624576 | ||
|
|
d1f0560595 | ||
|
|
df07fc1b1f | ||
|
|
8ca31e0c90 | ||
|
|
f1c6067485 | ||
|
|
ca04c63f5e | ||
|
|
89cdd2bece | ||
|
|
73d7dfa54f | ||
|
|
0a5b54a2e4 | ||
|
|
e43aa02a5b | ||
|
|
c3fb62a6ab | ||
|
|
62f3a339b7 | ||
|
|
767b14b37a | ||
|
|
e7fa160c9c | ||
|
|
7350d79c50 | ||
|
|
86f08554cd | ||
|
|
a741186c21 | ||
|
|
6acaab0ffa | ||
|
|
212e9b3a91 | ||
|
|
2bff37efae | ||
|
|
b88ab8e432 | ||
|
|
48f6cf904e | ||
|
|
367789bda2 | ||
|
|
d06924c59d | ||
|
|
2db99715b1 | ||
|
|
9688a8e52d | ||
|
|
b2c429f74d | ||
|
|
6ea6ab1bd9 | ||
|
|
c5aa070bf4 | ||
|
|
d67201ede9 | ||
|
|
4323fb2063 | ||
|
|
6d5452b8ee | ||
|
|
569d63ef0f | ||
|
|
ea910ba300 | ||
|
|
1c1e74d06f | ||
|
|
5dc16c06f1 | ||
|
|
4efaf20a1c | ||
|
|
9d96b1cd13 | ||
|
|
1d721ffb9a | ||
|
|
2130131a9d | ||
|
|
e0b091b474 | ||
|
|
8547de82ea | ||
|
|
aa871bd1c9 | ||
|
|
23806e1def | ||
|
|
31867993ce | ||
|
|
7b7a922d92 | ||
|
|
09bd958d8d | ||
|
|
576e2226fe | ||
|
|
1533270e4e | ||
|
|
e7b25719e4 | ||
|
|
7183e8541c | ||
|
|
9e71e64cbd | ||
|
|
4f3bae4a9a | ||
|
|
990059f8a6 | ||
|
|
af55af5e76 | ||
|
|
82d96a9691 | ||
|
|
9f3f215452 | ||
|
|
2dfc6a87b8 | ||
|
|
7261a86c48 | ||
|
|
2946dd2278 | ||
|
|
5065262aac | ||
|
|
4685d3b543 | ||
|
|
7a389e8755 | ||
|
|
4e5daf22a3 | ||
|
|
3bf9c10d7d | ||
|
|
2e175cb9fc | ||
|
|
823c1b5d3a | ||
|
|
92bc1a6f09 | ||
|
|
d511220f8b | ||
|
|
923e358aaa | ||
|
|
92b19eccf6 | ||
|
|
5358aaeb00 | ||
|
|
e31a2066c0 | ||
|
|
928c4f18c9 | ||
|
|
628e22869d | ||
|
|
c9cd860654 | ||
|
|
17984adae5 | ||
|
|
5601bc4fdf | ||
|
|
e14681801e | ||
|
|
f106b4d367 | ||
|
|
74802f30ed | ||
|
|
d63bf15011 | ||
|
|
60de146f03 | ||
|
|
c4f32eed31 | ||
|
|
2c9067b0de | ||
|
|
6844a2375b | ||
|
|
7b838e77a0 | ||
|
|
694e781beb | ||
|
|
399a8c6d28 | ||
|
|
dce08b3ecc | ||
|
|
2763da960f | ||
|
|
d4fff4af3c | ||
|
|
f0903c32f3 | ||
|
|
ea8875478e | ||
|
|
4c08e9f3bc | ||
|
|
e8736102bf | ||
|
|
371cadcc02 | ||
|
|
c3805195af | ||
|
|
2ef267bc44 | ||
|
|
02a98b9d68 | ||
|
|
94bae4b859 | ||
|
|
425acc5f8b | ||
|
|
bb87c0838d | ||
|
|
1542adba82 | ||
|
|
3aa8a46f6e | ||
|
|
1f08d78b43 | ||
|
|
268adfb0a1 | ||
|
|
c681611102 | ||
|
|
4fc2a23f49 | ||
|
|
23f4a6ec8e | ||
|
|
504862c2b8 | ||
|
|
a22a9448ca | ||
|
|
862e83ddf5 | ||
|
|
8735eee662 | ||
|
|
ff82cf5dc4 | ||
|
|
8648790583 | ||
|
|
b881d92a80 | ||
|
|
7ad7f31e4d | ||
|
|
138e6f70a4 | ||
|
|
6f94f4646a | ||
|
|
4a01d2cf20 | ||
|
|
8948601caa | ||
|
|
aa92ccd06d | ||
|
|
253ae75795 | ||
|
|
87cb5f620a | ||
|
|
46cd740a84 | ||
|
|
76e5039578 | ||
|
|
c6b131aa4c | ||
|
|
5e72bf945c | ||
|
|
eebf7eccec | ||
|
|
168c293bfe | ||
|
|
aae3cdcac1 | ||
|
|
96566f04ee | ||
|
|
fff15fffe2 | ||
|
|
4e5a03e7f1 | ||
|
|
7571bbc36e | ||
|
|
db4a1a62e5 | ||
|
|
581773ce03 | ||
|
|
46058f614e | ||
|
|
9cab51fb00 | ||
|
|
918be16372 | ||
|
|
175477d31f | ||
|
|
cd70b7e619 | ||
|
|
22011e263e | ||
|
|
88a2b9a07a | ||
|
|
248f487d4e | ||
|
|
572ef09296 | ||
|
|
03078236ab | ||
|
|
b39a0a1d94 | ||
|
|
e94fc688ba | ||
|
|
558f613acc | ||
|
|
d800a95a1d | ||
|
|
b8f100d4fa | ||
|
|
51618fb882 | ||
|
|
14f537ba76 | ||
|
|
3458ed78d7 | ||
|
|
4bc571f609 | ||
|
|
ee61f842e5 | ||
|
|
758b25947c | ||
|
|
b036c38981 | ||
|
|
eab2b8e45a | ||
|
|
dfdec2bf4b | ||
|
|
843156cf1b | ||
|
|
01413e5a4c | ||
|
|
743304c359 | ||
|
|
6c5d590169 | ||
|
|
a1e68f5506 | ||
|
|
424315b17f | ||
|
|
b83e74427e | ||
|
|
8fefae0325 | ||
|
|
ede633ea03 | ||
|
|
b4469064a2 | ||
|
|
393e289784 | ||
|
|
d18423ee8c | ||
|
|
3fc9edd346 | ||
|
|
1a1f75d873 | ||
|
|
df02e0bf78 | ||
|
|
264d77463d | ||
|
|
0a37ffd5e3 | ||
|
|
1343d10aa7 | ||
|
|
6f96ebd8bf | ||
|
|
cb531dacb3 | ||
|
|
dfd0b4d0e5 | ||
|
|
f978f55e7f | ||
|
|
73516c28af | ||
|
|
dc85a99e08 | ||
|
|
ef06fcb4f4 | ||
|
|
ffe2314d47 | ||
|
|
4e970322d0 | ||
|
|
8dee0d27cf | ||
|
|
c520f9a2a4 | ||
|
|
003c3a23c4 | ||
|
|
1754a82f67 | ||
|
|
3384008277 | ||
|
|
af22115706 | ||
|
|
4b114fd3b6 | ||
|
|
9d531f5d74 | ||
|
|
a5564148f5 | ||
|
|
196f7778fc | ||
|
|
de20add857 | ||
|
|
c59216b58a | ||
|
|
acf7fa261a | ||
|
|
c3eddc92bd | ||
|
|
18c74f4b02 | ||
|
|
3f90ee915d | ||
|
|
401ad0db0e | ||
|
|
5945133d30 | ||
|
|
ff4fbde0b0 | ||
|
|
74ae4f3e67 | ||
|
|
ae4b33d042 | ||
|
|
53fa280037 | ||
|
|
8ecde90bc7 | ||
|
|
34a583f272 | ||
|
|
5de4b8eeb8 | ||
|
|
aae420e469 | ||
|
|
0612f70c06 | ||
|
|
d0c82efa1c | ||
|
|
cf8492240e | ||
|
|
2bceb9f7ba | ||
|
|
760f935965 | ||
|
|
eeeb2805c5 | ||
|
|
9a592d67ad | ||
|
|
ea6618b2f6 | ||
|
|
7b092e73ad | ||
|
|
b2e25c42c7 | ||
|
|
c8dd38ac31 | ||
|
|
563ee4703f | ||
|
|
beceed81de | ||
|
|
3bf96253db | ||
|
|
da2d0ec203 | ||
|
|
008b858203 | ||
|
|
130fc8277d | ||
|
|
468d3357b8 | ||
|
|
f1271da527 | ||
|
|
249a7c7ca3 | ||
|
|
0094d0ebc4 | ||
|
|
834b504dff | ||
|
|
a516d0e757 | ||
|
|
afdfbdbc59 | ||
|
|
ef712b7054 | ||
|
|
c22f9ff08a | ||
|
|
04fb1825d5 | ||
|
|
4f8f873682 | ||
|
|
9fe75c6120 | ||
|
|
bb7e8f46cb | ||
|
|
5db0c281ee | ||
|
|
aac9bfcea6 | ||
|
|
e6ee9085a2 | ||
|
|
d62ade58a5 | ||
|
|
d8020878d5 | ||
|
|
b027fff103 | ||
|
|
a0c06048cd | ||
|
|
53746f2f66 | ||
|
|
2649dba4ad | ||
|
|
6a1e3c07b1 | ||
|
|
a365eff76f | ||
|
|
8f9acd9367 | ||
|
|
53fdf5f70d | ||
|
|
9be13ea465 | ||
|
|
871aced1d1 | ||
|
|
2254bfc128 | ||
|
|
b71dcb8dd0 | ||
|
|
33d1518fd2 | ||
|
|
ee5344a4ea | ||
|
|
abb3c918e3 | ||
|
|
ff348a348c | ||
|
|
d67c378bff | ||
|
|
d85a0439c5 | ||
|
|
9faabe9e7d | ||
|
|
5bd8c33a6d | ||
|
|
24759c92ad | ||
|
|
9e92ee020e | ||
|
|
7a4f6b628b | ||
|
|
7e2f223d7f | ||
|
|
eb48e4b668 | ||
|
|
9ace09a604 | ||
|
|
702735c2ca | ||
|
|
174f2ac3db | ||
|
|
e3b5b4a9d9 | ||
|
|
72ba012765 | ||
|
|
0f9bbcd060 | ||
|
|
a9d038d8bf | ||
|
|
54a6845315 | ||
|
|
0c7059a476 | ||
|
|
5bed92ab0b | ||
|
|
49a14785c6 | ||
|
|
2c78c06dda | ||
|
|
cf8a0efd0d | ||
|
|
5211cdd4c0 | ||
|
|
d10aa43d8b | ||
|
|
6b0f1ed429 | ||
|
|
4bde1ccb44 | ||
|
|
03c18c44e2 | ||
|
|
72ffc7ce6a | ||
|
|
87b738ef16 | ||
|
|
b868831bcb | ||
|
|
477d7214c5 | ||
|
|
f3cd3d4f06 | ||
|
|
aea4cc2389 | ||
|
|
245aa8eb8c | ||
|
|
f14a2add0f | ||
|
|
89703ba58f | ||
|
|
23715fca8b | ||
|
|
d90685600e | ||
|
|
f007e5eb5c | ||
|
|
a8ccea00c7 | ||
|
|
cd2ee00769 | ||
|
|
c98a418807 | ||
|
|
0e4ae26bae | ||
|
|
d50e7dd3f4 | ||
|
|
f0085f52eb | ||
|
|
5c19b08e5e | ||
|
|
79edbe52a3 | ||
|
|
0dd181bb5b | ||
|
|
d8682003fa | ||
|
|
f4a2cf9984 | ||
|
|
98e6358fd3 | ||
|
|
af90065d2e | ||
|
|
f372f4074b | ||
|
|
6a2e5f83a1 | ||
|
|
a2badd46c4 | ||
|
|
8623a983b8 | ||
|
|
151e662027 | ||
|
|
f588fe29db | ||
|
|
030b0351a2 | ||
|
|
d4453a5f38 | ||
|
|
2252905596 | ||
|
|
ec650a65f7 | ||
|
|
6953f8d814 | ||
|
|
624a84cbfb | ||
|
|
506d9793e1 | ||
|
|
ef52f6ab08 | ||
|
|
5312a6e885 | ||
|
|
fdd600794e | ||
|
|
7bfbdca72a | ||
|
|
a22c08a41d | ||
|
|
10ea9b418a | ||
|
|
e39efb1d68 | ||
|
|
7db84122f9 | ||
|
|
84ad167ab4 | ||
|
|
ed7e217a6b | ||
|
|
c1b0d4a4a7 | ||
|
|
2f84e24353 | ||
|
|
f73586185b | ||
|
|
653ffb9a68 | ||
|
|
fe8c2d157a | ||
|
|
86367a1276 | ||
|
|
b0fcf92ada | ||
|
|
283b6ebf81 | ||
|
|
d0a7fc5116 | ||
|
|
9851aacba7 | ||
|
|
51f9fb9e0a | ||
|
|
0325761f3e | ||
|
|
a6ca1b12da | ||
|
|
82a9e7e27d | ||
|
|
f5301e1315 | ||
|
|
adab30fc81 | ||
|
|
e7bd24f065 | ||
|
|
2ec448ba13 | ||
|
|
c6e6f2ae84 | ||
|
|
e9468a4c2f | ||
|
|
45de951897 | ||
|
|
db8d966fac | ||
|
|
6b69bc9618 | ||
|
|
0089b0b799 | ||
|
|
e4841e809b | ||
|
|
ba4237f1dd | ||
|
|
f6acec53c0 | ||
|
|
5f631eaa76 | ||
|
|
7730dd510c | ||
|
|
30bd264f17 | ||
|
|
5206665fa0 | ||
|
|
073491ccb4 | ||
|
|
561b62cd40 | ||
|
|
1284ed4d84 | ||
|
|
6f34443191 | ||
|
|
02f186c54e | ||
|
|
784c6cf585 | ||
|
|
14f132e127 | ||
|
|
9cb624e681 | ||
|
|
516e3da7e2 | ||
|
|
0e83586cae | ||
|
|
95bdae68f4 | ||
|
|
294778884b | ||
|
|
10caecbffd | ||
|
|
553a6a73dd | ||
|
|
e646b85e56 | ||
|
|
b7c513c05f | ||
|
|
9f82b4c21f | ||
|
|
02b2da38cf | ||
|
|
f51077b2be | ||
|
|
33f49bfddb | ||
|
|
9a81f13f81 | ||
|
|
915fb6759a | ||
|
|
c5a5bfde69 | ||
|
|
0a90fd110d | ||
|
|
541d6eb0b8 | ||
|
|
d443a0063d | ||
|
|
f0c6edb670 | ||
|
|
9189b53a0d | ||
|
|
fceccaefcc | ||
|
|
fbeabf43ca | ||
|
|
78c7893f90 | ||
|
|
cb9a25006c | ||
|
|
0e87550d85 | ||
|
|
dceb0ab832 | ||
|
|
a33590476a | ||
|
|
deaf618520 | ||
|
|
3d8a56d922 | ||
|
|
36af7cf471 | ||
|
|
ebd3449b4a | ||
|
|
99182f4a67 | ||
|
|
da84ba1a4d | ||
|
|
bca68fc185 | ||
|
|
59a7265bac | ||
|
|
9201ca1e03 | ||
|
|
6b6a76d2cc | ||
|
|
840c388ab9 | ||
|
|
5b4ec608c8 | ||
|
|
79ff1b81e0 | ||
|
|
ea67c01da8 | ||
|
|
1137e169ea | ||
|
|
17748cca47 | ||
|
|
080e1d98c6 | ||
|
|
ca633ae882 | ||
|
|
bb7b64fb96 | ||
|
|
bf901631bf | ||
|
|
0c0ce54b1f | ||
|
|
ee762c4cef | ||
|
|
ed9efb5a79 | ||
|
|
73eb85f2f4 | ||
|
|
cd055cff62 | ||
|
|
f8b2cce618 | ||
|
|
e648054c7a | ||
|
|
fe558163cc | ||
|
|
3883b8ff34 | ||
|
|
d286664763 | ||
|
|
b05ad2392b | ||
|
|
6dbdb85aaf | ||
|
|
26b48cfe4f | ||
|
|
2f39136143 | ||
|
|
8d0d3c5ce9 | ||
|
|
256081e4ed | ||
|
|
1dd7b0a221 | ||
|
|
82c0b28906 | ||
|
|
985fe083f0 | ||
|
|
6a0000dc4b | ||
|
|
1dd2f38066 | ||
|
|
004e1e3ca5 | ||
|
|
7c560d709b | ||
|
|
d3743ad62f | ||
|
|
ac234b77e2 | ||
|
|
9886987e68 | ||
|
|
d34cb8898f | ||
|
|
13aadbda64 | ||
|
|
c7c7c8eb01 | ||
|
|
b1e5bba33f | ||
|
|
474e7c6d62 | ||
|
|
794ec921b8 | ||
|
|
b674240362 | ||
|
|
a768c7c451 | ||
|
|
28d2a4ec2c | ||
|
|
9f1210d18f | ||
|
|
3012559627 | ||
|
|
b3ed57aee7 | ||
|
|
89d0a8107d | ||
|
|
6c0b71bd1b | ||
|
|
61abf74b2d | ||
|
|
21fdf02921 | ||
|
|
7a245d80ee | ||
|
|
da85922f23 | ||
|
|
a5356b6319 | ||
|
|
3828891b9b | ||
|
|
15d866ce04 | ||
|
|
560eb3d620 | ||
|
|
ac894254cc | ||
|
|
17e3fbde25 | ||
|
|
ee11a8410c | ||
|
|
ff5c51cfd9 | ||
|
|
b3943ae5e3 | ||
|
|
a32952fde6 | ||
|
|
9c4ee4014d | ||
|
|
dc9069f1f4 | ||
|
|
e402cacc05 | ||
|
|
a98cd248d6 | ||
|
|
00fbfb6a01 | ||
|
|
86c31c3766 | ||
|
|
698cfe910c | ||
|
|
16db23c159 | ||
|
|
b05a5ee1c6 | ||
|
|
8cb298937f | ||
|
|
68fe20ddf6 | ||
|
|
fab167bb34 | ||
|
|
f640d4b5f5 | ||
|
|
074562b141 | ||
|
|
fd030a5fd4 | ||
|
|
82fa6b13c6 | ||
|
|
bf16298c40 | ||
|
|
bcebb0a2b5 | ||
|
|
b27442cf74 | ||
|
|
92fbbd4812 | ||
|
|
321ed810e3 | ||
|
|
17ff530683 | ||
|
|
2b413736a4 | ||
|
|
a416d03614 | ||
|
|
4de9a274dd | ||
|
|
0b8f3c9d9d | ||
|
|
f3a168fd43 | ||
|
|
158c11a0ec | ||
|
|
f2d13148a3 | ||
|
|
6c8b4e1fb2 | ||
|
|
c95dffff0c | ||
|
|
6346fc04ae | ||
|
|
3a87354f8d | ||
|
|
e0863a58aa | ||
|
|
dba05aab07 | ||
|
|
fec904fd28 | ||
|
|
db321bed5d | ||
|
|
28cc0218c5 | ||
|
|
a2d5f25b58 | ||
|
|
3100160927 | ||
|
|
1c3fdd3c72 | ||
|
|
0c13eda1f5 | ||
|
|
f37b7cec87 | ||
|
|
962c75b2ca | ||
|
|
9a62e2ae41 | ||
|
|
0f00dc4e7c | ||
|
|
0f04cf6eae | ||
|
|
dc76ad820f | ||
|
|
e0cde9f138 | ||
|
|
db1dc78e38 | ||
|
|
fd98ef1250 | ||
|
|
cdf46c968a | ||
|
|
72b20ef563 | ||
|
|
30a0ac0def | ||
|
|
090208bd2c | ||
|
|
1e5c9c9c4d | ||
|
|
5918f37ffa | ||
|
|
554e1b1b91 | ||
|
|
8f510c1431 | ||
|
|
4723019624 | ||
|
|
1be9078b6c | ||
|
|
520658a295 | ||
|
|
b9ec722abb | ||
|
|
d917c798d7 | ||
|
|
0d168c039f | ||
|
|
af8f265555 | ||
|
|
c0e0d64284 | ||
|
|
9466a71141 | ||
|
|
d28a586a97 | ||
|
|
6fde0b6663 | ||
|
|
d143b9213b | ||
|
|
16433e9e46 | ||
|
|
39479d1999 | ||
|
|
a03b766e33 | ||
|
|
7df2655ba0 | ||
|
|
5c2ca9803d | ||
|
|
4e6af947fa | ||
|
|
bc9d5c8fd6 | ||
|
|
f412ac6260 | ||
|
|
26d8dfbb7f | ||
|
|
b45517bafd | ||
|
|
3edb6755b4 | ||
|
|
bda64fa391 | ||
|
|
e7040f7cc8 | ||
|
|
229970e799 | ||
|
|
7b0a9a055f | ||
|
|
06d370f716 | ||
|
|
ce0a4f1f96 | ||
|
|
3240aa3cb3 | ||
|
|
5c5935c738 | ||
|
|
00b2b1abcd | ||
|
|
e0891e1a15 | ||
|
|
f7df621c56 | ||
|
|
0b6dc5bcfc | ||
|
|
cbd6755aa5 | ||
|
|
3afbc248b1 | ||
|
|
30af81fe0a | ||
|
|
427b43c99b | ||
|
|
ed08ac6b46 | ||
|
|
1a2c1fa1b5 | ||
|
|
709fbac231 | ||
|
|
6b80a56f92 | ||
|
|
5c9d45a8a8 | ||
|
|
2edac24945 | ||
|
|
b68c9cc145 | ||
|
|
6f02d4eb62 | ||
|
|
6e60688e70 | ||
|
|
140b5c4292 | ||
|
|
8ee2ebbd20 | ||
|
|
b3eda4106d | ||
|
|
885e22be7c | ||
|
|
83ec073734 | ||
|
|
0160b0f9dc | ||
|
|
f55bd5e1a1 | ||
|
|
4d88eb8e79 | ||
|
|
874de74ac8 | ||
|
|
b3a4b34d48 | ||
|
|
f7b9d2bae7 | ||
|
|
ec4574bfcf | ||
|
|
73cfab166f | ||
|
|
0e8f85057d | ||
|
|
2e4908c557 | ||
|
|
401784414a | ||
|
|
8495124bc8 | ||
|
|
ba6ed540f5 | ||
|
|
19a60ea856 | ||
|
|
aecf1b463c | ||
|
|
ff24ba1011 | ||
|
|
802b708232 | ||
|
|
adeff3efb9 | ||
|
|
0103c1722e | ||
|
|
f9123e7b71 | ||
|
|
0f9ed4e69c | ||
|
|
9dfc95bac0 | ||
|
|
9b7914538f | ||
|
|
85c9cd260d | ||
|
|
73751cc049 | ||
|
|
2b61c48303 | ||
|
|
47b03f2bf4 | ||
|
|
3e02dfef63 | ||
|
|
4176d0130a | ||
|
|
f6175c2c69 | ||
|
|
b8b423ca19 | ||
|
|
3702d69b9d | ||
|
|
3253de9384 | ||
|
|
51070635a5 | ||
|
|
9b533f1ba6 | ||
|
|
9371dd405e | ||
|
|
27d8108e55 | ||
|
|
820a3f0b77 | ||
|
|
3d61b20271 | ||
|
|
eec81f8124 | ||
|
|
d7fbddb97f | ||
|
|
9170cb0318 | ||
|
|
76dec6fa9c | ||
|
|
4e3edcfc0a | ||
|
|
ad21d7ab64 | ||
|
|
f576eb509d | ||
|
|
33e229d0b2 | ||
|
|
78420d617b | ||
|
|
2552e33d64 | ||
|
|
301141c755 | ||
|
|
fac57ac89a | ||
|
|
da5ad0a845 | ||
|
|
60aeefe1c2 | ||
|
|
d527f6a5ce | ||
|
|
2802b42747 | ||
|
|
66c5d2f0a8 | ||
|
|
6dbf4ac62c | ||
|
|
ae2872830b | ||
|
|
6f4a3587e4 | ||
|
|
145f51906e | ||
|
|
12e72bc74b | ||
|
|
65a04799ef | ||
|
|
88cd5825d3 | ||
|
|
ecdad2a315 | ||
|
|
0398ddd6a2 | ||
|
|
50ea4d3b0f | ||
|
|
77be8169f2 | ||
|
|
7a435f76b6 | ||
|
|
42949e0dea | ||
|
|
26db423232 | ||
|
|
645cf52803 | ||
|
|
673b8ad5b2 | ||
|
|
2a03834bb2 | ||
|
|
c1b6149e49 | ||
|
|
0690d86e52 | ||
|
|
bb8a11d110 | ||
|
|
4f7ba4c9a8 | ||
|
|
aa41e4d915 | ||
|
|
bb43b5451f | ||
|
|
69dd415ab5 | ||
|
|
e605f549bd | ||
|
|
a961932b2e | ||
|
|
e8cc80f046 | ||
|
|
67f29ac483 | ||
|
|
c9bde5cdc0 | ||
|
|
e878911819 | ||
|
|
f1a0b7f0ef | ||
|
|
3e0a5104e7 | ||
|
|
8f53d563a4 | ||
|
|
414740ffb7 | ||
|
|
7437d47d92 | ||
|
|
a68f19d72f | ||
|
|
11641c5e22 | ||
|
|
8d3e21d46a | ||
|
|
5ad54bfdc1 | ||
|
|
a8f5e95fb1 | ||
|
|
a4f3d08c02 | ||
|
|
3d2174d84e | ||
|
|
12fbc7d258 | ||
|
|
164b7e2551 | ||
|
|
eafac491d8 | ||
|
|
3cfb6e968d | ||
|
|
375825125f | ||
|
|
a8520e7545 | ||
|
|
ac154cdd83 | ||
|
|
9290775ab5 | ||
|
|
d14e8cdee4 | ||
|
|
7aac9f9d0e | ||
|
|
41aaac7d32 | ||
|
|
c8d2399db9 | ||
|
|
f37c8e5fd4 | ||
|
|
9f5025c10b | ||
|
|
c27d999c41 | ||
|
|
9681bea237 | ||
|
|
8905d6352c | ||
|
|
8599b20678 | ||
|
|
491f09a51b | ||
|
|
0f1519a21f | ||
|
|
1a17f2956a | ||
|
|
d94e27bfa9 | ||
|
|
b892156092 | ||
|
|
7b1b8dc749 | ||
|
|
6d0167dcf3 | ||
|
|
0c2d661e1c | ||
|
|
95d1440d6f | ||
|
|
0cabf60dc4 | ||
|
|
836ca1cc6b | ||
|
|
be799daafe | ||
|
|
ad9d674a03 | ||
|
|
f52c3c430f | ||
|
|
a91ab0e910 | ||
|
|
39d1f1677f | ||
|
|
b0dcae3586 | ||
|
|
ed6351f8f1 | ||
|
|
e486a2fb26 | ||
|
|
195bdb947e | ||
|
|
1576aed1ea | ||
|
|
e66fbc3289 | ||
|
|
b4c89ad58f | ||
|
|
8cc5846808 | ||
|
|
2eaaf01ca1 | ||
|
|
67694c0f96 | ||
|
|
163e5b2c52 | ||
|
|
508f1d3a42 | ||
|
|
d2207a5255 | ||
|
|
bf03f5c9ae | ||
|
|
656223f57d | ||
|
|
dc6e3ec53b | ||
|
|
5835a756ce | ||
|
|
f3f98a50ed | ||
|
|
0f4bb78712 | ||
|
|
c4014518cb | ||
|
|
3605f62feb | ||
|
|
5c3e253067 | ||
|
|
8d43cee52e | ||
|
|
1e64413904 | ||
|
|
6fd1ea26ee | ||
|
|
e619fd4af9 | ||
|
|
e7658f9859 | ||
|
|
3defb09da9 | ||
|
|
a672434909 | ||
|
|
c3fdd977b1 | ||
|
|
dd233f77fc | ||
|
|
02efd9c217 | ||
|
|
20f3c0388a | ||
|
|
ef530780bd | ||
|
|
d7ec611ff4 | ||
|
|
b897e7102e | ||
|
|
1cc5c5384e | ||
|
|
db602ac65b | ||
|
|
eaa209bc3a | ||
|
|
51d4aea9e2 | ||
|
|
9738ada946 | ||
|
|
8ec105bee0 | ||
|
|
4fd0852bb3 | ||
|
|
a53e904f7b | ||
|
|
2d22b52b5d | ||
|
|
426ac49f6f | ||
|
|
c164814abd | ||
|
|
3084892ed8 | ||
|
|
9297f877c4 | ||
|
|
1bf808c9ee | ||
|
|
91f44fb394 | ||
|
|
385a52f676 | ||
|
|
8ef16781eb | ||
|
|
ad5ea1ca44 | ||
|
|
0ba5d754d5 | ||
|
|
ccdcfdce8a | ||
|
|
712fb4d0d3 | ||
|
|
de65a03998 | ||
|
|
6d6710db4a | ||
|
|
c34c3e51ea | ||
|
|
5dc3b64e0b | ||
|
|
e8ceeb6e20 | ||
|
|
d38c7ce6a5 | ||
|
|
e625543b94 | ||
|
|
679bd4e4c9 | ||
|
|
c6a312845a | ||
|
|
ef0530ec6b | ||
|
|
2c98a90d60 | ||
|
|
b90fad6664 | ||
|
|
5a5ea4a018 | ||
|
|
bc68c487ee | ||
|
|
617cdf14ad | ||
|
|
f1c970461f | ||
|
|
2fde47a86f | ||
|
|
4e5f2f44b6 | ||
|
|
e8a2e54d05 | ||
|
|
d77f3ecee8 | ||
|
|
06776ebe8f | ||
|
|
e1eec55f62 | ||
|
|
fcabf08e74 | ||
|
|
b4694313a0 | ||
|
|
abb2cae1f8 | ||
|
|
b0004fd9dc | ||
|
|
362a82f944 | ||
|
|
19fe61ed29 | ||
|
|
72de38b4fb | ||
|
|
02c0f96e5e | ||
|
|
aee82282ac | ||
|
|
8497aeeb91 | ||
|
|
5e9f688000 | ||
|
|
6a7e346695 | ||
|
|
071738116e | ||
|
|
147726ecb0 | ||
|
|
ae4ee6431d | ||
|
|
9cfcb714ae | ||
|
|
d1ccde2a4b | ||
|
|
4848091203 | ||
|
|
282f159311 | ||
|
|
4ef8c77a2d | ||
|
|
08c1cf2439 | ||
|
|
2fc33875bb | ||
|
|
9e92e4b5ff | ||
|
|
7f2cf70bf5 | ||
|
|
40725d4155 | ||
|
|
8164026891 | ||
|
|
0e23b3a1ac | ||
|
|
1739d4861e | ||
|
|
a6b6e7850d | ||
|
|
3e9dea6f07 | ||
|
|
1b37ca805f | ||
|
|
c772f56da7 | ||
|
|
bc183e39bb | ||
|
|
306d4f70a8 | ||
|
|
a386d39495 | ||
|
|
22b14dff5f | ||
|
|
e749cc7578 | ||
|
|
3e03002ead | ||
|
|
4ae9cddcce | ||
|
|
16724645ce | ||
|
|
6a12cad1c9 | ||
|
|
c15665803d | ||
|
|
97090888d5 | ||
|
|
4642308fbb | ||
|
|
59bccb1188 | ||
|
|
cd8fc007ac | ||
|
|
7cfb38307e | ||
|
|
994aa32745 | ||
|
|
f0b872e86b | ||
|
|
0c33432436 | ||
|
|
0bb4dd9442 | ||
|
|
7a54dc15da | ||
|
|
e16a1100d8 | ||
|
|
99214e22e3 | ||
|
|
c77d35a2ed | ||
|
|
d98fdbdc5c | ||
|
|
4551cf0a21 | ||
|
|
023c3474d2 | ||
|
|
2a4cefb4bf | ||
|
|
09305724fa | ||
|
|
360fda1ba7 | ||
|
|
dadf0cf96e | ||
|
|
3d60ac751e | ||
|
|
32793eef8c | ||
|
|
da1cdfd6fa | ||
|
|
58ad7dc161 | ||
|
|
0a15f44193 | ||
|
|
e1dec3c1ba | ||
|
|
7834860245 | ||
|
|
2da1025f26 | ||
|
|
78c83b2e21 | ||
|
|
414a47e2f2 | ||
|
|
6c78b4ec8f | ||
|
|
a6949bd3ae | ||
|
|
f7bed04ab2 | ||
|
|
6ec773079c | ||
|
|
366e27a321 | ||
|
|
32c304dc1b | ||
|
|
338499247d | ||
|
|
79e1761c1f | ||
|
|
4ea1a19572 | ||
|
|
e2ae341ba9 | ||
|
|
de03435bac | ||
|
|
e16c425f87 | ||
|
|
c461e00c5c | ||
|
|
fcf6bb43b7 | ||
|
|
f5f72f87a6 | ||
|
|
3340451245 | ||
|
|
c14f1b5000 | ||
|
|
a46e55d5c2 | ||
|
|
4b64bfaec0 | ||
|
|
2f0c1eeecc | ||
|
|
546d4c1d3d | ||
|
|
160d88f002 | ||
|
|
a83cd29f72 | ||
|
|
94304b5777 | ||
|
|
61ddfe01a1 | ||
|
|
00d334f704 | ||
|
|
f4a4979997 | ||
|
|
03171e4743 | ||
|
|
5369e68267 | ||
|
|
9eb23e38bd | ||
|
|
2a0166bb26 | ||
|
|
2df612ec1f | ||
|
|
36ba3758db | ||
|
|
7cc0f39d3c | ||
|
|
9cf5590371 | ||
|
|
81f835458f | ||
|
|
e01b1db706 | ||
|
|
cdb18de305 | ||
|
|
8e0eef3316 | ||
|
|
221d45f564 | ||
|
|
2a4a01a4be | ||
|
|
501670bdd2 | ||
|
|
24637a1693 | ||
|
|
7bd1340190 | ||
|
|
cb5c09d967 | ||
|
|
a01ba5909c | ||
|
|
1c4678af95 | ||
|
|
5f5435c645 | ||
|
|
2f7dc2c46c | ||
|
|
9bc1c9dd03 | ||
|
|
4c81cdb4a2 | ||
|
|
3406ffa7a2 | ||
|
|
6d05b6845e | ||
|
|
29b4966119 | ||
|
|
d0f8358431 | ||
|
|
a75bd07cd8 | ||
|
|
8c1835950b | ||
|
|
c7cd8e4c80 | ||
|
|
f65e4066e3 | ||
|
|
37c18c5d3c | ||
|
|
8885f580b2 | ||
|
|
512ac74ee6 | ||
|
|
384ce9853b | ||
|
|
b6d2030041 | ||
|
|
3ac09181c6 | ||
|
|
ffc9e5823a | ||
|
|
f5448fed59 | ||
|
|
8163e51434 | ||
|
|
3836836c72 | ||
|
|
b78bf39767 | ||
|
|
25f8283edd | ||
|
|
7c8399ce88 | ||
|
|
9fe2a1dd41 | ||
|
|
846f554157 | ||
|
|
d1f66cbf4d | ||
|
|
a4624c7377 | ||
|
|
10435cea69 | ||
|
|
ce9a23e021 | ||
|
|
4b7c8f21c2 | ||
|
|
6ddebdbbd1 | ||
|
|
d92729d346 | ||
|
|
fa06dbbd29 | ||
|
|
5d59a1a10e | ||
|
|
222a251180 | ||
|
|
9d6559f0d7 | ||
|
|
da02f49850 | ||
|
|
992961c488 | ||
|
|
0ce30a4e81 | ||
|
|
bb2d794b6f | ||
|
|
45dc302de4 | ||
|
|
4a2706a9d9 | ||
|
|
00be3c3ccc | ||
|
|
cb7fe50d46 | ||
|
|
d364dbac2c | ||
|
|
042788bec3 | ||
|
|
def261f578 | ||
|
|
c08e23085e | ||
|
|
e01dd2bf57 | ||
|
|
61396ec82e | ||
|
|
357c283437 | ||
|
|
1b38cd6ca7 | ||
|
|
bdfa8bfe5b | ||
|
|
7f2ef65fe6 | ||
|
|
102d0472c7 | ||
|
|
445fc6efb1 | ||
|
|
135726f177 | ||
|
|
671ca0a66f | ||
|
|
aa4a79934a | ||
|
|
16fc0617e4 | ||
|
|
64a2f3f8bb | ||
|
|
b7a65343af | ||
|
|
5c121ea48d | ||
|
|
673f28ed64 | ||
|
|
3fb97d16bb | ||
|
|
079c9176ef | ||
|
|
9377a0b545 | ||
|
|
1357c4a309 | ||
|
|
d77be5a244 | ||
|
|
08863edb52 | ||
|
|
3a77705142 | ||
|
|
1eafa9a38a | ||
|
|
396b7aac18 | ||
|
|
08defbbbd8 | ||
|
|
79d371fb76 | ||
|
|
9df262d502 | ||
|
|
6f392ce126 | ||
|
|
a83ec10b61 | ||
|
|
a93f75fb5a | ||
|
|
2353cc4f2c | ||
|
|
30709c66ef | ||
|
|
70e6a3d303 | ||
|
|
cc89939d05 | ||
|
|
7d4a01c757 | ||
|
|
e2d61cb518 | ||
|
|
fb1a9c9867 | ||
|
|
776ae04cbe | ||
|
|
2447ab4305 | ||
|
|
52124b15e8 | ||
|
|
b1e9e8677b | ||
|
|
617e772cc1 | ||
|
|
27f770604b | ||
|
|
5bfc581ad2 | ||
|
|
2664a52007 | ||
|
|
6dbbf1fc89 | ||
|
|
c254f2fdc4 | ||
|
|
cf450fa4e4 | ||
|
|
304f29bfac | ||
|
|
50b8b3d649 | ||
|
|
4e6c1094f3 | ||
|
|
a6134ca10f | ||
|
|
3d999a503c | ||
|
|
39c2124a26 | ||
|
|
4e3955b39d | ||
|
|
784ae0da53 | ||
|
|
eaede032b4 | ||
|
|
4ed153373f | ||
|
|
07d7fac490 | ||
|
|
5535b6a6e3 | ||
|
|
b7fbb84a58 | ||
|
|
19bd94ed02 | ||
|
|
54b45a36e1 | ||
|
|
ed1afa7549 | ||
|
|
2986a18c8f | ||
|
|
b2072c06b7 | ||
|
|
762018883f | ||
|
|
0322c01c0e | ||
|
|
16ccfb8714 | ||
|
|
68095700a2 | ||
|
|
058f8b544e | ||
|
|
4cb871849b | ||
|
|
423305c35a | ||
|
|
b55313527e | ||
|
|
af53c456ea | ||
|
|
37024eb91d | ||
|
|
ee99565b63 | ||
|
|
1a8c08799f | ||
|
|
8b08a5bee0 | ||
|
|
51497d87e0 | ||
|
|
7ede1a8d83 | ||
|
|
b4df5c076e | ||
|
|
52400252dd | ||
|
|
49923c4214 | ||
|
|
81b77c9688 | ||
|
|
7284bb54bc | ||
|
|
6afdd8375d | ||
|
|
af23d9fd14 | ||
|
|
e7aead292c | ||
|
|
fd2678ce2f | ||
|
|
a6d660e708 | ||
|
|
de35a26285 | ||
|
|
18bb045e9a | ||
|
|
9090ec54e7 | ||
|
|
6fa9994366 | ||
|
|
665f2412f1 | ||
|
|
395099aa40 | ||
|
|
97a72a9ee2 | ||
|
|
3414202b7b | ||
|
|
52e5453d56 | ||
|
|
dd039a612f | ||
|
|
f5ab034aeb | ||
|
|
893ec2d61c | ||
|
|
ff41b26e94 | ||
|
|
d4d6fbab88 | ||
|
|
e38fe871b2 | ||
|
|
152d7bc3b3 | ||
|
|
9e7cf3ccd9 | ||
|
|
f7370a0280 | ||
|
|
814c574f26 | ||
|
|
fd9f9ee178 | ||
|
|
29e8f8f5fb | ||
|
|
279692afea | ||
|
|
fd09321f8e | ||
|
|
ad236baa86 | ||
|
|
8965b1fbba | ||
|
|
9b32411659 | ||
|
|
32dda9b904 | ||
|
|
c0cb5b96bf | ||
|
|
ff60030ffb | ||
|
|
f40bf2d9ba | ||
|
|
9eebee3ce3 | ||
|
|
8a3bdf136b | ||
|
|
f62076d3fd | ||
|
|
92f4d6b392 | ||
|
|
96ffd7e147 | ||
|
|
07c38e9b6c | ||
|
|
c0aca97083 | ||
|
|
2fd25f53cc | ||
|
|
2b3383a163 | ||
|
|
421a27ceae | ||
|
|
10022451b4 | ||
|
|
3c8d923299 | ||
|
|
154044e32a | ||
|
|
bfc8c10f3d | ||
|
|
d93b5a7b5c | ||
|
|
4ae608ed93 | ||
|
|
e2aef1fc1d | ||
|
|
16cadfeae8 | ||
|
|
3c9b42b9f7 | ||
|
|
9c0f27edb4 | ||
|
|
f81ee1b267 | ||
|
|
a964d955f4 | ||
|
|
f11c65c393 | ||
|
|
f8e5e9f675 | ||
|
|
72eb36f5b3 | ||
|
|
97c0fe1ece | ||
|
|
844b552bf3 | ||
|
|
6bb85deca6 | ||
|
|
551f7616f0 | ||
|
|
285c508329 | ||
|
|
f751657903 | ||
|
|
89096554e8 | ||
|
|
4bf6cce4ba | ||
|
|
85eae0b74a | ||
|
|
0a5657738e | ||
|
|
3aa0adbf39 | ||
|
|
7dc21ce8a7 | ||
|
|
6a6b200861 | ||
|
|
1c7868312d | ||
|
|
90d1c16783 | ||
|
|
3cfca046ba | ||
|
|
85414eb65f | ||
|
|
fdff57da7c | ||
|
|
7c223feef5 | ||
|
|
3740cb2c30 | ||
|
|
b5dd48ad7b | ||
|
|
e46025739a | ||
|
|
66a3538d05 | ||
|
|
e1fa24c251 | ||
|
|
a76e22c021 | ||
|
|
c166327835 | ||
|
|
4ab006f065 | ||
|
|
7eaaef6e75 | ||
|
|
c4f94efe24 | ||
|
|
2eb729d712 | ||
|
|
0343b6cf98 | ||
|
|
7fc4ea0c68 | ||
|
|
195a3ab170 | ||
|
|
a96f485e3c | ||
|
|
1b3a32f83f | ||
|
|
cacf74af3c | ||
|
|
4e9f68acff | ||
|
|
b58295d1d6 | ||
|
|
cbcf187814 | ||
|
|
4baa003c0d | ||
|
|
8cf8c3c122 | ||
|
|
e3e2c0ab6a | ||
|
|
e8862a3811 | ||
|
|
1c1a82696b | ||
|
|
a2893bac7e | ||
|
|
0eda42f29f | ||
|
|
26f7310707 | ||
|
|
810da0db61 | ||
|
|
8f6aa950cd | ||
|
|
36a2482165 | ||
|
|
639c18395b | ||
|
|
fe08fd3f0a | ||
|
|
0822ee6b96 | ||
|
|
96fdb9241a | ||
|
|
0c6d193e0f | ||
|
|
ece15c7394 | ||
|
|
29bef052c7 | ||
|
|
3ce1e40b43 | ||
|
|
290316e23e | ||
|
|
a74736b100 | ||
|
|
3f7e7f2601 | ||
|
|
abefb011a5 | ||
|
|
259eff3fea | ||
|
|
c271235d16 | ||
|
|
7539afa91e | ||
|
|
4c79905f5b | ||
|
|
b6d020e202 | ||
|
|
5a4dec3dc5 | ||
|
|
23ad006187 | ||
|
|
f7926847ac | ||
|
|
895bd578e4 | ||
|
|
67a052b117 | ||
|
|
3e41dae3ea | ||
|
|
420c616e9d | ||
|
|
822009ec5f | ||
|
|
b5e1c78461 | ||
|
|
bd0e892447 | ||
|
|
92db453f81 | ||
|
|
0060f57b63 | ||
|
|
2c517de5bb | ||
|
|
9d5f7fdd9c | ||
|
|
ddaa5b784d | ||
|
|
fadd9ff95d | ||
|
|
a40f365a54 | ||
|
|
3964bffce4 | ||
|
|
a8deb3593b | ||
|
|
ba3ef98d1e | ||
|
|
59de0d47a3 | ||
|
|
e0d6e0117e | ||
|
|
e157160337 | ||
|
|
c7d2a3ffd4 | ||
|
|
2f897c9c98 | ||
|
|
de83db10d6 | ||
|
|
4c690dd3c6 | ||
|
|
14fa0b4fd3 | ||
|
|
f8b55eb017 | ||
|
|
2b0bccf2d8 | ||
|
|
c1c68cf72d | ||
|
|
3db5c49009 | ||
|
|
87ffaceca3 | ||
|
|
591d98d8b6 | ||
|
|
e0d93eaa9f | ||
|
|
5510ff7dce | ||
|
|
a1a6185fd6 | ||
|
|
b71b87e6e9 | ||
|
|
41cc518e20 | ||
|
|
508356898f | ||
|
|
0c40a954fa | ||
|
|
784f99a900 | ||
|
|
90ae0b3e44 | ||
|
|
520aaac31f | ||
|
|
8fe1370a74 | ||
|
|
c6ae89bf27 | ||
|
|
822ef8829e | ||
|
|
726e67ebb0 | ||
|
|
d4825b1d40 | ||
|
|
f58a16ca9d | ||
|
|
2c429fd406 | ||
|
|
ebcca0c3b8 | ||
|
|
925fd9f268 | ||
|
|
0058edc24e | ||
|
|
aa66133813 | ||
|
|
66f9a82f31 | ||
|
|
1092abe776 | ||
|
|
0411792ca5 | ||
|
|
b9a13d3a32 | ||
|
|
0a1359ed16 | ||
|
|
9bd8c774ab | ||
|
|
2943f93218 | ||
|
|
5b8a0881b7 | ||
|
|
5d677a9115 | ||
|
|
26bf9aab26 | ||
|
|
d9b4529932 | ||
|
|
35ecb8499d | ||
|
|
ed5dc7cdfd | ||
|
|
75489c00c2 | ||
|
|
3aaa7b62ef | ||
|
|
c13b9754eb | ||
|
|
a97417fd38 | ||
|
|
43261f8469 | ||
|
|
b32935dd97 | ||
|
|
35660ff5e7 | ||
|
|
9b07909ed8 | ||
|
|
b4eb317b00 | ||
|
|
464d77dfb5 | ||
|
|
a67ad12cde | ||
|
|
0dba9b8268 | ||
|
|
4a4c9cd63f | ||
|
|
d0c9c1043c | ||
|
|
795405c47d | ||
|
|
e0c2f873f5 | ||
|
|
1ec50cf6ad | ||
|
|
2ef53c6df9 | ||
|
|
249be451f7 | ||
|
|
34a228adbe | ||
|
|
9782eaaeea | ||
|
|
50d3122bee | ||
|
|
3a264e6baf | ||
|
|
1b75b68373 | ||
|
|
4224e8314b | ||
|
|
7b14ad9616 | ||
|
|
be7386f0d7 | ||
|
|
cd3263db50 | ||
|
|
3034019d5a | ||
|
|
1fd48a1cf8 | ||
|
|
c3f39ad24d | ||
|
|
68d9394d9f | ||
|
|
80fca589af | ||
|
|
420c33d3ba | ||
|
|
de0cd976de | ||
|
|
a116774104 | ||
|
|
1e180489a4 | ||
|
|
e00656d757 | ||
|
|
adcc74ac8e | ||
|
|
3e0085b4a4 | ||
|
|
17fb2a98d6 | ||
|
|
780efc2477 | ||
|
|
7f02fe4157 | ||
|
|
f722576bf2 | ||
|
|
7fecc2cf4c | ||
|
|
3519e070b4 | ||
|
|
ca6b7fbeb2 | ||
|
|
25d7e121c9 | ||
|
|
882077f0aa | ||
|
|
0479113949 | ||
|
|
b5b69ff369 | ||
|
|
f0ad76fff6 | ||
|
|
e651ea7614 | ||
|
|
acca85b99a | ||
|
|
230f44f4e6 | ||
|
|
19c42490e3 | ||
|
|
03a0e2084a | ||
|
|
116fa6777b | ||
|
|
35d4222c7a | ||
|
|
dd0de7e8be | ||
|
|
e3e7503a7c | ||
|
|
b66f4bf2be | ||
|
|
1c8dbae359 | ||
|
|
4f36349630 | ||
|
|
68b27451f2 | ||
|
|
c7acd63ea7 | ||
|
|
cfc17cf290 | ||
|
|
904e173037 | ||
|
|
8a8d38a30f | ||
|
|
a9ebf534c6 | ||
|
|
6429ff0603 | ||
|
|
f5057dfac4 | ||
|
|
00d61def0b | ||
|
|
f5a26c7116 | ||
|
|
54fba99bed | ||
|
|
7216a8b923 | ||
|
|
97e322ba22 | ||
|
|
fc603f11ce | ||
|
|
87f01007cc | ||
|
|
10bca290c3 | ||
|
|
3dabaeb2c9 | ||
|
|
cf74b879c6 | ||
|
|
0ae2a1f177 | ||
|
|
3d63d6c0f2 | ||
|
|
905a3a30f3 | ||
|
|
af29637163 | ||
|
|
7351fe9633 | ||
|
|
1a6b4a1188 | ||
|
|
ec96c1b534 | ||
|
|
8751dd3797 | ||
|
|
9a6df25280 | ||
|
|
ada8912a1f | ||
|
|
de4245025c | ||
|
|
f620f4a92e | ||
|
|
c74c5e0c6d | ||
|
|
2ca85c7829 | ||
|
|
8374404b0f | ||
|
|
c453c6c294 | ||
|
|
a5e5c3d941 | ||
|
|
c242a08653 | ||
|
|
f931603203 | ||
|
|
167d57408d | ||
|
|
ef03a3d9d4 | ||
|
|
ccfc9f2ad2 | ||
|
|
b3456ee96c | ||
|
|
05cd4ac14b | ||
|
|
ba2ecf9789 | ||
|
|
020e6b1cb3 | ||
|
|
2a7365730b | ||
|
|
ad77d74f4b | ||
|
|
ef8668dce4 | ||
|
|
3fb34e28a4 | ||
|
|
871214f907 | ||
|
|
7e717754a3 | ||
|
|
6e67946ae2 | ||
|
|
48d673c853 | ||
|
|
a9bb6f8099 | ||
|
|
f8ef69b88a | ||
|
|
a005ed2a84 | ||
|
|
1aa859b10d | ||
|
|
4aba34c18b | ||
|
|
5e099f522e | ||
|
|
15c33014e7 | ||
|
|
af5b9172ef | ||
|
|
fe9e813c79 | ||
|
|
51d7cdfd31 | ||
|
|
f3aef67be6 | ||
|
|
449af8060a | ||
|
|
fa19f1f5a0 | ||
|
|
9e461ef6e1 | ||
|
|
1ba301ed16 | ||
|
|
7e663b05d5 | ||
|
|
2225a735ca | ||
|
|
90c8fbb495 | ||
|
|
2b14efd3f8 | ||
|
|
3261c36f97 | ||
|
|
c48d65525a | ||
|
|
5fa6e755b2 | ||
|
|
8bbc0b9e1a | ||
|
|
3a5317f16a | ||
|
|
c0bb06bf49 | ||
|
|
8d45af2034 | ||
|
|
5f85bf62f5 | ||
|
|
8fa409d6c1 | ||
|
|
7167c2f20d | ||
|
|
20846c8eff | ||
|
|
1b44a01efd | ||
|
|
c4104dcd33 | ||
|
|
c319c3f520 | ||
|
|
85025695df | ||
|
|
f6ca22ecdd | ||
|
|
8d9594ba36 | ||
|
|
fe7c66cf1d | ||
|
|
b5aa940701 | ||
|
|
698dfb67d1 | ||
|
|
8e61d77497 | ||
|
|
38f2f1a3e4 | ||
|
|
b98c6ca5c5 | ||
|
|
5641874811 | ||
|
|
1dd34035c9 | ||
|
|
6bf170e273 | ||
|
|
084ac9b916 | ||
|
|
cebf061f85 | ||
|
|
5641db0026 | ||
|
|
a42ec8eddb | ||
|
|
ea9917dacc | ||
|
|
6272ae842c | ||
|
|
b000eda126 | ||
|
|
9962bac50b | ||
|
|
f0c3f1b9ba | ||
|
|
ec8eeef428 | ||
|
|
7eb1a4b489 | ||
|
|
23b23f0e38 | ||
|
|
cba78190a8 | ||
|
|
e097a49186 | ||
|
|
e895c784c7 | ||
|
|
7e1bcf84f0 | ||
|
|
a92f1b82d0 | ||
|
|
433e6901c6 | ||
|
|
e16d835727 | ||
|
|
a5cf2d37d4 | ||
|
|
4bd6fbd445 | ||
|
|
8f18933713 | ||
|
|
caad670dbf | ||
|
|
2d30f86cc6 | ||
|
|
1dd79d9e31 | ||
|
|
4171afe275 | ||
|
|
a287192649 | ||
|
|
f2e9631af4 | ||
|
|
c756ab70ae | ||
|
|
2e57fe0dfd | ||
|
|
be39d28be7 | ||
|
|
23a292b8ff | ||
|
|
cdbb247780 | ||
|
|
69d4f0ce10 | ||
|
|
2580475f67 | ||
|
|
138789149c | ||
|
|
1be66659d3 | ||
|
|
6d3ba0ae72 | ||
|
|
04f61677d7 | ||
|
|
0096e65fe6 | ||
|
|
b870306c5d | ||
|
|
480b2181f0 | ||
|
|
ddc3fe7807 | ||
|
|
a86e8659f7 | ||
|
|
ae10dd639b | ||
|
|
a0141624b9 | ||
|
|
dc137620eb | ||
|
|
e9b92b216a | ||
|
|
24bf057d17 | ||
|
|
b68c6b6807 | ||
|
|
34a268624b | ||
|
|
cdd527f3ac | ||
|
|
1f048a6d04 | ||
|
|
58cf10ffb2 | ||
|
|
1ef14e129f | ||
|
|
7c4030aaef | ||
|
|
c1bf0f8799 | ||
|
|
4b0a4aa5d2 | ||
|
|
115be88e5d | ||
|
|
268bd8f1a6 | ||
|
|
1d9ff17380 | ||
|
|
0fa0d72300 | ||
|
|
677d166b1e | ||
|
|
b5778a9cb5 | ||
|
|
b2fd94d20e | ||
|
|
16969bc39b | ||
|
|
3943314142 | ||
|
|
f7ee532add | ||
|
|
2fdab4c196 | ||
|
|
e6a95370c1 | ||
|
|
92907bced3 | ||
|
|
ca30b8b62a | ||
|
|
da9ac6ff25 | ||
|
|
475de5250e | ||
|
|
7a9860ac29 | ||
|
|
32ca02bcc7 | ||
|
|
ddefb99e5e | ||
|
|
ca4d6e0b2f | ||
|
|
c3fc3a3132 | ||
|
|
78b8a1c36f | ||
|
|
638c3492d6 | ||
|
|
4c1699935c | ||
|
|
25829451c8 | ||
|
|
74fbce8b96 | ||
|
|
542039df01 | ||
|
|
d652ecff21 | ||
|
|
00c4cf300a | ||
|
|
da47054497 | ||
|
|
051a2a3ef2 | ||
|
|
ee609f3e8f | ||
|
|
eb66354c76 | ||
|
|
c7fb7e58cf | ||
|
|
c2b02a45a2 | ||
|
|
e7ed532545 | ||
|
|
2a031320c2 | ||
|
|
d0379fdede | ||
|
|
45016b76e7 | ||
|
|
ee8cd8ef26 | ||
|
|
18d89e9cad | ||
|
|
623c8ca6b0 | ||
|
|
39736e865e | ||
|
|
8ea80a616e | ||
|
|
c5df7f9bb7 | ||
|
|
92374966fb | ||
|
|
6bebfb861c | ||
|
|
30ae13b245 | ||
|
|
891f990e35 | ||
|
|
2118327ae0 | ||
|
|
6aed2902a7 | ||
|
|
e149e32ce1 | ||
|
|
41f2af8c4e | ||
|
|
016f808345 | ||
|
|
bc69524654 | ||
|
|
3c6280c419 | ||
|
|
9d01a52a4a | ||
|
|
a8273a8fdc | ||
|
|
d6d3bf6943 | ||
|
|
f6e8346841 | ||
|
|
b9717e9894 | ||
|
|
6b3209e6ee | ||
|
|
0a6da5ead1 | ||
|
|
53441148b8 | ||
|
|
732d018819 | ||
|
|
e261a8ba21 | ||
|
|
c3c87bff74 | ||
|
|
c3f423aad5 | ||
|
|
580975fda1 | ||
|
|
9f114a1dad | ||
|
|
0ae93d0657 | ||
|
|
3a210c5bab | ||
|
|
44d2627e2a | ||
|
|
6e882760f1 | ||
|
|
ff79ffd1c8 | ||
|
|
3175b0c4ff | ||
|
|
21779395ef | ||
|
|
32cbb698ee | ||
|
|
3f99c52349 | ||
|
|
19a41b2792 | ||
|
|
599910daea | ||
|
|
bee42ea2fb | ||
|
|
221ea5ebb0 | ||
|
|
f24df9fb05 | ||
|
|
ce2a122d51 | ||
|
|
e8242aec85 | ||
|
|
6ed9dcbe93 | ||
|
|
58371e6e43 | ||
|
|
2d8957489e | ||
|
|
1fd0794bfb | ||
|
|
58da60985f | ||
|
|
6b6a3cd38e | ||
|
|
4b1df16ecf | ||
|
|
24ea686e4d | ||
|
|
a7030cdcb9 | ||
|
|
8c137ecc52 | ||
|
|
20e44aa891 | ||
|
|
118bb53c03 | ||
|
|
7c3be9f0b0 | ||
|
|
d1990a4263 | ||
|
|
396422ee3a | ||
|
|
f735b401df | ||
|
|
a42beb86c0 | ||
|
|
85020270d5 | ||
|
|
b1b9044021 | ||
|
|
8da30b216f | ||
|
|
167d3caa5d | ||
|
|
3259e6f0e8 | ||
|
|
64526c5232 | ||
|
|
d9630afafd | ||
|
|
c8c6e62aa0 | ||
|
|
7f561c30b3 | ||
|
|
1d8f342417 | ||
|
|
6ec090ea0d | ||
|
|
08a8eadb49 | ||
|
|
3e2835bef6 | ||
|
|
686fc754b2 | ||
|
|
ea17124d6d | ||
|
|
4000041308 | ||
|
|
1a50ed0316 | ||
|
|
42038cd6e7 | ||
|
|
44c5b41cc7 | ||
|
|
da7bd91514 | ||
|
|
0206623c6f | ||
|
|
3b2948d4dd | ||
|
|
918aeb670e | ||
|
|
fd9fec48a2 | ||
|
|
77e51b40b8 | ||
|
|
195663c6e3 | ||
|
|
ce4ca5c4d5 | ||
|
|
3e276c4111 | ||
|
|
41a4dc2fa2 | ||
|
|
7851047421 | ||
|
|
a30e478cbd | ||
|
|
ad4b3dfad1 | ||
|
|
ad7fc937a9 | ||
|
|
fd905ef308 | ||
|
|
3f257af7a9 | ||
|
|
030bb8fe76 | ||
|
|
131601d9d2 | ||
|
|
64317ffef5 | ||
|
|
911e65af55 | ||
|
|
82165eaf37 | ||
|
|
4a75f2ebca | ||
|
|
be39b3be8c | ||
|
|
50e8aff8fa | ||
|
|
804a790392 | ||
|
|
d04566a6c4 | ||
|
|
6c614a4b3c | ||
|
|
3277717a7f | ||
|
|
f9b2829396 | ||
|
|
81cf108471 | ||
|
|
5075fe358e | ||
|
|
c8085a368f | ||
|
|
8191c25dd7 | ||
|
|
dc4b2bd52e | ||
|
|
ca60afbcee | ||
|
|
374b74b710 | ||
|
|
ae0d3d78cd | ||
|
|
f9f197afd0 | ||
|
|
93c43ecbc3 | ||
|
|
1fd1b0388b | ||
|
|
833364a94e | ||
|
|
893c105bf2 | ||
|
|
e481bd4ec5 | ||
|
|
b0489aa61b | ||
|
|
376bc29e95 | ||
|
|
bd22e330a7 | ||
|
|
9ce7114f8c | ||
|
|
1303da1c20 | ||
|
|
c6bb33fa84 | ||
|
|
22939a6707 | ||
|
|
9222877306 | ||
|
|
5d8264e854 | ||
|
|
77124e098c | ||
|
|
bd0a70d024 | ||
|
|
914700712e | ||
|
|
80d792915a | ||
|
|
54dc363231 | ||
|
|
36c436af50 | ||
|
|
73eb9259e2 | ||
|
|
951d915326 | ||
|
|
14c25480ce | ||
|
|
77d87f8c50 | ||
|
|
ab389d4817 | ||
|
|
58b9e0cd7c | ||
|
|
ed2f5af204 | ||
|
|
5b2f1f8969 | ||
|
|
a2122cb7d7 | ||
|
|
d680702b15 | ||
|
|
d15de499dc | ||
|
|
bd382e3cc7 | ||
|
|
d417fa58ab | ||
|
|
ca9cb997b5 | ||
|
|
ebd920f3b2 | ||
|
|
a611ddea2d | ||
|
|
75614e0bbb | ||
|
|
dafef6463a | ||
|
|
61d502eea4 | ||
|
|
71aa525dfd | ||
|
|
c0e3875dfd | ||
|
|
9c188e0acd | ||
|
|
1de143362c | ||
|
|
7e37b6c151 | ||
|
|
0fa84a8b84 | ||
|
|
236ae57d01 | ||
|
|
7345f464a5 | ||
|
|
3c45f2abe2 | ||
|
|
9c5e1faf46 | ||
|
|
ce02d3a829 | ||
|
|
1b80c59e65 | ||
|
|
f20f528a11 | ||
|
|
814b66c04a | ||
|
|
00ad2e7a80 | ||
|
|
8f7e5e491f | ||
|
|
19101176a7 | ||
|
|
0bc383fec2 | ||
|
|
4df918d6a5 | ||
|
|
d3cbd8cdae | ||
|
|
0fa5dc225c | ||
|
|
b14f14c45a | ||
|
|
0851f4fdd4 | ||
|
|
807b3370e7 | ||
|
|
a0f114e15c | ||
|
|
1401fcd97d | ||
|
|
efcd291e65 | ||
|
|
f98792714e | ||
|
|
5649f85b58 | ||
|
|
cfaba932e0 | ||
|
|
b1db4e8a7a | ||
|
|
ab9c11e038 | ||
|
|
4d621dcbfe | ||
|
|
a45537cbf3 | ||
|
|
2d3ac286ac | ||
|
|
4e96b5d4a6 | ||
|
|
9294324732 | ||
|
|
b92422594b | ||
|
|
ea2637b1b4 | ||
|
|
c97b4859c1 | ||
|
|
7a2f42de30 | ||
|
|
99f5f78fc7 | ||
|
|
668f0ca675 | ||
|
|
6a89a51ea6 | ||
|
|
df6b0f3945 | ||
|
|
65cf243373 | ||
|
|
2ef7813219 | ||
|
|
216efd74bf | ||
|
|
b9d027a44a | ||
|
|
9097dd9645 | ||
|
|
d784e26913 | ||
|
|
cb69298385 | ||
|
|
a8fc42a17e | ||
|
|
005008814a | ||
|
|
ad3bbaedb3 | ||
|
|
cad0dabe42 | ||
|
|
48fa3c8aec | ||
|
|
fb585cbac0 | ||
|
|
dd1adda1a6 | ||
|
|
3742583508 | ||
|
|
3c6cd623af | ||
|
|
70c6e69b36 | ||
|
|
943e58d32a | ||
|
|
5237058016 | ||
|
|
b87c5f8a51 | ||
|
|
43e7a03af4 | ||
|
|
19fce4975d | ||
|
|
a9217810e7 | ||
|
|
a87610c856 | ||
|
|
0ec0cb1b19 | ||
|
|
9b1678a06c | ||
|
|
0da63062d7 | ||
|
|
c8dd12eb20 | ||
|
|
008592f13b | ||
|
|
56b9972053 | ||
|
|
1335c94bbc | ||
|
|
21a6ab369e | ||
|
|
2a80117d42 | ||
|
|
e65d312503 | ||
|
|
5fc34e643c | ||
|
|
3463a84903 | ||
|
|
48dc532de6 | ||
|
|
20cb62483f | ||
|
|
dcb5828313 | ||
|
|
74860256b9 | ||
|
|
97457f17c1 | ||
|
|
de9167cae6 | ||
|
|
2e5171c205 | ||
|
|
a0b5491178 | ||
|
|
fc8ef8678b | ||
|
|
2dc1f2ea5b | ||
|
|
7999527582 | ||
|
|
14be6506ee | ||
|
|
c85ad470ba | ||
|
|
8b6afcc5ec | ||
|
|
813dced6df | ||
|
|
0a39866045 | ||
|
|
48734689d8 | ||
|
|
dfe927dcbd | ||
|
|
ca158def31 | ||
|
|
409386336d | ||
|
|
eeed6c3474 | ||
|
|
3296cd6c39 | ||
|
|
60e25d9c67 | ||
|
|
ec65e2025e | ||
|
|
f09020c3bc | ||
|
|
91baaed96d | ||
|
|
64de64ce33 | ||
|
|
b685e7008e | ||
|
|
375b3d776c | ||
|
|
c1fddd7164 | ||
|
|
3af9bf9e12 | ||
|
|
9ef5d0c144 | ||
|
|
3d1b37dc85 | ||
|
|
80ce8347f6 | ||
|
|
3eb7e1392d | ||
|
|
ac78a44d74 | ||
|
|
87fed9fde3 | ||
|
|
2564430046 | ||
|
|
663861dc09 | ||
|
|
2d6a12101e | ||
|
|
e40110fa4c | ||
|
|
6ac162f3cd | ||
|
|
aea1d16e31 | ||
|
|
16e26ef215 | ||
|
|
7270e701d4 | ||
|
|
c8010d4d52 | ||
|
|
6941b7463e | ||
|
|
99a6cd82b2 | ||
|
|
1a44307664 | ||
|
|
53d7a92a0d | ||
|
|
4ba1f47423 | ||
|
|
c38e47b726 | ||
|
|
e6a4d79b86 | ||
|
|
b62d0697be | ||
|
|
f90ebbbb4e | ||
|
|
ab39802512 | ||
|
|
84da67adda | ||
|
|
cfff3c6d97 | ||
|
|
cc3d9e0d2d | ||
|
|
442e7eb015 | ||
|
|
59248b7c2e | ||
|
|
f2d7a45b74 | ||
|
|
c79b6147ea | ||
|
|
d93be76505 | ||
|
|
5fff65db5a | ||
|
|
53e240add7 | ||
|
|
9cfc65eeda | ||
|
|
47c305d557 | ||
|
|
c5ba89b054 | ||
|
|
de8977723a | ||
|
|
8cea93de94 | ||
|
|
e25cbe54d9 | ||
|
|
b356522f94 | ||
|
|
7a6c1de5d5 | ||
|
|
fb07adf7c1 | ||
|
|
9ceadd44c9 | ||
|
|
e1c529ab91 | ||
|
|
ce27af6083 | ||
|
|
ac7d224645 | ||
|
|
df26f492a7 | ||
|
|
448c01ca99 | ||
|
|
bd7a9e5444 | ||
|
|
1e717710b6 | ||
|
|
3cb14ad3bc | ||
|
|
4769a67936 | ||
|
|
279f866bf5 | ||
|
|
5cbe7600a6 | ||
|
|
cdb1a4c288 | ||
|
|
7d09d41a7d | ||
|
|
340bbd8727 | ||
|
|
53c916ea4f | ||
|
|
6451337274 | ||
|
|
294b75ce2d | ||
|
|
0ca4f3b104 | ||
|
|
ac74510d47 | ||
|
|
33ec69d33a | ||
|
|
f6d329ac48 | ||
|
|
a56fbeb611 | ||
|
|
8c20a67cfa | ||
|
|
fffa4fc031 | ||
|
|
3316b73ab6 | ||
|
|
211f7b7965 | ||
|
|
3a5a7bf674 | ||
|
|
7c749a964c | ||
|
|
997c8c87d0 | ||
|
|
ed6a417d7e | ||
|
|
4a54f545a4 | ||
|
|
789902b79a | ||
|
|
266859af19 | ||
|
|
9cca1a4819 | ||
|
|
aae1da3aa8 | ||
|
|
aed688224b | ||
|
|
3ce1ec708d | ||
|
|
510a564797 | ||
|
|
b36517babb | ||
|
|
415d18338e | ||
|
|
14384131f4 | ||
|
|
b3d54ce57e | ||
|
|
b5890340e3 | ||
|
|
050d987d3b | ||
|
|
c4651cd915 | ||
|
|
b5f97c0d94 | ||
|
|
35165ba2b8 | ||
|
|
aeb1dcdf15 | ||
|
|
37730744e7 | ||
|
|
f821fe0356 | ||
|
|
ba16fdb548 | ||
|
|
144e6f59c8 | ||
|
|
2e42c5e875 | ||
|
|
eec8743e2f | ||
|
|
e5e5684e2e | ||
|
|
efddc6ccec | ||
|
|
5aef46733b | ||
|
|
201782184c | ||
|
|
5f2ef046e1 | ||
|
|
42398950e4 | ||
|
|
506c8af1ea | ||
|
|
70d9c516af | ||
|
|
11ad86b9db | ||
|
|
2e9cfa6973 | ||
|
|
13e2c325e1 | ||
|
|
7e428a273f | ||
|
|
aa4bd516a9 | ||
|
|
82f8675b68 | ||
|
|
fdcb994e7a | ||
|
|
e89fa23533 | ||
|
|
21d4f0f569 | ||
|
|
625201e559 | ||
|
|
d36378fcbf | ||
|
|
929ecfd42b | ||
|
|
44496e424a | ||
|
|
1eb789e847 | ||
|
|
1ab76617df | ||
|
|
3bff653bbb | ||
|
|
4b7a8c6d6e | ||
|
|
9236be7fbd | ||
|
|
fd9b922b21 | ||
|
|
eea88f6e11 | ||
|
|
eb47c968ae | ||
|
|
560019b26c | ||
|
|
589e07f869 | ||
|
|
585de53148 | ||
|
|
1e85b25438 | ||
|
|
71b57bfed1 | ||
|
|
870cb26e01 | ||
|
|
9a180b098f | ||
|
|
06682c333f | ||
|
|
18c0aa5c81 | ||
|
|
e49b468fd5 | ||
|
|
7e5748b3a6 | ||
|
|
e99aa86863 | ||
|
|
bc6ae1d1b5 | ||
|
|
df94a87a51 | ||
|
|
9f7b2de311 | ||
|
|
dab6e10881 | ||
|
|
3b78c3a929 | ||
|
|
7029968c47 | ||
|
|
a8fe4e6aab | ||
|
|
b93dd8cb9b | ||
|
|
0ba614961d | ||
|
|
79c56d440c | ||
|
|
9601506270 | ||
|
|
9ed16b81e8 | ||
|
|
1800c1ff12 | ||
|
|
a8067bee36 | ||
|
|
f879663033 | ||
|
|
d618af19d6 | ||
|
|
8aa61bf5bc | ||
|
|
cd70f4b1c9 | ||
|
|
240e55029b | ||
|
|
9aabe7c72e | ||
|
|
6744dce57e | ||
|
|
d38e40d53f | ||
|
|
aa1b561bc0 | ||
|
|
6674b7890c | ||
|
|
a3a62165e9 | ||
|
|
8eb5a62737 | ||
|
|
c5ef462937 | ||
|
|
eca397b45f | ||
|
|
9f7a583c92 | ||
|
|
a59077b94f | ||
|
|
d61bfaa993 | ||
|
|
a17690f88b | ||
|
|
271e948c1f | ||
|
|
ac7de6213a | ||
|
|
7ff6b64742 | ||
|
|
531e0ad19d | ||
|
|
cc7112fb66 | ||
|
|
cf08af31ac | ||
|
|
1fdcf3877e | ||
|
|
0fadf035db | ||
|
|
58b1c4b511 | ||
|
|
0cc8feac57 | ||
|
|
ba1efd57a5 | ||
|
|
29656fb9a6 | ||
|
|
92d79ebeea | ||
|
|
3a69107eac | ||
|
|
4bfb528526 | ||
|
|
fe9dd1d014 | ||
|
|
4829c8b314 | ||
|
|
59b4bf5267 | ||
|
|
c19f34570d | ||
|
|
94d29796b8 | ||
|
|
e9f44ffcc6 | ||
|
|
a261ab4f0c | ||
|
|
929bcf03a0 | ||
|
|
a6a69ab1b6 | ||
|
|
39de79d3cd | ||
|
|
40ab540179 | ||
|
|
7d7b6f4475 | ||
|
|
ccdd433e35 | ||
|
|
4a6ea38ef8 | ||
|
|
b20e25f052 | ||
|
|
8591d4e96c | ||
|
|
912051637a | ||
|
|
c233f767f4 | ||
|
|
e1293c2c74 | ||
|
|
b63bf2465f | ||
|
|
505c9c6218 | ||
|
|
14350240eb | ||
|
|
cb21991efa | ||
|
|
49e58c25f2 | ||
|
|
ac6000a2ae | ||
|
|
6d1c9edc24 | ||
|
|
1a7fa3746d | ||
|
|
f7a57ffeb4 | ||
|
|
ad9d45a154 | ||
|
|
5fffb2afe3 | ||
|
|
1269114074 | ||
|
|
d24f6ae064 | ||
|
|
95fe09489c | ||
|
|
1a144da36d | ||
|
|
8e26da1759 | ||
|
|
daf53226c3 | ||
|
|
2b9e615e51 | ||
|
|
02acbecef5 | ||
|
|
8f28964ce2 | ||
|
|
495e74e2f0 | ||
|
|
543bd1a777 | ||
|
|
8f23970ccc | ||
|
|
9ec39658fe | ||
|
|
7c0518843f | ||
|
|
7131257354 | ||
|
|
db527be97c | ||
|
|
7c46e42820 | ||
|
|
9943e081d9 | ||
|
|
68a51c9c63 | ||
|
|
6a98cdf974 | ||
|
|
9ca8c66c47 | ||
|
|
56754d616b | ||
|
|
e7dd964825 | ||
|
|
39e348948c | ||
|
|
6583090d4f | ||
|
|
f20134415e | ||
|
|
bd9b9600c1 | ||
|
|
5ae9873455 | ||
|
|
5776ca0384 | ||
|
|
f8f4b39965 | ||
|
|
1d51419a11 | ||
|
|
5e1ca0c19f | ||
|
|
b341224c92 | ||
|
|
3861f23af3 | ||
|
|
e7beff79d0 | ||
|
|
2380875cbf | ||
|
|
343e6a50df | ||
|
|
5aa47d35e7 | ||
|
|
40996888c9 | ||
|
|
313ceed1d0 | ||
|
|
ac07d62344 | ||
|
|
2db1bbae4b | ||
|
|
090e50e936 | ||
|
|
b6bab0c723 | ||
|
|
1a333f7968 | ||
|
|
87c00b3804 | ||
|
|
eba71469a4 | ||
|
|
4eef127744 | ||
|
|
4c2941acf0 | ||
|
|
40791a9cd4 | ||
|
|
4d374581b5 | ||
|
|
4976dc3a4c | ||
|
|
9e0fd7d51e | ||
|
|
bf8b3c3b2f | ||
|
|
38336fdb02 | ||
|
|
7c7f77adc6 | ||
|
|
d28a2ebc57 | ||
|
|
67d413956d | ||
|
|
e644575bc5 | ||
|
|
0291ba8cb5 | ||
|
|
622a390b23 | ||
|
|
2e92705f9c | ||
|
|
c6548afa1b | ||
|
|
9e7deecb99 | ||
|
|
2773d7598b | ||
|
|
a9165aba25 | ||
|
|
fd9d54d2dd | ||
|
|
dde04ff979 | ||
|
|
e0f42f4a0a | ||
|
|
3a49d5fdc4 | ||
|
|
9dee7bb7e7 | ||
|
|
25428c9165 | ||
|
|
c6efc5b212 | ||
|
|
836075de10 | ||
|
|
396af917b5 | ||
|
|
a89104127a | ||
|
|
ed26706ee7 | ||
|
|
9de89e5544 | ||
|
|
d7f672ab0a | ||
|
|
b124aabf69 | ||
|
|
6c07a583f8 | ||
|
|
83256de752 | ||
|
|
44f4d083bf | ||
|
|
1b1a5be607 | ||
|
|
6b93f79d2e | ||
|
|
1ce26b3ada | ||
|
|
70f18151dd | ||
|
|
4de38a295c | ||
|
|
7701efc704 | ||
|
|
421f665e85 | ||
|
|
ca233be127 | ||
|
|
377bac67be | ||
|
|
1f97dd0111 | ||
|
|
f1fa22f4cf | ||
|
|
a998dc21b0 | ||
|
|
a0dbbfa04e | ||
|
|
e7092ae769 | ||
|
|
dd148b92c6 | ||
|
|
65d110eb0a | ||
|
|
78dec77c3c | ||
|
|
7723568cef | ||
|
|
ebfd50f30d | ||
|
|
e571c1f95c | ||
|
|
1b0e5e01fa | ||
|
|
a426591282 | ||
|
|
499ff590cd | ||
|
|
03aae3f787 | ||
|
|
ca25e257ef | ||
|
|
6e9c43c37b | ||
|
|
1db253f1fd | ||
|
|
67a55fee25 | ||
|
|
0f89c40a1d | ||
|
|
64627abe6d | ||
|
|
5895b37a06 | ||
|
|
5292d294be | ||
|
|
258d2c9ce3 | ||
|
|
b5cc515e42 | ||
|
|
88d5e9cbc3 | ||
|
|
6a9cc9bf37 | ||
|
|
cc7cee6d44 | ||
|
|
990db5967a | ||
|
|
28ae7eeaee | ||
|
|
ba8755a6d4 | ||
|
|
b4007038fb | ||
|
|
8c679a08c4 | ||
|
|
6c2e6ead2b | ||
|
|
f4a55d60a4 | ||
|
|
ab1e31b2ff | ||
|
|
1ceee8901e | ||
|
|
6042317552 | ||
|
|
d275702080 | ||
|
|
344d23bad1 | ||
|
|
ba85b56e9f | ||
|
|
64e5e02744 | ||
|
|
8084b2764a | ||
|
|
c057786011 | ||
|
|
9e0e66a0ae | ||
|
|
0168bfa67a | ||
|
|
d4b9557508 | ||
|
|
9ecb703b99 | ||
|
|
fa151cd320 | ||
|
|
a5bcf1a02d | ||
|
|
535e628eba | ||
|
|
edbfcda197 | ||
|
|
b8e35ed66c | ||
|
|
c548db513a | ||
|
|
476aabe671 | ||
|
|
969fc899a0 | ||
|
|
b61d89d01b | ||
|
|
bac7b3ab37 | ||
|
|
133a3e67d2 | ||
|
|
eb497be730 | ||
|
|
d553d7f772 | ||
|
|
5932db24e1 | ||
|
|
ddca4f9068 | ||
|
|
b244158b95 | ||
|
|
3bcc12869b | ||
|
|
6ee203a21d | ||
|
|
157d7c4f23 | ||
|
|
0d9f1ba95b | ||
|
|
a390f2e988 | ||
|
|
0faf6c8599 | ||
|
|
9ae2e3fba2 | ||
|
|
12b079df65 | ||
|
|
e920d9cdf3 | ||
|
|
542f363e92 | ||
|
|
40fa2d6779 | ||
|
|
3175bc1e48 | ||
|
|
62262a3572 | ||
|
|
6516a84986 | ||
|
|
16e887dcf0 | ||
|
|
63ffa4a212 | ||
|
|
539bf2ee24 | ||
|
|
deaeda59d0 | ||
|
|
4c0ff29488 | ||
|
|
7c28fe2795 | ||
|
|
deda2e158e | ||
|
|
a6e5cfff8a | ||
|
|
d1ea625435 | ||
|
|
ea551ab0a0 | ||
|
|
d9ae10f5bc | ||
|
|
d90211ef48 | ||
|
|
ace304914e | ||
|
|
6dc13b2c00 | ||
|
|
ca3617aa7d | ||
|
|
84ca7e8879 | ||
|
|
135b96a280 | ||
|
|
febf9cfafb | ||
|
|
27171ed974 | ||
|
|
2c346fdc08 | ||
|
|
229265cdd9 | ||
|
|
57234bc793 | ||
|
|
d496d0cccd | ||
|
|
c687bb39ef | ||
|
|
d1a3545912 | ||
|
|
accdedfead | ||
|
|
bedf669ca4 | ||
|
|
a9dc1b0603 | ||
|
|
9997fa8f3e | ||
|
|
26524dd93a | ||
|
|
bd586a1921 | ||
|
|
dec04ccd06 | ||
|
|
64d4e96068 | ||
|
|
6d83e16aa7 | ||
|
|
8d36c31cb4 | ||
|
|
a49db653a1 | ||
|
|
cd7884b508 | ||
|
|
5bf2c1d6e1 | ||
|
|
08b2824ff0 | ||
|
|
1baf36282e | ||
|
|
e45c7507f9 | ||
|
|
148b1dacce | ||
|
|
b8b0ffb626 | ||
|
|
d17906c2a6 | ||
|
|
b1f7baa79f | ||
|
|
973cbd83d9 | ||
|
|
a599ed6e24 | ||
|
|
a1cbf8824f | ||
|
|
52450ef2f5 | ||
|
|
69170940c9 | ||
|
|
e89caaee52 | ||
|
|
0b3535ff13 | ||
|
|
e7a22ad159 | ||
|
|
74a517d985 | ||
|
|
d57e56de70 | ||
|
|
e2f8f77adf | ||
|
|
db543b62ba | ||
|
|
999087337e | ||
|
|
6788f0b7eb | ||
|
|
2f6fb0d557 | ||
|
|
8bdfe1741a | ||
|
|
739781ece3 | ||
|
|
7e74b95976 | ||
|
|
531695bd0b | ||
|
|
dd959e7b26 | ||
|
|
c4235a60c8 | ||
|
|
f75456060f | ||
|
|
623aae3718 | ||
|
|
974832f7d9 | ||
|
|
d13df65bfb | ||
|
|
80ada3f241 | ||
|
|
aad3677d45 | ||
|
|
8892d3c5d9 | ||
|
|
da08bef2f9 | ||
|
|
d79483e967 | ||
|
|
906391f786 | ||
|
|
48a4aa399b | ||
|
|
a8dd319a9d | ||
|
|
815686cba6 | ||
|
|
7232a14926 | ||
|
|
dd526959eb | ||
|
|
fe6f89c551 | ||
|
|
16c754e004 | ||
|
|
8792a8673a | ||
|
|
9376df8703 | ||
|
|
435ee58d40 | ||
|
|
9dbe15a0e3 | ||
|
|
34b97bdc24 | ||
|
|
6c74f30d79 | ||
|
|
97e918ae72 | ||
|
|
d0c66a693b | ||
|
|
0ea085cc02 | ||
|
|
7fd13faa59 | ||
|
|
d4c0e519d9 | ||
|
|
4ba964db47 | ||
|
|
45e5ec76dd | ||
|
|
df27003998 | ||
|
|
9002568474 | ||
|
|
5c5411261a | ||
|
|
acf878c8dd | ||
|
|
491a09b175 | ||
|
|
51f7e6811e | ||
|
|
eee6b8b10f | ||
|
|
dc4a1c6eca | ||
|
|
02810ff844 | ||
|
|
1bb2ef9e30 | ||
|
|
df2a6dc278 | ||
|
|
835f767c3f | ||
|
|
e3fb239de9 | ||
|
|
de67f244da | ||
|
|
d424bb24cf | ||
|
|
615bba69e5 | ||
|
|
7bba7a9eab | ||
|
|
a5e9cea22f | ||
|
|
f9affb083b | ||
|
|
85b6b06cc9 | ||
|
|
059c6404ab | ||
|
|
0d989a7fae | ||
|
|
7dfc002316 | ||
|
|
8d8d392e84 | ||
|
|
c6e75d5f86 | ||
|
|
99bfd56ef4 | ||
|
|
c2f6c7d939 | ||
|
|
d831d68e73 | ||
|
|
84e4b776ac | ||
|
|
f9e1b2c6dc | ||
|
|
54fca5bebc | ||
|
|
c5ce417d79 | ||
|
|
6765142ebc | ||
|
|
ca898a6759 | ||
|
|
a2bb382652 | ||
|
|
02f966bc67 | ||
|
|
65acdc8c09 | ||
|
|
407ea77a9e | ||
|
|
3e4a9f54a7 | ||
|
|
6b77bd5f13 | ||
|
|
fb6de25e5f | ||
|
|
ffbe5107e2 | ||
|
|
40a5b2e3f3 | ||
|
|
c683884868 | ||
|
|
65961d8d2e | ||
|
|
7dc1f1e225 | ||
|
|
a47ab15aef | ||
|
|
c74efdaa9b | ||
|
|
39224c7bf7 | ||
|
|
96aa3d409d | ||
|
|
c63990f720 | ||
|
|
ad643bf76e | ||
|
|
c7ea4966fd | ||
|
|
8fd81be477 | ||
|
|
a1cb4ac544 | ||
|
|
f91854594c | ||
|
|
f661ea1d46 | ||
|
|
f50eea3eaf | ||
|
|
c15b57e690 | ||
|
|
5f7ef31345 | ||
|
|
447410a27a | ||
|
|
2aa9f9cca9 | ||
|
|
cba27d354d | ||
|
|
b398f42ada | ||
|
|
b6571d99de | ||
|
|
b2392c1943 | ||
|
|
048a673d31 | ||
|
|
c4df9c004b | ||
|
|
b9d4c53eb6 | ||
|
|
5fdeea0581 | ||
|
|
e1cd764050 | ||
|
|
1e54379cc0 | ||
|
|
ba3f69d206 | ||
|
|
fcfd4181c7 | ||
|
|
9f6ec6d3e2 | ||
|
|
2e50aae603 | ||
|
|
5a16a07b57 | ||
|
|
953eca6695 | ||
|
|
a50cdd5bc2 | ||
|
|
10eb65e545 | ||
|
|
5354344ba7 | ||
|
|
c000a93eef | ||
|
|
ef6bc5ae4a | ||
|
|
97c4564649 | ||
|
|
5bf31a8b50 | ||
|
|
18e0d7fb2c | ||
|
|
ac454d9d78 | ||
|
|
806677510f | ||
|
|
68e0ba9923 | ||
|
|
22b33a4f25 | ||
|
|
77f830acd5 | ||
|
|
eb3b090257 | ||
|
|
2e1c219093 | ||
|
|
e38f69caf0 | ||
|
|
795f286923 | ||
|
|
d62e5b51da | ||
|
|
96b267b1f7 | ||
|
|
568388367e | ||
|
|
5ca46edf33 | ||
|
|
af41abd346 | ||
|
|
5edb61641c | ||
|
|
af3b1caa87 | ||
|
|
cc4a7b32b0 | ||
|
|
dfd61f58d4 | ||
|
|
84a3c1f725 | ||
|
|
2419fa1a6e | ||
|
|
3b8ecd9e9d | ||
|
|
94ee4b76db | ||
|
|
d43a1cd753 | ||
|
|
8bc6852b39 | ||
|
|
3850be08f1 | ||
|
|
d72d1d56b1 | ||
|
|
2a4926843c | ||
|
|
1b357055a2 | ||
|
|
2d3d64e462 | ||
|
|
aa648d4e12 | ||
|
|
bfa0ac4c34 | ||
|
|
c37ddd83d5 | ||
|
|
96bbc1cdbc | ||
|
|
ec9ae0ef4f | ||
|
|
95cbb07cbb | ||
|
|
7b95d6a5fc | ||
|
|
bea19a9461 | ||
|
|
ba686556d2 | ||
|
|
15dc6eaaf8 | ||
|
|
3369a387ce | ||
|
|
dd46d8cf99 | ||
|
|
0b8c8835c4 | ||
|
|
441811e277 | ||
|
|
3645735053 | ||
|
|
416d597847 | ||
|
|
861c71e3a8 | ||
|
|
f45fb442de | ||
|
|
82241701a9 | ||
|
|
bfdbe27a8d | ||
|
|
c7dafe9c00 |
39
.clang-format
Normal file
39
.clang-format
Normal file
@@ -0,0 +1,39 @@
|
||||
BasedOnStyle: WebKit
|
||||
AccessModifierOffset: '-4'
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: 'true'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllArgumentsOnNextLine: 'true'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||
AllowShortBlocksOnASingleLine: 'false'
|
||||
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||
AllowShortEnumsOnASingleLine: 'false'
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AlwaysBreakTemplateDeclarations: 'No'
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: '120'
|
||||
CommentPragmas: '"^!|^:"'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
|
||||
ConstructorInitializerIndentWidth: '4'
|
||||
ContinuationIndentWidth: '8'
|
||||
IndentPPDirectives: BeforeHash
|
||||
NamespaceIndentation: All
|
||||
PenaltyExcessCharacter: '10'
|
||||
PointerAlignment: Right
|
||||
SortIncludes: 'true'
|
||||
SpaceAfterTemplateKeyword: 'false'
|
||||
Standard: Auto
|
||||
20
.clang-format-ignore
Normal file
20
.clang-format-ignore
Normal file
@@ -0,0 +1,20 @@
|
||||
/client/3rd
|
||||
/client/3rd-prebuild
|
||||
/client/android
|
||||
/client/cmake
|
||||
/client/core/serialization
|
||||
/client/daemon
|
||||
/client/fonts
|
||||
/client/images
|
||||
/client/ios
|
||||
/client/mozilla
|
||||
/client/platforms/dummy
|
||||
/client/platforms/linux
|
||||
/client/platforms/macos
|
||||
/client/platforms/windows
|
||||
/client/server_scripts
|
||||
/client/translations
|
||||
/deploy
|
||||
/docs
|
||||
/metadata
|
||||
/service/src
|
||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
deploy/data/windows/x64/tap/windows_7/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x64/tap/windows_10/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x32/tap/windows_7/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x32/tap/windows_10/OemVista.inf eol=crlf
|
||||
client/3rd/* linguist-vendored
|
||||
client/android/gradlew.bat eol=crlf
|
||||
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Log files**
|
||||
Attach log files to help explain your problem.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Server (please complete the following information):**
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
488
.github/workflows/deploy.yml
vendored
Normal file
488
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,488 @@
|
||||
name: 'Deploy workflow'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
|
||||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
sudo apt-get install libxkbcommon-x11-0
|
||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
|
||||
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
|
||||
bash deploy/build_linux.sh
|
||||
|
||||
- name: 'Pack installer'
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_installer.tar
|
||||
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_unpacked
|
||||
path: deploy/AppDir
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload translations artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_translations
|
||||
path: client/translations
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2019_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: 'x64'
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
|
||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||
call deploy\\build_windows.bat
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_unpacked
|
||||
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-13
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
CC: cc
|
||||
CXX: c++
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.2'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install iOS Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'ios'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.1'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
run: |
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
||||
export PATH=$PATH:~/go/bin
|
||||
sh deploy/build_ios.sh | \
|
||||
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||
env:
|
||||
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_PASSWORD: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
|
||||
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
|
||||
IOS_APP_PROVISIONING_PROFILE: ${{ secrets.IOS_APP_PROVISIONING_PROFILE }}
|
||||
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
# - name: 'Upload appstore .ipa and dSYMs to artifacts'
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: app-store ipa & dsyms
|
||||
# path: |
|
||||
# ${{ github.workspace }}/AmneziaVPN-iOS.ipa
|
||||
# ${{ github.workspace }}/*.app.dSYM.zip
|
||||
# retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.4.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||
run: |
|
||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
||||
bash deploy/build_macos.sh
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_installer
|
||||
path: AmneziaVPN.dmg
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86_64 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_armv7 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_armv7'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_arm64_v8a Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_arm64_v8a'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Grant execute permission for qt-cmake'
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_x86_64/bin/qt-cmake
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: 'Setup Android NDK'
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: 'r26b'
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo $KEYSTORE_BASE64 | base64 --decode > android.keystore
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
QT_HOST_PATH: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64
|
||||
ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
shell: bash
|
||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||
|
||||
- name: 'Upload x86_64 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86_64
|
||||
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86
|
||||
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-arm64-v8a
|
||||
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-armeabi-v7a
|
||||
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload aab'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android
|
||||
path: deploy/build/AmneziaVPN-release.aab
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
Extra:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Search a corresponding PR
|
||||
uses: octokit/request-action@v2.x
|
||||
id: pull_request
|
||||
with:
|
||||
route: GET /repos/${{ github.repository }}/pulls
|
||||
head: ${{ github.repository_owner }}:${{ github.ref_name }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add PR link to build summary
|
||||
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
147
.github/workflows/tag-deploy.yml
vendored
Normal file
147
.github/workflows/tag-deploy.yml
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
name: 'Release deploy workflow'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# tags:
|
||||
# - **
|
||||
|
||||
jobs:
|
||||
|
||||
Build-Android-Release:
|
||||
name: 'Build-Android-Release'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.4.1
|
||||
QIF_VERSION: 4.5
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt x86_64'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86_64'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt x86'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt arm_v7'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_armv7'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt arm_v8'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_arm64_v8a'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: main
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Preparations before keystore fetching'
|
||||
run: |
|
||||
mkdir keystore
|
||||
|
||||
- name: 'Getting keystore'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: amnezia-vpn/amnezia-android-certificates
|
||||
ssh-key: ${{ secrets.ANDROID_CERTS_SSH_PRIVATE_KEY }}
|
||||
path: keystore
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
|
||||
export NDK_VERSION=23c
|
||||
export ANDROID_NDK_PLATFORM=android-23
|
||||
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
|
||||
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
|
||||
|
||||
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
|
||||
wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
|
||||
unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
|
||||
fi
|
||||
|
||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_arm64_v8a/bin
|
||||
cd main
|
||||
bash deploy/build_android.sh
|
||||
|
||||
- name: 'Signing APK'
|
||||
run: |
|
||||
pwd
|
||||
|
||||
ANDROID_BUILD_TOOLS_VERSION=30.0.3
|
||||
|
||||
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/zipalign -f -v 4 AmneziaVPN-release-unsigned.apk AmneziaVPN-release-aligned.apk
|
||||
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner sign --out AmneziaVPN-release-signed.apk --ks keystore/debug.keystore --ks-key-alias ${{ secrets.DEBUG_ANDROID_KEYSTORE_KEY_ALIAS }} --ks-pass pass:${{secrets.DEBUG_ANDROID_KEYSTOTE_KEY_PASS }} AmneziaVPN-release-aligned.apk
|
||||
|
||||
- name: 'Upload'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Release APK
|
||||
path: ${{ runner.temp }}/main/AmneziaVPN-release-signed.apk
|
||||
64
.github/workflows/tag-upload.yml
vendored
Normal file
64
.github/workflows/tag-upload.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
GIT_TAG=${{ github.ref_name }}
|
||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
else
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
96
.gitignore
vendored
96
.gitignore
vendored
@@ -3,8 +3,13 @@
|
||||
macOSPackage/
|
||||
AmneziaVPN.dmg
|
||||
AmneziaVPN.exe
|
||||
AmneziaVPN_*.exe
|
||||
deploy/build/*
|
||||
winbuild.bat
|
||||
deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
|
||||
|
||||
# Qt-es
|
||||
/.qmake.cache
|
||||
@@ -19,7 +24,39 @@ qrc_*.cpp
|
||||
ui_*.h
|
||||
Makefile*
|
||||
*build-*
|
||||
compile_commands.json
|
||||
|
||||
# fastlane
|
||||
client/fastlane/report.xml
|
||||
client/fastlane/build/*
|
||||
|
||||
# Qt-es
|
||||
client/Release-iphoneos/
|
||||
client/Debug-iphoneos/
|
||||
client/.xcode/
|
||||
client/.qmake.cache
|
||||
client/.qmake.stash
|
||||
client/*.pro.user
|
||||
client/*.pro.user.*
|
||||
client/*.qbs.user
|
||||
client/*.qbs.user.*
|
||||
client/*.moc
|
||||
client/moc_*.cpp
|
||||
client/qrc_*.cpp
|
||||
client/ui_*.h
|
||||
client/ui_*.cpp
|
||||
client/Makefile*
|
||||
client/fastlane/build/
|
||||
client/*build-*
|
||||
client/AmneziaVPN.xcodeproj
|
||||
client/Debug-iphonesimulator/
|
||||
client/amneziavpn_plugin_import.cpp
|
||||
client/amneziavpn_qml_plugin_import.cpp
|
||||
client/qmlcache_loader.cpp
|
||||
client/rep_ipc_interface_replica.h
|
||||
client/resources_qmlcache.qrc
|
||||
client/3rd/OpenVPNAdpter/build/
|
||||
client/3rd/ShadowSocks/build/
|
||||
# QtCreator
|
||||
|
||||
*.autosave
|
||||
@@ -31,12 +68,69 @@ Makefile*
|
||||
# QtCtreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# Linux files
|
||||
*.7z
|
||||
deploy/AppDir
|
||||
deploy/Tools
|
||||
deploy/AmneziaVPN*Installer*
|
||||
|
||||
# MACOS files
|
||||
.DS_Store
|
||||
client/.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
*.dmg
|
||||
|
||||
# tmp files
|
||||
*.*~
|
||||
|
||||
######################### Android
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
client/3rd/ShadowSocks/ss_ios.xcconfig
|
||||
|
||||
# UML generated pics
|
||||
out/
|
||||
|
||||
# CMake files
|
||||
CMakeFiles/
|
||||
@@ -1,27 +0,0 @@
|
||||
variables:
|
||||
GIT_STRATEGY: clone
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
build-windows:
|
||||
stage: build
|
||||
tags:
|
||||
- windows
|
||||
script:
|
||||
- cmd.exe /k "deploy\windows-env.bat && cd deploy && windows.bat"
|
||||
artifacts:
|
||||
name: artifacts-windows
|
||||
paths:
|
||||
- AmneziaVPN.exe
|
||||
|
||||
build-macos:
|
||||
stage: build
|
||||
tags:
|
||||
- macos
|
||||
script:
|
||||
- cd deploy && ./macos.sh
|
||||
artifacts:
|
||||
name: artifacts-macos
|
||||
paths:
|
||||
- AmneziaVPN.dmg
|
||||
18
.gitmodules
vendored
18
.gitmodules
vendored
@@ -1,3 +1,15 @@
|
||||
[submodule "3rd/QtSsh"]
|
||||
path = 3rd/QtSsh
|
||||
url = https://github.com/amnezia-vpn/QtSsh.git
|
||||
[submodule "client/3rd/qtkeychain"]
|
||||
path = client/3rd/qtkeychain
|
||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||
[submodule "client/3rd/SortFilterProxyModel"]
|
||||
path = client/3rd/SortFilterProxyModel
|
||||
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
[submodule "client/3rd/QSimpleCrypto"]
|
||||
path = client/3rd/QSimpleCrypto
|
||||
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||
|
||||
47
.gitpod.Dockerfile
vendored
Normal file
47
.gitpod.Dockerfile
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
FROM gitpod/workspace-full-vnc
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get -q update \
|
||||
&& sudo apt-get install -yq \
|
||||
build-essential \
|
||||
libgl1-mesa-dev \
|
||||
libgstreamer-gl1.0-0 \
|
||||
libpulse-dev \
|
||||
libsecret-1-dev \
|
||||
libxcb-glx0 \
|
||||
libxcb-icccm4 \
|
||||
libxcb-image0 \
|
||||
libxcb-keysyms1 \
|
||||
libxcb-randr0 \
|
||||
libxcb-render-util0 \
|
||||
libxcb-render0 \
|
||||
libxcb-shape0 \
|
||||
libxcb-shm0 \
|
||||
libxcb-sync1 \
|
||||
libxcb-util1 \
|
||||
libxcb-xfixes0 \
|
||||
libxcb-xinerama0 \
|
||||
libxcb1 \
|
||||
libxkbcommon-dev \
|
||||
libxkbcommon-x11-0 \
|
||||
libxcb-xkb-dev \
|
||||
p7zip-full \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sudo pip3 install aqtinstall
|
||||
|
||||
ARG QT_VERSION=6.4.1
|
||||
ARG QT_ARCH=gcc_64
|
||||
|
||||
ARG QT_DIR=/opt/qt
|
||||
RUN sudo aqt install-qt --outputdir ${QT_DIR} linux desktop ${QT_VERSION} ${QT_ARCH} --modules \
|
||||
qtremoteobjects \
|
||||
qt5compat \
|
||||
qtshadertools
|
||||
ENV QT_BIN_DIR=${QT_DIR}/${QT_VERSION}/${QT_ARCH}/bin
|
||||
|
||||
ARG QIF_VERSION=4.5
|
||||
ARG QIF_DIR=/opt/qif
|
||||
RUN sudo aqt install-tool --outputdir ${QIF_DIR} linux desktop tools_ifw
|
||||
ENV QIF_BIN_DIR=${QIF_DIR}/Tools/QtInstallerFramework/${QIF_VERSION}/bin
|
||||
8
.gitpod.yml
Normal file
8
.gitpod.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
tasks:
|
||||
- init: >-
|
||||
deploy/build_linux.sh
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
vscode:
|
||||
extensions:
|
||||
- llvm-vs-code-extensions.vscode-clangd
|
||||
74
.travis.yml
74
.travis.yml
@@ -1,74 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
- /\d+\.\d+/
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: MacOS
|
||||
os: osx
|
||||
osx_image: xcode12.2
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ ! -f $HOME/Qt/5.14.2/clang_64/bin/qmake ]; then \
|
||||
brew install p7zip && \
|
||||
pip3 install aqtinstall requests py7zr && \
|
||||
python3 -m aqt install --outputdir $HOME/Qt 5.14.2 mac desktop clang_64 -m qtbase && \
|
||||
python3 -m aqt tool --outputdir $HOME/Qt mac tools_ifw 4.0.1 qt.tools.ifw.40;
|
||||
fi
|
||||
- bash deploy/build_macos.sh
|
||||
|
||||
- name: Windows
|
||||
os: windows
|
||||
|
||||
env:
|
||||
- PATH=/c/Python39:/c/Python39/Scripts:$PATH
|
||||
|
||||
before_install:
|
||||
- if [ ! -f /C/Qt/5.14.2/msvc2017/bin/qmake ]; then choco install python --version 3.9.1; fi
|
||||
|
||||
script:
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build"
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools"
|
||||
- |
|
||||
if [ ! -f /C/Qt/5.14.2/msvc2017/bin/qmake ]; then \
|
||||
pip3 install aqtinstall requests py7zr && \
|
||||
python -m aqt install --outputdir /C/Qt 5.14.2 windows desktop win32_msvc2017 -m qtbase && \
|
||||
python -m aqt tool --outputdir /C/Qt windows tools_ifw 4.0.1 qt.tools.ifw.40; \
|
||||
fi
|
||||
- echo 'call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools\VsDevCmd.bat"' > winbuild.bat
|
||||
- echo -e "\r\n" >> winbuild.bat
|
||||
- echo 'call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsamd64_x86.bat"' >> winbuild.bat
|
||||
- echo -e "\r\n" >> winbuild.bat
|
||||
- echo -e "deploy\\\build_windows.bat" >> winbuild.bat
|
||||
- cat winbuild.bat
|
||||
- cmd //c winbuild.bat
|
||||
|
||||
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GH_TOKEN
|
||||
file:
|
||||
- "AmneziaVPN.exe"
|
||||
- "AmneziaVPN.dmg"
|
||||
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
before_cache:
|
||||
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew cleanup; fi
|
||||
# Cache only .git files under "/usr/local/Homebrew" so "brew update" does not take 5min every build
|
||||
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then find /usr/local/Homebrew \! -regex ".+\.git.+" -delete; fi
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/Qt
|
||||
- /C/Qt
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
@@ -1,2 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = client service platform
|
||||
44
CMakeLists.txt
Normal file
44
CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.4.3
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2080)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||
set(MZ_PLATFORM_NAME "windows")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
|
||||
set(MZ_PLATFORM_NAME "macos")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
|
||||
set(MZ_PLATFORM_NAME "android")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
|
||||
set(MZ_PLATFORM_NAME "ios")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||
set(MZ_PLATFORM_NAME "wasm")
|
||||
endif()
|
||||
|
||||
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
endif()
|
||||
196
README.md
196
README.md
@@ -1,2 +1,196 @@
|
||||
# Amnezia
|
||||
# Amnezia VPN
|
||||
|
||||
### _The best client for self-hosted VPN_
|
||||
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Features
|
||||
|
||||
- Very easy to use - enter your IP address, SSH login, password and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||
- Classic VPN-protocols: OpenVPN, WireGuard and IKEv2 protocols.
|
||||
- Protocols with traffic Masking (Obfuscation): OpenVPN over [Cloak](https://github.com/cbeuw/Cloak) plugin, Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Split tunneling support - add any sites to the client to enable VPN only for them or add Apps (only for Android and Desktop).
|
||||
- Windows, MacOS, Linux, Android, iOS releases.
|
||||
- Support for AmneziaWG protocol configuration on [Keenetic beta firmware](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||
|
||||
## Tech
|
||||
|
||||
AmneziaVPN uses several open-source projects to work:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||
- and more...
|
||||
|
||||
## Checking out the source code
|
||||
|
||||
Make sure to pull all submodules after checking out the repo.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Want to contribute? Welcome!
|
||||
|
||||
### Help with translations
|
||||
|
||||
Download the most actual translation files.
|
||||
|
||||
Go to ["Actions" tab](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), click on the first line.
|
||||
Then scroll down to the "Artifacts" section and download "AmneziaVPN_translations".
|
||||
|
||||
Unzip this file.
|
||||
Each *.ts file contains strings for one corresponding language.
|
||||
|
||||
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
|
||||
You can do it via a web-interface or any other method you're familiar with.
|
||||
|
||||
### Building sources and deployment
|
||||
|
||||
Check deploy folder for build scripts.
|
||||
|
||||
### How to build an iOS app from source code on MacOS
|
||||
|
||||
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
||||
|
||||
2. We use QT to generate the XCode project. We need QT version 6.6.2. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Qt 5 Compatibility Module
|
||||
- Qt Shader Tools
|
||||
- Additional Libraries:
|
||||
- Qt Image Formats
|
||||
- Qt Multimedia
|
||||
- Qt Remote Objects
|
||||
|
||||
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
|
||||
|
||||
4. You also need to install go >= v1.16. If you don't have it installed already,
|
||||
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
||||
The latest version is recommended. Install gomobile
|
||||
```bash
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
```
|
||||
|
||||
5. Build the project
|
||||
```bash
|
||||
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||
export QT_IOS_BIN=$QT_BIN_DIR
|
||||
export PATH=$PATH:~/go/bin
|
||||
mkdir build-ios
|
||||
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
```
|
||||
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
|
||||
|
||||
|
||||
If you get `gomobile: command not found` make sure to set PATH to the location
|
||||
of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
|
||||
```bash
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
```
|
||||
|
||||
6. Open the XCode project. You can then run /test/archive/ship the app.
|
||||
|
||||
If the build fails with the following error
|
||||
```
|
||||
make: ***
|
||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||
Error 1
|
||||
```
|
||||
Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
||||
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||
|
||||
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
```
|
||||
|
||||
Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
|
||||
require them. In this case, simply restart the build.
|
||||
|
||||
## How to build the Android app
|
||||
|
||||
_Tested on Mac OS_
|
||||
|
||||
The Android app has the following requirements:
|
||||
* JDK 11
|
||||
* Android platform SDK 33
|
||||
* CMake 3.25.0
|
||||
|
||||
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly.
|
||||
|
||||
- Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||
- Set path to JDK 11
|
||||
- Set path to Android SDK (`$ANDROID_HOME`)
|
||||
|
||||
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
|
||||
|
||||
That's it! You should be ready to compile the project from QT Creator!
|
||||
|
||||
### Development flow
|
||||
|
||||
After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
|
||||
|
||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
|
||||
|
||||
## License
|
||||
|
||||
GPL v3.0
|
||||
|
||||
## Donate
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
## Acknowledgments
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
||||
|
||||
181
README_RU.md
Normal file
181
README_RU.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Amnezia VPN
|
||||
|
||||
### _Лучший клиент для создания VPN на собственном сервере_
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Особенности
|
||||
|
||||
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
|
||||
AmneziaVPN использует несколько проектов с открытым исходным кодом:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org)
|
||||
- и другие...
|
||||
|
||||
## Проверка исходного кода
|
||||
После клонирования репозитория обязательно загрузите все подмодули.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
|
||||
## Разработка
|
||||
Хотите внести свой вклад? Добро пожаловать!
|
||||
|
||||
### Помощь с переводами
|
||||
|
||||
Загрузите самые актуальные файлы перевода.
|
||||
|
||||
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
|
||||
|
||||
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
|
||||
|
||||
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
|
||||
|
||||
### Сборка исходного кода и деплой
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с Qt 5
|
||||
- Qt Shader Tools
|
||||
- Дополнительные библиотеки:
|
||||
- Qt Image Formats
|
||||
- Qt Multimedia
|
||||
- Qt Remote Objects
|
||||
|
||||
|
||||
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
|
||||
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
|
||||
|
||||
```bash
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
```
|
||||
|
||||
5. Соберите проект:
|
||||
```bash
|
||||
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||
export QT_IOS_BIN=$QT_BIN_DIR
|
||||
export PATH=$PATH:~/go/bin
|
||||
mkdir build-ios
|
||||
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
```
|
||||
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
|
||||
|
||||
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
|
||||
```bash
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
```
|
||||
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
make: ***
|
||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||
Error 1
|
||||
```
|
||||
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||
|
||||
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
```
|
||||
|
||||
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
|
||||
|
||||
|
||||
## Как собрать Android-приложение
|
||||
Сборка тестировалась на MacOS. Требования:
|
||||
- JDK 11
|
||||
- Android SDK 33
|
||||
- CMake 3.25.0
|
||||
|
||||
Установите QT, QT Creator и Android Studio.
|
||||
Настройте QT Creator:
|
||||
|
||||
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
|
||||
- Укажите путь к JDK 11.
|
||||
- Укажите путь к Android SDK (`$ANDROID_HOME`)
|
||||
|
||||
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
|
||||
|
||||
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
|
||||
|
||||
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
|
||||
|
||||
### Разработка Android-компонентов
|
||||
|
||||
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
|
||||
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
|
||||
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
|
||||
|
||||
|
||||
## Лицензия
|
||||
|
||||
GPL v3.0
|
||||
|
||||
## Донаты
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
|
||||
## Благодарности
|
||||
|
||||
Этот проект тестируется с помощью BrowserStack.
|
||||
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
|
||||
1
client/3rd-prebuilt
Submodule
1
client/3rd-prebuilt
Submodule
Submodule client/3rd-prebuilt added at efad1a5b5c
2
client/3rd/QJsonStruct/.gitignore
vendored
Normal file
2
client/3rd/QJsonStruct/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.user
|
||||
build/
|
||||
19
client/3rd/QJsonStruct/CMakeLists.txt
Normal file
19
client/3rd/QJsonStruct/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(QJsonStruct LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
option(BUILD_TESTING ON)
|
||||
|
||||
include(QJsonStruct.cmake)
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
21
client/3rd/QJsonStruct/LICENSE
Normal file
21
client/3rd/QJsonStruct/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Qv2ray Workgroup
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
190
client/3rd/QJsonStruct/QJsonIO.hpp
Normal file
190
client/3rd/QJsonStruct/QJsonIO.hpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#pragma once
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QList>
|
||||
#include <tuple>
|
||||
|
||||
enum class QJsonIOPathType
|
||||
{
|
||||
JSONIO_MODE_ARRAY,
|
||||
JSONIO_MODE_OBJECT
|
||||
};
|
||||
|
||||
typedef QPair<QString, QJsonIOPathType> QJsonIONodeType;
|
||||
|
||||
struct QJsonIOPath : QList<QJsonIONodeType>
|
||||
{
|
||||
template<typename type1, typename type2, typename... types>
|
||||
QJsonIOPath(const type1 t1, const type2 t2, const types... ts)
|
||||
{
|
||||
AppendPath(t1);
|
||||
AppendPath(t2);
|
||||
(AppendPath(ts), ...);
|
||||
}
|
||||
|
||||
void AppendPath(size_t index)
|
||||
{
|
||||
append({ QString::number(index), QJsonIOPathType::JSONIO_MODE_ARRAY });
|
||||
}
|
||||
|
||||
void AppendPath(const QString &key)
|
||||
{
|
||||
append({ key, QJsonIOPathType::JSONIO_MODE_OBJECT });
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator<<(const t &str)
|
||||
{
|
||||
AppendPath(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator+=(const t &val)
|
||||
{
|
||||
AppendPath(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
QJsonIOPath &operator<<(const QJsonIOPath &other)
|
||||
{
|
||||
for (const auto &x : other)
|
||||
this->append(x);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath &operator<<(const t &val) const
|
||||
{
|
||||
auto _new = *this;
|
||||
return _new << val;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
QJsonIOPath operator+(const t &val) const
|
||||
{
|
||||
auto _new = *this;
|
||||
return _new << val;
|
||||
}
|
||||
|
||||
QJsonIOPath operator+(const QJsonIOPath &other) const
|
||||
{
|
||||
auto _new = *this;
|
||||
for (const auto &x : other)
|
||||
_new.append(x);
|
||||
return _new;
|
||||
}
|
||||
};
|
||||
|
||||
class QJsonIO
|
||||
{
|
||||
public:
|
||||
const static inline QJsonValue Null = QJsonValue::Null;
|
||||
const static inline QJsonValue Undefined = QJsonValue::Undefined;
|
||||
|
||||
template<typename current_key_type, typename... t_other_types>
|
||||
static QJsonValue GetValue(const QJsonValue &parent, const current_key_type ¤t, const t_other_types &...other)
|
||||
{
|
||||
if constexpr (sizeof...(t_other_types) == 0)
|
||||
if constexpr (std::is_integral_v<current_key_type>)
|
||||
return parent.toArray()[current];
|
||||
else
|
||||
return parent.toObject()[current];
|
||||
else if constexpr (std::is_integral_v<current_key_type>)
|
||||
return GetValue(parent.toArray()[current], other...);
|
||||
else
|
||||
return GetValue(parent.toObject()[current], other...);
|
||||
}
|
||||
|
||||
template<typename... key_types_t>
|
||||
static QJsonValue GetValue(QJsonValue value, const std::tuple<key_types_t...> &keys, const QJsonValue &defaultValue = Undefined)
|
||||
{
|
||||
std::apply([&](auto &&...args) { ((value = value[args]), ...); }, keys);
|
||||
return value.isUndefined() ? defaultValue : value;
|
||||
}
|
||||
|
||||
template<typename parent_type, typename t_value_type, typename current_key_type, typename... t_other_key_types>
|
||||
static void SetValue(parent_type &parent, const t_value_type &val, const current_key_type ¤t, const t_other_key_types &...other)
|
||||
{
|
||||
// If current parent is an array, increase its size to fit the "key"
|
||||
if constexpr (std::is_integral_v<current_key_type>)
|
||||
for (auto i = parent.size(); i <= current; i++)
|
||||
parent.insert(i, {});
|
||||
|
||||
// If the t_other_key_types has nothing....
|
||||
// Means we have reached the end of recursion.
|
||||
if constexpr (sizeof...(t_other_key_types) == 0)
|
||||
parent[current] = val;
|
||||
else if constexpr (std::is_integral_v<typename std::tuple_element_t<0, std::tuple<t_other_key_types...>>>)
|
||||
{
|
||||
// Means we still have many keys
|
||||
// So this element is an array.
|
||||
auto _array = parent[current].toArray();
|
||||
SetValue(_array, val, other...);
|
||||
parent[current] = _array;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto _object = parent[current].toObject();
|
||||
SetValue(_object, val, other...);
|
||||
parent[current] = _object;
|
||||
}
|
||||
}
|
||||
|
||||
static QJsonValue GetValue(const QJsonValue &parent, const QJsonIOPath &path, const QJsonValue &defaultValue = QJsonIO::Undefined)
|
||||
{
|
||||
QJsonValue val = parent;
|
||||
for (const auto &[k, t] : path)
|
||||
{
|
||||
if (t == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
val = val.toArray()[k.toInt()];
|
||||
else
|
||||
val = val.toObject()[k];
|
||||
}
|
||||
return val.isUndefined() ? defaultValue : val;
|
||||
}
|
||||
|
||||
template<typename parent_type, typename value_type>
|
||||
static void SetValue(parent_type &parent, const value_type &t, const QJsonIOPath &path)
|
||||
{
|
||||
QList<std::tuple<QString, QJsonIOPathType, QJsonValue>> _stack;
|
||||
QJsonValue lastNode = parent;
|
||||
for (const auto &[key, type] : path)
|
||||
{
|
||||
_stack.prepend({ key, type, lastNode });
|
||||
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
lastNode = lastNode.toArray().at(key.toInt());
|
||||
else
|
||||
lastNode = lastNode.toObject()[key];
|
||||
}
|
||||
|
||||
lastNode = t;
|
||||
|
||||
for (const auto &[key, type, node] : _stack)
|
||||
{
|
||||
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||
{
|
||||
const auto index = key.toInt();
|
||||
auto nodeArray = node.toArray();
|
||||
for (auto i = nodeArray.size(); i <= index; i++)
|
||||
nodeArray.insert(i, {});
|
||||
nodeArray[index] = lastNode;
|
||||
lastNode = nodeArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto nodeObject = node.toObject();
|
||||
nodeObject[key] = lastNode;
|
||||
lastNode = nodeObject;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<parent_type, QJsonObject>)
|
||||
parent = lastNode.toObject();
|
||||
else if constexpr (std::is_same_v<parent_type, QJsonArray>)
|
||||
parent = lastNode.toArray();
|
||||
else
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
};
|
||||
5
client/3rd/QJsonStruct/QJsonStruct.cmake
Normal file
5
client/3rd/QJsonStruct/QJsonStruct.cmake
Normal file
@@ -0,0 +1,5 @@
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
set(QJSONSTRUCT_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/QJsonStruct.hpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/QJsonIO.hpp)
|
||||
215
client/3rd/QJsonStruct/QJsonStruct.hpp
Normal file
215
client/3rd/QJsonStruct/QJsonStruct.hpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
#include "macroexpansion.hpp"
|
||||
|
||||
#ifndef _X
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#endif
|
||||
|
||||
/// macro to define an operator==
|
||||
#define ___JSONSTRUCT_DEFAULT_COMPARE_IMPL(x) (this->x == ___another___instance__.x) &&
|
||||
#define JSONSTRUCT_COMPARE(CLASS, ...) \
|
||||
bool operator==(const CLASS &___another___instance__) const \
|
||||
{ \
|
||||
return FOR_EACH(___JSONSTRUCT_DEFAULT_COMPARE_IMPL, __VA_ARGS__) true; \
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// Load JSON IMPL
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL(name) name::loadJson(___json_object_);
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC(name) ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name) \
|
||||
if (___json_object_.toObject().contains(#name)) \
|
||||
{ \
|
||||
JsonStructHelper::Deserialize(this->name, ___json_object_.toObject()[#name]); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
this->name = ___qjsonstruct_default_check.name; \
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
// To JSON IMPL
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_F_FUNC(name) \
|
||||
if (!(___qjsonstruct_default_check.name == this->name)) \
|
||||
{ \
|
||||
___json_object_.insert(#name, JsonStructHelper::Serialize(name)); \
|
||||
}
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_A_FUNC(name) ___json_object_.insert(#name, JsonStructHelper::Serialize(name));
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL(name) JsonStructHelper::MergeJson(___json_object_, name::toJson());
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||
|
||||
// ============================================================================================
|
||||
// Load JSON Wrapper
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||
#define ___DESERIALIZE_FROM_JSON_EXTRACT_B_F(name_option) ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_##name_option
|
||||
|
||||
// ============================================================================================
|
||||
// To JSON Wrapper
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||
#define ___SERIALIZE_TO_JSON_EXTRACT_B_F(name_option) ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_##name_option
|
||||
|
||||
// ============================================================================================
|
||||
#define JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, ...) \
|
||||
void loadJson(const QJsonValue &___json_object_) \
|
||||
{ \
|
||||
___class_type_ ___qjsonstruct_default_check; \
|
||||
FOREACH_CALL_FUNC(___DESERIALIZE_FROM_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||
} \
|
||||
[[nodiscard]] const QJsonObject toJson() const \
|
||||
{ \
|
||||
___class_type_ ___qjsonstruct_default_check; \
|
||||
QJsonObject ___json_object_; \
|
||||
FOREACH_CALL_FUNC(___SERIALIZE_TO_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||
return ___json_object_; \
|
||||
}
|
||||
|
||||
#define JSONSTRUCT_REGISTER(___class_type_, ...) \
|
||||
JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, __VA_ARGS__); \
|
||||
[[nodiscard]] static auto fromJson(const QJsonValue &___json_object_) \
|
||||
{ \
|
||||
___class_type_ _t; \
|
||||
_t.loadJson(___json_object_); \
|
||||
return _t; \
|
||||
}
|
||||
|
||||
#define ___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(type, convert_func) \
|
||||
static void Deserialize(type &t, const QJsonValue &d) \
|
||||
{ \
|
||||
t = d.convert_func; \
|
||||
}
|
||||
|
||||
class JsonStructHelper
|
||||
{
|
||||
public:
|
||||
static void MergeJson(QJsonObject &mergeTo, const QJsonObject &mergeIn)
|
||||
{
|
||||
for (const auto &key : mergeIn.keys())
|
||||
mergeTo[key] = mergeIn.value(key);
|
||||
}
|
||||
//
|
||||
template<typename T>
|
||||
static void Deserialize(T &t, const QJsonValue &d)
|
||||
{
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
t = (T) d.toInt();
|
||||
else if constexpr (std::is_same_v<T, QJsonObject>)
|
||||
t = d.toObject();
|
||||
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||
t = d.toArray();
|
||||
else
|
||||
t.loadJson(d);
|
||||
}
|
||||
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QString, toString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QChar, toVariant().toChar());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::string, toString().toStdString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::wstring, toString().toStdWString());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(bool, toBool());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(double, toDouble());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(float, toVariant().toFloat());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(int, toInt());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long, toVariant().toLongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long long, toVariant().toLongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned int, toVariant().toUInt());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long, toVariant().toULongLong());
|
||||
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long long, toVariant().toULongLong());
|
||||
|
||||
template<typename T>
|
||||
static void Deserialize(QList<T> &t, const QJsonValue &d)
|
||||
{
|
||||
t.clear();
|
||||
for (const auto &val : d.toArray())
|
||||
{
|
||||
T data;
|
||||
Deserialize(data, val);
|
||||
t.push_back(data);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TKey, typename TValue>
|
||||
static void Deserialize(QMap<TKey, TValue> &t, const QJsonValue &d)
|
||||
{
|
||||
t.clear();
|
||||
const auto &jsonObject = d.toObject();
|
||||
TKey keyVal;
|
||||
TValue valueVal;
|
||||
for (const auto &key : jsonObject.keys())
|
||||
{
|
||||
Deserialize(keyVal, key);
|
||||
Deserialize(valueVal, jsonObject.value(key));
|
||||
t.insert(keyVal, valueVal);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================== Store Json Data ===========================
|
||||
|
||||
template<typename T>
|
||||
static QJsonValue Serialize(const T &t)
|
||||
{
|
||||
if constexpr (std::is_enum_v<T>)
|
||||
return (int) t;
|
||||
else if constexpr (std::is_same_v<T, QJsonObject> || std::is_same_v<T, QJsonArray>)
|
||||
return t;
|
||||
else
|
||||
return t.toJson();
|
||||
}
|
||||
|
||||
#define pure_func(x) (x)
|
||||
#define ___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(type) \
|
||||
static QJsonValue Serialize(const type &t) \
|
||||
{ \
|
||||
return QJsonValue(t); \
|
||||
}
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(int);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(bool);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonArray);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonObject);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QString);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(long long);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(float);
|
||||
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(double);
|
||||
|
||||
#define ___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(type, func) \
|
||||
static QJsonValue Serialize(const type &t) \
|
||||
{ \
|
||||
return QJsonValue::fromVariant(func); \
|
||||
}
|
||||
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::string, QString::fromStdString(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::wstring, QString::fromStdWString(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(long, QVariant::fromValue<long>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned int, QVariant::fromValue<unsigned int>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long, QVariant::fromValue<unsigned long>(t))
|
||||
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long long, QVariant::fromValue<unsigned long long>(t))
|
||||
|
||||
template<typename TValue>
|
||||
static QJsonValue Serialize(const QMap<QString, TValue> &t)
|
||||
{
|
||||
QJsonObject mapObject;
|
||||
for (const auto &key : t.keys())
|
||||
{
|
||||
auto valueVal = Serialize(t.value(key));
|
||||
mapObject.insert(key, valueVal);
|
||||
}
|
||||
return mapObject;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static QJsonValue Serialize(const QList<T> &t)
|
||||
{
|
||||
QJsonArray listObject;
|
||||
for (const auto &item : t)
|
||||
{
|
||||
listObject.push_back(Serialize(item));
|
||||
}
|
||||
return listObject;
|
||||
}
|
||||
};
|
||||
74
client/3rd/QJsonStruct/macroexpansion.hpp
Normal file
74
client/3rd/QJsonStruct/macroexpansion.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
|
||||
#define CONCATENATE2(arg1, arg2) arg1##arg2
|
||||
#define CONCATENATE(x, y) x##y
|
||||
|
||||
#define EXPAND(...) __VA_ARGS__
|
||||
#define FOR_EACH_1(what, x, ...) what(x)
|
||||
#define FOR_EACH_2(what, x, ...) what(x) EXPAND(FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define FOR_EACH_3(what, x, ...) what(x) EXPAND(FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define FOR_EACH_4(what, x, ...) what(x) EXPAND(FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define FOR_EACH_5(what, x, ...) what(x) EXPAND(FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define FOR_EACH_6(what, x, ...) what(x) EXPAND(FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define FOR_EACH_7(what, x, ...) what(x) EXPAND(FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define FOR_EACH_8(what, x, ...) what(x) EXPAND(FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define FOR_EACH_9(what, x, ...) what(x) EXPAND(FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define FOR_EACH_10(what, x, ...) what(x) EXPAND(FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define FOR_EACH_11(what, x, ...) what(x) EXPAND(FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define FOR_EACH_12(what, x, ...) what(x) EXPAND(FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define FOR_EACH_13(what, x, ...) what(x) EXPAND(FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define FOR_EACH_14(what, x, ...) what(x) EXPAND(FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define FOR_EACH_15(what, x, ...) what(x) EXPAND(FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define FOR_EACH_16(what, x, ...) what(x) EXPAND(FOR_EACH_15(what, __VA_ARGS__))
|
||||
|
||||
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
|
||||
#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__))
|
||||
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
|
||||
#define FOR_EACH_RSEQ_N() 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC(func, ...) FOR_EACH(func, __VA_ARGS__)
|
||||
|
||||
// Bad hack ==========================================================================================================================
|
||||
#define _2X_FOR_EACH_1(what, x, ...) what(x)
|
||||
#define _2X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_15(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_2X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define _2X_FOR_EACH(what, ...) _2X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC_2(func, ...) _2X_FOR_EACH(func, __VA_ARGS__)
|
||||
|
||||
// Bad hack ==========================================================================================================================
|
||||
#define _3X_FOR_EACH_1(what, x, ...) what(x)
|
||||
#define _3X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_1(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_2(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_3(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_4(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_5(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_6(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_7(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_8(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_9(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_10(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_11(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_12(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_13(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_14(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_15(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_3X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||
#define _3X_FOR_EACH(what, ...) _3X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||
#define FOREACH_CALL_FUNC_3(func, ...) _3X_FOR_EACH(func, __VA_ARGS__)
|
||||
16
client/3rd/QJsonStruct/test/CMakeLists.txt
Normal file
16
client/3rd/QJsonStruct/test/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
function(QJSONSTRUCT_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||
add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp ${QJSONSTRUCT_SOURCES})
|
||||
target_include_directories(${TEST_NAME}
|
||||
PRIVATE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
)
|
||||
target_link_libraries(
|
||||
${TEST_NAME}
|
||||
PRIVATE
|
||||
Qt::Core
|
||||
)
|
||||
add_test(NAME QJSONSTRUCT_TEST_${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}> -s)
|
||||
endfunction()
|
||||
|
||||
QJSONSTRUCT_ADD_TEST(serialization serialize/main.cpp)
|
||||
#QJSONSTRUCT_ADD_TEST(serialize_strings serialize/strings.cpp)
|
||||
45
client/3rd/QJsonStruct/test/TestIO.hpp
Normal file
45
client/3rd/QJsonStruct/test/TestIO.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "QJsonStruct.hpp"
|
||||
#ifndef _X
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#endif
|
||||
|
||||
struct BaseStruct
|
||||
{
|
||||
QString baseStr;
|
||||
int o;
|
||||
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr, o))
|
||||
};
|
||||
|
||||
struct BaseStruct2
|
||||
{
|
||||
QString baseStr2;
|
||||
int o2;
|
||||
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr2, o2))
|
||||
};
|
||||
struct TestInnerStruct
|
||||
: BaseStruct
|
||||
, BaseStruct2
|
||||
{
|
||||
QJsonObject jobj;
|
||||
QJsonArray jarray;
|
||||
QString str;
|
||||
JSONSTRUCT_REGISTER(TestInnerStruct, B(BaseStruct, BaseStruct2), F(str, jobj, jarray))
|
||||
};
|
||||
|
||||
struct JsonIOTest
|
||||
{
|
||||
QString str;
|
||||
QList<int> listOfNumber;
|
||||
QList<bool> listOfBool;
|
||||
QList<QString> listOfString;
|
||||
QList<QList<QString>> listOfListOfString;
|
||||
|
||||
QMap<QString, QString> map;
|
||||
TestInnerStruct inner;
|
||||
|
||||
JSONSTRUCT_REGISTER(JsonIOTest, F(str, listOfNumber, listOfBool, listOfString, listOfListOfString, map, inner));
|
||||
JsonIOTest(){};
|
||||
};
|
||||
19
client/3rd/QJsonStruct/test/TestOut.hpp
Normal file
19
client/3rd/QJsonStruct/test/TestOut.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "QJsonStruct.hpp"
|
||||
|
||||
struct SubData
|
||||
{
|
||||
QString subString;
|
||||
JSONSTRUCT_REGISTER_TOJSON(subString)
|
||||
};
|
||||
|
||||
struct ToJsonOnlyData
|
||||
{
|
||||
QString x;
|
||||
int y;
|
||||
int z;
|
||||
QList<int> ints;
|
||||
SubData sub;
|
||||
QMap<QString, SubData> subs;
|
||||
JSONSTRUCT_REGISTER_TOJSON(x, y, z, sub, ints, subs)
|
||||
};
|
||||
17698
client/3rd/QJsonStruct/test/catch.hpp
Normal file
17698
client/3rd/QJsonStruct/test/catch.hpp
Normal file
File diff suppressed because it is too large
Load Diff
70
client/3rd/QJsonStruct/test/main.cpp
Normal file
70
client/3rd/QJsonStruct/test/main.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "QJsonIO.hpp"
|
||||
#include "QJsonStruct.hpp"
|
||||
#include "TestIO.hpp"
|
||||
#include "TestOut.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Q_UNUSED(argc)
|
||||
Q_UNUSED(argv)
|
||||
|
||||
{
|
||||
ToJsonOnlyData data;
|
||||
data.x = "1string";
|
||||
data.y = 2;
|
||||
data.ints << 0;
|
||||
data.ints << 100;
|
||||
data.ints << 900;
|
||||
data.sub.subString = "subs";
|
||||
data.subs["subs-1"] = { "subs1-data" };
|
||||
data.subs["subs-2"] = { "subs2-data" };
|
||||
data.subs["subs-3"] = { "subs3-data" };
|
||||
data.z = 3;
|
||||
auto x = data.toJson();
|
||||
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||
}
|
||||
//
|
||||
{
|
||||
auto f = JsonIOTest::fromJson( //
|
||||
QJsonObject{
|
||||
{ "inner", QJsonObject{ { "str", "innerString" }, //
|
||||
{ "jobj", QJsonObject{ { "key", "value" } } }, //
|
||||
{ "jarray", QJsonArray{ "array0", "array1", "array2" } }, //
|
||||
{ "baseStr", "baseInnerString" } } }, //
|
||||
{ "str", "data1" }, //
|
||||
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||
QJsonArray{ "1", "2" }, //
|
||||
QJsonArray{ "1", "2", "3" }, //
|
||||
QJsonArray{ "1", "2", "3", "4" }, //
|
||||
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||
});
|
||||
auto x = f.toJson();
|
||||
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||
}
|
||||
{
|
||||
QJsonObject obj{
|
||||
{ "inner", QJsonObject{ { "str", "innerString" }, { "baseStr", "baseInnerString" } } }, //
|
||||
{ "str", "data1" }, //
|
||||
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||
QJsonArray{ "1", "2" }, //
|
||||
QJsonArray{ "1", "2", "3" }, //
|
||||
QJsonArray{ "1", "2", "3", "4" }, //
|
||||
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||
};
|
||||
auto y = QJsonIO::GetValue(obj, std::tuple{ "listOfListOfString", 2 });
|
||||
y.toObject();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
181
client/3rd/QJsonStruct/test/serialize/main.cpp
Normal file
181
client/3rd/QJsonStruct/test/serialize/main.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "QJsonStruct.hpp"
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
const static inline auto INT_TEST_MAX = std::numeric_limits<int>::max() - 1;
|
||||
const static inline auto INT_TEST_MIN = -(std::numeric_limits<int>::min() + 1);
|
||||
|
||||
#define SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance) \
|
||||
class CLASS \
|
||||
{ \
|
||||
public: \
|
||||
TYPE field = defaultvalue; \
|
||||
JSONSTRUCT_REGISTER(CLASS, existance(field)); \
|
||||
};
|
||||
|
||||
// SINGLE_ELEMENT_REQUIRE( CLASS_NAME , TYPE , FIELD , DEFAULT_VALUE , SET VALUE , CHECK VALUE )
|
||||
#define SINGLE_ELEMENT_REQUIRE(CLASS, TYPE, field, defaultvalue, value, checkvalue, existance) \
|
||||
SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance); \
|
||||
CLASS CLASS##_class; \
|
||||
CLASS##_class.field = value; \
|
||||
REQUIRE(CLASS##_class.toJson()[#field] == checkvalue);
|
||||
|
||||
using namespace std;
|
||||
SCENARIO("Test Serialization", "[Serialize]")
|
||||
{
|
||||
GIVEN("Single Element")
|
||||
{
|
||||
const static QList<QString> defaultList{ "entry 1", "entry 2" };
|
||||
const static QMap<QString, QString> defaultMap{ { "key1", "value1" }, { "key2", "value2" } };
|
||||
typedef QMap<QString, QString> QStringQStringMap;
|
||||
|
||||
WHEN("Serialize a single element")
|
||||
{
|
||||
const static QStringQStringMap setValueMap{ { "newkey1", "newvalue1" } };
|
||||
const static QJsonObject setValueJson{ { "newkey1", QJsonValue{ "newvalue1" } } };
|
||||
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_Empty, QString, a, "empty", "", "", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest, QString, a, "empty", "Some QString", "Some QString", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_WithQoutes, QString, a, "empty", "\"", "\"", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zint, int, a, -10, 0, 0, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_nint, int, a, -10, 1, 1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_pint, int, a, -10, -1, -1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_pmint, int, a, -1, INT_TEST_MAX, INT_TEST_MAX, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zmint, int, a, -1, INT_TEST_MIN, INT_TEST_MIN, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_zuint, uint, a, -10, 0, 0, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QStringTest_puint, uint, a, -10, 1, 1, F);
|
||||
SINGLE_ELEMENT_REQUIRE(BoolTest_True, bool, a, false, true, true, F);
|
||||
SINGLE_ELEMENT_REQUIRE(BoolTest_False, bool, a, true, false, false, F);
|
||||
SINGLE_ELEMENT_REQUIRE(StdStringTest, string, a, "def", "std::string _test", "std::string _test", F);
|
||||
SINGLE_ELEMENT_REQUIRE(QListTest, QList<QString>, a, defaultList, { "newEntry" }, QJsonArray{ "newEntry" }, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QMapTest, QStringQStringMap, a, defaultMap, {}, QJsonObject{}, F);
|
||||
SINGLE_ELEMENT_REQUIRE(QMapValueTest, QStringQStringMap, a, defaultMap, setValueMap, setValueJson, F);
|
||||
}
|
||||
|
||||
WHEN("Serialize a default value")
|
||||
{
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, QJsonValue::Undefined, F);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, QJsonValue::Undefined, F);
|
||||
}
|
||||
|
||||
WHEN("Serialize a force existance default value")
|
||||
{
|
||||
const static QJsonArray defaultListJson{ "entry 1", "entry 2" };
|
||||
const static QJsonObject defaultMapJson{ { "key1", "value1" }, { "key2", "value2" } };
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", "defaultvalue", A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, 12345, A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, defaultListJson, A);
|
||||
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, defaultMapJson, A);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Multiple Simple Elements")
|
||||
{
|
||||
WHEN("Can Omit Default Value")
|
||||
{
|
||||
class MultipleNonDefaultElementTestClass
|
||||
{
|
||||
public:
|
||||
QString astring;
|
||||
int integer = 0;
|
||||
double adouble = 0.0;
|
||||
QList<QString> myList;
|
||||
JSONSTRUCT_REGISTER(MultipleNonDefaultElementTestClass, F(astring, integer, adouble, myList))
|
||||
};
|
||||
MultipleNonDefaultElementTestClass instance;
|
||||
const auto json = instance.toJson();
|
||||
REQUIRE(json["astring"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["integer"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["adouble"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["myList"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Forcing Existance")
|
||||
{
|
||||
class MultipleNonDefaultExistanceElementTestClass
|
||||
{
|
||||
public:
|
||||
QString astring;
|
||||
int integer = 0;
|
||||
double adouble = 0.0;
|
||||
QList<QString> myList;
|
||||
JSONSTRUCT_REGISTER(MultipleNonDefaultExistanceElementTestClass, A(astring, integer, adouble, myList))
|
||||
};
|
||||
MultipleNonDefaultExistanceElementTestClass instance;
|
||||
const auto json = instance.toJson();
|
||||
REQUIRE(json["astring"] == "");
|
||||
REQUIRE(json["integer"] == 0);
|
||||
REQUIRE(json["adouble"] == 0.0);
|
||||
REQUIRE(json["myList"] == QJsonArray{});
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Nested Elements")
|
||||
{
|
||||
WHEN("Can Omit Default Value")
|
||||
{
|
||||
class Parent
|
||||
{
|
||||
class NestedChild
|
||||
{
|
||||
class NestedChild2
|
||||
{
|
||||
public:
|
||||
int childChildInt = 13579;
|
||||
JSONSTRUCT_COMPARE(NestedChild2, childChildInt)
|
||||
JSONSTRUCT_REGISTER(NestedChild2, F(childChildInt))
|
||||
};
|
||||
|
||||
public:
|
||||
int childInt = 54321;
|
||||
QString childQString = "A QString";
|
||||
NestedChild2 anotherChild;
|
||||
JSONSTRUCT_COMPARE(NestedChild, childInt, childQString, anotherChild)
|
||||
JSONSTRUCT_REGISTER(NestedChild, F(childInt, childQString, anotherChild))
|
||||
};
|
||||
|
||||
public:
|
||||
int parentInt = 12345;
|
||||
NestedChild child;
|
||||
JSONSTRUCT_REGISTER(Parent, F(parentInt, child))
|
||||
};
|
||||
|
||||
WHEN("Omitted whole child element")
|
||||
{
|
||||
Parent parent;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Omitted one element in the child")
|
||||
{
|
||||
const auto childJson = QJsonObject{ { "childInt", 1314 } };
|
||||
Parent parent;
|
||||
parent.child.childInt = 1314;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"] == childJson);
|
||||
REQUIRE(json["child"]["childInt"] == 1314);
|
||||
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"]["child"]["anotherChild"] == QJsonValue::Undefined);
|
||||
}
|
||||
|
||||
WHEN("Omitted one element in the child child")
|
||||
{
|
||||
Parent parent;
|
||||
parent.child.childInt = 1314;
|
||||
parent.child.anotherChild.childChildInt = 97531;
|
||||
const auto json = parent.toJson();
|
||||
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||
REQUIRE(json["child"]["childInt"] == 1314);
|
||||
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||
const QJsonObject childChild{ { "childChildInt", 97531 } };
|
||||
REQUIRE(json["child"]["anotherChild"] == childChild);
|
||||
REQUIRE(json["child"]["anotherChild"]["childChildInt"] == 97531);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
1
client/3rd/QSimpleCrypto
vendored
Submodule
Submodule client/3rd/QSimpleCrypto added at c99b33f0e0
50
client/3rd/QtSsh/.gitignore
vendored
50
client/3rd/QtSsh/.gitignore
vendored
@@ -1,50 +0,0 @@
|
||||
# User settings
|
||||
*.user
|
||||
macOSPackage/
|
||||
|
||||
# C++ objects and libs
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.a
|
||||
*.la
|
||||
*.lai
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
|
||||
# Qt-es
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
*.qbs.user.*
|
||||
*.moc
|
||||
moc_*.cpp
|
||||
qrc_*.cpp
|
||||
ui_*.h
|
||||
Makefile*
|
||||
*build-*
|
||||
|
||||
# QtCreator
|
||||
|
||||
*.autosave
|
||||
|
||||
# QtCtreator Qml
|
||||
*.qmlproject.user
|
||||
*.qmlproject.user.*
|
||||
|
||||
# QtCtreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# MACOS files
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
|
||||
# tmp files
|
||||
*.*~
|
||||
|
||||
# Certificates
|
||||
*.p12
|
||||
@@ -1,3 +0,0 @@
|
||||
load(qt_build_config)
|
||||
|
||||
MODULE_VERSION = 4.3.1
|
||||
@@ -1 +0,0 @@
|
||||
load(qt_parts)
|
||||
@@ -1,46 +0,0 @@
|
||||
# QSsh
|
||||
|
||||
this project is base on Qt-creator-open-source-4.3.1
|
||||
project is at
|
||||
|
||||
`http://code.qt.io/cgit/qt-creator/qt-creator.git/`
|
||||
|
||||
you can download code zip at
|
||||
|
||||
`http://download.qt.io/official_releases/qtcreator/4.3/4.3.1/`
|
||||
|
||||
## Getting Started
|
||||
|
||||
> * For linux user, if your Qt is installed through package manager tools such "apt-get", make sure that you have installed the Qt5 develop package *qtbase5-private-dev*
|
||||
|
||||
### Usage(1): Use QtSsh as Qt5's addon module
|
||||
|
||||
#### Building the module
|
||||
|
||||
> **Note**: Perl is needed in this step.
|
||||
|
||||
* Download the source code.
|
||||
|
||||
* Put the source code in any directory you like
|
||||
|
||||
* Go to top directory of the project in a terminal and run
|
||||
|
||||
```
|
||||
qmake
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
The library, the header files, and others will be installed to your system.
|
||||
|
||||
#### Import the module
|
||||
|
||||
` QT += ssh`
|
||||
|
||||
#### Include Headerfile
|
||||
|
||||
` #include<QtSsh/sshconnection.h>`
|
||||
|
||||
#### Close qtc.ssh log
|
||||
|
||||
` QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false")`
|
||||
@@ -1,2 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = gitlab
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>Qml/Main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,51 +0,0 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.0
|
||||
import Ssh 1.0
|
||||
Item {
|
||||
width: 1024
|
||||
height: 768
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
x: 104
|
||||
y: 44
|
||||
width: 482
|
||||
height: 40
|
||||
implicitWidth: 200
|
||||
selectByMouse: true
|
||||
text: "https://www.zhihu.com"
|
||||
}
|
||||
function get(url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
console.log(xhr.responseXML, xhr.responseText.toString())
|
||||
} else if (xhr.readyState === XMLHttpRequest) {
|
||||
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
}
|
||||
Button {
|
||||
x: 627
|
||||
y: 44
|
||||
text: "get"
|
||||
onClicked: {
|
||||
get(input.text)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
x: 104
|
||||
y: 170
|
||||
text: qsTr("Ssh")
|
||||
onClicked: ssh.connectToHost()
|
||||
}
|
||||
Ssh {
|
||||
id: ssh
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQuickView>
|
||||
#include "Ssh.hpp"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
qmlRegisterType<Ssh> ("Ssh", 1, 0, "Ssh");
|
||||
|
||||
QQuickView view;
|
||||
view.setSource(QUrl("qrc:/Qml/Main.qml"));
|
||||
view.show();
|
||||
return app.exec();
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#include "Ssh.hpp"
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Ssh::Ssh(QObject *parent) : QObject(parent) {
|
||||
//关掉qtc.ssh中的各种打印信息
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
|
||||
mParams.host="ftb.autoio.org";
|
||||
mParams.userName = "ftb";
|
||||
mParams.port = 11122;
|
||||
|
||||
mParams.privateKeyFile = QDir::homePath() + QStringLiteral("/.ssh/id_rsa");
|
||||
mParams.timeout = 5;
|
||||
mParams.authenticationType = SshConnectionParameters::AuthenticationTypePublicKey;
|
||||
mParams.options = SshIgnoreDefaultProxy;
|
||||
mParams.hostKeyCheckingMode = SshHostKeyCheckingNone;
|
||||
|
||||
mConnections = std::make_shared<SshConnection>(mParams);
|
||||
connect(mConnections.get(), &SshConnection::error, [&](QSsh::SshError){
|
||||
qWarning() << "Error: " << mConnections->errorString();
|
||||
});
|
||||
connect(mConnections.get(), &SshConnection::connected, [&](){
|
||||
qWarning() << "Connected";
|
||||
create();
|
||||
});
|
||||
connect(mConnections.get(), &SshConnection::disconnected, [](){
|
||||
qWarning() << "Disconnected";
|
||||
});
|
||||
connect(mConnections.get(), &SshConnection::dataAvailable, [](const QString &message){
|
||||
qWarning() << "Message: " << message;
|
||||
});
|
||||
}
|
||||
|
||||
void Ssh::connectToHost() {
|
||||
mConnections->connectToHost();
|
||||
}
|
||||
|
||||
void Ssh::create() {
|
||||
mRemoteProcess = mConnections->createRemoteProcess(QString::fromLatin1("/bin/ls -a").toUtf8());
|
||||
if (!mRemoteProcess) {
|
||||
qWarning() << QLatin1String("Error: UnmRemoteProcess SSH connection creates remote process.");
|
||||
return;
|
||||
}
|
||||
connect(mRemoteProcess.data(), &SshRemoteProcess::started, [&](){
|
||||
qWarning() << "started";
|
||||
});
|
||||
connect(mRemoteProcess.data(), &SshRemoteProcess::readyReadStandardOutput, [&](){
|
||||
qWarning() << "StandardOutput";
|
||||
qWarning() << QString::fromLatin1(mRemoteProcess->readAllStandardOutput()).split('\n');
|
||||
});
|
||||
connect(mRemoteProcess.data(), &SshRemoteProcess::readyReadStandardError, [&](){
|
||||
qWarning() << "StandardError" << mRemoteProcess->readAllStandardError();
|
||||
});
|
||||
connect(mRemoteProcess.data(), &SshRemoteProcess::closed, [&](int exitStatus){
|
||||
qWarning() << "Exit" << exitStatus;
|
||||
});
|
||||
mRemoteProcess->start();
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtSsh/sshconnection.h>
|
||||
#include <QtSsh/sshremoteprocess.h>
|
||||
|
||||
using namespace QSsh;
|
||||
|
||||
class Ssh : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Ssh(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void connectToHost();
|
||||
void create();
|
||||
|
||||
private:
|
||||
SshConnectionParameters mParams;
|
||||
std::shared_ptr<SshConnection> mConnections;
|
||||
QSharedPointer<SshRemoteProcess> mRemoteProcess;
|
||||
};
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
QT += quick network ssh
|
||||
CONFIG += c++11
|
||||
TEMPLATE = app
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which as been marked deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
#DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += Src/Main.cpp \
|
||||
Src/Ssh.cpp
|
||||
|
||||
RESOURCES += Qml.qrc
|
||||
|
||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||
QML_IMPORT_PATH =
|
||||
|
||||
# Additional import path used to resolve QML modules just for Qt Quick Designer
|
||||
QML_DESIGNER_IMPORT_PATH =
|
||||
|
||||
HEADERS += \
|
||||
Src/Ssh.hpp
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
INCLUDEPATH *= $$PWD/..
|
||||
HEADERS += $$PWD/botan.h
|
||||
|
||||
SOURCES += $$PWD/botan.cpp
|
||||
|
||||
CONFIG += exceptions
|
||||
|
||||
DEPENDPATH += .
|
||||
|
||||
DEFINES += BOTAN_DLL=
|
||||
unix:DEFINES += BOTAN_TARGET_OS_HAS_GETTIMEOFDAY BOTAN_HAS_ALLOC_MMAP \
|
||||
BOTAN_HAS_ENTROPY_SRC_DEV_RANDOM BOTAN_HAS_ENTROPY_SRC_EGD BOTAN_HAS_ENTROPY_SRC_FTW \
|
||||
BOTAN_HAS_ENTROPY_SRC_UNIX BOTAN_HAS_MUTEX_PTHREAD BOTAN_HAS_PIPE_UNIXFD_IO
|
||||
*linux*:DEFINES += BOTAN_TARGET_OS_IS_LINUX BOTAN_TARGET_OS_HAS_CLOCK_GETTIME \
|
||||
BOTAN_TARGET_OS_HAS_DLOPEN BOTAN_TARGET_OS_HAS_GMTIME_R BOTAN_TARGET_OS_HAS_POSIX_MLOCK \
|
||||
BOTAN_HAS_DYNAMICALLY_LOADED_ENGINE BOTAN_HAS_DYNAMIC_LOADER
|
||||
macx:DEFINES += BOTAN_TARGET_OS_IS_DARWIN
|
||||
*g++*:DEFINES += BOTAN_BUILD_COMPILER_IS_GCC
|
||||
*clang*:DEFINES += BOTAN_BUILD_COMPILER_IS_CLANG
|
||||
*icc*:DEFINES += BOTAN_BUILD_COMPILER_IS_INTEL
|
||||
|
||||
CONFIG(x86_64):DEFINES += BOTAN_TARGET_ARCH_IS_X86_64
|
||||
|
||||
win32 {
|
||||
DEFINES += BOTAN_TARGET_OS_IS_WINDOWS \
|
||||
BOTAN_TARGET_OS_HAS_LOADLIBRARY BOTAN_TARGET_OS_HAS_WIN32_GET_SYSTEMTIME \
|
||||
BOTAN_TARGET_OS_HAS_WIN32_VIRTUAL_LOCK \
|
||||
BOTAN_HAS_ENTROPY_SRC_CAPI BOTAN_HAS_ENTROPY_SRC_WIN32 \
|
||||
BOTAN_HAS_MUTEX_WIN32
|
||||
|
||||
msvc {
|
||||
QMAKE_CXXFLAGS_EXCEPTIONS_ON = -EHs
|
||||
QMAKE_CXXFLAGS += -wd4251 -wd4290 -wd4250 -wd4297 -wd4267 -wd4334
|
||||
DEFINES += BOTAN_BUILD_COMPILER_IS_MSVC BOTAN_TARGET_OS_HAS_GMTIME_S _SCL_SECURE_NO_WARNINGS
|
||||
} else {
|
||||
QMAKE_CFLAGS += -fpermissive -finline-functions -Wno-long-long
|
||||
QMAKE_CXXFLAGS += -fpermissive -finline-functions -Wno-long-long
|
||||
}
|
||||
LIBS += -ladvapi32 -luser32
|
||||
}
|
||||
|
||||
unix:*-g++* {
|
||||
QMAKE_CFLAGS += -fPIC -fpermissive -finline-functions -Wno-long-long
|
||||
QMAKE_CXXFLAGS += -fPIC -fpermissive -finline-functions -Wno-long-long
|
||||
}
|
||||
|
||||
linux*|freebsd* {
|
||||
LIBS += -lrt $$QMAKE_LIBS_DYNLOAD
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
|
||||
.. _license:
|
||||
.. highlight:: none
|
||||
|
||||
License
|
||||
========================================
|
||||
|
||||
Botan (http://botan.randombit.net/) is distributed under these terms::
|
||||
|
||||
Copyright (C) 1999-2011 Jack Lloyd
|
||||
2001 Peter J Jones
|
||||
2004-2007 Justin Karneges
|
||||
2004 Vaclav Ovsik
|
||||
2005 Matthew Gregan
|
||||
2005-2006 Matt Johnston
|
||||
2006 Luca Piccarreta
|
||||
2007 Yves Jerschow
|
||||
2007-2008 FlexSecure GmbH
|
||||
2007-2008 Technische Universitat Darmstadt
|
||||
2007-2008 Falko Strenzke
|
||||
2007-2008 Martin Doering
|
||||
2007 Manuel Hartl
|
||||
2007 Christoph Ludwig
|
||||
2007 Patrick Sona
|
||||
2010 Olivier de Gaalon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions, and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "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 AUTHOR(S) OR CONTRIBUTOR(S) 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,15 +0,0 @@
|
||||
Botan 1.10.2, 2012-06-17
|
||||
http://botan.randombit.net/
|
||||
|
||||
Botan is a C++ class library for performing a wide variety of
|
||||
cryptographic operations. It is released under the 2 clause BSD
|
||||
license; see doc/license.txt for the specifics. You can file bugs in
|
||||
Bugzilla (http://bugs.randombit.net/) or by sending a report to the
|
||||
botan-devel mailing list. More information about the mailing list is
|
||||
at http://lists.randombit.net/mailman/listinfo/botan-devel/
|
||||
|
||||
You can find documentation online at http://botan.randombit.net/ as
|
||||
well as in the doc directory in the distribution. Several examples can
|
||||
be found in doc/examples as well.
|
||||
|
||||
Jack Lloyd (lloyd@randombit.net)
|
||||
@@ -1,3 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS = ssh
|
||||
@@ -1,972 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpchannel.h"
|
||||
#include "sftpchannel_p.h"
|
||||
|
||||
#include "sshexception_p.h"
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
||||
/*!
|
||||
\class QSsh::SftpChannel
|
||||
|
||||
\brief The SftpChannel class provides SFTP operations.
|
||||
|
||||
Objects are created via SshConnection::createSftpChannel().
|
||||
The channel needs to be initialized with
|
||||
a call to initialize() and is closed via closeChannel(). After closing
|
||||
a channel, no more operations are possible. It cannot be re-opened
|
||||
using initialize(); use SshConnection::createSftpChannel() if you need
|
||||
a new one.
|
||||
|
||||
After the initialized() signal has been emitted, operations can be started.
|
||||
All SFTP operations are asynchronous (non-blocking) and can be in-flight
|
||||
simultaneously (though callers must ensure that concurrently running jobs
|
||||
are independent of each other, e.g. they must not write to the same file).
|
||||
Operations are identified by their job id, which is returned by
|
||||
the respective member function. If the function can right away detect that
|
||||
the operation cannot succeed, it returns SftpInvalidJob. If an error occurs
|
||||
later, the finished() signal is emitted for the respective job with a
|
||||
non-empty error string.
|
||||
|
||||
Note that directory names must not have a trailing slash.
|
||||
*/
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
namespace {
|
||||
const quint32 ProtocolVersion = 3;
|
||||
|
||||
QString errorMessage(const QString &serverMessage,
|
||||
const QString &alternativeMessage)
|
||||
{
|
||||
return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
|
||||
}
|
||||
|
||||
QString errorMessage(const SftpStatusResponse &response,
|
||||
const QString &alternativeMessage)
|
||||
{
|
||||
return response.status == SSH_FX_OK ? QString()
|
||||
: errorMessage(response.errorString, alternativeMessage);
|
||||
}
|
||||
} // anonymous namespace
|
||||
} // namespace Internal
|
||||
|
||||
SftpChannel::SftpChannel(quint32 channelId,
|
||||
Internal::SshSendFacility &sendFacility)
|
||||
: d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
|
||||
{
|
||||
connect(d, &Internal::SftpChannelPrivate::initialized,
|
||||
this, &SftpChannel::initialized, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SftpChannelPrivate::channelError,
|
||||
this, &SftpChannel::channelError, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SftpChannelPrivate::dataAvailable,
|
||||
this, &SftpChannel::dataAvailable, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SftpChannelPrivate::fileInfoAvailable,
|
||||
this, &SftpChannel::fileInfoAvailable, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SftpChannelPrivate::finished,
|
||||
this, &SftpChannel::finished, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SftpChannelPrivate::closed,
|
||||
this, &SftpChannel::closed, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
SftpChannel::State SftpChannel::state() const
|
||||
{
|
||||
switch (d->channelState()) {
|
||||
case Internal::AbstractSshChannel::Inactive:
|
||||
return Uninitialized;
|
||||
case Internal::AbstractSshChannel::SessionRequested:
|
||||
return Initializing;
|
||||
case Internal::AbstractSshChannel::CloseRequested:
|
||||
return Closing;
|
||||
case Internal::AbstractSshChannel::Closed:
|
||||
return Closed;
|
||||
case Internal::AbstractSshChannel::SessionEstablished:
|
||||
return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
|
||||
? Initialized : Initializing;
|
||||
default:
|
||||
Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
|
||||
return Closed; // For the compiler.
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannel::initialize()
|
||||
{
|
||||
d->requestSessionStart();
|
||||
d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
|
||||
}
|
||||
|
||||
void SftpChannel::closeChannel()
|
||||
{
|
||||
d->closeChannel();
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::statFile(const QString &path)
|
||||
{
|
||||
return d->createJob(Internal::SftpStatFile::Ptr(
|
||||
new Internal::SftpStatFile(++d->m_nextJobId, path)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::listDirectory(const QString &path)
|
||||
{
|
||||
return d->createJob(Internal::SftpListDir::Ptr(
|
||||
new Internal::SftpListDir(++d->m_nextJobId, path)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::createDirectory(const QString &path)
|
||||
{
|
||||
return d->createJob(Internal::SftpMakeDir::Ptr(
|
||||
new Internal::SftpMakeDir(++d->m_nextJobId, path)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::removeDirectory(const QString &path)
|
||||
{
|
||||
return d->createJob(Internal::SftpRmDir::Ptr(
|
||||
new Internal::SftpRmDir(++d->m_nextJobId, path)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::removeFile(const QString &path)
|
||||
{
|
||||
return d->createJob(Internal::SftpRm::Ptr(
|
||||
new Internal::SftpRm(++d->m_nextJobId, path)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
|
||||
const QString &newPath)
|
||||
{
|
||||
return d->createJob(Internal::SftpRename::Ptr(
|
||||
new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::createLink(const QString &filePath, const QString &target)
|
||||
{
|
||||
return d->createJob(Internal::SftpCreateLink::Ptr(
|
||||
new Internal::SftpCreateLink(++d->m_nextJobId, filePath, target)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
|
||||
{
|
||||
return d->createJob(Internal::SftpCreateFile::Ptr(
|
||||
new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
|
||||
const QString &remoteFilePath, SftpOverwriteMode mode)
|
||||
{
|
||||
QSharedPointer<QFile> localFile(new QFile(localFilePath));
|
||||
if (!localFile->open(QIODevice::ReadOnly))
|
||||
return SftpInvalidJob;
|
||||
return d->createJob(Internal::SftpUploadFile::Ptr(
|
||||
new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
|
||||
const QString &localFilePath, SftpOverwriteMode mode)
|
||||
{
|
||||
QSharedPointer<QFile> localFile(new QFile(localFilePath));
|
||||
if (mode == SftpSkipExisting && localFile->exists())
|
||||
return SftpInvalidJob;
|
||||
QIODevice::OpenMode openMode = QIODevice::WriteOnly;
|
||||
if (mode == SftpOverwriteExisting)
|
||||
openMode |= QIODevice::Truncate;
|
||||
else if (mode == SftpAppendToExisting)
|
||||
openMode |= QIODevice::Append;
|
||||
if (!localFile->open(openMode))
|
||||
return SftpInvalidJob;
|
||||
return d->createJob(Internal::SftpDownload::Ptr(
|
||||
new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile)));
|
||||
}
|
||||
|
||||
SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
|
||||
const QString &remoteParentDirPath)
|
||||
{
|
||||
if (state() != Initialized)
|
||||
return SftpInvalidJob;
|
||||
const QDir localDir(localDirPath);
|
||||
if (!localDir.exists() || !localDir.isReadable())
|
||||
return SftpInvalidJob;
|
||||
const Internal::SftpUploadDir::Ptr uploadDirOp(
|
||||
new Internal::SftpUploadDir(++d->m_nextJobId));
|
||||
const QString remoteDirPath
|
||||
= remoteParentDirPath + QLatin1Char('/') + localDir.dirName();
|
||||
const Internal::SftpMakeDir::Ptr mkdirOp(
|
||||
new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp));
|
||||
uploadDirOp->mkdirsInProgress.insert(mkdirOp,
|
||||
Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath));
|
||||
d->createJob(mkdirOp);
|
||||
return uploadDirOp->jobId;
|
||||
}
|
||||
|
||||
SftpChannel::~SftpChannel()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
|
||||
namespace Internal {
|
||||
|
||||
SftpChannelPrivate::SftpChannelPrivate(quint32 channelId,
|
||||
SshSendFacility &sendFacility, SftpChannel *sftp)
|
||||
: AbstractSshChannel(channelId, sendFacility),
|
||||
m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp)
|
||||
{
|
||||
}
|
||||
|
||||
SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job)
|
||||
{
|
||||
if (m_sftp->state() != SftpChannel::Initialized)
|
||||
return SftpInvalidJob;
|
||||
m_jobs.insert(job->jobId, job);
|
||||
sendData(job->initialPacket(m_outgoingPacket).rawData());
|
||||
return job->jobId;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleChannelSuccess()
|
||||
{
|
||||
if (channelState() == CloseRequested)
|
||||
return;
|
||||
qCDebug(sshLog, "sftp subsystem initialized");
|
||||
sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
|
||||
m_sftpState = InitSent;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleChannelFailure()
|
||||
{
|
||||
if (channelState() == CloseRequested)
|
||||
return;
|
||||
|
||||
if (m_sftpState != SubsystemRequested) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
|
||||
}
|
||||
emit channelError(tr("Server could not start SFTP subsystem."));
|
||||
closeChannel();
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
|
||||
{
|
||||
if (channelState() == CloseRequested)
|
||||
return;
|
||||
|
||||
m_incomingData += data;
|
||||
m_incomingPacket.consumeData(m_incomingData);
|
||||
while (m_incomingPacket.isComplete()) {
|
||||
handleCurrentPacket();
|
||||
m_incomingPacket.clear();
|
||||
m_incomingPacket.consumeData(m_incomingData);
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
|
||||
const QByteArray &data)
|
||||
{
|
||||
qCWarning(sshLog, "Unexpected extended data '%s' of type %d on SFTP channel.",
|
||||
data.data(), type);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
|
||||
{
|
||||
qCDebug(sshLog, "Remote SFTP service exited with exit code %d", exitStatus.exitStatus);
|
||||
|
||||
if (channelState() == CloseRequested || channelState() == Closed)
|
||||
return;
|
||||
|
||||
emit channelError(tr("The SFTP server finished unexpectedly with exit code %1.")
|
||||
.arg(exitStatus.exitStatus));
|
||||
|
||||
// Note: According to the specs, the server must close the channel after this happens,
|
||||
// but OpenSSH doesn't do that, so we need to initiate the closing procedure ourselves.
|
||||
closeChannel();
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
|
||||
{
|
||||
emit channelError(tr("The SFTP server crashed: %1.").arg(signal.error));
|
||||
closeChannel(); // See above.
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleCurrentPacket()
|
||||
{
|
||||
qCDebug(sshLog, "Handling SFTP packet of type %d", m_incomingPacket.type());
|
||||
switch (m_incomingPacket.type()) {
|
||||
case SSH_FXP_VERSION:
|
||||
handleServerVersion();
|
||||
break;
|
||||
case SSH_FXP_HANDLE:
|
||||
handleHandle();
|
||||
break;
|
||||
case SSH_FXP_NAME:
|
||||
handleName();
|
||||
break;
|
||||
case SSH_FXP_STATUS:
|
||||
handleStatus();
|
||||
break;
|
||||
case SSH_FXP_DATA:
|
||||
handleReadData();
|
||||
break;
|
||||
case SSH_FXP_ATTRS:
|
||||
handleAttrs();
|
||||
break;
|
||||
default:
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.",
|
||||
tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleServerVersion()
|
||||
{
|
||||
checkChannelActive();
|
||||
if (m_sftpState != InitSent) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_VERSION packet.");
|
||||
}
|
||||
|
||||
qCDebug(sshLog, "sftp init received");
|
||||
const quint32 serverVersion = m_incomingPacket.extractServerVersion();
|
||||
if (serverVersion != ProtocolVersion) {
|
||||
emit channelError(tr("Protocol version mismatch: Expected %1, got %2")
|
||||
.arg(serverVersion).arg(ProtocolVersion));
|
||||
closeChannel();
|
||||
} else {
|
||||
m_sftpState = Initialized;
|
||||
emit initialized();
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleHandle()
|
||||
{
|
||||
const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
|
||||
JobMap::Iterator it = lookupJob(response.requestId);
|
||||
const QSharedPointer<AbstractSftpOperationWithHandle> job
|
||||
= it.value().dynamicCast<AbstractSftpOperationWithHandle>();
|
||||
if (job.isNull()) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_HANDLE packet.");
|
||||
}
|
||||
if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_HANDLE packet.");
|
||||
}
|
||||
job->remoteHandle = response.handle;
|
||||
job->state = AbstractSftpOperationWithHandle::Open;
|
||||
|
||||
switch (it.value()->type()) {
|
||||
case AbstractSftpOperation::ListDir:
|
||||
handleLsHandle(it);
|
||||
break;
|
||||
case AbstractSftpOperation::CreateFile:
|
||||
handleCreateFileHandle(it);
|
||||
break;
|
||||
case AbstractSftpOperation::Download:
|
||||
handleGetHandle(it);
|
||||
break;
|
||||
case AbstractSftpOperation::UploadFile:
|
||||
handlePutHandle(it);
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleLsHandle(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
|
||||
sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleCreateFileHandle(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
|
||||
sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
|
||||
sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
op->statRequested = true;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
|
||||
if (op->parentJob && op->parentJob->hasError)
|
||||
sendTransferCloseHandle(op, it.key());
|
||||
|
||||
// OpenSSH does not implement the RFC's append functionality, so we
|
||||
// have to emulate it.
|
||||
if (op->mode == SftpAppendToExisting) {
|
||||
sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
op->statRequested = true;
|
||||
} else {
|
||||
spawnWriteRequests(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleStatus()
|
||||
{
|
||||
const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
|
||||
qCDebug(sshLog, "%s: status = %d", Q_FUNC_INFO, response.status);
|
||||
JobMap::Iterator it = lookupJob(response.requestId);
|
||||
switch (it.value()->type()) {
|
||||
case AbstractSftpOperation::ListDir:
|
||||
handleLsStatus(it, response);
|
||||
break;
|
||||
case AbstractSftpOperation::Download:
|
||||
handleGetStatus(it, response);
|
||||
break;
|
||||
case AbstractSftpOperation::UploadFile:
|
||||
handlePutStatus(it, response);
|
||||
break;
|
||||
case AbstractSftpOperation::MakeDir:
|
||||
handleMkdirStatus(it, response);
|
||||
break;
|
||||
case AbstractSftpOperation::StatFile:
|
||||
case AbstractSftpOperation::RmDir:
|
||||
case AbstractSftpOperation::Rm:
|
||||
case AbstractSftpOperation::Rename:
|
||||
case AbstractSftpOperation::CreateFile:
|
||||
case AbstractSftpOperation::CreateLink:
|
||||
handleStatusGeneric(it, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response)
|
||||
{
|
||||
AbstractSftpOperation::Ptr op = it.value();
|
||||
const QString error = errorMessage(response, tr("Unknown error."));
|
||||
emit finished(op->jobId, error);
|
||||
m_jobs.erase(it);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response)
|
||||
{
|
||||
SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
|
||||
QSharedPointer<SftpUploadDir> parentJob = op->parentJob;
|
||||
if (parentJob == SftpUploadDir::Ptr()) {
|
||||
handleStatusGeneric(it, response);
|
||||
return;
|
||||
}
|
||||
if (parentJob->hasError) {
|
||||
m_jobs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
|
||||
DirIt dirIt = parentJob->mkdirsInProgress.find(op);
|
||||
Q_ASSERT(dirIt != parentJob->mkdirsInProgress.end());
|
||||
const QString &remoteDir = dirIt.value().remoteDir;
|
||||
if (response.status == SSH_FX_OK) {
|
||||
emit dataAvailable(parentJob->jobId,
|
||||
tr("Created remote directory \"%1\".").arg(remoteDir));
|
||||
} else if (response.status == SSH_FX_FAILURE) {
|
||||
emit dataAvailable(parentJob->jobId,
|
||||
tr("Remote directory \"%1\" already exists.").arg(remoteDir));
|
||||
} else {
|
||||
parentJob->setError();
|
||||
emit finished(parentJob->jobId,
|
||||
tr("Error creating directory \"%1\": %2")
|
||||
.arg(remoteDir, response.errorString));
|
||||
m_jobs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
QDir localDir(dirIt.value().localDir);
|
||||
const QFileInfoList &dirInfos
|
||||
= localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QFileInfo &dirInfo, dirInfos) {
|
||||
const QString remoteSubDir = remoteDir + QLatin1Char('/') + dirInfo.fileName();
|
||||
const SftpMakeDir::Ptr mkdirOp(
|
||||
new SftpMakeDir(++m_nextJobId, remoteSubDir, parentJob));
|
||||
parentJob->mkdirsInProgress.insert(mkdirOp,
|
||||
SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
|
||||
createJob(mkdirOp);
|
||||
}
|
||||
|
||||
const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
|
||||
foreach (const QFileInfo &fileInfo, fileInfos) {
|
||||
QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
|
||||
if (!localFile->open(QIODevice::ReadOnly)) {
|
||||
parentJob->setError();
|
||||
emit finished(parentJob->jobId,
|
||||
tr("Could not open local file \"%1\": %2")
|
||||
.arg(fileInfo.absoluteFilePath(), localFile->errorString()));
|
||||
m_jobs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString remoteFilePath = remoteDir + QLatin1Char('/') + fileInfo.fileName();
|
||||
SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
|
||||
remoteFilePath, localFile, SftpOverwriteExisting, parentJob));
|
||||
createJob(uploadFileOp);
|
||||
parentJob->uploadsInProgress.append(uploadFileOp);
|
||||
}
|
||||
|
||||
parentJob->mkdirsInProgress.erase(dirIt);
|
||||
if (parentJob->mkdirsInProgress.isEmpty()
|
||||
&& parentJob->uploadsInProgress.isEmpty())
|
||||
emit finished(parentJob->jobId);
|
||||
m_jobs.erase(it);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response)
|
||||
{
|
||||
SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
|
||||
switch (op->state) {
|
||||
case SftpListDir::OpenRequested:
|
||||
emit finished(op->jobId, errorMessage(response.errorString,
|
||||
tr("Remote directory could not be opened for reading.")));
|
||||
m_jobs.erase(it);
|
||||
break;
|
||||
case SftpListDir::Open:
|
||||
if (response.status != SSH_FX_EOF)
|
||||
reportRequestError(op, errorMessage(response.errorString,
|
||||
tr("Failed to list remote directory contents.")));
|
||||
op->state = SftpListDir::CloseRequested;
|
||||
sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
break;
|
||||
case SftpListDir::CloseRequested:
|
||||
if (!op->hasError) {
|
||||
const QString error = errorMessage(response,
|
||||
tr("Failed to close remote directory."));
|
||||
emit finished(op->jobId, error);
|
||||
}
|
||||
m_jobs.erase(it);
|
||||
break;
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_STATUS packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response)
|
||||
{
|
||||
SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
|
||||
switch (op->state) {
|
||||
case SftpDownload::OpenRequested:
|
||||
emit finished(op->jobId,
|
||||
errorMessage(response.errorString,
|
||||
tr("Failed to open remote file for reading.")));
|
||||
m_jobs.erase(it);
|
||||
break;
|
||||
case SftpDownload::Open:
|
||||
if (op->statRequested) {
|
||||
reportRequestError(op, errorMessage(response.errorString,
|
||||
tr("Failed to retrieve information on the remote file ('stat' failed).")));
|
||||
sendTransferCloseHandle(op, response.requestId);
|
||||
} else {
|
||||
if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
|
||||
&& !op->hasError)
|
||||
reportRequestError(op, errorMessage(response.errorString,
|
||||
tr("Failed to read remote file.")));
|
||||
finishTransferRequest(it);
|
||||
}
|
||||
break;
|
||||
case SftpDownload::CloseRequested:
|
||||
Q_ASSERT(op->inFlightCount == 1);
|
||||
if (!op->hasError) {
|
||||
if (response.status == SSH_FX_OK)
|
||||
emit finished(op->jobId);
|
||||
else
|
||||
reportRequestError(op, errorMessage(response.errorString,
|
||||
tr("Failed to close remote file.")));
|
||||
}
|
||||
removeTransferRequest(it);
|
||||
break;
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_STATUS packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response)
|
||||
{
|
||||
SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
|
||||
switch (job->state) {
|
||||
case SftpUploadFile::OpenRequested: {
|
||||
bool emitError = false;
|
||||
if (job->parentJob) {
|
||||
if (!job->parentJob->hasError) {
|
||||
job->parentJob->setError();
|
||||
emitError = true;
|
||||
}
|
||||
} else {
|
||||
emitError = true;
|
||||
}
|
||||
|
||||
if (emitError) {
|
||||
emit finished(job->jobId,
|
||||
errorMessage(response.errorString,
|
||||
tr("Failed to open remote file for writing.")));
|
||||
}
|
||||
m_jobs.erase(it);
|
||||
break;
|
||||
}
|
||||
case SftpUploadFile::Open:
|
||||
if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
|
||||
job->hasError = true;
|
||||
finishTransferRequest(it);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == SSH_FX_OK) {
|
||||
sendWriteRequest(it);
|
||||
} else {
|
||||
if (job->parentJob)
|
||||
job->parentJob->setError();
|
||||
reportRequestError(job, errorMessage(response.errorString,
|
||||
tr("Failed to write remote file.")));
|
||||
finishTransferRequest(it);
|
||||
}
|
||||
break;
|
||||
case SftpUploadFile::CloseRequested:
|
||||
Q_ASSERT(job->inFlightCount == 1);
|
||||
if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
|
||||
m_jobs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == SSH_FX_OK) {
|
||||
if (job->parentJob) {
|
||||
job->parentJob->uploadsInProgress.removeOne(job);
|
||||
if (job->parentJob->mkdirsInProgress.isEmpty()
|
||||
&& job->parentJob->uploadsInProgress.isEmpty())
|
||||
emit finished(job->parentJob->jobId);
|
||||
} else {
|
||||
emit finished(job->jobId);
|
||||
}
|
||||
} else {
|
||||
const QString error = errorMessage(response.errorString,
|
||||
tr("Failed to close remote file."));
|
||||
if (job->parentJob) {
|
||||
job->parentJob->setError();
|
||||
emit finished(job->parentJob->jobId, error);
|
||||
} else {
|
||||
emit finished(job->jobId, error);
|
||||
}
|
||||
}
|
||||
m_jobs.erase(it);
|
||||
break;
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_STATUS packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleName()
|
||||
{
|
||||
const SftpNameResponse &response = m_incomingPacket.asNameResponse();
|
||||
JobMap::Iterator it = lookupJob(response.requestId);
|
||||
switch (it.value()->type()) {
|
||||
case AbstractSftpOperation::ListDir: {
|
||||
SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
|
||||
if (op->state != SftpListDir::Open) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_NAME packet.");
|
||||
}
|
||||
|
||||
QList<SftpFileInfo> fileInfoList;
|
||||
for (int i = 0; i < response.files.count(); ++i) {
|
||||
const SftpFile &file = response.files.at(i);
|
||||
|
||||
SftpFileInfo fileInfo;
|
||||
fileInfo.name = file.fileName;
|
||||
attributesToFileInfo(file.attributes, fileInfo);
|
||||
fileInfoList << fileInfo;
|
||||
}
|
||||
emit fileInfoAvailable(op->jobId, fileInfoList);
|
||||
sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
|
||||
op->jobId).rawData());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_NAME packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleReadData()
|
||||
{
|
||||
const SftpDataResponse &response = m_incomingPacket.asDataResponse();
|
||||
JobMap::Iterator it = lookupJob(response.requestId);
|
||||
if (it.value()->type() != AbstractSftpOperation::Download) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_DATA packet.");
|
||||
}
|
||||
|
||||
SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
|
||||
if (op->hasError) {
|
||||
finishTransferRequest(it);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!op->localFile->seek(op->offsets[response.requestId])) {
|
||||
reportRequestError(op, op->localFile->errorString());
|
||||
finishTransferRequest(it);
|
||||
return;
|
||||
}
|
||||
|
||||
if (op->localFile->write(response.data) != response.data.size()) {
|
||||
reportRequestError(op, op->localFile->errorString());
|
||||
finishTransferRequest(it);
|
||||
return;
|
||||
}
|
||||
|
||||
if (op->offset >= op->fileSize && op->fileSize != 0)
|
||||
finishTransferRequest(it);
|
||||
else
|
||||
sendReadRequest(op, response.requestId);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleAttrs()
|
||||
{
|
||||
const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse();
|
||||
JobMap::Iterator it = lookupJob(response.requestId);
|
||||
|
||||
SftpStatFile::Ptr statOp = it.value().dynamicCast<SftpStatFile>();
|
||||
if (statOp) {
|
||||
SftpFileInfo fileInfo;
|
||||
fileInfo.name = QFileInfo(statOp->path).fileName();
|
||||
attributesToFileInfo(response.attrs, fileInfo);
|
||||
emit fileInfoAvailable(it.key(), QList<SftpFileInfo>() << fileInfo);
|
||||
emit finished(it.key());
|
||||
m_jobs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractSftpTransfer::Ptr transfer
|
||||
= it.value().dynamicCast<AbstractSftpTransfer>();
|
||||
if (!transfer || transfer->state != AbstractSftpTransfer::Open
|
||||
|| !transfer->statRequested) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_FXP_ATTRS packet.");
|
||||
}
|
||||
Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
|
||||
|| transfer->type() == AbstractSftpOperation::Download);
|
||||
|
||||
if (transfer->type() == AbstractSftpOperation::Download) {
|
||||
SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
|
||||
if (response.attrs.sizePresent) {
|
||||
op->fileSize = response.attrs.size;
|
||||
} else {
|
||||
op->fileSize = 0;
|
||||
op->eofId = op->jobId;
|
||||
}
|
||||
op->statRequested = false;
|
||||
spawnReadRequests(op);
|
||||
} else {
|
||||
SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
|
||||
if (op->parentJob && op->parentJob->hasError) {
|
||||
op->hasError = true;
|
||||
sendTransferCloseHandle(op, op->jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.attrs.sizePresent) {
|
||||
op->offset = response.attrs.size;
|
||||
spawnWriteRequests(it);
|
||||
} else {
|
||||
if (op->parentJob)
|
||||
op->parentJob->setError();
|
||||
reportRequestError(op, tr("Cannot append to remote file: "
|
||||
"Server does not support the file size attribute."));
|
||||
sendTransferCloseHandle(op, op->jobId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id)
|
||||
{
|
||||
JobMap::Iterator it = m_jobs.find(id);
|
||||
if (it == m_jobs.end()) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid request id in SFTP packet.");
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::closeHook()
|
||||
{
|
||||
for (JobMap::ConstIterator it = m_jobs.constBegin(); it != m_jobs.constEnd(); ++it)
|
||||
emit finished(it.key(), tr("SFTP channel closed unexpectedly."));
|
||||
m_jobs.clear();
|
||||
m_incomingData.clear();
|
||||
m_incomingPacket.clear();
|
||||
emit closed();
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleOpenSuccessInternal()
|
||||
{
|
||||
qCDebug(sshLog, "SFTP session started");
|
||||
m_sendFacility.sendSftpPacket(remoteChannel());
|
||||
m_sftpState = SubsystemRequested;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
|
||||
{
|
||||
if (channelState() != SessionRequested) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
|
||||
}
|
||||
emit channelError(tr("Server could not start session: %1").arg(reason));
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
|
||||
quint32 requestId)
|
||||
{
|
||||
Q_ASSERT(job->eofId == SftpInvalidJob);
|
||||
sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
|
||||
AbstractSftpPacket::MaxDataSize, requestId).rawData());
|
||||
job->offsets[requestId] = job->offset;
|
||||
job->offset += AbstractSftpPacket::MaxDataSize;
|
||||
if (job->offset >= job->fileSize)
|
||||
job->eofId = requestId;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
|
||||
const QString &error)
|
||||
{
|
||||
emit finished(job->jobId, error);
|
||||
job->hasError = true;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::finishTransferRequest(const JobMap::Iterator &it)
|
||||
{
|
||||
AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>();
|
||||
if (job->inFlightCount == 1)
|
||||
sendTransferCloseHandle(job, it.key());
|
||||
else
|
||||
removeTransferRequest(it);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
|
||||
quint32 requestId)
|
||||
{
|
||||
sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle,
|
||||
requestId).rawData());
|
||||
job->state = SftpDownload::CloseRequested;
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::attributesToFileInfo(const SftpFileAttributes &attributes,
|
||||
SftpFileInfo &fileInfo) const
|
||||
{
|
||||
if (attributes.sizePresent) {
|
||||
fileInfo.sizeValid = true;
|
||||
fileInfo.size = attributes.size;
|
||||
}
|
||||
if (attributes.permissionsPresent) {
|
||||
if (attributes.permissions & 0x8000) // S_IFREG
|
||||
fileInfo.type = FileTypeRegular;
|
||||
else if (attributes.permissions & 0x4000) // S_IFDIR
|
||||
fileInfo.type = FileTypeDirectory;
|
||||
else
|
||||
fileInfo.type = FileTypeOther;
|
||||
fileInfo.permissionsValid = true;
|
||||
fileInfo.permissions = 0;
|
||||
if (attributes.permissions & 00001) // S_IXOTH
|
||||
fileInfo.permissions |= QFile::ExeOther;
|
||||
if (attributes.permissions & 00002) // S_IWOTH
|
||||
fileInfo.permissions |= QFile::WriteOther;
|
||||
if (attributes.permissions & 00004) // S_IROTH
|
||||
fileInfo.permissions |= QFile::ReadOther;
|
||||
if (attributes.permissions & 00010) // S_IXGRP
|
||||
fileInfo.permissions |= QFile::ExeGroup;
|
||||
if (attributes.permissions & 00020) // S_IWGRP
|
||||
fileInfo.permissions |= QFile::WriteGroup;
|
||||
if (attributes.permissions & 00040) // S_IRGRP
|
||||
fileInfo.permissions |= QFile::ReadGroup;
|
||||
if (attributes.permissions & 00100) // S_IXUSR
|
||||
fileInfo.permissions |= QFile::ExeUser | QFile::ExeOwner;
|
||||
if (attributes.permissions & 00200) // S_IWUSR
|
||||
fileInfo.permissions |= QFile::WriteUser | QFile::WriteOwner;
|
||||
if (attributes.permissions & 00400) // S_IRUSR
|
||||
fileInfo.permissions |= QFile::ReadUser | QFile::ReadOwner;
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it)
|
||||
{
|
||||
--it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
|
||||
m_jobs.erase(it);
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
|
||||
QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
|
||||
if (job->localFile->error() != QFile::NoError) {
|
||||
if (job->parentJob)
|
||||
job->parentJob->setError();
|
||||
reportRequestError(job, tr("Error reading local file: %1")
|
||||
.arg(job->localFile->errorString()));
|
||||
finishTransferRequest(it);
|
||||
} else if (data.isEmpty()) {
|
||||
finishTransferRequest(it);
|
||||
} else {
|
||||
sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle,
|
||||
job->offset, data, it.key()).rawData());
|
||||
job->offset += AbstractSftpPacket::MaxDataSize;
|
||||
}
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it)
|
||||
{
|
||||
SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
|
||||
op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
|
||||
sendWriteRequest(it);
|
||||
for (int i = 1; !op->hasError && i < op->inFlightCount; ++i)
|
||||
sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
|
||||
}
|
||||
|
||||
void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
|
||||
{
|
||||
job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
|
||||
sendReadRequest(job, job->jobId);
|
||||
for (int i = 1; i < job->inFlightCount; ++i) {
|
||||
const quint32 requestId = ++m_nextJobId;
|
||||
m_jobs.insert(requestId, job);
|
||||
sendReadRequest(job, requestId);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,103 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftpdefs.h"
|
||||
#include "sftpincomingpacket_p.h"
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
namespace Internal {
|
||||
class SftpChannelPrivate;
|
||||
class SshChannelManager;
|
||||
class SshSendFacility;
|
||||
} // namespace Internal
|
||||
|
||||
class QSSH_EXPORT SftpChannel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class Internal::SftpChannelPrivate;
|
||||
friend class Internal::SshChannelManager;
|
||||
public:
|
||||
typedef QSharedPointer<SftpChannel> Ptr;
|
||||
|
||||
enum State { Uninitialized, Initializing, Initialized, Closing, Closed };
|
||||
State state() const;
|
||||
|
||||
void initialize();
|
||||
void closeChannel();
|
||||
|
||||
SftpJobId statFile(const QString &path);
|
||||
SftpJobId listDirectory(const QString &dirPath);
|
||||
SftpJobId createDirectory(const QString &dirPath);
|
||||
SftpJobId removeDirectory(const QString &dirPath);
|
||||
SftpJobId removeFile(const QString &filePath);
|
||||
SftpJobId renameFileOrDirectory(const QString &oldPath,
|
||||
const QString &newPath);
|
||||
SftpJobId createFile(const QString &filePath, SftpOverwriteMode mode);
|
||||
SftpJobId createLink(const QString &filePath, const QString &target);
|
||||
SftpJobId uploadFile(const QString &localFilePath,
|
||||
const QString &remoteFilePath, SftpOverwriteMode mode);
|
||||
SftpJobId downloadFile(const QString &remoteFilePath,
|
||||
const QString &localFilePath, SftpOverwriteMode mode);
|
||||
SftpJobId uploadDir(const QString &localDirPath,
|
||||
const QString &remoteParentDirPath);
|
||||
|
||||
~SftpChannel();
|
||||
|
||||
signals:
|
||||
void initialized();
|
||||
void channelError(const QString &reason);
|
||||
void closed();
|
||||
|
||||
// error.isEmpty <=> finished successfully
|
||||
void finished(QSsh::SftpJobId job, const QString &error = QString());
|
||||
|
||||
// TODO: Also emit for each file copied by uploadDir().
|
||||
void dataAvailable(QSsh::SftpJobId job, const QString &data);
|
||||
|
||||
/*
|
||||
* This signal is emitted as a result of:
|
||||
* - statFile() (with the list having exactly one element)
|
||||
* - listDirectory() (potentially more than once)
|
||||
*/
|
||||
void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
|
||||
|
||||
private:
|
||||
SftpChannel(quint32 channelId, Internal::SshSendFacility &sendFacility);
|
||||
|
||||
Internal::SftpChannelPrivate *d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,124 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftpdefs.h"
|
||||
#include "sftpincomingpacket_p.h"
|
||||
#include "sftpoperation_p.h"
|
||||
#include "sftpoutgoingpacket_p.h"
|
||||
#include "sshchannel_p.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QMap>
|
||||
|
||||
namespace QSsh {
|
||||
class SftpChannel;
|
||||
namespace Internal {
|
||||
|
||||
class SftpChannelPrivate : public AbstractSshChannel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class QSsh::SftpChannel;
|
||||
public:
|
||||
enum SftpState { Inactive, SubsystemRequested, InitSent, Initialized };
|
||||
|
||||
signals:
|
||||
void initialized();
|
||||
void channelError(const QString &reason);
|
||||
void closed();
|
||||
void finished(QSsh::SftpJobId job, const QString &error = QString());
|
||||
void dataAvailable(QSsh::SftpJobId job, const QString &data);
|
||||
void fileInfoAvailable(QSsh::SftpJobId job, const QList<QSsh::SftpFileInfo> &fileInfoList);
|
||||
|
||||
private:
|
||||
typedef QMap<SftpJobId, AbstractSftpOperation::Ptr> JobMap;
|
||||
|
||||
SftpChannelPrivate(quint32 channelId, SshSendFacility &sendFacility,
|
||||
SftpChannel *sftp);
|
||||
SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
|
||||
|
||||
virtual void handleChannelSuccess();
|
||||
virtual void handleChannelFailure();
|
||||
|
||||
virtual void handleOpenSuccessInternal();
|
||||
virtual void handleOpenFailureInternal(const QString &reason);
|
||||
virtual void handleChannelDataInternal(const QByteArray &data);
|
||||
virtual void handleChannelExtendedDataInternal(quint32 type,
|
||||
const QByteArray &data);
|
||||
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus);
|
||||
virtual void handleExitSignal(const SshChannelExitSignal &signal);
|
||||
|
||||
virtual void closeHook();
|
||||
|
||||
void handleCurrentPacket();
|
||||
void handleServerVersion();
|
||||
void handleHandle();
|
||||
void handleStatus();
|
||||
void handleName();
|
||||
void handleReadData();
|
||||
void handleAttrs();
|
||||
|
||||
void handleStatusGeneric(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response);
|
||||
void handleMkdirStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response);
|
||||
void handleLsStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response);
|
||||
void handleGetStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response);
|
||||
void handlePutStatus(const JobMap::Iterator &it,
|
||||
const SftpStatusResponse &response);
|
||||
|
||||
void handleLsHandle(const JobMap::Iterator &it);
|
||||
void handleCreateFileHandle(const JobMap::Iterator &it);
|
||||
void handleGetHandle(const JobMap::Iterator &it);
|
||||
void handlePutHandle(const JobMap::Iterator &it);
|
||||
|
||||
void spawnReadRequests(const SftpDownload::Ptr &job);
|
||||
void spawnWriteRequests(const JobMap::Iterator &it);
|
||||
void sendReadRequest(const SftpDownload::Ptr &job, quint32 requestId);
|
||||
void sendWriteRequest(const JobMap::Iterator &it);
|
||||
void finishTransferRequest(const JobMap::Iterator &it);
|
||||
void removeTransferRequest(const JobMap::Iterator &it);
|
||||
void reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
|
||||
const QString &error);
|
||||
void sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
|
||||
quint32 requestId);
|
||||
|
||||
void attributesToFileInfo(const SftpFileAttributes &attributes, SftpFileInfo &fileInfo) const;
|
||||
|
||||
JobMap::Iterator lookupJob(SftpJobId id);
|
||||
JobMap m_jobs;
|
||||
SftpOutgoingPacket m_outgoingPacket;
|
||||
SftpIncomingPacket m_incomingPacket;
|
||||
QByteArray m_incomingData;
|
||||
SftpJobId m_nextJobId;
|
||||
SftpState m_sftpState;
|
||||
SftpChannel *m_sftp;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,28 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpdefs.h"
|
||||
|
||||
namespace QSsh { const SftpJobId SftpInvalidJob = 0; }
|
||||
@@ -1,59 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
typedef quint32 SftpJobId;
|
||||
QSSH_EXPORT extern const SftpJobId SftpInvalidJob;
|
||||
|
||||
enum SftpOverwriteMode {
|
||||
SftpOverwriteExisting, SftpAppendToExisting, SftpSkipExisting
|
||||
};
|
||||
|
||||
enum SftpFileType { FileTypeRegular, FileTypeDirectory, FileTypeOther, FileTypeUnknown };
|
||||
|
||||
class QSSH_EXPORT SftpFileInfo
|
||||
{
|
||||
public:
|
||||
SftpFileInfo() : type(FileTypeUnknown), sizeValid(false), permissionsValid(false) { }
|
||||
|
||||
QString name;
|
||||
SftpFileType type;
|
||||
quint64 size;
|
||||
QFile::Permissions permissions;
|
||||
|
||||
// The RFC allows an SFTP server not to support any file attributes beyond the name.
|
||||
bool sizeValid;
|
||||
bool permissionsValid;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,383 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpfilesystemmodel.h"
|
||||
|
||||
#include "sftpchannel.h"
|
||||
#include "sshconnection.h"
|
||||
#include "sshconnectionmanager.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
namespace {
|
||||
|
||||
class SftpDirNode;
|
||||
class SftpFileNode
|
||||
{
|
||||
public:
|
||||
SftpFileNode() : parent(0) { }
|
||||
virtual ~SftpFileNode() { }
|
||||
|
||||
QString path;
|
||||
SftpFileInfo fileInfo;
|
||||
SftpDirNode *parent;
|
||||
};
|
||||
|
||||
class SftpDirNode : public SftpFileNode
|
||||
{
|
||||
public:
|
||||
SftpDirNode() : lsState(LsNotYetCalled) { }
|
||||
~SftpDirNode() { qDeleteAll(children); }
|
||||
|
||||
enum { LsNotYetCalled, LsRunning, LsFinished } lsState;
|
||||
QList<SftpFileNode *> children;
|
||||
};
|
||||
|
||||
typedef QHash<SftpJobId, SftpDirNode *> DirNodeHash;
|
||||
|
||||
SftpFileNode *indexToFileNode(const QModelIndex &index)
|
||||
{
|
||||
return static_cast<SftpFileNode *>(index.internalPointer());
|
||||
}
|
||||
|
||||
SftpDirNode *indexToDirNode(const QModelIndex &index)
|
||||
{
|
||||
SftpFileNode * const fileNode = indexToFileNode(index);
|
||||
QSSH_ASSERT(fileNode);
|
||||
return dynamic_cast<SftpDirNode *>(fileNode);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class SftpFileSystemModelPrivate
|
||||
{
|
||||
public:
|
||||
SshConnection *sshConnection;
|
||||
SftpChannel::Ptr sftpChannel;
|
||||
QString rootDirectory;
|
||||
SftpFileNode *rootNode;
|
||||
SftpJobId statJobId;
|
||||
DirNodeHash lsOps;
|
||||
QList<SftpJobId> externalJobs;
|
||||
};
|
||||
} // namespace Internal
|
||||
|
||||
using namespace Internal;
|
||||
|
||||
SftpFileSystemModel::SftpFileSystemModel(QObject *parent)
|
||||
: QAbstractItemModel(parent), d(new SftpFileSystemModelPrivate)
|
||||
{
|
||||
d->sshConnection = 0;
|
||||
d->rootDirectory = QLatin1Char('/');
|
||||
d->rootNode = 0;
|
||||
d->statJobId = SftpInvalidJob;
|
||||
}
|
||||
|
||||
SftpFileSystemModel::~SftpFileSystemModel()
|
||||
{
|
||||
shutDown();
|
||||
delete d;
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::setSshConnection(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN(!d->sshConnection);
|
||||
d->sshConnection = QSsh::acquireConnection(sshParams);
|
||||
connect(d->sshConnection, &SshConnection::error,
|
||||
this, &SftpFileSystemModel::handleSshConnectionFailure);
|
||||
if (d->sshConnection->state() == SshConnection::Connected) {
|
||||
handleSshConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
connect(d->sshConnection, &SshConnection::connected,
|
||||
this, &SftpFileSystemModel::handleSshConnectionEstablished);
|
||||
if (d->sshConnection->state() == SshConnection::Unconnected)
|
||||
d->sshConnection->connectToHost();
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::setRootDirectory(const QString &path)
|
||||
{
|
||||
beginResetModel();
|
||||
d->rootDirectory = path;
|
||||
delete d->rootNode;
|
||||
d->rootNode = 0;
|
||||
d->lsOps.clear();
|
||||
d->statJobId = SftpInvalidJob;
|
||||
endResetModel();
|
||||
statRootDirectory();
|
||||
}
|
||||
|
||||
QString SftpFileSystemModel::rootDirectory() const
|
||||
{
|
||||
return d->rootDirectory;
|
||||
}
|
||||
|
||||
SftpJobId SftpFileSystemModel::downloadFile(const QModelIndex &index, const QString &targetFilePath)
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(d->rootNode, SftpInvalidJob);
|
||||
const SftpFileNode * const fileNode = indexToFileNode(index);
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(fileNode, SftpInvalidJob);
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(fileNode->fileInfo.type == FileTypeRegular, SftpInvalidJob);
|
||||
const SftpJobId jobId = d->sftpChannel->downloadFile(fileNode->path, targetFilePath,
|
||||
SftpOverwriteExisting);
|
||||
if (jobId != SftpInvalidJob)
|
||||
d->externalJobs << jobId;
|
||||
return jobId;
|
||||
}
|
||||
|
||||
int SftpFileSystemModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 2; // type + name
|
||||
}
|
||||
|
||||
QVariant SftpFileSystemModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const SftpFileNode * const node = indexToFileNode(index);
|
||||
if (index.column() == 0 && role == Qt::DecorationRole) {
|
||||
switch (node->fileInfo.type) {
|
||||
case FileTypeRegular:
|
||||
case FileTypeOther:
|
||||
return QIcon(":/utils/images/unknownfile.png");
|
||||
case FileTypeDirectory:
|
||||
return QIcon(":/utils/images/dir.png");
|
||||
case FileTypeUnknown:
|
||||
return QIcon(":/utils/images/help.png"); // Shows a question mark.
|
||||
}
|
||||
}
|
||||
if (index.column() == 1) {
|
||||
if (role == Qt::DisplayRole)
|
||||
return node->fileInfo.name;
|
||||
if (role == PathRole)
|
||||
return node->path;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags SftpFileSystemModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
}
|
||||
|
||||
QVariant SftpFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
if (section == 0)
|
||||
return tr("File Type");
|
||||
if (section == 1)
|
||||
return tr("File Name");
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex SftpFileSystemModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
|
||||
return QModelIndex();
|
||||
if (!d->rootNode)
|
||||
return QModelIndex();
|
||||
if (!parent.isValid())
|
||||
return createIndex(row, column, d->rootNode);
|
||||
const SftpDirNode * const parentNode = indexToDirNode(parent);
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(parentNode, QModelIndex());
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(row < parentNode->children.count(), QModelIndex());
|
||||
SftpFileNode * const childNode = parentNode->children.at(row);
|
||||
return createIndex(row, column, childNode);
|
||||
}
|
||||
|
||||
QModelIndex SftpFileSystemModel::parent(const QModelIndex &child) const
|
||||
{
|
||||
if (!child.isValid()) // Don't assert on this, since the model tester tries it.
|
||||
return QModelIndex();
|
||||
|
||||
const SftpFileNode * const childNode = indexToFileNode(child);
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(childNode, QModelIndex());
|
||||
if (childNode == d->rootNode)
|
||||
return QModelIndex();
|
||||
SftpDirNode * const parentNode = childNode->parent;
|
||||
if (parentNode == d->rootNode)
|
||||
return createIndex(0, 0, d->rootNode);
|
||||
const SftpDirNode * const grandParentNode = parentNode->parent;
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(grandParentNode, QModelIndex());
|
||||
return createIndex(grandParentNode->children.indexOf(parentNode), 0, parentNode);
|
||||
}
|
||||
|
||||
int SftpFileSystemModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (!d->rootNode)
|
||||
return 0;
|
||||
if (!parent.isValid())
|
||||
return 1;
|
||||
if (parent.column() != 0)
|
||||
return 0;
|
||||
SftpDirNode * const dirNode = indexToDirNode(parent);
|
||||
if (!dirNode)
|
||||
return 0;
|
||||
if (dirNode->lsState != SftpDirNode::LsNotYetCalled)
|
||||
return dirNode->children.count();
|
||||
d->lsOps.insert(d->sftpChannel->listDirectory(dirNode->path), dirNode);
|
||||
dirNode->lsState = SftpDirNode::LsRunning;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::statRootDirectory()
|
||||
{
|
||||
d->statJobId = d->sftpChannel->statFile(d->rootDirectory);
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::shutDown()
|
||||
{
|
||||
if (d->sftpChannel) {
|
||||
disconnect(d->sftpChannel.data(), 0, this, 0);
|
||||
d->sftpChannel->closeChannel();
|
||||
d->sftpChannel.clear();
|
||||
}
|
||||
if (d->sshConnection) {
|
||||
disconnect(d->sshConnection, 0, this, 0);
|
||||
QSsh::releaseConnection(d->sshConnection);
|
||||
d->sshConnection = 0;
|
||||
}
|
||||
delete d->rootNode;
|
||||
d->rootNode = 0;
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleSshConnectionFailure()
|
||||
{
|
||||
emit connectionError(d->sshConnection->errorString());
|
||||
beginResetModel();
|
||||
shutDown();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleSftpChannelInitialized()
|
||||
{
|
||||
connect(d->sftpChannel.data(),
|
||||
&SftpChannel::fileInfoAvailable,
|
||||
this, &SftpFileSystemModel::handleFileInfo);
|
||||
connect(d->sftpChannel.data(), &SftpChannel::finished,
|
||||
this, &SftpFileSystemModel::handleSftpJobFinished);
|
||||
statRootDirectory();
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleSshConnectionEstablished()
|
||||
{
|
||||
d->sftpChannel = d->sshConnection->createSftpChannel();
|
||||
connect(d->sftpChannel.data(), &SftpChannel::initialized,
|
||||
this, &SftpFileSystemModel::handleSftpChannelInitialized);
|
||||
connect(d->sftpChannel.data(), &SftpChannel::channelError,
|
||||
this, &SftpFileSystemModel::handleSftpChannelError);
|
||||
d->sftpChannel->initialize();
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleSftpChannelError(const QString &reason)
|
||||
{
|
||||
emit connectionError(reason);
|
||||
beginResetModel();
|
||||
shutDown();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleFileInfo(SftpJobId jobId, const QList<SftpFileInfo> &fileInfoList)
|
||||
{
|
||||
if (jobId == d->statJobId) {
|
||||
QSSH_ASSERT_AND_RETURN(!d->rootNode);
|
||||
beginInsertRows(QModelIndex(), 0, 0);
|
||||
d->rootNode = new SftpDirNode;
|
||||
d->rootNode->path = d->rootDirectory;
|
||||
d->rootNode->fileInfo = fileInfoList.first();
|
||||
d->rootNode->fileInfo.name = d->rootDirectory == QLatin1String("/")
|
||||
? d->rootDirectory : QFileInfo(d->rootDirectory).fileName();
|
||||
endInsertRows();
|
||||
return;
|
||||
}
|
||||
SftpDirNode * const parentNode = d->lsOps.value(jobId);
|
||||
QSSH_ASSERT_AND_RETURN(parentNode);
|
||||
QList<SftpFileInfo> filteredList;
|
||||
foreach (const SftpFileInfo &fi, fileInfoList) {
|
||||
if (fi.name != QLatin1String(".") && fi.name != QLatin1String(".."))
|
||||
filteredList << fi;
|
||||
}
|
||||
if (filteredList.isEmpty())
|
||||
return;
|
||||
|
||||
// In theory beginInsertRows() should suffice, but that fails to have an effect
|
||||
// if rowCount() returned 0 earlier.
|
||||
emit layoutAboutToBeChanged();
|
||||
|
||||
foreach (const SftpFileInfo &fileInfo, filteredList) {
|
||||
SftpFileNode *childNode;
|
||||
if (fileInfo.type == FileTypeDirectory)
|
||||
childNode = new SftpDirNode;
|
||||
else
|
||||
childNode = new SftpFileNode;
|
||||
childNode->path = parentNode->path;
|
||||
if (!childNode->path.endsWith(QLatin1Char('/')))
|
||||
childNode->path += QLatin1Char('/');
|
||||
childNode->path += fileInfo.name;
|
||||
childNode->fileInfo = fileInfo;
|
||||
childNode->parent = parentNode;
|
||||
parentNode->children << childNode;
|
||||
}
|
||||
emit layoutChanged(); // Should be endInsertRows(), see above.
|
||||
}
|
||||
|
||||
void SftpFileSystemModel::handleSftpJobFinished(SftpJobId jobId, const QString &errorMessage)
|
||||
{
|
||||
if (jobId == d->statJobId) {
|
||||
d->statJobId = SftpInvalidJob;
|
||||
if (!errorMessage.isEmpty())
|
||||
emit sftpOperationFailed(tr("Error getting \"stat\" info about \"%1\": %2")
|
||||
.arg(rootDirectory(), errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
DirNodeHash::Iterator it = d->lsOps.find(jobId);
|
||||
if (it != d->lsOps.end()) {
|
||||
QSSH_ASSERT(it.value()->lsState == SftpDirNode::LsRunning);
|
||||
it.value()->lsState = SftpDirNode::LsFinished;
|
||||
if (!errorMessage.isEmpty())
|
||||
emit sftpOperationFailed(tr("Error listing contents of directory \"%1\": %2")
|
||||
.arg(it.value()->path, errorMessage));
|
||||
d->lsOps.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
const int jobIndex = d->externalJobs.indexOf(jobId);
|
||||
QSSH_ASSERT_AND_RETURN(jobIndex != -1);
|
||||
d->externalJobs.removeAt(jobIndex);
|
||||
emit sftpOperationFinished(jobId, errorMessage);
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,100 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftpdefs.h"
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
namespace QSsh {
|
||||
class SshConnectionParameters;
|
||||
|
||||
namespace Internal { class SftpFileSystemModelPrivate; }
|
||||
|
||||
// Very simple read-only model. Symbolic links are not followed.
|
||||
class QSSH_EXPORT SftpFileSystemModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SftpFileSystemModel(QObject *parent = 0);
|
||||
~SftpFileSystemModel();
|
||||
|
||||
/*
|
||||
* Once this is called, an SFTP connection is established and the model is populated.
|
||||
* The effect of additional calls is undefined.
|
||||
*/
|
||||
void setSshConnection(const SshConnectionParameters &sshParams);
|
||||
|
||||
void setRootDirectory(const QString &path); // Default is "/".
|
||||
QString rootDirectory() const;
|
||||
|
||||
SftpJobId downloadFile(const QModelIndex &index, const QString &targetFilePath);
|
||||
|
||||
// Use this to get the full path of a file or directory.
|
||||
static const int PathRole = Qt::UserRole;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
|
||||
signals:
|
||||
/*
|
||||
* E.g. "Permission denied". Note that this can happen without direct user intervention,
|
||||
* due to e.g. the view calling rowCount() on a non-readable directory. This signal should
|
||||
* therefore not result in a message box or similar, since it might occur very often.
|
||||
*/
|
||||
void sftpOperationFailed(const QString &errorMessage);
|
||||
|
||||
/*
|
||||
* This error is not recoverable. The model will not have any content after
|
||||
* the signal has been emitted.
|
||||
*/
|
||||
void connectionError(const QString &errorMessage);
|
||||
|
||||
// Success <=> error.isEmpty().
|
||||
void sftpOperationFinished(QSsh::SftpJobId, const QString &error);
|
||||
|
||||
private:
|
||||
void handleSshConnectionEstablished();
|
||||
void handleSshConnectionFailure();
|
||||
void handleSftpChannelInitialized();
|
||||
void handleSftpChannelError(const QString &reason);
|
||||
void handleFileInfo(QSsh::SftpJobId jobId, const QList<QSsh::SftpFileInfo> &fileInfoList);
|
||||
void handleSftpJobFinished(QSsh::SftpJobId jobId, const QString &errorMessage);
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
|
||||
QModelIndex parent(const QModelIndex &child) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
|
||||
void statRootDirectory();
|
||||
void shutDown();
|
||||
|
||||
Internal::SftpFileSystemModelPrivate * const d;
|
||||
};
|
||||
|
||||
} // namespace QSsh;
|
||||
@@ -1,217 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpincomingpacket_p.h"
|
||||
|
||||
#include "sshexception_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshpacketparser_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
SftpIncomingPacket::SftpIncomingPacket() : m_length(0)
|
||||
{
|
||||
}
|
||||
|
||||
void SftpIncomingPacket::consumeData(QByteArray &newData)
|
||||
{
|
||||
qCDebug(sshLog, "%s: current data size = %d, new data size = %d", Q_FUNC_INFO,
|
||||
m_data.size(), newData.size());
|
||||
|
||||
if (isComplete() || dataSize() + newData.size() < sizeof m_length)
|
||||
return;
|
||||
|
||||
if (dataSize() < sizeof m_length) {
|
||||
moveFirstBytes(m_data, newData, sizeof m_length - m_data.size());
|
||||
m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
|
||||
if (m_length < static_cast<quint32>(TypeOffset + 1)
|
||||
|| m_length > MaxPacketSize) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid length field in SFTP packet.");
|
||||
}
|
||||
}
|
||||
|
||||
moveFirstBytes(m_data, newData,
|
||||
qMin<quint32>(m_length - dataSize() + 4, newData.size()));
|
||||
}
|
||||
|
||||
void SftpIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source,
|
||||
int n)
|
||||
{
|
||||
target.append(source.left(n));
|
||||
source.remove(0, n);
|
||||
}
|
||||
|
||||
bool SftpIncomingPacket::isComplete() const
|
||||
{
|
||||
return m_length == dataSize() - 4;
|
||||
}
|
||||
|
||||
void SftpIncomingPacket::clear()
|
||||
{
|
||||
m_data.clear();
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
quint32 SftpIncomingPacket::extractServerVersion() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_VERSION);
|
||||
try {
|
||||
return SshPacketParser::asUint32(m_data, TypeOffset + 1);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_VERSION packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SftpHandleResponse SftpIncomingPacket::asHandleResponse() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_HANDLE);
|
||||
try {
|
||||
SftpHandleResponse response;
|
||||
quint32 offset = RequestIdOffset;
|
||||
response.requestId = SshPacketParser::asUint32(m_data, &offset);
|
||||
response.handle = SshPacketParser::asString(m_data, &offset);
|
||||
return response;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_HANDLE packet");
|
||||
}
|
||||
}
|
||||
|
||||
SftpStatusResponse SftpIncomingPacket::asStatusResponse() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_STATUS);
|
||||
try {
|
||||
SftpStatusResponse response;
|
||||
quint32 offset = RequestIdOffset;
|
||||
response.requestId = SshPacketParser::asUint32(m_data, &offset);
|
||||
response.status = static_cast<SftpStatusCode>(SshPacketParser::asUint32(m_data, &offset));
|
||||
response.errorString = SshPacketParser::asUserString(m_data, &offset);
|
||||
response.language = SshPacketParser::asString(m_data, &offset);
|
||||
return response;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_STATUS packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SftpNameResponse SftpIncomingPacket::asNameResponse() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_NAME);
|
||||
try {
|
||||
SftpNameResponse response;
|
||||
quint32 offset = RequestIdOffset;
|
||||
response.requestId = SshPacketParser::asUint32(m_data, &offset);
|
||||
const quint32 count = SshPacketParser::asUint32(m_data, &offset);
|
||||
for (quint32 i = 0; i < count; ++i)
|
||||
response.files << asFile(offset);
|
||||
return response;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_NAME packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SftpDataResponse SftpIncomingPacket::asDataResponse() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_DATA);
|
||||
try {
|
||||
SftpDataResponse response;
|
||||
quint32 offset = RequestIdOffset;
|
||||
response.requestId = SshPacketParser::asUint32(m_data, &offset);
|
||||
response.data = SshPacketParser::asString(m_data, &offset);
|
||||
return response;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_DATA packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SftpAttrsResponse SftpIncomingPacket::asAttrsResponse() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_FXP_ATTRS);
|
||||
try {
|
||||
SftpAttrsResponse response;
|
||||
quint32 offset = RequestIdOffset;
|
||||
response.requestId = SshPacketParser::asUint32(m_data, &offset);
|
||||
response.attrs = asFileAttributes(offset);
|
||||
return response;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_FXP_ATTRS packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SftpFile SftpIncomingPacket::asFile(quint32 &offset) const
|
||||
{
|
||||
SftpFile file;
|
||||
file.fileName
|
||||
= QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
|
||||
file.longName
|
||||
= QString::fromUtf8(SshPacketParser::asString(m_data, &offset));
|
||||
file.attributes = asFileAttributes(offset);
|
||||
return file;
|
||||
}
|
||||
|
||||
SftpFileAttributes SftpIncomingPacket::asFileAttributes(quint32 &offset) const
|
||||
{
|
||||
SftpFileAttributes attributes;
|
||||
const quint32 flags = SshPacketParser::asUint32(m_data, &offset);
|
||||
attributes.sizePresent = flags & SSH_FILEXFER_ATTR_SIZE;
|
||||
attributes.timesPresent = flags & SSH_FILEXFER_ATTR_ACMODTIME;
|
||||
attributes.uidAndGidPresent = flags & SSH_FILEXFER_ATTR_UIDGID;
|
||||
attributes.permissionsPresent = flags & SSH_FILEXFER_ATTR_PERMISSIONS;
|
||||
if (attributes.sizePresent)
|
||||
attributes.size = SshPacketParser::asUint64(m_data, &offset);
|
||||
if (attributes.uidAndGidPresent) {
|
||||
attributes.uid = SshPacketParser::asUint32(m_data, &offset);
|
||||
attributes.gid = SshPacketParser::asUint32(m_data, &offset);
|
||||
}
|
||||
if (attributes.permissionsPresent)
|
||||
attributes.permissions = SshPacketParser::asUint32(m_data, &offset);
|
||||
if (attributes.timesPresent) {
|
||||
attributes.atime = SshPacketParser::asUint32(m_data, &offset);
|
||||
attributes.mtime = SshPacketParser::asUint32(m_data, &offset);
|
||||
}
|
||||
if (flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
||||
const quint32 count = SshPacketParser::asUint32(m_data, &offset);
|
||||
for (quint32 i = 0; i < count; ++i) {
|
||||
SshPacketParser::asString(m_data, &offset);
|
||||
SshPacketParser::asString(m_data, &offset);
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,104 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftppacket_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
struct SftpHandleResponse {
|
||||
quint32 requestId;
|
||||
QByteArray handle;
|
||||
};
|
||||
|
||||
struct SftpStatusResponse {
|
||||
quint32 requestId;
|
||||
SftpStatusCode status;
|
||||
QString errorString;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
struct SftpFileAttributes {
|
||||
bool sizePresent;
|
||||
bool timesPresent;
|
||||
bool uidAndGidPresent;
|
||||
bool permissionsPresent;
|
||||
quint64 size;
|
||||
quint32 uid;
|
||||
quint32 gid;
|
||||
quint32 permissions;
|
||||
quint32 atime;
|
||||
quint32 mtime;
|
||||
};
|
||||
|
||||
struct SftpFile {
|
||||
QString fileName;
|
||||
QString longName; // Not present in later RFCs, so we don't expose this to the user.
|
||||
SftpFileAttributes attributes;
|
||||
};
|
||||
|
||||
struct SftpNameResponse {
|
||||
quint32 requestId;
|
||||
QList<SftpFile> files;
|
||||
};
|
||||
|
||||
struct SftpDataResponse {
|
||||
quint32 requestId;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
struct SftpAttrsResponse {
|
||||
quint32 requestId;
|
||||
SftpFileAttributes attrs;
|
||||
};
|
||||
|
||||
class SftpIncomingPacket : public AbstractSftpPacket
|
||||
{
|
||||
public:
|
||||
SftpIncomingPacket();
|
||||
|
||||
void consumeData(QByteArray &data);
|
||||
void clear();
|
||||
bool isComplete() const;
|
||||
quint32 extractServerVersion() const;
|
||||
SftpHandleResponse asHandleResponse() const;
|
||||
SftpStatusResponse asStatusResponse() const;
|
||||
SftpNameResponse asNameResponse() const;
|
||||
SftpDataResponse asDataResponse() const;
|
||||
SftpAttrsResponse asAttrsResponse() const;
|
||||
|
||||
private:
|
||||
void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
|
||||
|
||||
SftpFileAttributes asFileAttributes(quint32 &offset) const;
|
||||
SftpFile asFile(quint32 &offset) const;
|
||||
|
||||
quint32 m_length;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,220 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpoperation_p.h"
|
||||
|
||||
#include "sftpoutgoingpacket_p.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
|
||||
{
|
||||
}
|
||||
|
||||
AbstractSftpOperation::~AbstractSftpOperation() { }
|
||||
|
||||
|
||||
SftpStatFile::SftpStatFile(SftpJobId jobId, const QString &path)
|
||||
: AbstractSftpOperation(jobId), path(path)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpStatFile::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateStat(path, jobId);
|
||||
}
|
||||
|
||||
SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path,
|
||||
const SftpUploadDir::Ptr &parentJob)
|
||||
: AbstractSftpOperation(jobId), parentJob(parentJob), remoteDir(path)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpMakeDir::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateMkDir(remoteDir, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpRmDir::SftpRmDir(SftpJobId id, const QString &path)
|
||||
: AbstractSftpOperation(id), remoteDir(path)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpRmDir::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateRmDir(remoteDir, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpRm::SftpRm(SftpJobId jobId, const QString &path)
|
||||
: AbstractSftpOperation(jobId), remoteFile(path) {}
|
||||
|
||||
SftpOutgoingPacket &SftpRm::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateRm(remoteFile, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpRename::SftpRename(SftpJobId jobId, const QString &oldPath,
|
||||
const QString &newPath)
|
||||
: AbstractSftpOperation(jobId), oldPath(oldPath), newPath(newPath)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpRename::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateRename(oldPath, newPath, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpCreateLink::SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target)
|
||||
: AbstractSftpOperation(jobId), filePath(filePath), target(target)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpCreateLink::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
return packet.generateCreateLink(filePath, target, jobId);
|
||||
}
|
||||
|
||||
|
||||
AbstractSftpOperationWithHandle::AbstractSftpOperationWithHandle(SftpJobId jobId,
|
||||
const QString &remotePath)
|
||||
: AbstractSftpOperation(jobId),
|
||||
remotePath(remotePath), state(Inactive), hasError(false)
|
||||
{
|
||||
}
|
||||
|
||||
AbstractSftpOperationWithHandle::~AbstractSftpOperationWithHandle() { }
|
||||
|
||||
|
||||
SftpListDir::SftpListDir(SftpJobId jobId, const QString &path)
|
||||
: AbstractSftpOperationWithHandle(jobId, path)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpListDir::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
state = OpenRequested;
|
||||
return packet.generateOpenDir(remotePath, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpCreateFile::SftpCreateFile(SftpJobId jobId, const QString &path,
|
||||
SftpOverwriteMode mode)
|
||||
: AbstractSftpOperationWithHandle(jobId, path), mode(mode)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket & SftpCreateFile::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
state = OpenRequested;
|
||||
return packet.generateOpenFileForWriting(remotePath, mode,
|
||||
SftpOutgoingPacket::DefaultPermissions, jobId);
|
||||
}
|
||||
|
||||
|
||||
const int AbstractSftpTransfer::MaxInFlightCount = 10; // Experimentally found to be enough.
|
||||
|
||||
AbstractSftpTransfer::AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile)
|
||||
: AbstractSftpOperationWithHandle(jobId, remotePath),
|
||||
localFile(localFile), fileSize(0), offset(0), inFlightCount(0),
|
||||
statRequested(false)
|
||||
{
|
||||
}
|
||||
|
||||
AbstractSftpTransfer::~AbstractSftpTransfer() {}
|
||||
|
||||
void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize)
|
||||
{
|
||||
if (fileSize == 0) {
|
||||
inFlightCount = 1;
|
||||
} else {
|
||||
inFlightCount = fileSize / chunkSize;
|
||||
if (fileSize % chunkSize)
|
||||
++inFlightCount;
|
||||
if (inFlightCount > MaxInFlightCount)
|
||||
inFlightCount = MaxInFlightCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SftpDownload::SftpDownload(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile)
|
||||
: AbstractSftpTransfer(jobId, remotePath, localFile), eofId(SftpInvalidJob)
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
state = OpenRequested;
|
||||
return packet.generateOpenFileForReading(remotePath, jobId);
|
||||
}
|
||||
|
||||
|
||||
SftpUploadFile::SftpUploadFile(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
|
||||
const SftpUploadDir::Ptr &parentJob)
|
||||
: AbstractSftpTransfer(jobId, remotePath, localFile),
|
||||
parentJob(parentJob), mode(mode)
|
||||
{
|
||||
fileSize = localFile->size();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpUploadFile::initialPacket(SftpOutgoingPacket &packet)
|
||||
{
|
||||
state = OpenRequested;
|
||||
quint32 permissions = 0;
|
||||
const QFile::Permissions &qtPermissions = localFile->permissions();
|
||||
if (qtPermissions & QFile::ExeOther)
|
||||
permissions |= 1 << 0;
|
||||
if (qtPermissions & QFile::WriteOther)
|
||||
permissions |= 1 << 1;
|
||||
if (qtPermissions & QFile::ReadOther)
|
||||
permissions |= 1 << 2;
|
||||
if (qtPermissions & QFile::ExeGroup)
|
||||
permissions |= 1<< 3;
|
||||
if (qtPermissions & QFile::WriteGroup)
|
||||
permissions |= 1<< 4;
|
||||
if (qtPermissions & QFile::ReadGroup)
|
||||
permissions |= 1<< 5;
|
||||
if (qtPermissions & QFile::ExeOwner)
|
||||
permissions |= 1<< 6;
|
||||
if (qtPermissions & QFile::WriteOwner)
|
||||
permissions |= 1<< 7;
|
||||
if (qtPermissions & QFile::ReadOwner)
|
||||
permissions |= 1<< 8;
|
||||
return packet.generateOpenFileForWriting(remotePath, mode, permissions, jobId);
|
||||
}
|
||||
|
||||
SftpUploadDir::~SftpUploadDir() {}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,244 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftpdefs.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QSharedPointer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QFile;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SftpOutgoingPacket;
|
||||
|
||||
struct AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<AbstractSftpOperation> Ptr;
|
||||
enum Type {
|
||||
StatFile, ListDir, MakeDir, RmDir, Rm, Rename, CreateLink, CreateFile, Download, UploadFile
|
||||
};
|
||||
|
||||
AbstractSftpOperation(SftpJobId jobId);
|
||||
virtual ~AbstractSftpOperation();
|
||||
virtual Type type() const = 0;
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet) = 0;
|
||||
|
||||
const SftpJobId jobId;
|
||||
|
||||
private:
|
||||
AbstractSftpOperation(const AbstractSftpOperation &);
|
||||
AbstractSftpOperation &operator=(const AbstractSftpOperation &);
|
||||
};
|
||||
|
||||
struct SftpUploadDir;
|
||||
|
||||
struct SftpStatFile : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpStatFile> Ptr;
|
||||
|
||||
SftpStatFile(SftpJobId jobId, const QString &path);
|
||||
virtual Type type() const { return StatFile; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QString path;
|
||||
};
|
||||
|
||||
struct SftpMakeDir : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpMakeDir> Ptr;
|
||||
|
||||
SftpMakeDir(SftpJobId jobId, const QString &path,
|
||||
const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
|
||||
virtual Type type() const { return MakeDir; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QSharedPointer<SftpUploadDir> parentJob;
|
||||
const QString remoteDir;
|
||||
};
|
||||
|
||||
struct SftpRmDir : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpRmDir> Ptr;
|
||||
|
||||
SftpRmDir(SftpJobId id, const QString &path);
|
||||
virtual Type type() const { return RmDir; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QString remoteDir;
|
||||
};
|
||||
|
||||
struct SftpRm : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpRm> Ptr;
|
||||
|
||||
SftpRm(SftpJobId jobId, const QString &path);
|
||||
virtual Type type() const { return Rm; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QString remoteFile;
|
||||
};
|
||||
|
||||
struct SftpRename : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpRename> Ptr;
|
||||
|
||||
SftpRename(SftpJobId jobId, const QString &oldPath, const QString &newPath);
|
||||
virtual Type type() const { return Rename; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QString oldPath;
|
||||
const QString newPath;
|
||||
};
|
||||
|
||||
struct SftpCreateLink : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<SftpCreateLink> Ptr;
|
||||
|
||||
SftpCreateLink(SftpJobId jobId, const QString &filePath, const QString &target);
|
||||
virtual Type type() const { return CreateLink; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QString filePath;
|
||||
const QString target;
|
||||
};
|
||||
|
||||
|
||||
struct AbstractSftpOperationWithHandle : public AbstractSftpOperation
|
||||
{
|
||||
typedef QSharedPointer<AbstractSftpOperationWithHandle> Ptr;
|
||||
enum State { Inactive, OpenRequested, Open, CloseRequested };
|
||||
|
||||
AbstractSftpOperationWithHandle(SftpJobId jobId, const QString &remotePath);
|
||||
~AbstractSftpOperationWithHandle();
|
||||
|
||||
const QString remotePath;
|
||||
QByteArray remoteHandle;
|
||||
State state;
|
||||
bool hasError;
|
||||
};
|
||||
|
||||
|
||||
struct SftpListDir : public AbstractSftpOperationWithHandle
|
||||
{
|
||||
typedef QSharedPointer<SftpListDir> Ptr;
|
||||
|
||||
SftpListDir(SftpJobId jobId, const QString &path);
|
||||
virtual Type type() const { return ListDir; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
};
|
||||
|
||||
|
||||
struct SftpCreateFile : public AbstractSftpOperationWithHandle
|
||||
{
|
||||
typedef QSharedPointer<SftpCreateFile> Ptr;
|
||||
|
||||
SftpCreateFile(SftpJobId jobId, const QString &path, SftpOverwriteMode mode);
|
||||
virtual Type type() const { return CreateFile; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const SftpOverwriteMode mode;
|
||||
};
|
||||
|
||||
struct AbstractSftpTransfer : public AbstractSftpOperationWithHandle
|
||||
{
|
||||
typedef QSharedPointer<AbstractSftpTransfer> Ptr;
|
||||
|
||||
AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile);
|
||||
~AbstractSftpTransfer();
|
||||
void calculateInFlightCount(quint32 chunkSize);
|
||||
|
||||
static const int MaxInFlightCount;
|
||||
|
||||
const QSharedPointer<QFile> localFile;
|
||||
quint64 fileSize;
|
||||
quint64 offset;
|
||||
int inFlightCount;
|
||||
bool statRequested;
|
||||
};
|
||||
|
||||
struct SftpDownload : public AbstractSftpTransfer
|
||||
{
|
||||
typedef QSharedPointer<SftpDownload> Ptr;
|
||||
SftpDownload(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile);
|
||||
virtual Type type() const { return Download; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
QMap<quint32, quint64> offsets;
|
||||
SftpJobId eofId;
|
||||
};
|
||||
|
||||
struct SftpUploadFile : public AbstractSftpTransfer
|
||||
{
|
||||
typedef QSharedPointer<SftpUploadFile> Ptr;
|
||||
|
||||
SftpUploadFile(SftpJobId jobId, const QString &remotePath,
|
||||
const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
|
||||
const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
|
||||
virtual Type type() const { return UploadFile; }
|
||||
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
|
||||
|
||||
const QSharedPointer<SftpUploadDir> parentJob;
|
||||
SftpOverwriteMode mode;
|
||||
};
|
||||
|
||||
// Composite operation.
|
||||
struct SftpUploadDir
|
||||
{
|
||||
typedef QSharedPointer<SftpUploadDir> Ptr;
|
||||
|
||||
struct Dir {
|
||||
Dir(const QString &l, const QString &r) : localDir(l), remoteDir(r) {}
|
||||
QString localDir;
|
||||
QString remoteDir;
|
||||
};
|
||||
|
||||
SftpUploadDir(SftpJobId jobId) : jobId(jobId), hasError(false) {}
|
||||
~SftpUploadDir();
|
||||
|
||||
void setError()
|
||||
{
|
||||
hasError = true;
|
||||
uploadsInProgress.clear();
|
||||
mkdirsInProgress.clear();
|
||||
}
|
||||
|
||||
const SftpJobId jobId;
|
||||
bool hasError;
|
||||
QList<SftpUploadFile::Ptr> uploadsInProgress;
|
||||
QMap<SftpMakeDir::Ptr, Dir> mkdirsInProgress;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,220 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpoutgoingpacket_p.h"
|
||||
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshpacket_p.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
namespace {
|
||||
const quint32 DefaultAttributes = 0;
|
||||
const quint32 SSH_FXF_READ = 0x00000001;
|
||||
const quint32 SSH_FXF_WRITE = 0x00000002;
|
||||
const quint32 SSH_FXF_APPEND = 0x00000004;
|
||||
const quint32 SSH_FXF_CREAT = 0x00000008;
|
||||
const quint32 SSH_FXF_TRUNC = 0x00000010;
|
||||
const quint32 SSH_FXF_EXCL = 0x00000020;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket::SftpOutgoingPacket()
|
||||
{
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateInit(quint32 version)
|
||||
{
|
||||
return init(SSH_FXP_INIT, 0).appendInt(version).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateStat(const QString &path, quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_LSTAT, requestId).appendString(path).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenDir(const QString &path,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_OPENDIR, requestId).appendString(path).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateReadDir(const QByteArray &handle,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_READDIR, requestId).appendString(handle).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateCloseHandle(const QByteArray &handle,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_CLOSE, requestId).appendString(handle).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateMkDir(const QString &path,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_MKDIR, requestId).appendString(path)
|
||||
.appendInt(DefaultAttributes).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateRmDir(const QString &path,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_RMDIR, requestId).appendString(path).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateRm(const QString &path,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_REMOVE, requestId).appendString(path).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateRename(const QString &oldPath,
|
||||
const QString &newPath, quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_RENAME, requestId).appendString(oldPath)
|
||||
.appendString(newPath).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForWriting(const QString &path,
|
||||
SftpOverwriteMode mode, quint32 permissions, quint32 requestId)
|
||||
{
|
||||
QList<quint32> attributes;
|
||||
if (permissions != DefaultPermissions)
|
||||
attributes << SSH_FILEXFER_ATTR_PERMISSIONS << permissions;
|
||||
else
|
||||
attributes << DefaultAttributes;
|
||||
return generateOpenFile(path, Write, mode, attributes, requestId);
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFileForReading(const QString &path,
|
||||
quint32 requestId)
|
||||
{
|
||||
// Note: Overwrite mode is irrelevant and will be ignored.
|
||||
return generateOpenFile(path, Read, SftpSkipExisting, QList<quint32>() << DefaultAttributes,
|
||||
requestId);
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateReadFile(const QByteArray &handle,
|
||||
quint64 offset, quint32 length, quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_READ, requestId).appendString(handle).appendInt64(offset)
|
||||
.appendInt(length).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateFstat(const QByteArray &handle,
|
||||
quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_FSTAT, requestId).appendString(handle).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateWriteFile(const QByteArray &handle,
|
||||
quint64 offset, const QByteArray &data, quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_WRITE, requestId).appendString(handle)
|
||||
.appendInt64(offset).appendString(data).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateCreateLink(const QString &filePath,
|
||||
const QString &target, quint32 requestId)
|
||||
{
|
||||
return init(SSH_FXP_SYMLINK, requestId).appendString(filePath).appendString(target).finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::generateOpenFile(const QString &path,
|
||||
OpenType openType, SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId)
|
||||
{
|
||||
quint32 pFlags = 0;
|
||||
switch (openType) {
|
||||
case Read:
|
||||
pFlags = SSH_FXF_READ;
|
||||
break;
|
||||
case Write:
|
||||
pFlags = SSH_FXF_WRITE | SSH_FXF_CREAT;
|
||||
switch (mode) {
|
||||
case SftpOverwriteExisting: pFlags |= SSH_FXF_TRUNC; break;
|
||||
case SftpAppendToExisting: pFlags |= SSH_FXF_APPEND; break;
|
||||
case SftpSkipExisting: pFlags |= SSH_FXF_EXCL; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
init(SSH_FXP_OPEN, requestId).appendString(path).appendInt(pFlags);
|
||||
foreach (const quint32 attribute, attributes)
|
||||
appendInt(attribute);
|
||||
return finalize();
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::init(SftpPacketType type,
|
||||
quint32 requestId)
|
||||
{
|
||||
m_data.resize(TypeOffset + 1);
|
||||
m_data[TypeOffset] = type;
|
||||
if (type != SSH_FXP_INIT) {
|
||||
appendInt(requestId);
|
||||
qCDebug(sshLog, "Generating SFTP packet of type %d with request id %u", type, requestId);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::appendInt(quint32 val)
|
||||
{
|
||||
m_data.append(AbstractSshPacket::encodeInt(val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::appendInt64(quint64 value)
|
||||
{
|
||||
m_data.append(AbstractSshPacket::encodeInt(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QString &string)
|
||||
{
|
||||
m_data.append(AbstractSshPacket::encodeString(string.toUtf8()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::appendString(const QByteArray &string)
|
||||
{
|
||||
m_data += AbstractSshPacket::encodeString(string);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SftpOutgoingPacket &SftpOutgoingPacket::finalize()
|
||||
{
|
||||
AbstractSshPacket::setLengthField(m_data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const quint32 SftpOutgoingPacket::DefaultPermissions = std::numeric_limits<quint32>::max();
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,84 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftppacket_p.h"
|
||||
#include "sftpdefs.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SftpOutgoingPacket : public AbstractSftpPacket
|
||||
{
|
||||
public:
|
||||
SftpOutgoingPacket();
|
||||
SftpOutgoingPacket &generateInit(quint32 version);
|
||||
SftpOutgoingPacket &generateStat(const QString &path, quint32 requestId);
|
||||
SftpOutgoingPacket &generateOpenDir(const QString &path, quint32 requestId);
|
||||
SftpOutgoingPacket &generateReadDir(const QByteArray &handle,
|
||||
quint32 requestId);
|
||||
SftpOutgoingPacket &generateCloseHandle(const QByteArray &handle,
|
||||
quint32 requestId);
|
||||
SftpOutgoingPacket &generateMkDir(const QString &path, quint32 requestId);
|
||||
SftpOutgoingPacket &generateRmDir(const QString &path, quint32 requestId);
|
||||
SftpOutgoingPacket &generateRm(const QString &path, quint32 requestId);
|
||||
SftpOutgoingPacket &generateRename(const QString &oldPath,
|
||||
const QString &newPath, quint32 requestId);
|
||||
SftpOutgoingPacket &generateOpenFileForWriting(const QString &path,
|
||||
SftpOverwriteMode mode, quint32 permissions, quint32 requestId);
|
||||
SftpOutgoingPacket &generateOpenFileForReading(const QString &path,
|
||||
quint32 requestId);
|
||||
SftpOutgoingPacket &generateReadFile(const QByteArray &handle,
|
||||
quint64 offset, quint32 length, quint32 requestId);
|
||||
SftpOutgoingPacket &generateFstat(const QByteArray &handle,
|
||||
quint32 requestId);
|
||||
SftpOutgoingPacket &generateWriteFile(const QByteArray &handle,
|
||||
quint64 offset, const QByteArray &data, quint32 requestId);
|
||||
|
||||
// Note: OpenSSH's SFTP server has a bug that reverses the filePath and target
|
||||
// arguments, so this operation is not portable.
|
||||
SftpOutgoingPacket &generateCreateLink(const QString &filePath, const QString &target,
|
||||
quint32 requestId);
|
||||
|
||||
static const quint32 DefaultPermissions;
|
||||
|
||||
private:
|
||||
static QByteArray encodeString(const QString &string);
|
||||
|
||||
enum OpenType { Read, Write };
|
||||
SftpOutgoingPacket &generateOpenFile(const QString &path, OpenType openType,
|
||||
SftpOverwriteMode mode, const QList<quint32> &attributes, quint32 requestId);
|
||||
|
||||
SftpOutgoingPacket &init(SftpPacketType type, quint32 requestId);
|
||||
SftpOutgoingPacket &appendInt(quint32 value);
|
||||
SftpOutgoingPacket &appendInt64(quint64 value);
|
||||
SftpOutgoingPacket &appendString(const QString &string);
|
||||
SftpOutgoingPacket &appendString(const QByteArray &string);
|
||||
SftpOutgoingPacket &finalize();
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,49 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftppacket_p.h"
|
||||
|
||||
#include "sshpacketparser_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
const quint32 AbstractSftpPacket::MaxDataSize = 32000;
|
||||
const quint32 AbstractSftpPacket::MaxPacketSize = 34000;
|
||||
const int AbstractSftpPacket::TypeOffset = 4;
|
||||
const int AbstractSftpPacket::RequestIdOffset = TypeOffset + 1;
|
||||
const int AbstractSftpPacket::PayloadOffset = RequestIdOffset + 4;
|
||||
|
||||
AbstractSftpPacket::AbstractSftpPacket()
|
||||
{
|
||||
}
|
||||
|
||||
quint32 AbstractSftpPacket::requestId() const
|
||||
{
|
||||
return SshPacketParser::asUint32(m_data, RequestIdOffset);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,109 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
enum SftpPacketType {
|
||||
SSH_FXP_INIT = 1,
|
||||
SSH_FXP_VERSION = 2,
|
||||
SSH_FXP_OPEN = 3,
|
||||
SSH_FXP_CLOSE = 4,
|
||||
SSH_FXP_READ = 5,
|
||||
SSH_FXP_WRITE = 6,
|
||||
SSH_FXP_LSTAT = 7,
|
||||
SSH_FXP_FSTAT = 8,
|
||||
SSH_FXP_SETSTAT = 9,
|
||||
SSH_FXP_FSETSTAT = 10,
|
||||
SSH_FXP_OPENDIR = 11,
|
||||
SSH_FXP_READDIR = 12,
|
||||
SSH_FXP_REMOVE = 13,
|
||||
SSH_FXP_MKDIR = 14,
|
||||
SSH_FXP_RMDIR = 15,
|
||||
SSH_FXP_REALPATH = 16,
|
||||
SSH_FXP_STAT = 17,
|
||||
SSH_FXP_RENAME = 18,
|
||||
SSH_FXP_READLINK = 19,
|
||||
SSH_FXP_SYMLINK = 20, // Removed from later protocol versions. Try not to use.
|
||||
|
||||
SSH_FXP_STATUS = 101,
|
||||
SSH_FXP_HANDLE = 102,
|
||||
SSH_FXP_DATA = 103,
|
||||
SSH_FXP_NAME = 104,
|
||||
SSH_FXP_ATTRS = 105,
|
||||
|
||||
SSH_FXP_EXTENDED = 200,
|
||||
SSH_FXP_EXTENDED_REPLY = 201
|
||||
};
|
||||
|
||||
enum SftpStatusCode {
|
||||
SSH_FX_OK = 0,
|
||||
SSH_FX_EOF = 1,
|
||||
SSH_FX_NO_SUCH_FILE = 2,
|
||||
SSH_FX_PERMISSION_DENIED = 3,
|
||||
SSH_FX_FAILURE = 4,
|
||||
SSH_FX_BAD_MESSAGE = 5,
|
||||
SSH_FX_NO_CONNECTION = 6,
|
||||
SSH_FX_CONNECTION_LOST = 7,
|
||||
SSH_FX_OP_UNSUPPORTED = 8
|
||||
};
|
||||
|
||||
enum SftpAttributeType {
|
||||
SSH_FILEXFER_ATTR_SIZE = 0x00000001,
|
||||
SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
|
||||
SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
|
||||
SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
|
||||
SSH_FILEXFER_ATTR_EXTENDED = 0x80000000
|
||||
};
|
||||
|
||||
class AbstractSftpPacket
|
||||
{
|
||||
public:
|
||||
AbstractSftpPacket();
|
||||
quint32 requestId() const;
|
||||
const QByteArray &rawData() const { return m_data; }
|
||||
SftpPacketType type() const { return static_cast<SftpPacketType>(m_data.at(TypeOffset)); }
|
||||
|
||||
static const quint32 MaxDataSize; // "Pure" data size per read/writepacket.
|
||||
static const quint32 MaxPacketSize;
|
||||
|
||||
protected:
|
||||
quint32 dataSize() const { return static_cast<quint32>(m_data.size()); }
|
||||
|
||||
static const int TypeOffset;
|
||||
static const int RequestIdOffset;
|
||||
static const int PayloadOffset;
|
||||
|
||||
QByteArray m_data;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,81 +0,0 @@
|
||||
QT += gui network widgets
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
SOURCES += $$PWD/sshsendfacility.cpp \
|
||||
$$PWD/sshremoteprocess.cpp \
|
||||
$$PWD/sshpacketparser.cpp \
|
||||
$$PWD/sshpacket.cpp \
|
||||
$$PWD/sshoutgoingpacket.cpp \
|
||||
$$PWD/sshkeygenerator.cpp \
|
||||
$$PWD/sshkeyexchange.cpp \
|
||||
$$PWD/sshincomingpacket.cpp \
|
||||
$$PWD/sshcryptofacility.cpp \
|
||||
$$PWD/sshconnection.cpp \
|
||||
$$PWD/sshchannelmanager.cpp \
|
||||
$$PWD/sshchannel.cpp \
|
||||
$$PWD/sshcapabilities.cpp \
|
||||
$$PWD/sftppacket.cpp \
|
||||
$$PWD/sftpoutgoingpacket.cpp \
|
||||
$$PWD/sftpoperation.cpp \
|
||||
$$PWD/sftpincomingpacket.cpp \
|
||||
$$PWD/sftpdefs.cpp \
|
||||
$$PWD/sftpchannel.cpp \
|
||||
$$PWD/sshremoteprocessrunner.cpp \
|
||||
$$PWD/sshconnectionmanager.cpp \
|
||||
$$PWD/sshkeypasswordretriever.cpp \
|
||||
$$PWD/sftpfilesystemmodel.cpp \
|
||||
$$PWD/sshkeycreationdialog.cpp \
|
||||
$$PWD/sshinit.cpp \
|
||||
$$PWD/sshdirecttcpiptunnel.cpp \
|
||||
$$PWD/sshlogging.cpp \
|
||||
$$PWD/sshhostkeydatabase.cpp \
|
||||
$$PWD/sshtcpipforwardserver.cpp \
|
||||
$$PWD/sshtcpiptunnel.cpp \
|
||||
$$PWD/sshforwardedtcpiptunnel.cpp
|
||||
|
||||
HEADERS += $$PWD/sshsendfacility_p.h \
|
||||
$$PWD/sshremoteprocess.h \
|
||||
$$PWD/sshremoteprocess_p.h \
|
||||
$$PWD/sshpacketparser_p.h \
|
||||
$$PWD/sshpacket_p.h \
|
||||
$$PWD/sshoutgoingpacket_p.h \
|
||||
$$PWD/sshkeygenerator.h \
|
||||
$$PWD/sshkeyexchange_p.h \
|
||||
$$PWD/sshincomingpacket_p.h \
|
||||
$$PWD/sshexception_p.h \
|
||||
$$PWD/ssherrors.h \
|
||||
$$PWD/sshcryptofacility_p.h \
|
||||
$$PWD/sshconnection.h \
|
||||
$$PWD/sshconnection_p.h \
|
||||
$$PWD/sshchannelmanager_p.h \
|
||||
$$PWD/sshchannel_p.h \
|
||||
$$PWD/sshcapabilities_p.h \
|
||||
$$PWD/sshbotanconversions_p.h \
|
||||
$$PWD/sftppacket_p.h \
|
||||
$$PWD/sftpoutgoingpacket_p.h \
|
||||
$$PWD/sftpoperation_p.h \
|
||||
$$PWD/sftpincomingpacket_p.h \
|
||||
$$PWD/sftpdefs.h \
|
||||
$$PWD/sftpchannel.h \
|
||||
$$PWD/sftpchannel_p.h \
|
||||
$$PWD/sshremoteprocessrunner.h \
|
||||
$$PWD/sshconnectionmanager.h \
|
||||
$$PWD/sshpseudoterminal.h \
|
||||
$$PWD/sshkeypasswordretriever_p.h \
|
||||
$$PWD/sftpfilesystemmodel.h \
|
||||
$$PWD/sshkeycreationdialog.h \
|
||||
$$PWD/ssh_global.h \
|
||||
$$PWD/sshdirecttcpiptunnel_p.h \
|
||||
$$PWD/sshinit_p.h \
|
||||
$$PWD/sshdirecttcpiptunnel.h \
|
||||
$$PWD/sshlogging_p.h \
|
||||
$$PWD/sshhostkeydatabase.h \
|
||||
$$PWD/sshtcpipforwardserver.h \
|
||||
$$PWD/sshtcpipforwardserver_p.h \
|
||||
$$PWD/sshtcpiptunnel_p.h \
|
||||
$$PWD/sshforwardedtcpiptunnel.h \
|
||||
$$PWD/sshforwardedtcpiptunnel_p.h
|
||||
|
||||
FORMS += $$PWD/sshkeycreationdialog.ui
|
||||
@@ -1,8 +0,0 @@
|
||||
TARGET = QtSsh
|
||||
|
||||
load(qt_module)
|
||||
|
||||
DEFINES += QTCSSH_LIBRARY
|
||||
|
||||
include($$PWD/ssh.pri)
|
||||
include($$PWD/../botan/botan.pri)
|
||||
@@ -1 +0,0 @@
|
||||
QTC_LIB_NAME = QtcSsh
|
||||
@@ -1,41 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
//#if defined(QTCSSH_LIBRARY)
|
||||
//# define QSSH_EXPORT Q_DECL_EXPORT
|
||||
//#else
|
||||
//# define QSSH_EXPORT Q_DECL_IMPORT
|
||||
//#endif
|
||||
|
||||
#define QSSH_EXPORT
|
||||
|
||||
#define QSSH_PRINT_WARNING qWarning("Soft assert at %s:%d", __FILE__, __LINE__)
|
||||
#define QSSH_ASSERT(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; } } while (false)
|
||||
#define QSSH_ASSERT_AND_RETURN(cond) do { if (!(cond)) { QSSH_PRINT_WARNING; return; } } while (false)
|
||||
#define QSSH_ASSERT_AND_RETURN_VALUE(cond, value) do { if (!(cond)) { QSSH_PRINT_WARNING; return value; } } while (false)
|
||||
@@ -1,132 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshcapabilities_p.h"
|
||||
#include "sshexception_p.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
inline const Botan::byte *convertByteArray(const QByteArray &a)
|
||||
{
|
||||
return reinterpret_cast<const Botan::byte *>(a.constData());
|
||||
}
|
||||
|
||||
inline Botan::byte *convertByteArray(QByteArray &a)
|
||||
{
|
||||
return reinterpret_cast<Botan::byte *>(a.data());
|
||||
}
|
||||
|
||||
inline QByteArray convertByteArray(const Botan::SecureVector<Botan::byte> &v)
|
||||
{
|
||||
return QByteArray(reinterpret_cast<const char *>(v.begin()), static_cast<int>(v.size()));
|
||||
}
|
||||
|
||||
inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName)
|
||||
{
|
||||
if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1)
|
||||
return "modp/ietf/1024";
|
||||
if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1)
|
||||
return "modp/ietf/2048";
|
||||
if (rfcAlgoName == SshCapabilities::EcdhNistp256)
|
||||
return "secp256r1";
|
||||
if (rfcAlgoName == SshCapabilities::EcdhNistp384)
|
||||
return "secp384r1";
|
||||
if (rfcAlgoName == SshCapabilities::EcdhNistp521)
|
||||
return "secp521r1";
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected key exchange algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(rfcAlgoName)));
|
||||
}
|
||||
|
||||
inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName)
|
||||
{
|
||||
if (rfcAlgoName == SshCapabilities::CryptAlgoAes128Cbc
|
||||
|| rfcAlgoName == SshCapabilities::CryptAlgoAes128Ctr) {
|
||||
return "AES-128";
|
||||
}
|
||||
if (rfcAlgoName == SshCapabilities::CryptAlgo3DesCbc
|
||||
|| rfcAlgoName == SshCapabilities::CryptAlgo3DesCtr) {
|
||||
return "TripleDES";
|
||||
}
|
||||
if (rfcAlgoName == SshCapabilities::CryptAlgoAes192Ctr) {
|
||||
return "AES-192";
|
||||
}
|
||||
if (rfcAlgoName == SshCapabilities::CryptAlgoAes256Ctr) {
|
||||
return "AES-256";
|
||||
}
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
|
||||
.arg(QString::fromLatin1(rfcAlgoName)));
|
||||
}
|
||||
|
||||
inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName)
|
||||
{
|
||||
if (rfcAlgoName == SshCapabilities::PubKeyDss)
|
||||
return "EMSA1(SHA-1)";
|
||||
if (rfcAlgoName == SshCapabilities::PubKeyRsa)
|
||||
return "EMSA3(SHA-1)";
|
||||
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa256)
|
||||
return "EMSA1_BSI(SHA-256)";
|
||||
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa384)
|
||||
return "EMSA1_BSI(SHA-384)";
|
||||
if (rfcAlgoName == SshCapabilities::PubKeyEcdsa521)
|
||||
return "EMSA1_BSI(SHA-512)";
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected host key algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(rfcAlgoName)));
|
||||
}
|
||||
|
||||
inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName)
|
||||
{
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha1)
|
||||
return "SHA-1";
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha256)
|
||||
return "SHA-256";
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha384)
|
||||
return "SHA-384";
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha512)
|
||||
return "SHA-512";
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(rfcAlgoName)));
|
||||
}
|
||||
|
||||
inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName)
|
||||
{
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha1)
|
||||
return 20;
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha256)
|
||||
return 32;
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha384)
|
||||
return 48;
|
||||
if (rfcAlgoName == SshCapabilities::HMacSha512)
|
||||
return 64;
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(rfcAlgoName)));
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,170 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshcapabilities_p.h"
|
||||
|
||||
#include "sshexception_p.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
namespace {
|
||||
QByteArray listAsByteArray(const QList<QByteArray> &list)
|
||||
{
|
||||
QByteArray array;
|
||||
foreach (const QByteArray &elem, list)
|
||||
array += elem + ',';
|
||||
if (!array.isEmpty())
|
||||
array.remove(array.count() - 1, 1);
|
||||
return array;
|
||||
}
|
||||
} // anonymous namspace
|
||||
|
||||
const QByteArray SshCapabilities::DiffieHellmanGroup1Sha1("diffie-hellman-group1-sha1");
|
||||
const QByteArray SshCapabilities::DiffieHellmanGroup14Sha1("diffie-hellman-group14-sha1");
|
||||
const QByteArray SshCapabilities::EcdhKexNamePrefix("ecdh-sha2-nistp");
|
||||
const QByteArray SshCapabilities::EcdhNistp256 = EcdhKexNamePrefix + "256";
|
||||
const QByteArray SshCapabilities::EcdhNistp384 = EcdhKexNamePrefix + "384";
|
||||
const QByteArray SshCapabilities::EcdhNistp521 = EcdhKexNamePrefix + "521";
|
||||
const QList<QByteArray> SshCapabilities::KeyExchangeMethods = QList<QByteArray>()
|
||||
<< SshCapabilities::EcdhNistp256
|
||||
<< SshCapabilities::EcdhNistp384
|
||||
<< SshCapabilities::EcdhNistp521
|
||||
<< SshCapabilities::DiffieHellmanGroup1Sha1
|
||||
<< SshCapabilities::DiffieHellmanGroup14Sha1;
|
||||
|
||||
const QByteArray SshCapabilities::PubKeyDss("ssh-dss");
|
||||
const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa");
|
||||
const QByteArray SshCapabilities::PubKeyEcdsaPrefix("ecdsa-sha2-nistp");
|
||||
const QByteArray SshCapabilities::PubKeyEcdsa256 = SshCapabilities::PubKeyEcdsaPrefix + "256";
|
||||
const QByteArray SshCapabilities::PubKeyEcdsa384 = SshCapabilities::PubKeyEcdsaPrefix + "384";
|
||||
const QByteArray SshCapabilities::PubKeyEcdsa521 = SshCapabilities::PubKeyEcdsaPrefix + "521";
|
||||
const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms = QList<QByteArray>()
|
||||
<< SshCapabilities::PubKeyEcdsa256
|
||||
<< SshCapabilities::PubKeyEcdsa384
|
||||
<< SshCapabilities::PubKeyEcdsa521
|
||||
<< SshCapabilities::PubKeyRsa
|
||||
<< SshCapabilities::PubKeyDss;
|
||||
|
||||
const QByteArray SshCapabilities::CryptAlgo3DesCbc("3des-cbc");
|
||||
const QByteArray SshCapabilities::CryptAlgo3DesCtr("3des-ctr");
|
||||
const QByteArray SshCapabilities::CryptAlgoAes128Cbc("aes128-cbc");
|
||||
const QByteArray SshCapabilities::CryptAlgoAes128Ctr("aes128-ctr");
|
||||
const QByteArray SshCapabilities::CryptAlgoAes192Ctr("aes192-ctr");
|
||||
const QByteArray SshCapabilities::CryptAlgoAes256Ctr("aes256-ctr");
|
||||
const QList<QByteArray> SshCapabilities::EncryptionAlgorithms
|
||||
= QList<QByteArray>() << SshCapabilities::CryptAlgoAes256Ctr
|
||||
<< SshCapabilities::CryptAlgoAes192Ctr
|
||||
<< SshCapabilities::CryptAlgoAes128Ctr
|
||||
<< SshCapabilities::CryptAlgo3DesCtr
|
||||
<< SshCapabilities::CryptAlgoAes128Cbc
|
||||
<< SshCapabilities::CryptAlgo3DesCbc;
|
||||
|
||||
const QByteArray SshCapabilities::HMacSha1("hmac-sha1");
|
||||
const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96");
|
||||
const QByteArray SshCapabilities::HMacSha256("hmac-sha2-256");
|
||||
const QByteArray SshCapabilities::HMacSha384("hmac-sha2-384");
|
||||
const QByteArray SshCapabilities::HMacSha512("hmac-sha2-512");
|
||||
const QList<QByteArray> SshCapabilities::MacAlgorithms
|
||||
= QList<QByteArray>() /* << SshCapabilities::HMacSha196 */
|
||||
<< SshCapabilities::HMacSha256
|
||||
<< SshCapabilities::HMacSha384
|
||||
<< SshCapabilities::HMacSha512
|
||||
<< SshCapabilities::HMacSha1;
|
||||
|
||||
const QList<QByteArray> SshCapabilities::CompressionAlgorithms
|
||||
= QList<QByteArray>() << "none";
|
||||
|
||||
const QByteArray SshCapabilities::SshConnectionService("ssh-connection");
|
||||
|
||||
QList<QByteArray> SshCapabilities::commonCapabilities(const QList<QByteArray> &myCapabilities,
|
||||
const QList<QByteArray> &serverCapabilities)
|
||||
{
|
||||
QList<QByteArray> capabilities;
|
||||
foreach (const QByteArray &myCapability, myCapabilities) {
|
||||
if (serverCapabilities.contains(myCapability))
|
||||
capabilities << myCapability;
|
||||
}
|
||||
|
||||
if (!capabilities.isEmpty())
|
||||
return capabilities;
|
||||
|
||||
throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
"Server and client capabilities do not match.",
|
||||
QCoreApplication::translate("SshConnection",
|
||||
"Server and client capabilities don't match. "
|
||||
"Client list was: %1.\nServer list was %2.")
|
||||
.arg(QString::fromLocal8Bit(listAsByteArray(myCapabilities).data()))
|
||||
.arg(QString::fromLocal8Bit(listAsByteArray(serverCapabilities).data())));
|
||||
|
||||
}
|
||||
|
||||
QByteArray SshCapabilities::findBestMatch(const QList<QByteArray> &myCapabilities,
|
||||
const QList<QByteArray> &serverCapabilities)
|
||||
{
|
||||
return commonCapabilities(myCapabilities, serverCapabilities).first();
|
||||
}
|
||||
|
||||
int SshCapabilities::ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo)
|
||||
{
|
||||
if (ecdsaAlgo == PubKeyEcdsa256)
|
||||
return 32;
|
||||
if (ecdsaAlgo == PubKeyEcdsa384)
|
||||
return 48;
|
||||
if (ecdsaAlgo == PubKeyEcdsa521)
|
||||
return 66;
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(ecdsaAlgo)));
|
||||
}
|
||||
|
||||
QByteArray SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes)
|
||||
{
|
||||
if (keyWidthInBytes <= 32)
|
||||
return PubKeyEcdsa256;
|
||||
if (keyWidthInBytes <= 48)
|
||||
return PubKeyEcdsa384;
|
||||
if (keyWidthInBytes <= 66)
|
||||
return PubKeyEcdsa521;
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa key size (%1 bytes)")
|
||||
.arg(keyWidthInBytes));
|
||||
}
|
||||
|
||||
const char *SshCapabilities::oid(const QByteArray &ecdsaAlgo)
|
||||
{
|
||||
if (ecdsaAlgo == PubKeyEcdsa256)
|
||||
return "secp256r1";
|
||||
if (ecdsaAlgo == PubKeyEcdsa384)
|
||||
return "secp384r1";
|
||||
if (ecdsaAlgo == PubKeyEcdsa521)
|
||||
return "secp521r1";
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected ecdsa algorithm \"%1\"")
|
||||
.arg(QString::fromLatin1(ecdsaAlgo)));
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,83 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SshCapabilities
|
||||
{
|
||||
public:
|
||||
static const QByteArray DiffieHellmanGroup1Sha1;
|
||||
static const QByteArray DiffieHellmanGroup14Sha1;
|
||||
static const QByteArray EcdhKexNamePrefix;
|
||||
static const QByteArray EcdhNistp256;
|
||||
static const QByteArray EcdhNistp384;
|
||||
static const QByteArray EcdhNistp521; // sic
|
||||
static const QList<QByteArray> KeyExchangeMethods;
|
||||
|
||||
static const QByteArray PubKeyDss;
|
||||
static const QByteArray PubKeyRsa;
|
||||
static const QByteArray PubKeyEcdsaPrefix;
|
||||
static const QByteArray PubKeyEcdsa256;
|
||||
static const QByteArray PubKeyEcdsa384;
|
||||
static const QByteArray PubKeyEcdsa521;
|
||||
static const QList<QByteArray> PublicKeyAlgorithms;
|
||||
|
||||
static const QByteArray CryptAlgo3DesCbc;
|
||||
static const QByteArray CryptAlgo3DesCtr;
|
||||
static const QByteArray CryptAlgoAes128Cbc;
|
||||
static const QByteArray CryptAlgoAes128Ctr;
|
||||
static const QByteArray CryptAlgoAes192Ctr;
|
||||
static const QByteArray CryptAlgoAes256Ctr;
|
||||
static const QList<QByteArray> EncryptionAlgorithms;
|
||||
|
||||
static const QByteArray HMacSha1;
|
||||
static const QByteArray HMacSha196;
|
||||
static const QByteArray HMacSha256;
|
||||
static const QByteArray HMacSha384;
|
||||
static const QByteArray HMacSha512;
|
||||
static const QList<QByteArray> MacAlgorithms;
|
||||
|
||||
static const QList<QByteArray> CompressionAlgorithms;
|
||||
|
||||
static const QByteArray SshConnectionService;
|
||||
|
||||
static QList<QByteArray> commonCapabilities(const QList<QByteArray> &myCapabilities,
|
||||
const QList<QByteArray> &serverCapabilities);
|
||||
static QByteArray findBestMatch(const QList<QByteArray> &myCapabilities,
|
||||
const QList<QByteArray> &serverCapabilities);
|
||||
|
||||
static int ecdsaIntegerWidthInBytes(const QByteArray &ecdsaAlgo);
|
||||
static QByteArray ecdsaPubKeyAlgoForKeyWidth(int keyWidthInBytes);
|
||||
static const char *oid(const QByteArray &ecdsaAlgo);
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,280 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshchannel_p.h"
|
||||
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
// "Payload length" (RFC 4253, 6.1), i.e. minus packet type, channel number
|
||||
// and length field for string.
|
||||
const quint32 MinMaxPacketSize = 32768 - sizeof(quint32) - sizeof(quint32) - 1;
|
||||
|
||||
const quint32 NoChannel = 0xffffffffu;
|
||||
|
||||
AbstractSshChannel::AbstractSshChannel(quint32 channelId,
|
||||
SshSendFacility &sendFacility)
|
||||
: m_sendFacility(sendFacility),
|
||||
m_localChannel(channelId), m_remoteChannel(NoChannel),
|
||||
m_localWindowSize(initialWindowSize()), m_remoteWindowSize(0),
|
||||
m_state(Inactive)
|
||||
{
|
||||
m_timeoutTimer.setSingleShot(true);
|
||||
connect(&m_timeoutTimer, &QTimer::timeout, this, &AbstractSshChannel::timeout);
|
||||
}
|
||||
|
||||
AbstractSshChannel::~AbstractSshChannel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AbstractSshChannel::setChannelState(ChannelState state)
|
||||
{
|
||||
m_state = state;
|
||||
if (state == Closed)
|
||||
closeHook();
|
||||
}
|
||||
|
||||
void AbstractSshChannel::requestSessionStart()
|
||||
{
|
||||
// Note: We are just being paranoid here about the Botan exceptions,
|
||||
// which are extremely unlikely to happen, because if there was a problem
|
||||
// with our cryptography stuff, it would have hit us before, on
|
||||
// establishing the connection.
|
||||
try {
|
||||
m_sendFacility.sendSessionPacket(m_localChannel, initialWindowSize(), maxPacketSize());
|
||||
setChannelState(SessionRequested);
|
||||
m_timeoutTimer.start(ReplyTimeout);
|
||||
} catch (const std::exception &e) {
|
||||
qCWarning(sshLog, "Botan error: %s", e.what());
|
||||
closeChannel();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSshChannel::sendData(const QByteArray &data)
|
||||
{
|
||||
try {
|
||||
m_sendBuffer += data;
|
||||
flushSendBuffer();
|
||||
} catch (const std::exception &e) {
|
||||
qCWarning(sshLog, "Botan error: %s", e.what());
|
||||
closeChannel();
|
||||
}
|
||||
}
|
||||
|
||||
quint32 AbstractSshChannel::initialWindowSize()
|
||||
{
|
||||
return maxPacketSize();
|
||||
}
|
||||
|
||||
quint32 AbstractSshChannel::maxPacketSize()
|
||||
{
|
||||
return 16 * 1024 * 1024;
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleWindowAdjust(quint32 bytesToAdd)
|
||||
{
|
||||
checkChannelActive();
|
||||
|
||||
const quint64 newValue = m_remoteWindowSize + bytesToAdd;
|
||||
if (newValue > 0xffffffffu) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Illegal window size requested.");
|
||||
}
|
||||
|
||||
m_remoteWindowSize = newValue;
|
||||
flushSendBuffer();
|
||||
}
|
||||
|
||||
void AbstractSshChannel::flushSendBuffer()
|
||||
{
|
||||
while (true) {
|
||||
const quint32 bytesToSend = qMin(m_remoteMaxPacketSize,
|
||||
qMin<quint32>(m_remoteWindowSize, m_sendBuffer.size()));
|
||||
if (bytesToSend == 0)
|
||||
break;
|
||||
const QByteArray &data = m_sendBuffer.left(bytesToSend);
|
||||
m_sendFacility.sendChannelDataPacket(m_remoteChannel, data);
|
||||
m_sendBuffer.remove(0, bytesToSend);
|
||||
m_remoteWindowSize -= bytesToSend;
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleOpenSuccess(quint32 remoteChannelId,
|
||||
quint32 remoteWindowSize, quint32 remoteMaxPacketSize)
|
||||
{
|
||||
const ChannelState oldState = m_state;
|
||||
switch (oldState) {
|
||||
case CloseRequested: // closeChannel() was called while we were in SessionRequested state
|
||||
case SessionRequested:
|
||||
break; // Ok, continue.
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
|
||||
}
|
||||
|
||||
m_timeoutTimer.stop();
|
||||
|
||||
if (remoteMaxPacketSize < MinMaxPacketSize) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Maximum packet size too low.");
|
||||
}
|
||||
|
||||
qCDebug(sshLog, "Channel opened. remote channel id: %u, remote window size: %u, "
|
||||
"remote max packet size: %u",
|
||||
remoteChannelId, remoteWindowSize, remoteMaxPacketSize);
|
||||
m_remoteChannel = remoteChannelId;
|
||||
m_remoteWindowSize = remoteWindowSize;
|
||||
m_remoteMaxPacketSize = remoteMaxPacketSize;
|
||||
setChannelState(SessionEstablished);
|
||||
if (oldState == CloseRequested)
|
||||
closeChannel();
|
||||
else
|
||||
handleOpenSuccessInternal();
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleOpenFailure(const QString &reason)
|
||||
{
|
||||
switch (m_state) {
|
||||
case SessionRequested:
|
||||
break; // Ok, continue.
|
||||
case CloseRequested:
|
||||
return; // Late server reply; we requested a channel close in the meantime.
|
||||
default:
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
|
||||
}
|
||||
|
||||
m_timeoutTimer.stop();
|
||||
|
||||
qCDebug(sshLog, "Channel open request failed for channel %u", m_localChannel);
|
||||
handleOpenFailureInternal(reason);
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleChannelEof()
|
||||
{
|
||||
if (m_state == Inactive || m_state == Closed) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_EOF message.");
|
||||
}
|
||||
m_localWindowSize = 0;
|
||||
emit eof();
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleChannelClose()
|
||||
{
|
||||
qCDebug(sshLog, "Receiving CLOSE for channel %u", m_localChannel);
|
||||
if (channelState() == Inactive || channelState() == Closed) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected SSH_MSG_CHANNEL_CLOSE message.");
|
||||
}
|
||||
closeChannel();
|
||||
setChannelState(Closed);
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleChannelData(const QByteArray &data)
|
||||
{
|
||||
const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
|
||||
handleChannelDataInternal(bytesToDeliver == data.size()
|
||||
? data : data.left(bytesToDeliver));
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleChannelExtendedData(quint32 type, const QByteArray &data)
|
||||
{
|
||||
const int bytesToDeliver = handleChannelOrExtendedChannelData(data);
|
||||
handleChannelExtendedDataInternal(type, bytesToDeliver == data.size()
|
||||
? data : data.left(bytesToDeliver));
|
||||
}
|
||||
|
||||
void AbstractSshChannel::handleChannelRequest(const SshIncomingPacket &packet)
|
||||
{
|
||||
checkChannelActive();
|
||||
const QByteArray &requestType = packet.extractChannelRequestType();
|
||||
if (requestType == SshIncomingPacket::ExitStatusType)
|
||||
handleExitStatus(packet.extractChannelExitStatus());
|
||||
else if (requestType == SshIncomingPacket::ExitSignalType)
|
||||
handleExitSignal(packet.extractChannelExitSignal());
|
||||
else if (requestType != "eow@openssh.com") // Suppress warning for this one, as it's sent all the time.
|
||||
qCWarning(sshLog, "Ignoring unknown request type '%s'", requestType.data());
|
||||
}
|
||||
|
||||
int AbstractSshChannel::handleChannelOrExtendedChannelData(const QByteArray &data)
|
||||
{
|
||||
checkChannelActive();
|
||||
|
||||
const int bytesToDeliver = qMin<quint32>(data.size(), maxDataSize());
|
||||
if (bytesToDeliver != data.size())
|
||||
qCWarning(sshLog, "Misbehaving server does not respect local window, clipping.");
|
||||
|
||||
m_localWindowSize -= bytesToDeliver;
|
||||
if (m_localWindowSize < maxPacketSize()) {
|
||||
m_localWindowSize += maxPacketSize();
|
||||
m_sendFacility.sendWindowAdjustPacket(m_remoteChannel, maxPacketSize());
|
||||
}
|
||||
return bytesToDeliver;
|
||||
}
|
||||
|
||||
void AbstractSshChannel::closeChannel()
|
||||
{
|
||||
if (m_state == CloseRequested) {
|
||||
m_timeoutTimer.stop();
|
||||
} else if (m_state != Closed) {
|
||||
if (m_state == Inactive) {
|
||||
setChannelState(Closed);
|
||||
} else {
|
||||
const ChannelState oldState = m_state;
|
||||
setChannelState(CloseRequested);
|
||||
if (m_remoteChannel != NoChannel) {
|
||||
m_sendFacility.sendChannelEofPacket(m_remoteChannel);
|
||||
m_sendFacility.sendChannelClosePacket(m_remoteChannel);
|
||||
} else {
|
||||
QSSH_ASSERT(oldState == SessionRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSshChannel::checkChannelActive()
|
||||
{
|
||||
if (channelState() == Inactive || channelState() == Closed)
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Channel not open.");
|
||||
}
|
||||
|
||||
quint32 AbstractSshChannel::maxDataSize() const
|
||||
{
|
||||
return qMin(m_localWindowSize, maxPacketSize());
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,117 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
struct SshChannelExitSignal;
|
||||
struct SshChannelExitStatus;
|
||||
class SshIncomingPacket;
|
||||
class SshSendFacility;
|
||||
|
||||
class AbstractSshChannel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ChannelState {
|
||||
Inactive, SessionRequested, SessionEstablished, CloseRequested, Closed
|
||||
};
|
||||
|
||||
quint32 localChannelId() const { return m_localChannel; }
|
||||
quint32 remoteChannel() const { return m_remoteChannel; }
|
||||
|
||||
virtual void handleChannelSuccess() = 0;
|
||||
virtual void handleChannelFailure() = 0;
|
||||
|
||||
void handleOpenSuccess(quint32 remoteChannelId, quint32 remoteWindowSize,
|
||||
quint32 remoteMaxPacketSize);
|
||||
void handleOpenFailure(const QString &reason);
|
||||
void handleWindowAdjust(quint32 bytesToAdd);
|
||||
void handleChannelEof();
|
||||
void handleChannelClose();
|
||||
void handleChannelData(const QByteArray &data);
|
||||
void handleChannelExtendedData(quint32 type, const QByteArray &data);
|
||||
void handleChannelRequest(const SshIncomingPacket &packet);
|
||||
|
||||
void closeChannel();
|
||||
|
||||
virtual ~AbstractSshChannel();
|
||||
|
||||
static const int ReplyTimeout = 10000; // milli seconds
|
||||
ChannelState channelState() const { return m_state; }
|
||||
|
||||
signals:
|
||||
void timeout();
|
||||
void eof();
|
||||
|
||||
protected:
|
||||
AbstractSshChannel(quint32 channelId, SshSendFacility &sendFacility);
|
||||
|
||||
void setChannelState(ChannelState state);
|
||||
|
||||
void requestSessionStart();
|
||||
void sendData(const QByteArray &data);
|
||||
|
||||
static quint32 initialWindowSize();
|
||||
static quint32 maxPacketSize();
|
||||
|
||||
quint32 maxDataSize() const;
|
||||
void checkChannelActive();
|
||||
|
||||
SshSendFacility &m_sendFacility;
|
||||
QTimer m_timeoutTimer;
|
||||
|
||||
private:
|
||||
virtual void handleOpenSuccessInternal() = 0;
|
||||
virtual void handleOpenFailureInternal(const QString &reason) = 0;
|
||||
virtual void handleChannelDataInternal(const QByteArray &data) = 0;
|
||||
virtual void handleChannelExtendedDataInternal(quint32 type,
|
||||
const QByteArray &data) = 0;
|
||||
virtual void handleExitStatus(const SshChannelExitStatus &exitStatus) = 0;
|
||||
virtual void handleExitSignal(const SshChannelExitSignal &signal) = 0;
|
||||
|
||||
virtual void closeHook() = 0;
|
||||
|
||||
void flushSendBuffer();
|
||||
int handleChannelOrExtendedChannelData(const QByteArray &data);
|
||||
|
||||
const quint32 m_localChannel;
|
||||
quint32 m_remoteChannel;
|
||||
quint32 m_localWindowSize;
|
||||
quint32 m_remoteWindowSize;
|
||||
quint32 m_remoteMaxPacketSize;
|
||||
ChannelState m_state;
|
||||
QByteArray m_sendBuffer;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,328 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshchannelmanager_p.h"
|
||||
|
||||
#include "sftpchannel.h"
|
||||
#include "sftpchannel_p.h"
|
||||
#include "sshdirecttcpiptunnel.h"
|
||||
#include "sshdirecttcpiptunnel_p.h"
|
||||
#include "sshforwardedtcpiptunnel.h"
|
||||
#include "sshforwardedtcpiptunnel_p.h"
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshremoteprocess.h"
|
||||
#include "sshremoteprocess_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
#include "sshtcpipforwardserver.h"
|
||||
#include "sshtcpipforwardserver_p.h"
|
||||
|
||||
#include <QList>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
SshChannelManager::SshChannelManager(SshSendFacility &sendFacility,
|
||||
QObject *parent)
|
||||
: QObject(parent), m_sendFacility(sendFacility), m_nextLocalChannelId(0)
|
||||
{
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelRequest(const SshIncomingPacket &packet)
|
||||
{
|
||||
lookupChannel(packet.extractRecipientChannel())
|
||||
->handleChannelRequest(packet);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelOpen(const SshIncomingPacket &packet)
|
||||
{
|
||||
SshChannelOpen channelOpen = packet.extractChannelOpen();
|
||||
|
||||
SshTcpIpForwardServer::Ptr server;
|
||||
|
||||
foreach (const SshTcpIpForwardServer::Ptr &candidate, m_listeningForwardServers) {
|
||||
if (candidate->port() == channelOpen.remotePort
|
||||
&& candidate->bindAddress().toUtf8() == channelOpen.remoteAddress) {
|
||||
server = candidate;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (server.isNull()) {
|
||||
// Apparently the server knows a remoteAddress we are not aware of. There are plenty of ways
|
||||
// to make that happen: /etc/hosts on the server, different writings for localhost,
|
||||
// different DNS servers, ...
|
||||
// Rather than trying to figure that out, we just use the first listening forwarder with the
|
||||
// same port.
|
||||
foreach (const SshTcpIpForwardServer::Ptr &candidate, m_listeningForwardServers) {
|
||||
if (candidate->port() == channelOpen.remotePort) {
|
||||
server = candidate;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (server.isNull()) {
|
||||
SshOpenFailureType reason = (channelOpen.remotePort == 0) ?
|
||||
SSH_OPEN_UNKNOWN_CHANNEL_TYPE : SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
|
||||
try {
|
||||
m_sendFacility.sendChannelOpenFailurePacket(channelOpen.remoteChannel, reason,
|
||||
QByteArray());
|
||||
} catch (const std::exception &e) {
|
||||
qCWarning(sshLog, "Botan error: %s", e.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SshForwardedTcpIpTunnel::Ptr tunnel(new SshForwardedTcpIpTunnel(m_nextLocalChannelId++,
|
||||
m_sendFacility));
|
||||
tunnel->d->handleOpenSuccess(channelOpen.remoteChannel, channelOpen.remoteWindowSize,
|
||||
channelOpen.remoteMaxPacketSize);
|
||||
tunnel->open(QIODevice::ReadWrite);
|
||||
server->setNewConnection(tunnel);
|
||||
insertChannel(tunnel->d, tunnel);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelOpenFailure(const SshIncomingPacket &packet)
|
||||
{
|
||||
const SshChannelOpenFailure &failure = packet.extractChannelOpenFailure();
|
||||
ChannelIterator it = lookupChannelAsIterator(failure.localChannel);
|
||||
try {
|
||||
it.value()->handleOpenFailure(failure.reasonString);
|
||||
} catch (const SshServerException &e) {
|
||||
removeChannel(it);
|
||||
throw e;
|
||||
}
|
||||
removeChannel(it);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelOpenConfirmation(const SshIncomingPacket &packet)
|
||||
{
|
||||
const SshChannelOpenConfirmation &confirmation
|
||||
= packet.extractChannelOpenConfirmation();
|
||||
lookupChannel(confirmation.localChannel)->handleOpenSuccess(confirmation.remoteChannel,
|
||||
confirmation.remoteWindowSize, confirmation.remoteMaxPacketSize);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelSuccess(const SshIncomingPacket &packet)
|
||||
{
|
||||
lookupChannel(packet.extractRecipientChannel())->handleChannelSuccess();
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelFailure(const SshIncomingPacket &packet)
|
||||
{
|
||||
lookupChannel(packet.extractRecipientChannel())->handleChannelFailure();
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelWindowAdjust(const SshIncomingPacket &packet)
|
||||
{
|
||||
const SshChannelWindowAdjust adjust = packet.extractWindowAdjust();
|
||||
lookupChannel(adjust.localChannel)->handleWindowAdjust(adjust.bytesToAdd);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelData(const SshIncomingPacket &packet)
|
||||
{
|
||||
const SshChannelData &data = packet.extractChannelData();
|
||||
lookupChannel(data.localChannel)->handleChannelData(data.data);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelExtendedData(const SshIncomingPacket &packet)
|
||||
{
|
||||
const SshChannelExtendedData &data = packet.extractChannelExtendedData();
|
||||
lookupChannel(data.localChannel)->handleChannelExtendedData(data.type, data.data);
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelEof(const SshIncomingPacket &packet)
|
||||
{
|
||||
AbstractSshChannel * const channel
|
||||
= lookupChannel(packet.extractRecipientChannel(), true);
|
||||
if (channel)
|
||||
channel->handleChannelEof();
|
||||
}
|
||||
|
||||
void SshChannelManager::handleChannelClose(const SshIncomingPacket &packet)
|
||||
{
|
||||
const quint32 channelId = packet.extractRecipientChannel();
|
||||
|
||||
ChannelIterator it = lookupChannelAsIterator(channelId, true);
|
||||
if (it != m_channels.end()) {
|
||||
it.value()->handleChannelClose();
|
||||
removeChannel(it);
|
||||
}
|
||||
}
|
||||
|
||||
void SshChannelManager::handleRequestSuccess(const SshIncomingPacket &packet)
|
||||
{
|
||||
if (m_waitingForwardServers.isEmpty()) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected request success packet.",
|
||||
tr("Unexpected request success packet."));
|
||||
}
|
||||
SshTcpIpForwardServer::Ptr server = m_waitingForwardServers.takeFirst();
|
||||
if (server->state() == SshTcpIpForwardServer::Closing) {
|
||||
server->setClosed();
|
||||
} else if (server->state() == SshTcpIpForwardServer::Initializing) {
|
||||
quint16 port = server->port();
|
||||
if (port == 0)
|
||||
port = packet.extractRequestSuccess().bindPort;
|
||||
server->setListening(port);
|
||||
m_listeningForwardServers.append(server);
|
||||
} else {
|
||||
QSSH_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SshChannelManager::handleRequestFailure(const SshIncomingPacket &packet)
|
||||
{
|
||||
Q_UNUSED(packet);
|
||||
if (m_waitingForwardServers.isEmpty()) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected request failure packet.",
|
||||
tr("Unexpected request failure packet."));
|
||||
}
|
||||
SshTcpIpForwardServer::Ptr tunnel = m_waitingForwardServers.takeFirst();
|
||||
tunnel->setClosed();
|
||||
}
|
||||
|
||||
SshChannelManager::ChannelIterator SshChannelManager::lookupChannelAsIterator(quint32 channelId,
|
||||
bool allowNotFound)
|
||||
{
|
||||
ChannelIterator it = m_channels.find(channelId);
|
||||
if (it == m_channels.end() && !allowNotFound) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid channel id.",
|
||||
tr("Invalid channel id %1").arg(channelId));
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
AbstractSshChannel *SshChannelManager::lookupChannel(quint32 channelId,
|
||||
bool allowNotFound)
|
||||
{
|
||||
ChannelIterator it = lookupChannelAsIterator(channelId, allowNotFound);
|
||||
return it == m_channels.end() ? 0 : it.value();
|
||||
}
|
||||
|
||||
QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteProcess(const QByteArray &command)
|
||||
{
|
||||
SshRemoteProcess::Ptr proc(new SshRemoteProcess(command, m_nextLocalChannelId++, m_sendFacility));
|
||||
insertChannel(proc->d, proc);
|
||||
return proc;
|
||||
}
|
||||
|
||||
QSsh::SshRemoteProcess::Ptr SshChannelManager::createRemoteShell()
|
||||
{
|
||||
SshRemoteProcess::Ptr proc(new SshRemoteProcess(m_nextLocalChannelId++, m_sendFacility));
|
||||
insertChannel(proc->d, proc);
|
||||
return proc;
|
||||
}
|
||||
|
||||
QSsh::SftpChannel::Ptr SshChannelManager::createSftpChannel()
|
||||
{
|
||||
SftpChannel::Ptr sftp(new SftpChannel(m_nextLocalChannelId++, m_sendFacility));
|
||||
insertChannel(sftp->d, sftp);
|
||||
return sftp;
|
||||
}
|
||||
|
||||
SshDirectTcpIpTunnel::Ptr SshChannelManager::createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
|
||||
{
|
||||
SshDirectTcpIpTunnel::Ptr tunnel(new SshDirectTcpIpTunnel(m_nextLocalChannelId++,
|
||||
originatingHost, originatingPort, remoteHost, remotePort, m_sendFacility));
|
||||
insertChannel(tunnel->d, tunnel);
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
SshTcpIpForwardServer::Ptr SshChannelManager::createForwardServer(const QString &remoteHost,
|
||||
quint16 remotePort)
|
||||
{
|
||||
SshTcpIpForwardServer::Ptr server(new SshTcpIpForwardServer(remoteHost, remotePort,
|
||||
m_sendFacility));
|
||||
connect(server.data(), &SshTcpIpForwardServer::stateChanged,
|
||||
this, [this, server](SshTcpIpForwardServer::State state) {
|
||||
switch (state) {
|
||||
case SshTcpIpForwardServer::Closing:
|
||||
m_listeningForwardServers.removeOne(server);
|
||||
// fall through
|
||||
case SshTcpIpForwardServer::Initializing:
|
||||
m_waitingForwardServers.append(server);
|
||||
break;
|
||||
case SshTcpIpForwardServer::Listening:
|
||||
case SshTcpIpForwardServer::Inactive:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return server;
|
||||
}
|
||||
|
||||
void SshChannelManager::insertChannel(AbstractSshChannel *priv,
|
||||
const QSharedPointer<QObject> &pub)
|
||||
{
|
||||
connect(priv, &AbstractSshChannel::timeout, this, &SshChannelManager::timeout);
|
||||
m_channels.insert(priv->localChannelId(), priv);
|
||||
m_sessions.insert(priv, pub);
|
||||
}
|
||||
|
||||
int SshChannelManager::closeAllChannels(CloseAllMode mode)
|
||||
{
|
||||
int count = 0;
|
||||
for (ChannelIterator it = m_channels.begin(); it != m_channels.end(); ++it) {
|
||||
AbstractSshChannel * const channel = it.value();
|
||||
QSSH_ASSERT(channel->channelState() != AbstractSshChannel::Closed);
|
||||
if (channel->channelState() != AbstractSshChannel::CloseRequested) {
|
||||
channel->closeChannel();
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (mode == CloseAllAndReset) {
|
||||
m_channels.clear();
|
||||
m_sessions.clear();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int SshChannelManager::channelCount() const
|
||||
{
|
||||
return m_channels.count();
|
||||
}
|
||||
|
||||
void SshChannelManager::removeChannel(ChannelIterator it)
|
||||
{
|
||||
if (it == m_channels.end()) {
|
||||
throw SshClientException(SshInternalError,
|
||||
QLatin1String("Internal error: Unexpected channel lookup failure"));
|
||||
}
|
||||
const int removeCount = m_sessions.remove(it.value());
|
||||
if (removeCount != 1) {
|
||||
throw SshClientException(SshInternalError,
|
||||
QString::fromLatin1("Internal error: Unexpected session count %1 for channel.")
|
||||
.arg(removeCount));
|
||||
}
|
||||
m_channels.erase(it);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,99 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace QSsh {
|
||||
class SftpChannel;
|
||||
class SshDirectTcpIpTunnel;
|
||||
class SshRemoteProcess;
|
||||
class SshTcpIpForwardServer;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
class AbstractSshChannel;
|
||||
class SshIncomingPacket;
|
||||
class SshSendFacility;
|
||||
|
||||
class SshChannelManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SshChannelManager(SshSendFacility &sendFacility, QObject *parent);
|
||||
|
||||
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
|
||||
QSharedPointer<SshRemoteProcess> createRemoteShell();
|
||||
QSharedPointer<SftpChannel> createSftpChannel();
|
||||
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
|
||||
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
|
||||
quint16 remotePort);
|
||||
|
||||
int channelCount() const;
|
||||
enum CloseAllMode { CloseAllRegular, CloseAllAndReset };
|
||||
int closeAllChannels(CloseAllMode mode);
|
||||
|
||||
void handleChannelRequest(const SshIncomingPacket &packet);
|
||||
void handleChannelOpen(const SshIncomingPacket &packet);
|
||||
void handleChannelOpenFailure(const SshIncomingPacket &packet);
|
||||
void handleChannelOpenConfirmation(const SshIncomingPacket &packet);
|
||||
void handleChannelSuccess(const SshIncomingPacket &packet);
|
||||
void handleChannelFailure(const SshIncomingPacket &packet);
|
||||
void handleChannelWindowAdjust(const SshIncomingPacket &packet);
|
||||
void handleChannelData(const SshIncomingPacket &packet);
|
||||
void handleChannelExtendedData(const SshIncomingPacket &packet);
|
||||
void handleChannelEof(const SshIncomingPacket &packet);
|
||||
void handleChannelClose(const SshIncomingPacket &packet);
|
||||
void handleRequestSuccess(const SshIncomingPacket &packet);
|
||||
void handleRequestFailure(const SshIncomingPacket &packet);
|
||||
|
||||
signals:
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
typedef QHash<quint32, AbstractSshChannel *>::Iterator ChannelIterator;
|
||||
|
||||
ChannelIterator lookupChannelAsIterator(quint32 channelId,
|
||||
bool allowNotFound = false);
|
||||
AbstractSshChannel *lookupChannel(quint32 channelId,
|
||||
bool allowNotFound = false);
|
||||
void removeChannel(ChannelIterator it);
|
||||
void insertChannel(AbstractSshChannel *priv,
|
||||
const QSharedPointer<QObject> &pub);
|
||||
|
||||
SshSendFacility &m_sendFacility;
|
||||
QHash<quint32, AbstractSshChannel *> m_channels;
|
||||
QHash<AbstractSshChannel *, QSharedPointer<QObject> > m_sessions;
|
||||
quint32 m_nextLocalChannelId;
|
||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_waitingForwardServers;
|
||||
QList<QSharedPointer<SshTcpIpForwardServer>> m_listeningForwardServers;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,864 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshconnection.h"
|
||||
#include "sshconnection_p.h"
|
||||
|
||||
#include "sftpchannel.h"
|
||||
#include "sshcapabilities_p.h"
|
||||
#include "sshchannelmanager_p.h"
|
||||
#include "sshcryptofacility_p.h"
|
||||
#include "sshdirecttcpiptunnel.h"
|
||||
#include "sshtcpipforwardserver.h"
|
||||
#include "sshexception_p.h"
|
||||
#include "sshinit_p.h"
|
||||
#include "sshkeyexchange_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshremoteprocess.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkProxy>
|
||||
#include <QRegExp>
|
||||
#include <QTcpSocket>
|
||||
|
||||
/*!
|
||||
\class QSsh::SshConnection
|
||||
|
||||
\brief The SshConnection class provides an SSH connection, implementing
|
||||
protocol version 2.0.
|
||||
|
||||
It can spawn channels for remote execution and SFTP operations (version 3).
|
||||
It operates asynchronously (non-blocking) and is not thread-safe.
|
||||
*/
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
const QByteArray ClientId("SSH-2.0-QtCreator\r\n");
|
||||
|
||||
SshConnectionParameters::SshConnectionParameters() :
|
||||
timeout(0), authenticationType(AuthenticationTypePublicKey), port(0),
|
||||
hostKeyCheckingMode(SshHostKeyCheckingNone)
|
||||
{
|
||||
options |= SshIgnoreDefaultProxy;
|
||||
options |= SshEnableStrictConformanceChecks;
|
||||
}
|
||||
|
||||
static inline bool equals(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
|
||||
{
|
||||
return p1.host == p2.host && p1.userName == p2.userName
|
||||
&& p1.authenticationType == p2.authenticationType
|
||||
&& (p1.authenticationType == SshConnectionParameters::AuthenticationTypePassword ?
|
||||
p1.password == p2.password : p1.privateKeyFile == p2.privateKeyFile)
|
||||
&& p1.hostKeyCheckingMode == p2.hostKeyCheckingMode
|
||||
&& p1.timeout == p2.timeout && p1.port == p2.port;
|
||||
}
|
||||
|
||||
bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
|
||||
{
|
||||
return equals(p1, p2);
|
||||
}
|
||||
|
||||
bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2)
|
||||
{
|
||||
return !equals(p1, p2);
|
||||
}
|
||||
|
||||
|
||||
SshConnection::SshConnection(const SshConnectionParameters &serverInfo, QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
Internal::initSsh();
|
||||
qRegisterMetaType<QSsh::SshError>("QSsh::SshError");
|
||||
qRegisterMetaType<QSsh::SftpJobId>("QSsh::SftpJobId");
|
||||
qRegisterMetaType<QSsh::SftpFileInfo>("QSsh::SftpFileInfo");
|
||||
qRegisterMetaType<QList <QSsh::SftpFileInfo> >("QList<QSsh::SftpFileInfo>");
|
||||
|
||||
d = new Internal::SshConnectionPrivate(this, serverInfo);
|
||||
connect(d, &Internal::SshConnectionPrivate::connected, this, &SshConnection::connected,
|
||||
Qt::QueuedConnection);
|
||||
connect(d, &Internal::SshConnectionPrivate::dataAvailable, this,
|
||||
&SshConnection::dataAvailable, Qt::QueuedConnection);
|
||||
connect(d, &Internal::SshConnectionPrivate::disconnected, this, &SshConnection::disconnected,
|
||||
Qt::QueuedConnection);
|
||||
connect(d, &Internal::SshConnectionPrivate::error, this,
|
||||
&SshConnection::error, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void SshConnection::connectToHost()
|
||||
{
|
||||
d->connectToHost();
|
||||
}
|
||||
|
||||
void SshConnection::disconnectFromHost()
|
||||
{
|
||||
d->closeConnection(Internal::SSH_DISCONNECT_BY_APPLICATION, SshNoError, "",
|
||||
QString());
|
||||
}
|
||||
|
||||
SshConnection::State SshConnection::state() const
|
||||
{
|
||||
switch (d->state()) {
|
||||
case Internal::SocketUnconnected:
|
||||
return Unconnected;
|
||||
case Internal::ConnectionEstablished:
|
||||
return Connected;
|
||||
default:
|
||||
return Connecting;
|
||||
}
|
||||
}
|
||||
|
||||
SshError SshConnection::errorState() const
|
||||
{
|
||||
return d->errorState();
|
||||
}
|
||||
|
||||
QString SshConnection::errorString() const
|
||||
{
|
||||
return d->errorString();
|
||||
}
|
||||
|
||||
SshConnectionParameters SshConnection::connectionParameters() const
|
||||
{
|
||||
return d->m_connParams;
|
||||
}
|
||||
|
||||
SshConnectionInfo SshConnection::connectionInfo() const
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshConnectionInfo());
|
||||
|
||||
return SshConnectionInfo(d->m_socket->localAddress(), d->m_socket->localPort(),
|
||||
d->m_socket->peerAddress(), d->m_socket->peerPort());
|
||||
}
|
||||
|
||||
SshConnection::~SshConnection()
|
||||
{
|
||||
disconnect();
|
||||
disconnectFromHost();
|
||||
delete d;
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteProcess(const QByteArray &command)
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
|
||||
return d->createRemoteProcess(command);
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> SshConnection::createRemoteShell()
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SshRemoteProcess>());
|
||||
return d->createRemoteShell();
|
||||
}
|
||||
|
||||
QSharedPointer<SftpChannel> SshConnection::createSftpChannel()
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, QSharedPointer<SftpChannel>());
|
||||
return d->createSftpChannel();
|
||||
}
|
||||
|
||||
SshDirectTcpIpTunnel::Ptr SshConnection::createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshDirectTcpIpTunnel::Ptr());
|
||||
return d->createDirectTunnel(originatingHost, originatingPort, remoteHost, remotePort);
|
||||
}
|
||||
|
||||
QSharedPointer<SshTcpIpForwardServer> SshConnection::createForwardServer(const QString &remoteHost,
|
||||
quint16 remotePort)
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(state() == Connected, SshTcpIpForwardServer::Ptr());
|
||||
return d->createForwardServer(remoteHost, remotePort);
|
||||
}
|
||||
|
||||
int SshConnection::closeAllChannels()
|
||||
{
|
||||
try {
|
||||
return d->m_channelManager->closeAllChannels(Internal::SshChannelManager::CloseAllRegular);
|
||||
} catch (const std::exception &e) {
|
||||
qCWarning(Internal::sshLog, "%s: %s", Q_FUNC_INFO, e.what());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int SshConnection::channelCount() const
|
||||
{
|
||||
return d->m_channelManager->channelCount();
|
||||
}
|
||||
|
||||
namespace Internal {
|
||||
|
||||
SshConnectionPrivate::SshConnectionPrivate(SshConnection *conn,
|
||||
const SshConnectionParameters &serverInfo)
|
||||
: m_socket(new QTcpSocket(this)), m_state(SocketUnconnected),
|
||||
m_sendFacility(m_socket),
|
||||
m_channelManager(new SshChannelManager(m_sendFacility, this)),
|
||||
m_connParams(serverInfo), m_error(SshNoError), m_ignoreNextPacket(false),
|
||||
m_conn(conn)
|
||||
{
|
||||
setupPacketHandlers();
|
||||
m_socket->setProxy((m_connParams.options & SshIgnoreDefaultProxy)
|
||||
? QNetworkProxy::NoProxy : QNetworkProxy::DefaultProxy);
|
||||
m_timeoutTimer.setSingleShot(true);
|
||||
m_timeoutTimer.setInterval(m_connParams.timeout * 1000);
|
||||
m_keepAliveTimer.setSingleShot(true);
|
||||
m_keepAliveTimer.setInterval(10000);
|
||||
connect(m_channelManager, &SshChannelManager::timeout,
|
||||
this, &SshConnectionPrivate::handleTimeout);
|
||||
}
|
||||
|
||||
SshConnectionPrivate::~SshConnectionPrivate()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::setupPacketHandlers()
|
||||
{
|
||||
typedef SshConnectionPrivate This;
|
||||
|
||||
setupPacketHandler(SSH_MSG_KEXINIT, StateList() << SocketConnected
|
||||
<< ConnectionEstablished, &This::handleKeyExchangeInitPacket);
|
||||
setupPacketHandler(SSH_MSG_KEXDH_REPLY, StateList() << SocketConnected
|
||||
<< ConnectionEstablished, &This::handleKeyExchangeReplyPacket);
|
||||
|
||||
setupPacketHandler(SSH_MSG_NEWKEYS, StateList() << SocketConnected
|
||||
<< ConnectionEstablished, &This::handleNewKeysPacket);
|
||||
setupPacketHandler(SSH_MSG_SERVICE_ACCEPT,
|
||||
StateList() << UserAuthServiceRequested,
|
||||
&This::handleServiceAcceptPacket);
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePassword
|
||||
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
|
||||
StateList() << UserAuthRequested, &This::handlePasswordExpiredPacket);
|
||||
}
|
||||
setupPacketHandler(SSH_MSG_GLOBAL_REQUEST,
|
||||
StateList() << ConnectionEstablished, &This::handleGlobalRequest);
|
||||
|
||||
const StateList authReqList = StateList() << UserAuthRequested;
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_BANNER, authReqList,
|
||||
&This::handleUserAuthBannerPacket);
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_SUCCESS, authReqList,
|
||||
&This::handleUserAuthSuccessPacket);
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_FAILURE, authReqList,
|
||||
&This::handleUserAuthFailurePacket);
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeKeyboardInteractive
|
||||
|| m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods) {
|
||||
setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
|
||||
&This::handleUserAuthInfoRequestPacket);
|
||||
}
|
||||
|
||||
const StateList connectedList
|
||||
= StateList() << ConnectionEstablished;
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_REQUEST, connectedList,
|
||||
&This::handleChannelRequest);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_OPEN, connectedList,
|
||||
&This::handleChannelOpen);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_OPEN_FAILURE, connectedList,
|
||||
&This::handleChannelOpenFailure);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, connectedList,
|
||||
&This::handleChannelOpenConfirmation);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_SUCCESS, connectedList,
|
||||
&This::handleChannelSuccess);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_FAILURE, connectedList,
|
||||
&This::handleChannelFailure);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_WINDOW_ADJUST, connectedList,
|
||||
&This::handleChannelWindowAdjust);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_DATA, connectedList,
|
||||
&This::handleChannelData);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_EXTENDED_DATA, connectedList,
|
||||
&This::handleChannelExtendedData);
|
||||
|
||||
const StateList connectedOrClosedList
|
||||
= StateList() << SocketUnconnected << ConnectionEstablished;
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_EOF, connectedOrClosedList,
|
||||
&This::handleChannelEof);
|
||||
setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
|
||||
&This::handleChannelClose);
|
||||
|
||||
setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected
|
||||
<< UserAuthServiceRequested << UserAuthRequested
|
||||
<< ConnectionEstablished, &This::handleDisconnect);
|
||||
|
||||
setupPacketHandler(SSH_MSG_UNIMPLEMENTED,
|
||||
StateList() << ConnectionEstablished, &This::handleUnimplementedPacket);
|
||||
|
||||
setupPacketHandler(SSH_MSG_REQUEST_SUCCESS, connectedList,
|
||||
&This::handleRequestSuccess);
|
||||
setupPacketHandler(SSH_MSG_REQUEST_FAILURE, connectedList,
|
||||
&This::handleRequestFailure);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::setupPacketHandler(SshPacketType type,
|
||||
const SshConnectionPrivate::StateList &states,
|
||||
SshConnectionPrivate::PacketHandler handler)
|
||||
{
|
||||
m_packetHandlers.insert(type, HandlerInStates(states, handler));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleSocketConnected()
|
||||
{
|
||||
m_state = SocketConnected;
|
||||
sendData(ClientId);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleIncomingData()
|
||||
{
|
||||
if (m_state == SocketUnconnected)
|
||||
return; // For stuff queued in the event loop after we've called closeConnection();
|
||||
|
||||
try {
|
||||
if (!canUseSocket())
|
||||
return;
|
||||
m_incomingData += m_socket->readAll();
|
||||
qCDebug(sshLog, "state = %d, remote data size = %d", m_state, m_incomingData.count());
|
||||
if (m_serverId.isEmpty())
|
||||
handleServerId();
|
||||
handlePackets();
|
||||
} catch (const SshServerException &e) {
|
||||
closeConnection(e.error, SshProtocolError, e.errorStringServer,
|
||||
tr("SSH Protocol error: %1").arg(e.errorStringUser));
|
||||
} catch (const SshClientException &e) {
|
||||
closeConnection(SSH_DISCONNECT_BY_APPLICATION, e.error, "",
|
||||
e.errorString);
|
||||
} catch (const std::exception &e) {
|
||||
closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshInternalError, "",
|
||||
tr("Botan library exception: %1").arg(QString::fromLatin1(e.what())));
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 4253, 4.2.
|
||||
void SshConnectionPrivate::handleServerId()
|
||||
{
|
||||
qCDebug(sshLog, "%s: incoming data size = %d, incoming data = '%s'",
|
||||
Q_FUNC_INFO, m_incomingData.count(), m_incomingData.data());
|
||||
const int newLinePos = m_incomingData.indexOf('\n');
|
||||
if (newLinePos == -1)
|
||||
return; // Not enough data yet.
|
||||
|
||||
// Lines not starting with "SSH-" are ignored.
|
||||
if (!m_incomingData.startsWith("SSH-")) {
|
||||
m_incomingData.remove(0, newLinePos + 1);
|
||||
m_serverHasSentDataBeforeId = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newLinePos > 255 - 1) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Identification string too long.",
|
||||
tr("Server identification string is %n characters long, but the maximum "
|
||||
"allowed length is 255.", 0, newLinePos + 1));
|
||||
}
|
||||
|
||||
const bool hasCarriageReturn = m_incomingData.at(newLinePos - 1) == '\r';
|
||||
m_serverId = m_incomingData.left(newLinePos);
|
||||
if (hasCarriageReturn)
|
||||
m_serverId.chop(1);
|
||||
m_incomingData.remove(0, newLinePos + 1);
|
||||
|
||||
if (m_serverId.contains('\0')) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Identification string contains illegal NUL character.",
|
||||
tr("Server identification string contains illegal NUL character."));
|
||||
}
|
||||
|
||||
// "printable US-ASCII characters, with the exception of whitespace characters
|
||||
// and the minus sign"
|
||||
QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+");
|
||||
const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?").arg(legalString));
|
||||
if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Identification string is invalid.",
|
||||
tr("Server Identification string \"%1\" is invalid.")
|
||||
.arg(QString::fromLatin1(m_serverId)));
|
||||
}
|
||||
const QString serverProtoVersion = versionIdpattern.cap(1);
|
||||
if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
|
||||
"Invalid protocol version.",
|
||||
tr("Server protocol version is \"%1\", but needs to be 2.0 or 1.99.")
|
||||
.arg(serverProtoVersion));
|
||||
}
|
||||
|
||||
if (m_connParams.options & SshEnableStrictConformanceChecks) {
|
||||
if (serverProtoVersion == QLatin1String("2.0") && !hasCarriageReturn) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Identification string is invalid.",
|
||||
tr("Server identification string is invalid (missing carriage return)."));
|
||||
}
|
||||
|
||||
if (serverProtoVersion == QLatin1String("1.99") && m_serverHasSentDataBeforeId) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"No extra data preceding identification string allowed for 1.99.",
|
||||
tr("Server reports protocol version 1.99, but sends data "
|
||||
"before the identification string, which is not allowed."));
|
||||
}
|
||||
}
|
||||
|
||||
m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
|
||||
m_keyExchange->sendKexInitPacket(m_serverId);
|
||||
m_keyExchangeState = KexInitSent;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handlePackets()
|
||||
{
|
||||
m_incomingPacket.consumeData(m_incomingData);
|
||||
while (m_incomingPacket.isComplete()) {
|
||||
handleCurrentPacket();
|
||||
m_incomingPacket.clear();
|
||||
m_incomingPacket.consumeData(m_incomingData);
|
||||
}
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleCurrentPacket()
|
||||
{
|
||||
Q_ASSERT(m_incomingPacket.isComplete());
|
||||
Q_ASSERT(m_keyExchangeState == DhInitSent || !m_ignoreNextPacket);
|
||||
|
||||
if (m_ignoreNextPacket) {
|
||||
m_ignoreNextPacket = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QHash<SshPacketType, HandlerInStates>::ConstIterator it
|
||||
= m_packetHandlers.constFind(m_incomingPacket.type());
|
||||
if (it == m_packetHandlers.constEnd()) {
|
||||
m_sendFacility.sendMsgUnimplementedPacket(m_incomingPacket.serverSeqNr());
|
||||
return;
|
||||
}
|
||||
if (!it.value().first.contains(m_state)) {
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
(this->*it.value().second)();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleKeyExchangeInitPacket()
|
||||
{
|
||||
if (m_keyExchangeState != NoKeyExchange
|
||||
&& m_keyExchangeState != KexInitSent) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
}
|
||||
|
||||
// Server-initiated re-exchange.
|
||||
if (m_keyExchangeState == NoKeyExchange) {
|
||||
m_keyExchange.reset(new SshKeyExchange(m_connParams, m_sendFacility));
|
||||
m_keyExchange->sendKexInitPacket(m_serverId);
|
||||
}
|
||||
|
||||
// If the server sends a guessed packet, the guess must be wrong,
|
||||
// because the algorithms we support require us to initiate the
|
||||
// key exchange.
|
||||
if (m_keyExchange->sendDhInitPacket(m_incomingPacket))
|
||||
m_ignoreNextPacket = true;
|
||||
|
||||
m_keyExchangeState = DhInitSent;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleKeyExchangeReplyPacket()
|
||||
{
|
||||
if (m_keyExchangeState != DhInitSent) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
}
|
||||
|
||||
m_keyExchange->sendNewKeysPacket(m_incomingPacket,
|
||||
ClientId.left(ClientId.size() - 2));
|
||||
m_sendFacility.recreateKeys(*m_keyExchange);
|
||||
m_keyExchangeState = NewKeysSent;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleNewKeysPacket()
|
||||
{
|
||||
if (m_keyExchangeState != NewKeysSent) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
}
|
||||
|
||||
m_incomingPacket.recreateKeys(*m_keyExchange);
|
||||
m_keyExchange.reset();
|
||||
m_keyExchangeState = NoKeyExchange;
|
||||
|
||||
if (m_state == SocketConnected) {
|
||||
m_sendFacility.sendUserAuthServiceRequestPacket();
|
||||
m_state = UserAuthServiceRequested;
|
||||
}
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleServiceAcceptPacket()
|
||||
{
|
||||
switch (m_connParams.authenticationType) {
|
||||
case SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods:
|
||||
m_triedAllPasswordBasedMethods = false;
|
||||
// Fall-through.
|
||||
case SshConnectionParameters::AuthenticationTypePassword:
|
||||
m_sendFacility.sendUserAuthByPasswordRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService, m_connParams.password.toUtf8());
|
||||
break;
|
||||
case SshConnectionParameters::AuthenticationTypeKeyboardInteractive:
|
||||
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
break;
|
||||
case SshConnectionParameters::AuthenticationTypePublicKey:
|
||||
m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
break;
|
||||
}
|
||||
m_state = UserAuthRequested;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handlePasswordExpiredPacket()
|
||||
{
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& m_triedAllPasswordBasedMethods) {
|
||||
// This means we just tried to authorize via "keyboard-interactive", in which case
|
||||
// this type of packet is not allowed.
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
throw SshClientException(SshAuthenticationError, tr("Password expired."));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthInfoRequestPacket()
|
||||
{
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& !m_triedAllPasswordBasedMethods) {
|
||||
// This means we just tried to authorize via "password", in which case
|
||||
// this type of packet is not allowed.
|
||||
handleUnexpectedPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
const SshUserAuthInfoRequestPacket requestPacket
|
||||
= m_incomingPacket.extractUserAuthInfoRequest();
|
||||
QStringList responses;
|
||||
responses.reserve(requestPacket.prompts.count());
|
||||
|
||||
// Not very interactive, admittedly, but we don't want to be for now.
|
||||
for (int i = 0; i < requestPacket.prompts.count(); ++i)
|
||||
responses << m_connParams.password;
|
||||
m_sendFacility.sendUserAuthInfoResponsePacket(responses);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthBannerPacket()
|
||||
{
|
||||
emit dataAvailable(m_incomingPacket.extractUserAuthBanner().message);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUnexpectedPacket()
|
||||
{
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet.", tr("Unexpected packet of type %1.")
|
||||
.arg(m_incomingPacket.type()));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleGlobalRequest()
|
||||
{
|
||||
m_sendFacility.sendRequestFailurePacket();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthSuccessPacket()
|
||||
{
|
||||
m_state = ConnectionEstablished;
|
||||
m_timeoutTimer.stop();
|
||||
emit connected();
|
||||
m_lastInvalidMsgSeqNr = InvalidSeqNr;
|
||||
connect(&m_keepAliveTimer, &QTimer::timeout, this, &SshConnectionPrivate::sendKeepAlivePacket);
|
||||
m_keepAliveTimer.start();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUserAuthFailurePacket()
|
||||
{
|
||||
// TODO: Evaluate "authentications that can continue" field and act on it.
|
||||
if (m_connParams.authenticationType
|
||||
== SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
|
||||
&& !m_triedAllPasswordBasedMethods) {
|
||||
m_triedAllPasswordBasedMethods = true;
|
||||
m_sendFacility.sendUserAuthByKeyboardInteractiveRequestPacket(
|
||||
m_connParams.userName.toUtf8(),
|
||||
SshCapabilities::SshConnectionService);
|
||||
return;
|
||||
}
|
||||
|
||||
m_timeoutTimer.stop();
|
||||
const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey
|
||||
? tr("Server rejected key.") : tr("Server rejected password.");
|
||||
throw SshClientException(SshAuthenticationError, errorMsg);
|
||||
}
|
||||
void SshConnectionPrivate::handleDebugPacket()
|
||||
{
|
||||
const SshDebug &msg = m_incomingPacket.extractDebug();
|
||||
if (msg.display)
|
||||
emit dataAvailable(msg.message);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleUnimplementedPacket()
|
||||
{
|
||||
const SshUnimplemented &msg = m_incomingPacket.extractUnimplemented();
|
||||
if (msg.invalidMsgSeqNr != m_lastInvalidMsgSeqNr) {
|
||||
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Unexpected packet", tr("The server sent an unexpected SSH packet "
|
||||
"of type SSH_MSG_UNIMPLEMENTED."));
|
||||
}
|
||||
m_lastInvalidMsgSeqNr = InvalidSeqNr;
|
||||
m_timeoutTimer.stop();
|
||||
m_keepAliveTimer.start();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelRequest()
|
||||
{
|
||||
m_channelManager->handleChannelRequest(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelOpen()
|
||||
{
|
||||
m_channelManager->handleChannelOpen(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelOpenFailure()
|
||||
{
|
||||
m_channelManager->handleChannelOpenFailure(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelOpenConfirmation()
|
||||
{
|
||||
m_channelManager->handleChannelOpenConfirmation(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelSuccess()
|
||||
{
|
||||
m_channelManager->handleChannelSuccess(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelFailure()
|
||||
{
|
||||
m_channelManager->handleChannelFailure(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelWindowAdjust()
|
||||
{
|
||||
m_channelManager->handleChannelWindowAdjust(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelData()
|
||||
{
|
||||
m_channelManager->handleChannelData(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelExtendedData()
|
||||
{
|
||||
m_channelManager->handleChannelExtendedData(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelEof()
|
||||
{
|
||||
m_channelManager->handleChannelEof(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleChannelClose()
|
||||
{
|
||||
m_channelManager->handleChannelClose(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleDisconnect()
|
||||
{
|
||||
const SshDisconnect msg = m_incomingPacket.extractDisconnect();
|
||||
throw SshServerException(SSH_DISCONNECT_CONNECTION_LOST,
|
||||
"", tr("Server closed connection: %1").arg(msg.description));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleRequestSuccess()
|
||||
{
|
||||
m_channelManager->handleRequestSuccess(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleRequestFailure()
|
||||
{
|
||||
m_channelManager->handleRequestFailure(m_incomingPacket);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::sendData(const QByteArray &data)
|
||||
{
|
||||
if (canUseSocket())
|
||||
m_socket->write(data);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleSocketDisconnected()
|
||||
{
|
||||
closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
|
||||
"Connection closed unexpectedly.",
|
||||
tr("Connection closed unexpectedly."));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleSocketError()
|
||||
{
|
||||
if (m_error == SshNoError) {
|
||||
closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshSocketError,
|
||||
"Network error", m_socket->errorString());
|
||||
}
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::handleTimeout()
|
||||
{
|
||||
closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
|
||||
tr("Timeout waiting for reply from server."));
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::sendKeepAlivePacket()
|
||||
{
|
||||
// This type of message is not allowed during key exchange.
|
||||
if (m_keyExchangeState != NoKeyExchange) {
|
||||
m_keepAliveTimer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_lastInvalidMsgSeqNr == InvalidSeqNr);
|
||||
m_lastInvalidMsgSeqNr = m_sendFacility.nextClientSeqNr();
|
||||
m_sendFacility.sendInvalidPacket();
|
||||
m_timeoutTimer.start();
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::connectToHost()
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
|
||||
|
||||
m_incomingData.clear();
|
||||
m_incomingPacket.reset();
|
||||
m_sendFacility.reset();
|
||||
m_error = SshNoError;
|
||||
m_ignoreNextPacket = false;
|
||||
m_errorString.clear();
|
||||
m_serverId.clear();
|
||||
m_serverHasSentDataBeforeId = false;
|
||||
|
||||
try {
|
||||
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
|
||||
createPrivateKey();
|
||||
} catch (const SshClientException &ex) {
|
||||
m_error = ex.error;
|
||||
m_errorString = ex.errorString;
|
||||
emit error(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_socket, &QAbstractSocket::connected,
|
||||
this, &SshConnectionPrivate::handleSocketConnected);
|
||||
connect(m_socket, &QIODevice::readyRead,
|
||||
this, &SshConnectionPrivate::handleIncomingData);
|
||||
connect(m_socket,
|
||||
static_cast<void (QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
|
||||
this, &SshConnectionPrivate::handleSocketError);
|
||||
connect(m_socket, &QAbstractSocket::disconnected,
|
||||
this, &SshConnectionPrivate::handleSocketDisconnected);
|
||||
connect(&m_timeoutTimer, &QTimer::timeout, this, &SshConnectionPrivate::handleTimeout);
|
||||
m_state = SocketConnecting;
|
||||
m_keyExchangeState = NoKeyExchange;
|
||||
m_timeoutTimer.start();
|
||||
m_socket->connectToHost(m_connParams.host, m_connParams.port);
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::closeConnection(SshErrorCode sshError,
|
||||
SshError userError, const QByteArray &serverErrorString,
|
||||
const QString &userErrorString)
|
||||
{
|
||||
// Prevent endless loops by recursive exceptions.
|
||||
if (m_state == SocketUnconnected || m_error != SshNoError)
|
||||
return;
|
||||
|
||||
m_error = userError;
|
||||
m_errorString = userErrorString;
|
||||
m_timeoutTimer.stop();
|
||||
disconnect(m_socket, 0, this, 0);
|
||||
disconnect(&m_timeoutTimer, 0, this, 0);
|
||||
m_keepAliveTimer.stop();
|
||||
disconnect(&m_keepAliveTimer, 0, this, 0);
|
||||
try {
|
||||
m_channelManager->closeAllChannels(SshChannelManager::CloseAllAndReset);
|
||||
m_sendFacility.sendDisconnectPacket(sshError, serverErrorString);
|
||||
} catch (...) {} // Nothing sensible to be done here.
|
||||
if (m_error != SshNoError)
|
||||
emit error(userError);
|
||||
if (m_state == ConnectionEstablished)
|
||||
emit disconnected();
|
||||
if (canUseSocket())
|
||||
m_socket->disconnectFromHost();
|
||||
m_state = SocketUnconnected;
|
||||
}
|
||||
|
||||
bool SshConnectionPrivate::canUseSocket() const
|
||||
{
|
||||
return m_socket->isValid()
|
||||
&& m_socket->state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
void SshConnectionPrivate::createPrivateKey()
|
||||
{
|
||||
if (m_connParams.privateKeyFile.isEmpty())
|
||||
throw SshClientException(SshKeyFileError, tr("No private key file given."));
|
||||
QFile keyFile(m_connParams.privateKeyFile);
|
||||
if (!keyFile.open(QIODevice::ReadOnly)) {
|
||||
throw SshClientException(SshKeyFileError,
|
||||
tr("Private key file error: %1").arg(keyFile.errorString()));
|
||||
}
|
||||
m_sendFacility.createAuthenticationKey(keyFile.readAll());
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteProcess(const QByteArray &command)
|
||||
{
|
||||
return m_channelManager->createRemoteProcess(command);
|
||||
}
|
||||
|
||||
QSharedPointer<SshRemoteProcess> SshConnectionPrivate::createRemoteShell()
|
||||
{
|
||||
return m_channelManager->createRemoteShell();
|
||||
}
|
||||
|
||||
QSharedPointer<SftpChannel> SshConnectionPrivate::createSftpChannel()
|
||||
{
|
||||
return m_channelManager->createSftpChannel();
|
||||
}
|
||||
|
||||
SshDirectTcpIpTunnel::Ptr SshConnectionPrivate::createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort)
|
||||
{
|
||||
return m_channelManager->createDirectTunnel(originatingHost, originatingPort, remoteHost,
|
||||
remotePort);
|
||||
}
|
||||
|
||||
SshTcpIpForwardServer::Ptr SshConnectionPrivate::createForwardServer(const QString &bindAddress,
|
||||
quint16 bindPort)
|
||||
{
|
||||
return m_channelManager->createForwardServer(bindAddress, bindPort);
|
||||
}
|
||||
|
||||
const quint64 SshConnectionPrivate::InvalidSeqNr = static_cast<quint64>(-1);
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,145 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssherrors.h"
|
||||
#include "sshhostkeydatabase.h"
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFlags>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
|
||||
namespace QSsh {
|
||||
class SftpChannel;
|
||||
class SshDirectTcpIpTunnel;
|
||||
class SshRemoteProcess;
|
||||
class SshTcpIpForwardServer;
|
||||
|
||||
namespace Internal { class SshConnectionPrivate; }
|
||||
|
||||
enum SshConnectionOption {
|
||||
SshIgnoreDefaultProxy = 0x1,
|
||||
SshEnableStrictConformanceChecks = 0x2
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(SshConnectionOptions, SshConnectionOption)
|
||||
|
||||
enum SshHostKeyCheckingMode {
|
||||
SshHostKeyCheckingNone,
|
||||
SshHostKeyCheckingStrict,
|
||||
SshHostKeyCheckingAllowNoMatch,
|
||||
SshHostKeyCheckingAllowMismatch
|
||||
};
|
||||
|
||||
class QSSH_EXPORT SshConnectionParameters
|
||||
{
|
||||
public:
|
||||
enum AuthenticationType {
|
||||
AuthenticationTypePassword,
|
||||
AuthenticationTypePublicKey,
|
||||
AuthenticationTypeKeyboardInteractive,
|
||||
|
||||
// Some servers disable "password", others disable "keyboard-interactive".
|
||||
AuthenticationTypeTryAllPasswordBasedMethods
|
||||
};
|
||||
|
||||
SshConnectionParameters();
|
||||
|
||||
QString host;
|
||||
QString userName;
|
||||
QString password;
|
||||
QString privateKeyFile;
|
||||
int timeout; // In seconds.
|
||||
AuthenticationType authenticationType;
|
||||
quint16 port;
|
||||
SshConnectionOptions options;
|
||||
SshHostKeyCheckingMode hostKeyCheckingMode;
|
||||
SshHostKeyDatabasePtr hostKeyDatabase;
|
||||
};
|
||||
|
||||
QSSH_EXPORT bool operator==(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
|
||||
QSSH_EXPORT bool operator!=(const SshConnectionParameters &p1, const SshConnectionParameters &p2);
|
||||
|
||||
class QSSH_EXPORT SshConnectionInfo
|
||||
{
|
||||
public:
|
||||
SshConnectionInfo() : localPort(0), peerPort(0) {}
|
||||
SshConnectionInfo(const QHostAddress &la, quint16 lp, const QHostAddress &pa, quint16 pp)
|
||||
: localAddress(la), localPort(lp), peerAddress(pa), peerPort(pp) {}
|
||||
|
||||
QHostAddress localAddress;
|
||||
quint16 localPort;
|
||||
QHostAddress peerAddress;
|
||||
quint16 peerPort;
|
||||
};
|
||||
|
||||
class QSSH_EXPORT SshConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum State { Unconnected, Connecting, Connected };
|
||||
|
||||
explicit SshConnection(const SshConnectionParameters &serverInfo, QObject *parent = 0);
|
||||
|
||||
void connectToHost();
|
||||
void disconnectFromHost();
|
||||
State state() const;
|
||||
SshError errorState() const;
|
||||
QString errorString() const;
|
||||
SshConnectionParameters connectionParameters() const;
|
||||
SshConnectionInfo connectionInfo() const;
|
||||
~SshConnection();
|
||||
|
||||
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
|
||||
QSharedPointer<SshRemoteProcess> createRemoteShell();
|
||||
QSharedPointer<SftpChannel> createSftpChannel();
|
||||
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
|
||||
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
|
||||
quint16 remotePort);
|
||||
|
||||
// -1 if an error occurred, number of channels closed otherwise.
|
||||
int closeAllChannels();
|
||||
|
||||
int channelCount() const;
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void dataAvailable(const QString &message);
|
||||
void error(QSsh::SshError);
|
||||
|
||||
private:
|
||||
Internal::SshConnectionPrivate *d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,179 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshconnection.h"
|
||||
#include "sshexception_p.h"
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QScopedPointer>
|
||||
#include <QTimer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTcpSocket;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace QSsh {
|
||||
class SftpChannel;
|
||||
class SshRemoteProcess;
|
||||
class SshDirectTcpIpTunnel;
|
||||
class SshTcpIpForwardServer;
|
||||
|
||||
namespace Internal {
|
||||
class SshChannelManager;
|
||||
|
||||
// NOTE: When you add stuff here, don't forget to update m_packetHandlers.
|
||||
enum SshStateInternal {
|
||||
SocketUnconnected, // initial and after disconnect
|
||||
SocketConnecting, // After connectToHost()
|
||||
SocketConnected, // After socket's connected() signal
|
||||
UserAuthServiceRequested,
|
||||
UserAuthRequested,
|
||||
ConnectionEstablished // After service has been started
|
||||
// ...
|
||||
};
|
||||
|
||||
enum SshKeyExchangeState {
|
||||
NoKeyExchange,
|
||||
KexInitSent,
|
||||
DhInitSent,
|
||||
NewKeysSent,
|
||||
KeyExchangeSuccess // After server's DH_REPLY message
|
||||
};
|
||||
|
||||
class SshConnectionPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class QSsh::SshConnection;
|
||||
public:
|
||||
SshConnectionPrivate(SshConnection *conn,
|
||||
const SshConnectionParameters &serverInfo);
|
||||
~SshConnectionPrivate();
|
||||
|
||||
void connectToHost();
|
||||
void closeConnection(SshErrorCode sshError, SshError userError,
|
||||
const QByteArray &serverErrorString, const QString &userErrorString);
|
||||
QSharedPointer<SshRemoteProcess> createRemoteProcess(const QByteArray &command);
|
||||
QSharedPointer<SshRemoteProcess> createRemoteShell();
|
||||
QSharedPointer<SftpChannel> createSftpChannel();
|
||||
QSharedPointer<SshDirectTcpIpTunnel> createDirectTunnel(const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort);
|
||||
QSharedPointer<SshTcpIpForwardServer> createForwardServer(const QString &remoteHost,
|
||||
quint16 remotePort);
|
||||
|
||||
SshStateInternal state() const { return m_state; }
|
||||
SshError errorState() const { return m_error; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void dataAvailable(const QString &message);
|
||||
void error(QSsh::SshError);
|
||||
|
||||
private:
|
||||
void handleSocketConnected();
|
||||
void handleIncomingData();
|
||||
void handleSocketError();
|
||||
void handleSocketDisconnected();
|
||||
void handleTimeout();
|
||||
void sendKeepAlivePacket();
|
||||
|
||||
void handleServerId();
|
||||
void handlePackets();
|
||||
void handleCurrentPacket();
|
||||
void handleKeyExchangeInitPacket();
|
||||
void handleKeyExchangeReplyPacket();
|
||||
void handleNewKeysPacket();
|
||||
void handleServiceAcceptPacket();
|
||||
void handlePasswordExpiredPacket();
|
||||
void handleUserAuthInfoRequestPacket();
|
||||
void handleUserAuthSuccessPacket();
|
||||
void handleUserAuthFailurePacket();
|
||||
void handleUserAuthBannerPacket();
|
||||
void handleUnexpectedPacket();
|
||||
void handleGlobalRequest();
|
||||
void handleDebugPacket();
|
||||
void handleUnimplementedPacket();
|
||||
void handleChannelRequest();
|
||||
void handleChannelOpen();
|
||||
void handleChannelOpenFailure();
|
||||
void handleChannelOpenConfirmation();
|
||||
void handleChannelSuccess();
|
||||
void handleChannelFailure();
|
||||
void handleChannelWindowAdjust();
|
||||
void handleChannelData();
|
||||
void handleChannelExtendedData();
|
||||
void handleChannelEof();
|
||||
void handleChannelClose();
|
||||
void handleDisconnect();
|
||||
void handleRequestSuccess();
|
||||
void handleRequestFailure();
|
||||
|
||||
bool canUseSocket() const;
|
||||
void createPrivateKey();
|
||||
|
||||
void sendData(const QByteArray &data);
|
||||
|
||||
typedef void (SshConnectionPrivate::*PacketHandler)();
|
||||
typedef QList<SshStateInternal> StateList;
|
||||
void setupPacketHandlers();
|
||||
void setupPacketHandler(SshPacketType type, const StateList &states,
|
||||
PacketHandler handler);
|
||||
|
||||
typedef QPair<StateList, PacketHandler> HandlerInStates;
|
||||
QHash<SshPacketType, HandlerInStates> m_packetHandlers;
|
||||
|
||||
static const quint64 InvalidSeqNr;
|
||||
|
||||
QTcpSocket *m_socket;
|
||||
SshStateInternal m_state;
|
||||
SshKeyExchangeState m_keyExchangeState;
|
||||
SshIncomingPacket m_incomingPacket;
|
||||
SshSendFacility m_sendFacility;
|
||||
SshChannelManager * const m_channelManager;
|
||||
const SshConnectionParameters m_connParams;
|
||||
QByteArray m_incomingData;
|
||||
SshError m_error;
|
||||
QString m_errorString;
|
||||
QScopedPointer<SshKeyExchange> m_keyExchange;
|
||||
QTimer m_timeoutTimer;
|
||||
QTimer m_keepAliveTimer;
|
||||
bool m_ignoreNextPacket;
|
||||
SshConnection *m_conn;
|
||||
quint64 m_lastInvalidMsgSeqNr;
|
||||
QByteArray m_serverId;
|
||||
bool m_serverHasSentDataBeforeId;
|
||||
bool m_triedAllPasswordBasedMethods;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,270 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshconnectionmanager.h"
|
||||
|
||||
#include "sshconnection.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
class UnaquiredConnection {
|
||||
public:
|
||||
UnaquiredConnection(SshConnection *conn) : connection(conn), scheduledForRemoval(false) {}
|
||||
|
||||
SshConnection *connection;
|
||||
bool scheduledForRemoval;
|
||||
};
|
||||
bool operator==(const UnaquiredConnection &c1, const UnaquiredConnection &c2) {
|
||||
return c1.connection == c2.connection;
|
||||
}
|
||||
bool operator!=(const UnaquiredConnection &c1, const UnaquiredConnection &c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
class SshConnectionManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SshConnectionManager()
|
||||
{
|
||||
moveToThread(QCoreApplication::instance()->thread());
|
||||
connect(&m_removalTimer, &QTimer::timeout,
|
||||
this, &SshConnectionManager::removeInactiveConnections);
|
||||
m_removalTimer.start(150000); // For a total timeout of five minutes.
|
||||
}
|
||||
|
||||
~SshConnectionManager()
|
||||
{
|
||||
foreach (const UnaquiredConnection &connection, m_unacquiredConnections) {
|
||||
disconnect(connection.connection, 0, this, 0);
|
||||
delete connection.connection;
|
||||
}
|
||||
|
||||
QSSH_ASSERT(m_acquiredConnections.isEmpty());
|
||||
QSSH_ASSERT(m_deprecatedConnections.isEmpty());
|
||||
}
|
||||
|
||||
SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
QMutexLocker locker(&m_listMutex);
|
||||
|
||||
// Check in-use connections:
|
||||
foreach (SshConnection * const connection, m_acquiredConnections) {
|
||||
if (connection->connectionParameters() != sshParams)
|
||||
continue;
|
||||
|
||||
if (connection->thread() != QThread::currentThread())
|
||||
continue;
|
||||
|
||||
if (m_deprecatedConnections.contains(connection)) // we were asked to no longer use this one...
|
||||
continue;
|
||||
|
||||
m_acquiredConnections.append(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Check cached open connections:
|
||||
foreach (const UnaquiredConnection &c, m_unacquiredConnections) {
|
||||
SshConnection * const connection = c.connection;
|
||||
if (connection->state() != SshConnection::Connected
|
||||
|| connection->connectionParameters() != sshParams)
|
||||
continue;
|
||||
|
||||
if (connection->thread() != QThread::currentThread()) {
|
||||
if (connection->channelCount() != 0)
|
||||
continue;
|
||||
QMetaObject::invokeMethod(this, "switchToCallerThread",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_ARG(SshConnection *, connection),
|
||||
Q_ARG(QObject *, QThread::currentThread()));
|
||||
}
|
||||
|
||||
m_unacquiredConnections.removeOne(c);
|
||||
m_acquiredConnections.append(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
// create a new connection:
|
||||
SshConnection * const connection = new SshConnection(sshParams);
|
||||
connect(connection, &SshConnection::disconnected,
|
||||
this, &SshConnectionManager::cleanup);
|
||||
m_acquiredConnections.append(connection);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void releaseConnection(SshConnection *connection)
|
||||
{
|
||||
QMutexLocker locker(&m_listMutex);
|
||||
|
||||
const bool wasAquired = m_acquiredConnections.removeOne(connection);
|
||||
QSSH_ASSERT_AND_RETURN(wasAquired);
|
||||
if (m_acquiredConnections.contains(connection))
|
||||
return;
|
||||
|
||||
bool doDelete = false;
|
||||
connection->moveToThread(QCoreApplication::instance()->thread());
|
||||
if (m_deprecatedConnections.removeOne(connection)
|
||||
|| connection->state() != SshConnection::Connected) {
|
||||
doDelete = true;
|
||||
} else {
|
||||
QSSH_ASSERT_AND_RETURN(!m_unacquiredConnections.contains(UnaquiredConnection(connection)));
|
||||
|
||||
// It can happen that two or more connections with the same parameters were acquired
|
||||
// if the clients were running in different threads. Only keep one of them in
|
||||
// such a case.
|
||||
bool haveConnection = false;
|
||||
foreach (const UnaquiredConnection &c, m_unacquiredConnections) {
|
||||
if (c.connection->connectionParameters() == connection->connectionParameters()) {
|
||||
haveConnection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!haveConnection) {
|
||||
connection->closeAllChannels(); // Clean up after neglectful clients.
|
||||
m_unacquiredConnections.append(UnaquiredConnection(connection));
|
||||
} else {
|
||||
doDelete = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (doDelete) {
|
||||
disconnect(connection, 0, this, 0);
|
||||
m_deprecatedConnections.removeAll(connection);
|
||||
connection->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void forceNewConnection(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
QMutexLocker locker(&m_listMutex);
|
||||
|
||||
for (int i = 0; i < m_unacquiredConnections.count(); ++i) {
|
||||
SshConnection * const connection = m_unacquiredConnections.at(i).connection;
|
||||
if (connection->connectionParameters() == sshParams) {
|
||||
disconnect(connection, 0, this, 0);
|
||||
delete connection;
|
||||
m_unacquiredConnections.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SshConnection * const connection, m_acquiredConnections) {
|
||||
if (connection->connectionParameters() == sshParams) {
|
||||
if (!m_deprecatedConnections.contains(connection))
|
||||
m_deprecatedConnections.append(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Q_INVOKABLE void switchToCallerThread(SshConnection *connection, QObject *threadObj)
|
||||
{
|
||||
connection->moveToThread(qobject_cast<QThread *>(threadObj));
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
QMutexLocker locker(&m_listMutex);
|
||||
|
||||
SshConnection *currentConnection = qobject_cast<SshConnection *>(sender());
|
||||
if (!currentConnection)
|
||||
return;
|
||||
|
||||
if (m_unacquiredConnections.removeOne(UnaquiredConnection(currentConnection))) {
|
||||
disconnect(currentConnection, 0, this, 0);
|
||||
currentConnection->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void removeInactiveConnections()
|
||||
{
|
||||
QMutexLocker locker(&m_listMutex);
|
||||
for (int i = m_unacquiredConnections.count() - 1; i >= 0; --i) {
|
||||
UnaquiredConnection &c = m_unacquiredConnections[i];
|
||||
if (c.scheduledForRemoval) {
|
||||
disconnect(c.connection, 0, this, 0);
|
||||
c.connection->deleteLater();
|
||||
m_unacquiredConnections.removeAt(i);
|
||||
} else {
|
||||
c.scheduledForRemoval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// We expect the number of concurrently open connections to be small.
|
||||
// If that turns out to not be the case, we can still use a data
|
||||
// structure with faster access.
|
||||
QList<UnaquiredConnection> m_unacquiredConnections;
|
||||
|
||||
// Can contain the same connection more than once; this acts as a reference count.
|
||||
QList<SshConnection *> m_acquiredConnections;
|
||||
|
||||
QList<SshConnection *> m_deprecatedConnections;
|
||||
QMutex m_listMutex;
|
||||
QTimer m_removalTimer;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
static QMutex instanceMutex;
|
||||
|
||||
static Internal::SshConnectionManager &instance()
|
||||
{
|
||||
static Internal::SshConnectionManager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
SshConnection *acquireConnection(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
QMutexLocker locker(&instanceMutex);
|
||||
return instance().acquireConnection(sshParams);
|
||||
}
|
||||
|
||||
void releaseConnection(SshConnection *connection)
|
||||
{
|
||||
QMutexLocker locker(&instanceMutex);
|
||||
instance().releaseConnection(connection);
|
||||
}
|
||||
|
||||
void forceNewConnection(const SshConnectionParameters &sshParams)
|
||||
{
|
||||
QMutexLocker locker(&instanceMutex);
|
||||
instance().forceNewConnection(sshParams);
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
|
||||
#include "sshconnectionmanager.moc"
|
||||
@@ -1,41 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
class SshConnection;
|
||||
class SshConnectionParameters;
|
||||
|
||||
QSSH_EXPORT SshConnection *acquireConnection(const SshConnectionParameters &sshParams);
|
||||
QSSH_EXPORT void releaseConnection(SshConnection *connection);
|
||||
|
||||
// Make sure the next acquireConnection with the given parameters will return a new connection.
|
||||
QSSH_EXPORT void forceNewConnection(const SshConnectionParameters &sshParams);
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,439 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshcryptofacility_p.h"
|
||||
|
||||
#include "sshbotanconversions_p.h"
|
||||
#include "sshcapabilities_p.h"
|
||||
#include "sshexception_p.h"
|
||||
#include "sshkeyexchange_p.h"
|
||||
#include "sshkeypasswordretriever_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshpacket_p.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace Botan;
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
SshAbstractCryptoFacility::SshAbstractCryptoFacility()
|
||||
: m_cipherBlockSize(0), m_macLength(0)
|
||||
{
|
||||
}
|
||||
|
||||
SshAbstractCryptoFacility::~SshAbstractCryptoFacility() {}
|
||||
|
||||
void SshAbstractCryptoFacility::clearKeys()
|
||||
{
|
||||
m_cipherBlockSize = 0;
|
||||
m_macLength = 0;
|
||||
m_sessionId.clear();
|
||||
m_pipe.reset(0);
|
||||
m_hMac.reset(0);
|
||||
}
|
||||
|
||||
SshAbstractCryptoFacility::Mode SshAbstractCryptoFacility::getMode(const QByteArray &algoName)
|
||||
{
|
||||
if (algoName.endsWith("-ctr"))
|
||||
return CtrMode;
|
||||
if (algoName.endsWith("-cbc"))
|
||||
return CbcMode;
|
||||
throw SshClientException(SshInternalError, SSH_TR("Unexpected cipher \"%1\"")
|
||||
.arg(QString::fromLatin1(algoName)));
|
||||
}
|
||||
|
||||
void SshAbstractCryptoFacility::recreateKeys(const SshKeyExchange &kex)
|
||||
{
|
||||
checkInvariant();
|
||||
|
||||
if (m_sessionId.isEmpty())
|
||||
m_sessionId = kex.h();
|
||||
Algorithm_Factory &af = global_state().algorithm_factory();
|
||||
const QByteArray &rfcCryptAlgoName = cryptAlgoName(kex);
|
||||
BlockCipher * const cipher
|
||||
= af.prototype_block_cipher(botanCryptAlgoName(rfcCryptAlgoName))->clone();
|
||||
|
||||
m_cipherBlockSize = static_cast<quint32>(cipher->block_size());
|
||||
const QByteArray ivData = generateHash(kex, ivChar(), m_cipherBlockSize);
|
||||
const InitializationVector iv(convertByteArray(ivData), m_cipherBlockSize);
|
||||
|
||||
const quint32 keySize = static_cast<quint32>(cipher->key_spec().maximum_keylength());
|
||||
const QByteArray cryptKeyData = generateHash(kex, keyChar(), keySize);
|
||||
SymmetricKey cryptKey(convertByteArray(cryptKeyData), keySize);
|
||||
Keyed_Filter * const cipherMode
|
||||
= makeCipherMode(cipher, getMode(rfcCryptAlgoName), iv, cryptKey);
|
||||
m_pipe.reset(new Pipe(cipherMode));
|
||||
|
||||
m_macLength = botanHMacKeyLen(hMacAlgoName(kex));
|
||||
const QByteArray hMacKeyData = generateHash(kex, macChar(), macLength());
|
||||
SymmetricKey hMacKey(convertByteArray(hMacKeyData), macLength());
|
||||
const HashFunction * const hMacProto
|
||||
= af.prototype_hash_function(botanHMacAlgoName(hMacAlgoName(kex)));
|
||||
m_hMac.reset(new HMAC(hMacProto->clone()));
|
||||
m_hMac->set_key(hMacKey);
|
||||
}
|
||||
|
||||
void SshAbstractCryptoFacility::convert(QByteArray &data, quint32 offset,
|
||||
quint32 dataSize) const
|
||||
{
|
||||
Q_ASSERT(offset + dataSize <= static_cast<quint32>(data.size()));
|
||||
checkInvariant();
|
||||
|
||||
// Session id empty => No key exchange has happened yet.
|
||||
if (dataSize == 0 || m_sessionId.isEmpty())
|
||||
return;
|
||||
|
||||
if (dataSize % cipherBlockSize() != 0) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid packet size");
|
||||
}
|
||||
m_pipe->process_msg(reinterpret_cast<const byte *>(data.constData()) + offset,
|
||||
dataSize);
|
||||
// Can't use Pipe::LAST_MESSAGE because of a VC bug.
|
||||
quint32 bytesRead = static_cast<quint32>(m_pipe->read(
|
||||
reinterpret_cast<byte *>(data.data()) + offset, dataSize, m_pipe->message_count() - 1));
|
||||
if (bytesRead != dataSize) {
|
||||
throw SshClientException(SshInternalError,
|
||||
QLatin1String("Internal error: Botan::Pipe::read() returned unexpected value"));
|
||||
}
|
||||
}
|
||||
|
||||
Keyed_Filter *SshAbstractCryptoFacility::makeCtrCipherMode(BlockCipher *cipher,
|
||||
const InitializationVector &iv, const SymmetricKey &key)
|
||||
{
|
||||
StreamCipher_Filter * const filter = new StreamCipher_Filter(new CTR_BE(cipher));
|
||||
filter->set_key(key);
|
||||
filter->set_iv(iv);
|
||||
return filter;
|
||||
}
|
||||
|
||||
QByteArray SshAbstractCryptoFacility::generateMac(const QByteArray &data,
|
||||
quint32 dataSize) const
|
||||
{
|
||||
return m_sessionId.isEmpty()
|
||||
? QByteArray()
|
||||
: convertByteArray(m_hMac->process(reinterpret_cast<const byte *>(data.constData()),
|
||||
dataSize));
|
||||
}
|
||||
|
||||
QByteArray SshAbstractCryptoFacility::generateHash(const SshKeyExchange &kex,
|
||||
char c, quint32 length)
|
||||
{
|
||||
const QByteArray &k = kex.k();
|
||||
const QByteArray &h = kex.h();
|
||||
QByteArray data(k);
|
||||
data.append(h).append(c).append(m_sessionId);
|
||||
SecureVector<byte> key
|
||||
= kex.hash()->process(convertByteArray(data), data.size());
|
||||
while (key.size() < length) {
|
||||
SecureVector<byte> tmpKey;
|
||||
tmpKey += SecureVector<byte>(convertByteArray(k), k.size());
|
||||
tmpKey += SecureVector<byte>(convertByteArray(h), h.size());
|
||||
tmpKey += key;
|
||||
key += kex.hash()->process(tmpKey);
|
||||
}
|
||||
return QByteArray(reinterpret_cast<const char *>(key.begin()), length);
|
||||
}
|
||||
|
||||
void SshAbstractCryptoFacility::checkInvariant() const
|
||||
{
|
||||
Q_ASSERT(m_sessionId.isEmpty() == !m_pipe);
|
||||
}
|
||||
|
||||
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileStartLineRsa("-----BEGIN RSA PRIVATE KEY-----");
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileStartLineDsa("-----BEGIN DSA PRIVATE KEY-----");
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileEndLineRsa("-----END RSA PRIVATE KEY-----");
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileEndLineDsa("-----END DSA PRIVATE KEY-----");
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileStartLineEcdsa("-----BEGIN EC PRIVATE KEY-----");
|
||||
const QByteArray SshEncryptionFacility::PrivKeyFileEndLineEcdsa("-----END EC PRIVATE KEY-----");
|
||||
|
||||
QByteArray SshEncryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
|
||||
{
|
||||
return kex.encryptionAlgo();
|
||||
}
|
||||
|
||||
QByteArray SshEncryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
|
||||
{
|
||||
return kex.hMacAlgoClientToServer();
|
||||
}
|
||||
|
||||
Keyed_Filter *SshEncryptionFacility::makeCipherMode(BlockCipher *cipher, Mode mode,
|
||||
const InitializationVector &iv, const SymmetricKey &key)
|
||||
{
|
||||
switch (mode) {
|
||||
case CbcMode:
|
||||
return new CBC_Encryption(cipher, new Null_Padding, key, iv);
|
||||
case CtrMode:
|
||||
return makeCtrCipherMode(cipher, iv, key);
|
||||
}
|
||||
return 0; // For dumb compilers.
|
||||
}
|
||||
|
||||
void SshEncryptionFacility::encrypt(QByteArray &data) const
|
||||
{
|
||||
convert(data, 0, data.size());
|
||||
}
|
||||
|
||||
void SshEncryptionFacility::createAuthenticationKey(const QByteArray &privKeyFileContents)
|
||||
{
|
||||
if (privKeyFileContents == m_cachedPrivKeyContents)
|
||||
return;
|
||||
|
||||
m_authKeyAlgoName.clear();
|
||||
qCDebug(sshLog, "%s: Key not cached, reading", Q_FUNC_INFO);
|
||||
QList<BigInt> pubKeyParams;
|
||||
QList<BigInt> allKeyParams;
|
||||
QString error1;
|
||||
QString error2;
|
||||
if (!createAuthenticationKeyFromPKCS8(privKeyFileContents, pubKeyParams, allKeyParams, error1)
|
||||
&& !createAuthenticationKeyFromOpenSSL(privKeyFileContents, pubKeyParams, allKeyParams,
|
||||
error2)) {
|
||||
qCDebug(sshLog, "%s: %s\n\t%s\n", Q_FUNC_INFO, qPrintable(error1), qPrintable(error2));
|
||||
throw SshClientException(SshKeyFileError, SSH_TR("Decoding of private key file failed: "
|
||||
"Format not understood."));
|
||||
}
|
||||
|
||||
foreach (const BigInt &b, allKeyParams) {
|
||||
if (b.is_zero()) {
|
||||
throw SshClientException(SshKeyFileError,
|
||||
SSH_TR("Decoding of private key file failed: Invalid zero parameter."));
|
||||
}
|
||||
}
|
||||
|
||||
m_authPubKeyBlob = AbstractSshPacket::encodeString(m_authKeyAlgoName);
|
||||
auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data());
|
||||
if (ecdsaKey) {
|
||||
m_authPubKeyBlob += AbstractSshPacket::encodeString(m_authKeyAlgoName.mid(11)); // Without "ecdsa-sha2-" prefix.
|
||||
m_authPubKeyBlob += AbstractSshPacket::encodeString(
|
||||
convertByteArray(EC2OSP(ecdsaKey->public_point(), PointGFp::UNCOMPRESSED)));
|
||||
} else {
|
||||
foreach (const BigInt &b, pubKeyParams)
|
||||
m_authPubKeyBlob += AbstractSshPacket::encodeMpInt(b);
|
||||
}
|
||||
m_cachedPrivKeyContents = privKeyFileContents;
|
||||
}
|
||||
|
||||
bool SshEncryptionFacility::createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
|
||||
QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
|
||||
{
|
||||
try {
|
||||
Pipe pipe;
|
||||
pipe.process_msg(convertByteArray(privKeyFileContents), privKeyFileContents.size());
|
||||
m_authKey.reset(PKCS8::load_key(pipe, m_rng, SshKeyPasswordRetriever()));
|
||||
if (auto * const dsaKey = dynamic_cast<DSA_PrivateKey *>(m_authKey.data())) {
|
||||
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
|
||||
pubKeyParams << dsaKey->group_p() << dsaKey->group_q()
|
||||
<< dsaKey->group_g() << dsaKey->get_y();
|
||||
allKeyParams << pubKeyParams << dsaKey->get_x();
|
||||
} else if (auto * const rsaKey = dynamic_cast<RSA_PrivateKey *>(m_authKey.data())) {
|
||||
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
|
||||
pubKeyParams << rsaKey->get_e() << rsaKey->get_n();
|
||||
allKeyParams << pubKeyParams << rsaKey->get_p() << rsaKey->get_q()
|
||||
<< rsaKey->get_d();
|
||||
} else if (auto * const ecdsaKey = dynamic_cast<ECDSA_PrivateKey *>(m_authKey.data())) {
|
||||
const BigInt value = ecdsaKey->private_value();
|
||||
m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
|
||||
static_cast<int>(value.bytes()));
|
||||
pubKeyParams << ecdsaKey->public_point().get_affine_x()
|
||||
<< ecdsaKey->public_point().get_affine_y();
|
||||
allKeyParams << pubKeyParams << value;
|
||||
} else {
|
||||
qCWarning(sshLog, "%s: Unexpected code flow, expected success or exception.",
|
||||
Q_FUNC_INFO);
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
error = QLatin1String(ex.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SshEncryptionFacility::createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
|
||||
QList<BigInt> &pubKeyParams, QList<BigInt> &allKeyParams, QString &error)
|
||||
{
|
||||
try {
|
||||
bool syntaxOk = true;
|
||||
QList<QByteArray> lines = privKeyFileContents.split('\n');
|
||||
while (lines.last().isEmpty())
|
||||
lines.removeLast();
|
||||
if (lines.count() < 3) {
|
||||
syntaxOk = false;
|
||||
} else if (lines.first() == PrivKeyFileStartLineRsa) {
|
||||
if (lines.last() != PrivKeyFileEndLineRsa)
|
||||
syntaxOk = false;
|
||||
else
|
||||
m_authKeyAlgoName = SshCapabilities::PubKeyRsa;
|
||||
} else if (lines.first() == PrivKeyFileStartLineDsa) {
|
||||
if (lines.last() != PrivKeyFileEndLineDsa)
|
||||
syntaxOk = false;
|
||||
else
|
||||
m_authKeyAlgoName = SshCapabilities::PubKeyDss;
|
||||
} else if (lines.first() == PrivKeyFileStartLineEcdsa) {
|
||||
if (lines.last() != PrivKeyFileEndLineEcdsa)
|
||||
syntaxOk = false;
|
||||
// m_authKeyAlgoName set below, as we don't know the size yet.
|
||||
} else {
|
||||
syntaxOk = false;
|
||||
}
|
||||
if (!syntaxOk) {
|
||||
error = SSH_TR("Unexpected format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray privateKeyBlob;
|
||||
for (int i = 1; i < lines.size() - 1; ++i)
|
||||
privateKeyBlob += lines.at(i);
|
||||
privateKeyBlob = QByteArray::fromBase64(privateKeyBlob);
|
||||
|
||||
BER_Decoder decoder(convertByteArray(privateKeyBlob), privateKeyBlob.size());
|
||||
BER_Decoder sequence = decoder.start_cons(SEQUENCE);
|
||||
size_t version;
|
||||
sequence.decode (version);
|
||||
const size_t expectedVersion = m_authKeyAlgoName.isEmpty() ? 1 : 0;
|
||||
if (version != expectedVersion) {
|
||||
error = SSH_TR("Key encoding has version %1, expected %2.")
|
||||
.arg(version).arg(expectedVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_authKeyAlgoName == SshCapabilities::PubKeyDss) {
|
||||
BigInt p, q, g, y, x;
|
||||
sequence.decode (p).decode (q).decode (g).decode (y).decode (x);
|
||||
DSA_PrivateKey * const dsaKey = new DSA_PrivateKey(m_rng, DL_Group(p, q, g), x);
|
||||
m_authKey.reset(dsaKey);
|
||||
pubKeyParams << p << q << g << y;
|
||||
allKeyParams << pubKeyParams << x;
|
||||
} else if (m_authKeyAlgoName == SshCapabilities::PubKeyRsa) {
|
||||
BigInt p, q, e, d, n;
|
||||
sequence.decode(n).decode(e).decode(d).decode(p).decode(q);
|
||||
RSA_PrivateKey * const rsaKey = new RSA_PrivateKey(m_rng, p, q, e, d, n);
|
||||
m_authKey.reset(rsaKey);
|
||||
pubKeyParams << e << n;
|
||||
allKeyParams << pubKeyParams << p << q << d;
|
||||
} else {
|
||||
BigInt privKey;
|
||||
sequence.decode_octet_string_bigint(privKey);
|
||||
m_authKeyAlgoName = SshCapabilities::ecdsaPubKeyAlgoForKeyWidth(
|
||||
static_cast<int>(privKey.bytes()));
|
||||
const EC_Group group(SshCapabilities::oid(m_authKeyAlgoName));
|
||||
auto * const key = new ECDSA_PrivateKey(m_rng, group, privKey);
|
||||
m_authKey.reset(key);
|
||||
pubKeyParams << key->public_point().get_affine_x()
|
||||
<< key->public_point().get_affine_y();
|
||||
allKeyParams << pubKeyParams << privKey;
|
||||
}
|
||||
|
||||
sequence.discard_remaining();
|
||||
sequence.verify_end();
|
||||
} catch (const std::exception &ex) {
|
||||
error = QLatin1String(ex.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SshEncryptionFacility::authenticationAlgorithmName() const
|
||||
{
|
||||
Q_ASSERT(m_authKey);
|
||||
return m_authKeyAlgoName;
|
||||
}
|
||||
|
||||
QByteArray SshEncryptionFacility::authenticationKeySignature(const QByteArray &data) const
|
||||
{
|
||||
Q_ASSERT(m_authKey);
|
||||
|
||||
QScopedPointer<PK_Signer> signer(new PK_Signer(*m_authKey,
|
||||
botanEmsaAlgoName(m_authKeyAlgoName)));
|
||||
QByteArray dataToSign = AbstractSshPacket::encodeString(sessionId()) + data;
|
||||
QByteArray signature
|
||||
= convertByteArray(signer->sign_message(convertByteArray(dataToSign),
|
||||
dataToSign.size(), m_rng));
|
||||
if (m_authKeyAlgoName.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
|
||||
// The Botan output is not quite in the format that SSH defines.
|
||||
const int halfSize = signature.count() / 2;
|
||||
const BigInt r = BigInt::decode(convertByteArray(signature), halfSize);
|
||||
const BigInt s = BigInt::decode(convertByteArray(signature.mid(halfSize)), halfSize);
|
||||
signature = AbstractSshPacket::encodeMpInt(r) + AbstractSshPacket::encodeMpInt(s);
|
||||
}
|
||||
return AbstractSshPacket::encodeString(m_authKeyAlgoName)
|
||||
+ AbstractSshPacket::encodeString(signature);
|
||||
}
|
||||
|
||||
QByteArray SshEncryptionFacility::getRandomNumbers(int count) const
|
||||
{
|
||||
QByteArray data;
|
||||
data.resize(count);
|
||||
m_rng.randomize(convertByteArray(data), count);
|
||||
return data;
|
||||
}
|
||||
|
||||
SshEncryptionFacility::~SshEncryptionFacility() {}
|
||||
|
||||
|
||||
QByteArray SshDecryptionFacility::cryptAlgoName(const SshKeyExchange &kex) const
|
||||
{
|
||||
return kex.decryptionAlgo();
|
||||
}
|
||||
|
||||
QByteArray SshDecryptionFacility::hMacAlgoName(const SshKeyExchange &kex) const
|
||||
{
|
||||
return kex.hMacAlgoServerToClient();
|
||||
}
|
||||
|
||||
Keyed_Filter *SshDecryptionFacility::makeCipherMode(BlockCipher *cipher, Mode mode, const InitializationVector &iv,
|
||||
const SymmetricKey &key)
|
||||
{
|
||||
switch (mode) {
|
||||
case CbcMode:
|
||||
return new CBC_Decryption(cipher, new Null_Padding, key, iv);
|
||||
case CtrMode:
|
||||
return makeCtrCipherMode(cipher, iv, key);
|
||||
}
|
||||
return 0; // For dumb compilers.
|
||||
}
|
||||
|
||||
void SshDecryptionFacility::decrypt(QByteArray &data, quint32 offset,
|
||||
quint32 dataSize) const
|
||||
{
|
||||
convert(data, offset, dataSize);
|
||||
qCDebug(sshLog, "Decrypted data:");
|
||||
const char * const start = data.constData() + offset;
|
||||
const char * const end = start + dataSize;
|
||||
for (const char *c = start; c < end; ++c)
|
||||
qCDebug(sshLog, ) << "'" << *c << "' (0x" << (static_cast<int>(*c) & 0xff) << ")";
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,138 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SshKeyExchange;
|
||||
|
||||
class SshAbstractCryptoFacility
|
||||
{
|
||||
public:
|
||||
virtual ~SshAbstractCryptoFacility();
|
||||
|
||||
void clearKeys();
|
||||
void recreateKeys(const SshKeyExchange &kex);
|
||||
QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
|
||||
quint32 cipherBlockSize() const { return m_cipherBlockSize; }
|
||||
quint32 macLength() const { return m_macLength; }
|
||||
|
||||
protected:
|
||||
enum Mode { CbcMode, CtrMode };
|
||||
|
||||
SshAbstractCryptoFacility();
|
||||
void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
|
||||
QByteArray sessionId() const { return m_sessionId; }
|
||||
Botan::Keyed_Filter *makeCtrCipherMode(Botan::BlockCipher *cipher,
|
||||
const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
|
||||
|
||||
private:
|
||||
SshAbstractCryptoFacility(const SshAbstractCryptoFacility &);
|
||||
SshAbstractCryptoFacility &operator=(const SshAbstractCryptoFacility &);
|
||||
|
||||
virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const = 0;
|
||||
virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const = 0;
|
||||
virtual Botan::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
|
||||
Mode mode, const Botan::InitializationVector &iv, const Botan::SymmetricKey &key) = 0;
|
||||
virtual char ivChar() const = 0;
|
||||
virtual char keyChar() const = 0;
|
||||
virtual char macChar() const = 0;
|
||||
|
||||
QByteArray generateHash(const SshKeyExchange &kex, char c, quint32 length);
|
||||
void checkInvariant() const;
|
||||
static Mode getMode(const QByteArray &algoName);
|
||||
|
||||
QByteArray m_sessionId;
|
||||
QScopedPointer<Botan::Pipe> m_pipe;
|
||||
QScopedPointer<Botan::HMAC> m_hMac;
|
||||
quint32 m_cipherBlockSize;
|
||||
quint32 m_macLength;
|
||||
};
|
||||
|
||||
class SshEncryptionFacility : public SshAbstractCryptoFacility
|
||||
{
|
||||
public:
|
||||
void encrypt(QByteArray &data) const;
|
||||
|
||||
void createAuthenticationKey(const QByteArray &privKeyFileContents);
|
||||
QByteArray authenticationAlgorithmName() const;
|
||||
QByteArray authenticationPublicKey() const { return m_authPubKeyBlob; }
|
||||
QByteArray authenticationKeySignature(const QByteArray &data) const;
|
||||
QByteArray getRandomNumbers(int count) const;
|
||||
|
||||
~SshEncryptionFacility();
|
||||
|
||||
private:
|
||||
virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const;
|
||||
virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const;
|
||||
virtual Botan::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
|
||||
Mode mode, const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
|
||||
virtual char ivChar() const { return 'A'; }
|
||||
virtual char keyChar() const { return 'C'; }
|
||||
virtual char macChar() const { return 'E'; }
|
||||
|
||||
bool createAuthenticationKeyFromPKCS8(const QByteArray &privKeyFileContents,
|
||||
QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
|
||||
bool createAuthenticationKeyFromOpenSSL(const QByteArray &privKeyFileContents,
|
||||
QList<Botan::BigInt> &pubKeyParams, QList<Botan::BigInt> &allKeyParams, QString &error);
|
||||
|
||||
static const QByteArray PrivKeyFileStartLineRsa;
|
||||
static const QByteArray PrivKeyFileStartLineDsa;
|
||||
static const QByteArray PrivKeyFileEndLineRsa;
|
||||
static const QByteArray PrivKeyFileEndLineDsa;
|
||||
static const QByteArray PrivKeyFileStartLineEcdsa;
|
||||
static const QByteArray PrivKeyFileEndLineEcdsa;
|
||||
|
||||
QByteArray m_authKeyAlgoName;
|
||||
QByteArray m_authPubKeyBlob;
|
||||
QByteArray m_cachedPrivKeyContents;
|
||||
QScopedPointer<Botan::Private_Key> m_authKey;
|
||||
mutable Botan::AutoSeeded_RNG m_rng;
|
||||
};
|
||||
|
||||
class SshDecryptionFacility : public SshAbstractCryptoFacility
|
||||
{
|
||||
public:
|
||||
void decrypt(QByteArray &data, quint32 offset, quint32 dataSize) const;
|
||||
|
||||
private:
|
||||
virtual QByteArray cryptAlgoName(const SshKeyExchange &kex) const;
|
||||
virtual QByteArray hMacAlgoName(const SshKeyExchange &kex) const;
|
||||
virtual Botan::Keyed_Filter *makeCipherMode(Botan::BlockCipher *cipher,
|
||||
Mode mode, const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
|
||||
virtual char ivChar() const { return 'B'; }
|
||||
virtual char keyChar() const { return 'D'; }
|
||||
virtual char macChar() const { return 'F'; }
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,122 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshdirecttcpiptunnel.h"
|
||||
#include "sshdirecttcpiptunnel_p.h"
|
||||
|
||||
#include "sshincomingpacket_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
SshDirectTcpIpTunnelPrivate::SshDirectTcpIpTunnelPrivate(quint32 channelId,
|
||||
const QString &originatingHost, quint16 originatingPort, const QString &remoteHost,
|
||||
quint16 remotePort, SshSendFacility &sendFacility)
|
||||
: SshTcpIpTunnelPrivate(channelId, sendFacility),
|
||||
m_originatingHost(originatingHost),
|
||||
m_originatingPort(originatingPort),
|
||||
m_remoteHost(remoteHost),
|
||||
m_remotePort(remotePort)
|
||||
{
|
||||
}
|
||||
|
||||
void SshDirectTcpIpTunnelPrivate::handleOpenSuccessInternal()
|
||||
{
|
||||
emit initialized();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
using namespace Internal;
|
||||
|
||||
SshDirectTcpIpTunnel::SshDirectTcpIpTunnel(quint32 channelId, const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
|
||||
SshSendFacility &sendFacility)
|
||||
: d(new SshDirectTcpIpTunnelPrivate(channelId, originatingHost, originatingPort, remoteHost,
|
||||
remotePort, sendFacility))
|
||||
{
|
||||
d->init(this);
|
||||
connect(d, &SshDirectTcpIpTunnelPrivate::initialized,
|
||||
this, &SshDirectTcpIpTunnel::initialized, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
SshDirectTcpIpTunnel::~SshDirectTcpIpTunnel()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool SshDirectTcpIpTunnel::atEnd() const
|
||||
{
|
||||
return QIODevice::atEnd() && d->m_data.isEmpty();
|
||||
}
|
||||
|
||||
qint64 SshDirectTcpIpTunnel::bytesAvailable() const
|
||||
{
|
||||
return QIODevice::bytesAvailable() + d->m_data.count();
|
||||
}
|
||||
|
||||
bool SshDirectTcpIpTunnel::canReadLine() const
|
||||
{
|
||||
return QIODevice::canReadLine() || d->m_data.contains('\n');
|
||||
}
|
||||
|
||||
void SshDirectTcpIpTunnel::close()
|
||||
{
|
||||
d->closeChannel();
|
||||
QIODevice::close();
|
||||
}
|
||||
|
||||
void SshDirectTcpIpTunnel::initialize()
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN(d->channelState() == AbstractSshChannel::Inactive);
|
||||
|
||||
try {
|
||||
QIODevice::open(QIODevice::ReadWrite);
|
||||
d->m_sendFacility.sendDirectTcpIpPacket(d->localChannelId(), d->initialWindowSize(),
|
||||
d->maxPacketSize(), d->m_remoteHost.toUtf8(), d->m_remotePort,
|
||||
d->m_originatingHost.toUtf8(), d->m_originatingPort);
|
||||
d->setChannelState(AbstractSshChannel::SessionRequested);
|
||||
d->m_timeoutTimer.start(d->ReplyTimeout);
|
||||
} catch (const std::exception &e) { // Won't happen, but let's play it safe.
|
||||
qCWarning(sshLog, "Botan error: %s", e.what());
|
||||
d->closeChannel();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 SshDirectTcpIpTunnel::readData(char *data, qint64 maxlen)
|
||||
{
|
||||
return d->readData(data, maxlen);
|
||||
}
|
||||
|
||||
qint64 SshDirectTcpIpTunnel::writeData(const char *data, qint64 len)
|
||||
{
|
||||
return d->writeData(data, len);
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,79 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
namespace Internal {
|
||||
class SshChannelManager;
|
||||
class SshDirectTcpIpTunnelPrivate;
|
||||
class SshSendFacility;
|
||||
class SshTcpIpTunnelPrivate;
|
||||
} // namespace Internal
|
||||
|
||||
class QSSH_EXPORT SshDirectTcpIpTunnel : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class Internal::SshChannelManager;
|
||||
friend class Internal::SshTcpIpTunnelPrivate;
|
||||
|
||||
public:
|
||||
typedef QSharedPointer<SshDirectTcpIpTunnel> Ptr;
|
||||
|
||||
~SshDirectTcpIpTunnel();
|
||||
|
||||
// QIODevice stuff
|
||||
bool atEnd() const;
|
||||
qint64 bytesAvailable() const;
|
||||
bool canReadLine() const;
|
||||
void close();
|
||||
bool isSequential() const { return true; }
|
||||
|
||||
void initialize();
|
||||
|
||||
signals:
|
||||
void initialized();
|
||||
void error(const QString &reason);
|
||||
|
||||
private:
|
||||
SshDirectTcpIpTunnel(quint32 channelId, const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
|
||||
Internal::SshSendFacility &sendFacility);
|
||||
|
||||
// QIODevice stuff
|
||||
qint64 readData(char *data, qint64 maxlen);
|
||||
qint64 writeData(const char *data, qint64 len);
|
||||
|
||||
Internal::SshDirectTcpIpTunnelPrivate * const d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,59 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshtcpiptunnel_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
class SshDirectTcpIpTunnel;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
class SshDirectTcpIpTunnelPrivate : public SshTcpIpTunnelPrivate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class QSsh::SshDirectTcpIpTunnel;
|
||||
|
||||
public:
|
||||
explicit SshDirectTcpIpTunnelPrivate(quint32 channelId, const QString &originatingHost,
|
||||
quint16 originatingPort, const QString &remoteHost, quint16 remotePort,
|
||||
SshSendFacility &sendFacility);
|
||||
|
||||
signals:
|
||||
void initialized();
|
||||
|
||||
private:
|
||||
void handleOpenSuccessInternal();
|
||||
|
||||
const QString m_originatingHost;
|
||||
const quint16 m_originatingPort;
|
||||
const QString m_remoteHost;
|
||||
const quint16 m_remotePort;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,37 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#define SSHERRORS_P_H
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
enum SshError {
|
||||
SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
|
||||
SshHostKeyError, SshKeyFileError, SshAuthenticationError,
|
||||
SshClosedByServerError, SshInternalError
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,82 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssherrors.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
enum SshErrorCode {
|
||||
SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT = 1,
|
||||
SSH_DISCONNECT_PROTOCOL_ERROR = 2,
|
||||
SSH_DISCONNECT_KEY_EXCHANGE_FAILED = 3,
|
||||
SSH_DISCONNECT_RESERVED = 4,
|
||||
SSH_DISCONNECT_MAC_ERROR = 5,
|
||||
SSH_DISCONNECT_COMPRESSION_ERROR = 6,
|
||||
SSH_DISCONNECT_SERVICE_NOT_AVAILABLE = 7,
|
||||
SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED = 8,
|
||||
SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE = 9,
|
||||
SSH_DISCONNECT_CONNECTION_LOST = 10,
|
||||
SSH_DISCONNECT_BY_APPLICATION = 11,
|
||||
SSH_DISCONNECT_TOO_MANY_CONNECTIONS = 12,
|
||||
SSH_DISCONNECT_AUTH_CANCELLED_BY_USER = 13,
|
||||
SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 14,
|
||||
SSH_DISCONNECT_ILLEGAL_USER_NAME = 15
|
||||
};
|
||||
|
||||
#define SSH_TR(string) QCoreApplication::translate("SshConnection", string)
|
||||
|
||||
#define SSH_SERVER_EXCEPTION(error, errorString) \
|
||||
SshServerException((error), (errorString), SSH_TR(errorString))
|
||||
|
||||
struct SshServerException
|
||||
{
|
||||
SshServerException(SshErrorCode error, const QByteArray &errorStringServer,
|
||||
const QString &errorStringUser)
|
||||
: error(error), errorStringServer(errorStringServer),
|
||||
errorStringUser(errorStringUser) {}
|
||||
|
||||
const SshErrorCode error;
|
||||
const QByteArray errorStringServer;
|
||||
const QString errorStringUser;
|
||||
};
|
||||
|
||||
struct SshClientException
|
||||
{
|
||||
SshClientException(SshError error, const QString &errorString)
|
||||
: error(error), errorString(errorString) {}
|
||||
|
||||
const SshError error;
|
||||
const QString errorString;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,100 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshforwardedtcpiptunnel.h"
|
||||
#include "sshforwardedtcpiptunnel_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
namespace Internal {
|
||||
SshForwardedTcpIpTunnelPrivate::SshForwardedTcpIpTunnelPrivate(quint32 channelId,
|
||||
SshSendFacility &sendFacility) :
|
||||
SshTcpIpTunnelPrivate(channelId, sendFacility)
|
||||
{
|
||||
setChannelState(SessionRequested);
|
||||
}
|
||||
|
||||
void SshForwardedTcpIpTunnelPrivate::handleOpenSuccessInternal()
|
||||
{
|
||||
QSSH_ASSERT_AND_RETURN(channelState() == AbstractSshChannel::SessionEstablished);
|
||||
|
||||
try {
|
||||
m_sendFacility.sendChannelOpenConfirmationPacket(remoteChannel(), localChannelId(),
|
||||
initialWindowSize(), maxPacketSize());
|
||||
} catch (const std::exception &e) { // Won't happen, but let's play it safe.
|
||||
qCWarning(sshLog, "Botan error: %s", e.what());
|
||||
closeChannel();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
using namespace Internal;
|
||||
|
||||
SshForwardedTcpIpTunnel::SshForwardedTcpIpTunnel(quint32 channelId, SshSendFacility &sendFacility) :
|
||||
d(new SshForwardedTcpIpTunnelPrivate(channelId, sendFacility))
|
||||
{
|
||||
d->init(this);
|
||||
}
|
||||
|
||||
SshForwardedTcpIpTunnel::~SshForwardedTcpIpTunnel()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool SshForwardedTcpIpTunnel::atEnd() const
|
||||
{
|
||||
return QIODevice::atEnd() && d->m_data.isEmpty();
|
||||
}
|
||||
|
||||
qint64 SshForwardedTcpIpTunnel::bytesAvailable() const
|
||||
{
|
||||
return QIODevice::bytesAvailable() + d->m_data.count();
|
||||
}
|
||||
|
||||
bool SshForwardedTcpIpTunnel::canReadLine() const
|
||||
{
|
||||
return QIODevice::canReadLine() || d->m_data.contains('\n');
|
||||
}
|
||||
|
||||
void SshForwardedTcpIpTunnel::close()
|
||||
{
|
||||
d->closeChannel();
|
||||
QIODevice::close();
|
||||
}
|
||||
|
||||
qint64 SshForwardedTcpIpTunnel::readData(char *data, qint64 maxlen)
|
||||
{
|
||||
return d->readData(data, maxlen);
|
||||
}
|
||||
|
||||
qint64 SshForwardedTcpIpTunnel::writeData(const char *data, qint64 len)
|
||||
{
|
||||
return d->writeData(data, len);
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,70 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
#include <QIODevice>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
namespace Internal {
|
||||
class SshChannelManager;
|
||||
class SshForwardedTcpIpTunnelPrivate;
|
||||
class SshSendFacility;
|
||||
class SshTcpIpTunnelPrivate;
|
||||
} // namespace Internal
|
||||
|
||||
class QSSH_EXPORT SshForwardedTcpIpTunnel : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class Internal::SshChannelManager;
|
||||
friend class Internal::SshTcpIpTunnelPrivate;
|
||||
|
||||
public:
|
||||
typedef QSharedPointer<SshForwardedTcpIpTunnel> Ptr;
|
||||
~SshForwardedTcpIpTunnel() override;
|
||||
|
||||
// QIODevice stuff
|
||||
bool atEnd() const override;
|
||||
qint64 bytesAvailable() const override;
|
||||
bool canReadLine() const override;
|
||||
void close() override;
|
||||
bool isSequential() const override { return true; }
|
||||
|
||||
signals:
|
||||
void error(const QString &reason);
|
||||
|
||||
private:
|
||||
SshForwardedTcpIpTunnel(quint32 channelId, Internal::SshSendFacility &sendFacility);
|
||||
|
||||
// QIODevice stuff
|
||||
qint64 readData(char *data, qint64 maxlen) override;
|
||||
qint64 writeData(const char *data, qint64 len) override;
|
||||
|
||||
Internal::SshForwardedTcpIpTunnelPrivate * const d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,44 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshforwardedtcpiptunnel.h"
|
||||
#include "sshtcpiptunnel_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SshForwardedTcpIpTunnelPrivate : public SshTcpIpTunnelPrivate
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class QSsh::SshForwardedTcpIpTunnel;
|
||||
public:
|
||||
SshForwardedTcpIpTunnelPrivate(quint32 channelId, SshSendFacility &sendFacility);
|
||||
void handleOpenSuccessInternal() override;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,118 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshhostkeydatabase.h"
|
||||
|
||||
#include "sshlogging_p.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
class SshHostKeyDatabase::SshHostKeyDatabasePrivate
|
||||
{
|
||||
public:
|
||||
QHash<QString, QByteArray> hostKeys;
|
||||
};
|
||||
|
||||
SshHostKeyDatabase::SshHostKeyDatabase() : d(new SshHostKeyDatabasePrivate)
|
||||
{
|
||||
}
|
||||
|
||||
SshHostKeyDatabase::~SshHostKeyDatabase()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool SshHostKeyDatabase::load(const QString &filePath, QString *error)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (error) {
|
||||
*error = QCoreApplication::translate("QSsh::Ssh",
|
||||
"Failed to open key file \"%1\" for reading: %2")
|
||||
.arg(QDir::toNativeSeparators(filePath), file.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
d->hostKeys.clear();
|
||||
const QByteArray content = file.readAll().trimmed();
|
||||
if (content.isEmpty())
|
||||
return true;
|
||||
foreach (const QByteArray &line, content.split('\n')) {
|
||||
const QList<QByteArray> &lineData = line.trimmed().split(' ');
|
||||
if (lineData.count() != 2) {
|
||||
qCDebug(Internal::sshLog, "Unexpected line \"%s\" in file \"%s\".", line.constData(),
|
||||
qPrintable(filePath));
|
||||
continue;
|
||||
}
|
||||
d->hostKeys.insert(QString::fromUtf8(lineData.first()),
|
||||
QByteArray::fromHex(lineData.last()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SshHostKeyDatabase::store(const QString &filePath, QString *error) const
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
if (error) {
|
||||
*error = QCoreApplication::translate("QSsh::Ssh",
|
||||
"Failed to open key file \"%1\" for writing: %2")
|
||||
.arg(QDir::toNativeSeparators(filePath), file.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
file.resize(0);
|
||||
for (auto it = d->hostKeys.constBegin(); it != d->hostKeys.constEnd(); ++it)
|
||||
file.write(it.key().toUtf8() + ' ' + it.value().toHex() + '\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
SshHostKeyDatabase::KeyLookupResult SshHostKeyDatabase::matchHostKey(const QString &hostName,
|
||||
const QByteArray &key) const
|
||||
{
|
||||
auto it = d->hostKeys.constFind(hostName);
|
||||
if (it == d->hostKeys.constEnd())
|
||||
return KeyLookupNoMatch;
|
||||
if (it.value() == key)
|
||||
return KeyLookupMatch;
|
||||
return KeyLookupMismatch;
|
||||
}
|
||||
|
||||
void SshHostKeyDatabase::insertHostKey(const QString &hostName, const QByteArray &key)
|
||||
{
|
||||
d->hostKeys.insert(hostName, key);
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,66 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QByteArray;
|
||||
class QString;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace QSsh {
|
||||
class SshHostKeyDatabase;
|
||||
typedef QSharedPointer<SshHostKeyDatabase> SshHostKeyDatabasePtr;
|
||||
|
||||
class QSSH_EXPORT SshHostKeyDatabase
|
||||
{
|
||||
friend class QSharedPointer<SshHostKeyDatabase>; // To give create() access to our constructor.
|
||||
|
||||
public:
|
||||
enum KeyLookupResult {
|
||||
KeyLookupMatch,
|
||||
KeyLookupNoMatch,
|
||||
KeyLookupMismatch
|
||||
};
|
||||
|
||||
~SshHostKeyDatabase();
|
||||
|
||||
bool load(const QString &filePath, QString *error = 0);
|
||||
bool store(const QString &filePath, QString *error = 0) const;
|
||||
KeyLookupResult matchHostKey(const QString &hostName, const QByteArray &key) const;
|
||||
void insertHostKey(const QString &hostName, const QByteArray &key);
|
||||
|
||||
private:
|
||||
SshHostKeyDatabase();
|
||||
|
||||
class SshHostKeyDatabasePrivate;
|
||||
SshHostKeyDatabasePrivate * const d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,552 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshincomingpacket_p.h"
|
||||
|
||||
#include "ssh_global.h"
|
||||
#include "sshbotanconversions_p.h"
|
||||
#include "sshcapabilities_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
const QByteArray SshIncomingPacket::ExitStatusType("exit-status");
|
||||
const QByteArray SshIncomingPacket::ExitSignalType("exit-signal");
|
||||
const QByteArray SshIncomingPacket::ForwardedTcpIpType("forwarded-tcpip");
|
||||
|
||||
SshIncomingPacket::SshIncomingPacket() : m_serverSeqNr(0) { }
|
||||
|
||||
quint32 SshIncomingPacket::cipherBlockSize() const
|
||||
{
|
||||
return qMax(m_decrypter.cipherBlockSize(), 8U);
|
||||
}
|
||||
|
||||
quint32 SshIncomingPacket::macLength() const
|
||||
{
|
||||
return m_decrypter.macLength();
|
||||
}
|
||||
|
||||
void SshIncomingPacket::recreateKeys(const SshKeyExchange &keyExchange)
|
||||
{
|
||||
m_decrypter.recreateKeys(keyExchange);
|
||||
}
|
||||
|
||||
void SshIncomingPacket::reset()
|
||||
{
|
||||
clear();
|
||||
m_serverSeqNr = 0;
|
||||
m_decrypter.clearKeys();
|
||||
}
|
||||
|
||||
void SshIncomingPacket::consumeData(QByteArray &newData)
|
||||
{
|
||||
qCDebug(sshLog, "%s: current data size = %d, new data size = %d",
|
||||
Q_FUNC_INFO, m_data.size(), newData.size());
|
||||
|
||||
if (isComplete() || newData.isEmpty())
|
||||
return;
|
||||
|
||||
/*
|
||||
* Until we have reached the minimum packet size, we cannot decrypt the
|
||||
* length field.
|
||||
*/
|
||||
const quint32 minSize = minPacketSize();
|
||||
if (currentDataSize() < minSize) {
|
||||
const int bytesToTake
|
||||
= qMin<quint32>(minSize - currentDataSize(), newData.size());
|
||||
moveFirstBytes(m_data, newData, bytesToTake);
|
||||
qCDebug(sshLog, "Took %d bytes from new data", bytesToTake);
|
||||
if (currentDataSize() < minSize)
|
||||
return;
|
||||
}
|
||||
|
||||
if (4 + length() + macLength() < currentDataSize())
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, "Server sent invalid packet.");
|
||||
|
||||
const int bytesToTake
|
||||
= qMin<quint32>(length() + 4 + macLength() - currentDataSize(),
|
||||
newData.size());
|
||||
moveFirstBytes(m_data, newData, bytesToTake);
|
||||
qCDebug(sshLog, "Took %d bytes from new data", bytesToTake);
|
||||
if (isComplete()) {
|
||||
qCDebug(sshLog, "Message complete. Overall size: %u, payload size: %u",
|
||||
m_data.size(), m_length - paddingLength() - 1);
|
||||
decrypt();
|
||||
++m_serverSeqNr;
|
||||
}
|
||||
}
|
||||
|
||||
void SshIncomingPacket::decrypt()
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
const quint32 netDataLength = length() + 4;
|
||||
m_decrypter.decrypt(m_data, cipherBlockSize(),
|
||||
netDataLength - cipherBlockSize());
|
||||
const QByteArray &mac = m_data.mid(netDataLength, macLength());
|
||||
if (mac != generateMac(m_decrypter, m_serverSeqNr)) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_MAC_ERROR,
|
||||
"Message authentication failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void SshIncomingPacket::moveFirstBytes(QByteArray &target, QByteArray &source,
|
||||
int n)
|
||||
{
|
||||
target.append(source.left(n));
|
||||
source.remove(0, n);
|
||||
}
|
||||
|
||||
SshKeyExchangeInit SshIncomingPacket::extractKeyExchangeInitData() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_KEXINIT);
|
||||
|
||||
SshKeyExchangeInit exchangeData;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
std::memcpy(exchangeData.cookie, &m_data.constData()[offset],
|
||||
sizeof exchangeData.cookie);
|
||||
offset += sizeof exchangeData.cookie;
|
||||
exchangeData.keyAlgorithms
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.serverHostKeyAlgorithms
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.encryptionAlgorithmsClientToServer
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.encryptionAlgorithmsServerToClient
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.macAlgorithmsClientToServer
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.macAlgorithmsServerToClient
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.compressionAlgorithmsClientToServer
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.compressionAlgorithmsServerToClient
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.languagesClientToServer
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.languagesServerToClient
|
||||
= SshPacketParser::asNameList(m_data, &offset);
|
||||
exchangeData.firstKexPacketFollows
|
||||
= SshPacketParser::asBool(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
"Key exchange failed: Server sent invalid SSH_MSG_KEXINIT packet.");
|
||||
}
|
||||
return exchangeData;
|
||||
}
|
||||
|
||||
static void getHostKeySpecificReplyData(SshKeyExchangeReply &replyData,
|
||||
const QByteArray &hostKeyAlgo, const QByteArray &input)
|
||||
{
|
||||
quint32 offset = 0;
|
||||
if (hostKeyAlgo == SshCapabilities::PubKeyDss || hostKeyAlgo == SshCapabilities::PubKeyRsa) {
|
||||
// DSS: p and q, RSA: e and n
|
||||
replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
|
||||
replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
|
||||
|
||||
// g and y
|
||||
if (hostKeyAlgo == SshCapabilities::PubKeyDss) {
|
||||
replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
|
||||
replyData.hostKeyParameters << SshPacketParser::asBigInt(input, &offset);
|
||||
}
|
||||
} else {
|
||||
QSSH_ASSERT_AND_RETURN(hostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix));
|
||||
if (SshPacketParser::asString(input, &offset)
|
||||
!= hostKeyAlgo.mid(11)) { // Without "ecdsa-sha2-" prefix.
|
||||
throw SshPacketParseException();
|
||||
}
|
||||
replyData.q = SshPacketParser::asString(input, &offset);
|
||||
}
|
||||
}
|
||||
|
||||
static QByteArray &padToWidth(QByteArray &data, int targetWidth)
|
||||
{
|
||||
return data.prepend(QByteArray(targetWidth - data.count(), 0));
|
||||
}
|
||||
|
||||
SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &kexAlgo,
|
||||
const QByteArray &hostKeyAlgo) const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY);
|
||||
|
||||
try {
|
||||
SshKeyExchangeReply replyData;
|
||||
quint32 topLevelOffset = TypeOffset + 1;
|
||||
replyData.k_s = SshPacketParser::asString(m_data, &topLevelOffset);
|
||||
quint32 k_sOffset = 0;
|
||||
if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != hostKeyAlgo)
|
||||
throw SshPacketParseException();
|
||||
getHostKeySpecificReplyData(replyData, hostKeyAlgo, replyData.k_s.mid(k_sOffset));
|
||||
|
||||
if (kexAlgo == SshCapabilities::DiffieHellmanGroup1Sha1) {
|
||||
replyData.f = SshPacketParser::asBigInt(m_data, &topLevelOffset);
|
||||
} else {
|
||||
QSSH_ASSERT_AND_RETURN_VALUE(kexAlgo.startsWith(SshCapabilities::EcdhKexNamePrefix),
|
||||
SshKeyExchangeReply());
|
||||
replyData.q_s = SshPacketParser::asString(m_data, &topLevelOffset);
|
||||
}
|
||||
const QByteArray fullSignature = SshPacketParser::asString(m_data, &topLevelOffset);
|
||||
quint32 sigOffset = 0;
|
||||
if (SshPacketParser::asString(fullSignature, &sigOffset) != hostKeyAlgo)
|
||||
throw SshPacketParseException();
|
||||
replyData.signatureBlob = SshPacketParser::asString(fullSignature, &sigOffset);
|
||||
if (hostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix)) {
|
||||
// Botan's PK_Verifier wants the signature in this format.
|
||||
quint32 blobOffset = 0;
|
||||
const Botan::BigInt r = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset);
|
||||
const Botan::BigInt s = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset);
|
||||
const int width = SshCapabilities::ecdsaIntegerWidthInBytes(hostKeyAlgo);
|
||||
QByteArray encodedR = convertByteArray(Botan::BigInt::encode(r));
|
||||
replyData.signatureBlob = padToWidth(encodedR, width);
|
||||
QByteArray encodedS = convertByteArray(Botan::BigInt::encode(s));
|
||||
replyData.signatureBlob += padToWidth(encodedS, width);
|
||||
}
|
||||
replyData.k_s.prepend(m_data.mid(TypeOffset + 1, 4));
|
||||
return replyData;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
"Key exchange failed: "
|
||||
"Server sent invalid key exchange reply packet.");
|
||||
}
|
||||
}
|
||||
|
||||
SshDisconnect SshIncomingPacket::extractDisconnect() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_DISCONNECT);
|
||||
|
||||
SshDisconnect msg;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.reasonCode = SshPacketParser::asUint32(m_data, &offset);
|
||||
msg.description = SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.language = SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_DISCONNECT.");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
SshUserAuthBanner SshIncomingPacket::extractUserAuthBanner() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_USERAUTH_BANNER);
|
||||
|
||||
try {
|
||||
SshUserAuthBanner msg;
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.message = SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.language = SshPacketParser::asString(m_data, &offset);
|
||||
return msg;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_USERAUTH_BANNER.");
|
||||
}
|
||||
}
|
||||
|
||||
SshUserAuthInfoRequestPacket SshIncomingPacket::extractUserAuthInfoRequest() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_USERAUTH_INFO_REQUEST);
|
||||
|
||||
try {
|
||||
SshUserAuthInfoRequestPacket msg;
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.name = SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.instruction = SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.languageTag = SshPacketParser::asString(m_data, &offset);
|
||||
const quint32 promptCount = SshPacketParser::asUint32(m_data, &offset);
|
||||
msg.prompts.reserve(promptCount);
|
||||
msg.echos.reserve(promptCount);
|
||||
for (quint32 i = 0; i < promptCount; ++i) {
|
||||
msg.prompts << SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.echos << SshPacketParser::asBool(m_data, &offset);
|
||||
}
|
||||
return msg;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_USERAUTH_INFO_REQUEST.");
|
||||
}
|
||||
}
|
||||
|
||||
SshDebug SshIncomingPacket::extractDebug() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_DEBUG);
|
||||
|
||||
try {
|
||||
SshDebug msg;
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.display = SshPacketParser::asBool(m_data, &offset);
|
||||
msg.message = SshPacketParser::asUserString(m_data, &offset);
|
||||
msg.language = SshPacketParser::asString(m_data, &offset);
|
||||
return msg;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_DEBUG.");
|
||||
}
|
||||
}
|
||||
|
||||
SshRequestSuccess SshIncomingPacket::extractRequestSuccess() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_REQUEST_SUCCESS);
|
||||
|
||||
try {
|
||||
SshRequestSuccess msg;
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.bindPort = SshPacketParser::asUint32(m_data, &offset);
|
||||
return msg;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_REQUEST_SUCCESS.");
|
||||
}
|
||||
}
|
||||
|
||||
SshUnimplemented SshIncomingPacket::extractUnimplemented() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_UNIMPLEMENTED);
|
||||
|
||||
try {
|
||||
SshUnimplemented msg;
|
||||
quint32 offset = TypeOffset + 1;
|
||||
msg.invalidMsgSeqNr = SshPacketParser::asUint32(m_data, &offset);
|
||||
return msg;
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_UNIMPLEMENTED.");
|
||||
}
|
||||
}
|
||||
|
||||
SshChannelOpen SshIncomingPacket::extractChannelOpen() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN);
|
||||
|
||||
SshChannelOpen open;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
QByteArray type = SshPacketParser::asString(m_data, &offset);
|
||||
open.remoteChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
open.remoteWindowSize = SshPacketParser::asUint32(m_data, &offset);
|
||||
open.remoteMaxPacketSize = SshPacketParser::asUint32(m_data, &offset);
|
||||
if (type == ForwardedTcpIpType) {
|
||||
open.remoteAddress = SshPacketParser::asString(m_data, &offset);
|
||||
open.remotePort = SshPacketParser::asUint32(m_data, &offset);
|
||||
} else {
|
||||
open.remotePort = 0;
|
||||
}
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Server sent invalid SSH_MSG_CHANNEL_OPEN packet.");
|
||||
}
|
||||
return open;
|
||||
}
|
||||
|
||||
SshChannelOpenFailure SshIncomingPacket::extractChannelOpenFailure() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_FAILURE);
|
||||
|
||||
SshChannelOpenFailure openFailure;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
openFailure.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
openFailure.reasonCode = SshPacketParser::asUint32(m_data, &offset);
|
||||
openFailure.reasonString = QString::fromLocal8Bit(SshPacketParser::asString(m_data, &offset));
|
||||
openFailure.language = SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Server sent invalid SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
|
||||
}
|
||||
return openFailure;
|
||||
}
|
||||
|
||||
SshChannelOpenConfirmation SshIncomingPacket::extractChannelOpenConfirmation() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
|
||||
|
||||
SshChannelOpenConfirmation confirmation;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
confirmation.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
confirmation.remoteChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
confirmation.remoteWindowSize = SshPacketParser::asUint32(m_data, &offset);
|
||||
confirmation.remoteMaxPacketSize = SshPacketParser::asUint32(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Server sent invalid SSH_MSG_CHANNEL_OPEN_CONFIRMATION packet.");
|
||||
}
|
||||
return confirmation;
|
||||
}
|
||||
|
||||
SshChannelWindowAdjust SshIncomingPacket::extractWindowAdjust() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_WINDOW_ADJUST);
|
||||
|
||||
SshChannelWindowAdjust adjust;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
adjust.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
adjust.bytesToAdd = SshPacketParser::asUint32(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_CHANNEL_WINDOW_ADJUST packet.");
|
||||
}
|
||||
return adjust;
|
||||
}
|
||||
|
||||
SshChannelData SshIncomingPacket::extractChannelData() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_DATA);
|
||||
|
||||
SshChannelData data;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
data.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
data.data = SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_CHANNEL_DATA packet.");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
SshChannelExtendedData SshIncomingPacket::extractChannelExtendedData() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_EXTENDED_DATA);
|
||||
|
||||
SshChannelExtendedData data;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
data.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
data.type = SshPacketParser::asUint32(m_data, &offset);
|
||||
data.data = SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_CHANNEL_EXTENDED_DATA packet.");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
SshChannelExitStatus SshIncomingPacket::extractChannelExitStatus() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
|
||||
|
||||
SshChannelExitStatus exitStatus;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
exitStatus.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
const QByteArray &type = SshPacketParser::asString(m_data, &offset);
|
||||
Q_ASSERT(type == ExitStatusType);
|
||||
Q_UNUSED(type);
|
||||
if (SshPacketParser::asBool(m_data, &offset))
|
||||
throw SshPacketParseException();
|
||||
exitStatus.exitStatus = SshPacketParser::asUint32(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid exit-status packet.");
|
||||
}
|
||||
return exitStatus;
|
||||
}
|
||||
|
||||
SshChannelExitSignal SshIncomingPacket::extractChannelExitSignal() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
|
||||
|
||||
SshChannelExitSignal exitSignal;
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
exitSignal.localChannel = SshPacketParser::asUint32(m_data, &offset);
|
||||
const QByteArray &type = SshPacketParser::asString(m_data, &offset);
|
||||
Q_ASSERT(type == ExitSignalType);
|
||||
Q_UNUSED(type);
|
||||
if (SshPacketParser::asBool(m_data, &offset))
|
||||
throw SshPacketParseException();
|
||||
exitSignal.signal = SshPacketParser::asString(m_data, &offset);
|
||||
exitSignal.coreDumped = SshPacketParser::asBool(m_data, &offset);
|
||||
exitSignal.error = SshPacketParser::asUserString(m_data, &offset);
|
||||
exitSignal.language = SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid exit-signal packet.");
|
||||
}
|
||||
return exitSignal;
|
||||
}
|
||||
|
||||
quint32 SshIncomingPacket::extractRecipientChannel() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
return SshPacketParser::asUint32(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Server sent invalid packet.");
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SshIncomingPacket::extractChannelRequestType() const
|
||||
{
|
||||
Q_ASSERT(isComplete());
|
||||
Q_ASSERT(type() == SSH_MSG_CHANNEL_REQUEST);
|
||||
|
||||
try {
|
||||
quint32 offset = TypeOffset + 1;
|
||||
SshPacketParser::asUint32(m_data, &offset);
|
||||
return SshPacketParser::asString(m_data, &offset);
|
||||
} catch (const SshPacketParseException &) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
|
||||
"Invalid SSH_MSG_CHANNEL_REQUEST packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SshIncomingPacket::calculateLength() const
|
||||
{
|
||||
Q_ASSERT(currentDataSize() >= minPacketSize());
|
||||
qCDebug(sshLog, "Length field before decryption: %d-%d-%d-%d", m_data.at(0) & 0xff,
|
||||
m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff);
|
||||
m_decrypter.decrypt(m_data, 0, cipherBlockSize());
|
||||
qCDebug(sshLog, "Length field after decryption: %d-%d-%d-%d", m_data.at(0) & 0xff, m_data.at(1) & 0xff, m_data.at(2) & 0xff, m_data.at(3) & 0xff);
|
||||
qCDebug(sshLog, "message type = %d", m_data.at(TypeOffset));
|
||||
m_length = SshPacketParser::asUint32(m_data, static_cast<quint32>(0));
|
||||
qCDebug(sshLog, "decrypted length is %u", m_length);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,213 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sshpacket_p.h"
|
||||
|
||||
#include "sshcryptofacility_p.h"
|
||||
#include "sshpacketparser_p.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
class SshKeyExchange;
|
||||
|
||||
struct SshKeyExchangeInit
|
||||
{
|
||||
char cookie[16];
|
||||
SshNameList keyAlgorithms;
|
||||
SshNameList serverHostKeyAlgorithms;
|
||||
SshNameList encryptionAlgorithmsClientToServer;
|
||||
SshNameList encryptionAlgorithmsServerToClient;
|
||||
SshNameList macAlgorithmsClientToServer;
|
||||
SshNameList macAlgorithmsServerToClient;
|
||||
SshNameList compressionAlgorithmsClientToServer;
|
||||
SshNameList compressionAlgorithmsServerToClient;
|
||||
SshNameList languagesClientToServer;
|
||||
SshNameList languagesServerToClient;
|
||||
bool firstKexPacketFollows;
|
||||
};
|
||||
|
||||
struct SshKeyExchangeReply
|
||||
{
|
||||
QByteArray k_s;
|
||||
QList<Botan::BigInt> hostKeyParameters; // DSS: p, q, g, y. RSA: e, n.
|
||||
QByteArray q; // For ECDSA host keys only.
|
||||
Botan::BigInt f; // For DH only.
|
||||
QByteArray q_s; // For ECDH only.
|
||||
QByteArray signatureBlob;
|
||||
};
|
||||
|
||||
struct SshDisconnect
|
||||
{
|
||||
quint32 reasonCode;
|
||||
QString description;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
struct SshUserAuthBanner
|
||||
{
|
||||
QString message;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
struct SshUserAuthInfoRequestPacket
|
||||
{
|
||||
QString name;
|
||||
QString instruction;
|
||||
QByteArray languageTag;
|
||||
QStringList prompts;
|
||||
QList<bool> echos;
|
||||
};
|
||||
|
||||
struct SshDebug
|
||||
{
|
||||
bool display;
|
||||
QString message;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
struct SshUnimplemented
|
||||
{
|
||||
quint32 invalidMsgSeqNr;
|
||||
};
|
||||
|
||||
struct SshRequestSuccess
|
||||
{
|
||||
quint32 bindPort;
|
||||
};
|
||||
|
||||
struct SshChannelOpen
|
||||
{
|
||||
quint32 remoteChannel;
|
||||
quint32 remoteWindowSize;
|
||||
quint32 remoteMaxPacketSize;
|
||||
QByteArray remoteAddress;
|
||||
quint32 remotePort;
|
||||
};
|
||||
|
||||
struct SshChannelOpenFailure
|
||||
{
|
||||
quint32 localChannel;
|
||||
quint32 reasonCode;
|
||||
QString reasonString;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
struct SshChannelOpenConfirmation
|
||||
{
|
||||
quint32 localChannel;
|
||||
quint32 remoteChannel;
|
||||
quint32 remoteWindowSize;
|
||||
quint32 remoteMaxPacketSize;
|
||||
};
|
||||
|
||||
struct SshChannelWindowAdjust
|
||||
{
|
||||
quint32 localChannel;
|
||||
quint32 bytesToAdd;
|
||||
};
|
||||
|
||||
struct SshChannelData
|
||||
{
|
||||
quint32 localChannel;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
struct SshChannelExtendedData
|
||||
{
|
||||
quint32 localChannel;
|
||||
quint32 type;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
struct SshChannelExitStatus
|
||||
{
|
||||
quint32 localChannel;
|
||||
quint32 exitStatus;
|
||||
};
|
||||
|
||||
struct SshChannelExitSignal
|
||||
{
|
||||
quint32 localChannel;
|
||||
QByteArray signal;
|
||||
bool coreDumped;
|
||||
QString error;
|
||||
QByteArray language;
|
||||
};
|
||||
|
||||
class SshIncomingPacket : public AbstractSshPacket
|
||||
{
|
||||
public:
|
||||
SshIncomingPacket();
|
||||
|
||||
void consumeData(QByteArray &data);
|
||||
void recreateKeys(const SshKeyExchange &keyExchange);
|
||||
void reset();
|
||||
|
||||
SshKeyExchangeInit extractKeyExchangeInitData() const;
|
||||
SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &kexAlgo,
|
||||
const QByteArray &hostKeyAlgo) const;
|
||||
SshDisconnect extractDisconnect() const;
|
||||
SshUserAuthBanner extractUserAuthBanner() const;
|
||||
SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
|
||||
SshDebug extractDebug() const;
|
||||
SshRequestSuccess extractRequestSuccess() const;
|
||||
SshUnimplemented extractUnimplemented() const;
|
||||
|
||||
SshChannelOpen extractChannelOpen() const;
|
||||
SshChannelOpenFailure extractChannelOpenFailure() const;
|
||||
SshChannelOpenConfirmation extractChannelOpenConfirmation() const;
|
||||
SshChannelWindowAdjust extractWindowAdjust() const;
|
||||
SshChannelData extractChannelData() const;
|
||||
SshChannelExtendedData extractChannelExtendedData() const;
|
||||
SshChannelExitStatus extractChannelExitStatus() const;
|
||||
SshChannelExitSignal extractChannelExitSignal() const;
|
||||
quint32 extractRecipientChannel() const;
|
||||
QByteArray extractChannelRequestType() const;
|
||||
|
||||
quint32 serverSeqNr() const { return m_serverSeqNr; }
|
||||
|
||||
static const QByteArray ExitStatusType;
|
||||
static const QByteArray ExitSignalType;
|
||||
static const QByteArray ForwardedTcpIpType;
|
||||
|
||||
private:
|
||||
virtual quint32 cipherBlockSize() const;
|
||||
virtual quint32 macLength() const;
|
||||
virtual void calculateLength() const;
|
||||
|
||||
void decrypt();
|
||||
void moveFirstBytes(QByteArray &target, QByteArray &source, int n);
|
||||
|
||||
quint32 m_serverSeqNr;
|
||||
SshDecryptionFacility m_decrypter;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,49 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshinit_p.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
static bool initialized = false;
|
||||
static QMutex initMutex;
|
||||
|
||||
void initSsh()
|
||||
{
|
||||
QMutexLocker locker(&initMutex);
|
||||
if (!initialized) {
|
||||
Botan::LibraryInitializer::initialize("thread_safe=true");
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,32 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
void initSsh();
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
@@ -1,174 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshkeycreationdialog.h"
|
||||
#include "ui_sshkeycreationdialog.h"
|
||||
|
||||
#include "sshkeygenerator.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace QSsh {
|
||||
|
||||
SshKeyCreationDialog::SshKeyCreationDialog(QWidget *parent)
|
||||
: QDialog(parent), m_keyGenerator(0), m_ui(new Ui::SshKeyCreationDialog)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
// Not using Utils::PathChooser::browseButtonLabel to avoid dependency
|
||||
#ifdef Q_OS_MAC
|
||||
m_ui->privateKeyFileButton->setText(tr("Choose..."));
|
||||
#else
|
||||
m_ui->privateKeyFileButton->setText(tr("Browse..."));
|
||||
#endif
|
||||
const QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
|
||||
+ QLatin1String("/.ssh/qtc_id");
|
||||
setPrivateKeyFile(defaultPath);
|
||||
|
||||
connect(m_ui->rsa, &QRadioButton::toggled,
|
||||
this, &SshKeyCreationDialog::keyTypeChanged);
|
||||
connect(m_ui->dsa, &QRadioButton::toggled,
|
||||
this, &SshKeyCreationDialog::keyTypeChanged);
|
||||
connect(m_ui->privateKeyFileButton, &QPushButton::clicked,
|
||||
this, &SshKeyCreationDialog::handleBrowseButtonClicked);
|
||||
connect(m_ui->generateButton, &QPushButton::clicked,
|
||||
this, &SshKeyCreationDialog::generateKeys);
|
||||
keyTypeChanged();
|
||||
}
|
||||
|
||||
SshKeyCreationDialog::~SshKeyCreationDialog()
|
||||
{
|
||||
delete m_keyGenerator;
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void SshKeyCreationDialog::keyTypeChanged()
|
||||
{
|
||||
m_ui->comboBox->clear();
|
||||
QStringList keySizes;
|
||||
if (m_ui->rsa->isChecked())
|
||||
keySizes << QLatin1String("1024") << QLatin1String("2048") << QLatin1String("4096");
|
||||
else if (m_ui->ecdsa->isChecked())
|
||||
keySizes << QLatin1String("256") << QLatin1String("384") << QLatin1String("521");
|
||||
else if (m_ui->dsa->isChecked())
|
||||
keySizes << QLatin1String("1024");
|
||||
m_ui->comboBox->addItems(keySizes);
|
||||
if (!keySizes.isEmpty())
|
||||
m_ui->comboBox->setCurrentIndex(0);
|
||||
m_ui->comboBox->setEnabled(!keySizes.isEmpty());
|
||||
}
|
||||
|
||||
void SshKeyCreationDialog::generateKeys()
|
||||
{
|
||||
if (userForbidsOverwriting())
|
||||
return;
|
||||
|
||||
const SshKeyGenerator::KeyType keyType = m_ui->rsa->isChecked()
|
||||
? SshKeyGenerator::Rsa : m_ui->dsa->isChecked()
|
||||
? SshKeyGenerator::Dsa : SshKeyGenerator::Ecdsa;
|
||||
|
||||
if (!m_keyGenerator)
|
||||
m_keyGenerator = new SshKeyGenerator;
|
||||
|
||||
QApplication::setOverrideCursor(Qt::BusyCursor);
|
||||
const bool success = m_keyGenerator->generateKeys(keyType, SshKeyGenerator::Mixed,
|
||||
m_ui->comboBox->currentText().toUShort());
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (success)
|
||||
saveKeys();
|
||||
else
|
||||
QMessageBox::critical(this, tr("Key Generation Failed"), m_keyGenerator->error());
|
||||
}
|
||||
|
||||
void SshKeyCreationDialog::handleBrowseButtonClicked()
|
||||
{
|
||||
const QString filePath = QFileDialog::getSaveFileName(this, tr("Choose Private Key File Name"));
|
||||
if (!filePath.isEmpty())
|
||||
setPrivateKeyFile(filePath);
|
||||
}
|
||||
|
||||
void SshKeyCreationDialog::setPrivateKeyFile(const QString &filePath)
|
||||
{
|
||||
m_ui->privateKeyFileValueLabel->setText(filePath);
|
||||
m_ui->generateButton->setEnabled(!privateKeyFilePath().isEmpty());
|
||||
m_ui->publicKeyFileLabel->setText(filePath + QLatin1String(".pub"));
|
||||
}
|
||||
|
||||
void SshKeyCreationDialog::saveKeys()
|
||||
{
|
||||
const QString parentDir = QFileInfo(privateKeyFilePath()).dir().path();
|
||||
if (!QDir::root().mkpath(parentDir)) {
|
||||
QMessageBox::critical(this, tr("Cannot Save Key File"),
|
||||
tr("Failed to create directory: \"%1\".").arg(parentDir));
|
||||
return;
|
||||
}
|
||||
|
||||
QFile privateKeyFile(privateKeyFilePath());
|
||||
if (!privateKeyFile.open(QIODevice::WriteOnly)
|
||||
|| !privateKeyFile.write(m_keyGenerator->privateKey())) {
|
||||
QMessageBox::critical(this, tr("Cannot Save Private Key File"),
|
||||
tr("The private key file could not be saved: %1").arg(privateKeyFile.errorString()));
|
||||
return;
|
||||
}
|
||||
QFile::setPermissions(privateKeyFilePath(), QFile::ReadOwner | QFile::WriteOwner);
|
||||
|
||||
QFile publicKeyFile(publicKeyFilePath());
|
||||
if (!publicKeyFile.open(QIODevice::WriteOnly)
|
||||
|| !publicKeyFile.write(m_keyGenerator->publicKey())) {
|
||||
QMessageBox::critical(this, tr("Cannot Save Public Key File"),
|
||||
tr("The public key file could not be saved: %1").arg(publicKeyFile.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
bool SshKeyCreationDialog::userForbidsOverwriting()
|
||||
{
|
||||
if (!QFileInfo::exists(privateKeyFilePath()) && !QFileInfo::exists(publicKeyFilePath()))
|
||||
return false;
|
||||
const QMessageBox::StandardButton reply = QMessageBox::question(this, tr("File Exists"),
|
||||
tr("There already is a file of that name. Do you want to overwrite it?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
return reply != QMessageBox::Yes;
|
||||
}
|
||||
|
||||
QString SshKeyCreationDialog::privateKeyFilePath() const
|
||||
{
|
||||
return m_ui->privateKeyFileValueLabel->text();
|
||||
}
|
||||
|
||||
QString SshKeyCreationDialog::publicKeyFilePath() const
|
||||
{
|
||||
return m_ui->publicKeyFileLabel->text();
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,60 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace QSsh {
|
||||
class SshKeyGenerator;
|
||||
|
||||
namespace Ui { class SshKeyCreationDialog; }
|
||||
|
||||
class QSSH_EXPORT SshKeyCreationDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SshKeyCreationDialog(QWidget *parent = 0);
|
||||
~SshKeyCreationDialog();
|
||||
|
||||
QString privateKeyFilePath() const;
|
||||
QString publicKeyFilePath() const;
|
||||
|
||||
private:
|
||||
void keyTypeChanged();
|
||||
void generateKeys();
|
||||
void handleBrowseButtonClicked();
|
||||
void setPrivateKeyFile(const QString &filePath);
|
||||
void saveKeys();
|
||||
bool userForbidsOverwriting();
|
||||
|
||||
private:
|
||||
SshKeyGenerator *m_keyGenerator;
|
||||
Ui::SshKeyCreationDialog *m_ui;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
||||
@@ -1,264 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QSsh::SshKeyCreationDialog</class>
|
||||
<widget class="QDialog" name="QSsh::SshKeyCreationDialog">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>380</width>
|
||||
<height>231</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>SSH Key Configuration</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="keyAlgo">
|
||||
<property name="text">
|
||||
<string>Key algorithm:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rsa">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&RSA</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="dsa">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&DSA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="ecdsa">
|
||||
<property name="text">
|
||||
<string>ECDSA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="keySize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Key &size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>comboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="privateKeyFileLabel">
|
||||
<property name="text">
|
||||
<string>Private key file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="privateKeyFileValueLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="privateKeyFileButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Public key file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="publicKeyFileLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="generateButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Generate And Save Key Pair</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>closeButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QSsh::SshKeyCreationDialog</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>195</x>
|
||||
<y>184</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>381</x>
|
||||
<y>107</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user