mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 22:43:24 +00:00
Compare commits
2104 Commits
1.3
...
feature/gr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12206cdb52 | ||
|
|
abb3c918e3 | ||
|
|
ff348a348c | ||
|
|
d67c378bff | ||
|
|
d85a0439c5 | ||
|
|
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 | ||
|
|
93aebf5256 | ||
|
|
502e815a9f | ||
|
|
5312a6e885 | ||
|
|
8bc69bdc62 | ||
|
|
fdd600794e | ||
|
|
7bfbdca72a | ||
|
|
a22c08a41d | ||
|
|
10ea9b418a | ||
|
|
e39efb1d68 | ||
|
|
7db84122f9 | ||
|
|
84ad167ab4 | ||
|
|
ed7e217a6b | ||
|
|
c1b0d4a4a7 | ||
|
|
2f84e24353 | ||
|
|
f73586185b | ||
|
|
653ffb9a68 | ||
|
|
fe8c2d157a | ||
|
|
86367a1276 | ||
|
|
b0fcf92ada | ||
|
|
283b6ebf81 | ||
|
|
d0a7fc5116 | ||
|
|
9851aacba7 | ||
|
|
51f9fb9e0a | ||
|
|
0325761f3e | ||
|
|
b3ae687feb | ||
|
|
a6ca1b12da | ||
|
|
d6d11d6e60 | ||
|
|
82a9e7e27d | ||
|
|
f5301e1315 | ||
|
|
adab30fc81 | ||
|
|
e7bd24f065 | ||
|
|
2ec448ba13 | ||
|
|
c6e6f2ae84 | ||
|
|
b90bf16945 | ||
|
|
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 | ||
|
|
b162147a89 | ||
|
|
bb7b64fb96 | ||
|
|
bf901631bf | ||
|
|
0c0ce54b1f | ||
|
|
ee762c4cef | ||
|
|
ed9efb5a79 | ||
|
|
73eb85f2f4 | ||
|
|
cd055cff62 | ||
|
|
f8b2cce618 | ||
|
|
e648054c7a | ||
|
|
fe558163cc | ||
|
|
3883b8ff34 | ||
|
|
d286664763 | ||
|
|
b05ad2392b | ||
|
|
6dbdb85aaf | ||
|
|
26b48cfe4f | ||
|
|
faaf5973b4 | ||
|
|
aa4bfc70b9 | ||
|
|
ef674d1e4f | ||
|
|
2f39136143 | ||
|
|
8d0d3c5ce9 | ||
|
|
256081e4ed | ||
|
|
1dd7b0a221 | ||
|
|
82c0b28906 | ||
|
|
985fe083f0 | ||
|
|
6a0000dc4b | ||
|
|
1dd2f38066 | ||
|
|
004e1e3ca5 | ||
|
|
7c560d709b | ||
|
|
d3743ad62f | ||
|
|
ac234b77e2 | ||
|
|
9886987e68 | ||
|
|
d34cb8898f | ||
|
|
23633e8fa7 | ||
|
|
13aadbda64 | ||
|
|
c7c7c8eb01 | ||
|
|
b1e5bba33f | ||
|
|
3cc846678e | ||
|
|
474e7c6d62 | ||
|
|
794ec921b8 | ||
|
|
b674240362 | ||
|
|
a768c7c451 | ||
|
|
28d2a4ec2c | ||
|
|
9f1210d18f | ||
|
|
3012559627 | ||
|
|
b3ed57aee7 | ||
|
|
89d0a8107d | ||
|
|
6c0b71bd1b | ||
|
|
61abf74b2d | ||
|
|
21fdf02921 | ||
|
|
7a245d80ee | ||
|
|
da85922f23 | ||
|
|
a5356b6319 | ||
|
|
3828891b9b | ||
|
|
15d866ce04 | ||
|
|
560eb3d620 | ||
|
|
ac894254cc | ||
|
|
17e3fbde25 | ||
|
|
84e8667e57 | ||
|
|
b500a1f09d | ||
|
|
ee11a8410c | ||
|
|
ff5c51cfd9 | ||
|
|
b3943ae5e3 | ||
|
|
a32952fde6 | ||
|
|
9c4ee4014d | ||
|
|
dc9069f1f4 | ||
|
|
e402cacc05 | ||
|
|
a98cd248d6 | ||
|
|
00fbfb6a01 | ||
|
|
2399d45bab | ||
|
|
77e82fbf40 | ||
|
|
a6467dd0f0 | ||
|
|
86c31c3766 | ||
|
|
698cfe910c | ||
|
|
16db23c159 | ||
|
|
b05a5ee1c6 | ||
|
|
8cb298937f | ||
|
|
68fe20ddf6 | ||
|
|
fab167bb34 | ||
|
|
2e11cc56ab | ||
|
|
f640d4b5f5 | ||
|
|
074562b141 | ||
|
|
fd030a5fd4 | ||
|
|
82fa6b13c6 | ||
|
|
c2d204b362 | ||
|
|
77a83e4fc3 | ||
|
|
8617896c7a | ||
|
|
97c6b217f2 | ||
|
|
bf16298c40 | ||
|
|
bcebb0a2b5 | ||
|
|
efde0c99a3 | ||
|
|
b27442cf74 | ||
|
|
92fbbd4812 | ||
|
|
321ed810e3 | ||
|
|
17ff530683 | ||
|
|
2b413736a4 | ||
|
|
a416d03614 | ||
|
|
4de9a274dd | ||
|
|
0b8f3c9d9d | ||
|
|
2dccfce468 | ||
|
|
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 | ||
|
|
eaa603684c | ||
|
|
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 | ||
|
|
7ef41bfe75 | ||
|
|
e5c25e8a0c | ||
|
|
f412ac6260 | ||
|
|
26d8dfbb7f | ||
|
|
b45517bafd | ||
|
|
3edb6755b4 | ||
|
|
bda64fa391 | ||
|
|
e7040f7cc8 | ||
|
|
229970e799 | ||
|
|
7b0a9a055f | ||
|
|
06d370f716 | ||
|
|
ce0a4f1f96 | ||
|
|
3240aa3cb3 | ||
|
|
c9af9f34fc | ||
|
|
5c5935c738 | ||
|
|
00b2b1abcd | ||
|
|
e0891e1a15 | ||
|
|
f7df621c56 | ||
|
|
0b6dc5bcfc | ||
|
|
cbd6755aa5 | ||
|
|
3afbc248b1 | ||
|
|
30af81fe0a | ||
|
|
427b43c99b | ||
|
|
ed08ac6b46 | ||
|
|
1a2c1fa1b5 | ||
|
|
709fbac231 | ||
|
|
acfe19b914 | ||
|
|
6b80a56f92 | ||
|
|
81242b405f | ||
|
|
5c9d45a8a8 | ||
|
|
2edac24945 | ||
|
|
b68c9cc145 | ||
|
|
6f02d4eb62 | ||
|
|
6e60688e70 | ||
|
|
61092259ba | ||
|
|
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 | ||
|
|
22b33a4f25 |
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.
|
||||
434
.github/workflows/deploy.yml
vendored
Normal file
434
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,434 @@
|
||||
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:
|
||||
name: 'Build-Linux-Ubuntu'
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QIF_VERSION: 4.7
|
||||
|
||||
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 qtcharts'
|
||||
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
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows:
|
||||
name: Build-Windows
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
|
||||
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 qtcharts'
|
||||
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:
|
||||
name: 'Build-iOS'
|
||||
runs-on: macos-13
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
CC: cc
|
||||
CXX: c++
|
||||
|
||||
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 qtcharts'
|
||||
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 qtcharts'
|
||||
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.20'
|
||||
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
|
||||
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:
|
||||
name: '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
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
|
||||
- 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 qtcharts'
|
||||
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:
|
||||
name: 'Build-Android'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.6.2
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qtcharts'
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'gcc_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86_64 Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86 Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_armv7 Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_armv7'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_arm64_v8a Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_arm64_v8a'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --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
|
||||
142
.github/workflows/tag-deploy.yml
vendored
Normal file
142
.github/workflows/tag-deploy.yml
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
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
|
||||
|
||||
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/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.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
|
||||
|
||||
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
|
||||
96
.travis.yml
96
.travis.yml
@@ -1,96 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
- /\d+\.\d+/
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: MacOS
|
||||
os: osx
|
||||
osx_image: xcode12.2
|
||||
|
||||
env:
|
||||
- QT_VERSION=5.15.1
|
||||
|
||||
before_install:
|
||||
- export CERTIFICATE_P12=deploy/PrivacyTechAppleCert.p12
|
||||
- export KEYCHAIN=build.keychain
|
||||
- security create-keychain -p $MAC_CERT_PW $KEYCHAIN
|
||||
- security default-keychain -s $KEYCHAIN
|
||||
- security unlock-keychain -p $MAC_CERT_PW $KEYCHAIN
|
||||
- security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MAC_CERT_PW -T /usr/bin/codesign
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ ! -f $HOME/Qt/$QT_VERSION/clang_64/bin/qmake ]; then \
|
||||
brew install p7zip && \
|
||||
pip3 install aqtinstall requests py7zr && \
|
||||
python3 -m aqt install --outputdir $HOME/Qt $QT_VERSION 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
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: $GH_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "AmneziaVPN.dmg"
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
|
||||
- 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 "set WIN_CERT_PW=$WIN_CERT_PW" >> winbuild.bat
|
||||
- echo -e "\r\n" >> winbuild.bat
|
||||
- echo -e "call deploy\\\build_windows.bat" >> winbuild.bat
|
||||
- cmd //c winbuild.bat
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: $GH_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "AmneziaVPN.exe"
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
|
||||
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.5.3.0
|
||||
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 52)
|
||||
|
||||
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()
|
||||
154
README.md
154
README.md
@@ -1,2 +1,154 @@
|
||||
# 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)
|
||||
|
||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
## Features
|
||||
|
||||
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
|
||||
- Masking VPN with OpenVPN over Cloak plugin
|
||||
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
||||
- Windows, MacOS, Linux, Android, iOS releases.
|
||||
|
||||
## Links
|
||||
|
||||
[https://amnezia.org](https://amnezia.org) - project website
|
||||
[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](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
|
||||
## 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!
|
||||
|
||||
### 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.1. 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 Charts
|
||||
- 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
|
||||
|
||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||
payeer.com: P2561305
|
||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
||||
|
||||
1
client/3rd-prebuilt
Submodule
1
client/3rd-prebuilt
Submodule
Submodule client/3rd-prebuilt added at c969f28b84
1
client/3rd/OpenVPNAdapter
vendored
Submodule
1
client/3rd/OpenVPNAdapter
vendored
Submodule
Submodule client/3rd/OpenVPNAdapter added at 7c821a8d5c
20
client/3rd/QSimpleCrypto/QSimpleCrypto.cmake
Normal file
20
client/3rd/QSimpleCrypto/QSimpleCrypto.cmake
Normal file
@@ -0,0 +1,20 @@
|
||||
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QAead.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp
|
||||
)
|
||||
18
client/3rd/QSimpleCrypto/QSimpleCrypto.pri
Normal file
18
client/3rd/QSimpleCrypto/QSimpleCrypto.pri
Normal file
@@ -0,0 +1,18 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/include/QAead.h \
|
||||
$$PWD/include/QBlockCipher.h \
|
||||
$$PWD/include/QCryptoError.h \
|
||||
$$PWD/include/QRsa.h \
|
||||
$$PWD/include/QSimpleCrypto_global.h \
|
||||
$$PWD/include/QX509.h \
|
||||
$$PWD/include/QX509Store.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/sources/QAead.cpp \
|
||||
$$PWD/sources/QBlockCipher.cpp \
|
||||
$$PWD/sources/QCryptoError.cpp \
|
||||
$$PWD/sources/QRsa.cpp \
|
||||
$$PWD/sources/QX509.cpp \
|
||||
$$PWD/sources/QX509Store.cpp
|
||||
87
client/3rd/QSimpleCrypto/include/QAead.h
Normal file
87
client/3rd/QSimpleCrypto/include/QAead.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QAEAD_H
|
||||
#define QAEAD_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QAead {
|
||||
public:
|
||||
QAead();
|
||||
|
||||
///
|
||||
/// \brief encryptAesGcm - Function encrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
||||
|
||||
///
|
||||
/// \brief decryptAesGcm - Function decrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be decrypted
|
||||
/// \param key - AES key
|
||||
/// \param iv - Initialization vector
|
||||
/// \param tag - Authorization tag
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_gcm());
|
||||
|
||||
///
|
||||
/// \brief encryptAesCcm - Function encrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
||||
|
||||
///
|
||||
/// \brief decryptAesCcm - Function decrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad = "", const EVP_CIPHER* cipher = EVP_aes_256_ccm());
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QAEAD_H
|
||||
84
client/3rd/QSimpleCrypto/include/QBlockCipher.h
Normal file
84
client/3rd/QSimpleCrypto/include/QBlockCipher.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QBLOCKCIPHER_H
|
||||
#define QBLOCKCIPHER_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QBlockCipher {
|
||||
|
||||
#define Aes128Rounds 10
|
||||
#define Aes192Rounds 12
|
||||
#define Aes256Rounds 14
|
||||
|
||||
public:
|
||||
QBlockCipher();
|
||||
|
||||
///
|
||||
/// \brief generateRandomBytes - Function generates random bytes by size.
|
||||
/// \param size - Size of generated bytes.
|
||||
/// \return Returns random bytes.
|
||||
///
|
||||
QByteArray generateRandomBytes(const int& size);
|
||||
QByteArray generateSecureRandomBytes(const int& size);
|
||||
|
||||
///
|
||||
/// \brief encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Encryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
||||
|
||||
///
|
||||
/// \brief decryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Decryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv = "", const int& rounds = Aes256Rounds,
|
||||
const EVP_CIPHER* cipher = EVP_aes_256_cbc(), const EVP_MD* md = EVP_sha512());
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QBLOCKCIPHER_H
|
||||
45
client/3rd/QSimpleCrypto/include/QCryptoError.h
Normal file
45
client/3rd/QSimpleCrypto/include/QCryptoError.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef QCRYPTOERROR_H
|
||||
#define QCRYPTOERROR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
/// TODO: Add Special error code for each error.
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QCryptoError : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QCryptoError(QObject* parent = nullptr);
|
||||
|
||||
///
|
||||
/// \brief setError - Sets error information
|
||||
/// \param errorCode - Error code.
|
||||
/// \param errorSummary - Error summary.
|
||||
///
|
||||
inline void setError(const quint8 errorCode, const QString& errorSummary)
|
||||
{
|
||||
m_currentErrorCode = errorCode;
|
||||
m_errorSummary = errorSummary;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief lastError - Returns last error.
|
||||
/// \return Returns eror ID and error Text.
|
||||
///
|
||||
inline QPair<quint8, QString> lastError() const
|
||||
{
|
||||
return QPair<quint8, QString>(m_currentErrorCode, m_errorSummary);
|
||||
}
|
||||
|
||||
private:
|
||||
quint8 m_currentErrorCode;
|
||||
QString m_errorSummary;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QCRYPTOERROR_H
|
||||
104
client/3rd/QSimpleCrypto/include/QRsa.h
Normal file
104
client/3rd/QSimpleCrypto/include/QRsa.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QRSA_H
|
||||
#define QRSA_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QRsa {
|
||||
|
||||
#define PublicEncrypt 0
|
||||
#define PrivateEncrypt 1
|
||||
#define PublicDecrypt 2
|
||||
#define PrivateDecrypt 3
|
||||
|
||||
public:
|
||||
QRsa();
|
||||
|
||||
///
|
||||
/// \brief generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
||||
/// \param bits - RSA key size.
|
||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
||||
///
|
||||
RSA* generateRsaKeys(const int& bits, const int& rsaBigNumber);
|
||||
|
||||
///
|
||||
/// \brief savePublicKey - Saves to file RSA public key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param publicKeyFileName - Public key file name.
|
||||
///
|
||||
void savePublicKey(RSA *rsa, const QByteArray& publicKeyFileName);
|
||||
|
||||
///
|
||||
/// \brief savePrivateKey - Saves to file RSA private key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param privateKeyFileName - Private key file name.
|
||||
/// \param password - Private key password.
|
||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
///
|
||||
void savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password = "", const EVP_CIPHER* cipher = nullptr);
|
||||
|
||||
///
|
||||
/// \brief getPublicKeyFromFile - Gets RSA public key from a file.
|
||||
/// \param filePath - File path to public key file.
|
||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* getPublicKeyFromFile(const QByteArray& filePath);
|
||||
|
||||
///
|
||||
/// \brief getPrivateKeyFromFile - Gets RSA private key from a file.
|
||||
/// \param filePath - File path to private key file.
|
||||
/// \param password - Private key password.
|
||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password = "");
|
||||
|
||||
///
|
||||
/// \brief encrypt - Encrypt data with RSA algorithm.
|
||||
/// \param plaintext - Text that must be encrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray encrypt(QByteArray plainText, RSA* rsa, const int& encryptType = PublicEncrypt, const int& padding = RSA_PKCS1_PADDING);
|
||||
|
||||
///
|
||||
/// \brief decrypt - Decrypt data with RSA algorithm.
|
||||
/// \param cipherText - Text that must be decrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return - Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType = PrivateDecrypt, const int& padding = RSA_PKCS1_PADDING);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QRSA_H
|
||||
9
client/3rd/QSimpleCrypto/include/QSimpleCrypto_global.h
Normal file
9
client/3rd/QSimpleCrypto/include/QSimpleCrypto_global.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef QSIMPLECRYPTO_GLOBAL_H
|
||||
#define QSIMPLECRYPTO_GLOBAL_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#define QSIMPLECRYPTO_EXPORT
|
||||
|
||||
#endif // QSIMPLECRYPTO_GLOBAL_H
|
||||
87
client/3rd/QSimpleCrypto/include/QX509.h
Normal file
87
client/3rd/QSimpleCrypto/include/QX509.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QX509_H
|
||||
#define QX509_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QX509 {
|
||||
|
||||
#define oneYear 31536000L
|
||||
#define x509LastVersion 2
|
||||
|
||||
public:
|
||||
QX509();
|
||||
|
||||
///
|
||||
/// \brief loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
||||
/// \param fileName - File path to certificate.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* loadCertificateFromFile(const QByteArray& fileName);
|
||||
|
||||
///
|
||||
/// \brief signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
||||
/// \param endCertificate - Certificate that will be signed
|
||||
/// \param caCertificate - CA certificate that will sign end certificate
|
||||
/// \param caPrivateKey - CA certificate private key
|
||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
||||
///
|
||||
X509* signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName = "");
|
||||
|
||||
///
|
||||
/// \brief verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
||||
///
|
||||
X509* verifyCertificate(X509* x509, X509_STORE* store);
|
||||
|
||||
///
|
||||
/// \brief generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
||||
/// \param rsa - OpenSSL RSA.
|
||||
/// \param additionalData - Certificate information.
|
||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
||||
/// \param serialNumber - X509 certificate serial number.
|
||||
/// \param version - X509 certificate version.
|
||||
/// \param notBefore - X509 start date.
|
||||
/// \param notAfter - X509 end date.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
} // namespace QSimpleCrypto
|
||||
|
||||
#endif // QX509_H
|
||||
120
client/3rd/QSimpleCrypto/include/QX509Store.h
Normal file
120
client/3rd/QSimpleCrypto/include/QX509Store.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#ifndef QX509STORE_H
|
||||
#define QX509STORE_H
|
||||
|
||||
#include "QSimpleCrypto_global.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509_vfy.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include "QCryptoError.h"
|
||||
|
||||
// clang-format off
|
||||
namespace QSimpleCrypto
|
||||
{
|
||||
class QSIMPLECRYPTO_EXPORT QX509Store {
|
||||
public:
|
||||
QX509Store();
|
||||
|
||||
///
|
||||
/// \brief addCertificateToStore
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param x509 - OpenSSL X509.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool addCertificateToStore(X509_STORE* store, X509* x509);
|
||||
|
||||
///
|
||||
/// \brief addLookup
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method);
|
||||
|
||||
///
|
||||
/// \brief setCertificateDepth
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setDepth(X509_STORE* store, const int& depth);
|
||||
|
||||
///
|
||||
/// \brief setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setFlag(X509_STORE* store, const unsigned long& flag);
|
||||
|
||||
///
|
||||
/// \brief setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setPurpose(X509_STORE* store, const int& purpose);
|
||||
|
||||
///
|
||||
/// \brief setTrust
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setTrust(X509_STORE* store, const int& trust);
|
||||
|
||||
///
|
||||
/// \brief setDefaultPaths
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool setDefaultPaths(X509_STORE* store);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param file - Qt QFile that will be loaded.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QFile& file);
|
||||
|
||||
///
|
||||
/// \brief loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileInfo - Qt QFileInfo.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool loadLocations(X509_STORE* store, const QFileInfo& fileInfo);
|
||||
|
||||
///
|
||||
/// \brief error - Error handler class.
|
||||
///
|
||||
QCryptoError error;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QX509STORE_H
|
||||
364
client/3rd/QSimpleCrypto/sources/QAead.cpp
Normal file
364
client/3rd/QSimpleCrypto/sources/QAead.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QAead.h"
|
||||
|
||||
QSimpleCrypto::QAead::QAead()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::encryptAesGcm - Function encrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::encryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength = data.size();
|
||||
int cipherTextLength = 0;
|
||||
|
||||
/* Initialize cipherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
||||
}
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Check if aad need to be used */
|
||||
// if (aad.length() > 0) {
|
||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
||||
// if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
// throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the encryption. Normally cipher text bytes may be written at
|
||||
* this stage, but this does not occur in GCM mode
|
||||
*/
|
||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Get tag */
|
||||
// if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_GCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
// throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::decryptAesGcm - Function decrypts data with Gcm algorithm.
|
||||
/// \param data - Data that will be decrypted
|
||||
/// \param key - AES key
|
||||
/// \param iv - Initialization vector
|
||||
/// \param tag - Authorization tag
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (gcm) - 128, 192, 256. Example: EVP_aes_256_gcm()
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::decryptAesGcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength = data.size();
|
||||
int plainTextLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
||||
}
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Check if aad need to be used */
|
||||
// if (aad.length() > 0) {
|
||||
// /* Provide any AAD data. This can be called zero or more times as required */
|
||||
// if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
// throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plain text output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
||||
// if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_GCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
// throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plain text is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::encryptAesCcm - Function encrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::encryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength = data.size();
|
||||
int cipherTextLength = 0;
|
||||
|
||||
/* Initialize cipherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[plainTextLength]() };
|
||||
if (cipherText.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'ciphertext'.");
|
||||
}
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length if default 12 bytes (96 bits) is not appropriate */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set tag length */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), nullptr)) {
|
||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Check if aad need to be used */
|
||||
if (aad.length() > 0) {
|
||||
/* Provide the total plain text length */
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, nullptr, plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Provide any AAD data. This can be called zero or more times as required */
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), nullptr, &cipherTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
throw std::runtime_error("Couldn't provide aad data. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the encryption. Normally ciphertext bytes may be written at
|
||||
* this stage, but this does not occur in GCM mode
|
||||
*/
|
||||
if (!EVP_EncryptFinal_ex(encryptionCipher.get(), cipherText.get(), &plainTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get tag */
|
||||
if (!EVP_CIPHER_CTX_ctrl(encryptionCipher.get(), EVP_CTRL_CCM_GET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
throw std::runtime_error("Couldn't get tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QAEAD::decryptAesCcm - Function decrypts data with Ccm algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param tag - Authorization tag.
|
||||
/// \param aad - Additional authenticated data. Must be nullptr, if not used.
|
||||
/// \param cipher - Can be used with OpenSSL EVP_CIPHER (ccm) - 128, 192, 256. Example: EVP_aes_256_ccm().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QAead::decryptAesCcm(QByteArray data, QByteArray key, QByteArray iv, QByteArray* tag, QByteArray aad, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher.get() == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength = data.size();
|
||||
int plainTextLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherTextLength]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plaintext'.");
|
||||
}
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, reinterpret_cast<unsigned char*>(key.data()), reinterpret_cast<unsigned char*>(iv.data()))) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set IV length. Not necessary if this is 12 bytes (96 bits) */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_IVLEN, iv.length(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't set IV length. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */
|
||||
if (!EVP_CIPHER_CTX_ctrl(decryptionCipher.get(), EVP_CTRL_CCM_SET_TAG, tag->length(), reinterpret_cast<unsigned char*>(tag->data()))) {
|
||||
throw std::runtime_error("Coldn't set tag. EVP_CIPHER_CTX_ctrl(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Check if aad need to be used */
|
||||
if (aad.length() > 0) {
|
||||
/* Provide the total ciphertext length */
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, nullptr, cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide total plaintext length. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Provide any AAD data. This can be called zero or more times as required */
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), nullptr, &plainTextLength, reinterpret_cast<unsigned char*>(aad.data()), aad.length())) {
|
||||
throw std::runtime_error("Couldn't provide aad data. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plaintext is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal_ex(decryptionCipher.get(), plainText.get(), &cipherTextLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QAead::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QAead::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
193
client/3rd/QSimpleCrypto/sources/QBlockCipher.cpp
Normal file
193
client/3rd/QSimpleCrypto/sources/QBlockCipher.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QBlockCipher.h"
|
||||
|
||||
QSimpleCrypto::QBlockCipher::QBlockCipher()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::generateRandomBytes - Function generates random bytes by size.
|
||||
/// \param size - Size of generated bytes.
|
||||
/// \return Returns random bytes.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::generateRandomBytes(const int& size)
|
||||
{
|
||||
unsigned char arr[sizeof(size)];
|
||||
RAND_bytes(arr, sizeof(size));
|
||||
|
||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
QByteArray QSimpleCrypto::QBlockCipher::generateSecureRandomBytes(const int &size)
|
||||
{
|
||||
unsigned char arr[sizeof(size)];
|
||||
RAND_priv_bytes(arr, sizeof(size));
|
||||
|
||||
QByteArray buffer = QByteArray(reinterpret_cast<char*>(arr), size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function encrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be encrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Encryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::encryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv,
|
||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> encryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (encryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'encryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Reinterpret values for multi use */
|
||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
||||
|
||||
/* Set data length */
|
||||
int cipherTextLength(data.size() + AES_BLOCK_SIZE);
|
||||
int finalLength = 0;
|
||||
|
||||
/* Initialize cipcherText. Here encrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[cipherTextLength]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
||||
}
|
||||
|
||||
// Bug here
|
||||
// /* Start encryption with password based encryption routine */
|
||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<unsigned char*>(salt.data()), reinterpret_cast<unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
||||
// throw std::runtime_error("Couldn't start encryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Initialize encryption operation. */
|
||||
if (!EVP_EncryptInit_ex(encryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
||||
throw std::runtime_error("Couldn't initialize encryption operation. EVP_EncryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be encrypted, and obtain the encrypted output.
|
||||
* EVP_EncryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_EncryptUpdate(encryptionCipher.get(), cipherText.get(), &cipherTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
||||
throw std::runtime_error("Couldn't provide message to be encrypted. EVP_EncryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finalize the encryption. Normally ciphertext bytes may be written at this stage */
|
||||
if (!EVP_EncryptFinal(encryptionCipher.get(), cipherText.get() + cipherTextLength, &finalLength)) {
|
||||
throw std::runtime_error("Couldn't finalize encryption. EVP_EncryptFinal(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), cipherTextLength + finalLength);
|
||||
|
||||
return encryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
||||
return QByteArray();
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QBlockCipher::encryptAesBlockCipher - Function decrypts data with Aes Block Cipher algorithm.
|
||||
/// \param data - Data that will be decrypted.
|
||||
/// \param key - AES key.
|
||||
/// \param iv - Initialization vector.
|
||||
/// \param password - Decryption password.
|
||||
/// \param salt - Random delta.
|
||||
/// \param rounds - Transformation rounds.
|
||||
/// \param chiper - Can be used with OpenSSL EVP_CIPHER (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
/// \param md - Hash algroitm (OpenSSL EVP_MD). Example: EVP_sha512().
|
||||
/// \return Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QBlockCipher::decryptAesBlockCipher(QByteArray data, QByteArray key,
|
||||
QByteArray iv,
|
||||
const int& rounds, const EVP_CIPHER* cipher, const EVP_MD* md)
|
||||
{
|
||||
try {
|
||||
/* Initialize EVP_CIPHER_CTX */
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX*)> decryptionCipher { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
if (decryptionCipher == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'decryptionCipher\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Reinterpret values for multi use */
|
||||
unsigned char* m_key = reinterpret_cast<unsigned char*>(key.data());
|
||||
unsigned char* m_iv = reinterpret_cast<unsigned char*>(iv.data());
|
||||
|
||||
/* Set data length */
|
||||
int plainTextLength(data.size());
|
||||
int finalLength = 0;
|
||||
|
||||
/* Initialize plainText. Here decrypted data will be stored */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[plainTextLength + AES_BLOCK_SIZE]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for \'plainText\'. EVP_CIPHER_CTX_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
// Bug here
|
||||
// /* Start encryption with password based encryption routine */
|
||||
// if (!EVP_BytesToKey(cipher, md, reinterpret_cast<const unsigned char*>(salt.data()), reinterpret_cast<const unsigned char*>(password.data()), password.length(), rounds, m_key, m_iv)) {
|
||||
// throw std::runtime_error("Couldn't start decryption routine. EVP_BytesToKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
// }
|
||||
|
||||
/* Initialize decryption operation. */
|
||||
if (!EVP_DecryptInit_ex(decryptionCipher.get(), cipher, nullptr, m_key, m_iv)) {
|
||||
throw std::runtime_error("Couldn't initialize decryption operation. EVP_DecryptInit_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide the message to be decrypted, and obtain the plaintext output.
|
||||
* EVP_DecryptUpdate can be called multiple times if necessary
|
||||
*/
|
||||
if (!EVP_DecryptUpdate(decryptionCipher.get(), plainText.get(), &plainTextLength, reinterpret_cast<const unsigned char*>(data.data()), data.size())) {
|
||||
throw std::runtime_error("Couldn't provide message to be decrypted. EVP_DecryptUpdate(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalize the decryption. A positive return value indicates success,
|
||||
* anything else is a failure - the plaintext is not trustworthy.
|
||||
*/
|
||||
if (!EVP_DecryptFinal(decryptionCipher.get(), plainText.get() + plainTextLength, &finalLength)) {
|
||||
throw std::runtime_error("Couldn't finalize decryption. EVP_DecryptFinal. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Finilize data to be readable with qt */
|
||||
QByteArray decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()), plainTextLength + finalLength);
|
||||
|
||||
return decryptedData;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(1, exception.what());
|
||||
return QByteArray(exception.what());
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QBlockCipher::error.setError(2, "Unknown error!");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
6
client/3rd/QSimpleCrypto/sources/QCryptoError.cpp
Normal file
6
client/3rd/QSimpleCrypto/sources/QCryptoError.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "include/QCryptoError.h"
|
||||
|
||||
QSimpleCrypto::QCryptoError::QCryptoError(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
274
client/3rd/QSimpleCrypto/sources/QRsa.cpp
Normal file
274
client/3rd/QSimpleCrypto/sources/QRsa.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QRsa.h"
|
||||
|
||||
QSimpleCrypto::QRsa::QRsa()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::generateRsaKeys - Function generate Rsa Keys and returns them in OpenSSL structure.
|
||||
/// \param bits - RSA key size.
|
||||
/// \param rsaBigNumber - The exponent is an odd number, typically 3, 17 or 65537.
|
||||
/// \return Returns 'OpenSSL RSA structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'RSA_free()' to avoid memory leak.
|
||||
///
|
||||
RSA* QSimpleCrypto::QRsa::generateRsaKeys(const int& bits, const int& rsaBigNumber)
|
||||
{
|
||||
try {
|
||||
/* Initialize big number */
|
||||
std::unique_ptr<BIGNUM, void (*)(BIGNUM*)> bigNumber { BN_new(), BN_free };
|
||||
if (bigNumber == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'bigNumber\'. BN_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Set big number */
|
||||
if (!BN_set_word(bigNumber.get(), rsaBigNumber)) {
|
||||
throw std::runtime_error("Couldn't set bigNumber. BN_set_word(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize RSA */
|
||||
RSA* rsa = nullptr;
|
||||
if (!(rsa = RSA_new())) {
|
||||
throw std::runtime_error("Couldn't initialize x509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Generate key pair and store it in RSA */
|
||||
if (!RSA_generate_key_ex(rsa, bits, bigNumber.get(), nullptr)) {
|
||||
throw std::runtime_error("Couldn't generate RSA. RSA_generate_key_ex(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return rsa;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::savePublicKey - Saves to file RSA public key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param publicKeyFileName - Public key file name.
|
||||
///
|
||||
void QSimpleCrypto::QRsa::savePublicKey(RSA* rsa, const QByteArray& publicKeyFileName)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(publicKeyFileName.data(), "w+"), BIO_free_all };
|
||||
if (bioPublicKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize \'bioPublicKey\'. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write public key on file */
|
||||
if (!PEM_write_bio_RSA_PUBKEY(bioPublicKey.get(), rsa)) {
|
||||
throw std::runtime_error("Couldn't save public key. PEM_write_bio_RSAPublicKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::savePrivateKey - Saves to file RSA private key.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param privateKeyFileName - Private key file name.
|
||||
/// \param password - Private key password.
|
||||
/// \param cipher - Can be used with 'OpenSSL EVP_CIPHER' (ecb, cbc, cfb, ofb, ctr) - 128, 192, 256. Example: EVP_aes_256_cbc().
|
||||
///
|
||||
void QSimpleCrypto::QRsa::savePrivateKey(RSA* rsa, const QByteArray& privateKeyFileName, QByteArray password, const EVP_CIPHER* cipher)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(privateKeyFileName.data(), "w+"), BIO_free_all };
|
||||
if (bioPrivateKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_write_bio_RSAPrivateKey(bioPrivateKey.get(), rsa, cipher, reinterpret_cast<unsigned char*>(password.data()), password.size(), nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't save private key. PEM_write_bio_RSAPrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::getPublicKeyFromFile - Gets RSA public key from a file.
|
||||
/// \param filePath - File path to public key file.
|
||||
/// \return Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* QSimpleCrypto::QRsa::getPublicKeyFromFile(const QByteArray& filePath)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPublicKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
||||
if (bioPublicKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPublicKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
EVP_PKEY* keyStore = nullptr;
|
||||
if (!(keyStore = EVP_PKEY_new())) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_read_bio_PUBKEY(bioPublicKey.get(), &keyStore, nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::getPrivateKeyFromFile - Gets RSA private key from a file.
|
||||
/// \param filePath - File path to private key file.
|
||||
/// \param password - Private key password.
|
||||
/// \return - Returns 'OpenSSL EVP_PKEY structure' or 'nullptr', if error happened. Returned value must be cleaned up with 'EVP_PKEY_free()' to avoid memory leak.
|
||||
///
|
||||
EVP_PKEY* QSimpleCrypto::QRsa::getPrivateKeyFromFile(const QByteArray& filePath, const QByteArray& password)
|
||||
{
|
||||
try {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> bioPrivateKey { BIO_new_file(filePath.data(), "r"), BIO_free_all };
|
||||
if (bioPrivateKey == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize bioPrivateKey. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
EVP_PKEY* keyStore = nullptr;
|
||||
if (!(keyStore = EVP_PKEY_new())) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write private key to file */
|
||||
if (!PEM_read_bio_PrivateKey(bioPrivateKey.get(), &keyStore, nullptr, (void*)password.data())) {
|
||||
throw std::runtime_error("Couldn't read private key. PEM_read_bio_PrivateKey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return keyStore;
|
||||
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::encrypt - Encrypt data with RSA algorithm.
|
||||
/// \param plaintext - Text that must be encrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param encryptType - Public or private encrypt type. (PUBLIC_ENCRYPT, PRIVATE_ENCRYPT).
|
||||
/// \param padding - OpenSSL RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return Returns encrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QRsa::encrypt(QByteArray plainText, RSA* rsa, const int& encryptType, const int& padding)
|
||||
{
|
||||
try {
|
||||
/* Initialize array. Here encrypted data will be saved */
|
||||
std::unique_ptr<unsigned char[]> cipherText { new unsigned char[RSA_size(rsa)]() };
|
||||
if (cipherText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'cipherText'.");
|
||||
}
|
||||
|
||||
/* Result of encryption operation */
|
||||
short int result = 0;
|
||||
|
||||
/* Execute encryption operation */
|
||||
if (encryptType == PublicDecrypt) {
|
||||
result = RSA_public_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
||||
} else if (encryptType == PrivateDecrypt) {
|
||||
result = RSA_private_encrypt(plainText.size(), reinterpret_cast<unsigned char*>(plainText.data()), cipherText.get(), rsa, padding);
|
||||
}
|
||||
|
||||
/* Check for result */
|
||||
if (result <= -1) {
|
||||
throw std::runtime_error("Couldn't encrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get encrypted data */
|
||||
const QByteArray& encryptedData = QByteArray(reinterpret_cast<char*>(cipherText.get()), RSA_size(rsa));
|
||||
|
||||
return encryptedData;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return "";
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QRSA::decrypt - Decrypt data with RSA algorithm.
|
||||
/// \param cipherText - Text that must be decrypted.
|
||||
/// \param rsa - OpenSSL RSA structure.
|
||||
/// \param decryptType - Public or private type. (PUBLIC_DECRYPT, PRIVATE_DECRYPT).
|
||||
/// \param padding - RSA padding can be used with: 'RSA_PKCS1_PADDING', 'RSA_NO_PADDING' and etc.
|
||||
/// \return - Returns decrypted data or "", if error happened.
|
||||
///
|
||||
QByteArray QSimpleCrypto::QRsa::decrypt(QByteArray cipherText, RSA* rsa, const int& decryptType, const int& padding)
|
||||
{
|
||||
try {
|
||||
/* Initialize array. Here decrypted data will be saved */
|
||||
std::unique_ptr<unsigned char[]> plainText { new unsigned char[cipherText.size()]() };
|
||||
if (plainText == nullptr) {
|
||||
throw std::runtime_error("Couldn't allocate memory for 'plainText'.");
|
||||
}
|
||||
|
||||
/* Result of decryption operation */
|
||||
short int result = 0;
|
||||
|
||||
/* Execute decryption operation */
|
||||
if (decryptType == PublicDecrypt) {
|
||||
result = RSA_public_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
||||
} else if (decryptType == PrivateDecrypt) {
|
||||
result = RSA_private_decrypt(RSA_size(rsa), reinterpret_cast<unsigned char*>(cipherText.data()), plainText.get(), rsa, padding);
|
||||
}
|
||||
|
||||
/* Check for result */
|
||||
if (result <= -1) {
|
||||
throw std::runtime_error("Couldn't decrypt data. Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Get decrypted data */
|
||||
const QByteArray& decryptedData = QByteArray(reinterpret_cast<char*>(plainText.get()));
|
||||
|
||||
return decryptedData;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QRsa::error.setError(1, exception.what());
|
||||
return "";
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QRsa::error.setError(2, "Unknown error!");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
234
client/3rd/QSimpleCrypto/sources/QX509.cpp
Normal file
234
client/3rd/QSimpleCrypto/sources/QX509.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QX509.h"
|
||||
|
||||
QSimpleCrypto::QX509::QX509()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::loadCertificateFromFile - Function load X509 from file and returns OpenSSL structure.
|
||||
/// \param fileName - File path to certificate.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::loadCertificateFromFile(const QByteArray& fileName)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509 */
|
||||
X509* x509 = nullptr;
|
||||
if (!(x509 = X509_new())) {
|
||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "r+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Read file */
|
||||
if (!PEM_read_bio_X509(certFile.get(), &x509, nullptr, nullptr)) {
|
||||
throw std::runtime_error("Couldn't read certificate file from disk. PEM_read_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::signCertificate - Function signs X509 certificate and returns signed X509 OpenSSL structure.
|
||||
/// \param endCertificate - Certificate that will be signed
|
||||
/// \param caCertificate - CA certificate that will sign end certificate
|
||||
/// \param caPrivateKey - CA certificate private key
|
||||
/// \param fileName - With that name certificate will be saved. Leave "", if don't need to save it
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::signCertificate(X509* endCertificate, X509* caCertificate, EVP_PKEY* caPrivateKey, const QByteArray& fileName)
|
||||
{
|
||||
try {
|
||||
/* Set issuer to CA's subject. */
|
||||
if (!X509_set_issuer_name(endCertificate, X509_get_subject_name(caCertificate))) {
|
||||
throw std::runtime_error("Couldn't set issuer name for X509. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign the certificate with key. */
|
||||
if (!X509_sign(endCertificate, caPrivateKey, EVP_sha256())) {
|
||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write certificate file on disk. If needed */
|
||||
if (!fileName.isEmpty()) {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(fileName.data(), "w+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write file on disk */
|
||||
if (!PEM_write_bio_X509(certFile.get(), endCertificate)) {
|
||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
return endCertificate;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::verifyCertificate - Function verifies X509 certificate and returns verified X509 OpenSSL structure.
|
||||
/// \param x509 - OpenSSL X509. That certificate will be verified.
|
||||
/// \param store - Trusted certificate must be added to X509_Store with 'addCertificateToStore(X509_STORE* ctx, X509* x509)'.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509_STORE_CTX */
|
||||
std::unique_ptr<X509_STORE_CTX, void (*)(X509_STORE_CTX*)> ctx { X509_STORE_CTX_new(), X509_STORE_CTX_free };
|
||||
if (ctx == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set up CTX for a subsequent verification operation */
|
||||
if (!X509_STORE_CTX_init(ctx.get(), store, x509, nullptr)) {
|
||||
throw std::runtime_error("Couldn't initialize X509_STORE_CTX. X509_STORE_CTX_init(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Verify X509 */
|
||||
if (!X509_verify_cert(ctx.get())) {
|
||||
throw std::runtime_error("Couldn't verify cert. X509_verify_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::generateSelfSignedCertificate - Function generatesand returns self signed X509.
|
||||
/// \param rsa - OpenSSL RSA.
|
||||
/// \param additionalData - Certificate information.
|
||||
/// \param certificateFileName - With that name certificate will be saved. Leave "", if don't need to save it.
|
||||
/// \param md - OpenSSL EVP_MD structure. Example: EVP_sha512().
|
||||
/// \param serialNumber - X509 certificate serial number.
|
||||
/// \param version - X509 certificate version.
|
||||
/// \param notBefore - X509 start date.
|
||||
/// \param notAfter - X509 end date.
|
||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||
///
|
||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
||||
const long& serialNumber, const long& version,
|
||||
const long& notBefore, const long& notAfter)
|
||||
{
|
||||
try {
|
||||
/* Initialize X509 */
|
||||
X509* x509 = nullptr;
|
||||
if (!(x509 = X509_new())) {
|
||||
throw std::runtime_error("Couldn't initialize X509. X509_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize EVP_PKEY */
|
||||
std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> keyStore { EVP_PKEY_new(), EVP_PKEY_free };
|
||||
if (keyStore == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize keyStore. EVP_PKEY_new(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign rsa key */
|
||||
if (!EVP_PKEY_assign_RSA(keyStore.get(), rsa)) {
|
||||
throw std::runtime_error("Couldn't assign rsa. EVP_PKEY_assign_RSA(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate serial number. */
|
||||
if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)) {
|
||||
throw std::runtime_error("Couldn't set serial number. ASN1_INTEGER_set(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate version */
|
||||
if (!X509_set_version(x509, version)) {
|
||||
throw std::runtime_error("Couldn't set version. X509_set_version(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Set certificate creation and expiration date */
|
||||
X509_gmtime_adj(X509_get_notBefore(x509), notBefore);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509), notAfter);
|
||||
|
||||
/* Set certificate public key */
|
||||
if (!X509_set_pubkey(x509, keyStore.get())) {
|
||||
throw std::runtime_error("Couldn't set public key. X509_set_pubkey(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Initialize X509_NAME */
|
||||
X509_NAME* x509Name = X509_get_subject_name(x509);
|
||||
if (x509Name == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize X509_NAME. X509_NAME(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Add additional data to certificate */
|
||||
QMapIterator<QByteArray, QByteArray> certificateInformationList(additionalData);
|
||||
while (certificateInformationList.hasNext()) {
|
||||
/* Read next item in list */
|
||||
certificateInformationList.next();
|
||||
|
||||
/* Set additional data */
|
||||
if (!X509_NAME_add_entry_by_txt(x509Name, certificateInformationList.key().data(), MBSTRING_UTF8, reinterpret_cast<const unsigned char*>(certificateInformationList.value().data()), -1, -1, 0)) {
|
||||
throw std::runtime_error("Couldn't set additional information. X509_NAME_add_entry_by_txt(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Set certificate info */
|
||||
if (!X509_set_issuer_name(x509, x509Name)) {
|
||||
throw std::runtime_error("Couldn't set issuer name. X509_set_issuer_name(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Sign certificate */
|
||||
if (!X509_sign(x509, keyStore.get(), md)) {
|
||||
throw std::runtime_error("Couldn't sign X509. X509_sign(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write certificate file on disk. If needed */
|
||||
if (!certificateFileName.isEmpty()) {
|
||||
/* Initialize BIO */
|
||||
std::unique_ptr<BIO, void (*)(BIO*)> certFile { BIO_new_file(certificateFileName.data(), "w+"), BIO_free_all };
|
||||
if (certFile == nullptr) {
|
||||
throw std::runtime_error("Couldn't initialize certFile. BIO_new_file(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
|
||||
/* Write file on disk */
|
||||
if (!PEM_write_bio_X509(certFile.get(), x509)) {
|
||||
throw std::runtime_error("Couldn't write certificate file on disk. PEM_write_bio_X509(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
}
|
||||
}
|
||||
|
||||
return x509;
|
||||
} catch (std::exception& exception) {
|
||||
QSimpleCrypto::QX509::error.setError(1, exception.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
QSimpleCrypto::QX509::error.setError(2, "Unknown error!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
176
client/3rd/QSimpleCrypto/sources/QX509Store.cpp
Normal file
176
client/3rd/QSimpleCrypto/sources/QX509Store.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright 2021 BrutalWizard (https://github.com/bru74lw1z4rd). All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution
|
||||
**/
|
||||
|
||||
#include "include/QX509Store.h"
|
||||
|
||||
QSimpleCrypto::QX509Store::QX509Store()
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509::addCertificateToStore
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param x509 - OpenSSL X509.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::addCertificateToStore(X509_STORE* store, X509* x509)
|
||||
{
|
||||
if (!X509_STORE_add_cert(store, x509)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add certificate to X509_STORE. X509_STORE_add_cert(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::addLookup
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param method - OpenSSL X509_LOOKUP_METHOD. Example: X509_LOOKUP_file.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::addLookup(X509_STORE* store, X509_LOOKUP_METHOD* method)
|
||||
{
|
||||
if (!X509_STORE_add_lookup(store, method)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't add lookup to X509_STORE. X509_STORE_add_lookup(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setCertificateDepth
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param depth - That is the maximum number of untrusted CA certificates that can appear in a chain. Example: 0.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setDepth(X509_STORE* store, const int& depth)
|
||||
{
|
||||
if (!X509_STORE_set_depth(store, depth)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set depth for X509_STORE. X509_STORE_set_depth(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param flag - The verification flags consists of zero or more of the following flags ored together. Example: X509_V_FLAG_CRL_CHECK.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setFlag(X509_STORE* store, const unsigned long& flag)
|
||||
{
|
||||
if (!X509_STORE_set_flags(store, flag)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set flag for X509_STORE. X509_STORE_set_flags(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setFlag
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param purpose - Verification purpose in param to purpose. Example: X509_PURPOSE_ANY.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setPurpose(X509_STORE* store, const int& purpose)
|
||||
{
|
||||
if (!X509_STORE_set_purpose(store, purpose)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set purpose for X509_STORE. X509_STORE_set_purpose(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setTrust
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param trust - Trust Level. Example: X509_TRUST_SSL_SERVER.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setTrust(X509_STORE* store, const int& trust)
|
||||
{
|
||||
if (!X509_STORE_set_trust(store, trust)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set trust for X509_STORE. X509_STORE_set_trust(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::setDefaultPaths
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::setDefaultPaths(X509_STORE* store)
|
||||
{
|
||||
if (!X509_STORE_set_default_paths(store)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't set default paths for X509_STORE. X509_STORE_set_default_paths(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileName - File name. Example: "caCertificate.pem".
|
||||
/// \param dirPath - Path to file. Example: "path/To/File".
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QByteArray& fileName, const QByteArray& dirPath)
|
||||
{
|
||||
if (!X509_STORE_load_locations(store, fileName, dirPath)) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param file - Qt QFile that will be loaded.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFile& file)
|
||||
{
|
||||
/* Initialize QFileInfo to read information about file */
|
||||
QFileInfo info(file);
|
||||
|
||||
if (!X509_STORE_load_locations(store, info.fileName().toLocal8Bit(), info.absoluteDir().path().toLocal8Bit())) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
/// \brief QSimpleCrypto::QX509Store::loadLocations
|
||||
/// \param store - OpenSSL X509_STORE.
|
||||
/// \param fileInfo - Qt QFileInfo.
|
||||
/// \return Returns 'true' on success and 'false', if error happened.
|
||||
///
|
||||
bool QSimpleCrypto::QX509Store::loadLocations(X509_STORE* store, const QFileInfo& fileInfo)
|
||||
{
|
||||
if (!X509_STORE_load_locations(store, fileInfo.fileName().toLocal8Bit(), fileInfo.absoluteDir().path().toLocal8Bit())) {
|
||||
QSimpleCrypto::QX509Store::error.setError(1, "Couldn't load locations for X509_STORE. X509_STORE_load_locations(). Error: " + QByteArray(ERR_error_string(ERR_get_error(), nullptr)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
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>
|
||||
@@ -1,273 +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 "sshkeyexchange_p.h"
|
||||
|
||||
#include "ssh_global.h"
|
||||
#include "sshbotanconversions_p.h"
|
||||
#include "sshcapabilities_p.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshsendfacility_p.h"
|
||||
#include "sshexception_p.h"
|
||||
#include "sshincomingpacket_p.h"
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace Botan;
|
||||
|
||||
namespace QSsh {
|
||||
namespace Internal {
|
||||
|
||||
namespace {
|
||||
|
||||
// For debugging
|
||||
void printNameList(const char *listName, const SshNameList &list)
|
||||
{
|
||||
qCDebug(sshLog, "%s:", listName);
|
||||
foreach (const QByteArray &name, list.names)
|
||||
qCDebug(sshLog, "%s", name.constData());
|
||||
}
|
||||
|
||||
void printData(const char *name, const QByteArray &data)
|
||||
{
|
||||
qCDebug(sshLog, "The client thinks the %s has length %d and is: %s", name, data.count(),
|
||||
data.toHex().constData());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
SshKeyExchange::SshKeyExchange(const SshConnectionParameters &connParams,
|
||||
SshSendFacility &sendFacility)
|
||||
: m_connParams(connParams), m_sendFacility(sendFacility)
|
||||
{
|
||||
}
|
||||
|
||||
SshKeyExchange::~SshKeyExchange() {}
|
||||
|
||||
void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
|
||||
{
|
||||
m_serverId = serverId;
|
||||
m_clientKexInitPayload = m_sendFacility.sendKeyExchangeInitPacket();
|
||||
}
|
||||
|
||||
bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
|
||||
{
|
||||
qCDebug(sshLog, "server requests key exchange");
|
||||
serverKexInit.printRawBytes();
|
||||
SshKeyExchangeInit kexInitParams
|
||||
= serverKexInit.extractKeyExchangeInitData();
|
||||
|
||||
printNameList("Key Algorithms", kexInitParams.keyAlgorithms);
|
||||
printNameList("Server Host Key Algorithms", kexInitParams.serverHostKeyAlgorithms);
|
||||
printNameList("Encryption algorithms client to server", kexInitParams.encryptionAlgorithmsClientToServer);
|
||||
printNameList("Encryption algorithms server to client", kexInitParams.encryptionAlgorithmsServerToClient);
|
||||
printNameList("MAC algorithms client to server", kexInitParams.macAlgorithmsClientToServer);
|
||||
printNameList("MAC algorithms server to client", kexInitParams.macAlgorithmsServerToClient);
|
||||
printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
|
||||
printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
|
||||
printNameList("Languages client to server", kexInitParams.languagesClientToServer);
|
||||
printNameList("Languages server to client", kexInitParams.languagesServerToClient);
|
||||
qCDebug(sshLog, "First packet follows: %d", kexInitParams.firstKexPacketFollows);
|
||||
|
||||
m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods,
|
||||
kexInitParams.keyAlgorithms.names);
|
||||
m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
|
||||
kexInitParams.serverHostKeyAlgorithms.names);
|
||||
determineHashingAlgorithm(kexInitParams, true);
|
||||
determineHashingAlgorithm(kexInitParams, false);
|
||||
|
||||
m_encryptionAlgo
|
||||
= SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
|
||||
kexInitParams.encryptionAlgorithmsClientToServer.names);
|
||||
m_decryptionAlgo
|
||||
= SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
|
||||
kexInitParams.encryptionAlgorithmsServerToClient.names);
|
||||
SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
|
||||
kexInitParams.compressionAlgorithmsClientToServer.names);
|
||||
SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
|
||||
kexInitParams.compressionAlgorithmsServerToClient.names);
|
||||
|
||||
AutoSeeded_RNG rng;
|
||||
if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
|
||||
m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
|
||||
m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value()));
|
||||
} else {
|
||||
m_dhKey.reset(new DH_PrivateKey(rng, DL_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
|
||||
m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y());
|
||||
}
|
||||
|
||||
m_serverKexInitPayload = serverKexInit.payLoad();
|
||||
return kexInitParams.firstKexPacketFollows;
|
||||
}
|
||||
|
||||
void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
|
||||
const QByteArray &clientId)
|
||||
{
|
||||
|
||||
const SshKeyExchangeReply &reply
|
||||
= dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo);
|
||||
if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
"Server sent invalid f.");
|
||||
}
|
||||
|
||||
QByteArray concatenatedData = AbstractSshPacket::encodeString(clientId);
|
||||
concatenatedData += AbstractSshPacket::encodeString(m_serverId);
|
||||
concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload);
|
||||
concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload);
|
||||
concatenatedData += reply.k_s;
|
||||
|
||||
printData("Client Id", AbstractSshPacket::encodeString(clientId));
|
||||
printData("Server Id", AbstractSshPacket::encodeString(m_serverId));
|
||||
printData("Client Payload", AbstractSshPacket::encodeString(m_clientKexInitPayload));
|
||||
printData("Server payload", AbstractSshPacket::encodeString(m_serverKexInitPayload));
|
||||
printData("K_S", reply.k_s);
|
||||
|
||||
SecureVector<byte> encodedK;
|
||||
if (m_dhKey) {
|
||||
concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y());
|
||||
concatenatedData += AbstractSshPacket::encodeMpInt(reply.f);
|
||||
DH_KA_Operation dhOp(*m_dhKey);
|
||||
SecureVector<byte> encodedF = BigInt::encode(reply.f);
|
||||
encodedK = dhOp.agree(encodedF, encodedF.size());
|
||||
printData("y", AbstractSshPacket::encodeMpInt(m_dhKey->get_y()));
|
||||
printData("f", AbstractSshPacket::encodeMpInt(reply.f));
|
||||
m_dhKey.reset(nullptr);
|
||||
} else {
|
||||
Q_ASSERT(m_ecdhKey);
|
||||
concatenatedData // Q_C.
|
||||
+= AbstractSshPacket::encodeString(convertByteArray(m_ecdhKey->public_value()));
|
||||
concatenatedData += AbstractSshPacket::encodeString(reply.q_s);
|
||||
ECDH_KA_Operation ecdhOp(*m_ecdhKey);
|
||||
encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count());
|
||||
m_ecdhKey.reset(nullptr);
|
||||
}
|
||||
|
||||
const BigInt k = BigInt::decode(encodedK);
|
||||
m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently.
|
||||
printData("K", m_k);
|
||||
concatenatedData += m_k;
|
||||
printData("Concatenated data", concatenatedData);
|
||||
|
||||
m_hash.reset(get_hash(botanHMacAlgoName(hashAlgoForKexAlgo())));
|
||||
const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData),
|
||||
concatenatedData.size());
|
||||
m_h = convertByteArray(hashResult);
|
||||
printData("H", m_h);
|
||||
|
||||
QScopedPointer<Public_Key> sigKey;
|
||||
if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) {
|
||||
const DL_Group group(reply.hostKeyParameters.at(0), reply.hostKeyParameters.at(1),
|
||||
reply.hostKeyParameters.at(2));
|
||||
DSA_PublicKey * const dsaKey
|
||||
= new DSA_PublicKey(group, reply.hostKeyParameters.at(3));
|
||||
sigKey.reset(dsaKey);
|
||||
} else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) {
|
||||
RSA_PublicKey * const rsaKey
|
||||
= new RSA_PublicKey(reply.hostKeyParameters.at(1), reply.hostKeyParameters.at(0));
|
||||
sigKey.reset(rsaKey);
|
||||
} else {
|
||||
QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo.startsWith(SshCapabilities::PubKeyEcdsaPrefix));
|
||||
const EC_Group domain(SshCapabilities::oid(m_serverHostKeyAlgo));
|
||||
const PointGFp point = OS2ECP(convertByteArray(reply.q), reply.q.count(),
|
||||
domain.get_curve());
|
||||
ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point);
|
||||
sigKey.reset(ecdsaKey);
|
||||
}
|
||||
|
||||
const byte * const botanH = convertByteArray(m_h);
|
||||
const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob);
|
||||
PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo));
|
||||
if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) {
|
||||
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
"Invalid signature in key exchange reply packet.");
|
||||
}
|
||||
|
||||
checkHostKey(reply.k_s);
|
||||
|
||||
m_sendFacility.sendNewKeysPacket();
|
||||
}
|
||||
|
||||
QByteArray SshKeyExchange::hashAlgoForKexAlgo() const
|
||||
{
|
||||
if (m_kexAlgoName == SshCapabilities::EcdhNistp256)
|
||||
return SshCapabilities::HMacSha256;
|
||||
if (m_kexAlgoName == SshCapabilities::EcdhNistp384)
|
||||
return SshCapabilities::HMacSha384;
|
||||
if (m_kexAlgoName == SshCapabilities::EcdhNistp521)
|
||||
return SshCapabilities::HMacSha512;
|
||||
return SshCapabilities::HMacSha1;
|
||||
}
|
||||
|
||||
void SshKeyExchange::determineHashingAlgorithm(const SshKeyExchangeInit &kexInit,
|
||||
bool serverToClient)
|
||||
{
|
||||
QByteArray * const algo = serverToClient ? &m_s2cHMacAlgo : &m_c2sHMacAlgo;
|
||||
const QList<QByteArray> &serverCapabilities = serverToClient
|
||||
? kexInit.macAlgorithmsServerToClient.names
|
||||
: kexInit.macAlgorithmsClientToServer.names;
|
||||
*algo = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, serverCapabilities);
|
||||
}
|
||||
|
||||
void SshKeyExchange::checkHostKey(const QByteArray &hostKey)
|
||||
{
|
||||
if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingNone) {
|
||||
if (m_connParams.hostKeyDatabase)
|
||||
m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_connParams.hostKeyDatabase) {
|
||||
throw SshClientException(SshInternalError,
|
||||
SSH_TR("Host key database must exist "
|
||||
"if host key checking is enabled."));
|
||||
}
|
||||
|
||||
switch (m_connParams.hostKeyDatabase->matchHostKey(m_connParams.host, hostKey)) {
|
||||
case SshHostKeyDatabase::KeyLookupMatch:
|
||||
return; // Nothing to do.
|
||||
case SshHostKeyDatabase::KeyLookupMismatch:
|
||||
if (m_connParams.hostKeyCheckingMode != SshHostKeyCheckingAllowMismatch)
|
||||
throwHostKeyException();
|
||||
break;
|
||||
case SshHostKeyDatabase::KeyLookupNoMatch:
|
||||
if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingStrict)
|
||||
throwHostKeyException();
|
||||
break;
|
||||
}
|
||||
m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
|
||||
}
|
||||
|
||||
void SshKeyExchange::throwHostKeyException()
|
||||
{
|
||||
throw SshServerException(SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key changed",
|
||||
SSH_TR("Host key of machine \"%1\" has changed.")
|
||||
.arg(m_connParams.host));
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QSsh
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user