Compare commits
1240 Commits
GUACAMOLE-
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
47b9360d46 | ||
|
98c2a6adcb | ||
|
3b0a9bac75 | ||
|
f6893ed319 | ||
|
a5214c971a | ||
|
ccfcef8c0f | ||
|
1a7a57ed19 | ||
|
eac064bde9 | ||
|
4afc1d85ce | ||
|
818b5f79df | ||
|
8ef60bfa9d | ||
|
d90e0e97fe | ||
|
ec7964e8fb | ||
|
add7ce361b | ||
|
7d16f67d6d | ||
|
e3adb97085 | ||
|
55941823ec | ||
|
07acce8a76 | ||
|
5b1677f21a | ||
|
623c398005 | ||
|
aa92239edd | ||
|
897712c743 | ||
|
02b24d0101 | ||
|
26eadc37a3 | ||
|
6d7156bc70 | ||
|
6312e1720d | ||
|
cb7ae25177 | ||
|
a4adb3f5c0 | ||
|
5cf408ebbb | ||
|
3ca6bb0a61 | ||
|
457a169c49 | ||
|
bad381cebe | ||
|
6171da6d0b | ||
|
067f2a91a0 | ||
|
bc52485570 | ||
|
b20afa275a | ||
|
b096e47f57 | ||
|
4d211e0c9e | ||
|
dffbeac57a | ||
|
0361adc01f | ||
|
1971a9dad2 | ||
|
5dbf4820ab | ||
|
b2eb13a178 | ||
|
2bc9d5ff01 | ||
|
5918cc9f7c | ||
|
15f6e9f678 | ||
|
b5addfe3da | ||
|
7f4246b6d5 | ||
|
6ab82446bb | ||
|
9c93337d97 | ||
|
cdee93ae25 | ||
|
eee3ac092c | ||
|
5bb56ed5ba | ||
|
0aae5eeadb | ||
|
6d994db9d2 | ||
|
cba5484be0 | ||
|
4048dd4900 | ||
|
98556fbe2e | ||
|
f438a36612 | ||
|
e8d966aec6 | ||
|
523532a52d | ||
|
51c640fdbd | ||
|
4cf1bfae0e | ||
|
9642afc468 | ||
|
ffb6c809be | ||
|
64ea9c4d1f | ||
|
a5834fd319 | ||
|
1e9cd9137b | ||
|
d4cd9b3e3a | ||
|
31f1b2c7c4 | ||
|
ce27936ed5 | ||
|
b7f05b9e4f | ||
|
d5761ad625 | ||
|
b26f9d64d6 | ||
|
da80163e24 | ||
|
28396ae345 | ||
|
a0e9f6ed9b | ||
|
bde8cdee46 | ||
|
669e02b4dc | ||
|
52c8683bcf | ||
|
c19eab9691 | ||
|
dd85c54961 | ||
|
c795bf9e4a | ||
|
c469300941 | ||
|
81300052e0 | ||
|
df4e5c6fdf | ||
|
a175a3d902 | ||
|
9cbd768210 | ||
|
c716a07abc | ||
|
c880f02fe8 | ||
|
ce88fa4d4a | ||
|
d734bac590 | ||
|
75a11b05b2 | ||
|
854b5ecbb8 | ||
|
d8073f9b17 | ||
|
46e813343e | ||
|
ad0155b5f5 | ||
|
0856e94ece | ||
|
1c97cdb115 | ||
|
ce2ffdf75f | ||
|
6dd33a8d90 | ||
|
589e0ecd03 | ||
|
037045a054 | ||
|
63bf5b329c | ||
|
dc9dfe562f | ||
|
757928dfa1 | ||
|
a0faa02616 | ||
|
44d76da21a | ||
|
05c6131cf3 | ||
|
001347b4e8 | ||
|
edce11fcb4 | ||
|
e78d589e25 | ||
|
06461cac53 | ||
|
8e94b00587 | ||
|
9245d02bc5 | ||
|
4d41b38a24 | ||
|
29535e6cb8 | ||
|
10126444bf | ||
|
23612720ce | ||
|
f84db7d166 | ||
|
a1d0d0aea4 | ||
|
534158c1cb | ||
|
56dca9880d | ||
|
703f258b06 | ||
|
bce1d2a434 | ||
|
084ddaf81f | ||
|
be9041fefd | ||
|
09bd4af77e | ||
|
7472310a03 | ||
|
27db57f704 | ||
|
1f6f45e6e9 | ||
|
e3e75464fb | ||
|
90322cd4d3 | ||
|
b9cc76058b | ||
|
daa052398e | ||
|
6760974912 | ||
|
e84317ff86 | ||
|
a1a758f13c | ||
|
8dc92e69ca | ||
|
73ac4fcdbe | ||
|
bc6b5cef25 | ||
|
491be8382a | ||
|
329171a950 | ||
|
12b8eac514 | ||
|
2524af80a9 | ||
|
91d79da49f | ||
|
eb52b4e258 | ||
|
278745d6d8 | ||
|
910c87bda0 | ||
|
31b4246e18 | ||
|
a91c4b3869 | ||
|
26d87aa5d3 | ||
|
44145f681a | ||
|
0182de6d18 | ||
|
df25aa9fdc | ||
|
68fc594247 | ||
|
a6a7e8ac26 | ||
|
189d8fab30 | ||
|
e90e438cf6 | ||
|
bf6922b31e | ||
|
27e762d06f | ||
|
b2ae2fdf00 | ||
|
1b78f611d3 | ||
|
e2a136f41e | ||
|
18a0362dab | ||
|
c2b7e2d039 | ||
|
27f8403178 | ||
|
0729a6cc73 | ||
|
d9d5c79644 | ||
|
650e7ad90f | ||
|
7bbab0efdd | ||
|
a920932703 | ||
|
c7935736da | ||
|
7f55399304 | ||
|
5428ac5057 | ||
|
a8cf250c98 | ||
|
ca1fbd5e98 | ||
|
d16ba33dee | ||
|
5eb2867733 | ||
|
048a59310b | ||
|
c88c0d1c89 | ||
|
4a38d39694 | ||
|
841dc28e9b | ||
|
c122a5f14a | ||
|
5cee64514f | ||
|
b40b7e7bf6 | ||
|
6d526cb60f | ||
|
46bed49a43 | ||
|
c769d18cf6 | ||
|
612c5eba20 | ||
|
fe62300223 | ||
|
0b6b14b71e | ||
|
1c3e86dc2c | ||
|
9dc793b0e5 | ||
|
1e6c42594f | ||
|
430182dce2 | ||
|
f710e00d26 | ||
|
53f981f864 | ||
|
a37668e9f5 | ||
|
68c5dd1730 | ||
|
90e15cb706 | ||
|
9475c0b6fa | ||
|
218f8d36b1 | ||
|
d20b385834 | ||
|
b608bb1513 | ||
|
3196f9f0d0 | ||
|
2fc3a43e87 | ||
|
ced24fde7d | ||
|
c6e8ad7a38 | ||
|
47c7450f0f | ||
|
4a5a350f59 | ||
|
b48e34fc3e | ||
|
d40db7cd3c | ||
|
004f57e19a | ||
|
7809447c3f | ||
|
7a1ba51bae | ||
|
9df3e294c3 | ||
|
7fcddef117 | ||
|
c867d392d0 | ||
|
8faeea18f8 | ||
|
c2c67ee34d | ||
|
1e3fc25268 | ||
|
299b7b4370 | ||
|
1e856d4e2d | ||
|
5124e78e05 | ||
|
4d3280e817 | ||
|
8041585379 | ||
|
2a4ecda216 | ||
|
e3df475bda | ||
|
c48feb30f5 | ||
|
1e550b58d9 | ||
|
1e8d9d92a5 | ||
|
7563402631 | ||
|
0be71a8c67 | ||
|
4e6ad1939f | ||
|
c7d3814450 | ||
|
c1ad6115a2 | ||
|
256487c95a | ||
|
2c86e20ab9 | ||
|
683ef1722e | ||
|
79239e3be0 | ||
|
6b58e2e5a9 | ||
|
164f792b86 | ||
|
2aa0218cc7 | ||
|
8d683b560c | ||
|
c449d83790 | ||
|
d6a817f58c | ||
|
ceabd68e28 | ||
|
558eb149f4 | ||
|
3e1e1bd268 | ||
|
aa870debad | ||
|
3e19583b29 | ||
|
9e1dada14b | ||
|
3b4007c9fa | ||
|
bfb54f72a0 | ||
|
6605f217c5 | ||
|
b6d3edb749 | ||
|
f70fdfc612 | ||
|
ec3cdfd17b | ||
|
26b9850d87 | ||
|
98dbf15d0b | ||
|
e8feeabfef | ||
|
0db61198e9 | ||
|
bc8ed4e104 | ||
|
b00b629b96 | ||
|
0761908a77 | ||
|
c579e7337f | ||
|
5ec2551761 | ||
|
5881209f12 | ||
|
5c309f5cb1 | ||
|
7759f9b1c0 | ||
|
51b9c9c103 | ||
|
4318083511 | ||
|
939d954810 | ||
|
76ef6332cc | ||
|
7369bed22c | ||
|
21a5d9ee62 | ||
|
7683a17d69 | ||
|
382d72a26a | ||
|
df33cd0874 | ||
|
be01e7e93a | ||
|
7ee9c64c04 | ||
|
a2fb09021b | ||
|
0cdc51acd1 | ||
|
aa3a9cde6c | ||
|
08a57d3375 | ||
|
2f6de25418 | ||
|
f8f2c7f747 | ||
|
a8151c40c4 | ||
|
f0dee00d33 | ||
|
3fc3880f0e | ||
|
d35a97d28e | ||
|
7d06113cbe | ||
|
c10ceab7e8 | ||
|
d34745a40b | ||
|
264192fd25 | ||
|
79c6e5787d | ||
|
6042222d44 | ||
|
7de6ba7ea9 | ||
|
630798503c | ||
|
f4ff5f337c | ||
|
d8c32b1e82 | ||
|
86176ff770 | ||
|
45a0cd943b | ||
|
99fce8fa19 | ||
|
af2c109079 | ||
|
4ac0940e81 | ||
|
3a87dd0c96 | ||
|
37965a961e | ||
|
9ee956f765 | ||
|
096a067b1f | ||
|
8d9049942d | ||
|
708769b4c3 | ||
|
614f38767e | ||
|
f1f1b0d438 | ||
|
4815f62358 | ||
|
025525f93a | ||
|
debd888cf5 | ||
|
3e73e392a0 | ||
|
fb94ef9e9a | ||
|
628f2fd815 | ||
|
3798d85bd1 | ||
|
2407157d00 | ||
|
7d17e6898a | ||
|
337f3bbff2 | ||
|
9b891e2360 | ||
|
81601d99fe | ||
|
e8deeeae97 | ||
|
5f5080994f | ||
|
60944f1092 | ||
|
a246403137 | ||
|
3f375a4501 | ||
|
e8153f9002 | ||
|
568e037012 | ||
|
31d05de72a | ||
|
f884ab76b1 | ||
|
1117cf052c | ||
|
b69248048c | ||
|
38737a8353 | ||
|
5c1a2fc44c | ||
|
ba3d1de3bb | ||
|
2cec040b9e | ||
|
e9652becfd | ||
|
31a415cc59 | ||
|
024e281252 | ||
|
48b3d5038f | ||
|
cb6ffd06e6 | ||
|
67450d89f3 | ||
|
96c4c208b4 | ||
|
7fd54c56a8 | ||
|
ce0982fefd | ||
|
0c784d434c | ||
|
120be65dbc | ||
|
7598f5a95a | ||
|
d5608fb8a2 | ||
|
a7732e72be | ||
|
6a50d3076c | ||
|
29b055b511 | ||
|
f899fe0b2f | ||
|
d76502d169 | ||
|
787ae317fc | ||
|
e82eb45a74 | ||
|
db4b155c51 | ||
|
72489d5690 | ||
|
2c2f372def | ||
|
e51c269a51 | ||
|
b0bcb30346 | ||
|
ec305903d0 | ||
|
57c4dbf454 | ||
|
30bbb892db | ||
|
b8ecfe2425 | ||
|
9ca382e2aa | ||
|
0c59897da9 | ||
|
b6568d11b3 | ||
|
65c07b75cc | ||
|
a5a89bcf1d | ||
|
32723d229f | ||
|
2aa2ccc90c | ||
|
df8030d9bb | ||
|
42e223f4a6 | ||
|
4dce306a04 | ||
|
e526174009 | ||
|
af89f828eb | ||
|
d03d45fbf0 | ||
|
4184a52c98 | ||
|
ec093d3cea | ||
|
029563a4b9 | ||
|
8d895f13c7 | ||
|
ff34146f57 | ||
|
234f5aff1a | ||
|
a0e11dc817 | ||
|
0bf65ce8b0 | ||
|
557e2f5944 | ||
|
47bf3ab672 | ||
|
315a8a7179 | ||
|
e761e47cd0 | ||
|
71769b9715 | ||
|
ac9e5e91f6 | ||
|
98f0c271fb | ||
|
8560ff9718 | ||
|
ce28575b3a | ||
|
8838199f5c | ||
|
f910d29083 | ||
|
02a7291742 | ||
|
9c37fc5617 | ||
|
a55e1893dd | ||
|
b077013c30 | ||
|
bbb794966b | ||
|
e78eb589d9 | ||
|
04b8633410 | ||
|
aa8c8cac84 | ||
|
a0d4bacbc6 | ||
|
0feda1fa2f | ||
|
45e46bd245 | ||
|
3dc2591517 | ||
|
3d4a27607d | ||
|
fba6ef461c | ||
|
fa86d18353 | ||
|
f47491eaca | ||
|
e4407167ab | ||
|
253213b29d | ||
|
5e1b92cb65 | ||
|
789463ce76 | ||
|
cc688485db | ||
|
cf845be5ee | ||
|
28983d964b | ||
|
0832dda409 | ||
|
4b4b11da57 | ||
|
34d798a0ff | ||
|
811d2f70e4 | ||
|
83d2f30f8b | ||
|
8ea9b14a80 | ||
|
d2ee11f15a | ||
|
a80cd8db06 | ||
|
3b0abe376e | ||
|
090bb3bbea | ||
|
783aa85c1b | ||
|
5ecad1c3d9 | ||
|
659cdd09a0 | ||
|
fe0658dd36 | ||
|
fb237d4fc9 | ||
|
bb825de73b | ||
|
1a699686b9 | ||
|
42e382062c | ||
|
68a6285818 | ||
|
0e845a2914 | ||
|
550e13ae9f | ||
|
b13fae7a9f | ||
|
0aecca3e04 | ||
|
a054073ec7 | ||
|
8ebe5c571a | ||
|
28f7d3694e | ||
|
3bc00c429a | ||
|
87a3c7392b | ||
|
49df9f04e0 | ||
|
26dfc533bd | ||
|
75b658c6c9 | ||
|
24b6a3f357 | ||
|
b21d19d37d | ||
|
b8148b0daf | ||
|
ddc09b161a | ||
|
32f8f20852 | ||
|
a447b3dc1a | ||
|
0cd9ba387b | ||
|
35e44db713 | ||
|
cc8d879a12 | ||
|
77f6168729 | ||
|
a384e749e2 | ||
|
2c6be5afd1 | ||
|
0676e70325 | ||
|
39f7d5a843 | ||
|
fd2c5b9259 | ||
|
39defa27fa | ||
|
be5cdf52c1 | ||
|
37dceed8ec | ||
|
65b4722a1a | ||
|
fcac1a26f8 | ||
|
956c5f293e | ||
|
3e22526ad9 | ||
|
99b00a8cc0 | ||
|
de3300ed89 | ||
|
3dfd2467c3 | ||
|
e5fdda1286 | ||
|
308d7a09a8 | ||
|
9a34caf40f | ||
|
eaae203e94 | ||
|
4282da662f | ||
|
e9846945c7 | ||
|
4b1243fbf8 | ||
|
5e9c7cdb42 | ||
|
01c731e241 | ||
|
92d97a3244 | ||
|
e9670df145 | ||
|
a3d9a685e8 | ||
|
f805a80bc1 | ||
|
62ee36142d | ||
|
b3a713bf7a | ||
|
c4f6c5161c | ||
|
feb376ea1e | ||
|
2139d40e97 | ||
|
55959b5456 | ||
|
666c4fb299 | ||
|
67c5bdfdfe | ||
|
dc8c60f30f | ||
|
80988cd6f4 | ||
|
1f24c47e29 | ||
|
0677a9ae4d | ||
|
a54a12362c | ||
|
e325dbc672 | ||
|
9855d875c7 | ||
|
2bbc4bfbff | ||
|
f57382f885 | ||
|
555973f6b0 | ||
|
902c5e1bd4 | ||
|
1bc9384ea8 | ||
|
36f227586e | ||
|
2d4412316f | ||
|
d7151e0d84 | ||
|
7ef1dcafba | ||
|
f33416949f | ||
|
8dda26af54 | ||
|
0926864ecb | ||
|
9ad3bc9a49 | ||
|
6940875e6e | ||
|
d2083a1aed | ||
|
36545cc92c | ||
|
a54c61e860 | ||
|
fbb759ab82 | ||
|
ab05502494 | ||
|
12febd5162 | ||
|
6c239a7b98 | ||
|
598b86a4cf | ||
|
69831995cb | ||
|
47a1dcc82e | ||
|
0c7091198f | ||
|
bced87cff9 | ||
|
024fc2a1f7 | ||
|
4752863b5b | ||
|
d7bbee35b0 | ||
|
a63dcb46b2 | ||
|
827951dcf6 | ||
|
875d51c1ed | ||
|
664586ea54 | ||
|
4612e79b8d | ||
|
3255b182ab | ||
|
352b9c517c | ||
|
7b93b3d2e9 | ||
|
a7352b1429 | ||
|
ee4d91ea98 | ||
|
68710a6702 | ||
|
6f2b124472 | ||
|
0497a33ece | ||
|
f3cef7e2f0 | ||
|
233c0555c3 | ||
|
c97b8f287e | ||
|
1822e59ac3 | ||
|
fa0ad267b8 | ||
|
6ae6ea587b | ||
|
199c2a0961 | ||
|
77a32398e5 | ||
|
6dc8b57ca4 | ||
|
07f6e6afc2 | ||
|
eab07b4a61 | ||
|
de493ba959 | ||
|
9dd1555c81 | ||
|
8c7984d201 | ||
|
b64b8f375a | ||
|
831606a4e9 | ||
|
cc7cd78e5b | ||
|
16be3af03c | ||
|
b89ed7ff15 | ||
|
17045d5d3a | ||
|
7904d9c002 | ||
|
7a7ffc2c19 | ||
|
65fe6c9735 | ||
|
c311d8bde9 | ||
|
1e692094be | ||
|
7332e633dc | ||
|
2ed0d042a3 | ||
|
a76e307176 | ||
|
6886665f65 | ||
|
703ce5c223 | ||
|
ad7ab67571 | ||
|
9fa9adbd58 | ||
|
3b560044bc | ||
|
82664b4e6b | ||
|
fbfbaff540 | ||
|
e4a68d776f | ||
|
9b08a716d6 | ||
|
554251cc72 | ||
|
17d31d94b7 | ||
|
a5b62aa82e | ||
|
b181026e58 | ||
|
4cc9c2d3e1 | ||
|
f34be230aa | ||
|
a189c9ab8a | ||
|
51463209a8 | ||
|
7ac840090e | ||
|
61d12f1668 | ||
|
f21621e677 | ||
|
88425160ae | ||
|
b9001f4ec7 | ||
|
4329739d3e | ||
|
90d55956d0 | ||
|
51ae8a41a1 | ||
|
f962eab27a | ||
|
381ff1a421 | ||
|
43269920db | ||
|
f56df8b8be | ||
|
ccfcfb116d | ||
|
4dabea37af | ||
|
0516d599cf | ||
|
b5191caddc | ||
|
0c7898c55a | ||
|
289ceac222 | ||
|
e25f83d629 | ||
|
e6835795f0 | ||
|
b6005cc8a0 | ||
|
95039ea3b3 | ||
|
ca073db5b6 | ||
|
c3a295e9b7 | ||
|
c47aa0cea1 | ||
|
c1b8250300 | ||
|
a763d47bc7 | ||
|
a3101e9744 | ||
|
c26672e281 | ||
|
26bb10a486 | ||
|
41b0c21322 | ||
|
87a5479ff8 | ||
|
b4ef38c064 | ||
|
ce7bea66cf | ||
|
1a9d1e8b71 | ||
|
55add063c5 | ||
|
9228d2637a | ||
|
8f1826d3e4 | ||
|
badee3274b | ||
|
e149fd4f70 | ||
|
7e9b97007e | ||
|
1baa91f852 | ||
|
22874e2388 | ||
|
3511991e2f | ||
|
9a51d513f2 | ||
|
3d15454097 | ||
|
4641da06ac | ||
|
b7dca0ed16 | ||
|
a1c382c8ce | ||
|
8b53be49f3 | ||
|
bf741a46d6 | ||
|
652ca302cb | ||
|
93a240b8ad | ||
|
5e8f5eaa50 | ||
|
fc68113d75 | ||
|
4b43de963e | ||
|
588e0f194a | ||
|
ab12b2aa8e | ||
|
98cb7ccf67 | ||
|
75c0deac1f | ||
|
54f88531d4 | ||
|
340aef5362 | ||
|
379fce2d77 | ||
|
c750b18f60 | ||
|
2f57564f5d | ||
|
2b68925ec9 | ||
|
0ee47e0186 | ||
|
5480b288e8 | ||
|
5caa8a25f7 | ||
|
6fae0b4b23 | ||
|
f70aa4939f | ||
|
cd3432e594 | ||
|
b90e566e1b | ||
|
0c25782036 | ||
|
6dad6cd919 | ||
|
dd4c3968d1 | ||
|
dc71987fca | ||
|
871f31353b | ||
|
2db7ffbaab | ||
|
61070cb367 | ||
|
f8ec709e33 | ||
|
cda7bca126 | ||
|
6e2be38ae2 | ||
|
986f7f5d64 | ||
|
1591980579 | ||
|
591e494dfd | ||
|
f19754cfa6 | ||
|
24ab5ca85b | ||
|
1b8e31b70c | ||
|
ffe0b57faa | ||
|
a4521208ba | ||
|
3989c40da6 | ||
|
a6f2ab9d93 | ||
|
bfc6c1e6e0 | ||
|
36817f3774 | ||
|
df4c93b3e8 | ||
|
c90c057e12 | ||
|
e9a10d66b7 | ||
|
5e2ddb890a | ||
|
1300b64bb9 | ||
|
228cea4af1 | ||
|
254615509a | ||
|
993d5c5707 | ||
|
7d2b7126db | ||
|
802e5b5547 | ||
|
67a2b75fe6 | ||
|
cb227cc3a1 | ||
|
a0d030a7ae | ||
|
193f721c7b | ||
|
7065ff5586 | ||
|
aeb9b99a6c | ||
|
877eca691c | ||
|
63ede7e406 | ||
|
c6feef6c86 | ||
|
350d8e5995 | ||
|
ba8fd17394 | ||
|
9fb713d804 | ||
|
b7761e9a2e | ||
|
789e3883d6 | ||
|
7da837b42a | ||
|
e6c5da315e | ||
|
258946cd88 | ||
|
068f33aaef | ||
|
dec3642905 | ||
|
fdd3292f09 | ||
|
3549da0dd1 | ||
|
e5c1147cf6 | ||
|
5bf6a1479c | ||
|
a78f254611 | ||
|
d7909a77aa | ||
|
f6953e1317 | ||
|
10e06c15c9 | ||
|
7b7c8a1b02 | ||
|
a06edb9deb | ||
|
768b2ba0f5 | ||
|
d73b86b4b7 | ||
|
bb9560716d | ||
|
cc4671d7a1 | ||
|
47ad6f4b59 | ||
|
aba7b987d3 | ||
|
b6477ea7ae | ||
|
01142e6dd8 | ||
|
bbb6afaf46 | ||
|
9486ec7cc4 | ||
|
476b431041 | ||
|
877bf59cb6 | ||
|
ca4009c982 | ||
|
d7118fda70 | ||
|
2827af33d6 | ||
|
867e63b524 | ||
|
0d435e2435 | ||
|
7b1ba3f269 | ||
|
d1b3695282 | ||
|
f61539c4e7 | ||
|
9a944637be | ||
|
4bd19160de | ||
|
b3be9eb869 | ||
|
0b71559017 | ||
|
e2b4de9d95 | ||
|
d7ed452d69 | ||
|
5536b836ad | ||
|
ffdc98d024 | ||
|
381c5d1a76 | ||
|
5683be0ea3 | ||
|
9e28de70ec | ||
|
d2cb7a9ce9 | ||
|
0cf24219d8 | ||
|
e132c79348 | ||
|
edbdd08476 | ||
|
454682979e | ||
|
f293c5e9c0 | ||
|
dcab540839 | ||
|
2f16eadb35 | ||
|
6f9f2189f2 | ||
|
6f49194640 | ||
|
0ffda8aaf0 | ||
|
34c088882b | ||
|
f8d2bd13b1 | ||
|
d851f10a48 | ||
|
356dcef9e8 | ||
|
7352d66819 | ||
|
64b1572d13 | ||
|
7374b29364 | ||
|
b0be808036 | ||
|
9c593bde89 | ||
|
44d3433ea9 | ||
|
7ee624844a | ||
|
b48a1b3a5d | ||
|
d8618b0682 | ||
|
af93cfb32a | ||
|
acfc759527 | ||
|
760f7a649a | ||
|
2d6ce1a5fd | ||
|
43db1965ef | ||
|
61df2956b3 | ||
|
83a531bc89 | ||
|
2e50573531 | ||
|
5e3aec6df2 | ||
|
371eed1f93 | ||
|
77a866129b | ||
|
c5f67a31dc | ||
|
ed56093888 | ||
|
34f8f8b30d | ||
|
fe7edce569 | ||
|
b7c938c239 | ||
|
f35517b3ff | ||
|
cbe593503f | ||
|
f72877bf0d | ||
|
7165fa949d | ||
|
519c90a887 | ||
|
5bae422b29 | ||
|
b8bd0e4c6a | ||
|
45e8503ead | ||
|
8456c050ea | ||
|
54fda21366 | ||
|
d7cfff324e | ||
|
622a849bae | ||
|
462d494ed8 | ||
|
442b1d5cc2 | ||
|
5f8c6470ff | ||
|
948d1bcac8 | ||
|
1178b475da | ||
|
286cbf32a7 | ||
|
4606607309 | ||
|
0b39b0fc5f | ||
|
61a51df1b2 | ||
|
332e187813 | ||
|
a1ba91b01d | ||
|
eb5aa14a6f | ||
|
911e60cf5c | ||
|
994cb95893 | ||
|
0e6d549a40 | ||
|
6a576f0121 | ||
|
0062f61d67 | ||
|
427c4c8b44 | ||
|
4f25410aa9 | ||
|
860a5fca8f | ||
|
d8cb2218ee | ||
|
e66178ff9a | ||
|
99b17b0ac4 | ||
|
e02df8d550 | ||
|
79ce5ad8b0 | ||
|
b0b0b186f5 | ||
|
f5b5ac7183 | ||
|
71f993b25d | ||
|
83f8dd50df | ||
|
f980d4b926 | ||
|
adcdb080cb | ||
|
5e942c9a67 | ||
|
958fb4c8e0 | ||
|
cfcfe8866c | ||
|
35237a4f88 | ||
|
da1e078242 | ||
|
a1ec5d9ad7 | ||
|
e68fe81938 | ||
|
b21f00c29d | ||
|
67680bd2d5 | ||
|
addb473148 | ||
|
fe44fd7c3b | ||
|
7bc6a62365 | ||
|
ba684962b6 | ||
|
f9379dc6bb | ||
|
7e254955e8 | ||
|
ebbb7492e7 | ||
|
27c977adb2 | ||
|
428243bb78 | ||
|
ac2b4f8d12 | ||
|
aec2be6da2 | ||
|
2bebb96804 | ||
|
551598e0a4 | ||
|
42044e4279 | ||
|
ec4315dfbe | ||
|
5bb616832e | ||
|
c080569cac | ||
|
2f0c6dcfa3 | ||
|
9112c4f32f | ||
|
0d82cd1e6c | ||
|
171bae1f5c | ||
|
c120aa0274 | ||
|
b650bef139 | ||
|
97593958e4 | ||
|
f3d9c2f610 | ||
|
7bfd3e1c6a | ||
|
da2e8220f3 | ||
|
ecda5c1df9 | ||
|
1756c01522 | ||
|
6f08ef2a07 | ||
|
c0d323828e | ||
|
f87af06ad6 | ||
|
30e90b2425 | ||
|
334849e2a6 | ||
|
107fdda1f0 | ||
|
6850fd40fe | ||
|
21f54b9e12 | ||
|
9c10ddae3b | ||
|
526152b9c6 | ||
|
81bba1b587 | ||
|
4eae5d2e6d | ||
|
87df97317f | ||
|
b96afce222 | ||
|
6da9236ffd | ||
|
03d9c51b5d | ||
|
7e68901ceb | ||
|
1bd537c350 | ||
|
f8b35078fc | ||
|
b61a6ab758 | ||
|
8d43c4344d | ||
|
325c8061ea | ||
|
6d8319e1bd | ||
|
70b2b8a1bf | ||
|
dc1918b217 | ||
|
7453bc8f44 | ||
|
b441181c18 | ||
|
ea946f2492 | ||
|
c898f35959 | ||
|
d6a5695f8a | ||
|
0126880dad | ||
|
b3c1471180 | ||
|
1f60526ab8 | ||
|
14389326b4 | ||
|
eb282e49d9 | ||
|
2e4fb5b91c | ||
|
d6510360d0 | ||
|
554d3209db | ||
|
72638aa03e | ||
|
3516704b82 | ||
|
a9d01af104 | ||
|
1cfba83164 | ||
|
e16bfd7837 | ||
|
11136f7d7b | ||
|
112ce5299e | ||
|
86dde85b2d | ||
|
e2feb41605 | ||
|
fef819fbb9 | ||
|
f75579de7e | ||
|
77cac3b30d | ||
|
344ed4f42e | ||
|
a27757682a | ||
|
b72bcafecd | ||
|
45b832bfdc | ||
|
dd7522bd9f | ||
|
c3e1b2afef | ||
|
64ca77f3a5 | ||
|
33cca46346 | ||
|
dd78d230ea | ||
|
c286668b79 | ||
|
9bd28321e5 | ||
|
fd58d31eea | ||
|
46e908c06e | ||
|
2ace9385a2 | ||
|
5583748b54 | ||
|
f1bf70a4a2 | ||
|
bc1e2f5276 | ||
|
5d37530687 | ||
|
bc5b01d4d8 | ||
|
d239207f0f | ||
|
3187a641cf | ||
|
329cc9ee48 | ||
|
3b327378eb | ||
|
f4f5b4e65a | ||
|
5f27616f32 | ||
|
05f54d098c | ||
|
599ca960aa | ||
|
cd0e48234a | ||
|
5b58c7e15b | ||
|
9ed3baf004 | ||
|
9705b39c2b | ||
|
19b5050fbf | ||
|
b0c14bd59f | ||
|
1cf86e12a1 | ||
|
d75a18e603 | ||
|
02cd424d18 | ||
|
4d7191147c | ||
|
ebc6b9429f | ||
|
139251ea73 | ||
|
3f6acb6378 | ||
|
b1d050285a | ||
|
8ca6ff3a94 | ||
|
35eebe8553 | ||
|
7f9e61a1f2 | ||
|
555126441e | ||
|
cfd69cd122 | ||
|
5f547fb118 | ||
|
575ff91369 | ||
|
5f5b4ea8eb | ||
|
08f854ffef | ||
|
c543adddb2 | ||
|
053d9d420c | ||
|
4e5a7e97ad | ||
|
9a5b5574a8 | ||
|
5d56985479 | ||
|
e37fb1dad9 | ||
|
57428a95aa | ||
|
4fb17d5610 | ||
|
876516a1fb | ||
|
b21aef565b | ||
|
fdd17e3042 | ||
|
c0b2871b31 | ||
|
b7257d9ae4 | ||
|
5e5f1fcb3e | ||
|
5b612b856a | ||
|
86b09c8cf7 | ||
|
3633af5e41 | ||
|
df29735c83 | ||
|
d39757b4dc | ||
|
ebc731aaf3 | ||
|
81a0e66d9f | ||
|
7eb4e22515 | ||
|
a74d6a2aaf | ||
|
e2455d6f26 | ||
|
cafcd90f9f | ||
|
a14832c4da | ||
|
df770ae4ea | ||
|
b37e73488f | ||
|
dc5245025e | ||
|
dcaf7b2c21 | ||
|
db85163e20 | ||
|
8c844e6eab | ||
|
822a6c6b9d | ||
|
61c16a89d2 | ||
|
d562cb7648 | ||
|
0f78b01e81 | ||
|
11605ff5ed | ||
|
93b3eebc5d | ||
|
66ffda24f0 | ||
|
5295886f68 | ||
|
7c191d7be0 | ||
|
f72de10328 | ||
|
ff6c4b04f4 | ||
|
1b81549c97 | ||
|
37236207a0 | ||
|
76a6e41031 | ||
|
e3d8c3fa12 | ||
|
6322874d3a | ||
|
e4f4761c87 | ||
|
798ba1e5be | ||
|
89912dc657 | ||
|
a514f03fd4 | ||
|
d201344443 | ||
|
2c12c12850 | ||
|
aa6d81d6f9 | ||
|
a75bca1e95 | ||
|
fc599d2aec | ||
|
493fa4df16 | ||
|
c4c32264bf | ||
|
fc15850288 | ||
|
a8174eeac9 | ||
|
ed8a32f98b | ||
|
625eee0caa | ||
|
e139b20d12 | ||
|
f7990af6d0 | ||
|
da0fc1a6d8 | ||
|
025fc0525f | ||
|
233f5e27e2 | ||
|
b9f8e13d80 | ||
|
bbafa00df0 | ||
|
d33bd8deff | ||
|
15f6c4f3dc | ||
|
95be88be19 | ||
|
012a3497eb | ||
|
caedf26a06 | ||
|
9487eb2dc9 | ||
|
c49c57ef30 | ||
|
bb527f30f8 | ||
|
c8eaa91ad3 | ||
|
d35cc7a83e | ||
|
3c7a09f52b | ||
|
1d0e63b251 | ||
|
9200bc789f | ||
|
e4dd8de4f1 | ||
|
afb554a014 | ||
|
f559701645 | ||
|
4e80960933 | ||
|
d2102e5705 | ||
|
c5f674340a | ||
|
fc071fd1af | ||
|
99e6f89eba | ||
|
b0d9bbc121 | ||
|
27cf97cb9e | ||
|
6400af605e | ||
|
ef18f858cb | ||
|
c53575b18c | ||
|
eec3607b16 | ||
|
d3c5a8a050 | ||
|
eb73563445 | ||
|
d85f61deaf | ||
|
1c404d1881 | ||
|
944857d130 | ||
|
7b8d250bd5 | ||
|
9dcddd1947 | ||
|
50d2dd51cb | ||
|
6236eb8f98 | ||
|
b608d2266b | ||
|
3fdd1e973a | ||
|
7857dd0a9a | ||
|
07db9808a0 | ||
|
60141175dd | ||
|
f504b1a5e8 | ||
|
5b748a4b42 | ||
|
06a5043442 | ||
|
f311c23ffa | ||
|
78dbf64416 | ||
|
3a46fffe44 | ||
|
6c484c1efd | ||
|
7f3b985cc6 | ||
|
69d8d87ec9 | ||
|
a56a7207ac | ||
|
d51e92eb31 | ||
|
0474f86c46 | ||
|
836fc3eaa0 | ||
|
e5a1b4d8ae | ||
|
d9c1ce7738 | ||
|
a5efbb5933 | ||
|
650f7a0a32 | ||
|
041fcc4651 | ||
|
711cdd6929 | ||
|
05dfb1a6ae | ||
|
ed77114038 | ||
|
8c24c77d55 | ||
|
af4d762147 | ||
|
89b9a905db | ||
|
df718395e8 | ||
|
193051dfd5 | ||
|
070bd25721 | ||
|
770a2805e4 | ||
|
ca3563a38e | ||
|
584c108d1a | ||
|
57ee384180 | ||
|
03403e3ea5 | ||
|
e7fc8a0d98 | ||
|
9993684205 | ||
|
1e3d82cc63 | ||
|
f693b02e12 | ||
|
75019f5e4b | ||
|
8ab7e56972 | ||
|
f42f05aab7 | ||
|
9ee224f2ca | ||
|
9da27ad578 | ||
|
2146200dfd | ||
|
6d2cfdabf8 | ||
|
8024f13458 | ||
|
04205a9b97 | ||
|
58e26eb312 | ||
|
0e5498fc83 | ||
|
79b3b1029c | ||
|
4b7c679808 | ||
|
d88b5d1011 | ||
|
2a91e2ff2d | ||
|
a04022883c | ||
|
008ba46597 | ||
|
691a3ab840 | ||
|
10180095d8 | ||
|
e4ce7b0eeb | ||
|
19f7424c62 | ||
|
87fba523ee | ||
|
11bf6f05c4 | ||
|
eaa71f5717 | ||
|
48fc4afc5b | ||
|
69caa9936b | ||
|
d3fa5e7fbd | ||
|
228874804e | ||
|
09099050cb | ||
|
da8636ef54 | ||
|
af8ef9f526 | ||
|
37fe3548de | ||
|
8833c0dd79 | ||
|
2ac1f14973 | ||
|
3b0b36ad5d | ||
|
1081131aa5 | ||
|
457c48ec71 | ||
|
4dd41fa7d2 | ||
|
e910dcbfcc | ||
|
aba2b46a7c | ||
|
61896e829c | ||
|
114f5b23e3 | ||
|
8cc9a47e5c | ||
|
039a1c52e4 | ||
|
ddc1aa0b5f | ||
|
516c4a0593 | ||
|
cbca2f169b | ||
|
6a3ce58ab7 | ||
|
adf9db7454 | ||
|
2c2824fc5b | ||
|
145762a2b0 | ||
|
fe8771e181 | ||
|
240e18cd92 | ||
|
ae7e8d3890 | ||
|
1a96c5b415 | ||
|
1d5d8784b3 | ||
|
cf05eca68b | ||
|
6a1db08299 | ||
|
da7b77d2ec | ||
|
de9b8d6d2c | ||
|
72284cfc7c | ||
|
1f8dbb265e | ||
|
270d51d4d4 | ||
|
98a5faaa77 | ||
|
dbfb782dd5 | ||
|
c8fca94899 | ||
|
c4903a8e36 | ||
|
3ff8323153 | ||
|
08ce4331bb | ||
|
eb5ef5a027 | ||
|
3157361077 | ||
|
f85978e43a | ||
|
e27996b199 | ||
|
af46b484dd | ||
|
3f7ccb6b9a | ||
|
d371f2d9ee | ||
|
fb8a2766da | ||
|
c344aa854f | ||
|
9218a79e62 | ||
|
cf01d0d634 | ||
|
821feeabb0 | ||
|
58b85da8f2 | ||
|
f23133043b | ||
|
a808a6b177 | ||
|
4fe5d7f642 | ||
|
6939142d2f | ||
|
2703c5afff | ||
|
6449f047e5 | ||
|
0fb567bb53 | ||
|
12d29569a6 | ||
|
d40b2d8153 | ||
|
c796787f25 | ||
|
077cc84d27 | ||
|
4f4643dd2f | ||
|
a78d52e615 | ||
|
0210b7dc6b | ||
|
cd8646b38f | ||
|
a7ee5edd8b | ||
|
12cde5d520 | ||
|
7a4b737a3a | ||
|
0ea95822bc | ||
|
da81d16c27 | ||
|
bf2a5885d0 | ||
|
5a68f932d6 | ||
|
1537e475af | ||
|
17093a8149 | ||
|
d23a22b7c6 | ||
|
3fc43fba37 | ||
|
4da4ce7279 | ||
|
a6e3f19bf7 |
@ -1,3 +1,5 @@
|
|||||||
|
# Docker build spec
|
||||||
|
Dockerfile
|
||||||
|
|
||||||
# Git repository metadata
|
# Git repository metadata
|
||||||
.git
|
.git
|
||||||
@ -54,5 +56,5 @@ tests/test_*
|
|||||||
!tests/test_*.[ch]
|
!tests/test_*.[ch]
|
||||||
|
|
||||||
# Generated docs
|
# Generated docs
|
||||||
doc/doxygen-output
|
doc/*/doxygen-output
|
||||||
|
|
||||||
|
24
.gitignore
vendored
24
.gitignore
vendored
@ -10,6 +10,10 @@
|
|||||||
*.gcov
|
*.gcov
|
||||||
*.gcno
|
*.gcno
|
||||||
|
|
||||||
|
# Test suite output
|
||||||
|
*.log
|
||||||
|
*.trs
|
||||||
|
|
||||||
# Backup files
|
# Backup files
|
||||||
*~
|
*~
|
||||||
|
|
||||||
@ -23,32 +27,24 @@
|
|||||||
.deps/
|
.deps/
|
||||||
.dirstamp
|
.dirstamp
|
||||||
.libs/
|
.libs/
|
||||||
|
Doxyfile
|
||||||
Makefile
|
Makefile
|
||||||
Makefile.in
|
Makefile.in
|
||||||
aclocal.m4
|
aclocal.m4
|
||||||
autom4te.cache/
|
autom4te.cache/
|
||||||
|
build-aux/
|
||||||
|
libtool
|
||||||
m4/*
|
m4/*
|
||||||
!README
|
!README
|
||||||
compile
|
|
||||||
config.guess
|
|
||||||
config.h
|
config.h
|
||||||
config.h.in
|
config.h.in
|
||||||
config.log
|
config.log
|
||||||
config.status
|
config.status
|
||||||
config.sub
|
|
||||||
configure
|
configure
|
||||||
depcomp
|
|
||||||
install-sh
|
|
||||||
libtool
|
|
||||||
ltmain.sh
|
|
||||||
missing
|
|
||||||
stamp-h1
|
stamp-h1
|
||||||
test-driver
|
|
||||||
|
|
||||||
# Test binaries
|
|
||||||
tests/test_*
|
|
||||||
!tests/test_*.[ch]
|
|
||||||
|
|
||||||
# Generated docs
|
# Generated docs
|
||||||
doc/doxygen-output
|
doc/*/doxygen-output
|
||||||
|
|
||||||
|
# IDE metadata
|
||||||
|
nbproject/
|
||||||
|
14
AUTHORS
14
AUTHORS
@ -1,14 +0,0 @@
|
|||||||
Michael Jumper <mike.jumper@guac-dev.org>
|
|
||||||
James Muehlner <james.muehlner@guac-dev.org>
|
|
||||||
Matt Hortman <matt@FlintRiverSystems.com>
|
|
||||||
Jocelyn Delalande <j.delalande@ulteo.com>
|
|
||||||
David Lechevalier <david@ulteo.com>
|
|
||||||
Alexandre Devely <alex@koumoula.com>
|
|
||||||
Laurent Meunier <laurent@deltalima.net>
|
|
||||||
Saul Gio Perez <gio.perez@sv.cmu.edu>
|
|
||||||
Tom Sealy <tom.sealy@yahoo.com>
|
|
||||||
Felipe Weckx <felipe@weckx.net>
|
|
||||||
Ruggero Vecchio <ruggero.vecchio@datev.it>
|
|
||||||
Denis Bernacci <dbernaci@hotmail.com>
|
|
||||||
Frode Langelo <frode@skytap.com>
|
|
||||||
Daryl Borth <dborth@gmail.com>
|
|
@ -24,10 +24,10 @@ the review process.
|
|||||||
|
|
||||||
The Guacamole source is maintained in git repositories hosted on GitHub:
|
The Guacamole source is maintained in git repositories hosted on GitHub:
|
||||||
|
|
||||||
https://github.com/apache/incubator-guacamole-client
|
https://github.com/apache/guacamole-client
|
||||||
https://github.com/apache/incubator-guacamole-manual
|
https://github.com/apache/guacamole-manual
|
||||||
https://github.com/apache/incubator-guacamole-server
|
https://github.com/apache/guacamole-server
|
||||||
https://github.com/apache/incubator-guacamole-website
|
https://github.com/apache/guacamole-website
|
||||||
|
|
||||||
To make your changes, fork the applicable repositories and make commits
|
To make your changes, fork the applicable repositories and make commits
|
||||||
to a topic branch in your fork. Commits should be made in logical units
|
to a topic branch in your fork. Commits should be made in logical units
|
||||||
|
18
ChangeLog
18
ChangeLog
@ -1,18 +0,0 @@
|
|||||||
2013-08-26 Michael Jumper <mike.jumper@guac-dev.org>
|
|
||||||
|
|
||||||
* Experimental sound support for VNC (ticket #369)
|
|
||||||
* Improved handling of frame flush (ticket #380)
|
|
||||||
* SSL transport for guacd (ticket #371)
|
|
||||||
* Fix segfault in RDP disconnect (ticket #385)
|
|
||||||
* Add security options to RDP (ticket #190)
|
|
||||||
|
|
||||||
2013-07-02 Michael Jumper <mike.jumper@guac-dev.org>
|
|
||||||
|
|
||||||
* Optional threadsafety in guac_socket
|
|
||||||
* Printing support in for RDP (ticket #110)
|
|
||||||
* Fix ENABLE_OGG bug (ticket #355)
|
|
||||||
|
|
||||||
2013-06-06 Michael Jumper <mike.jumper@guac-dev.org>
|
|
||||||
|
|
||||||
* Created new repository layout
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
Apache Guacamole is an effort undergoing incubation at The Apache Software
|
|
||||||
Foundation (ASF). Incubation is required of all newly accepted projects until a
|
|
||||||
further review indicates that the infrastructure, communications, and decision
|
|
||||||
making process have stabilized in a manner consistent with other successful ASF
|
|
||||||
projects. While incubation status is not necessarily a reflection of the
|
|
||||||
completeness or stability of the code, it does indicate that the project has
|
|
||||||
yet to be fully endorsed by the ASF.
|
|
222
Dockerfile
222
Dockerfile
@ -21,69 +21,181 @@
|
|||||||
# Dockerfile for guacamole-server
|
# Dockerfile for guacamole-server
|
||||||
#
|
#
|
||||||
|
|
||||||
# Start from CentOS base image
|
# The Alpine Linux image that should be used as the basis for the guacd image
|
||||||
FROM centos:centos7
|
ARG ALPINE_BASE_IMAGE=latest
|
||||||
MAINTAINER Michael Jumper <mike.jumper@guac-dev.org>
|
FROM alpine:${ALPINE_BASE_IMAGE} AS builder
|
||||||
|
|
||||||
# Environment variables
|
# Install build dependencies
|
||||||
ENV \
|
RUN apk add --no-cache \
|
||||||
BUILD_DIR=/tmp/guacd-docker-BUILD \
|
|
||||||
LC_ALL=en_US.UTF-8 \
|
|
||||||
RUNTIME_DEPENDENCIES=" \
|
|
||||||
cairo \
|
|
||||||
dejavu-sans-mono-fonts \
|
|
||||||
freerdp \
|
|
||||||
freerdp-plugins \
|
|
||||||
ghostscript \
|
|
||||||
libjpeg-turbo \
|
|
||||||
libssh2 \
|
|
||||||
liberation-mono-fonts \
|
|
||||||
libtelnet \
|
|
||||||
libvorbis \
|
|
||||||
libvncserver \
|
|
||||||
libwebp \
|
|
||||||
pango \
|
|
||||||
pulseaudio-libs \
|
|
||||||
terminus-fonts \
|
|
||||||
uuid" \
|
|
||||||
BUILD_DEPENDENCIES=" \
|
|
||||||
autoconf \
|
autoconf \
|
||||||
automake \
|
automake \
|
||||||
cairo-devel \
|
build-base \
|
||||||
freerdp-devel \
|
cairo-dev \
|
||||||
gcc \
|
cmake \
|
||||||
libjpeg-turbo-devel \
|
git \
|
||||||
libssh2-devel \
|
grep \
|
||||||
|
libjpeg-turbo-dev \
|
||||||
|
libpng-dev \
|
||||||
libtool \
|
libtool \
|
||||||
libtelnet-devel \
|
libwebp-dev \
|
||||||
libvorbis-devel \
|
|
||||||
libvncserver-devel \
|
|
||||||
libwebp-devel \
|
|
||||||
make \
|
make \
|
||||||
pango-devel \
|
openssl-dev \
|
||||||
pulseaudio-libs-devel \
|
pango-dev \
|
||||||
uuid-devel"
|
pulseaudio-dev \
|
||||||
|
util-linux-dev
|
||||||
# Bring environment up-to-date and install guacamole-server dependencies
|
|
||||||
RUN yum -y update && \
|
|
||||||
yum -y install epel-release && \
|
|
||||||
yum -y install $RUNTIME_DEPENDENCIES && \
|
|
||||||
yum clean all
|
|
||||||
|
|
||||||
# Add configuration scripts
|
|
||||||
COPY src/guacd-docker/bin /opt/guacd/bin/
|
|
||||||
|
|
||||||
# Copy source to container for sake of build
|
# Copy source to container for sake of build
|
||||||
COPY . "$BUILD_DIR"
|
ARG BUILD_DIR=/tmp/guacamole-server
|
||||||
|
COPY . ${BUILD_DIR}
|
||||||
|
|
||||||
# Build guacamole-server from local source
|
#
|
||||||
RUN yum -y install $BUILD_DEPENDENCIES && \
|
# Base directory for installed build artifacts.
|
||||||
/opt/guacd/bin/build-guacd.sh "$BUILD_DIR" && \
|
#
|
||||||
rm -Rf "$BUILD_DIR" && \
|
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||||
yum -y autoremove $BUILD_DEPENDENCIES && \
|
# duplicated in an ARG in the second stage of the build.
|
||||||
yum clean all
|
#
|
||||||
|
ARG PREFIX_DIR=/opt/guacamole
|
||||||
|
|
||||||
|
#
|
||||||
|
# Automatically select the latest versions of each core protocol support
|
||||||
|
# library (these can be overridden at build time if a specific version is
|
||||||
|
# needed)
|
||||||
|
#
|
||||||
|
ARG WITH_FREERDP='2(\.\d+)+'
|
||||||
|
ARG WITH_LIBSSH2='libssh2-\d+(\.\d+)+'
|
||||||
|
ARG WITH_LIBTELNET='\d+(\.\d+)+'
|
||||||
|
ARG WITH_LIBVNCCLIENT='LibVNCServer-\d+(\.\d+)+'
|
||||||
|
ARG WITH_LIBWEBSOCKETS='v\d+(\.\d+)+'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Default build options for each core protocol support library, as well as
|
||||||
|
# guacamole-server itself (these can be overridden at build time if different
|
||||||
|
# options are needed)
|
||||||
|
#
|
||||||
|
|
||||||
|
ARG FREERDP_OPTS="\
|
||||||
|
-DBUILTIN_CHANNELS=OFF \
|
||||||
|
-DCHANNEL_URBDRC=OFF \
|
||||||
|
-DWITH_ALSA=OFF \
|
||||||
|
-DWITH_CAIRO=ON \
|
||||||
|
-DWITH_CHANNELS=ON \
|
||||||
|
-DWITH_CLIENT=ON \
|
||||||
|
-DWITH_CUPS=OFF \
|
||||||
|
-DWITH_DIRECTFB=OFF \
|
||||||
|
-DWITH_FFMPEG=OFF \
|
||||||
|
-DWITH_GSM=OFF \
|
||||||
|
-DWITH_GSSAPI=OFF \
|
||||||
|
-DWITH_IPP=OFF \
|
||||||
|
-DWITH_JPEG=ON \
|
||||||
|
-DWITH_LIBSYSTEMD=OFF \
|
||||||
|
-DWITH_MANPAGES=OFF \
|
||||||
|
-DWITH_OPENH264=OFF \
|
||||||
|
-DWITH_OPENSSL=ON \
|
||||||
|
-DWITH_OSS=OFF \
|
||||||
|
-DWITH_PCSC=OFF \
|
||||||
|
-DWITH_PULSE=OFF \
|
||||||
|
-DWITH_SERVER=OFF \
|
||||||
|
-DWITH_SERVER_INTERFACE=OFF \
|
||||||
|
-DWITH_SHADOW_MAC=OFF \
|
||||||
|
-DWITH_SHADOW_X11=OFF \
|
||||||
|
-DWITH_SSE2=ON \
|
||||||
|
-DWITH_WAYLAND=OFF \
|
||||||
|
-DWITH_X11=OFF \
|
||||||
|
-DWITH_X264=OFF \
|
||||||
|
-DWITH_XCURSOR=ON \
|
||||||
|
-DWITH_XEXT=ON \
|
||||||
|
-DWITH_XI=OFF \
|
||||||
|
-DWITH_XINERAMA=OFF \
|
||||||
|
-DWITH_XKBFILE=ON \
|
||||||
|
-DWITH_XRENDER=OFF \
|
||||||
|
-DWITH_XTEST=OFF \
|
||||||
|
-DWITH_XV=OFF \
|
||||||
|
-DWITH_ZLIB=ON"
|
||||||
|
|
||||||
|
ARG GUACAMOLE_SERVER_OPTS="\
|
||||||
|
--disable-guaclog"
|
||||||
|
|
||||||
|
ARG LIBSSH2_OPTS="\
|
||||||
|
-DBUILD_EXAMPLES=OFF \
|
||||||
|
-DBUILD_SHARED_LIBS=ON"
|
||||||
|
|
||||||
|
ARG LIBTELNET_OPTS="\
|
||||||
|
--disable-static \
|
||||||
|
--disable-util"
|
||||||
|
|
||||||
|
ARG LIBVNCCLIENT_OPTS=""
|
||||||
|
|
||||||
|
ARG LIBWEBSOCKETS_OPTS="\
|
||||||
|
-DDISABLE_WERROR=ON \
|
||||||
|
-DLWS_WITHOUT_SERVER=ON \
|
||||||
|
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_CLIENT=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_PING=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_SERVER=ON \
|
||||||
|
-DLWS_WITHOUT_TEST_SERVER_EXTPOLL=ON \
|
||||||
|
-DLWS_WITH_STATIC=OFF"
|
||||||
|
|
||||||
|
# Build guacamole-server and its core protocol library dependencies
|
||||||
|
RUN ${BUILD_DIR}/src/guacd-docker/bin/build-all.sh
|
||||||
|
|
||||||
|
# Record the packages of all runtime library dependencies
|
||||||
|
RUN ${BUILD_DIR}/src/guacd-docker/bin/list-dependencies.sh \
|
||||||
|
${PREFIX_DIR}/sbin/guacd \
|
||||||
|
${PREFIX_DIR}/lib/libguac-client-*.so \
|
||||||
|
${PREFIX_DIR}/lib/freerdp2/*guac*.so \
|
||||||
|
> ${PREFIX_DIR}/DEPENDENCIES
|
||||||
|
|
||||||
|
# Use same Alpine version as the base for the runtime image
|
||||||
|
FROM alpine:${ALPINE_BASE_IMAGE}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Base directory for installed build artifacts. See also the
|
||||||
|
# CMD directive at the end of this build stage.
|
||||||
|
#
|
||||||
|
# NOTE: Due to limitations of the Docker image build process, this value is
|
||||||
|
# duplicated in an ARG in the first stage of the build.
|
||||||
|
#
|
||||||
|
ARG PREFIX_DIR=/opt/guacamole
|
||||||
|
|
||||||
|
# Runtime environment
|
||||||
|
ENV LC_ALL=C.UTF-8
|
||||||
|
ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib
|
||||||
|
ENV GUACD_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Copy build artifacts into this stage
|
||||||
|
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
|
||||||
|
|
||||||
|
# Bring runtime environment up to date and install runtime dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
ghostscript \
|
||||||
|
netcat-openbsd \
|
||||||
|
shadow \
|
||||||
|
terminus-font \
|
||||||
|
ttf-dejavu \
|
||||||
|
ttf-liberation \
|
||||||
|
util-linux-login && \
|
||||||
|
xargs apk add --no-cache < ${PREFIX_DIR}/DEPENDENCIES
|
||||||
|
|
||||||
|
# Checks the operating status every 5 minutes with a timeout of 5 seconds
|
||||||
|
HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
|
||||||
|
|
||||||
|
# Create a new user guacd
|
||||||
|
ARG UID=1000
|
||||||
|
ARG GID=1000
|
||||||
|
RUN groupadd --gid $GID guacd
|
||||||
|
RUN useradd --system --create-home --shell /sbin/nologin --uid $UID --gid $GID guacd
|
||||||
|
|
||||||
|
# Run with user guacd
|
||||||
|
USER guacd
|
||||||
|
|
||||||
|
# Expose the default listener port
|
||||||
|
EXPOSE 4822
|
||||||
|
|
||||||
# Start guacd, listening on port 0.0.0.0:4822
|
# Start guacd, listening on port 0.0.0.0:4822
|
||||||
EXPOSE 4822
|
#
|
||||||
CMD [ "/usr/local/sbin/guacd", "-b", "0.0.0.0", "-f" ]
|
# Note the path here MUST correspond to the value specified in the
|
||||||
|
# PREFIX_DIR build argument.
|
||||||
|
#
|
||||||
|
CMD /opt/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f
|
||||||
|
|
||||||
|
31
Makefile.am
31
Makefile.am
@ -16,30 +16,34 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
# Subprojects
|
# Subprojects
|
||||||
DIST_SUBDIRS = \
|
DIST_SUBDIRS = \
|
||||||
src/libguac \
|
src/libguac \
|
||||||
src/libguacd \
|
|
||||||
src/common \
|
src/common \
|
||||||
src/common-ssh \
|
src/common-ssh \
|
||||||
src/terminal \
|
src/terminal \
|
||||||
src/guacd \
|
src/guacd \
|
||||||
src/guacenc \
|
src/guacenc \
|
||||||
|
src/guaclog \
|
||||||
src/pulse \
|
src/pulse \
|
||||||
|
src/protocols/kubernetes \
|
||||||
src/protocols/rdp \
|
src/protocols/rdp \
|
||||||
src/protocols/ssh \
|
src/protocols/ssh \
|
||||||
src/protocols/telnet \
|
src/protocols/telnet \
|
||||||
src/protocols/vnc \
|
src/protocols/vnc
|
||||||
tests
|
|
||||||
|
|
||||||
SUBDIRS = \
|
SUBDIRS = \
|
||||||
src/libguac \
|
src/libguac \
|
||||||
src/common \
|
src/common
|
||||||
src/libguacd \
|
|
||||||
tests
|
|
||||||
|
|
||||||
if ENABLE_COMMON_SSH
|
if ENABLE_COMMON_SSH
|
||||||
SUBDIRS += src/common-ssh
|
SUBDIRS += src/common-ssh
|
||||||
@ -53,6 +57,10 @@ if ENABLE_PULSE
|
|||||||
SUBDIRS += src/pulse
|
SUBDIRS += src/pulse
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_KUBERNETES
|
||||||
|
SUBDIRS += src/protocols/kubernetes
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_RDP
|
if ENABLE_RDP
|
||||||
SUBDIRS += src/protocols/rdp
|
SUBDIRS += src/protocols/rdp
|
||||||
endif
|
endif
|
||||||
@ -77,14 +85,19 @@ if ENABLE_GUACENC
|
|||||||
SUBDIRS += src/guacenc
|
SUBDIRS += src/guacenc
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_GUACLOG
|
||||||
|
SUBDIRS += src/guaclog
|
||||||
|
endif
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
.dockerignore \
|
.dockerignore \
|
||||||
CONTRIBUTING \
|
CONTRIBUTING \
|
||||||
DISCLAIMER \
|
|
||||||
Dockerfile \
|
Dockerfile \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
NOTICE \
|
NOTICE \
|
||||||
bin/guacctl \
|
bin/guacctl \
|
||||||
doc/Doxyfile \
|
doc/libguac/Doxyfile.in \
|
||||||
src/guacd-docker
|
doc/libguac-terminal/Doxyfile.in \
|
||||||
|
src/guacd-docker \
|
||||||
|
util/generate-test-runner.pl
|
||||||
|
|
||||||
|
2
NOTICE
2
NOTICE
@ -1,5 +1,5 @@
|
|||||||
Apache Guacamole
|
Apache Guacamole
|
||||||
Copyright 2016 The Apache Software Foundation
|
Copyright 2020 The Apache Software Foundation
|
||||||
|
|
||||||
This product includes software developed at
|
This product includes software developed at
|
||||||
The Apache Software Foundation (http://www.apache.org/).
|
The Apache Software Foundation (http://www.apache.org/).
|
||||||
|
6
README
6
README
@ -8,11 +8,11 @@ technical users intending to compile parts of Apache Guacamole themselves.
|
|||||||
|
|
||||||
Source archives are available from the downloads section of the project website:
|
Source archives are available from the downloads section of the project website:
|
||||||
|
|
||||||
http://guacamole.incubator.apache.org/
|
http://guacamole.apache.org/
|
||||||
|
|
||||||
A full manual is available as well:
|
A full manual is available as well:
|
||||||
|
|
||||||
http://guacamole.incubator.apache.org/doc/gug/
|
http://guacamole.apache.org/doc/gug/
|
||||||
|
|
||||||
|
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
@ -25,7 +25,7 @@ libraries.
|
|||||||
|
|
||||||
guacd is the Guacamole proxy daemon used by the Guacamole web application and
|
guacd is the Guacamole proxy daemon used by the Guacamole web application and
|
||||||
framework. As JavaScript cannot handle binary protocols (like VNC and remote
|
framework. As JavaScript cannot handle binary protocols (like VNC and remote
|
||||||
desktop) efficiently, a new test-based protocol was developed which would
|
desktop) efficiently, a new text-based protocol was developed which would
|
||||||
contain a common superset of the operations needed for efficient remote desktop
|
contain a common superset of the operations needed for efficient remote desktop
|
||||||
access, but would be easy for JavaScript programs to process. guacd is the
|
access, but would be easy for JavaScript programs to process. guacd is the
|
||||||
proxy which translates between arbitrary protocols and the Guacamole protocol.
|
proxy which translates between arbitrary protocols and the Guacamole protocol.
|
||||||
|
89
README-unit-testing.md
Normal file
89
README-unit-testing.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
Unit testing and guacamole-server
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Unit tests within guacamole-server are implemented using the following:
|
||||||
|
|
||||||
|
* automake, which allows arbitrary tests to be declared within `Makefile.am`
|
||||||
|
and uses `make check` to run those tests.
|
||||||
|
* CUnit (libcunit), a unit testing framework.
|
||||||
|
* `util/generate-test-runner.pl`, a Perl script which generates a test runner
|
||||||
|
written in C which leverages CUnit, running the unit tests declared in each
|
||||||
|
of the given `.c` files. The generated test runner produces output in [TAP
|
||||||
|
format](https://testanything.org/) which is consumed by the TAP test driver
|
||||||
|
provided by automake.
|
||||||
|
|
||||||
|
Writing unit tests
|
||||||
|
------------------
|
||||||
|
|
||||||
|
All unit tests should be within reasonably-isolated C source files, with each
|
||||||
|
logical test having its own function of the form:
|
||||||
|
|
||||||
|
void test_SUITENAME__TESTNAME() {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
where `TESTNAME` is the arbitrary name of the test and `SUITENAME` is the
|
||||||
|
arbitrary name of the test suite that this test belongs to.
|
||||||
|
|
||||||
|
**This naming convention is required by `generate-test-runner.pl`.** Absolutely
|
||||||
|
all tests MUST follow the above convention if they are to be picked up and
|
||||||
|
organized by the test runner generation script. Functions which are not tests
|
||||||
|
MUST NOT follow the above convention so that they are _not_ picked up mistakenly
|
||||||
|
by the test runner generator as if they were tests.
|
||||||
|
|
||||||
|
The `Makefile.am` for a subproject which contains such tests is typically
|
||||||
|
modified to contain a sections like the following:
|
||||||
|
|
||||||
|
#
|
||||||
|
# Unit tests for myproj
|
||||||
|
#
|
||||||
|
|
||||||
|
check_PROGRAMS = test_myproj
|
||||||
|
TESTS = $(check_PROGRAMS)
|
||||||
|
|
||||||
|
test_myproj_SOURCES = \
|
||||||
|
...all source files...
|
||||||
|
|
||||||
|
test_myproj_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic \
|
||||||
|
...other flags...
|
||||||
|
|
||||||
|
test_myproj_LDADD = \
|
||||||
|
...libraries...
|
||||||
|
|
||||||
|
#
|
||||||
|
# Autogenerate test runner
|
||||||
|
#
|
||||||
|
|
||||||
|
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
|
||||||
|
CLEANFILES = _generated_runner.c
|
||||||
|
|
||||||
|
_generated_runner.c: $(test_myproj_SOURCES)
|
||||||
|
$(AM_V_GEN) $(GEN_RUNNER) $(test_myproj_SOURCES) > $@
|
||||||
|
|
||||||
|
nodist_test_libguac_SOURCES = \
|
||||||
|
_generated_runner.c
|
||||||
|
|
||||||
|
# Use automake's TAP test driver for running any tests
|
||||||
|
LOG_DRIVER = \
|
||||||
|
env AM_TAP_AWK='$(AWK)' \
|
||||||
|
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
||||||
|
|
||||||
|
The above declares ...
|
||||||
|
|
||||||
|
* ... that a binary, `test_myproj` should be built from the given sources.
|
||||||
|
Note that `test_myproj_SOURCES` contains only the source which was actually
|
||||||
|
written by hand while `nodist_test_myproj_SOURCES` contains only the source
|
||||||
|
which was generated by `generate-test-runner.pl`.
|
||||||
|
* ... that this `test_myproj` binary should be run to test this project when
|
||||||
|
`make check` is run, and that automake's TAP driver should be used to
|
||||||
|
consume its output.
|
||||||
|
* ... that the `_generated_runner.c` source file is generated dynamically
|
||||||
|
(through running `generate-test-runner.pl` on all non-generated test source)
|
||||||
|
and should not be distributed as part of the source archive.
|
||||||
|
|
||||||
|
With tests following the above naming convention in place, and with the
|
||||||
|
necessary changes made to the applicable `Makefile.am`, all tests will be
|
||||||
|
run automatically when `make check` is run.
|
||||||
|
|
58
bin/guacctl
58
bin/guacctl
@ -90,6 +90,18 @@ send_close_pipe_stream() {
|
|||||||
printf "\033]482203;\007"
|
printf "\033]482203;\007"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
## Sends the Guacamole-specific console code for resizing the scrollback
|
||||||
|
## buffer.
|
||||||
|
##
|
||||||
|
## @param ROWS
|
||||||
|
## The number of rows that the scrollback buffer should contain.
|
||||||
|
##
|
||||||
|
send_resize_scrollback() {
|
||||||
|
ROWS="$1"
|
||||||
|
printf "\033]482204;%s\007" "$ROWS"
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
## Prints the given error text to STDERR.
|
## Prints the given error text to STDERR.
|
||||||
##
|
##
|
||||||
@ -105,7 +117,7 @@ error() {
|
|||||||
##
|
##
|
||||||
usage() {
|
usage() {
|
||||||
cat >&2 <<END
|
cat >&2 <<END
|
||||||
guacctl 0.9.11-incubating, Guacamole terminal session control utility.
|
guacctl 1.5.0, Apache Guacamole terminal session control utility.
|
||||||
Usage: guacctl [OPTION] [FILE or NAME]...
|
Usage: guacctl [OPTION] [FILE or NAME]...
|
||||||
|
|
||||||
-d, --download download each of the files listed.
|
-d, --download download each of the files listed.
|
||||||
@ -115,6 +127,8 @@ Usage: guacctl [OPTION] [FILE or NAME]...
|
|||||||
name.
|
name.
|
||||||
-c, --close-pipe close any existing pipe stream and redirect output
|
-c, --close-pipe close any existing pipe stream and redirect output
|
||||||
back to the terminal emulator.
|
back to the terminal emulator.
|
||||||
|
-S, --scrollback request that the scrollback buffer be limited to the
|
||||||
|
given number of rows.
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +257,39 @@ close_pipe_stream() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
## Resizes the scrollback buffer to the given number of rows.
|
||||||
|
##
|
||||||
|
## @param ...
|
||||||
|
## The number of rows that should be contained within the scrollback
|
||||||
|
## buffer, as provided to guacctl.
|
||||||
|
##
|
||||||
|
resize_scrollback() {
|
||||||
|
|
||||||
|
#
|
||||||
|
# Validate arguments
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
error "No row count specified."
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
error "Only one row count may be given."
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Send code for resizing scrollback
|
||||||
|
#
|
||||||
|
|
||||||
|
ROWS="$1"
|
||||||
|
send_resize_scrollback "$ROWS"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Get script name
|
# Get script name
|
||||||
#
|
#
|
||||||
@ -300,6 +347,15 @@ case "$1" in
|
|||||||
close_pipe_stream "$@"
|
close_pipe_stream "$@"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Resize scrollback
|
||||||
|
#
|
||||||
|
|
||||||
|
"--scrollback"|"-S")
|
||||||
|
shift
|
||||||
|
resize_scrollback "$@"
|
||||||
|
;;
|
||||||
|
|
||||||
#
|
#
|
||||||
# Show usage info if options are invalid
|
# Show usage info if options are invalid
|
||||||
#
|
#
|
||||||
|
1025
configure.ac
1025
configure.ac
File diff suppressed because it is too large
Load Diff
58
doc/libguac-terminal/Doxyfile.in
Normal file
58
doc/libguac-terminal/Doxyfile.in
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Project name / version
|
||||||
|
#
|
||||||
|
|
||||||
|
PROJECT_NAME = libguac-terminal
|
||||||
|
PROJECT_NUMBER = @PACKAGE_VERSION@
|
||||||
|
|
||||||
|
#
|
||||||
|
# Warn about undocumented parameters and return values, but do not fill output
|
||||||
|
# with verbose progress info.
|
||||||
|
#
|
||||||
|
|
||||||
|
QUIET = YES
|
||||||
|
WARN_NO_PARAMDOC = YES
|
||||||
|
|
||||||
|
#
|
||||||
|
# Output format
|
||||||
|
#
|
||||||
|
|
||||||
|
ALPHABETICAL_INDEX = YES
|
||||||
|
GENERATE_HTML = YES
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||||
|
OUTPUT_DIRECTORY = doxygen-output
|
||||||
|
RECURSIVE = YES
|
||||||
|
SHOW_INCLUDE_FILES = NO
|
||||||
|
|
||||||
|
#
|
||||||
|
# Input format
|
||||||
|
#
|
||||||
|
|
||||||
|
CASE_SENSE_NAMES = YES
|
||||||
|
FILE_PATTERNS = *.h
|
||||||
|
STRIP_FROM_PATH = ../../src/terminal
|
||||||
|
INPUT = ../../src/terminal/terminal/terminal.h
|
||||||
|
JAVADOC_AUTOBRIEF = YES
|
||||||
|
TAB_SIZE = 4
|
||||||
|
TYPEDEF_HIDES_STRUCT = YES
|
||||||
|
|
@ -22,7 +22,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
PROJECT_NAME = libguac
|
PROJECT_NAME = libguac
|
||||||
PROJECT_NUMBER = 0.9.11-incubating
|
PROJECT_NUMBER = @PACKAGE_VERSION@
|
||||||
|
|
||||||
#
|
#
|
||||||
# Warn about undocumented parameters and return values, but do not fill output
|
# Warn about undocumented parameters and return values, but do not fill output
|
||||||
@ -52,9 +52,9 @@ SHOW_INCLUDE_FILES = NO
|
|||||||
CASE_SENSE_NAMES = YES
|
CASE_SENSE_NAMES = YES
|
||||||
EXCLUDE_SYMBOLS = __* guac_palette*
|
EXCLUDE_SYMBOLS = __* guac_palette*
|
||||||
FILE_PATTERNS = *.h
|
FILE_PATTERNS = *.h
|
||||||
INPUT = ../src/libguac/guacamole
|
INPUT = ../../src/libguac/guacamole
|
||||||
JAVADOC_AUTOBRIEF = YES
|
JAVADOC_AUTOBRIEF = YES
|
||||||
STRIP_FROM_PATH = ../src/libguac
|
STRIP_FROM_PATH = ../../src/libguac
|
||||||
TAB_SIZE = 4
|
TAB_SIZE = 4
|
||||||
TYPEDEF_HIDES_STRUCT = YES
|
TYPEDEF_HIDES_STRUCT = YES
|
||||||
|
|
5
src/common-ssh/.gitignore
vendored
Normal file
5
src/common-ssh/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
# Auto-generated test runner and binary
|
||||||
|
_generated_runner.c
|
||||||
|
test_common_ssh
|
||||||
|
|
@ -16,25 +16,32 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libguac_common_ssh.la
|
noinst_LTLIBRARIES = libguac_common_ssh.la
|
||||||
|
SUBDIRS = . tests
|
||||||
|
|
||||||
libguac_common_ssh_la_SOURCES = \
|
libguac_common_ssh_la_SOURCES = \
|
||||||
guac_sftp.c \
|
buffer.c \
|
||||||
guac_ssh.c \
|
sftp.c \
|
||||||
guac_ssh_buffer.c \
|
ssh.c \
|
||||||
guac_ssh_key.c \
|
key.c \
|
||||||
guac_ssh_user.c
|
user.c
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
guac_sftp.h \
|
common-ssh/buffer.h \
|
||||||
guac_ssh.h \
|
common-ssh/key.h \
|
||||||
guac_ssh_buffer.h \
|
common-ssh/sftp.h \
|
||||||
guac_ssh_key.h \
|
common-ssh/ssh.h \
|
||||||
guac_ssh_user.h
|
common-ssh/user.h
|
||||||
|
|
||||||
libguac_common_ssh_la_CFLAGS = \
|
libguac_common_ssh_la_CFLAGS = \
|
||||||
-Werror -Wall -pedantic \
|
-Werror -Wall -pedantic \
|
||||||
|
@ -54,7 +54,7 @@ void guac_common_ssh_buffer_write_data(char** buffer, const char* data,
|
|||||||
*buffer += length;
|
*buffer += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value) {
|
void guac_common_ssh_buffer_write_bignum(char** buffer, const BIGNUM* value) {
|
||||||
|
|
||||||
unsigned char* bn_buffer;
|
unsigned char* bn_buffer;
|
||||||
int length;
|
int length;
|
@ -76,7 +76,7 @@ void guac_common_ssh_buffer_write_string(char** buffer, const char* string,
|
|||||||
* @param value
|
* @param value
|
||||||
* The value to write.
|
* The value to write.
|
||||||
*/
|
*/
|
||||||
void guac_common_ssh_buffer_write_bignum(char** buffer, BIGNUM* value);
|
void guac_common_ssh_buffer_write_bignum(char** buffer, const BIGNUM* value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given data the given buffer, advancing the buffer pointer by the
|
* Writes the given data the given buffer, advancing the buffer pointer by the
|
152
src/common-ssh/common-ssh/key.h
Normal file
152
src/common-ssh/common-ssh/key.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUAC_COMMON_SSH_KEY_H
|
||||||
|
#define GUAC_COMMON_SSH_KEY_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <libssh2.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenSSH v1 private keys are PEM-wrapped base64-encoded blobs. The encoded data begins with:
|
||||||
|
* "openssh-key-v1\0"
|
||||||
|
*/
|
||||||
|
#define OPENSSH_V1_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEA"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base64-encoded prefix indicating an OpenSSH v1 private key is NOT protected by a
|
||||||
|
* passphrase. Specifically, it is the following data fields and values:
|
||||||
|
* pascal string: cipher name ("none")
|
||||||
|
* pascal string: kdf name ("none")
|
||||||
|
* pascal string: kdf params (NULL)
|
||||||
|
* 32-bit int: number of keys (1)
|
||||||
|
*/
|
||||||
|
#define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction of a key used for SSH authentication.
|
||||||
|
*/
|
||||||
|
typedef struct guac_common_ssh_key {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key, encoded as necessary for SSH.
|
||||||
|
*/
|
||||||
|
char* private_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of the private key, in bytes.
|
||||||
|
*/
|
||||||
|
int private_key_length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key's passphrase, if any.
|
||||||
|
*/
|
||||||
|
char *passphrase;
|
||||||
|
|
||||||
|
} guac_common_ssh_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new key containing the given private key data and specified
|
||||||
|
* passphrase. If unable to read the key, NULL is returned.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The base64-encoded data to decode when reading the key.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The length of the provided data, in bytes.
|
||||||
|
*
|
||||||
|
* @param passphrase
|
||||||
|
* The passphrase to use when decrypting the key, if any, or an empty
|
||||||
|
* string or NULL if no passphrase is needed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The decoded, decrypted private key, or NULL if the key could not be
|
||||||
|
* decoded.
|
||||||
|
*/
|
||||||
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||||
|
char* passphrase);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a statically-allocated string describing the most recent SSH key
|
||||||
|
* error.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A statically-allocated string describing the most recent SSH key error.
|
||||||
|
*/
|
||||||
|
const char* guac_common_ssh_key_error();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees all memory associated with the given key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* The key to free.
|
||||||
|
*/
|
||||||
|
void guac_common_ssh_key_free(guac_common_ssh_key* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the host key for the given hostname/port combination against
|
||||||
|
* one or more known_hosts entries. The known_host entries can either be a
|
||||||
|
* single host_key, provided by the client, or a set of known_hosts entries
|
||||||
|
* provided in the /etc/guacamole/ssh_known_hosts file. Failure to correctly
|
||||||
|
* load the known_hosts entries will result in a connection abort and a returned
|
||||||
|
* error code. A return code of zero indiciates that either no known_hosts entries
|
||||||
|
* were provided, or that the verification succeeded (match). Negative values
|
||||||
|
* indicate internal libssh2 error codes; positive values indicate a failure
|
||||||
|
* during verification of the host key against the known hosts.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* A pointer to the LIBSSH2_SESSION structure of the SSH connection already
|
||||||
|
* in progress.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The current guac_client instance for which the known_hosts checking is
|
||||||
|
* being performed.
|
||||||
|
*
|
||||||
|
* @param host_key
|
||||||
|
* The known host entry provided by the client. If this is non-null and not
|
||||||
|
* empty, it will be the only host key loaded and used for verification. If
|
||||||
|
* this is null or empty an attempt will be made to read the
|
||||||
|
* /etc/guacamole/ssh_known_hosts file and load entries from it.
|
||||||
|
*
|
||||||
|
* @param hostname
|
||||||
|
* The hostname or IP of the server that is being verified.
|
||||||
|
*
|
||||||
|
* @param port
|
||||||
|
* The port number of the server being verified.
|
||||||
|
*
|
||||||
|
* @param remote_hostkey
|
||||||
|
* The host key of the remote system being verified.
|
||||||
|
*
|
||||||
|
* @param remote_hostkey_len
|
||||||
|
* The length of the remote host key being verified
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The status of the known_hosts check. This will be zero if no entries
|
||||||
|
* are provided or if the match succeeds, negative to indicate internal
|
||||||
|
* libssh2 errors, or positive to indicate failures during host key
|
||||||
|
* checking.
|
||||||
|
*/
|
||||||
|
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
|
||||||
|
const char* host_key, const char* hostname, int port, const char* remote_hostkey,
|
||||||
|
const size_t remote_hostkey_len);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -21,7 +21,7 @@
|
|||||||
#define GUAC_COMMON_SSH_SFTP_H
|
#define GUAC_COMMON_SSH_SFTP_H
|
||||||
|
|
||||||
#include "common/json.h"
|
#include "common/json.h"
|
||||||
#include "guac_ssh.h"
|
#include "ssh.h"
|
||||||
|
|
||||||
#include <guacamole/object.h>
|
#include <guacamole/object.h>
|
||||||
#include <guacamole/user.h>
|
#include <guacamole/user.h>
|
||||||
@ -33,6 +33,11 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_COMMON_SSH_SFTP_MAX_PATH 2048
|
#define GUAC_COMMON_SSH_SFTP_MAX_PATH 2048
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of path components per path.
|
||||||
|
*/
|
||||||
|
#define GUAC_COMMON_SSH_SFTP_MAX_DEPTH 1024
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of an SFTP-driven filesystem object. Unlike guac_object, this
|
* Representation of an SFTP-driven filesystem object. Unlike guac_object, this
|
||||||
* structure is not tied to any particular user.
|
* structure is not tied to any particular user.
|
||||||
@ -54,12 +59,27 @@ typedef struct guac_common_ssh_sftp_filesystem {
|
|||||||
*/
|
*/
|
||||||
LIBSSH2_SFTP* sftp_session;
|
LIBSSH2_SFTP* sftp_session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the directory to expose to the user as a filesystem object.
|
||||||
|
*/
|
||||||
|
char root_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path files will be sent to, if uploaded directly via a "file"
|
* The path files will be sent to, if uploaded directly via a "file"
|
||||||
* instruction.
|
* instruction.
|
||||||
*/
|
*/
|
||||||
char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If downloads from SFTP to the local browser should be disabled.
|
||||||
|
*/
|
||||||
|
int disable_download;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If uploads from the local browser to SFTP should be disabled.
|
||||||
|
*/
|
||||||
|
int disable_upload;
|
||||||
|
|
||||||
} guac_common_ssh_sftp_filesystem;
|
} guac_common_ssh_sftp_filesystem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,15 +123,29 @@ typedef struct guac_common_ssh_sftp_ls_state {
|
|||||||
* The session to use to provide SFTP. This session will automatically be
|
* The session to use to provide SFTP. This session will automatically be
|
||||||
* destroyed when this filesystem is destroyed.
|
* destroyed when this filesystem is destroyed.
|
||||||
*
|
*
|
||||||
|
* @param root_path
|
||||||
|
* The path accessible via SFTP to consider the root path of the filesystem
|
||||||
|
* exposed to the user. Only the contents of this path will be available
|
||||||
|
* via the filesystem object.
|
||||||
|
*
|
||||||
* @param name
|
* @param name
|
||||||
* The name to send as the name of the filesystem whenever it is exposed
|
* The name to send as the name of the filesystem whenever it is exposed
|
||||||
* to a user.
|
* to a user, or NULL to automatically generate a name from the provided
|
||||||
|
* root_path.
|
||||||
|
*
|
||||||
|
* @param disable_download
|
||||||
|
* Whether downloads from the SFTP share to the local browser should be
|
||||||
|
* disabled.
|
||||||
|
*
|
||||||
|
* @param disable_upload
|
||||||
|
* Whether uploads from the local browser to SFTP should be disabled.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* A new SFTP filesystem object, not yet exposed to users.
|
* A new SFTP filesystem object, not yet exposed to users.
|
||||||
*/
|
*/
|
||||||
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
|
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
|
||||||
guac_common_ssh_session* session, const char* name);
|
guac_common_ssh_session* session, const char* root_path,
|
||||||
|
const char* name, int disable_download, int disable_upload);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys the given filesystem object, disconnecting from SFTP and freeing
|
* Destroys the given filesystem object, disconnecting from SFTP and freeing
|
||||||
@ -238,5 +272,30 @@ int guac_common_ssh_sftp_handle_file_stream(
|
|||||||
void guac_common_ssh_sftp_set_upload_path(
|
void guac_common_ssh_sftp_set_upload_path(
|
||||||
guac_common_ssh_sftp_filesystem* filesystem, const char* path);
|
guac_common_ssh_sftp_filesystem* filesystem, const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an arbitrary absolute path, which may contain "..", ".", and
|
||||||
|
* backslashes, creates an equivalent absolute path which does NOT contain
|
||||||
|
* relative path components (".." or "."), backslashes, or empty path
|
||||||
|
* components. With the exception of paths referring to the root directory, the
|
||||||
|
* resulting path is guaranteed to not contain trailing slashes.
|
||||||
|
*
|
||||||
|
* Normalization will fail if the given path is not absolute, is too long, or
|
||||||
|
* contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
|
||||||
|
*
|
||||||
|
* @param fullpath
|
||||||
|
* The buffer to populate with the normalized path. The normalized path
|
||||||
|
* will not contain relative path components like ".." or ".", nor will it
|
||||||
|
* contain backslashes. This buffer MUST be at least
|
||||||
|
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* The absolute path to normalize.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if normalization succeeded, zero otherwise.
|
||||||
|
*/
|
||||||
|
int guac_common_ssh_sftp_normalize_path(char* fullpath,
|
||||||
|
const char* path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -20,11 +20,28 @@
|
|||||||
#ifndef GUAC_COMMON_SSH_H
|
#ifndef GUAC_COMMON_SSH_H
|
||||||
#define GUAC_COMMON_SSH_H
|
#define GUAC_COMMON_SSH_H
|
||||||
|
|
||||||
#include "guac_ssh_user.h"
|
#include "user.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <libssh2.h>
|
#include <libssh2.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for retrieving additional credentials.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The Guacamole Client associated with this need for additional
|
||||||
|
* credentials.
|
||||||
|
*
|
||||||
|
* @param cred_name
|
||||||
|
* The name of the credential being requested, which will be shared
|
||||||
|
* with the client in order to generate a meaningful prompt.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A newly-allocated string containing the credentials provided by
|
||||||
|
* the user, which must be freed by a call to free().
|
||||||
|
*/
|
||||||
|
typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An SSH session, backed by libssh2 and associated with a particular
|
* An SSH session, backed by libssh2 and associated with a particular
|
||||||
* Guacamole client.
|
* Guacamole client.
|
||||||
@ -51,6 +68,11 @@ typedef struct guac_common_ssh_session {
|
|||||||
*/
|
*/
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function to retrieve credentials.
|
||||||
|
*/
|
||||||
|
guac_ssh_credential_handler* credential_handler;
|
||||||
|
|
||||||
} guac_common_ssh_session;
|
} guac_common_ssh_session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,12 +115,31 @@ void guac_common_ssh_uninit();
|
|||||||
* @param user
|
* @param user
|
||||||
* The user to authenticate as, once connected.
|
* The user to authenticate as, once connected.
|
||||||
*
|
*
|
||||||
|
* @param keepalive
|
||||||
|
* How frequently the connection should send keepalive packets, in
|
||||||
|
* seconds. Zero disables keepalive packets, and 2 is the minimum
|
||||||
|
* configurable value.
|
||||||
|
*
|
||||||
|
* @param host_key
|
||||||
|
* The known public host key of the server, as provided by the client. If
|
||||||
|
* provided the identity of the server will be checked against this key,
|
||||||
|
* and a mis-match between this and the server identity will cause the
|
||||||
|
* connection to fail. If not provided, no checks will be done and the
|
||||||
|
* connection will proceed.
|
||||||
|
*
|
||||||
|
* @param credential_handler
|
||||||
|
* The handler function for retrieving additional credentials from the user
|
||||||
|
* as required by the SSH server, or NULL if the user will not be asked
|
||||||
|
* for additional credentials.
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
* A new SSH session if the connection and authentication succeed, or NULL
|
* A new SSH session if the connection and authentication succeed, or NULL
|
||||||
* if the connection or authentication were not successful.
|
* if the connection or authentication were not successful.
|
||||||
*/
|
*/
|
||||||
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
||||||
const char* hostname, const char* port, guac_common_ssh_user* user);
|
const char* hostname, const char* port, guac_common_ssh_user* user,
|
||||||
|
int keepalive, const char* host_key,
|
||||||
|
guac_ssh_credential_handler* credential_handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects and destroys the given SSH session, freeing all associated
|
* Disconnects and destroys the given SSH session, freeing all associated
|
@ -20,7 +20,7 @@
|
|||||||
#ifndef GUAC_COMMON_SSH_USER_H
|
#ifndef GUAC_COMMON_SSH_USER_H
|
||||||
#define GUAC_COMMON_SSH_USER_H
|
#define GUAC_COMMON_SSH_USER_H
|
||||||
|
|
||||||
#include "guac_ssh_key.h"
|
#include "key.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data describing an SSH user, including their credentials.
|
* Data describing an SSH user, including their credentials.
|
@ -1,217 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include "guac_ssh_buffer.h"
|
|
||||||
#include "guac_ssh_key.h"
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/bn.h>
|
|
||||||
#include <openssl/dsa.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/obj_mac.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
|
||||||
char* passphrase) {
|
|
||||||
|
|
||||||
guac_common_ssh_key* key;
|
|
||||||
BIO* key_bio;
|
|
||||||
|
|
||||||
char* public_key;
|
|
||||||
char* pos;
|
|
||||||
|
|
||||||
/* Create BIO for reading key from memory */
|
|
||||||
key_bio = BIO_new_mem_buf(data, length);
|
|
||||||
|
|
||||||
/* If RSA key, load RSA */
|
|
||||||
if (length > sizeof(SSH_RSA_KEY_HEADER)-1
|
|
||||||
&& memcmp(SSH_RSA_KEY_HEADER, data,
|
|
||||||
sizeof(SSH_RSA_KEY_HEADER)-1) == 0) {
|
|
||||||
|
|
||||||
RSA* rsa_key;
|
|
||||||
|
|
||||||
/* Read key */
|
|
||||||
rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase);
|
|
||||||
if (rsa_key == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* Allocate key */
|
|
||||||
key = malloc(sizeof(guac_common_ssh_key));
|
|
||||||
key->rsa = rsa_key;
|
|
||||||
|
|
||||||
/* Set type */
|
|
||||||
key->type = SSH_KEY_RSA;
|
|
||||||
|
|
||||||
/* Allocate space for public key */
|
|
||||||
public_key = malloc(4096);
|
|
||||||
pos = public_key;
|
|
||||||
|
|
||||||
/* Derive public key */
|
|
||||||
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->e);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, rsa_key->n);
|
|
||||||
|
|
||||||
/* Save public key to structure */
|
|
||||||
key->public_key = public_key;
|
|
||||||
key->public_key_length = pos - public_key;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If DSA key, load DSA */
|
|
||||||
else if (length > sizeof(SSH_DSA_KEY_HEADER)-1
|
|
||||||
&& memcmp(SSH_DSA_KEY_HEADER, data,
|
|
||||||
sizeof(SSH_DSA_KEY_HEADER)-1) == 0) {
|
|
||||||
|
|
||||||
DSA* dsa_key;
|
|
||||||
|
|
||||||
/* Read key */
|
|
||||||
dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase);
|
|
||||||
if (dsa_key == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* Allocate key */
|
|
||||||
key = malloc(sizeof(guac_common_ssh_key));
|
|
||||||
key->dsa = dsa_key;
|
|
||||||
|
|
||||||
/* Set type */
|
|
||||||
key->type = SSH_KEY_DSA;
|
|
||||||
|
|
||||||
/* Allocate space for public key */
|
|
||||||
public_key = malloc(4096);
|
|
||||||
pos = public_key;
|
|
||||||
|
|
||||||
/* Derive public key */
|
|
||||||
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->p);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->q);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->g);
|
|
||||||
guac_common_ssh_buffer_write_bignum(&pos, dsa_key->pub_key);
|
|
||||||
|
|
||||||
/* Save public key to structure */
|
|
||||||
key->public_key = public_key;
|
|
||||||
key->public_key_length = pos - public_key;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Otherwise, unsupported type */
|
|
||||||
else {
|
|
||||||
BIO_free(key_bio);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy private key to structure */
|
|
||||||
key->private_key_length = length;
|
|
||||||
key->private_key = malloc(length);
|
|
||||||
memcpy(key->private_key, data, length);
|
|
||||||
|
|
||||||
BIO_free(key_bio);
|
|
||||||
return key;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* guac_common_ssh_key_error() {
|
|
||||||
|
|
||||||
/* Return static error string */
|
|
||||||
return ERR_reason_error_string(ERR_get_error());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void guac_common_ssh_key_free(guac_common_ssh_key* key) {
|
|
||||||
|
|
||||||
/* Free key-specific data */
|
|
||||||
if (key->type == SSH_KEY_RSA)
|
|
||||||
RSA_free(key->rsa);
|
|
||||||
else if (key->type == SSH_KEY_DSA)
|
|
||||||
DSA_free(key->dsa);
|
|
||||||
|
|
||||||
free(key->private_key);
|
|
||||||
free(key->public_key);
|
|
||||||
free(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
|
||||||
int length, unsigned char* sig) {
|
|
||||||
|
|
||||||
const EVP_MD* md;
|
|
||||||
EVP_MD_CTX md_ctx;
|
|
||||||
|
|
||||||
unsigned char digest[EVP_MAX_MD_SIZE];
|
|
||||||
unsigned int dlen, len;
|
|
||||||
|
|
||||||
/* Get SHA1 digest */
|
|
||||||
if ((md = EVP_get_digestbynid(NID_sha1)) == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
/* Digest data */
|
|
||||||
EVP_DigestInit(&md_ctx, md);
|
|
||||||
EVP_DigestUpdate(&md_ctx, data, length);
|
|
||||||
EVP_DigestFinal(&md_ctx, digest, &dlen);
|
|
||||||
|
|
||||||
/* Sign with key */
|
|
||||||
switch (key->type) {
|
|
||||||
|
|
||||||
case SSH_KEY_RSA:
|
|
||||||
if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1)
|
|
||||||
return len;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SSH_KEY_DSA: {
|
|
||||||
|
|
||||||
DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa);
|
|
||||||
if (dsa_sig != NULL) {
|
|
||||||
|
|
||||||
/* Compute size of each half of signature */
|
|
||||||
int rlen = BN_num_bytes(dsa_sig->r);
|
|
||||||
int slen = BN_num_bytes(dsa_sig->s);
|
|
||||||
|
|
||||||
/* Ensure each number is within the required size */
|
|
||||||
if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
/* Init to all zeroes */
|
|
||||||
memset(sig, 0, DSA_SIG_SIZE);
|
|
||||||
|
|
||||||
/* Add R at the end of the first block of the signature */
|
|
||||||
BN_bn2bin(dsa_sig->r, sig + DSA_SIG_SIZE
|
|
||||||
- DSA_SIG_NUMBER_SIZE - rlen);
|
|
||||||
|
|
||||||
/* Add S at the end of the second block of the signature */
|
|
||||||
BN_bn2bin(dsa_sig->s, sig + DSA_SIG_SIZE - slen);
|
|
||||||
|
|
||||||
/* Done */
|
|
||||||
DSA_SIG_free(dsa_sig);
|
|
||||||
return DSA_SIG_SIZE;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef GUAC_COMMON_SSH_KEY_H
|
|
||||||
#define GUAC_COMMON_SSH_KEY_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <openssl/ossl_typ.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The expected header of RSA private keys.
|
|
||||||
*/
|
|
||||||
#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The expected header of DSA private keys.
|
|
||||||
*/
|
|
||||||
#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of single number within a DSA signature, in bytes.
|
|
||||||
*/
|
|
||||||
#define DSA_SIG_NUMBER_SIZE 20
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of a DSA signature, in bytes.
|
|
||||||
*/
|
|
||||||
#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of an SSH key.
|
|
||||||
*/
|
|
||||||
typedef enum guac_common_ssh_key_type {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RSA key.
|
|
||||||
*/
|
|
||||||
SSH_KEY_RSA,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DSA key.
|
|
||||||
*/
|
|
||||||
SSH_KEY_DSA
|
|
||||||
|
|
||||||
} guac_common_ssh_key_type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstraction of a key used for SSH authentication.
|
|
||||||
*/
|
|
||||||
typedef struct guac_common_ssh_key {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of this key.
|
|
||||||
*/
|
|
||||||
guac_common_ssh_key_type type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Underlying RSA private key, if any.
|
|
||||||
*/
|
|
||||||
RSA* rsa;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Underlying DSA private key, if any.
|
|
||||||
*/
|
|
||||||
DSA* dsa;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The associated public key, encoded as necessary for SSH.
|
|
||||||
*/
|
|
||||||
char* public_key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of the public key, in bytes.
|
|
||||||
*/
|
|
||||||
int public_key_length;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The private key, encoded as necessary for SSH.
|
|
||||||
*/
|
|
||||||
char* private_key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of the private key, in bytes.
|
|
||||||
*/
|
|
||||||
int private_key_length;
|
|
||||||
|
|
||||||
} guac_common_ssh_key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocates a new key containing the given private key data and specified
|
|
||||||
* passphrase. If unable to read the key, NULL is returned.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The base64-encoded data to decode when reading the key.
|
|
||||||
*
|
|
||||||
* @param length
|
|
||||||
* The length of the provided data, in bytes.
|
|
||||||
*
|
|
||||||
* @param passphrase
|
|
||||||
* The passphrase to use when decrypting the key, if any, or an empty
|
|
||||||
* string or NULL if no passphrase is needed.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The decoded, decrypted private key, or NULL if the key could not be
|
|
||||||
* decoded.
|
|
||||||
*/
|
|
||||||
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
|
||||||
char* passphrase);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a statically-allocated string describing the most recent SSH key
|
|
||||||
* error.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A statically-allocated string describing the most recent SSH key error.
|
|
||||||
*/
|
|
||||||
const char* guac_common_ssh_key_error();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees all memory associated with the given key.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* The key to free.
|
|
||||||
*/
|
|
||||||
void guac_common_ssh_key_free(guac_common_ssh_key* key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the given data using the given key, returning the length of the
|
|
||||||
* signature in bytes, or a value less than zero on error.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* The key to use when signing the given data.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The arbitrary data to sign.
|
|
||||||
*
|
|
||||||
* @param length
|
|
||||||
* The length of the arbitrary data being signed, in bytes.
|
|
||||||
*
|
|
||||||
* @param sig
|
|
||||||
* The buffer into which the signature should be written. The buffer must
|
|
||||||
* be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size
|
|
||||||
* is dependent only on key size, and is equal to the length of the
|
|
||||||
* modulus, in bytes.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes in the resulting signature.
|
|
||||||
*/
|
|
||||||
int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
|
|
||||||
int length, unsigned char* sig);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
248
src/common-ssh/key.c
Normal file
248
src/common-ssh/key.c
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "common-ssh/buffer.h"
|
||||||
|
#include "common-ssh/key.h"
|
||||||
|
|
||||||
|
#include <guacamole/string.h>
|
||||||
|
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/dsa.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/obj_mac.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for a PKCS#1/PKCS#8 ENCRYPTED marker.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The buffer to scan.
|
||||||
|
* @param length
|
||||||
|
* The length of the buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if the buffer contains the marker, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool is_pkcs_encrypted_key(char* data, int length) {
|
||||||
|
return guac_strnstr(data, "ENCRYPTED", length) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for a PEM header & initial base64-encoded data indicating this is an
|
||||||
|
* OpenSSH v1 key.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The buffer to scan.
|
||||||
|
* @param length
|
||||||
|
* The length of the buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if the buffer contains a private key, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool is_ssh_private_key(char* data, int length) {
|
||||||
|
if (length < sizeof(OPENSSH_V1_KEY_HEADER) - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !strncmp(data, OPENSSH_V1_KEY_HEADER, sizeof(OPENSSH_V1_KEY_HEADER) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assuming an offset into a key past the header, check for the base64-encoded
|
||||||
|
* data indicating this key is not protected by a passphrase.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The buffer to scan.
|
||||||
|
* @param length
|
||||||
|
* The length of the buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if the buffer contains an unencrypted key, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool is_ssh_key_unencrypted(char* data, int length) {
|
||||||
|
if (length < sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !strncmp(data, OPENSSH_V1_UNENCRYPTED_KEY, sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A passphrase is needed if the key is an encrypted PKCS#1/PKCS#8 key OR if
|
||||||
|
* the key is both an OpenSSH v1 key AND there isn't a marker indicating the
|
||||||
|
* key is unprotected.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The buffer to scan.
|
||||||
|
* @param length
|
||||||
|
* The length of the buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* True if the buffer contains a key needing a passphrase, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool is_passphrase_needed(char* data, int length) {
|
||||||
|
/* Is this an encrypted PKCS#1/PKCS#8 key? */
|
||||||
|
if (is_pkcs_encrypted_key(data, length)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is this an OpenSSH v1 key? */
|
||||||
|
if (is_ssh_private_key(data, length)) {
|
||||||
|
/* This is safe due to the check in is_ssh_private_key. */
|
||||||
|
data += sizeof(OPENSSH_V1_KEY_HEADER) - 1;
|
||||||
|
length -= sizeof(OPENSSH_V1_KEY_HEADER) - 1;
|
||||||
|
/* If this is NOT unprotected, we need a passphrase. */
|
||||||
|
if (!is_ssh_key_unencrypted(data, length)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||||
|
char* passphrase) {
|
||||||
|
|
||||||
|
/* Because libssh2 will do the actual key parsing (to let it deal with
|
||||||
|
* different key algorithms) we need to perform a heuristic here to check
|
||||||
|
* if a passphrase is needed. This could allow junk keys through that
|
||||||
|
* would never be able to auth. libssh2 should display errors to help
|
||||||
|
* admins track down malformed keys and delete or replace them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key));
|
||||||
|
|
||||||
|
/* Copy private key to structure */
|
||||||
|
key->private_key_length = length;
|
||||||
|
key->private_key = malloc(length);
|
||||||
|
memcpy(key->private_key, data, length);
|
||||||
|
key->passphrase = strdup(passphrase);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* guac_common_ssh_key_error() {
|
||||||
|
|
||||||
|
/* Return static error string */
|
||||||
|
return ERR_reason_error_string(ERR_get_error());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_common_ssh_key_free(guac_common_ssh_key* key) {
|
||||||
|
|
||||||
|
free(key->private_key);
|
||||||
|
free(key->passphrase);
|
||||||
|
free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
|
||||||
|
const char* host_key, const char* hostname, int port, const char* remote_hostkey,
|
||||||
|
const size_t remote_hostkey_len) {
|
||||||
|
|
||||||
|
LIBSSH2_KNOWNHOSTS* ssh_known_hosts = libssh2_knownhost_init(session);
|
||||||
|
int known_hosts = 0;
|
||||||
|
|
||||||
|
/* Add host key provided from settings */
|
||||||
|
if (host_key && strcmp(host_key, "") != 0) {
|
||||||
|
|
||||||
|
known_hosts = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key),
|
||||||
|
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
|
||||||
|
|
||||||
|
/* readline function returns 0 on success, so we increment to indicate a valid entry */
|
||||||
|
if (known_hosts == 0)
|
||||||
|
known_hosts++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */
|
||||||
|
else {
|
||||||
|
|
||||||
|
const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts";
|
||||||
|
if (access(guac_known_hosts, F_OK) != -1)
|
||||||
|
known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there's an error provided, abort connection and return that. */
|
||||||
|
if (known_hosts < 0) {
|
||||||
|
|
||||||
|
char* errmsg;
|
||||||
|
int errval = libssh2_session_last_error(session, &errmsg, NULL, 0);
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"Error %d trying to load SSH host keys: %s", errval, errmsg);
|
||||||
|
|
||||||
|
libssh2_knownhost_free(ssh_known_hosts);
|
||||||
|
return known_hosts;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No host keys were loaded, so we bail out checking and continue the connection. */
|
||||||
|
else if (known_hosts == 0) {
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING,
|
||||||
|
"No known host keys provided, host identity will not be verified.");
|
||||||
|
libssh2_knownhost_free(ssh_known_hosts);
|
||||||
|
return known_hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Check remote host key against known hosts */
|
||||||
|
int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port,
|
||||||
|
remote_hostkey, remote_hostkey_len,
|
||||||
|
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
|
||||||
|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* Deal with the return of the host key check */
|
||||||
|
switch (kh_check) {
|
||||||
|
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"Host key match found for %s", hostname);
|
||||||
|
break;
|
||||||
|
case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"Host key not found for %s.", hostname);
|
||||||
|
break;
|
||||||
|
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"Host key does not match known hosts entry for %s", hostname);
|
||||||
|
break;
|
||||||
|
case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
|
||||||
|
default:
|
||||||
|
guac_client_log(client, GUAC_LOG_ERROR,
|
||||||
|
"Host %s could not be checked against known hosts.",
|
||||||
|
hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the check value */
|
||||||
|
libssh2_knownhost_free(ssh_known_hosts);
|
||||||
|
return kh_check;
|
||||||
|
|
||||||
|
}
|
@ -17,13 +17,14 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "guac_sftp.h"
|
#include "common-ssh/sftp.h"
|
||||||
#include "guac_ssh.h"
|
#include "common-ssh/ssh.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/object.h>
|
#include <guacamole/object.h>
|
||||||
#include <guacamole/protocol.h>
|
#include <guacamole/protocol.h>
|
||||||
#include <guacamole/socket.h>
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/string.h>
|
||||||
#include <guacamole/user.h>
|
#include <guacamole/user.h>
|
||||||
#include <libssh2.h>
|
#include <libssh2.h>
|
||||||
|
|
||||||
@ -32,6 +33,74 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
int guac_common_ssh_sftp_normalize_path(char* fullpath,
|
||||||
|
const char* path) {
|
||||||
|
|
||||||
|
int path_depth = 0;
|
||||||
|
const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH];
|
||||||
|
|
||||||
|
/* If original path is not absolute, normalization fails */
|
||||||
|
if (path[0] != '\\' && path[0] != '/')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Create scratch copy of path excluding leading slash (we will be
|
||||||
|
* replacing path separators with null terminators and referencing those
|
||||||
|
* substrings directly as path components) */
|
||||||
|
char path_scratch[GUAC_COMMON_SSH_SFTP_MAX_PATH - 1];
|
||||||
|
int length = guac_strlcpy(path_scratch, path + 1,
|
||||||
|
sizeof(path_scratch));
|
||||||
|
|
||||||
|
/* Fail if provided path is too long */
|
||||||
|
if (length >= sizeof(path_scratch))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Locate all path components within path */
|
||||||
|
const char* current_path_component = &(path_scratch[0]);
|
||||||
|
for (int i = 0; i <= length; i++) {
|
||||||
|
|
||||||
|
/* If current character is a path separator, parse as component */
|
||||||
|
char c = path_scratch[i];
|
||||||
|
if (c == '/' || c == '\\' || c == '\0') {
|
||||||
|
|
||||||
|
/* Terminate current component */
|
||||||
|
path_scratch[i] = '\0';
|
||||||
|
|
||||||
|
/* If component refers to parent, just move up in depth */
|
||||||
|
if (strcmp(current_path_component, "..") == 0) {
|
||||||
|
if (path_depth > 0)
|
||||||
|
path_depth--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise, if component not current directory, add to list */
|
||||||
|
else if (strcmp(current_path_component, ".") != 0
|
||||||
|
&& strcmp(current_path_component, "") != 0) {
|
||||||
|
|
||||||
|
/* Fail normalization if path is too deep */
|
||||||
|
if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
path_components[path_depth++] = current_path_component;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update start of next component */
|
||||||
|
current_path_component = &(path_scratch[i+1]);
|
||||||
|
|
||||||
|
} /* end if separator */
|
||||||
|
|
||||||
|
} /* end for each character */
|
||||||
|
|
||||||
|
/* Add leading slash for resulting absolute path */
|
||||||
|
fullpath[0] = '/';
|
||||||
|
|
||||||
|
/* Append normalized components to path, separated by slashes */
|
||||||
|
guac_strljoin(fullpath + 1, path_components, path_depth,
|
||||||
|
"/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the last error message received by the SFTP layer of an SSH
|
* Translates the last error message received by the SFTP layer of an SSH
|
||||||
* session into a Guacamole protocol status code.
|
* session into a Guacamole protocol status code.
|
||||||
@ -124,7 +193,7 @@ static guac_protocol_status guac_sftp_get_status(
|
|||||||
static int guac_ssh_append_filename(char* fullpath, const char* path,
|
static int guac_ssh_append_filename(char* fullpath, const char* path,
|
||||||
const char* filename) {
|
const char* filename) {
|
||||||
|
|
||||||
int i;
|
int length;
|
||||||
|
|
||||||
/* Disallow "." as a filename */
|
/* Disallow "." as a filename */
|
||||||
if (strcmp(filename, ".") == 0)
|
if (strcmp(filename, ".") == 0)
|
||||||
@ -134,49 +203,80 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
|
|||||||
if (strcmp(filename, "..") == 0)
|
if (strcmp(filename, "..") == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Copy path, append trailing slash */
|
/* Filenames may not contain slashes */
|
||||||
for (i=0; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
|
if (strchr(filename, '/') != NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Copy base path */
|
||||||
|
length = guac_strlcpy(fullpath, path, GUAC_COMMON_SSH_SFTP_MAX_PATH);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Append trailing slash only if:
|
* Append trailing slash only if:
|
||||||
* 1) Trailing slash is not already present
|
* 1) Trailing slash is not already present
|
||||||
* 2) Path is non-empty
|
* 2) Path is non-empty
|
||||||
*/
|
*/
|
||||||
|
if (length > 0 && fullpath[length - 1] != '/')
|
||||||
char c = path[i];
|
length += guac_strlcpy(fullpath + length, "/",
|
||||||
if (c == '\0') {
|
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
|
||||||
if (i > 0 && path[i-1] != '/')
|
|
||||||
fullpath[i++] = '/';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy character if not end of string */
|
|
||||||
fullpath[i] = c;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append filename */
|
/* Append filename */
|
||||||
for (; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
|
length += guac_strlcpy(fullpath + length, filename,
|
||||||
|
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
|
||||||
char c = *(filename++);
|
|
||||||
if (c == '\0')
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* Filenames may not contain slashes */
|
|
||||||
if (c == '\\' || c == '/')
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Append each character within filename */
|
|
||||||
fullpath[i] = c;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify path length is within maximum */
|
/* Verify path length is within maximum */
|
||||||
if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
|
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Terminate path string */
|
/* Append was successful */
|
||||||
fullpath[i] = '\0';
|
return 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates the given paths, separating the two with a single forward
|
||||||
|
* slash. The full result must be no more than GUAC_COMMON_SSH_SFTP_MAX_PATH
|
||||||
|
* bytes long, counting null terminator.
|
||||||
|
*
|
||||||
|
* @param fullpath
|
||||||
|
* The buffer to store the result within. This buffer must be at least
|
||||||
|
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes long.
|
||||||
|
*
|
||||||
|
* @param path_a
|
||||||
|
* The path to place at the beginning of the resulting path.
|
||||||
|
*
|
||||||
|
* @param path_b
|
||||||
|
* The path to append after path_a within the resulting path.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if the paths were successfully concatenated together, zero
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
static int guac_ssh_append_path(char* fullpath, const char* path_a,
|
||||||
|
const char* path_b) {
|
||||||
|
|
||||||
|
int length;
|
||||||
|
|
||||||
|
/* Copy first half of path */
|
||||||
|
length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH);
|
||||||
|
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Ensure path ends with trailing slash */
|
||||||
|
if (length == 0 || fullpath[length - 1] != '/')
|
||||||
|
length += guac_strlcpy(fullpath + length, "/",
|
||||||
|
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
|
||||||
|
|
||||||
|
/* Skip past leading slashes in second path */
|
||||||
|
while (*path_b == '/')
|
||||||
|
path_b++;
|
||||||
|
|
||||||
|
/* Append final half of path */
|
||||||
|
length += guac_strlcpy(fullpath + length, path_b,
|
||||||
|
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
|
||||||
|
|
||||||
|
/* Verify path length is within maximum */
|
||||||
|
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* Append was successful */
|
/* Append was successful */
|
||||||
return 1;
|
return 1;
|
||||||
@ -276,6 +376,18 @@ int guac_common_ssh_sftp_handle_file_stream(
|
|||||||
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
LIBSSH2_SFTP_HANDLE* file;
|
LIBSSH2_SFTP_HANDLE* file;
|
||||||
|
|
||||||
|
/* Ignore upload if uploads have been disabled */
|
||||||
|
if (filesystem->disable_upload) {
|
||||||
|
guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has "
|
||||||
|
"been blocked due to uploads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Concatenate filename with path */
|
/* Concatenate filename with path */
|
||||||
if (!guac_ssh_append_filename(fullpath, filesystem->upload_path,
|
if (!guac_ssh_append_filename(fullpath, filesystem->upload_path,
|
||||||
filename)) {
|
filename)) {
|
||||||
@ -329,7 +441,7 @@ int guac_common_ssh_sftp_handle_file_stream(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for ack messages which continue an outbound SFTP data transfer
|
* Handler for ack messages which continue an outbound SFTP data transfer
|
||||||
* (download), signalling the current status and requesting additional data.
|
* (download), signaling the current status and requesting additional data.
|
||||||
* The data associated with the given stream is expected to be a pointer to an
|
* The data associated with the given stream is expected to be a pointer to an
|
||||||
* open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read.
|
* open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read.
|
||||||
*
|
*
|
||||||
@ -373,8 +485,11 @@ static int guac_common_ssh_sftp_ack_handler(guac_user* user,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If bytes could not be read, handle EOF or error condition */
|
||||||
|
else {
|
||||||
|
|
||||||
/* If EOF, send end */
|
/* If EOF, send end */
|
||||||
else if (bytes_read == 0) {
|
if (bytes_read == 0) {
|
||||||
guac_user_log(user, GUAC_LOG_DEBUG, "File sent");
|
guac_user_log(user, GUAC_LOG_DEBUG, "File sent");
|
||||||
guac_protocol_send_end(user->socket, stream);
|
guac_protocol_send_end(user->socket, stream);
|
||||||
guac_user_free_stream(user, stream);
|
guac_user_free_stream(user, stream);
|
||||||
@ -387,6 +502,14 @@ static int guac_common_ssh_sftp_ack_handler(guac_user* user,
|
|||||||
guac_user_free_stream(user, stream);
|
guac_user_free_stream(user, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Close file */
|
||||||
|
if (libssh2_sftp_close(file) == 0)
|
||||||
|
guac_user_log(user, GUAC_LOG_DEBUG, "File closed");
|
||||||
|
else
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to close file");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
guac_socket_flush(user->socket);
|
guac_socket_flush(user->socket);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -405,6 +528,15 @@ guac_stream* guac_common_ssh_sftp_download_file(
|
|||||||
guac_stream* stream;
|
guac_stream* stream;
|
||||||
LIBSSH2_SFTP_HANDLE* file;
|
LIBSSH2_SFTP_HANDLE* file;
|
||||||
|
|
||||||
|
/* Ignore download if downloads have been disabled */
|
||||||
|
if (filesystem->disable_download) {
|
||||||
|
guac_user_log(user, GUAC_LOG_WARNING, "A download attempt has "
|
||||||
|
"been blocked due to downloads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Attempt to open file for reading */
|
/* Attempt to open file for reading */
|
||||||
file = libssh2_sftp_open(filesystem->sftp_session, filename,
|
file = libssh2_sftp_open(filesystem->sftp_session, filename,
|
||||||
LIBSSH2_FXF_READ, 0);
|
LIBSSH2_FXF_READ, 0);
|
||||||
@ -475,7 +607,6 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
|
|||||||
guac_stream* stream, char* message, guac_protocol_status status) {
|
guac_stream* stream, char* message, guac_protocol_status status) {
|
||||||
|
|
||||||
int bytes_read;
|
int bytes_read;
|
||||||
int blob_written = 0;
|
|
||||||
|
|
||||||
char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
||||||
@ -497,8 +628,7 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
|
|||||||
|
|
||||||
/* While directory entries remain */
|
/* While directory entries remain */
|
||||||
while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
|
while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
|
||||||
filename, sizeof(filename), &attributes)) > 0
|
filename, sizeof(filename), &attributes)) > 0) {
|
||||||
&& !blob_written) {
|
|
||||||
|
|
||||||
char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
@ -528,9 +658,10 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
|
|||||||
else
|
else
|
||||||
mimetype = "application/octet-stream";
|
mimetype = "application/octet-stream";
|
||||||
|
|
||||||
/* Write entry */
|
/* Write entry, waiting for next ack if a blob is written */
|
||||||
blob_written |= guac_common_json_write_property(user, stream,
|
if (guac_common_json_write_property(user, stream,
|
||||||
&list_state->json_state, absolute_path, mimetype);
|
&list_state->json_state, absolute_path, mimetype))
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,6 +687,38 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a stream name for the given SFTP filesystem object into the
|
||||||
|
* absolute path corresponding to the actual file it represents.
|
||||||
|
*
|
||||||
|
* @param fullpath
|
||||||
|
* The buffer to populate with the translated path. This buffer MUST be at
|
||||||
|
* least GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
|
||||||
|
*
|
||||||
|
* @param object
|
||||||
|
* The Guacamole protocol object associated with the SFTP filesystem.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the stream (file) to translate into an absolute path.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if translation succeeded, zero otherwise.
|
||||||
|
*/
|
||||||
|
static int guac_common_ssh_sftp_translate_name(char* fullpath,
|
||||||
|
guac_object* object, char* name) {
|
||||||
|
|
||||||
|
char normalized_name[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
guac_common_ssh_sftp_filesystem* filesystem =
|
||||||
|
(guac_common_ssh_sftp_filesystem*) object->data;
|
||||||
|
|
||||||
|
/* Normalize stream name into a path, and append to the root path */
|
||||||
|
return guac_common_ssh_sftp_normalize_path(normalized_name, name)
|
||||||
|
&& guac_ssh_append_path(fullpath, filesystem->root_path,
|
||||||
|
normalized_name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for get messages. In context of SFTP and the filesystem exposed via
|
* Handler for get messages. In context of SFTP and the filesystem exposed via
|
||||||
* the Guacamole protocol, get messages request the body of a file within the
|
* the Guacamole protocol, get messages request the body of a file within the
|
||||||
@ -576,16 +739,25 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
|
|||||||
static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
||||||
guac_object* object, char* name) {
|
guac_object* object, char* name) {
|
||||||
|
|
||||||
|
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
guac_common_ssh_sftp_filesystem* filesystem =
|
guac_common_ssh_sftp_filesystem* filesystem =
|
||||||
(guac_common_ssh_sftp_filesystem*) object->data;
|
(guac_common_ssh_sftp_filesystem*) object->data;
|
||||||
|
|
||||||
LIBSSH2_SFTP* sftp = filesystem->sftp_session;
|
LIBSSH2_SFTP* sftp = filesystem->sftp_session;
|
||||||
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
LIBSSH2_SFTP_ATTRIBUTES attributes;
|
||||||
|
|
||||||
|
/* Translate stream name into filesystem path */
|
||||||
|
if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) {
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path "
|
||||||
|
"for stream \"%s\"", name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Attempt to read file information */
|
/* Attempt to read file information */
|
||||||
if (libssh2_sftp_stat(sftp, name, &attributes)) {
|
if (libssh2_sftp_stat(sftp, fullpath, &attributes)) {
|
||||||
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
|
||||||
name);
|
fullpath);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,10 +765,10 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
|||||||
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
|
if (LIBSSH2_SFTP_S_ISDIR(attributes.permissions)) {
|
||||||
|
|
||||||
/* Open as directory */
|
/* Open as directory */
|
||||||
LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, name);
|
LIBSSH2_SFTP_HANDLE* dir = libssh2_sftp_opendir(sftp, fullpath);
|
||||||
if (dir == NULL) {
|
if (dir == NULL) {
|
||||||
guac_user_log(user, GUAC_LOG_INFO,
|
guac_user_log(user, GUAC_LOG_INFO,
|
||||||
"Unable to read directory \"%s\"", name);
|
"Unable to read directory \"%s\"", fullpath);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,8 +778,17 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
|||||||
|
|
||||||
list_state->directory = dir;
|
list_state->directory = dir;
|
||||||
list_state->filesystem = filesystem;
|
list_state->filesystem = filesystem;
|
||||||
strncpy(list_state->directory_name, name,
|
|
||||||
sizeof(list_state->directory_name) - 1);
|
int length = guac_strlcpy(list_state->directory_name, name,
|
||||||
|
sizeof(list_state->directory_name));
|
||||||
|
|
||||||
|
/* Bail out if directory name is too long to store */
|
||||||
|
if (length >= sizeof(list_state->directory_name)) {
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory "
|
||||||
|
"\"%s\": Path too long", fullpath);
|
||||||
|
free(list_state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Allocate stream for body */
|
/* Allocate stream for body */
|
||||||
guac_stream* stream = guac_user_alloc_stream(user);
|
guac_stream* stream = guac_user_alloc_stream(user);
|
||||||
@ -626,12 +807,20 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
|||||||
/* Otherwise, send file contents */
|
/* Otherwise, send file contents */
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
/* If downloads are disabled, log and return. */
|
||||||
|
if (filesystem->disable_download) {
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO,
|
||||||
|
"Unable to download file \"%s\", "
|
||||||
|
"file downloads have been disabled.", fullpath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Open as normal file */
|
/* Open as normal file */
|
||||||
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
|
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath,
|
||||||
LIBSSH2_FXF_READ, 0);
|
LIBSSH2_FXF_READ, 0);
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
guac_user_log(user, GUAC_LOG_INFO,
|
guac_user_log(user, GUAC_LOG_INFO,
|
||||||
"Unable to read file \"%s\"", name);
|
"Unable to read file \"%s\"", fullpath);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,19 +866,40 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
|
|||||||
static int guac_common_ssh_sftp_put_handler(guac_user* user,
|
static int guac_common_ssh_sftp_put_handler(guac_user* user,
|
||||||
guac_object* object, guac_stream* stream, char* mimetype, char* name) {
|
guac_object* object, guac_stream* stream, char* mimetype, char* name) {
|
||||||
|
|
||||||
|
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
guac_common_ssh_sftp_filesystem* filesystem =
|
guac_common_ssh_sftp_filesystem* filesystem =
|
||||||
(guac_common_ssh_sftp_filesystem*) object->data;
|
(guac_common_ssh_sftp_filesystem*) object->data;
|
||||||
|
|
||||||
|
/* Ignore upload if uploads have been disabled */
|
||||||
|
if (filesystem->disable_upload) {
|
||||||
|
guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has "
|
||||||
|
"been blocked due to uploads being disabled, however it "
|
||||||
|
"should have been blocked at a higher level. This is likely "
|
||||||
|
"a bug.");
|
||||||
|
guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled",
|
||||||
|
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
|
||||||
|
guac_socket_flush(user->socket);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
LIBSSH2_SFTP* sftp = filesystem->sftp_session;
|
LIBSSH2_SFTP* sftp = filesystem->sftp_session;
|
||||||
|
|
||||||
|
/* Translate stream name into filesystem path */
|
||||||
|
if (!guac_common_ssh_sftp_translate_name(fullpath, object, name)) {
|
||||||
|
guac_user_log(user, GUAC_LOG_INFO, "Unable to generate real path "
|
||||||
|
"for stream \"%s\"", name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Open file via SFTP */
|
/* Open file via SFTP */
|
||||||
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, name,
|
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath,
|
||||||
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
|
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
|
||||||
S_IRUSR | S_IWUSR);
|
S_IRUSR | S_IWUSR);
|
||||||
|
|
||||||
/* Acknowledge stream if successful */
|
/* Acknowledge stream if successful */
|
||||||
if (file != NULL) {
|
if (file != NULL) {
|
||||||
guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", name);
|
guac_user_log(user, GUAC_LOG_DEBUG, "File \"%s\" opened", fullpath);
|
||||||
guac_protocol_send_ack(user->socket, stream, "SFTP: File opened",
|
guac_protocol_send_ack(user->socket, stream, "SFTP: File opened",
|
||||||
GUAC_PROTOCOL_STATUS_SUCCESS);
|
GUAC_PROTOCOL_STATUS_SUCCESS);
|
||||||
}
|
}
|
||||||
@ -697,7 +907,7 @@ static int guac_common_ssh_sftp_put_handler(guac_user* user,
|
|||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
else {
|
else {
|
||||||
guac_user_log(user, GUAC_LOG_INFO,
|
guac_user_log(user, GUAC_LOG_INFO,
|
||||||
"Unable to open file \"%s\"", name);
|
"Unable to open file \"%s\"", fullpath);
|
||||||
guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed",
|
guac_protocol_send_ack(user->socket, stream, "SFTP: Open failed",
|
||||||
guac_sftp_get_status(filesystem));
|
guac_sftp_get_status(filesystem));
|
||||||
}
|
}
|
||||||
@ -733,7 +943,11 @@ guac_object* guac_common_ssh_alloc_sftp_filesystem_object(
|
|||||||
/* Init filesystem */
|
/* Init filesystem */
|
||||||
guac_object* fs_object = guac_user_alloc_object(user);
|
guac_object* fs_object = guac_user_alloc_object(user);
|
||||||
fs_object->get_handler = guac_common_ssh_sftp_get_handler;
|
fs_object->get_handler = guac_common_ssh_sftp_get_handler;
|
||||||
|
|
||||||
|
/* Only handle uploads if not disabled. */
|
||||||
|
if (!filesystem->disable_upload)
|
||||||
fs_object->put_handler = guac_common_ssh_sftp_put_handler;
|
fs_object->put_handler = guac_common_ssh_sftp_put_handler;
|
||||||
|
|
||||||
fs_object->data = filesystem;
|
fs_object->data = filesystem;
|
||||||
|
|
||||||
/* Send filesystem to user */
|
/* Send filesystem to user */
|
||||||
@ -745,7 +959,8 @@ guac_object* guac_common_ssh_alloc_sftp_filesystem_object(
|
|||||||
}
|
}
|
||||||
|
|
||||||
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
|
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
|
||||||
guac_common_ssh_session* session, const char* name) {
|
guac_common_ssh_session* session, const char* root_path,
|
||||||
|
const char* name, int disable_download, int disable_upload) {
|
||||||
|
|
||||||
/* Request SFTP */
|
/* Request SFTP */
|
||||||
LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session);
|
LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session);
|
||||||
@ -757,10 +972,28 @@ guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
|
|||||||
malloc(sizeof(guac_common_ssh_sftp_filesystem));
|
malloc(sizeof(guac_common_ssh_sftp_filesystem));
|
||||||
|
|
||||||
/* Associate SSH session with SFTP data and user */
|
/* Associate SSH session with SFTP data and user */
|
||||||
filesystem->name = strdup(name);
|
|
||||||
filesystem->ssh_session = session;
|
filesystem->ssh_session = session;
|
||||||
filesystem->sftp_session = sftp_session;
|
filesystem->sftp_session = sftp_session;
|
||||||
|
|
||||||
|
/* Copy over disable flags */
|
||||||
|
filesystem->disable_download = disable_download;
|
||||||
|
filesystem->disable_upload = disable_upload;
|
||||||
|
|
||||||
|
/* Normalize and store the provided root path */
|
||||||
|
if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path,
|
||||||
|
root_path)) {
|
||||||
|
guac_client_log(session->client, GUAC_LOG_WARNING, "Cannot create "
|
||||||
|
"SFTP filesystem - \"%s\" is not a valid path.", root_path);
|
||||||
|
free(filesystem);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate filesystem name from root path if no name is provided */
|
||||||
|
if (name != NULL)
|
||||||
|
filesystem->name = strdup(name);
|
||||||
|
else
|
||||||
|
filesystem->name = strdup(filesystem->root_path);
|
||||||
|
|
||||||
/* Initially upload files to current directory */
|
/* Initially upload files to current directory */
|
||||||
strcpy(filesystem->upload_path, ".");
|
strcpy(filesystem->upload_path, ".");
|
||||||
|
|
@ -17,11 +17,12 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "guac_ssh.h"
|
#include "common-ssh/key.h"
|
||||||
#include "guac_ssh_key.h"
|
#include "common-ssh/ssh.h"
|
||||||
#include "guac_ssh_user.h"
|
#include "common-ssh/user.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/fips.h>
|
||||||
#include <libssh2.h>
|
#include <libssh2.h>
|
||||||
|
|
||||||
#ifdef LIBSSH2_USES_GCRYPT
|
#ifdef LIBSSH2_USES_GCRYPT
|
||||||
@ -35,6 +36,7 @@
|
|||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <pwd.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -45,6 +47,21 @@
|
|||||||
GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all key exchange algorithms that are both FIPS-compliant, and
|
||||||
|
* OpenSSL-supported. Note that "ext-info-c" is also included. While not a key
|
||||||
|
* exchange algorithm per se, it must be in the list to ensure that the server
|
||||||
|
* will send a SSH_MSG_EXT_INFO response, which is required to perform RSA key
|
||||||
|
* upgrades.
|
||||||
|
*/
|
||||||
|
#define FIPS_COMPLIANT_KEX_ALGORITHMS "diffie-hellman-group-exchange-sha256,ext-info-c"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of ciphers that are both FIPS-compliant, and OpenSSL-supported.
|
||||||
|
*/
|
||||||
|
#define FIPS_COMPLIANT_CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,aes192-cbc,aes256-cbc"
|
||||||
|
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
/**
|
/**
|
||||||
* Array of mutexes, used by OpenSSL.
|
* Array of mutexes, used by OpenSSL.
|
||||||
*/
|
*/
|
||||||
@ -133,26 +150,41 @@ static void guac_common_ssh_openssl_free_locks(int count) {
|
|||||||
free(guac_common_ssh_openssl_locks);
|
free(guac_common_ssh_openssl_locks);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int guac_common_ssh_init(guac_client* client) {
|
int guac_common_ssh_init(guac_client* client) {
|
||||||
|
|
||||||
#ifdef LIBSSH2_USES_GCRYPT
|
#ifdef LIBSSH2_USES_GCRYPT
|
||||||
|
|
||||||
|
if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
|
||||||
|
|
||||||
/* Init threadsafety in libgcrypt */
|
/* Init threadsafety in libgcrypt */
|
||||||
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
|
||||||
|
|
||||||
|
/* Initialize GCrypt */
|
||||||
if (!gcry_check_version(GCRYPT_VERSION)) {
|
if (!gcry_check_version(GCRYPT_VERSION)) {
|
||||||
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
|
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mark initialization as completed. */
|
||||||
|
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
|
||||||
|
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
/* Init threadsafety in OpenSSL */
|
/* Init threadsafety in OpenSSL */
|
||||||
guac_common_ssh_openssl_init_locks(CRYPTO_num_locks());
|
guac_common_ssh_openssl_init_locks(CRYPTO_num_locks());
|
||||||
CRYPTO_set_id_callback(guac_common_ssh_openssl_id_callback);
|
CRYPTO_set_id_callback(guac_common_ssh_openssl_id_callback);
|
||||||
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
|
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Init OpenSSL */
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
/* Init OpenSSL - only required for OpenSSL Versions < 1.1.0 */
|
||||||
SSL_library_init();
|
SSL_library_init();
|
||||||
ERR_load_crypto_strings();
|
ERR_load_crypto_strings();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Init libssh2 */
|
/* Init libssh2 */
|
||||||
libssh2_init(0);
|
libssh2_init(0);
|
||||||
@ -163,56 +195,9 @@ int guac_common_ssh_init(guac_client* client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_ssh_uninit() {
|
void guac_common_ssh_uninit() {
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
guac_common_ssh_openssl_free_locks(CRYPTO_num_locks());
|
guac_common_ssh_openssl_free_locks(CRYPTO_num_locks());
|
||||||
}
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked.
|
|
||||||
* This callback must sign the given data, returning the signature as newly-
|
|
||||||
* allocated buffer space.
|
|
||||||
*
|
|
||||||
* @param session
|
|
||||||
* The SSH session for which the signature is being generated.
|
|
||||||
*
|
|
||||||
* @param sig
|
|
||||||
* A pointer to the buffer space containing the signature. This callback
|
|
||||||
* MUST allocate and assign this space.
|
|
||||||
*
|
|
||||||
* @param sig_len
|
|
||||||
* The length of the signature within the allocated buffer space, in bytes.
|
|
||||||
* This value must be set to the size of the signature after the signing
|
|
||||||
* operation completes.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The arbitrary data that must be signed.
|
|
||||||
*
|
|
||||||
* @param data_len
|
|
||||||
* The length of the arbitrary data to be signed, in bytes.
|
|
||||||
*
|
|
||||||
* @param abstract
|
|
||||||
* The value of the abstract parameter provided with the corresponding call
|
|
||||||
* to libssh2_userauth_publickey().
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero on success, non-zero if the signing operation failed.
|
|
||||||
*/
|
|
||||||
static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session,
|
|
||||||
unsigned char** sig, size_t* sig_len,
|
|
||||||
const unsigned char* data, size_t data_len, void **abstract) {
|
|
||||||
|
|
||||||
guac_common_ssh_key* key = (guac_common_ssh_key*) abstract;
|
|
||||||
int length;
|
|
||||||
|
|
||||||
/* Allocate space for signature */
|
|
||||||
*sig = malloc(4096);
|
|
||||||
|
|
||||||
/* Sign with key */
|
|
||||||
length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig);
|
|
||||||
if (length < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
*sig_len = length;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -297,20 +282,27 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
LIBSSH2_SESSION* session = common_session->session;
|
LIBSSH2_SESSION* session = common_session->session;
|
||||||
|
|
||||||
/* Get user credentials */
|
/* Get user credentials */
|
||||||
char* username = user->username;
|
|
||||||
char* password = user->password;
|
|
||||||
guac_common_ssh_key* key = user->private_key;
|
guac_common_ssh_key* key = user->private_key;
|
||||||
|
|
||||||
/* Validate username provided */
|
/* Validate username provided */
|
||||||
if (username == NULL) {
|
if (user->username == NULL) {
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
|
||||||
"SSH authentication requires a username.");
|
"SSH authentication requires a username.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get list of supported authentication methods */
|
/* Get list of supported authentication methods */
|
||||||
char* user_authlist = libssh2_userauth_list(session, username,
|
size_t username_len = strlen(user->username);
|
||||||
strlen(username));
|
char* user_authlist = libssh2_userauth_list(session, user->username,
|
||||||
|
username_len);
|
||||||
|
|
||||||
|
/* If auth list is NULL, then authentication has succeeded with NONE */
|
||||||
|
if (user_authlist == NULL) {
|
||||||
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
|
"SSH NONE authentication succeeded.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
"Supported authentication methods: %s", user_authlist);
|
"Supported authentication methods: %s", user_authlist);
|
||||||
|
|
||||||
@ -326,9 +318,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Attempt public key auth */
|
/* Attempt public key auth */
|
||||||
if (libssh2_userauth_publickey(session, username,
|
if (libssh2_userauth_publickey_frommemory(session, user->username,
|
||||||
(unsigned char*) key->public_key, key->public_key_length,
|
username_len, NULL, 0, key->private_key,
|
||||||
guac_common_ssh_sign_callback, (void**) key)) {
|
key->private_key_length, key->passphrase)) {
|
||||||
|
|
||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
char* error_message;
|
char* error_message;
|
||||||
@ -345,14 +337,18 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Attempt authentication with username + password. */
|
||||||
|
if (user->password == NULL && common_session->credential_handler)
|
||||||
|
user->password = common_session->credential_handler(client, "Password: ");
|
||||||
|
|
||||||
/* Authenticate with password, if provided */
|
/* Authenticate with password, if provided */
|
||||||
else if (password != NULL) {
|
if (user->password != NULL) {
|
||||||
|
|
||||||
/* Check if password auth is supported on the server */
|
/* Check if password auth is supported on the server */
|
||||||
if (strstr(user_authlist, "password") != NULL) {
|
if (strstr(user_authlist, "password") != NULL) {
|
||||||
|
|
||||||
/* Attempt password authentication */
|
/* Attempt password authentication */
|
||||||
if (libssh2_userauth_password(session, username, password)) {
|
if (libssh2_userauth_password(session, user->username, user->password)) {
|
||||||
|
|
||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
char* error_message;
|
char* error_message;
|
||||||
@ -373,7 +369,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
if (strstr(user_authlist, "keyboard-interactive") != NULL) {
|
if (strstr(user_authlist, "keyboard-interactive") != NULL) {
|
||||||
|
|
||||||
/* Attempt keyboard-interactive auth using provided password */
|
/* Attempt keyboard-interactive auth using provided password */
|
||||||
if (libssh2_userauth_keyboard_interactive(session, username,
|
if (libssh2_userauth_keyboard_interactive(session, user->username,
|
||||||
&guac_common_ssh_kbd_callback)) {
|
&guac_common_ssh_kbd_callback)) {
|
||||||
|
|
||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
@ -408,7 +404,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
}
|
}
|
||||||
|
|
||||||
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
||||||
const char* hostname, const char* port, guac_common_ssh_user* user) {
|
const char* hostname, const char* port, guac_common_ssh_user* user,
|
||||||
|
int keepalive, const char* host_key,
|
||||||
|
guac_ssh_credential_handler* credential_handler) {
|
||||||
|
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
@ -425,20 +423,11 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
.ai_protocol = IPPROTO_TCP
|
.ai_protocol = IPPROTO_TCP
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Get socket */
|
|
||||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (fd < 0) {
|
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
|
||||||
"Unable to create socket: %s", strerror(errno));
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get addresses connection */
|
/* Get addresses connection */
|
||||||
if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) {
|
if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) {
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||||
"Error parsing given address or port: %s",
|
"Error parsing given address or port: %s",
|
||||||
gai_strerror(retval));
|
gai_strerror(retval));
|
||||||
close(fd);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,6 +444,15 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
||||||
"Unable to resolve host: %s", gai_strerror(retval));
|
"Unable to resolve host: %s", gai_strerror(retval));
|
||||||
|
|
||||||
|
/* Get socket */
|
||||||
|
fd = socket(current_address->ai_family, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||||
|
"Unable to create socket: %s", strerror(errno));
|
||||||
|
freeaddrinfo(addresses);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Connect */
|
/* Connect */
|
||||||
if (connect(fd, current_address->ai_addr,
|
if (connect(fd, current_address->ai_addr,
|
||||||
current_address->ai_addrlen) == 0) {
|
current_address->ai_addrlen) == 0) {
|
||||||
@ -469,11 +467,11 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Otherwise log information regarding bind failure */
|
/* Otherwise log information regarding bind failure */
|
||||||
else
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
|
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
|
||||||
"host %s, port %s: %s",
|
"host %s, port %s: %s",
|
||||||
connected_address, connected_port, strerror(errno));
|
connected_address, connected_port, strerror(errno));
|
||||||
|
|
||||||
|
close(fd);
|
||||||
current_address = current_address->ai_next;
|
current_address = current_address->ai_next;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -483,9 +481,8 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
|
|
||||||
/* If unable to connect to anything, fail */
|
/* If unable to connect to anything, fail */
|
||||||
if (current_address == NULL) {
|
if (current_address == NULL) {
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
|
||||||
"Unable to connect to any addresses.");
|
"Unable to connect to any addresses.");
|
||||||
close(fd);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,6 +501,17 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If FIPS mode is enabled, prefer only FIPS-compatible algorithms and
|
||||||
|
* ciphers that are also supported by libssh2. For more info, see:
|
||||||
|
* https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp2906.pdf
|
||||||
|
*/
|
||||||
|
if (guac_fips_enabled()) {
|
||||||
|
libssh2_session_method_pref(session, LIBSSH2_METHOD_KEX, FIPS_COMPLIANT_KEX_ALGORITHMS);
|
||||||
|
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_CS, FIPS_COMPLIANT_CIPHERS);
|
||||||
|
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_SC, FIPS_COMPLIANT_CIPHERS);
|
||||||
|
}
|
||||||
|
|
||||||
/* Perform handshake */
|
/* Perform handshake */
|
||||||
if (libssh2_session_handshake(session, fd)) {
|
if (libssh2_session_handshake(session, fd)) {
|
||||||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
|
||||||
@ -513,11 +521,48 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get host key of remote system we're connecting to */
|
||||||
|
size_t remote_hostkey_len;
|
||||||
|
const char *remote_hostkey = libssh2_session_hostkey(session, &remote_hostkey_len, NULL);
|
||||||
|
|
||||||
|
/* Failure to retrieve a host key means we should abort */
|
||||||
|
if (!remote_hostkey) {
|
||||||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||||
|
"Failed to get host key for %s", hostname);
|
||||||
|
free(common_session);
|
||||||
|
close(fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SSH known host key checking. */
|
||||||
|
int known_host_check = guac_common_ssh_verify_host_key(session, client, host_key,
|
||||||
|
hostname, atoi(port), remote_hostkey,
|
||||||
|
remote_hostkey_len);
|
||||||
|
|
||||||
|
/* Abort on any error codes */
|
||||||
|
if (known_host_check != 0) {
|
||||||
|
char* err_msg;
|
||||||
|
libssh2_session_last_error(session, &err_msg, NULL, 0);
|
||||||
|
|
||||||
|
if (known_host_check < 0)
|
||||||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||||
|
"Error occurred attempting to check host key: %s", err_msg);
|
||||||
|
|
||||||
|
if (known_host_check > 0)
|
||||||
|
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
|
||||||
|
"Host key did not match any provided known host keys. %s", err_msg);
|
||||||
|
|
||||||
|
free(common_session);
|
||||||
|
close(fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Store basic session data */
|
/* Store basic session data */
|
||||||
common_session->client = client;
|
common_session->client = client;
|
||||||
common_session->user = user;
|
common_session->user = user;
|
||||||
common_session->session = session;
|
common_session->session = session;
|
||||||
common_session->fd = fd;
|
common_session->fd = fd;
|
||||||
|
common_session->credential_handler = credential_handler;
|
||||||
|
|
||||||
/* Attempt authentication */
|
/* Attempt authentication */
|
||||||
if (guac_common_ssh_authenticate(common_session)) {
|
if (guac_common_ssh_authenticate(common_session)) {
|
||||||
@ -526,6 +571,20 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Warn if keepalive below minimum value */
|
||||||
|
if (keepalive < 0) {
|
||||||
|
keepalive = 0;
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING, "negative keepalive intervals "
|
||||||
|
"are converted to 0, disabling keepalive.");
|
||||||
|
}
|
||||||
|
else if (keepalive == 1) {
|
||||||
|
guac_client_log(client, GUAC_LOG_WARNING, "keepalive interval will "
|
||||||
|
"be rounded up to minimum value of 2.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure session keepalive */
|
||||||
|
libssh2_keepalive_config(common_session->session, 1, keepalive);
|
||||||
|
|
||||||
/* Return created session */
|
/* Return created session */
|
||||||
return common_session;
|
return common_session;
|
||||||
|
|
||||||
@ -541,4 +600,3 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) {
|
|||||||
free(session);
|
free(session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
67
src/common-ssh/tests/Makefile.am
Normal file
67
src/common-ssh/tests/Makefile.am
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
|
#
|
||||||
|
# Unit tests for common SSH support
|
||||||
|
#
|
||||||
|
|
||||||
|
check_PROGRAMS = test_common_ssh
|
||||||
|
TESTS = $(check_PROGRAMS)
|
||||||
|
|
||||||
|
test_common_ssh_SOURCES = \
|
||||||
|
sftp/normalize_path.c
|
||||||
|
|
||||||
|
test_common_ssh_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic \
|
||||||
|
@COMMON_INCLUDE@ \
|
||||||
|
@COMMON_SSH_INCLUDE@ \
|
||||||
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
|
test_common_ssh_LDADD = \
|
||||||
|
@CUNIT_LIBS@ \
|
||||||
|
@COMMON_SSH_LTLIB@ \
|
||||||
|
@COMMON_LTLIB@
|
||||||
|
|
||||||
|
#
|
||||||
|
# Autogenerate test runner
|
||||||
|
#
|
||||||
|
|
||||||
|
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
|
||||||
|
CLEANFILES = _generated_runner.c
|
||||||
|
|
||||||
|
_generated_runner.c: $(test_common_ssh_SOURCES)
|
||||||
|
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_ssh_SOURCES) > $@
|
||||||
|
|
||||||
|
nodist_test_common_ssh_SOURCES = \
|
||||||
|
_generated_runner.c
|
||||||
|
|
||||||
|
# Use automake's TAP test driver for running any tests
|
||||||
|
LOG_DRIVER = \
|
||||||
|
env AM_TAP_AWK='$(AWK)' \
|
||||||
|
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
||||||
|
|
263
src/common-ssh/tests/sftp/normalize_path.c
Normal file
263
src/common-ssh/tests/sftp/normalize_path.c
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common-ssh/sftp.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies absolute Windows-style paths are correctly normalized to
|
||||||
|
* absolute paths with UNIX separators and no relative components.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_absolute_windows() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\baz\\"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\baz\\a\\..\\b"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar\\baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\..\\..\\..\\..\\baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies absolute UNIX-style paths are correctly normalized to
|
||||||
|
* absolute paths with UNIX separators and no relative components.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_absolute_unix() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../baz/"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../baz/a/../b"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/./bar/baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../../../../../baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies absolute paths consisting of mixed Windows and UNIX path
|
||||||
|
* separators are correctly normalized to absolute paths with UNIX separators
|
||||||
|
* and no relative components.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_absolute_mixed() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo\\bar/..\\baz/"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../../baz\\a\\..\\b"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar/baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../..\\..\\..\\../..\\baz"), 0);
|
||||||
|
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies relative Windows-style paths are always rejected.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_relative_windows() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar\\baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo\\bar\\baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo\\bar\\baz"), 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies relative UNIX-style paths are always rejected.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_relative_unix() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo/bar/baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo/bar/baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo/bar/baz"), 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies relative paths consisting of mixed Windows and UNIX path
|
||||||
|
* separators are always rejected.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_relative_mixed() {
|
||||||
|
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar/baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo/bar/baz"), 0);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo\\bar\\baz"), 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a dynamically-allocated path having the given number of bytes, not
|
||||||
|
* counting the null-terminator. The path will contain only UNIX-style path
|
||||||
|
* separators. The returned path must eventually be freed with a call to
|
||||||
|
* free().
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* The number of bytes to include in the generated path, not counting the
|
||||||
|
* null-terminator. If -1, the length of the path will be automatically
|
||||||
|
* determined from the provided max_depth.
|
||||||
|
*
|
||||||
|
* @param max_depth
|
||||||
|
* The maximum number of path components to include within the generated
|
||||||
|
* path.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A dynamically-allocated path containing the given number of bytes, not
|
||||||
|
* counting the null-terminator. This path must eventually be freed with a
|
||||||
|
* call to free().
|
||||||
|
*/
|
||||||
|
static char* generate_path(int length, int max_depth) {
|
||||||
|
|
||||||
|
/* If no length given, calculate space required from max_depth */
|
||||||
|
if (length == -1)
|
||||||
|
length = max_depth * 2;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
char* input = malloc(length + 1);
|
||||||
|
|
||||||
|
/* Fill path with /x/x/x/x/x/x/x/x/x/x/.../xxxxxxxxx... */
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
if (max_depth > 0 && i % 2 == 0) {
|
||||||
|
input[i] = '/';
|
||||||
|
max_depth--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
input[i] = 'x';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add null terminator */
|
||||||
|
input[length] = '\0';
|
||||||
|
|
||||||
|
return input;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that paths exceeding the maximum path length are
|
||||||
|
* rejected.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_long() {
|
||||||
|
|
||||||
|
char* input;
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
/* Exceeds maximum length by a factor of 2 */
|
||||||
|
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH * 2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
/* Exceeds maximum length by one byte */
|
||||||
|
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
/* Exactly maximum length */
|
||||||
|
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that paths exceeding the maximum path depth are
|
||||||
|
* rejected.
|
||||||
|
*/
|
||||||
|
void test_sftp__normalize_deep() {
|
||||||
|
|
||||||
|
char* input;
|
||||||
|
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
|
||||||
|
|
||||||
|
/* Exceeds maximum depth by a factor of 2 */
|
||||||
|
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH * 2);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
/* Exceeds maximum depth by one component */
|
||||||
|
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH + 1);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
/* Exactly maximum depth (should still be rejected as SFTP depth limits are
|
||||||
|
* set such that a path with the maximum depth will exceed the maximum
|
||||||
|
* length) */
|
||||||
|
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
|
||||||
|
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
/* Less than maximum depth */
|
||||||
|
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH - 1);
|
||||||
|
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
|
||||||
|
free(input);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,8 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "guac_ssh_key.h"
|
#include "common-ssh/key.h"
|
||||||
#include "guac_ssh_user.h"
|
#include "common-ssh/user.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
5
src/common/.gitignore
vendored
Normal file
5
src/common/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
# Auto-generated test runner and binary
|
||||||
|
_generated_runner.c
|
||||||
|
test_common
|
||||||
|
|
@ -16,17 +16,25 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libguac_common.la
|
noinst_LTLIBRARIES = libguac_common.la
|
||||||
|
SUBDIRS = . tests
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
common/io.h \
|
common/io.h \
|
||||||
common/blank_cursor.h \
|
common/blank_cursor.h \
|
||||||
common/clipboard.h \
|
common/clipboard.h \
|
||||||
common/cursor.h \
|
common/cursor.h \
|
||||||
|
common/defaults.h \
|
||||||
common/display.h \
|
common/display.h \
|
||||||
common/dot_cursor.h \
|
common/dot_cursor.h \
|
||||||
common/ibar_cursor.h \
|
common/ibar_cursor.h \
|
||||||
@ -34,7 +42,6 @@ noinst_HEADERS = \
|
|||||||
common/json.h \
|
common/json.h \
|
||||||
common/list.h \
|
common/list.h \
|
||||||
common/pointer_cursor.h \
|
common/pointer_cursor.h \
|
||||||
common/recording.h \
|
|
||||||
common/rect.h \
|
common/rect.h \
|
||||||
common/string.h \
|
common/string.h \
|
||||||
common/surface.h
|
common/surface.h
|
||||||
@ -51,7 +58,6 @@ libguac_common_la_SOURCES = \
|
|||||||
json.c \
|
json.c \
|
||||||
list.c \
|
list.c \
|
||||||
pointer_cursor.c \
|
pointer_cursor.c \
|
||||||
recording.c \
|
|
||||||
rect.c \
|
rect.c \
|
||||||
string.c \
|
string.c \
|
||||||
surface.c
|
surface.c
|
||||||
|
@ -23,26 +23,37 @@
|
|||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/protocol.h>
|
#include <guacamole/protocol.h>
|
||||||
#include <guacamole/stream.h>
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/string.h>
|
||||||
#include <guacamole/user.h>
|
#include <guacamole/user.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
guac_common_clipboard* guac_common_clipboard_alloc(int size) {
|
guac_common_clipboard* guac_common_clipboard_alloc() {
|
||||||
|
|
||||||
guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard));
|
guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard));
|
||||||
|
|
||||||
/* Init clipboard */
|
/* Init clipboard */
|
||||||
clipboard->mimetype[0] = '\0';
|
clipboard->mimetype[0] = '\0';
|
||||||
clipboard->buffer = malloc(size);
|
clipboard->buffer = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
|
||||||
|
clipboard->available = GUAC_COMMON_CLIPBOARD_MAX_LENGTH;
|
||||||
clipboard->length = 0;
|
clipboard->length = 0;
|
||||||
clipboard->available = size;
|
|
||||||
|
pthread_mutex_init(&(clipboard->lock), NULL);
|
||||||
|
|
||||||
return clipboard;
|
return clipboard;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_clipboard_free(guac_common_clipboard* clipboard) {
|
void guac_common_clipboard_free(guac_common_clipboard* clipboard) {
|
||||||
|
|
||||||
|
/* Destroy lock */
|
||||||
|
pthread_mutex_destroy(&(clipboard->lock));
|
||||||
|
|
||||||
|
/* Free buffer */
|
||||||
free(clipboard->buffer);
|
free(clipboard->buffer);
|
||||||
|
|
||||||
|
/* Free base structure */
|
||||||
free(clipboard);
|
free(clipboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,18 +119,36 @@ static void* __send_user_clipboard(guac_user* user, void* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) {
|
void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&(clipboard->lock));
|
||||||
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcasting clipboard to all connected users.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcasting clipboard to all connected users.");
|
||||||
guac_client_foreach_user(client, __send_user_clipboard, clipboard);
|
guac_client_foreach_user(client, __send_user_clipboard, clipboard);
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcast of clipboard complete.");
|
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcast of clipboard complete.");
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&(clipboard->lock));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_clipboard_reset(guac_common_clipboard* clipboard, const char* mimetype) {
|
void guac_common_clipboard_reset(guac_common_clipboard* clipboard,
|
||||||
|
const char* mimetype) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&(clipboard->lock));
|
||||||
|
|
||||||
|
/* Clear clipboard contents */
|
||||||
clipboard->length = 0;
|
clipboard->length = 0;
|
||||||
strncpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype)-1);
|
|
||||||
|
/* Assign given mimetype */
|
||||||
|
guac_strlcpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype));
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&(clipboard->lock));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char* data, int length) {
|
void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char* data, int length) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&(clipboard->lock));
|
||||||
|
|
||||||
/* Truncate data to available length */
|
/* Truncate data to available length */
|
||||||
int remaining = clipboard->available - clipboard->length;
|
int remaining = clipboard->available - clipboard->length;
|
||||||
if (remaining < length)
|
if (remaining < length)
|
||||||
@ -131,5 +160,7 @@ void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char*
|
|||||||
/* Update length */
|
/* Update length */
|
||||||
clipboard->length += length;
|
clipboard->length += length;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&(clipboard->lock));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of bytes to send in an individual blob when
|
* The maximum number of bytes to send in an individual blob when
|
||||||
@ -30,11 +31,23 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_COMMON_CLIPBOARD_BLOCK_SIZE 4096
|
#define GUAC_COMMON_CLIPBOARD_BLOCK_SIZE 4096
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes to allow within the clipboard.
|
||||||
|
*/
|
||||||
|
#define GUAC_COMMON_CLIPBOARD_MAX_LENGTH 262144
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic clipboard structure.
|
* Generic clipboard structure.
|
||||||
*/
|
*/
|
||||||
typedef struct guac_common_clipboard {
|
typedef struct guac_common_clipboard {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock which restricts simultaneous access to the clipboard, guaranteeing
|
||||||
|
* ordered modifications to the clipboard and that changes to the clipboard
|
||||||
|
* are not allowed while the clipboard is being broadcast to all users.
|
||||||
|
*/
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mimetype of the contained clipboard data.
|
* The mimetype of the contained clipboard data.
|
||||||
*/
|
*/
|
||||||
@ -58,12 +71,9 @@ typedef struct guac_common_clipboard {
|
|||||||
} guac_common_clipboard;
|
} guac_common_clipboard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new clipboard having the given initial size.
|
* Creates a new clipboard.
|
||||||
*
|
|
||||||
* @param size The maximum number of bytes to allow within the clipboard.
|
|
||||||
* @return A newly-allocated clipboard.
|
|
||||||
*/
|
*/
|
||||||
guac_common_clipboard* guac_common_clipboard_alloc(int size);
|
guac_common_clipboard* guac_common_clipboard_alloc();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees the given clipboard.
|
* Frees the given clipboard.
|
||||||
|
@ -45,10 +45,9 @@ typedef struct guac_common_cursor {
|
|||||||
guac_client* client;
|
guac_client* client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cursor layer. This layer will be available to all connected users,
|
* The buffer containing the current cursor image.
|
||||||
* but will be visible only to those users who are not moving the mouse.
|
|
||||||
*/
|
*/
|
||||||
guac_layer* layer;
|
guac_layer* buffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the cursor image, in pixels.
|
* The width of the cursor image, in pixels.
|
||||||
@ -103,6 +102,27 @@ typedef struct guac_common_cursor {
|
|||||||
*/
|
*/
|
||||||
int y;
|
int y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An integer value representing the current state of each button, where
|
||||||
|
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||||
|
* button is currently pressed. The lowest-order bit is the left mouse
|
||||||
|
* button, followed by the middle button, right button, and finally the up
|
||||||
|
* and down buttons of the scroll wheel.
|
||||||
|
*
|
||||||
|
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||||
|
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||||
|
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||||
|
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||||
|
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||||
|
*/
|
||||||
|
int button_mask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server timestamp representing the point in time when the mousr
|
||||||
|
* location was last updated.
|
||||||
|
*/
|
||||||
|
guac_timestamp timestamp;
|
||||||
|
|
||||||
} guac_common_cursor;
|
} guac_common_cursor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,12 +163,12 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
|||||||
guac_socket* socket);
|
guac_socket* socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the mouse cursor, marking the given user as the most recent user of
|
* Updates the current position and button state of the mouse cursor, marking
|
||||||
* the mouse. The remote mouse cursor will be hidden for this user and shown
|
* the given user as the most recent user of the mouse. The remote mouse cursor
|
||||||
* for all others.
|
* will be hidden for this user and shown for all others.
|
||||||
*
|
*
|
||||||
* @param cursor
|
* @param cursor
|
||||||
* The cursor being moved.
|
* The cursor being updated.
|
||||||
*
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* The user that moved the cursor.
|
* The user that moved the cursor.
|
||||||
@ -158,9 +178,22 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
|||||||
*
|
*
|
||||||
* @param y
|
* @param y
|
||||||
* The new Y coordinate of the cursor.
|
* The new Y coordinate of the cursor.
|
||||||
|
*
|
||||||
|
* @param button_mask
|
||||||
|
* An integer value representing the current state of each button, where
|
||||||
|
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||||
|
* button is currently pressed. The lowest-order bit is the left mouse
|
||||||
|
* button, followed by the middle button, right button, and finally the up
|
||||||
|
* and down buttons of the scroll wheel.
|
||||||
|
*
|
||||||
|
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||||
|
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||||
|
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||||
|
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||||
|
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||||
*/
|
*/
|
||||||
void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
|
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
|
||||||
int x, int y);
|
int x, int y, int button_mask);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cursor image to the given raw image data. This raw image data must
|
* Sets the cursor image to the given raw image data. This raw image data must
|
||||||
|
31
src/common/common/defaults.h
Normal file
31
src/common/common/defaults.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUAC_COMMON_DEFAULTS_H
|
||||||
|
#define GUAC_COMMON_DEFAULTS_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default number of seconds to wait after sending the Wake-on-LAN packet
|
||||||
|
* for the destination host to start responding.
|
||||||
|
*/
|
||||||
|
#define GUAC_WOL_DEFAULT_BOOT_WAIT_TIME 0
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* GUAC_COMMON_DEFAULTS_H */
|
||||||
|
|
@ -99,6 +99,13 @@ typedef struct guac_common_display {
|
|||||||
*/
|
*/
|
||||||
guac_common_display_layer* buffers;
|
guac_common_display_layer* buffers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-zero if all graphical updates for this display should use lossless
|
||||||
|
* compression, 0 otherwise. By default, newly-created displays will use
|
||||||
|
* lossy compression when heuristics determine it is appropriate.
|
||||||
|
*/
|
||||||
|
int lossless;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutex which is locked internally when access to the display must be
|
* Mutex which is locked internally when access to the display must be
|
||||||
* synchronized. All public functions of guac_common_display should be
|
* synchronized. All public functions of guac_common_display should be
|
||||||
@ -228,5 +235,27 @@ void guac_common_display_free_layer(guac_common_display* display,
|
|||||||
void guac_common_display_free_buffer(guac_common_display* display,
|
void guac_common_display_free_buffer(guac_common_display* display,
|
||||||
guac_common_display_layer* display_buffer);
|
guac_common_display_layer* display_buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the overall lossless compression policy of the given display to the
|
||||||
|
* given value, affecting all current and future layers/buffers maintained by
|
||||||
|
* the display. By default, newly-created displays will use lossy compression
|
||||||
|
* for graphical updates when heuristics determine that doing so is
|
||||||
|
* appropriate. Specifying a non-zero value here will force all graphical
|
||||||
|
* updates to always use lossless compression, whereas specifying zero will
|
||||||
|
* restore the default policy.
|
||||||
|
*
|
||||||
|
* Note that this can also be adjusted on a per-layer / per-buffer basis with
|
||||||
|
* guac_common_surface_set_lossless().
|
||||||
|
*
|
||||||
|
* @param display
|
||||||
|
* The display to modify.
|
||||||
|
*
|
||||||
|
* @param lossless
|
||||||
|
* Non-zero if all graphical updates for this display should use lossless
|
||||||
|
* compression, 0 otherwise.
|
||||||
|
*/
|
||||||
|
void guac_common_display_set_lossless(guac_common_display* display,
|
||||||
|
int lossless);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -76,6 +76,30 @@ guac_iconv_read GUAC_READ_CP1252;
|
|||||||
*/
|
*/
|
||||||
guac_iconv_read GUAC_READ_ISO8859_1;
|
guac_iconv_read GUAC_READ_ISO8859_1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read function for UTF-8 which normalizes newline character sequences like
|
||||||
|
* "\r\n" to Unix-style newlines ('\n').
|
||||||
|
*/
|
||||||
|
guac_iconv_read GUAC_READ_UTF8_NORMALIZED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read function for UTF-16 which normalizes newline character sequences like
|
||||||
|
* "\r\n" to Unix-style newlines ('\n').
|
||||||
|
*/
|
||||||
|
guac_iconv_read GUAC_READ_UTF16_NORMALIZED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read function for CP-1252 which normalizes newline character sequences like
|
||||||
|
* "\r\n" to Unix-style newlines ('\n').
|
||||||
|
*/
|
||||||
|
guac_iconv_read GUAC_READ_CP1252_NORMALIZED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read function for ISO 8859-1 which normalizes newline character sequences
|
||||||
|
* like "\r\n" to Unix-style newlines ('\n').
|
||||||
|
*/
|
||||||
|
guac_iconv_read GUAC_READ_ISO8859_1_NORMALIZED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write function for UTF8.
|
* Write function for UTF8.
|
||||||
*/
|
*/
|
||||||
@ -96,5 +120,29 @@ guac_iconv_write GUAC_WRITE_CP1252;
|
|||||||
*/
|
*/
|
||||||
guac_iconv_write GUAC_WRITE_ISO8859_1;
|
guac_iconv_write GUAC_WRITE_ISO8859_1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write function for UTF-8 which writes newline characters ('\n') as
|
||||||
|
* Windows-style newlines ("\r\n").
|
||||||
|
*/
|
||||||
|
guac_iconv_write GUAC_WRITE_UTF8_CRLF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write function for UTF-16 which writes newline characters ('\n') as
|
||||||
|
* Windows-style newlines ("\r\n").
|
||||||
|
*/
|
||||||
|
guac_iconv_write GUAC_WRITE_UTF16_CRLF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write function for CP-1252 which writes newline characters ('\n') as
|
||||||
|
* Windows-style newlines ("\r\n").
|
||||||
|
*/
|
||||||
|
guac_iconv_write GUAC_WRITE_CP1252_CRLF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write function for ISO 8859-1 which writes newline characters ('\n') as
|
||||||
|
* Windows-style newlines ("\r\n").
|
||||||
|
*/
|
||||||
|
guac_iconv_write GUAC_WRITE_ISO8859_1_CRLF;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef GUAC_COMMON_RECORDING_H
|
|
||||||
#define GUAC_COMMON_RECORDING_H
|
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum numeric value allowed for the .1, .2, .3, etc. suffix appended
|
|
||||||
* to the end of the session recording filename if a recording having the
|
|
||||||
* requested name already exists.
|
|
||||||
*/
|
|
||||||
#define GUAC_COMMON_RECORDING_MAX_SUFFIX 255
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum length of the string containing a sequential numeric suffix
|
|
||||||
* between 1 and GUAC_COMMON_RECORDING_MAX_SUFFIX inclusive, in bytes,
|
|
||||||
* including NULL terminator.
|
|
||||||
*/
|
|
||||||
#define GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH 4
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum overall length of the full path to the session recording file,
|
|
||||||
* including any additional suffix and NULL terminator, in bytes.
|
|
||||||
*/
|
|
||||||
#define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the socket of the given client such that all further Guacamole
|
|
||||||
* protocol output will be copied into a file within the given path and having
|
|
||||||
* the given name. If the create_path flag is non-zero, the given path will be
|
|
||||||
* created if it does not yet exist. If creation of the recording file or path
|
|
||||||
* fails, error messages will automatically be logged, and no recording will be
|
|
||||||
* written. The recording will automatically be closed once the client is
|
|
||||||
* freed.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The client whose output should be copied to a recording file.
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
* The full absolute path to a directory in which the recording file should
|
|
||||||
* be created.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The base name to use for the recording file created within the specified
|
|
||||||
* path.
|
|
||||||
*
|
|
||||||
* @param create_path
|
|
||||||
* Zero if the specified path MUST exist for the recording file to be
|
|
||||||
* written, or non-zero if the path should be created if it does not yet
|
|
||||||
* exist.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the recording file has been successfully created and a recording
|
|
||||||
* will be written, non-zero otherwise.
|
|
||||||
*/
|
|
||||||
int guac_common_recording_create(guac_client* client, const char* path,
|
|
||||||
const char* name, int create_path);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -120,6 +120,19 @@ typedef struct guac_common_surface {
|
|||||||
*/
|
*/
|
||||||
guac_socket* socket;
|
guac_socket* socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of simultaneous touches that this surface can accept, where 0
|
||||||
|
* indicates that the surface does not support touch events at all.
|
||||||
|
*/
|
||||||
|
int touches;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-zero if all graphical updates for this surface should use lossless
|
||||||
|
* compression, 0 otherwise. By default, newly-created surfaces will use
|
||||||
|
* lossy compression when heuristics determine it is appropriate.
|
||||||
|
*/
|
||||||
|
int lossless;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The X coordinate of the upper-left corner of this layer, in pixels,
|
* The X coordinate of the upper-left corner of this layer, in pixels,
|
||||||
* relative to its parent layer. This is only applicable to visible
|
* relative to its parent layer. This is only applicable to visible
|
||||||
@ -279,14 +292,25 @@ void guac_common_surface_free(guac_common_surface* surface);
|
|||||||
void guac_common_surface_resize(guac_common_surface* surface, int w, int h);
|
void guac_common_surface_resize(guac_common_surface* surface, int w, int h);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the given data to the given guac_common_surface.
|
* Draws the given data to the given guac_common_surface. If the source surface
|
||||||
|
* is ARGB, the draw operation will be performed using the Porter-Duff "over"
|
||||||
|
* composite operator. If the source surface is RGB (no alpha channel), no
|
||||||
|
* compositing is performed and destination pixels are ignored.
|
||||||
*
|
*
|
||||||
* @param surface The surface to draw to.
|
* @param surface
|
||||||
* @param x The X coordinate of the draw location.
|
* The surface to draw to.
|
||||||
* @param y The Y coordinate of the draw location.
|
*
|
||||||
* @param src The Cairo surface to retrieve data from.
|
* @param x
|
||||||
|
* The X coordinate of the draw location.
|
||||||
|
*
|
||||||
|
* @param y
|
||||||
|
* The Y coordinate of the draw location.
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* The Cairo surface to retrieve data from.
|
||||||
*/
|
*/
|
||||||
void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_surface_t* src);
|
void guac_common_surface_draw(guac_common_surface* surface, int x, int y,
|
||||||
|
cairo_surface_t* src);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paints to the given guac_common_surface using the given data as a stencil,
|
* Paints to the given guac_common_surface using the given data as a stencil,
|
||||||
@ -475,5 +499,41 @@ void guac_common_surface_flush(guac_common_surface* surface);
|
|||||||
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
|
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
|
||||||
guac_socket* socket);
|
guac_socket* socket);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declares that the given surface should receive touch events. By default,
|
||||||
|
* surfaces are assumed to not expect touch events. This value is advisory, and
|
||||||
|
* the client is not required to honor the declared level of touch support.
|
||||||
|
* Implementations are expected to safely handle or ignore any received touch
|
||||||
|
* events, regardless of the level of touch support declared. regardless of
|
||||||
|
* the level of touch support declared.
|
||||||
|
*
|
||||||
|
* @param surface
|
||||||
|
* The surface to modify.
|
||||||
|
*
|
||||||
|
* @param touches
|
||||||
|
* The number of simultaneous touches that this surface can accept, where 0
|
||||||
|
* indicates that the surface does not support touch events at all.
|
||||||
|
*/
|
||||||
|
void guac_common_surface_set_multitouch(guac_common_surface* surface,
|
||||||
|
int touches);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lossless compression policy of the given surface to the given
|
||||||
|
* value. By default, newly-created surfaces will use lossy compression for
|
||||||
|
* graphical updates when heuristics determine that doing so is appropriate.
|
||||||
|
* Specifying a non-zero value here will force all graphical updates to always
|
||||||
|
* use lossless compression, whereas specifying zero will restore the default
|
||||||
|
* policy.
|
||||||
|
*
|
||||||
|
* @param surface
|
||||||
|
* The surface to modify.
|
||||||
|
*
|
||||||
|
* @param lossless
|
||||||
|
* Non-zero if all graphical updates for this surface should use lossless
|
||||||
|
* compression, 0 otherwise.
|
||||||
|
*/
|
||||||
|
void guac_common_surface_set_lossless(guac_common_surface* surface,
|
||||||
|
int lossless);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -26,24 +26,34 @@
|
|||||||
|
|
||||||
#include <cairo/cairo.h>
|
#include <cairo/cairo.h>
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/layer.h>
|
|
||||||
#include <guacamole/protocol.h>
|
#include <guacamole/protocol.h>
|
||||||
#include <guacamole/socket.h>
|
#include <guacamole/socket.h>
|
||||||
|
#include <guacamole/timestamp.h>
|
||||||
#include <guacamole/user.h>
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a cursor as well as an image buffer where the cursor is rendered.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The client owning the cursor.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The newly-allocated cursor or NULL if cursor cannot be allocated.
|
||||||
|
*/
|
||||||
guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
|
guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
|
||||||
|
|
||||||
guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor));
|
guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor));
|
||||||
if (cursor == NULL)
|
if (cursor == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Associate cursor with client and allocate cursor layer */
|
/* Associate cursor with client and allocate cursor buffer */
|
||||||
cursor->client = client;
|
cursor->client = client;
|
||||||
cursor->layer= guac_client_alloc_layer(client);
|
cursor->buffer = guac_client_alloc_buffer(client);
|
||||||
|
|
||||||
/* Allocate initial image buffer */
|
/* Allocate initial image buffer */
|
||||||
cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE;
|
cursor->image_buffer_size = GUAC_COMMON_CURSOR_DEFAULT_SIZE;
|
||||||
@ -58,6 +68,7 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
|
|||||||
|
|
||||||
/* No user has moved the mouse yet */
|
/* No user has moved the mouse yet */
|
||||||
cursor->user = NULL;
|
cursor->user = NULL;
|
||||||
|
cursor->timestamp = guac_timestamp_current();
|
||||||
|
|
||||||
/* Start cursor in upper-left */
|
/* Start cursor in upper-left */
|
||||||
cursor->x = 0;
|
cursor->x = 0;
|
||||||
@ -70,7 +81,7 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
|
|||||||
void guac_common_cursor_free(guac_common_cursor* cursor) {
|
void guac_common_cursor_free(guac_common_cursor* cursor) {
|
||||||
|
|
||||||
guac_client* client = cursor->client;
|
guac_client* client = cursor->client;
|
||||||
guac_layer* layer = cursor->layer;
|
guac_layer* buffer = cursor->buffer;
|
||||||
cairo_surface_t* surface = cursor->surface;
|
cairo_surface_t* surface = cursor->surface;
|
||||||
|
|
||||||
/* Free image buffer and surface */
|
/* Free image buffer and surface */
|
||||||
@ -78,11 +89,11 @@ void guac_common_cursor_free(guac_common_cursor* cursor) {
|
|||||||
if (surface != NULL)
|
if (surface != NULL)
|
||||||
cairo_surface_destroy(surface);
|
cairo_surface_destroy(surface);
|
||||||
|
|
||||||
/* Destroy layer within remotely-connected client */
|
/* Destroy buffer within remotely-connected client */
|
||||||
guac_protocol_send_dispose(client->socket, layer);
|
guac_protocol_send_dispose(client->socket, buffer);
|
||||||
|
|
||||||
/* Return layer to pool */
|
/* Return buffer to pool */
|
||||||
guac_client_free_layer(client, layer);
|
guac_client_free_buffer(client, buffer);
|
||||||
|
|
||||||
free(cursor);
|
free(cursor);
|
||||||
|
|
||||||
@ -92,18 +103,20 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
|||||||
guac_socket* socket) {
|
guac_socket* socket) {
|
||||||
|
|
||||||
/* Synchronize location */
|
/* Synchronize location */
|
||||||
guac_protocol_send_move(socket, cursor->layer, GUAC_DEFAULT_LAYER,
|
guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask,
|
||||||
cursor->x - cursor->hotspot_x,
|
cursor->timestamp);
|
||||||
cursor->y - cursor->hotspot_y,
|
|
||||||
INT_MAX);
|
|
||||||
|
|
||||||
/* Synchronize cursor image */
|
/* Synchronize cursor image */
|
||||||
if (cursor->surface != NULL) {
|
if (cursor->surface != NULL) {
|
||||||
guac_protocol_send_size(socket, cursor->layer,
|
guac_protocol_send_size(socket, cursor->buffer,
|
||||||
cursor->width, cursor->height);
|
cursor->width, cursor->height);
|
||||||
|
|
||||||
guac_user_stream_png(user, socket, GUAC_COMP_SRC,
|
guac_user_stream_png(user, socket, GUAC_COMP_SRC,
|
||||||
cursor->layer, 0, 0, cursor->surface);
|
cursor->buffer, 0, 0, cursor->surface);
|
||||||
|
|
||||||
|
guac_protocol_send_cursor(socket,
|
||||||
|
cursor->hotspot_x, cursor->hotspot_y,
|
||||||
|
cursor->buffer, 0, 0, cursor->width, cursor->height);
|
||||||
}
|
}
|
||||||
|
|
||||||
guac_socket_flush(socket);
|
guac_socket_flush(socket);
|
||||||
@ -111,28 +124,26 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for guac_client_for_user() which shows the cursor layer for the
|
* Callback for guac_client_foreach_user() which sends the current cursor
|
||||||
* given user (if they exist). The cursor layer is normally hidden when a user
|
* position and button state to any given user except the user that moved the
|
||||||
* is moving the mouse, and will only be shown if a DIFFERENT user is moving
|
* cursor last.
|
||||||
* the mouse.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to show the cursor to, or NULL if that user does not exist.
|
|
||||||
*
|
*
|
||||||
* @param data
|
* @param data
|
||||||
* A pointer to the guac_common_cursor structure describing the cursor to
|
* A pointer to the guac_common_cursor whose state should be broadcast to
|
||||||
* be shown.
|
* all users except the user that moved the cursor last.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* Always NULL.
|
* Always NULL.
|
||||||
*/
|
*/
|
||||||
static void* guac_common_cursor_show(guac_user* user, void* data) {
|
static void* guac_common_cursor_broadcast_state(guac_user* user,
|
||||||
|
void* data) {
|
||||||
|
|
||||||
guac_common_cursor* cursor = (guac_common_cursor*) data;
|
guac_common_cursor* cursor = (guac_common_cursor*) data;
|
||||||
|
|
||||||
/* Make cursor layer visible to given user */
|
/* Send cursor state only if the user is not moving the cursor */
|
||||||
if (user != NULL) {
|
if (user != cursor->user) {
|
||||||
guac_protocol_send_shade(user->socket, cursor->layer, 255);
|
guac_protocol_send_mouse(user->socket, cursor->x, cursor->y,
|
||||||
|
cursor->button_mask, cursor->timestamp);
|
||||||
guac_socket_flush(user->socket);
|
guac_socket_flush(user->socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,42 +151,23 @@ static void* guac_common_cursor_show(guac_user* user, void* data) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
|
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user,
|
||||||
int x, int y) {
|
int x, int y, int button_mask) {
|
||||||
|
|
||||||
guac_user* last_user = cursor->user;
|
|
||||||
|
|
||||||
/* Update current user of cursor */
|
/* Update current user of cursor */
|
||||||
if (last_user != user) {
|
|
||||||
|
|
||||||
cursor->user = user;
|
cursor->user = user;
|
||||||
|
|
||||||
/* Make cursor layer visible to previous user */
|
/* Update cursor state */
|
||||||
guac_client_for_user(cursor->client, last_user,
|
|
||||||
guac_common_cursor_show, cursor);
|
|
||||||
|
|
||||||
/* Show hardware cursor */
|
|
||||||
guac_protocol_send_cursor(user->socket,
|
|
||||||
cursor->hotspot_x, cursor->hotspot_y,
|
|
||||||
cursor->layer, 0, 0, cursor->width, cursor->height);
|
|
||||||
|
|
||||||
/* Hide cursor layer from new user */
|
|
||||||
guac_protocol_send_shade(user->socket, cursor->layer, 0);
|
|
||||||
guac_socket_flush(user->socket);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update cursor position */
|
|
||||||
cursor->x = x;
|
cursor->x = x;
|
||||||
cursor->y = y;
|
cursor->y = y;
|
||||||
|
cursor->button_mask = button_mask;
|
||||||
|
|
||||||
guac_protocol_send_move(cursor->client->socket, cursor->layer,
|
/* Store time at which cursor was updated */
|
||||||
GUAC_DEFAULT_LAYER,
|
cursor->timestamp = guac_timestamp_current();
|
||||||
x - cursor->hotspot_x,
|
|
||||||
y - cursor->hotspot_y,
|
|
||||||
INT_MAX);
|
|
||||||
|
|
||||||
guac_socket_flush(cursor->client->socket);
|
/* Notify all other users of change in cursor state */
|
||||||
|
guac_client_foreach_user(cursor->client,
|
||||||
|
guac_common_cursor_broadcast_state, cursor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,65 +209,6 @@ static void guac_common_cursor_resize(guac_common_cursor* cursor,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for guac_client_foreach_user() which sends the current cursor image
|
|
||||||
* as PNG data to each connected client.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to send the cursor image to.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* A pointer to the guac_common_cursor structure containing the cursor
|
|
||||||
* image that should be sent to the given user.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* __send_user_cursor_image(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
guac_common_cursor* cursor = (guac_common_cursor*) data;
|
|
||||||
|
|
||||||
guac_user_stream_png(user, user->socket, GUAC_COMP_SRC,
|
|
||||||
cursor->layer, 0, 0, cursor->surface);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for guac_client_for_user() which updates the hardware cursor and
|
|
||||||
* hotspot for the given user (if they exist). The hardware cursor image is
|
|
||||||
* normally hidden when a user is not moving the mouse, and will only be shown
|
|
||||||
* if that user begins moving the mouse.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user whose hardware cursor should be updated, or NULL if that user
|
|
||||||
* does not exist.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* A pointer to the guac_common_cursor structure describing the cursor to
|
|
||||||
* be sent as the hardware cursor.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Always NULL.
|
|
||||||
*/
|
|
||||||
static void* guac_common_cursor_update(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
guac_common_cursor* cursor = (guac_common_cursor*) data;
|
|
||||||
|
|
||||||
/* Update hardware cursor of current user */
|
|
||||||
if (user != NULL) {
|
|
||||||
guac_protocol_send_cursor(user->socket,
|
|
||||||
cursor->hotspot_x, cursor->hotspot_y,
|
|
||||||
cursor->layer, 0, 0, cursor->width, cursor->height);
|
|
||||||
|
|
||||||
guac_socket_flush(user->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
|
void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
|
||||||
unsigned const char* data, int width, int height, int stride) {
|
unsigned const char* data, int width, int height, int stride) {
|
||||||
|
|
||||||
@ -295,26 +228,20 @@ void guac_common_cursor_set_argb(guac_common_cursor* cursor, int hx, int hy,
|
|||||||
cursor->hotspot_x = hx;
|
cursor->hotspot_x = hx;
|
||||||
cursor->hotspot_y = hy;
|
cursor->hotspot_y = hy;
|
||||||
|
|
||||||
/* Update location based on new hotspot */
|
|
||||||
guac_protocol_send_move(cursor->client->socket, cursor->layer,
|
|
||||||
GUAC_DEFAULT_LAYER,
|
|
||||||
cursor->x - hx,
|
|
||||||
cursor->y - hy,
|
|
||||||
INT_MAX);
|
|
||||||
|
|
||||||
/* Broadcast new cursor image to all users */
|
/* Broadcast new cursor image to all users */
|
||||||
guac_protocol_send_size(cursor->client->socket, cursor->layer,
|
guac_protocol_send_size(cursor->client->socket, cursor->buffer,
|
||||||
width, height);
|
width, height);
|
||||||
|
|
||||||
guac_client_foreach_user(cursor->client, __send_user_cursor_image, cursor);
|
guac_client_stream_png(cursor->client, cursor->client->socket,
|
||||||
|
GUAC_COMP_SRC, cursor->buffer, 0, 0, cursor->surface);
|
||||||
|
|
||||||
|
/* Update cursor image */
|
||||||
|
guac_protocol_send_cursor(cursor->client->socket,
|
||||||
|
cursor->hotspot_x, cursor->hotspot_y,
|
||||||
|
cursor->buffer, 0, 0, cursor->width, cursor->height);
|
||||||
|
|
||||||
guac_socket_flush(cursor->client->socket);
|
guac_socket_flush(cursor->client->socket);
|
||||||
|
|
||||||
/* Update hardware cursor of current user (if they are indeed valid) */
|
|
||||||
if (cursor->user != NULL)
|
|
||||||
guac_client_for_user(cursor->client, cursor->user,
|
|
||||||
guac_common_cursor_update, cursor);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy,
|
void guac_common_cursor_set_surface(guac_common_cursor* cursor, int hx, int hy,
|
||||||
|
@ -99,6 +99,22 @@ static void guac_common_display_free_layers(guac_common_display_layer* layers,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a display and a cursor which are used to represent the remote
|
||||||
|
* display and cursor.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The client owning the cursor.
|
||||||
|
*
|
||||||
|
* @param width
|
||||||
|
* The desired width of the display.
|
||||||
|
*
|
||||||
|
* @param height
|
||||||
|
* The desired height of the display.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The newly-allocated display or NULL if display cannot be allocated.
|
||||||
|
*/
|
||||||
guac_common_display* guac_common_display_alloc(guac_client* client,
|
guac_common_display* guac_common_display_alloc(guac_client* client,
|
||||||
int width, int height) {
|
int width, int height) {
|
||||||
|
|
||||||
@ -107,14 +123,18 @@ guac_common_display* guac_common_display_alloc(guac_client* client,
|
|||||||
if (display == NULL)
|
if (display == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
/* Allocate shared cursor */
|
||||||
|
display->cursor = guac_common_cursor_alloc(client);
|
||||||
|
if (display->cursor == NULL) {
|
||||||
|
free(display);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_init(&display->_lock, NULL);
|
pthread_mutex_init(&display->_lock, NULL);
|
||||||
|
|
||||||
/* Associate display with given client */
|
/* Associate display with given client */
|
||||||
display->client = client;
|
display->client = client;
|
||||||
|
|
||||||
/* Allocate shared cursor */
|
|
||||||
display->cursor = guac_common_cursor_alloc(client);
|
|
||||||
|
|
||||||
display->default_surface = guac_common_surface_alloc(client,
|
display->default_surface = guac_common_surface_alloc(client,
|
||||||
client->socket, GUAC_DEFAULT_LAYER, width, height);
|
client->socket, GUAC_DEFAULT_LAYER, width, height);
|
||||||
|
|
||||||
@ -146,6 +166,8 @@ void guac_common_display_free(guac_common_display* display) {
|
|||||||
void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
||||||
guac_socket* socket) {
|
guac_socket* socket) {
|
||||||
|
|
||||||
|
guac_client* client = user->client;
|
||||||
|
|
||||||
pthread_mutex_lock(&display->_lock);
|
pthread_mutex_lock(&display->_lock);
|
||||||
|
|
||||||
/* Sunchronize shared cursor */
|
/* Sunchronize shared cursor */
|
||||||
@ -158,6 +180,33 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
|||||||
guac_common_display_dup_layers(display->layers, user, socket);
|
guac_common_display_dup_layers(display->layers, user, socket);
|
||||||
guac_common_display_dup_layers(display->buffers, user, socket);
|
guac_common_display_dup_layers(display->buffers, user, socket);
|
||||||
|
|
||||||
|
/* Sends a sync instruction to mark the boundary of the first frame */
|
||||||
|
guac_protocol_send_sync(socket, client->last_sent_timestamp, 1);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&display->_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_common_display_set_lossless(guac_common_display* display,
|
||||||
|
int lossless) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&display->_lock);
|
||||||
|
|
||||||
|
/* Update lossless setting to be applied to all newly-allocated
|
||||||
|
* layers/buffers */
|
||||||
|
display->lossless = lossless;
|
||||||
|
|
||||||
|
/* Update losslessness of all allocated layers/buffers */
|
||||||
|
guac_common_display_layer* current = display->layers;
|
||||||
|
while (current != NULL) {
|
||||||
|
guac_common_surface_set_lossless(current->surface, lossless);
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update losslessness of default display layer (not included within layers
|
||||||
|
* list) */
|
||||||
|
guac_common_surface_set_lossless(display->default_surface, lossless);
|
||||||
|
|
||||||
pthread_mutex_unlock(&display->_lock);
|
pthread_mutex_unlock(&display->_lock);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -267,6 +316,9 @@ guac_common_display_layer* guac_common_display_alloc_layer(
|
|||||||
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
||||||
display->client->socket, layer, width, height);
|
display->client->socket, layer, width, height);
|
||||||
|
|
||||||
|
/* Apply current display losslessness */
|
||||||
|
guac_common_surface_set_lossless(surface, display->lossless);
|
||||||
|
|
||||||
/* Add layer and surface to list */
|
/* Add layer and surface to list */
|
||||||
guac_common_display_layer* display_layer =
|
guac_common_display_layer* display_layer =
|
||||||
guac_common_display_add_layer(&display->layers, layer, surface);
|
guac_common_display_add_layer(&display->layers, layer, surface);
|
||||||
@ -288,6 +340,9 @@ guac_common_display_layer* guac_common_display_alloc_buffer(
|
|||||||
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
guac_common_surface* surface = guac_common_surface_alloc(display->client,
|
||||||
display->client->socket, buffer, width, height);
|
display->client->socket, buffer, width, height);
|
||||||
|
|
||||||
|
/* Apply current display losslessness */
|
||||||
|
guac_common_surface_set_lossless(surface, display->lossless);
|
||||||
|
|
||||||
/* Add buffer and surface to list */
|
/* Add buffer and surface to list */
|
||||||
guac_common_display_layer* display_layer =
|
guac_common_display_layer* display_layer =
|
||||||
guac_common_display_add_layer(&display->buffers, buffer, surface);
|
guac_common_display_add_layer(&display->buffers, buffer, surface);
|
||||||
@ -334,4 +389,3 @@ void guac_common_display_free_buffer(guac_common_display* display,
|
|||||||
pthread_mutex_unlock(&display->_lock);
|
pthread_mutex_unlock(&display->_lock);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,70 @@ int GUAC_READ_ISO8859_1(const char** input, int remaining) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given reader function, automatically normalizing newline
|
||||||
|
* sequences as Unix-style newline characters ('\n'). All other charaters are
|
||||||
|
* read verbatim.
|
||||||
|
*
|
||||||
|
* @param reader
|
||||||
|
* The reader to use to read the given character.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* Pointer to the location within the input buffer that the next character
|
||||||
|
* should be read from.
|
||||||
|
*
|
||||||
|
* @param remaining
|
||||||
|
* The number of bytes remaining in the input buffer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The codepoint that was read, or zero if the end of the input string has
|
||||||
|
* been reached.
|
||||||
|
*/
|
||||||
|
static int guac_iconv_read_normalized(guac_iconv_read* reader,
|
||||||
|
const char** input, int remaining) {
|
||||||
|
|
||||||
|
/* Read requested character */
|
||||||
|
const char* input_start = *input;
|
||||||
|
int value = reader(input, remaining);
|
||||||
|
|
||||||
|
/* Automatically translate CRLF pairs to simple newlines */
|
||||||
|
if (value == '\r') {
|
||||||
|
|
||||||
|
/* Peek ahead by one character, adjusting remaining bytes relative to
|
||||||
|
* last read */
|
||||||
|
int peek_remaining = remaining - (*input - input_start);
|
||||||
|
const char* peek_input = *input;
|
||||||
|
int peek_value = reader(&peek_input, peek_remaining);
|
||||||
|
|
||||||
|
/* Consider read value to be a newline if we have encountered a "\r\n"
|
||||||
|
* (CRLF) pair */
|
||||||
|
if (peek_value == '\n') {
|
||||||
|
value = '\n';
|
||||||
|
*input = peek_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int GUAC_READ_UTF8_NORMALIZED(const char** input, int remaining) {
|
||||||
|
return guac_iconv_read_normalized(GUAC_READ_UTF8, input, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GUAC_READ_UTF16_NORMALIZED(const char** input, int remaining) {
|
||||||
|
return guac_iconv_read_normalized(GUAC_READ_UTF16, input, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GUAC_READ_CP1252_NORMALIZED(const char** input, int remaining) {
|
||||||
|
return guac_iconv_read_normalized(GUAC_READ_CP1252, input, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GUAC_READ_ISO8859_1_NORMALIZED(const char** input, int remaining) {
|
||||||
|
return guac_iconv_read_normalized(GUAC_READ_ISO8859_1, input, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
void GUAC_WRITE_UTF8(char** output, int remaining, int value) {
|
void GUAC_WRITE_UTF8(char** output, int remaining, int value) {
|
||||||
*output += guac_utf8_write(value, *output, remaining);
|
*output += guac_utf8_write(value, *output, remaining);
|
||||||
}
|
}
|
||||||
@ -190,3 +254,53 @@ void GUAC_WRITE_ISO8859_1(char** output, int remaining, int value) {
|
|||||||
(*output)++;
|
(*output)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given writer function, automatically writing newline characters
|
||||||
|
* ('\n') as CRLF ("\r\n"). All other charaters are written verbatim.
|
||||||
|
*
|
||||||
|
* @param writer
|
||||||
|
* The writer to use to write the given character.
|
||||||
|
*
|
||||||
|
* @param output
|
||||||
|
* Pointer to the location within the output buffer that the next character
|
||||||
|
* should be written.
|
||||||
|
*
|
||||||
|
* @param remaining
|
||||||
|
* The number of bytes remaining in the output buffer.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The codepoint of the character to write.
|
||||||
|
*/
|
||||||
|
static void guac_iconv_write_crlf(guac_iconv_write* writer, char** output,
|
||||||
|
int remaining, int value) {
|
||||||
|
|
||||||
|
if (value != '\n') {
|
||||||
|
writer(output, remaining, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* output_start = *output;
|
||||||
|
writer(output, remaining, '\r');
|
||||||
|
|
||||||
|
remaining -= *output - output_start;
|
||||||
|
if (remaining > 0)
|
||||||
|
writer(output, remaining, '\n');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUAC_WRITE_UTF8_CRLF(char** output, int remaining, int value) {
|
||||||
|
guac_iconv_write_crlf(GUAC_WRITE_UTF8, output, remaining, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUAC_WRITE_UTF16_CRLF(char** output, int remaining, int value) {
|
||||||
|
guac_iconv_write_crlf(GUAC_WRITE_UTF16, output, remaining, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUAC_WRITE_CP1252_CRLF(char** output, int remaining, int value) {
|
||||||
|
guac_iconv_write_crlf(GUAC_WRITE_CP1252, output, remaining, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUAC_WRITE_ISO8859_1_CRLF(char** output, int remaining, int value) {
|
||||||
|
guac_iconv_write_crlf(GUAC_WRITE_ISO8859_1, output, remaining, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -97,15 +97,15 @@ int guac_common_json_write_string(guac_user* user,
|
|||||||
const char* current = str;
|
const char* current = str;
|
||||||
for (; *current != '\0'; current++) {
|
for (; *current != '\0'; current++) {
|
||||||
|
|
||||||
/* Escape all quotes */
|
/* Escape all quotes and back-slashes */
|
||||||
if (*current == '"') {
|
if (*current == '"' || *current == '\\') {
|
||||||
|
|
||||||
/* Write any string content up to current character */
|
/* Write any string content up to current character */
|
||||||
if (current != str)
|
if (current != str)
|
||||||
blob_written |= guac_common_json_write(user, stream,
|
blob_written |= guac_common_json_write(user, stream,
|
||||||
json_state, str, current - str);
|
json_state, str, current - str);
|
||||||
|
|
||||||
/* Escape the quote that was just read */
|
/* Escape the character that was just read */
|
||||||
blob_written |= guac_common_json_write(user, stream,
|
blob_written |= guac_common_json_write(user, stream,
|
||||||
json_state, "\\", 1);
|
json_state, "\\", 1);
|
||||||
|
|
||||||
|
@ -78,13 +78,6 @@
|
|||||||
#define cairo_format_stride_for_width(format, width) (width*4)
|
#define cairo_format_stride_for_width(format, width) (width*4)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* The JPEG image quality ('quantization') setting to use. Range 0-100 where
|
|
||||||
* 100 is the highest quality/largest file size, and 0 is the lowest
|
|
||||||
* quality/smallest file size.
|
|
||||||
*/
|
|
||||||
#define GUAC_SURFACE_JPEG_IMAGE_QUALITY 90
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The framerate which, if exceeded, indicates that JPEG is preferred.
|
* The framerate which, if exceeded, indicates that JPEG is preferred.
|
||||||
*/
|
*/
|
||||||
@ -96,13 +89,6 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096
|
#define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096
|
||||||
|
|
||||||
/**
|
|
||||||
* The WebP image quality ('quantization') setting to use. Range 0-100 where
|
|
||||||
* 100 is the highest quality/largest file size, and 0 is the lowest
|
|
||||||
* quality/smallest file size.
|
|
||||||
*/
|
|
||||||
#define GUAC_SURFACE_WEBP_IMAGE_QUALITY 90
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The JPEG compression min block size. This defines the optimal rectangle block
|
* The JPEG compression min block size. This defines the optimal rectangle block
|
||||||
* size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to
|
* size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to
|
||||||
@ -117,6 +103,28 @@
|
|||||||
*/
|
*/
|
||||||
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8
|
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8
|
||||||
|
|
||||||
|
void guac_common_surface_set_multitouch(guac_common_surface* surface,
|
||||||
|
int touches) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&surface->_lock);
|
||||||
|
|
||||||
|
surface->touches = touches;
|
||||||
|
guac_protocol_send_set_int(surface->socket, surface->layer,
|
||||||
|
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH, touches);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&surface->_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guac_common_surface_set_lossless(guac_common_surface* surface,
|
||||||
|
int lossless) {
|
||||||
|
|
||||||
|
pthread_mutex_lock(&surface->_lock);
|
||||||
|
surface->lossless = lossless;
|
||||||
|
pthread_mutex_unlock(&surface->_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void guac_common_surface_move(guac_common_surface* surface, int x, int y) {
|
void guac_common_surface_move(guac_common_surface* surface, int x, int y) {
|
||||||
|
|
||||||
pthread_mutex_lock(&surface->_lock);
|
pthread_mutex_lock(&surface->_lock);
|
||||||
@ -274,18 +282,31 @@ static int __guac_common_surface_is_opaque(guac_common_surface* surface,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given rectangle should be combined into the existing
|
* Returns whether the given rectangle should be combined into the existing
|
||||||
* dirty rectangle, to be eventually flushed as a "png" instruction.
|
* dirty rectangle, to be eventually flushed as image data, or would be best
|
||||||
|
* kept independent of the current rectangle.
|
||||||
*
|
*
|
||||||
* @param surface The surface to be queried.
|
* @param surface
|
||||||
* @param rect The update rectangle.
|
* The surface being updated.
|
||||||
* @param rect_only Non-zero if this update, by its nature, contains only
|
*
|
||||||
* metainformation about the update's rectangle, zero if
|
* @param rect
|
||||||
* the update also contains image data.
|
* The bounding rectangle of the update being made to the surface.
|
||||||
* @return Non-zero if the update should be combined with any existing update,
|
*
|
||||||
* zero otherwise.
|
* @param rect_only
|
||||||
|
* Non-zero if this update, by its nature, contains only metainformation
|
||||||
|
* about the update's bounding rectangle, zero if the update also contains
|
||||||
|
* image data.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Non-zero if the update should be combined with any existing update, zero
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) {
|
static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) {
|
||||||
|
|
||||||
|
/* Always favor combining updates if surface is currently a purely
|
||||||
|
* server-side scratch area */
|
||||||
|
if (!surface->realized)
|
||||||
|
return 1;
|
||||||
|
|
||||||
if (surface->dirty) {
|
if (surface->dirty) {
|
||||||
|
|
||||||
int combined_cost, dirty_cost, update_cost;
|
int combined_cost, dirty_cost, update_cost;
|
||||||
@ -522,6 +543,10 @@ static int __guac_common_surface_png_optimality(guac_common_surface* surface,
|
|||||||
static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface,
|
static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface,
|
||||||
const guac_common_rect* rect) {
|
const guac_common_rect* rect) {
|
||||||
|
|
||||||
|
/* Do not use JPEG if lossless quality is required */
|
||||||
|
if (surface->lossless)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* Calculate the average framerate for the given rect */
|
/* Calculate the average framerate for the given rect */
|
||||||
int framerate = __guac_common_surface_calculate_framerate(surface, rect);
|
int framerate = __guac_common_surface_calculate_framerate(surface, rect);
|
||||||
|
|
||||||
@ -724,51 +749,51 @@ static int __guac_common_surface_transfer_int(guac_transfer_function op, uint32_
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NSRC:
|
case GUAC_TRANSFER_BINARY_NSRC:
|
||||||
*dst = ~(*src);
|
*dst = *src ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NDEST:
|
case GUAC_TRANSFER_BINARY_NDEST:
|
||||||
*dst = ~(*dst);
|
*dst = *dst ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_AND:
|
case GUAC_TRANSFER_BINARY_AND:
|
||||||
*dst = (*dst) & (*src);
|
*dst = ((*dst) & (0xFF000000 | *src));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NAND:
|
case GUAC_TRANSFER_BINARY_NAND:
|
||||||
*dst = ~((*dst) & (*src));
|
*dst = ((*dst) & (0xFF000000 | *src)) ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_OR:
|
case GUAC_TRANSFER_BINARY_OR:
|
||||||
*dst = (*dst) | (*src);
|
*dst = ((*dst) | (0x00FFFFFF & *src));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NOR:
|
case GUAC_TRANSFER_BINARY_NOR:
|
||||||
*dst = ~((*dst) | (*src));
|
*dst = ((*dst) | (0x00FFFFFF & *src)) ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_XOR:
|
case GUAC_TRANSFER_BINARY_XOR:
|
||||||
*dst = (*dst) ^ (*src);
|
*dst = ((*dst) ^ (0x00FFFFFF & *src));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_XNOR:
|
case GUAC_TRANSFER_BINARY_XNOR:
|
||||||
*dst = ~((*dst) ^ (*src));
|
*dst = ((*dst) ^ (0x00FFFFFF & *src)) ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NSRC_AND:
|
case GUAC_TRANSFER_BINARY_NSRC_AND:
|
||||||
*dst = (*dst) & ~(*src);
|
*dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NSRC_NAND:
|
case GUAC_TRANSFER_BINARY_NSRC_NAND:
|
||||||
*dst = ~((*dst) & ~(*src));
|
*dst = ((*dst) & (0xFF000000 | (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NSRC_OR:
|
case GUAC_TRANSFER_BINARY_NSRC_OR:
|
||||||
*dst = (*dst) | ~(*src);
|
*dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GUAC_TRANSFER_BINARY_NSRC_NOR:
|
case GUAC_TRANSFER_BINARY_NSRC_NOR:
|
||||||
*dst = ~((*dst) | ~(*src));
|
*dst = ((*dst) | (0x00FFFFFF & (*src ^ 0x00FFFFFF))) ^ 0x00FFFFFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -862,6 +887,83 @@ static void __guac_common_surface_set(guac_common_surface* dst,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the Porter-Duff "over" composite operator, blending the two given
|
||||||
|
* color components using the given alpha value.
|
||||||
|
*
|
||||||
|
* @param dst
|
||||||
|
* The destination color component.
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* The source color component.
|
||||||
|
*
|
||||||
|
* @param alpha
|
||||||
|
* The alpha value which applies to the blending operation.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The result of applying the Porter-Duff "over" composite operator to the
|
||||||
|
* given source and destination components.
|
||||||
|
*/
|
||||||
|
static int guac_common_surface_blend_component(int dst, int src, int alpha) {
|
||||||
|
|
||||||
|
int blended = src + dst * (0xFF - alpha);
|
||||||
|
|
||||||
|
/* Do not exceed maximum component value */
|
||||||
|
if (blended > 0xFF)
|
||||||
|
return 0xFF;
|
||||||
|
|
||||||
|
return blended;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the Porter-Duff "over" composite operator, blending each component
|
||||||
|
* of the two given ARGB colors.
|
||||||
|
*
|
||||||
|
* @param dst
|
||||||
|
* The destination ARGB color.
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
* The source ARGB color.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The result of applying the Porter-Duff "over" composite operator to the
|
||||||
|
* given source and destination colors.
|
||||||
|
*/
|
||||||
|
static uint32_t guac_common_surface_argb_blend(uint32_t dst, uint32_t src) {
|
||||||
|
|
||||||
|
/* Separate destination ARGB color into its components */
|
||||||
|
int dst_a = (dst >> 24) & 0xFF;
|
||||||
|
int dst_r = (dst >> 16) & 0xFF;
|
||||||
|
int dst_g = (dst >> 8) & 0xFF;
|
||||||
|
int dst_b = dst & 0xFF;
|
||||||
|
|
||||||
|
/* Separate source ARGB color into its components */
|
||||||
|
int src_a = (src >> 24) & 0xFF;
|
||||||
|
int src_r = (src >> 16) & 0xFF;
|
||||||
|
int src_g = (src >> 8) & 0xFF;
|
||||||
|
int src_b = src & 0xFF;
|
||||||
|
|
||||||
|
/* If source is fully opaque (or destination is fully transparent), the
|
||||||
|
* blended result is the source */
|
||||||
|
if (src_a == 0xFF || dst_a == 0x00)
|
||||||
|
return src;
|
||||||
|
|
||||||
|
/* If source is fully transparent, the blended result is the destination */
|
||||||
|
if (src_a == 0x00)
|
||||||
|
return dst;
|
||||||
|
|
||||||
|
/* Otherwise, blend each ARGB component, assuming pre-multiplied alpha */
|
||||||
|
int r = guac_common_surface_blend_component(dst_r, src_r, src_a);
|
||||||
|
int g = guac_common_surface_blend_component(dst_g, src_g, src_a);
|
||||||
|
int b = guac_common_surface_blend_component(dst_b, src_b, src_a);
|
||||||
|
int a = guac_common_surface_blend_component(dst_a, src_a, src_a);
|
||||||
|
|
||||||
|
/* Recombine blended components */
|
||||||
|
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies data from the given buffer to the surface at the given coordinates.
|
* Copies data from the given buffer to the surface at the given coordinates.
|
||||||
* The dimensions and location of the destination rectangle will be altered
|
* The dimensions and location of the destination rectangle will be altered
|
||||||
@ -906,22 +1008,34 @@ static void __guac_common_surface_put(unsigned char* src_buffer, int src_stride,
|
|||||||
/* Copy row */
|
/* Copy row */
|
||||||
for (x=0; x < rect->width; x++) {
|
for (x=0; x < rect->width; x++) {
|
||||||
|
|
||||||
if (opaque || (*src_current & 0xFF000000)) {
|
uint32_t color;
|
||||||
|
|
||||||
uint32_t new_color = *src_current | 0xFF000000;
|
/* Get source and destination color values */
|
||||||
uint32_t old_color = *dst_current;
|
uint32_t src_color = *src_current;
|
||||||
|
uint32_t dst_color = *dst_current;
|
||||||
|
|
||||||
if (old_color != new_color) {
|
/* Ignore alpha channel if opaque */
|
||||||
|
if (opaque)
|
||||||
|
color = src_color | 0xFF000000;
|
||||||
|
|
||||||
|
/* Otherwise, perform alpha blending operation */
|
||||||
|
else
|
||||||
|
color = guac_common_surface_argb_blend(dst_color, src_color);
|
||||||
|
|
||||||
|
/* If the destination color is changing, update rectangle bounds
|
||||||
|
* and store the new color */
|
||||||
|
if (dst_color != color) {
|
||||||
if (x < min_x) min_x = x;
|
if (x < min_x) min_x = x;
|
||||||
if (y < min_y) min_y = y;
|
if (y < min_y) min_y = y;
|
||||||
if (x > max_x) max_x = x;
|
if (x > max_x) max_x = x;
|
||||||
if (y > max_y) max_y = y;
|
if (y > max_y) max_y = y;
|
||||||
*dst_current = new_color;
|
*dst_current = color;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Advance to next pixel */
|
||||||
src_current++;
|
src_current++;
|
||||||
dst_current++;
|
dst_current++;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Next row */
|
/* Next row */
|
||||||
@ -1122,6 +1236,7 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client,
|
|||||||
surface->socket = socket;
|
surface->socket = socket;
|
||||||
surface->layer = layer;
|
surface->layer = layer;
|
||||||
surface->parent = GUAC_DEFAULT_LAYER;
|
surface->parent = GUAC_DEFAULT_LAYER;
|
||||||
|
surface->opacity = 0xFF;
|
||||||
surface->width = w;
|
surface->width = w;
|
||||||
surface->height = h;
|
surface->height = h;
|
||||||
|
|
||||||
@ -1576,6 +1691,36 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an appropriate quality between 0 and 100 for lossy encoding
|
||||||
|
* depending on the current processing lag calculated for the given client.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The client for which the lossy quality is being calculated.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A value between 0 and 100 inclusive which seems appropriate for the
|
||||||
|
* client based on lag measurements.
|
||||||
|
*/
|
||||||
|
static int guac_common_surface_suggest_quality(guac_client* client) {
|
||||||
|
|
||||||
|
int lag = guac_client_get_processing_lag(client);
|
||||||
|
|
||||||
|
/* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */
|
||||||
|
int quality = 90 - (lag - 20);
|
||||||
|
|
||||||
|
/* Do not exceed 90 for quality */
|
||||||
|
if (quality > 90)
|
||||||
|
return 90;
|
||||||
|
|
||||||
|
/* Do not go below 30 for quality */
|
||||||
|
if (quality < 30)
|
||||||
|
return 30;
|
||||||
|
|
||||||
|
return quality;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes the bitmap update currently described by the dirty rectangle within
|
* Flushes the bitmap update currently described by the dirty rectangle within
|
||||||
* the given surface directly via an "img" instruction as JPEG data. The
|
* the given surface directly via an "img" instruction as JPEG data. The
|
||||||
@ -1612,7 +1757,7 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) {
|
|||||||
/* Send JPEG for rect */
|
/* Send JPEG for rect */
|
||||||
guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer,
|
guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer,
|
||||||
surface->dirty_rect.x, surface->dirty_rect.y, rect,
|
surface->dirty_rect.x, surface->dirty_rect.y, rect,
|
||||||
GUAC_SURFACE_JPEG_IMAGE_QUALITY);
|
guac_common_surface_suggest_quality(surface->client));
|
||||||
|
|
||||||
cairo_surface_destroy(rect);
|
cairo_surface_destroy(rect);
|
||||||
surface->realized = 1;
|
surface->realized = 1;
|
||||||
@ -1674,7 +1819,8 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface,
|
|||||||
/* Send WebP for rect */
|
/* Send WebP for rect */
|
||||||
guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer,
|
guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer,
|
||||||
surface->dirty_rect.x, surface->dirty_rect.y, rect,
|
surface->dirty_rect.x, surface->dirty_rect.y, rect,
|
||||||
GUAC_SURFACE_WEBP_IMAGE_QUALITY, 0);
|
guac_common_surface_suggest_quality(surface->client),
|
||||||
|
surface->lossless ? 1 : 0);
|
||||||
|
|
||||||
cairo_surface_destroy(rect);
|
cairo_surface_destroy(rect);
|
||||||
surface->realized = 1;
|
surface->realized = 1;
|
||||||
@ -1862,6 +2008,11 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
|
|||||||
guac_protocol_send_move(socket, surface->layer,
|
guac_protocol_send_move(socket, surface->layer,
|
||||||
surface->parent, surface->x, surface->y, surface->z);
|
surface->parent, surface->x, surface->y, surface->z);
|
||||||
|
|
||||||
|
/* Synchronize multi-touch support level */
|
||||||
|
guac_protocol_send_set_int(surface->socket, surface->layer,
|
||||||
|
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
|
||||||
|
surface->touches);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sync size to new socket */
|
/* Sync size to new socket */
|
||||||
|
76
src/common/tests/Makefile.am
Normal file
76
src/common/tests/Makefile.am
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
|
#
|
||||||
|
# Unit tests for libguac_common
|
||||||
|
#
|
||||||
|
|
||||||
|
check_PROGRAMS = test_common
|
||||||
|
TESTS = $(check_PROGRAMS)
|
||||||
|
|
||||||
|
noinst_HEADERS = \
|
||||||
|
iconv/convert-test-data.h
|
||||||
|
|
||||||
|
test_common_SOURCES = \
|
||||||
|
iconv/convert.c \
|
||||||
|
iconv/convert-test-data.c \
|
||||||
|
rect/clip_and_split.c \
|
||||||
|
rect/constrain.c \
|
||||||
|
rect/expand_to_grid.c \
|
||||||
|
rect/extend.c \
|
||||||
|
rect/init.c \
|
||||||
|
rect/intersects.c \
|
||||||
|
string/count_occurrences.c \
|
||||||
|
string/split.c
|
||||||
|
|
||||||
|
test_common_CFLAGS = \
|
||||||
|
-Werror -Wall -pedantic \
|
||||||
|
@COMMON_INCLUDE@
|
||||||
|
|
||||||
|
test_common_LDADD = \
|
||||||
|
@COMMON_LTLIB@ \
|
||||||
|
@CUNIT_LIBS@
|
||||||
|
|
||||||
|
#
|
||||||
|
# Autogenerate test runner
|
||||||
|
#
|
||||||
|
|
||||||
|
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
|
||||||
|
CLEANFILES = _generated_runner.c
|
||||||
|
|
||||||
|
_generated_runner.c: $(test_common_SOURCES)
|
||||||
|
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_SOURCES) > $@
|
||||||
|
|
||||||
|
nodist_test_common_SOURCES = \
|
||||||
|
_generated_runner.c
|
||||||
|
|
||||||
|
# Use automake's TAP test driver for running any tests
|
||||||
|
LOG_DRIVER = \
|
||||||
|
env AM_TAP_AWK='$(AWK)' \
|
||||||
|
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
||||||
|
|
153
src/common/tests/iconv/convert-test-data.c
Normal file
153
src/common/tests/iconv/convert-test-data.c
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/iconv.h"
|
||||||
|
#include "convert-test-data.h"
|
||||||
|
|
||||||
|
encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS] = {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UTF-8
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"UTF-8",
|
||||||
|
GUAC_READ_UTF8, GUAC_READ_UTF8_NORMALIZED,
|
||||||
|
GUAC_WRITE_UTF8, GUAC_WRITE_UTF8_CRLF,
|
||||||
|
.test_mixed = TEST_STRING(
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||||
|
),
|
||||||
|
.test_unix = TEST_STRING(
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||||
|
),
|
||||||
|
.test_windows = TEST_STRING(
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
|
||||||
|
"pap\xC3\xA0 \xC3\xA8 bello"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UTF-16
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"UTF-16",
|
||||||
|
GUAC_READ_UTF16, GUAC_READ_UTF16_NORMALIZED,
|
||||||
|
GUAC_WRITE_UTF16, GUAC_WRITE_UTF16_CRLF,
|
||||||
|
.test_mixed = TEST_STRING(
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||||
|
"\x00"
|
||||||
|
),
|
||||||
|
.test_unix = TEST_STRING(
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||||
|
"\x00"
|
||||||
|
),
|
||||||
|
.test_windows = TEST_STRING(
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
|
||||||
|
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
|
||||||
|
"\x00"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ISO 8859-1
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"ISO 8859-1",
|
||||||
|
GUAC_READ_ISO8859_1, GUAC_READ_ISO8859_1_NORMALIZED,
|
||||||
|
GUAC_WRITE_ISO8859_1, GUAC_WRITE_ISO8859_1_CRLF,
|
||||||
|
.test_mixed = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
),
|
||||||
|
.test_unix = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
),
|
||||||
|
.test_windows = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CP-1252
|
||||||
|
*/
|
||||||
|
|
||||||
|
{
|
||||||
|
"CP-1252",
|
||||||
|
GUAC_READ_CP1252, GUAC_READ_CP1252_NORMALIZED,
|
||||||
|
GUAC_WRITE_CP1252, GUAC_WRITE_CP1252_CRLF,
|
||||||
|
.test_mixed = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
),
|
||||||
|
.test_unix = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
),
|
||||||
|
.test_windows = TEST_STRING(
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello\r\n"
|
||||||
|
"pap\xE0 \xE8 bello"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
121
src/common/tests/iconv/convert-test-data.h
Normal file
121
src/common/tests/iconv/convert-test-data.h
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/iconv.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of test string data and its length in bytes.
|
||||||
|
*/
|
||||||
|
typedef struct test_string {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw content of the test string.
|
||||||
|
*/
|
||||||
|
unsigned char* buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes within the test string, including null terminator.
|
||||||
|
*/
|
||||||
|
int size;
|
||||||
|
|
||||||
|
} test_string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience macro which statically-initializes a test_string with the given
|
||||||
|
* string value, automatically calculating its size in bytes.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The string value.
|
||||||
|
*/
|
||||||
|
#define TEST_STRING(value) { \
|
||||||
|
.buffer = (unsigned char*) (value), \
|
||||||
|
.size = sizeof(value) \
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parameters applicable to a unit test for a particular encoding supported
|
||||||
|
* by guac_iconv().
|
||||||
|
*/
|
||||||
|
typedef struct encoding_test_parameters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The human-readable name of this encoding. This will be logged to the
|
||||||
|
* test suite log to assist with debugging test failures.
|
||||||
|
*/
|
||||||
|
const char* name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader function which reads using this encoding and does not perform any
|
||||||
|
* transformation on newline characters.
|
||||||
|
*/
|
||||||
|
guac_iconv_read* reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader function which reads using this encoding and automatically
|
||||||
|
* normalizes newline sequences to Unix-style newline characters.
|
||||||
|
*/
|
||||||
|
guac_iconv_read* reader_normalized;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writer function which writes using this encoding and does not perform
|
||||||
|
* any transformation on newline characters.
|
||||||
|
*/
|
||||||
|
guac_iconv_write* writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writer function which writes using this encoding, but writes newline
|
||||||
|
* characters as CRLF sequences.
|
||||||
|
*/
|
||||||
|
guac_iconv_write* writer_crlf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test string having both Windows- and Unix-style line endings. Except
|
||||||
|
* for the line endings, the characters represented within this test string
|
||||||
|
* must be identical to all other test strings.
|
||||||
|
*/
|
||||||
|
test_string test_mixed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test string having only Unix-style line endings. Except for the line
|
||||||
|
* endings, the characters represented within this test string must be
|
||||||
|
* identical to all other test strings.
|
||||||
|
*/
|
||||||
|
test_string test_unix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test string having only Windows-style line endings. Except for the
|
||||||
|
* line endings, the characters represented within this test string must be
|
||||||
|
* identical to all other test strings.
|
||||||
|
*/
|
||||||
|
test_string test_windows;
|
||||||
|
|
||||||
|
} encoding_test_parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of encodings supported by guac_iconv().
|
||||||
|
*/
|
||||||
|
#define NUM_SUPPORTED_ENCODINGS 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test parameters for each supported encoding. The test strings included each
|
||||||
|
* consist of five repeated lines of "papà è bello", omitting the line ending
|
||||||
|
* of the final line.
|
||||||
|
*/
|
||||||
|
extern encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS];
|
||||||
|
|
129
src/common/tests/iconv/convert.c
Normal file
129
src/common/tests/iconv/convert.c
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/iconv.h"
|
||||||
|
#include "convert-test-data.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that conversion between character sets using the given guac_iconv_read
|
||||||
|
* and guac_iconv_write implementations matches expectations.
|
||||||
|
*
|
||||||
|
* @param reader
|
||||||
|
* The guac_iconv_read implementation to use to read the input string.
|
||||||
|
*
|
||||||
|
* @param in_string
|
||||||
|
* A pointer to the test_string structure describing the input string being
|
||||||
|
* tested.
|
||||||
|
*
|
||||||
|
* @param writer
|
||||||
|
* The guac_iconv_write implementation to use to write the output string
|
||||||
|
* (the converted input string).
|
||||||
|
*
|
||||||
|
* @param out_string
|
||||||
|
* A pointer to the test_string structure describing the expected result of
|
||||||
|
* the conversion.
|
||||||
|
*/
|
||||||
|
static void verify_conversion(
|
||||||
|
guac_iconv_read* reader, test_string* in_string,
|
||||||
|
guac_iconv_write* writer, test_string* out_string) {
|
||||||
|
|
||||||
|
char output[4096];
|
||||||
|
char input[4096];
|
||||||
|
|
||||||
|
const char* current_input = input;
|
||||||
|
char* current_output = output;
|
||||||
|
|
||||||
|
memcpy(input, in_string->buffer, in_string->size);
|
||||||
|
guac_iconv(reader, ¤t_input, sizeof(input),
|
||||||
|
writer, ¤t_output, sizeof(output));
|
||||||
|
|
||||||
|
/* Verify output length */
|
||||||
|
CU_ASSERT_EQUAL(out_string->size, current_output - output);
|
||||||
|
|
||||||
|
/* Verify entire input read */
|
||||||
|
CU_ASSERT_EQUAL(in_string->size, current_input - input);
|
||||||
|
|
||||||
|
/* Verify output content */
|
||||||
|
CU_ASSERT_EQUAL(0, memcmp(output, out_string->buffer, out_string->size));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that every supported encoding can be correctly converted
|
||||||
|
* to every other supported encoding, with all line endings preserved verbatim
|
||||||
|
* (not normalized).
|
||||||
|
*/
|
||||||
|
void test_iconv__preserve() {
|
||||||
|
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||||
|
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||||
|
|
||||||
|
encoding_test_parameters* from = &test_params[i];
|
||||||
|
encoding_test_parameters* to = &test_params[j];
|
||||||
|
|
||||||
|
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||||
|
verify_conversion(from->reader, &from->test_mixed,
|
||||||
|
to->writer, &to->test_mixed);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that every supported encoding can be correctly converted
|
||||||
|
* to every other supported encoding, normalizing all line endings to
|
||||||
|
* Unix-style line endings.
|
||||||
|
*/
|
||||||
|
void test_iconv__normalize_unix() {
|
||||||
|
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||||
|
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||||
|
|
||||||
|
encoding_test_parameters* from = &test_params[i];
|
||||||
|
encoding_test_parameters* to = &test_params[j];
|
||||||
|
|
||||||
|
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||||
|
verify_conversion(from->reader_normalized, &from->test_mixed,
|
||||||
|
to->writer, &to->test_unix);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that every supported encoding can be correctly converted
|
||||||
|
* to every other supported encoding, normalizing all line endings to
|
||||||
|
* Windows-style line endings.
|
||||||
|
*/
|
||||||
|
void test_iconv__normalize_crlf() {
|
||||||
|
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
|
||||||
|
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
|
||||||
|
|
||||||
|
encoding_test_parameters* from = &test_params[i];
|
||||||
|
encoding_test_parameters* to = &test_params[j];
|
||||||
|
|
||||||
|
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
|
||||||
|
verify_conversion(from->reader_normalized, &from->test_mixed,
|
||||||
|
to->writer_crlf, &to->test_windows);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
156
src/common/tests/rect/clip_and_split.c
Normal file
156
src/common/tests/rect/clip_and_split.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/rect.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that guac_common_rect_clip_and_split() divides a
|
||||||
|
* rectangle into subrectangles after removing a "hole" rectangle.
|
||||||
|
*/
|
||||||
|
void test_rect__clip_and_split() {
|
||||||
|
|
||||||
|
int res;
|
||||||
|
|
||||||
|
guac_common_rect cut;
|
||||||
|
guac_common_rect min;
|
||||||
|
guac_common_rect rect;
|
||||||
|
|
||||||
|
guac_common_rect_init(&min, 10, 10, 10, 10);
|
||||||
|
|
||||||
|
/* Clip top */
|
||||||
|
guac_common_rect_init(&rect, 10, 5, 10, 10);
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(5, rect.height);
|
||||||
|
|
||||||
|
/* Clip bottom */
|
||||||
|
guac_common_rect_init(&rect, 10, 15, 10, 10);
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(20, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(15, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(5, rect.height);
|
||||||
|
|
||||||
|
/* Clip left */
|
||||||
|
guac_common_rect_init(&rect, 5, 10, 10, 10);
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(5, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.height);
|
||||||
|
|
||||||
|
/* Clip right */
|
||||||
|
guac_common_rect_init(&rect, 15, 10, 10, 10);
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(20, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(15, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(5, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.height);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test a rectangle which completely covers the hole.
|
||||||
|
* Clip and split until done.
|
||||||
|
*/
|
||||||
|
guac_common_rect_init(&rect, 5, 5, 20, 20);
|
||||||
|
|
||||||
|
/* Clip top */
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(20, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(5, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(20, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(15, rect.height);
|
||||||
|
|
||||||
|
/* Clip left */
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(15, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(15, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(15, rect.height);
|
||||||
|
|
||||||
|
/* Clip bottom */
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(20, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(15, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(15, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.height);
|
||||||
|
|
||||||
|
/* Clip right */
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(20, cut.x);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.y);
|
||||||
|
CU_ASSERT_EQUAL(5, cut.width);
|
||||||
|
CU_ASSERT_EQUAL(10, cut.height);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(10, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(10, rect.height);
|
||||||
|
|
||||||
|
/* Make sure nothing is left to do */
|
||||||
|
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
|
||||||
|
CU_ASSERT_EQUAL(0, res);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,31 +17,27 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "common/rect.h"
|
||||||
|
|
||||||
#include "client/client_suite.h"
|
#include <CUnit/CUnit.h>
|
||||||
#include "common/common_suite.h"
|
|
||||||
#include "protocol/suite.h"
|
|
||||||
#include "util/util_suite.h"
|
|
||||||
|
|
||||||
#include <CUnit/Basic.h>
|
/**
|
||||||
|
* Test which verifies that guac_common_rect_constrain() restricts a given
|
||||||
|
* rectangle to arbitrary bounds.
|
||||||
|
*/
|
||||||
|
void test_rect__constrain() {
|
||||||
|
|
||||||
int main() {
|
guac_common_rect max;
|
||||||
|
guac_common_rect rect;
|
||||||
|
|
||||||
/* Init registry */
|
guac_common_rect_init(&rect, -10, -10, 110, 110);
|
||||||
if (CU_initialize_registry() != CUE_SUCCESS)
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
return CU_get_error();
|
guac_common_rect_constrain(&rect, &max);
|
||||||
|
|
||||||
/* Register suites */
|
CU_ASSERT_EQUAL(0, rect.x);
|
||||||
register_protocol_suite();
|
CU_ASSERT_EQUAL(0, rect.y);
|
||||||
register_client_suite();
|
CU_ASSERT_EQUAL(100, rect.width);
|
||||||
register_util_suite();
|
CU_ASSERT_EQUAL(100, rect.height);
|
||||||
|
|
||||||
/* Run tests */
|
|
||||||
CU_basic_set_mode(CU_BRM_VERBOSE);
|
|
||||||
CU_basic_run_tests();
|
|
||||||
CU_cleanup_registry();
|
|
||||||
return CU_get_error();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
71
src/common/tests/rect/expand_to_grid.c
Normal file
71
src/common/tests/rect/expand_to_grid.c
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/rect.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies guac_common_rect_expand_to_grid() properly shifts and
|
||||||
|
* resizes rectangles to fit an NxN grid.
|
||||||
|
*/
|
||||||
|
void test_rect__expand_to_grid() {
|
||||||
|
|
||||||
|
int cell_size = 16;
|
||||||
|
|
||||||
|
guac_common_rect max;
|
||||||
|
guac_common_rect rect;
|
||||||
|
|
||||||
|
/* Simple adjustment */
|
||||||
|
guac_common_rect_init(&rect, 0, 0, 25, 25);
|
||||||
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
|
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.height);
|
||||||
|
|
||||||
|
/* Adjustment with moving of rect */
|
||||||
|
guac_common_rect_init(&rect, 75, 75, 25, 25);
|
||||||
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
|
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
|
||||||
|
CU_ASSERT_EQUAL(max.width - 32, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(max.height - 32, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.height);
|
||||||
|
|
||||||
|
guac_common_rect_init(&rect, -5, -5, 25, 25);
|
||||||
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
|
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(32, rect.height);
|
||||||
|
|
||||||
|
/* Adjustment with moving and clamping of rect */
|
||||||
|
guac_common_rect_init(&rect, 0, 0, 25, 15);
|
||||||
|
guac_common_rect_init(&max, 0, 5, 32, 15);
|
||||||
|
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
|
||||||
|
CU_ASSERT_EQUAL(max.x, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(max.y, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(max.width, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(max.height, rect.height);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
42
src/common/tests/rect/extend.c
Normal file
42
src/common/tests/rect/extend.c
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/rect.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that guac_common_rect_extend() expands the given
|
||||||
|
* rectangle as necessary to contain at least the given bounds.
|
||||||
|
*/
|
||||||
|
void test_rect__extend() {
|
||||||
|
|
||||||
|
guac_common_rect max;
|
||||||
|
guac_common_rect rect;
|
||||||
|
|
||||||
|
guac_common_rect_init(&rect, 10, 10, 90, 90);
|
||||||
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
|
guac_common_rect_extend(&rect, &max);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.x);
|
||||||
|
CU_ASSERT_EQUAL(0, rect.y);
|
||||||
|
CU_ASSERT_EQUAL(100, rect.width);
|
||||||
|
CU_ASSERT_EQUAL(100, rect.height);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
39
src/common/tests/rect/init.c
Normal file
39
src/common/tests/rect/init.c
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/rect.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies rectangle initialization via guac_common_rect_init().
|
||||||
|
*/
|
||||||
|
void test_rect__init() {
|
||||||
|
|
||||||
|
guac_common_rect max;
|
||||||
|
|
||||||
|
guac_common_rect_init(&max, 0, 0, 100, 100);
|
||||||
|
|
||||||
|
CU_ASSERT_EQUAL(0, max.x);
|
||||||
|
CU_ASSERT_EQUAL(0, max.y);
|
||||||
|
CU_ASSERT_EQUAL(100, max.width);
|
||||||
|
CU_ASSERT_EQUAL(100, max.height);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
91
src/common/tests/rect/intersects.c
Normal file
91
src/common/tests/rect/intersects.c
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/rect.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies intersection testing via guac_common_rect_intersects().
|
||||||
|
*/
|
||||||
|
void test_rect__intersects() {
|
||||||
|
|
||||||
|
int res;
|
||||||
|
|
||||||
|
guac_common_rect min;
|
||||||
|
guac_common_rect rect;
|
||||||
|
|
||||||
|
guac_common_rect_init(&min, 10, 10, 10, 10);
|
||||||
|
|
||||||
|
/* Rectangle intersection - empty
|
||||||
|
* rectangle is outside */
|
||||||
|
guac_common_rect_init(&rect, 25, 25, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(0, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - complete
|
||||||
|
* rectangle is completely inside */
|
||||||
|
guac_common_rect_init(&rect, 11, 11, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(2, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - partial
|
||||||
|
* rectangle intersects UL */
|
||||||
|
guac_common_rect_init(&rect, 8, 8, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - partial
|
||||||
|
* rectangle intersects LR */
|
||||||
|
guac_common_rect_init(&rect, 18, 18, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - complete
|
||||||
|
* rect intersects along UL but inside */
|
||||||
|
guac_common_rect_init(&rect, 10, 10, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(2, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - partial
|
||||||
|
* rectangle intersects along L but outside */
|
||||||
|
guac_common_rect_init(&rect, 5, 10, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - complete
|
||||||
|
* rectangle intersects along LR but rest is inside */
|
||||||
|
guac_common_rect_init(&rect, 15, 15, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(2, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - partial
|
||||||
|
* rectangle intersects along R but rest is outside */
|
||||||
|
guac_common_rect_init(&rect, 20, 10, 5, 5);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
|
||||||
|
/* Rectangle intersection - partial
|
||||||
|
* rectangle encloses min; which is a partial intersection */
|
||||||
|
guac_common_rect_init(&rect, 5, 5, 20, 20);
|
||||||
|
res = guac_common_rect_intersects(&rect, &min);
|
||||||
|
CU_ASSERT_EQUAL(1, res);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
33
src/common/tests/string/count_occurrences.c
Normal file
33
src/common/tests/string/count_occurrences.c
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/string.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test which verifies that guac_count_occurrences() counts the number of
|
||||||
|
* occurrences of an arbitrary character within a given string.
|
||||||
|
*/
|
||||||
|
void test_string__guac_count_occurrences() {
|
||||||
|
CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's'));
|
||||||
|
CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i'));
|
||||||
|
CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's'));
|
||||||
|
}
|
||||||
|
|
@ -17,26 +17,20 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include "common_suite.h"
|
|
||||||
#include "common/string.h"
|
#include "common/string.h"
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <CUnit/Basic.h>
|
|
||||||
|
|
||||||
void test_guac_string() {
|
/**
|
||||||
|
* Test which verifies that guac_split() splits a string on occurrences of a
|
||||||
char** tokens;
|
* given character.
|
||||||
|
*/
|
||||||
/* Test occurrence counting */
|
void test_string__split() {
|
||||||
CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's'));
|
|
||||||
CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i'));
|
|
||||||
CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's'));
|
|
||||||
|
|
||||||
/* Split test string */
|
/* Split test string */
|
||||||
tokens = guac_split("this is a test string", ' ');
|
char** tokens = guac_split("this is a test string", ' ');
|
||||||
|
|
||||||
CU_ASSERT_PTR_NOT_NULL(tokens);
|
CU_ASSERT_PTR_NOT_NULL(tokens);
|
||||||
|
|
||||||
/* Check resulting tokens */
|
/* Check resulting tokens */
|
||||||
@ -57,7 +51,6 @@ void test_guac_string() {
|
|||||||
|
|
||||||
CU_ASSERT_PTR_NULL(tokens[5]);
|
CU_ASSERT_PTR_NULL(tokens[5]);
|
||||||
|
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
free(tokens[0]);
|
free(tokens[0]);
|
||||||
free(tokens[1]);
|
free(tokens[1]);
|
@ -1,11 +1,11 @@
|
|||||||
What is guacd?
|
What is guacd?
|
||||||
==============
|
==============
|
||||||
|
|
||||||
[guacd](https://github.com/apache/incubator/guacamole-server/) is the native
|
[guacd](https://github.com/apache/guacamole-server/) is the native
|
||||||
server-side proxy used by the [Apache Guacamole web
|
server-side proxy used by the [Apache Guacamole web
|
||||||
application](http://guacamole.incubator.apache.org/). If you wish to deploy
|
application](http://guacamole.apache.org/). If you wish to deploy
|
||||||
Guacamole, or an application using the [Guacamole core
|
Guacamole, or an application using the [Guacamole core
|
||||||
APIs](http://guacamole.incubator.apache.org/api-documentation), you will need a
|
APIs](http://guacamole.apache.org/api-documentation), you will need a
|
||||||
copy of guacd running.
|
copy of guacd running.
|
||||||
|
|
||||||
How to use this image
|
How to use this image
|
||||||
|
115
src/guacd-docker/bin/build-all.sh
Executable file
115
src/guacd-docker/bin/build-all.sh
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##
|
||||||
|
## @fn build-all.sh
|
||||||
|
##
|
||||||
|
## Builds the source of guacamole-server and its various core protocol library
|
||||||
|
## dependencies.
|
||||||
|
##
|
||||||
|
|
||||||
|
# Pre-populate build control variables such that the custom build prefix is
|
||||||
|
# used for C headers, locating libraries, etc.
|
||||||
|
export CFLAGS="-I${PREFIX_DIR}/include"
|
||||||
|
export LDFLAGS="-L${PREFIX_DIR}/lib"
|
||||||
|
export PKG_CONFIG_PATH="${PREFIX_DIR}/lib/pkgconfig"
|
||||||
|
|
||||||
|
# Ensure thread stack size will be 8 MB (glibc's default on Linux) rather than
|
||||||
|
# 128 KB (musl's default)
|
||||||
|
export LDFLAGS="$LDFLAGS -Wl,-z,stack-size=8388608"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Builds and installs the source at the given git repository, automatically
|
||||||
|
## switching to the version of the source at the tag/commit that matches the
|
||||||
|
## given pattern.
|
||||||
|
##
|
||||||
|
## @param URL
|
||||||
|
## The URL of the git repository that the source should be downloaded from.
|
||||||
|
##
|
||||||
|
## @param PATTERN
|
||||||
|
## The Perl-compatible regular expression that the tag must match. If no
|
||||||
|
## tag matches the regular expression, the pattern is assumed to be an
|
||||||
|
## exact reference to a commit, branch, etc. acceptable by git checkout.
|
||||||
|
##
|
||||||
|
## @param ...
|
||||||
|
## Any additional command-line options that should be provided to CMake or
|
||||||
|
## the configure script.
|
||||||
|
##
|
||||||
|
install_from_git() {
|
||||||
|
|
||||||
|
URL="$1"
|
||||||
|
PATTERN="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
# Calculate top-level directory name of resulting repository from the
|
||||||
|
# provided URL
|
||||||
|
REPO_DIR="$(basename "$URL" .git)"
|
||||||
|
|
||||||
|
# Allow dependencies to be manually omitted with the tag/commit pattern "NO"
|
||||||
|
if [ "$PATTERN" = "NO" ]; then
|
||||||
|
echo "NOT building $REPO_DIR (explicitly skipped)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clone repository and change to top-level directory of source
|
||||||
|
cd /tmp
|
||||||
|
git clone "$URL"
|
||||||
|
cd $REPO_DIR/
|
||||||
|
|
||||||
|
# Locate tag/commit based on provided pattern
|
||||||
|
VERSION="$(git tag -l --sort=-v:refname | grep -Px -m1 "$PATTERN" \
|
||||||
|
|| echo "$PATTERN")"
|
||||||
|
|
||||||
|
# Switch to desired version of source
|
||||||
|
echo "Building $REPO_DIR @ $VERSION ..."
|
||||||
|
git -c advice.detachedHead=false checkout "$VERSION"
|
||||||
|
|
||||||
|
# Configure build using CMake or GNU Autotools, whichever happens to be
|
||||||
|
# used by the library being built
|
||||||
|
if [ -e CMakeLists.txt ]; then
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX_DIR" "$@" .
|
||||||
|
else
|
||||||
|
[ -e configure ] || autoreconf -fi
|
||||||
|
./configure --prefix="$PREFIX_DIR" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build and install
|
||||||
|
make && make install
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build and install core protocol library dependencies
|
||||||
|
#
|
||||||
|
|
||||||
|
install_from_git "https://github.com/FreeRDP/FreeRDP" "$WITH_FREERDP" $FREERDP_OPTS
|
||||||
|
install_from_git "https://github.com/libssh2/libssh2" "$WITH_LIBSSH2" $LIBSSH2_OPTS
|
||||||
|
install_from_git "https://github.com/seanmiddleditch/libtelnet" "$WITH_LIBTELNET" $LIBTELNET_OPTS
|
||||||
|
install_from_git "https://github.com/LibVNC/libvncserver" "$WITH_LIBVNCCLIENT" $LIBVNCCLIENT_OPTS
|
||||||
|
install_from_git "https://libwebsockets.org/repo/libwebsockets" "$WITH_LIBWEBSOCKETS" $LIBWEBSOCKETS_OPTS
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build guacamole-server
|
||||||
|
#
|
||||||
|
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
autoreconf -fi && ./configure --prefix="$PREFIX_DIR" $GUACAMOLE_SERVER_OPTS
|
||||||
|
make && make install
|
||||||
|
|
@ -1,62 +0,0 @@
|
|||||||
#!/bin/sh -e
|
|
||||||
#
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
##
|
|
||||||
## @fn build-guacd.sh
|
|
||||||
##
|
|
||||||
## Builds the source of guacamole-server, automatically creating any required
|
|
||||||
## symbolic links for the proper loading of FreeRDP plugins.
|
|
||||||
##
|
|
||||||
## @param BUILD_DIR
|
|
||||||
## The directory which currently contains the guacamole-server source and
|
|
||||||
## in which the build should be performed.
|
|
||||||
##
|
|
||||||
|
|
||||||
BUILD_DIR="$1"
|
|
||||||
|
|
||||||
##
|
|
||||||
## Locates the directory in which the FreeRDP libraries (.so files) are
|
|
||||||
## located, printing the result to STDOUT.
|
|
||||||
##
|
|
||||||
where_is_freerdp() {
|
|
||||||
dirname `rpm -ql freerdp-libs | grep 'libfreerdp.*\.so' | head -n1`
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
|
||||||
# Build guacamole-server
|
|
||||||
#
|
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
|
||||||
autoreconf -fi
|
|
||||||
./configure
|
|
||||||
make
|
|
||||||
make install
|
|
||||||
ldconfig
|
|
||||||
|
|
||||||
#
|
|
||||||
# Add FreeRDP plugins to proper path
|
|
||||||
#
|
|
||||||
|
|
||||||
FREERDP_DIR=`where_is_freerdp`
|
|
||||||
FREERDP_PLUGIN_DIR="$FREERDP_DIR/freerdp"
|
|
||||||
|
|
||||||
mkdir -p "$FREERDP_PLUGIN_DIR"
|
|
||||||
ln -s /usr/local/lib/freerdp/*.so "$FREERDP_PLUGIN_DIR"
|
|
||||||
|
|
51
src/guacd-docker/bin/list-dependencies.sh
Executable file
51
src/guacd-docker/bin/list-dependencies.sh
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
#
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##
|
||||||
|
## @fn list-dependencies.sh
|
||||||
|
##
|
||||||
|
## Lists the Alpine Linux package names for all library dependencies of the
|
||||||
|
## given binaries. Each package is only listed once, even if multiple binaries
|
||||||
|
## provided by the same package are given.
|
||||||
|
##
|
||||||
|
## @param ...
|
||||||
|
## The full paths to all binaries being checked.
|
||||||
|
##
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
|
||||||
|
# For all non-Guacamole library dependencies
|
||||||
|
ldd "$1" | grep -v 'libguac' | awk '/=>/{print $(NF-1)}' \
|
||||||
|
| while read LIBRARY; do
|
||||||
|
|
||||||
|
# List the package providing that library, if any
|
||||||
|
apk info -W "$LIBRARY" 2> /dev/null \
|
||||||
|
| grep 'is owned by' | grep -o '[^ ]*$' || true
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# Next binary
|
||||||
|
shift
|
||||||
|
|
||||||
|
# Strip the "-VERSION" suffix from each package name, listing each resulting
|
||||||
|
# package uniquely ("apk add" cannot handle package names that include the
|
||||||
|
# version number)
|
||||||
|
done | sed 's/\(.*\)-[0-9]\+\..*$/\1/' | sort -u
|
||||||
|
|
40
src/guacd/.gitignore
vendored
40
src/guacd/.gitignore
vendored
@ -2,42 +2,14 @@
|
|||||||
# Compiled init script
|
# Compiled init script
|
||||||
init.d/guacd
|
init.d/guacd
|
||||||
|
|
||||||
|
# Compiled systemd unit
|
||||||
|
systemd/guacd.service
|
||||||
|
|
||||||
# Compiled proxy
|
# Compiled proxy
|
||||||
guacd
|
guacd
|
||||||
guacd.exe
|
guacd.exe
|
||||||
|
|
||||||
# Object code
|
# Documentation (built from .in files)
|
||||||
*.o
|
man/guacd.8
|
||||||
*.so
|
man/guacd.conf.5
|
||||||
*.lo
|
|
||||||
*.la
|
|
||||||
|
|
||||||
# Backup files
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Release files
|
|
||||||
*.tar.gz
|
|
||||||
|
|
||||||
# Files currently being edited by vim or vi
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# automake/autoconf
|
|
||||||
.deps/
|
|
||||||
.libs/
|
|
||||||
Makefile
|
|
||||||
Makefile.in
|
|
||||||
aclocal.m4
|
|
||||||
autom4te.cache/
|
|
||||||
m4/
|
|
||||||
config.guess
|
|
||||||
config.log
|
|
||||||
config.status
|
|
||||||
config.sub
|
|
||||||
configure
|
|
||||||
depcomp
|
|
||||||
install-sh
|
|
||||||
libtool
|
|
||||||
ltmain.sh
|
|
||||||
missing
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ man_MANS = \
|
|||||||
man/guacd.conf.5
|
man/guacd.conf.5
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
|
conf.h \
|
||||||
conf-args.h \
|
conf-args.h \
|
||||||
conf-file.h \
|
conf-file.h \
|
||||||
conf-parse.h \
|
conf-parse.h \
|
||||||
@ -49,12 +56,10 @@ guacd_SOURCES = \
|
|||||||
guacd_CFLAGS = \
|
guacd_CFLAGS = \
|
||||||
-Werror -Wall -pedantic \
|
-Werror -Wall -pedantic \
|
||||||
@COMMON_INCLUDE@ \
|
@COMMON_INCLUDE@ \
|
||||||
@LIBGUACD_INCLUDE@ \
|
|
||||||
@LIBGUAC_INCLUDE@
|
@LIBGUAC_INCLUDE@
|
||||||
|
|
||||||
guacd_LDADD = \
|
guacd_LDADD = \
|
||||||
@COMMON_LTLIB@ \
|
@COMMON_LTLIB@ \
|
||||||
@LIBGUACD_LTLIB@ \
|
|
||||||
@LIBGUAC_LTLIB@
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
guacd_LDFLAGS = \
|
guacd_LDFLAGS = \
|
||||||
@ -63,10 +68,11 @@ guacd_LDFLAGS = \
|
|||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
init.d/guacd.in \
|
init.d/guacd.in \
|
||||||
man/guacd.8 \
|
systemd/guacd.service.in \
|
||||||
man/guacd.conf.5
|
man/guacd.8.in \
|
||||||
|
man/guacd.conf.5.in
|
||||||
|
|
||||||
CLEANFILES = $(init_SCRIPTS)
|
CLEANFILES = $(init_SCRIPTS) $(systemd_UNITS)
|
||||||
|
|
||||||
# Init script
|
# Init script
|
||||||
if ENABLE_INIT
|
if ENABLE_INIT
|
||||||
@ -78,3 +84,12 @@ init.d/guacd: init.d/guacd.in
|
|||||||
chmod +x init.d/guacd
|
chmod +x init.d/guacd
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Systemd service
|
||||||
|
if ENABLE_SYSTEMD
|
||||||
|
systemddir = @systemd_dir@
|
||||||
|
systemd_DATA = systemd/guacd.service
|
||||||
|
|
||||||
|
systemd/guacd.service: systemd/guacd.service.in
|
||||||
|
sed -e 's,[@]sbindir[@],$(sbindir),g' < systemd/guacd.service.in > systemd/guacd.service
|
||||||
|
endif
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "conf.h"
|
||||||
#include "conf-args.h"
|
#include "conf-args.h"
|
||||||
#include "conf-file.h"
|
|
||||||
#include "conf-parse.h"
|
#include "conf-parse.h"
|
||||||
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
@ -32,7 +32,7 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
|
|||||||
|
|
||||||
/* Parse arguments */
|
/* Parse arguments */
|
||||||
int opt;
|
int opt;
|
||||||
while ((opt = getopt(argc, argv, "l:b:p:L:C:K:f")) != -1) {
|
while ((opt = getopt(argc, argv, "l:b:p:L:C:K:fv")) != -1) {
|
||||||
|
|
||||||
/* -l: Bind port */
|
/* -l: Bind port */
|
||||||
if (opt == 'l') {
|
if (opt == 'l') {
|
||||||
@ -51,6 +51,11 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
|
|||||||
config->foreground = 1;
|
config->foreground = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -v: Print version and exit */
|
||||||
|
else if (opt == 'v') {
|
||||||
|
config->print_version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* -p: PID file */
|
/* -p: PID file */
|
||||||
else if (opt == 'p') {
|
else if (opt == 'p') {
|
||||||
free(config->pidfile);
|
free(config->pidfile);
|
||||||
@ -63,7 +68,7 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
|
|||||||
/* Validate and parse log level */
|
/* Validate and parse log level */
|
||||||
int level = guacd_parse_log_level(optarg);
|
int level = guacd_parse_log_level(optarg);
|
||||||
if (level == -1) {
|
if (level == -1) {
|
||||||
fprintf(stderr, "Invalid log level. Valid levels are: \"debug\", \"info\", \"warning\", and \"error\".\n");
|
fprintf(stderr, "Invalid log level. Valid levels are: \"trace\", \"debug\", \"info\", \"warning\", and \"error\".\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +110,8 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
|
|||||||
" [-C CERTIFICATE_FILE]"
|
" [-C CERTIFICATE_FILE]"
|
||||||
" [-K PEM_FILE]"
|
" [-K PEM_FILE]"
|
||||||
#endif
|
#endif
|
||||||
" [-f]\n", argv[0]);
|
" [-f]"
|
||||||
|
" [-v]\n", argv[0]);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "conf-file.h"
|
#include "conf.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the given arguments into the given configuration. Zero is returned on
|
* Parses the given arguments into the given configuration. Zero is returned on
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "conf.h"
|
||||||
#include "conf-file.h"
|
#include "conf-file.h"
|
||||||
#include "conf-parse.h"
|
#include "conf-parse.h"
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ static int guacd_conf_callback(const char* section, const char* param, const cha
|
|||||||
|
|
||||||
/* Invalid log level */
|
/* Invalid log level */
|
||||||
if (level < 0) {
|
if (level < 0) {
|
||||||
guacd_conf_parse_error = "Invalid log level. Valid levels are: \"debug\", \"info\", \"warning\", and \"error\".";
|
guacd_conf_parse_error = "Invalid log level. Valid levels are: \"trace\", \"debug\", \"info\", \"warning\", and \"error\".";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,10 +176,11 @@ guacd_config* guacd_conf_load() {
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Load defaults */
|
/* Load defaults */
|
||||||
conf->bind_host = NULL;
|
conf->bind_host = strdup(GUACD_DEFAULT_BIND_HOST);
|
||||||
conf->bind_port = strdup("4822");
|
conf->bind_port = strdup(GUACD_DEFAULT_BIND_PORT);
|
||||||
conf->pidfile = NULL;
|
conf->pidfile = NULL;
|
||||||
conf->foreground = 0;
|
conf->foreground = 0;
|
||||||
|
conf->print_version = 0;
|
||||||
conf->max_log_level = GUAC_LOG_INFO;
|
conf->max_log_level = GUAC_LOG_INFO;
|
||||||
|
|
||||||
#ifdef ENABLE_SSL
|
#ifdef ENABLE_SSL
|
||||||
@ -195,6 +197,7 @@ guacd_config* guacd_conf_load() {
|
|||||||
|
|
||||||
if (retval != 0) {
|
if (retval != 0) {
|
||||||
fprintf(stderr, "Unable to parse \"" GUACD_CONF_FILE "\".\n");
|
fprintf(stderr, "Unable to parse \"" GUACD_CONF_FILE "\".\n");
|
||||||
|
free(conf);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +206,7 @@ guacd_config* guacd_conf_load() {
|
|||||||
/* Notify of errors preventing reading */
|
/* Notify of errors preventing reading */
|
||||||
else if (errno != ENOENT) {
|
else if (errno != ENOENT) {
|
||||||
fprintf(stderr, "Unable to open \"" GUACD_CONF_FILE "\": %s\n", strerror(errno));
|
fprintf(stderr, "Unable to open \"" GUACD_CONF_FILE "\": %s\n", strerror(errno));
|
||||||
|
free(conf);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,51 +22,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include "conf.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* The contents of a guacd configuration file.
|
|
||||||
*/
|
|
||||||
typedef struct guacd_config {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The host to bind on.
|
|
||||||
*/
|
|
||||||
char* bind_host;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The port to bind on.
|
|
||||||
*/
|
|
||||||
char* bind_port;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file to write the PID in, if any.
|
|
||||||
*/
|
|
||||||
char* pidfile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether guacd should run in the foreground.
|
|
||||||
*/
|
|
||||||
int foreground;
|
|
||||||
|
|
||||||
#ifdef ENABLE_SSL
|
|
||||||
/**
|
|
||||||
* SSL certificate file.
|
|
||||||
*/
|
|
||||||
char* cert_file;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SSL private key file.
|
|
||||||
*/
|
|
||||||
char* key_file;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum log level to be logged by guacd.
|
|
||||||
*/
|
|
||||||
guac_client_log_level max_log_level;
|
|
||||||
|
|
||||||
} guacd_config;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the given file descriptor, parsing its contents into the guacd_config.
|
* Reads the given file descriptor, parsing its contents into the guacd_config.
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "conf.h"
|
||||||
#include "conf-parse.h"
|
#include "conf-parse.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
@ -527,6 +528,7 @@ int guacd_parse_log_level(const char* name) {
|
|||||||
if (strcmp(name, "error") == 0) return GUAC_LOG_ERROR;
|
if (strcmp(name, "error") == 0) return GUAC_LOG_ERROR;
|
||||||
if (strcmp(name, "warning") == 0) return GUAC_LOG_WARNING;
|
if (strcmp(name, "warning") == 0) return GUAC_LOG_WARNING;
|
||||||
if (strcmp(name, "debug") == 0) return GUAC_LOG_DEBUG;
|
if (strcmp(name, "debug") == 0) return GUAC_LOG_DEBUG;
|
||||||
|
if (strcmp(name, "trace") == 0) return GUAC_LOG_TRACE;
|
||||||
|
|
||||||
/* No such log level */
|
/* No such log level */
|
||||||
return -1;
|
return -1;
|
||||||
|
89
src/guacd/conf.h
Normal file
89
src/guacd/conf.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUACD_CONF_H
|
||||||
|
#define GUACD_CONF_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default host that guacd should bind to, if no other host is explicitly
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
#define GUACD_DEFAULT_BIND_HOST "localhost"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default port that guacd should bind to, if no other port is explicitly
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
#define GUACD_DEFAULT_BIND_PORT "4822"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contents of a guacd configuration file.
|
||||||
|
*/
|
||||||
|
typedef struct guacd_config {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host to bind on.
|
||||||
|
*/
|
||||||
|
char* bind_host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port to bind on.
|
||||||
|
*/
|
||||||
|
char* bind_port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file to write the PID in, if any.
|
||||||
|
*/
|
||||||
|
char* pidfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether guacd should run in the foreground.
|
||||||
|
*/
|
||||||
|
int foreground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether guacd should simply print its version information and exit.
|
||||||
|
*/
|
||||||
|
int print_version;
|
||||||
|
|
||||||
|
#ifdef ENABLE_SSL
|
||||||
|
/**
|
||||||
|
* SSL certificate file.
|
||||||
|
*/
|
||||||
|
char* cert_file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL private key file.
|
||||||
|
*/
|
||||||
|
char* key_file;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum log level to be logged by guacd.
|
||||||
|
*/
|
||||||
|
guac_client_log_level max_log_level;
|
||||||
|
|
||||||
|
} guacd_config;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -22,7 +22,6 @@
|
|||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "move-fd.h"
|
#include "move-fd.h"
|
||||||
#include "libguacd/user.h"
|
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
#include "proc-map.h"
|
#include "proc-map.h"
|
||||||
|
|
||||||
@ -36,7 +35,7 @@
|
|||||||
|
|
||||||
#ifdef ENABLE_SSL
|
#ifdef ENABLE_SSL
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include "libguacd/socket-ssl.h"
|
#include <guacamole/socket-ssl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -279,10 +278,13 @@ static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
|
|||||||
proc = guacd_proc_map_retrieve(map, identifier);
|
proc = guacd_proc_map_retrieve(map, identifier);
|
||||||
new_process = 0;
|
new_process = 0;
|
||||||
|
|
||||||
/* Warn if requested connection does not exist */
|
/* Warn and ward off client if requested connection does not exist */
|
||||||
if (proc == NULL)
|
if (proc == NULL) {
|
||||||
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist.",
|
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist", identifier);
|
||||||
identifier);
|
guac_protocol_send_error(socket, "No such connection.",
|
||||||
|
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"",
|
guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"",
|
||||||
identifier);
|
identifier);
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "connection.h"
|
#include "conf.h"
|
||||||
#include "conf-args.h"
|
#include "conf-args.h"
|
||||||
#include "conf-file.h"
|
#include "conf-file.h"
|
||||||
#include "libguacd/user.h"
|
#include "connection.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "proc-map.h"
|
#include "proc-map.h"
|
||||||
|
|
||||||
@ -152,6 +152,99 @@ static int daemonize() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_SSL
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
|
/**
|
||||||
|
* Array of mutexes, used by OpenSSL.
|
||||||
|
*/
|
||||||
|
static pthread_mutex_t* guacd_openssl_locks = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by OpenSSL when locking or unlocking the Nth mutex.
|
||||||
|
*
|
||||||
|
* @param mode
|
||||||
|
* A bitmask denoting the action to be taken on the Nth lock, such as
|
||||||
|
* CRYPTO_LOCK or CRYPTO_UNLOCK.
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
* The index of the lock to lock or unlock.
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* The filename of the function setting the lock, for debugging purposes.
|
||||||
|
*
|
||||||
|
* @param line
|
||||||
|
* The line number of the function setting the lock, for debugging
|
||||||
|
* purposes.
|
||||||
|
*/
|
||||||
|
static void guacd_openssl_locking_callback(int mode, int n,
|
||||||
|
const char* file, int line){
|
||||||
|
|
||||||
|
/* Lock given mutex upon request */
|
||||||
|
if (mode & CRYPTO_LOCK)
|
||||||
|
pthread_mutex_lock(&(guacd_openssl_locks[n]));
|
||||||
|
|
||||||
|
/* Unlock given mutex upon request */
|
||||||
|
else if (mode & CRYPTO_UNLOCK)
|
||||||
|
pthread_mutex_unlock(&(guacd_openssl_locks[n]));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by OpenSSL when determining the current thread ID.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An ID which uniquely identifies the current thread.
|
||||||
|
*/
|
||||||
|
static unsigned long guacd_openssl_id_callback() {
|
||||||
|
return (unsigned long) pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the given number of mutexes, such that OpenSSL will have at least
|
||||||
|
* this number of mutexes at its disposal.
|
||||||
|
*
|
||||||
|
* @param count
|
||||||
|
* The number of mutexes (locks) to create.
|
||||||
|
*/
|
||||||
|
static void guacd_openssl_init_locks(int count) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Allocate required number of locks */
|
||||||
|
guacd_openssl_locks =
|
||||||
|
malloc(sizeof(pthread_mutex_t) * count);
|
||||||
|
|
||||||
|
/* Initialize each lock */
|
||||||
|
for (i=0; i < count; i++)
|
||||||
|
pthread_mutex_init(&(guacd_openssl_locks[i]), NULL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the given number of mutexes.
|
||||||
|
*
|
||||||
|
* @param count
|
||||||
|
* The number of mutexes (locks) to free.
|
||||||
|
*/
|
||||||
|
static void guacd_openssl_free_locks(int count) {
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* SSL lock array was not initialized */
|
||||||
|
if (guacd_openssl_locks == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Free all locks */
|
||||||
|
for (i=0; i < count; i++)
|
||||||
|
pthread_mutex_destroy(&(guacd_openssl_locks[i]));
|
||||||
|
|
||||||
|
/* Free lock array */
|
||||||
|
free(guacd_openssl_locks);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
/* Server */
|
/* Server */
|
||||||
@ -187,6 +280,13 @@ int main(int argc, char* argv[]) {
|
|||||||
if (config == NULL || guacd_conf_parse_args(config, argc, argv))
|
if (config == NULL || guacd_conf_parse_args(config, argc, argv))
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
/* If requested, simply print version and exit, without initializing the
|
||||||
|
* logging system, etc. */
|
||||||
|
if (config->print_version) {
|
||||||
|
printf("Guacamole proxy daemon (guacd) version " VERSION "\n");
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
/* Init logging as early as possible */
|
/* Init logging as early as possible */
|
||||||
guacd_log_level = config->max_log_level;
|
guacd_log_level = config->max_log_level;
|
||||||
openlog(GUACD_LOG_NAME, LOG_PID, LOG_DAEMON);
|
openlog(GUACD_LOG_NAME, LOG_PID, LOG_DAEMON);
|
||||||
@ -204,20 +304,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get socket */
|
|
||||||
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (socket_fd < 0) {
|
|
||||||
guacd_log(GUAC_LOG_ERROR, "Error opening socket: %s", strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allow socket reuse */
|
|
||||||
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
|
|
||||||
(void*) &opt_on, sizeof(opt_on))) {
|
|
||||||
guacd_log(GUAC_LOG_WARNING, "Unable to set socket options for reuse: %s",
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attempt binding of each address until success */
|
/* Attempt binding of each address until success */
|
||||||
current_address = addresses;
|
current_address = addresses;
|
||||||
while (current_address != NULL) {
|
while (current_address != NULL) {
|
||||||
@ -233,27 +319,47 @@ int main(int argc, char* argv[]) {
|
|||||||
guacd_log(GUAC_LOG_ERROR, "Unable to resolve host: %s",
|
guacd_log(GUAC_LOG_ERROR, "Unable to resolve host: %s",
|
||||||
gai_strerror(retval));
|
gai_strerror(retval));
|
||||||
|
|
||||||
|
/* Get socket */
|
||||||
|
socket_fd = socket(current_address->ai_family, SOCK_STREAM, 0);
|
||||||
|
if (socket_fd < 0) {
|
||||||
|
guacd_log(GUAC_LOG_ERROR, "Error opening socket: %s", strerror(errno));
|
||||||
|
|
||||||
|
/* Unable to get a socket for the resolved address family, try next */
|
||||||
|
current_address = current_address->ai_next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow socket reuse */
|
||||||
|
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
|
||||||
|
(void*) &opt_on, sizeof(opt_on))) {
|
||||||
|
guacd_log(GUAC_LOG_WARNING, "Unable to set socket options for reuse: %s",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
/* Attempt to bind socket to address */
|
/* Attempt to bind socket to address */
|
||||||
if (bind(socket_fd,
|
if (bind(socket_fd,
|
||||||
current_address->ai_addr,
|
current_address->ai_addr,
|
||||||
current_address->ai_addrlen) == 0) {
|
current_address->ai_addrlen) == 0) {
|
||||||
|
|
||||||
guacd_log(GUAC_LOG_DEBUG, "Successfully bound socket to "
|
guacd_log(GUAC_LOG_DEBUG, "Successfully bound "
|
||||||
"host %s, port %s", bound_address, bound_port);
|
"%s socket to host %s, port %s",
|
||||||
|
(current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
|
||||||
|
bound_address, bound_port);
|
||||||
|
|
||||||
/* Done if successful bind */
|
/* Done if successful bind */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Otherwise log information regarding bind failure */
|
/* Otherwise log information regarding bind failure */
|
||||||
else
|
close(socket_fd);
|
||||||
guacd_log(GUAC_LOG_DEBUG, "Unable to bind socket to "
|
socket_fd = -1;
|
||||||
|
guacd_log(GUAC_LOG_DEBUG, "Unable to bind %s socket to "
|
||||||
"host %s, port %s: %s",
|
"host %s, port %s: %s",
|
||||||
|
(current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
|
||||||
bound_address, bound_port, strerror(errno));
|
bound_address, bound_port, strerror(errno));
|
||||||
|
|
||||||
|
/* Try next address */
|
||||||
current_address = current_address->ai_next;
|
current_address = current_address->ai_next;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If unable to bind to anything, fail */
|
/* If unable to bind to anything, fail */
|
||||||
@ -266,11 +372,24 @@ int main(int argc, char* argv[]) {
|
|||||||
/* Init SSL if enabled */
|
/* Init SSL if enabled */
|
||||||
if (config->key_file != NULL || config->cert_file != NULL) {
|
if (config->key_file != NULL || config->cert_file != NULL) {
|
||||||
|
|
||||||
/* Init SSL */
|
|
||||||
guacd_log(GUAC_LOG_INFO, "Communication will require SSL/TLS.");
|
guacd_log(GUAC_LOG_INFO, "Communication will require SSL/TLS.");
|
||||||
|
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
|
/* Init threadsafety in OpenSSL */
|
||||||
|
guacd_openssl_init_locks(CRYPTO_num_locks());
|
||||||
|
CRYPTO_set_id_callback(guacd_openssl_id_callback);
|
||||||
|
CRYPTO_set_locking_callback(guacd_openssl_locking_callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
/* Init OpenSSL for OpenSSL Versions < 1.1.0 */
|
||||||
SSL_library_init();
|
SSL_library_init();
|
||||||
SSL_load_error_strings();
|
SSL_load_error_strings();
|
||||||
ssl_context = SSL_CTX_new(SSLv23_server_method());
|
ssl_context = SSL_CTX_new(SSLv23_server_method());
|
||||||
|
#else
|
||||||
|
/* Set up OpenSSL for OpenSSL Versions >= 1.1.0 */
|
||||||
|
ssl_context = SSL_CTX_new(TLS_server_method());
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Load key */
|
/* Load key */
|
||||||
if (config->key_file != NULL) {
|
if (config->key_file != NULL) {
|
||||||
@ -391,6 +510,15 @@ int main(int argc, char* argv[]) {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_SSL
|
||||||
|
if (ssl_context != NULL) {
|
||||||
|
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
|
||||||
|
guacd_openssl_free_locks(CRYPTO_num_locks());
|
||||||
|
#endif
|
||||||
|
SSL_CTX_free(ssl_context);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,12 @@ void vguacd_log(guac_client_log_level level, const char* format,
|
|||||||
priority_name = "DEBUG";
|
priority_name = "DEBUG";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* Trace log level */
|
||||||
|
case GUAC_LOG_TRACE:
|
||||||
|
priority = LOG_DEBUG;
|
||||||
|
priority_name = "TRACE";
|
||||||
|
break;
|
||||||
|
|
||||||
/* Any unknown/undefined log level */
|
/* Any unknown/undefined log level */
|
||||||
default:
|
default:
|
||||||
priority = LOG_INFO;
|
priority = LOG_INFO;
|
||||||
@ -127,7 +133,7 @@ void guacd_log_guac_error(guac_client_log_level level, const char* message) {
|
|||||||
void guacd_log_handshake_failure() {
|
void guacd_log_handshake_failure() {
|
||||||
|
|
||||||
if (guac_error == GUAC_STATUS_CLOSED)
|
if (guac_error == GUAC_STATUS_CLOSED)
|
||||||
guacd_log(GUAC_LOG_INFO,
|
guacd_log(GUAC_LOG_DEBUG,
|
||||||
"Guacamole connection closed during handshake");
|
"Guacamole connection closed during handshake");
|
||||||
else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
|
else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
|
||||||
guacd_log(GUAC_LOG_ERROR,
|
guacd_log(GUAC_LOG_ERROR,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
.\" specific language governing permissions and limitations
|
.\" specific language governing permissions and limitations
|
||||||
.\" under the License.
|
.\" under the License.
|
||||||
.\"
|
.\"
|
||||||
.TH guacd 8 "9 Jan 2017" "version 0.9.11-incubating" "Guacamole"
|
.TH guacd 8 "1 Jun 2017" "version @PACKAGE_VERSION@" "Apache Guacamole"
|
||||||
.
|
.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
guacd \- Guacamole proxy daemon
|
guacd \- Guacamole proxy daemon
|
||||||
@ -30,6 +30,7 @@ guacd \- Guacamole proxy daemon
|
|||||||
[\fB-C\fR \fICERTIFICATE FILE\fR]
|
[\fB-C\fR \fICERTIFICATE FILE\fR]
|
||||||
[\fB-K\fR \fIKEY FILE\fR]
|
[\fB-K\fR \fIKEY FILE\fR]
|
||||||
[\fB-f\fR]
|
[\fB-f\fR]
|
||||||
|
[\fB-v\fR]
|
||||||
.
|
.
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
.B guacd
|
.B guacd
|
||||||
@ -66,6 +67,7 @@ Sets the maximum level at which
|
|||||||
.B guacd
|
.B guacd
|
||||||
will log messages to syslog and, if running in the foreground, the console.
|
will log messages to syslog and, if running in the foreground, the console.
|
||||||
Legal values are
|
Legal values are
|
||||||
|
.B trace,
|
||||||
.B debug,
|
.B debug,
|
||||||
.B info,
|
.B info,
|
||||||
.B warning,
|
.B warning,
|
||||||
@ -79,6 +81,11 @@ Causes
|
|||||||
.B guacd
|
.B guacd
|
||||||
to run in the foreground, rather than automatically forking into the
|
to run in the foreground, rather than automatically forking into the
|
||||||
background.
|
background.
|
||||||
|
.TP
|
||||||
|
\fB\-v\fR
|
||||||
|
Causes
|
||||||
|
.B guacd
|
||||||
|
to simply print its version information and exit.
|
||||||
.
|
.
|
||||||
.SH SSL/TLS OPTIONS
|
.SH SSL/TLS OPTIONS
|
||||||
If libssl was present at the time
|
If libssl was present at the time
|
||||||
@ -112,6 +119,3 @@ this option is not given, communication with guacd must be unencrypted.
|
|||||||
.
|
.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR guacd.conf (5)
|
.BR guacd.conf (5)
|
||||||
.
|
|
||||||
.SH AUTHOR
|
|
||||||
Written by Michael Jumper <mike.jumper@guac-dev.org>
|
|
@ -16,7 +16,7 @@
|
|||||||
.\" specific language governing permissions and limitations
|
.\" specific language governing permissions and limitations
|
||||||
.\" under the License.
|
.\" under the License.
|
||||||
.\"
|
.\"
|
||||||
.TH guacd.conf 5 "9 Jan 2017" "version 0.9.11-incubating" "Guacamole"
|
.TH guacd.conf 5 "1 Jun 2017" "version @PACKAGE_VERSION@" "Apache Guacamole"
|
||||||
.
|
.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
/etc/guacamole/guacd.conf \- Configuration file for guacd
|
/etc/guacamole/guacd.conf \- Configuration file for guacd
|
||||||
@ -109,6 +109,7 @@ Sets the maximum level at which
|
|||||||
.B guacd
|
.B guacd
|
||||||
will log messages to syslog and, if running in the foreground, the console.
|
will log messages to syslog and, if running in the foreground, the console.
|
||||||
Legal values are
|
Legal values are
|
||||||
|
.B trace,
|
||||||
.B debug,
|
.B debug,
|
||||||
.B info,
|
.B info,
|
||||||
.B warning,
|
.B warning,
|
||||||
@ -175,6 +176,3 @@ server_certificate = /etc/ssl/certs/guacd.crt
|
|||||||
server_key = /etc/ssl/private/guacd.key
|
server_key = /etc/ssl/private/guacd.key
|
||||||
.RE
|
.RE
|
||||||
.fi
|
.fi
|
||||||
.
|
|
||||||
.SH AUTHOR
|
|
||||||
Written by Michael Jumper <mike.jumper@guac-dev.org>
|
|
@ -44,7 +44,7 @@ int guacd_send_fd(int sock, int fd) {
|
|||||||
message.msg_iovlen = 1;
|
message.msg_iovlen = 1;
|
||||||
|
|
||||||
/* Assign ancillary data buffer */
|
/* Assign ancillary data buffer */
|
||||||
char buffer[CMSG_SPACE(sizeof(fd))];
|
char buffer[CMSG_SPACE(sizeof(fd))] = {0};
|
||||||
message.msg_control = buffer;
|
message.msg_control = buffer;
|
||||||
message.msg_controllen = sizeof(buffer);
|
message.msg_controllen = sizeof(buffer);
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common/list.h"
|
#include "common/list.h"
|
||||||
#include "libguacd/user.h"
|
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
#include "proc-map.h"
|
#include "proc-map.h"
|
||||||
|
|
||||||
|
@ -23,11 +23,16 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "common/list.h"
|
#include "common/list.h"
|
||||||
#include "libguacd/user.h"
|
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of concurrent connections to a single instance
|
||||||
|
* of guacd.
|
||||||
|
*/
|
||||||
|
#define GUACD_CLIENT_MAX_CONNECTIONS 65536
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of hash buckets in each process map.
|
* The number of hash buckets in each process map.
|
||||||
*/
|
*/
|
||||||
|
215
src/guacd/proc.c
215
src/guacd/proc.c
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "libguacd/user.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "move-fd.h"
|
#include "move-fd.h"
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
@ -34,11 +33,15 @@
|
|||||||
#include <guacamole/user.h>
|
#include <guacamole/user.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters for the user thread.
|
* Parameters for the user thread.
|
||||||
@ -92,7 +95,7 @@ static void* guacd_user_thread(void* data) {
|
|||||||
user->owner = params->owner;
|
user->owner = params->owner;
|
||||||
|
|
||||||
/* Handle user connection from handshake until disconnect/completion */
|
/* Handle user connection from handshake until disconnect/completion */
|
||||||
guacd_handle_user(user);
|
guac_user_handle_connection(user, GUACD_USEC_TIMEOUT);
|
||||||
|
|
||||||
/* Stop client and prevent future users if all users are disconnected */
|
/* Stop client and prevent future users if all users are disconnected */
|
||||||
if (client->connected_users == 0) {
|
if (client->connected_users == 0) {
|
||||||
@ -139,6 +142,151 @@ static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forcibly kills all processes within the current process group, including the
|
||||||
|
* current process and all child processes. This function is only safe to call
|
||||||
|
* if the process group ID has been correctly set. Calling this function within
|
||||||
|
* a process which does not have a PGID separate from the main guacd process
|
||||||
|
* can result in guacd itself being terminated.
|
||||||
|
*/
|
||||||
|
static void guacd_kill_current_proc_group() {
|
||||||
|
|
||||||
|
/* Forcibly kill all children within process group */
|
||||||
|
if (kill(0, SIGKILL))
|
||||||
|
guacd_log(GUAC_LOG_WARNING, "Unable to forcibly terminate "
|
||||||
|
"client process: %s ", strerror(errno));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of a background attempt to free a guac_client instance.
|
||||||
|
*/
|
||||||
|
typedef struct guacd_client_free {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guac_client instance being freed.
|
||||||
|
*/
|
||||||
|
guac_client* client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The condition which is signalled whenever changes are made to the
|
||||||
|
* completed flag. The completed flag only changes from zero (not yet
|
||||||
|
* freed) to non-zero (successfully freed).
|
||||||
|
*/
|
||||||
|
pthread_cond_t completed_cond;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutex which must be acquired before any changes are made to the
|
||||||
|
* completed flag.
|
||||||
|
*/
|
||||||
|
pthread_mutex_t completed_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the guac_client has been successfully freed. Initially, this
|
||||||
|
* will be zero, indicating that the free operation has not yet been
|
||||||
|
* attempted. If the client is eventually successfully freed, this will be
|
||||||
|
* set to a non-zero value. Changes to this flag are signalled through
|
||||||
|
* the completed_cond condition.
|
||||||
|
*/
|
||||||
|
int completed;
|
||||||
|
|
||||||
|
} guacd_client_free;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread which frees a given guac_client instance in the background. If the
|
||||||
|
* free operation succeeds, a flag is set on the provided structure, and the
|
||||||
|
* change in that flag is signalled with a pthread condition.
|
||||||
|
*
|
||||||
|
* At the time this function is provided to a pthread_create() call, the
|
||||||
|
* completed flag of the associated guacd_client_free structure MUST be
|
||||||
|
* initialized to zero, the pthread mutex and condition MUST both be
|
||||||
|
* initialized, and the client pointer must point to the guac_client being
|
||||||
|
* freed.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* A pointer to a guacd_client_free structure describing the free
|
||||||
|
* operation.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Always NULL.
|
||||||
|
*/
|
||||||
|
static void* guacd_client_free_thread(void* data) {
|
||||||
|
|
||||||
|
guacd_client_free* free_operation = (guacd_client_free*) data;
|
||||||
|
|
||||||
|
/* Attempt to free client (this may never return if the client is
|
||||||
|
* malfunctioning) */
|
||||||
|
guac_client_free(free_operation->client);
|
||||||
|
|
||||||
|
/* Signal that the client was successfully freed */
|
||||||
|
pthread_mutex_lock(&free_operation->completed_mutex);
|
||||||
|
free_operation->completed = 1;
|
||||||
|
pthread_cond_broadcast(&free_operation->completed_cond);
|
||||||
|
pthread_mutex_unlock(&free_operation->completed_mutex);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to free the given guac_client, restricting the time taken by the
|
||||||
|
* free handler of the guac_client to a finite number of seconds. If the free
|
||||||
|
* handler does not complete within the time alotted, this function returns
|
||||||
|
* and the intended free operation is left in an undefined state.
|
||||||
|
*
|
||||||
|
* @param client
|
||||||
|
* The guac_client instance to free.
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* The maximum amount of time to wait for the guac_client to be freed,
|
||||||
|
* in seconds.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if the guac_client was successfully freed within the time alotted,
|
||||||
|
* non-zero otherwise.
|
||||||
|
*/
|
||||||
|
static int guacd_timed_client_free(guac_client* client, int timeout) {
|
||||||
|
|
||||||
|
pthread_t client_free_thread;
|
||||||
|
|
||||||
|
guacd_client_free free_operation = {
|
||||||
|
.client = client,
|
||||||
|
.completed_cond = PTHREAD_COND_INITIALIZER,
|
||||||
|
.completed_mutex = PTHREAD_MUTEX_INITIALIZER,
|
||||||
|
.completed = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Get current time */
|
||||||
|
struct timeval current_time;
|
||||||
|
if (gettimeofday(¤t_time, NULL))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Calculate exact time that the free operation MUST complete by */
|
||||||
|
struct timespec deadline = {
|
||||||
|
.tv_sec = current_time.tv_sec + timeout,
|
||||||
|
.tv_nsec = current_time.tv_usec * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The mutex associated with the pthread conditional and flag MUST be
|
||||||
|
* acquired before attempting to wait for the condition */
|
||||||
|
if (pthread_mutex_lock(&free_operation.completed_mutex))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Free the client in a separate thread, so we can time the free operation */
|
||||||
|
if (!pthread_create(&client_free_thread, NULL,
|
||||||
|
guacd_client_free_thread, &free_operation)) {
|
||||||
|
|
||||||
|
/* Wait a finite amount of time for the free operation to finish */
|
||||||
|
(void) pthread_cond_timedwait(&free_operation.completed_cond,
|
||||||
|
&free_operation.completed_mutex, &deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) pthread_mutex_unlock(&free_operation.completed_mutex);
|
||||||
|
|
||||||
|
/* Return status of free operation */
|
||||||
|
return !free_operation.completed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts protocol-specific handling on the given process by loading the client
|
* Starts protocol-specific handling on the given process by loading the client
|
||||||
* plugin for that protocol. This function does NOT return. It initializes the
|
* plugin for that protocol. This function does NOT return. It initializes the
|
||||||
@ -155,8 +303,18 @@ static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) {
|
|||||||
*/
|
*/
|
||||||
static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
|
static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
|
||||||
|
|
||||||
|
int result = 1;
|
||||||
|
|
||||||
|
/* Set process group ID to match PID */
|
||||||
|
if (setpgid(0, 0)) {
|
||||||
|
guacd_log(GUAC_LOG_ERROR, "Cannot set PGID for connection process: %s",
|
||||||
|
strerror(errno));
|
||||||
|
goto cleanup_process;
|
||||||
|
}
|
||||||
|
|
||||||
/* Init client for selected protocol */
|
/* Init client for selected protocol */
|
||||||
if (guac_client_load_plugin(proc->client, protocol)) {
|
guac_client* client = proc->client;
|
||||||
|
if (guac_client_load_plugin(client, protocol)) {
|
||||||
|
|
||||||
/* Log error */
|
/* Log error */
|
||||||
if (guac_error == GUAC_STATUS_NOT_FOUND)
|
if (guac_error == GUAC_STATUS_NOT_FOUND)
|
||||||
@ -166,15 +324,15 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
|
|||||||
guacd_log_guac_error(GUAC_LOG_ERROR,
|
guacd_log_guac_error(GUAC_LOG_ERROR,
|
||||||
"Unable to load client plugin");
|
"Unable to load client plugin");
|
||||||
|
|
||||||
guac_client_free(proc->client);
|
goto cleanup_client;
|
||||||
close(proc->fd_socket);
|
|
||||||
free(proc);
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The first file descriptor is the owner */
|
/* The first file descriptor is the owner */
|
||||||
int owner = 1;
|
int owner = 1;
|
||||||
|
|
||||||
|
/* Enable keep alive on the broadcast socket */
|
||||||
|
guac_socket_require_keep_alive(client->socket);
|
||||||
|
|
||||||
/* Add each received file descriptor as a new user */
|
/* Add each received file descriptor as a new user */
|
||||||
int received_fd;
|
int received_fd;
|
||||||
while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) {
|
while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) {
|
||||||
@ -186,14 +344,47 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stop and free client */
|
cleanup_client:
|
||||||
guac_client_stop(proc->client);
|
|
||||||
guac_client_free(proc->client);
|
|
||||||
|
|
||||||
/* Child is finished */
|
/* Request client to stop/disconnect */
|
||||||
|
guac_client_stop(client);
|
||||||
|
|
||||||
|
/* Attempt to free client cleanly */
|
||||||
|
guacd_log(GUAC_LOG_DEBUG, "Requesting termination of client...");
|
||||||
|
result = guacd_timed_client_free(client, GUACD_CLIENT_FREE_TIMEOUT);
|
||||||
|
|
||||||
|
/* If client was unable to be freed, warn and forcibly kill */
|
||||||
|
if (result) {
|
||||||
|
guacd_log(GUAC_LOG_WARNING, "Client did not terminate in a timely "
|
||||||
|
"manner. Forcibly terminating client and any child "
|
||||||
|
"processes.");
|
||||||
|
guacd_kill_current_proc_group();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
guacd_log(GUAC_LOG_DEBUG, "Client terminated successfully.");
|
||||||
|
|
||||||
|
/* Verify whether children were all properly reaped */
|
||||||
|
pid_t child_pid;
|
||||||
|
while ((child_pid = waitpid(0, NULL, WNOHANG)) > 0) {
|
||||||
|
guacd_log(GUAC_LOG_DEBUG, "Automatically reaped unreaped "
|
||||||
|
"(zombie) child process with PID %i.", child_pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If running children remain, warn and forcibly kill */
|
||||||
|
if (child_pid == 0) {
|
||||||
|
guacd_log(GUAC_LOG_WARNING, "Client reported successful termination, "
|
||||||
|
"but child processes remain. Forcibly terminating client and "
|
||||||
|
"child processes.");
|
||||||
|
guacd_kill_current_proc_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_process:
|
||||||
|
|
||||||
|
/* Free up all internal resources outside the client */
|
||||||
close(proc->fd_socket);
|
close(proc->fd_socket);
|
||||||
free(proc);
|
free(proc);
|
||||||
exit(0);
|
|
||||||
|
exit(result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,27 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of milliseconds to wait for messages in any phase before
|
||||||
|
* timing out and closing the connection with an error.
|
||||||
|
*/
|
||||||
|
#define GUACD_TIMEOUT 15000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of microseconds to wait for messages in any phase before
|
||||||
|
* timing out and closing the conncetion with an error. This is always
|
||||||
|
* equal to GUACD_TIMEOUT * 1000.
|
||||||
|
*/
|
||||||
|
#define GUACD_USEC_TIMEOUT (GUACD_TIMEOUT*1000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds to wait for any particular guac_client instance
|
||||||
|
* to be freed following disconnect. If the free operation does not complete
|
||||||
|
* within this period of time, the associated process will be forcibly
|
||||||
|
* terminated.
|
||||||
|
*/
|
||||||
|
#define GUACD_CLIENT_FREE_TIMEOUT 5
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process information of the internal remote desktop client.
|
* Process information of the internal remote desktop client.
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
# or more contributor license agreements. See the NOTICE file
|
# or more contributor license agreements. See the NOTICE file
|
||||||
# distributed with this work for additional information
|
# distributed with this work for additional information
|
||||||
@ -15,36 +14,16 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
[Unit]
|
||||||
|
Description=Guacamole Server
|
||||||
|
Documentation=man:guacd(8)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
noinst_LTLIBRARIES = libguacd.la
|
[Service]
|
||||||
|
User=daemon
|
||||||
noinst_HEADERS = \
|
ExecStart=@sbindir@/guacd -f
|
||||||
libguacd/log.h \
|
Restart=on-abnormal
|
||||||
libguacd/user.h
|
|
||||||
|
|
||||||
libguacd_la_SOURCES = \
|
|
||||||
log.c \
|
|
||||||
user.c
|
|
||||||
|
|
||||||
libguacd_la_CFLAGS = \
|
|
||||||
-Werror -Wall -pedantic \
|
|
||||||
@COMMON_INCLUDE@ \
|
|
||||||
@LIBGUAC_INCLUDE@
|
|
||||||
|
|
||||||
libguacd_la_LIBADD = \
|
|
||||||
@COMMON_LTLIB@ \
|
|
||||||
@LIBGUAC_LTLIB@
|
|
||||||
|
|
||||||
libguacd_la_LDFLAGS = \
|
|
||||||
@PTHREAD_LIBS@ \
|
|
||||||
@SSL_LIBS@
|
|
||||||
|
|
||||||
# SSL support
|
|
||||||
if ENABLE_SSL
|
|
||||||
noinst_HEADERS += libguacd/socket-ssl.h
|
|
||||||
libguacd_la_SOURCES += socket-ssl.c
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
3
src/guacenc/.gitignore
vendored
3
src/guacenc/.gitignore
vendored
@ -3,3 +3,6 @@
|
|||||||
guacenc
|
guacenc
|
||||||
guacenc.exe
|
guacenc.exe
|
||||||
|
|
||||||
|
# Documentation (built from .in files)
|
||||||
|
man/guacenc.1
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
|
||||||
|
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
|
||||||
|
# its own license boilerplate to the generated Makefile.in, that boilerplate
|
||||||
|
# does not apply to the transcluded portions of Makefile.am which are licensed
|
||||||
|
# to you by the ASF under the Apache License, Version 2.0, as described above.
|
||||||
|
#
|
||||||
|
|
||||||
AUTOMAKE_OPTIONS = foreign
|
AUTOMAKE_OPTIONS = foreign
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ man_MANS = \
|
|||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
buffer.h \
|
buffer.h \
|
||||||
|
cursor.h \
|
||||||
display.h \
|
display.h \
|
||||||
encode.h \
|
encode.h \
|
||||||
ffmpeg-compat.h \
|
ffmpeg-compat.h \
|
||||||
@ -41,6 +48,7 @@ noinst_HEADERS = \
|
|||||||
|
|
||||||
guacenc_SOURCES = \
|
guacenc_SOURCES = \
|
||||||
buffer.c \
|
buffer.c \
|
||||||
|
cursor.c \
|
||||||
display.c \
|
display.c \
|
||||||
display-buffers.c \
|
display-buffers.c \
|
||||||
display-image-streams.c \
|
display-image-streams.c \
|
||||||
@ -59,6 +67,7 @@ guacenc_SOURCES = \
|
|||||||
instruction-dispose.c \
|
instruction-dispose.c \
|
||||||
instruction-end.c \
|
instruction-end.c \
|
||||||
instruction-img.c \
|
instruction-img.c \
|
||||||
|
instruction-mouse.c \
|
||||||
instruction-move.c \
|
instruction-move.c \
|
||||||
instruction-rect.c \
|
instruction-rect.c \
|
||||||
instruction-shade.c \
|
instruction-shade.c \
|
||||||
@ -81,6 +90,7 @@ endif
|
|||||||
guacenc_CFLAGS = \
|
guacenc_CFLAGS = \
|
||||||
-Werror -Wall \
|
-Werror -Wall \
|
||||||
@AVCODEC_CFLAGS@ \
|
@AVCODEC_CFLAGS@ \
|
||||||
|
@AVFORMAT_CFLAGS@ \
|
||||||
@AVUTIL_CFLAGS@ \
|
@AVUTIL_CFLAGS@ \
|
||||||
@LIBGUAC_INCLUDE@ \
|
@LIBGUAC_INCLUDE@ \
|
||||||
@SWSCALE_CFLAGS@
|
@SWSCALE_CFLAGS@
|
||||||
@ -90,6 +100,7 @@ guacenc_LDADD = \
|
|||||||
|
|
||||||
guacenc_LDFLAGS = \
|
guacenc_LDFLAGS = \
|
||||||
@AVCODEC_LIBS@ \
|
@AVCODEC_LIBS@ \
|
||||||
|
@AVFORMAT_LIBS@ \
|
||||||
@AVUTIL_LIBS@ \
|
@AVUTIL_LIBS@ \
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@JPEG_LIBS@ \
|
@JPEG_LIBS@ \
|
||||||
@ -97,5 +108,5 @@ guacenc_LDFLAGS = \
|
|||||||
@WEBP_LIBS@
|
@WEBP_LIBS@
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
man/guacenc.1
|
man/guacenc.1.in
|
||||||
|
|
||||||
|
@ -18,39 +18,42 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "cursor.h"
|
||||||
|
|
||||||
#include "client_suite.h"
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <CUnit/Basic.h>
|
guacenc_cursor* guacenc_cursor_alloc() {
|
||||||
|
|
||||||
int client_suite_init() {
|
/* Allocate new cursor */
|
||||||
return 0;
|
guacenc_cursor* cursor = (guacenc_cursor*) malloc(sizeof(guacenc_cursor));
|
||||||
}
|
if (cursor == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
int client_suite_cleanup() {
|
/* Allocate associated buffer (image) */
|
||||||
return 0;
|
cursor->buffer = guacenc_buffer_alloc();
|
||||||
}
|
if (cursor->buffer == NULL) {
|
||||||
|
free(cursor);
|
||||||
int register_client_suite() {
|
return NULL;
|
||||||
|
|
||||||
/* Add client test suite */
|
|
||||||
CU_pSuite suite = CU_add_suite("client",
|
|
||||||
client_suite_init, client_suite_cleanup);
|
|
||||||
if (suite == NULL) {
|
|
||||||
CU_cleanup_registry();
|
|
||||||
return CU_get_error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add tests */
|
/* Do not initially render cursor, unless it moves */
|
||||||
if (
|
cursor->x = cursor->y = -1;
|
||||||
CU_add_test(suite, "layer-pool", test_layer_pool) == NULL
|
|
||||||
|| CU_add_test(suite, "buffer-pool", test_buffer_pool) == NULL
|
|
||||||
) {
|
|
||||||
CU_cleanup_registry();
|
|
||||||
return CU_get_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return cursor;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void guacenc_cursor_free(guacenc_cursor* cursor) {
|
||||||
|
|
||||||
|
/* Ignore NULL cursors */
|
||||||
|
if (cursor == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Free underlying buffer */
|
||||||
|
guacenc_buffer_free(cursor->buffer);
|
||||||
|
|
||||||
|
free(cursor);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
87
src/guacenc/cursor.h
Normal file
87
src/guacenc/cursor.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUACENC_CURSOR_H
|
||||||
|
#define GUACENC_CURSOR_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/timestamp.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mouse cursor, having a current location, hostspot, and associated cursor
|
||||||
|
* image.
|
||||||
|
*/
|
||||||
|
typedef struct guacenc_cursor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current X coordinate of the mouse cursor, in pixels. Valid values
|
||||||
|
* are non-negative. Negative values indicate that the cursor should not
|
||||||
|
* be rendered.
|
||||||
|
*/
|
||||||
|
int x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current Y coordinate of the mouse cursor, in pixels. Valid values
|
||||||
|
* are non-negative. Negative values indicate that the cursor should not
|
||||||
|
* be rendered.
|
||||||
|
*/
|
||||||
|
int y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X coordinate of the mouse cursor hotspot within the cursor image,
|
||||||
|
* in pixels.
|
||||||
|
*/
|
||||||
|
int hotspot_x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y coordinate of the mouse cursor hotspot within the cursor image,
|
||||||
|
* in pixels.
|
||||||
|
*/
|
||||||
|
int hotspot_y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current mouse cursor image.
|
||||||
|
*/
|
||||||
|
guacenc_buffer* buffer;
|
||||||
|
|
||||||
|
} guacenc_cursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates and initializes a new cursor object.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A newly-allocated and initialized guacenc_cursor, or NULL if allocation
|
||||||
|
* fails.
|
||||||
|
*/
|
||||||
|
guacenc_cursor* guacenc_cursor_alloc();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees all memory associated with the given cursor object. If the cursor
|
||||||
|
* provided is NULL, this function has no effect.
|
||||||
|
*
|
||||||
|
* @param cursor
|
||||||
|
* The cursor to free, which may be NULL.
|
||||||
|
*/
|
||||||
|
void guacenc_cursor_free(guacenc_cursor* cursor);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -20,9 +20,12 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "layer.h"
|
#include "layer.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#include <cairo/cairo.h>
|
#include <cairo/cairo.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -75,6 +78,50 @@ static int guacenc_display_layer_comparator(const void* a, const void* b) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the mouse cursor on top of the frame buffer of the default layer of
|
||||||
|
* the given display.
|
||||||
|
*
|
||||||
|
* @param display
|
||||||
|
* The display whose mouse cursor should be rendered to the frame buffer
|
||||||
|
* of its default layer.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero if rendering succeeds, non-zero otherwise.
|
||||||
|
*/
|
||||||
|
static int guacenc_display_render_cursor(guacenc_display* display) {
|
||||||
|
|
||||||
|
guacenc_cursor* cursor = display->cursor;
|
||||||
|
|
||||||
|
/* Do not render cursor if coordinates are negative */
|
||||||
|
if (cursor->x < 0 || cursor->y < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Retrieve default layer (guaranteed to not be NULL) */
|
||||||
|
guacenc_layer* def_layer = guacenc_display_get_layer(display, 0);
|
||||||
|
assert(def_layer != NULL);
|
||||||
|
|
||||||
|
/* Get source and destination buffers */
|
||||||
|
guacenc_buffer* src = cursor->buffer;
|
||||||
|
guacenc_buffer* dst = def_layer->frame;
|
||||||
|
|
||||||
|
/* Render cursor to layer */
|
||||||
|
if (src->width > 0 && src->height > 0) {
|
||||||
|
cairo_set_source_surface(dst->cairo, src->surface,
|
||||||
|
cursor->x - cursor->hotspot_x,
|
||||||
|
cursor->y - cursor->hotspot_y);
|
||||||
|
cairo_rectangle(dst->cairo,
|
||||||
|
cursor->x - cursor->hotspot_x,
|
||||||
|
cursor->y - cursor->hotspot_y,
|
||||||
|
src->width, src->height);
|
||||||
|
cairo_fill(dst->cairo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always succeeds */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
int guacenc_display_flatten(guacenc_display* display) {
|
int guacenc_display_flatten(guacenc_display* display) {
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
@ -151,7 +198,8 @@ int guacenc_display_flatten(guacenc_display* display) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
/* Render cursor on top of everything else */
|
||||||
|
return guacenc_display_render_cursor(display);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "cursor.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
|
|
||||||
@ -97,6 +98,9 @@ guacenc_display* guacenc_display_alloc(const char* path, const char* codec,
|
|||||||
/* Associate display with video output */
|
/* Associate display with video output */
|
||||||
display->output = video;
|
display->output = video;
|
||||||
|
|
||||||
|
/* Allocate special-purpose cursor layer */
|
||||||
|
display->cursor = guacenc_cursor_alloc();
|
||||||
|
|
||||||
return display;
|
return display;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -124,6 +128,9 @@ int guacenc_display_free(guacenc_display* display) {
|
|||||||
for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++)
|
for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++)
|
||||||
guacenc_image_stream_free(display->image_streams[i]);
|
guacenc_image_stream_free(display->image_streams[i]);
|
||||||
|
|
||||||
|
/* Free cursor */
|
||||||
|
guacenc_cursor_free(display->cursor);
|
||||||
|
|
||||||
free(display);
|
free(display);
|
||||||
return retval;
|
return retval;
|
||||||
|
|
||||||
|
@ -22,10 +22,12 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
|
#include "cursor.h"
|
||||||
#include "image-stream.h"
|
#include "image-stream.h"
|
||||||
#include "layer.h"
|
#include "layer.h"
|
||||||
#include "video.h"
|
#include "video.h"
|
||||||
|
|
||||||
|
#include <cairo/cairo.h>
|
||||||
#include <guacamole/protocol.h>
|
#include <guacamole/protocol.h>
|
||||||
#include <guacamole/timestamp.h>
|
#include <guacamole/timestamp.h>
|
||||||
|
|
||||||
@ -52,6 +54,11 @@
|
|||||||
*/
|
*/
|
||||||
typedef struct guacenc_display {
|
typedef struct guacenc_display {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current mouse cursor state.
|
||||||
|
*/
|
||||||
|
guacenc_cursor* cursor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All currently-allocated buffers. The index of the buffer corresponds to
|
* All currently-allocated buffers. The index of the buffer corresponds to
|
||||||
* its position within this array, where -1 is the 0th entry. If a buffer
|
* its position within this array, where -1 is the 0th entry. If a buffer
|
||||||
|
@ -63,8 +63,11 @@ static int guacenc_read_instructions(guacenc_display* display,
|
|||||||
|
|
||||||
/* Continuously read and handle all instructions */
|
/* Continuously read and handle all instructions */
|
||||||
while (!guac_parser_read(parser, socket, -1)) {
|
while (!guac_parser_read(parser, socket, -1)) {
|
||||||
guacenc_handle_instruction(display, parser->opcode,
|
if (guacenc_handle_instruction(display, parser->opcode,
|
||||||
parser->argc, parser->argv);
|
parser->argc, parser->argv)) {
|
||||||
|
guacenc_log(GUAC_LOG_DEBUG, "Handling of \"%s\" instruction "
|
||||||
|
"failed.", parser->opcode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fail on read/parse error */
|
/* Fail on read/parse error */
|
||||||
|
@ -51,8 +51,41 @@
|
|||||||
*/
|
*/
|
||||||
static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
|
static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
|
||||||
|
|
||||||
/* Write data, logging any errors */
|
int ret;
|
||||||
if (fwrite(data, 1, size, video->output) == 0) {
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0)
|
||||||
|
|
||||||
|
AVPacket pkt;
|
||||||
|
|
||||||
|
/* Have to create a packet around the encoded data we have */
|
||||||
|
av_init_packet(&pkt);
|
||||||
|
|
||||||
|
if (video->context->coded_frame->pts != AV_NOPTS_VALUE) {
|
||||||
|
pkt.pts = av_rescale_q(video->context->coded_frame->pts,
|
||||||
|
video->context->time_base,
|
||||||
|
video->output_stream->time_base);
|
||||||
|
}
|
||||||
|
if (video->context->coded_frame->key_frame) {
|
||||||
|
pkt->flags |= AV_PKT_FLAG_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.data = data;
|
||||||
|
pkt.size = size;
|
||||||
|
pkt.stream_index = video->output_stream->index;
|
||||||
|
ret = av_interleaved_write_frame(video->container_format_context, &pkt);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* We know data is already a packet if we're using a newer libavcodec */
|
||||||
|
AVPacket* pkt = (AVPacket*) data;
|
||||||
|
av_packet_rescale_ts(pkt, video->context->time_base, video->output_stream->time_base);
|
||||||
|
pkt->stream_index = video->output_stream->index;
|
||||||
|
ret = av_interleaved_write_frame(video->container_format_context, pkt);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
|
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
|
||||||
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
|
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
@ -62,8 +95,7 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
|
|||||||
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
|
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
|
||||||
video->next_pts, size);
|
video->next_pts, size);
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||||
@ -103,6 +135,15 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
/* For libavcodec < 57.37.100: input/output was not decoupled and static
|
||||||
|
* allocation of AVPacket was supported.
|
||||||
|
*
|
||||||
|
* NOTE: Since dynamic allocation of AVPacket was added before this point (in
|
||||||
|
* 57.12.100) and static allocation was deprecated later (in 58.133.100), it is
|
||||||
|
* convenient to tie static vs. dynamic allocation to the old vs. new I/O
|
||||||
|
* mechanism and avoid further complicating the version comparison logic. */
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 37, 100)
|
||||||
|
|
||||||
/* Init video packet */
|
/* Init video packet */
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
av_init_packet(&packet);
|
av_init_packet(&packet);
|
||||||
@ -111,8 +152,6 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
packet.data = NULL;
|
packet.data = NULL;
|
||||||
packet.size = 0;
|
packet.size = 0;
|
||||||
|
|
||||||
/* For libavcodec < 57.16.0: input/output was not decoupled */
|
|
||||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,16,0)
|
|
||||||
/* Write frame to video */
|
/* Write frame to video */
|
||||||
int got_data;
|
int got_data;
|
||||||
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
|
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
|
||||||
@ -123,10 +162,12 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
|
|
||||||
/* Write corresponding data to file */
|
/* Write corresponding data to file */
|
||||||
if (got_data) {
|
if (got_data) {
|
||||||
guacenc_write_packet(video, packet.data, packet.size);
|
guacenc_write_packet(video, (void*) &packet, packet.size);
|
||||||
av_packet_unref(&packet);
|
av_packet_unref(&packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/* Write frame to video */
|
/* Write frame to video */
|
||||||
int result = avcodec_send_frame(video->context, frame);
|
int result = avcodec_send_frame(video->context, frame);
|
||||||
|
|
||||||
@ -141,18 +182,25 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AVPacket* packet = av_packet_alloc();
|
||||||
|
if (packet == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
/* Flush all available packets */
|
/* Flush all available packets */
|
||||||
int got_data = 0;
|
int got_data = 0;
|
||||||
while (avcodec_receive_packet(video->context, &packet) == 0) {
|
while (avcodec_receive_packet(video->context, packet) == 0) {
|
||||||
|
|
||||||
/* Data was received */
|
/* Data was received */
|
||||||
got_data = 1;
|
got_data = 1;
|
||||||
|
|
||||||
/* Attempt to write data to output file */
|
/* Attempt to write data to output file */
|
||||||
guacenc_write_packet(video, packet.data, packet.size);
|
guacenc_write_packet(video, (void*) packet, packet->size);
|
||||||
av_packet_unref(&packet);
|
av_packet_unref(packet);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
av_packet_free(&packet);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Frame may have been queued for later writing / reordering */
|
/* Frame may have been queued for later writing / reordering */
|
||||||
@ -165,3 +213,54 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
||||||
|
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||||
|
int pix_fmt, AVRational time_base) {
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 33, 100)
|
||||||
|
stream->codec->bit_rate = bitrate;
|
||||||
|
stream->codec->width = width;
|
||||||
|
stream->codec->height = height;
|
||||||
|
stream->codec->gop_size = gop_size;
|
||||||
|
stream->codec->qmax = qmax;
|
||||||
|
stream->codec->qmin = qmin;
|
||||||
|
stream->codec->pix_fmt = pix_fmt;
|
||||||
|
stream->codec->time_base = time_base;
|
||||||
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(55, 44, 100)
|
||||||
|
stream->time_base = time_base;
|
||||||
|
#endif
|
||||||
|
return stream->codec;
|
||||||
|
#else
|
||||||
|
AVCodecContext* context = avcodec_alloc_context3(codec);
|
||||||
|
if (context) {
|
||||||
|
context->bit_rate = bitrate;
|
||||||
|
context->width = width;
|
||||||
|
context->height = height;
|
||||||
|
context->gop_size = gop_size;
|
||||||
|
context->qmax = qmax;
|
||||||
|
context->qmin = qmin;
|
||||||
|
context->pix_fmt = pix_fmt;
|
||||||
|
context->time_base = time_base;
|
||||||
|
stream->time_base = time_base;
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||||
|
const AVCodec *codec, AVDictionary **options,
|
||||||
|
AVStream* stream) {
|
||||||
|
|
||||||
|
int ret = avcodec_open2(avcodec_context, codec, options);
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
|
||||||
|
/* Copy stream parameters to the muxer */
|
||||||
|
int codecpar_ret = avcodec_parameters_from_context(stream->codecpar, avcodec_context);
|
||||||
|
if (codecpar_ret < 0)
|
||||||
|
return codecpar_ret;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -52,6 +52,16 @@
|
|||||||
#define av_packet_unref av_free_packet
|
#define av_packet_unref av_free_packet
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* For libavcodec <= 56.41.100: Global header flag didn't have AV_ prefix.
|
||||||
|
* Guacenc defines its own flag here to avoid conflicts with libavcodec
|
||||||
|
* macros.
|
||||||
|
*/
|
||||||
|
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(56,41,100)
|
||||||
|
#define GUACENC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER
|
||||||
|
#else
|
||||||
|
#define GUACENC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER
|
||||||
|
#endif
|
||||||
|
|
||||||
/* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */
|
/* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */
|
||||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
|
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
|
||||||
#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
|
#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
|
||||||
@ -78,5 +88,78 @@
|
|||||||
*/
|
*/
|
||||||
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
|
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and sets up the AVCodecContext for the appropriate version of
|
||||||
|
* libavformat installed. The AVCodecContext will be built, but the AVStream
|
||||||
|
* will also be affected by having its time_base field set to the value passed
|
||||||
|
* into this function.
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
* The open AVStream.
|
||||||
|
*
|
||||||
|
* @param codec
|
||||||
|
* The codec used on the AVStream.
|
||||||
|
*
|
||||||
|
* @param bitrate
|
||||||
|
* The target bitrate for the encoded video
|
||||||
|
*
|
||||||
|
* @param width
|
||||||
|
* The target width for the encoded video.
|
||||||
|
*
|
||||||
|
* @param height
|
||||||
|
* The target height for the encoded video.
|
||||||
|
*
|
||||||
|
* @param gop_size
|
||||||
|
* The size of the Group of Pictures.
|
||||||
|
*
|
||||||
|
* @param qmax
|
||||||
|
* The max value of the quantizer.
|
||||||
|
*
|
||||||
|
* @param qmin
|
||||||
|
* The min value of the quantizer.
|
||||||
|
*
|
||||||
|
* @param pix_fmt
|
||||||
|
* The target pixel format for the encoded video.
|
||||||
|
*
|
||||||
|
* @param time_base
|
||||||
|
* The target time base for the encoded video.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The pointer to the configured AVCodecContext.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
||||||
|
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||||
|
int pix_fmt, AVRational time_base);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for avcodec_open2(). Because libavformat ver 57.33.100 and greater
|
||||||
|
* use stream->codecpar rather than stream->codec to handle information to the
|
||||||
|
* codec, there needs to be an additional step in that version. So this
|
||||||
|
* wrapper handles that. Otherwise, it's the same as avcodec_open2().
|
||||||
|
*
|
||||||
|
* @param avcodec_context
|
||||||
|
* The context to initialize.
|
||||||
|
*
|
||||||
|
* @param codec
|
||||||
|
* The codec to open this context for. If a non-NULL codec has been
|
||||||
|
* previously passed to avcodec_alloc_context3() or for this context, then
|
||||||
|
* this parameter MUST be either NULL or equal to the previously passed
|
||||||
|
* codec.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* A dictionary filled with AVCodecContext and codec-private options. On
|
||||||
|
* return this object will be filled with options that were not found.
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
* The stream for the codec context.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero on success, a negative value on error.
|
||||||
|
*/
|
||||||
|
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||||
|
const AVCodec *codec, AVDictionary **options,
|
||||||
|
AVStream* stream);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -75,8 +76,14 @@ int main(int argc, char* argv[]) {
|
|||||||
guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) "
|
guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) "
|
||||||
"version " VERSION);
|
"version " VERSION);
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
|
||||||
/* Prepare libavcodec */
|
/* Prepare libavcodec */
|
||||||
avcodec_register_all();
|
avcodec_register_all();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||||
|
av_register_all();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Track number of overall failures */
|
/* Track number of overall failures */
|
||||||
int total_files = argc - optind;
|
int total_files = argc - optind;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "display.h"
|
||||||
#include "image-stream.h"
|
#include "image-stream.h"
|
||||||
#include "jpeg.h"
|
#include "jpeg.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@ -139,6 +140,7 @@ int guacenc_image_stream_end(guacenc_image_stream* stream,
|
|||||||
|
|
||||||
/* Draw surface to buffer */
|
/* Draw surface to buffer */
|
||||||
if (buffer->cairo != NULL) {
|
if (buffer->cairo != NULL) {
|
||||||
|
cairo_set_operator(buffer->cairo, guacenc_display_cairo_operator(stream->mask));
|
||||||
cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y);
|
cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y);
|
||||||
cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height);
|
cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height);
|
||||||
cairo_fill(buffer->cairo);
|
cairo_fill(buffer->cairo);
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "cursor.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
@ -36,16 +38,32 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) {
|
|||||||
/* Parse arguments */
|
/* Parse arguments */
|
||||||
int hotspot_x = atoi(argv[0]);
|
int hotspot_x = atoi(argv[0]);
|
||||||
int hotspot_y = atoi(argv[1]);
|
int hotspot_y = atoi(argv[1]);
|
||||||
int src_index = atoi(argv[2]);
|
int sindex = atoi(argv[2]);
|
||||||
int src_x = atoi(argv[3]);
|
int sx = atoi(argv[3]);
|
||||||
int src_y = atoi(argv[4]);
|
int sy = atoi(argv[4]);
|
||||||
int src_w = atoi(argv[5]);
|
int width = atoi(argv[5]);
|
||||||
int src_h = atoi(argv[6]);
|
int height = atoi(argv[6]);
|
||||||
|
|
||||||
/* Nothing to do with cursor (yet) */
|
/* Pull buffer of source layer/buffer */
|
||||||
guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) "
|
guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex);
|
||||||
"src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y,
|
if (src == NULL)
|
||||||
src_index, src_x, src_y, src_w, src_h);
|
return 1;
|
||||||
|
|
||||||
|
/* Update cursor hotspot */
|
||||||
|
guacenc_cursor* cursor = display->cursor;
|
||||||
|
cursor->hotspot_x = hotspot_x;
|
||||||
|
cursor->hotspot_y = hotspot_y;
|
||||||
|
|
||||||
|
/* Resize cursor to exactly fit */
|
||||||
|
guacenc_buffer_resize(cursor->buffer, width, height);
|
||||||
|
|
||||||
|
/* Copy rectangle from source to cursor */
|
||||||
|
guacenc_buffer* dst = cursor->buffer;
|
||||||
|
if (src->surface != NULL && dst->cairo != NULL) {
|
||||||
|
cairo_set_operator(dst->cairo, CAIRO_OPERATOR_SOURCE);
|
||||||
|
cairo_set_source_surface(dst->cairo, src->surface, sx, sy);
|
||||||
|
cairo_paint(dst->cairo);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -17,42 +17,40 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef __GUAC_RDP_RDP_RAIL_H
|
|
||||||
#define __GUAC_RDP_RDP_RAIL_H
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "cursor.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "parse.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
|
|
||||||
#ifdef ENABLE_WINPR
|
#include <stdlib.h>
|
||||||
#include <winpr/stream.h>
|
|
||||||
#else
|
|
||||||
#include "compat/winpr-stream.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) {
|
||||||
* Dispatches a given RAIL event to the appropriate handler.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The guac_client associated with the current RDP session.
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* The RAIL event to process.
|
|
||||||
*/
|
|
||||||
void guac_rdp_process_rail_event(guac_client* client, wMessage* event);
|
|
||||||
|
|
||||||
/**
|
/* Verify argument count */
|
||||||
* Handles the event sent when updating system parameters. The event given
|
if (argc < 2) {
|
||||||
* MUST be a SYSPARAM event.
|
guacenc_log(GUAC_LOG_WARNING, "\"mouse\" instruction incomplete");
|
||||||
*
|
return 1;
|
||||||
* @param client
|
}
|
||||||
* The guac_client associated with the current RDP session.
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* The system parameter event to process.
|
|
||||||
*/
|
|
||||||
void guac_rdp_process_rail_get_sysparam(guac_client* client, wMessage* event);
|
|
||||||
|
|
||||||
#endif
|
/* Parse arguments */
|
||||||
|
int x = atoi(argv[0]);
|
||||||
|
int y = atoi(argv[1]);
|
||||||
|
|
||||||
|
/* Update cursor properties */
|
||||||
|
guacenc_cursor* cursor = display->cursor;
|
||||||
|
cursor->x = x;
|
||||||
|
cursor->y = y;
|
||||||
|
|
||||||
|
/* If no timestamp provided, nothing further to do */
|
||||||
|
if (argc < 4)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Leverage timestamp to render frame */
|
||||||
|
guac_timestamp timestamp = guacenc_parse_timestamp(argv[3]);
|
||||||
|
return guacenc_display_sync(display, timestamp);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -38,13 +38,13 @@ int guacenc_handle_size(guacenc_display* display, int argc, char** argv) {
|
|||||||
int width = atoi(argv[1]);
|
int width = atoi(argv[1]);
|
||||||
int height = atoi(argv[2]);
|
int height = atoi(argv[2]);
|
||||||
|
|
||||||
/* Retrieve requested layer */
|
/* Retrieve requested layer/buffer */
|
||||||
guacenc_layer* layer = guacenc_display_get_layer(display, index);
|
guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index);
|
||||||
if (layer == NULL)
|
if (buffer == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
/* Resize layer */
|
/* Resize layer/buffer */
|
||||||
return guacenc_buffer_resize(layer->buffer, width, height);
|
return guacenc_buffer_resize(buffer, width, height);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "parse.h"
|
||||||
|
|
||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <guacamole/timestamp.h>
|
#include <guacamole/timestamp.h>
|
||||||
@ -27,40 +28,6 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a guac_timestamp from the given string. The string is assumed to
|
|
||||||
* consist solely of decimal digits with an optional leading minus sign. If the
|
|
||||||
* given string contains other characters, the behavior of this function is
|
|
||||||
* undefined.
|
|
||||||
*
|
|
||||||
* @param str
|
|
||||||
* The string to parse, which must contain only decimal digits and an
|
|
||||||
* optional leading minus sign.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A guac_timestamp having the same value as the provided string.
|
|
||||||
*/
|
|
||||||
static guac_timestamp guacenc_parse_timestamp(const char* str) {
|
|
||||||
|
|
||||||
int sign = 1;
|
|
||||||
int64_t num = 0;
|
|
||||||
|
|
||||||
for (; *str != '\0'; str++) {
|
|
||||||
|
|
||||||
/* Flip sign for each '-' encountered */
|
|
||||||
if (*str == '-')
|
|
||||||
sign = -sign;
|
|
||||||
|
|
||||||
/* If not '-', assume the character is a digit */
|
|
||||||
else
|
|
||||||
num = num * 10 + (*str - '0');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return (guac_timestamp) (num * sign);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) {
|
int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) {
|
||||||
|
|
||||||
/* Verify argument count */
|
/* Verify argument count */
|
||||||
|
@ -30,6 +30,7 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = {
|
|||||||
{"blob", guacenc_handle_blob},
|
{"blob", guacenc_handle_blob},
|
||||||
{"img", guacenc_handle_img},
|
{"img", guacenc_handle_img},
|
||||||
{"end", guacenc_handle_end},
|
{"end", guacenc_handle_end},
|
||||||
|
{"mouse", guacenc_handle_mouse},
|
||||||
{"sync", guacenc_handle_sync},
|
{"sync", guacenc_handle_sync},
|
||||||
{"cursor", guacenc_handle_cursor},
|
{"cursor", guacenc_handle_cursor},
|
||||||
{"copy", guacenc_handle_copy},
|
{"copy", guacenc_handle_copy},
|
||||||
|
@ -114,6 +114,11 @@ guacenc_instruction_handler guacenc_handle_img;
|
|||||||
*/
|
*/
|
||||||
guacenc_instruction_handler guacenc_handle_end;
|
guacenc_instruction_handler guacenc_handle_end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for the Guacamole "mouse" instruction.
|
||||||
|
*/
|
||||||
|
guacenc_instruction_handler guacenc_handle_mouse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for the Guacamole "sync" instruction.
|
* Handler for the Guacamole "sync" instruction.
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
.\" specific language governing permissions and limitations
|
.\" specific language governing permissions and limitations
|
||||||
.\" under the License.
|
.\" under the License.
|
||||||
.\"
|
.\"
|
||||||
.TH guacenc 1 "9 Jan 2017" "version 0.9.11-incubating" "Guacamole"
|
.TH guacenc 1 "26 Jan 2018" "version @PACKAGE_VERSION@" "Apache Guacamole"
|
||||||
.
|
.
|
||||||
.SH NAME
|
.SH NAME
|
||||||
guacenc \- Guacamole video encoder
|
guacenc \- Guacamole video encoder
|
||||||
@ -38,7 +38,7 @@ is essentially an implementation of a Guacamole client which accepts
|
|||||||
its input from files instead of a network connection, and renders directly to
|
its input from files instead of a network connection, and renders directly to
|
||||||
video instead of to the user's screen.
|
video instead of to the user's screen.
|
||||||
.P
|
.P
|
||||||
Each \fIFILE\fR specified will be encoded as a raw MPEG-4 video stream to a new
|
Each \fIFILE\fR specified will be encoded as MPEG-4 video to a new
|
||||||
file named \fIFILE\fR.m4v, encoded according to the other options specified. By
|
file named \fIFILE\fR.m4v, encoded according to the other options specified. By
|
||||||
default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved
|
default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved
|
||||||
with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be
|
with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be
|
||||||
@ -76,5 +76,5 @@ Overrides the default behavior of
|
|||||||
such that input files will be encoded even if they appear to be recordings of
|
such that input files will be encoded even if they appear to be recordings of
|
||||||
in-progress Guacamole sessions.
|
in-progress Guacamole sessions.
|
||||||
.
|
.
|
||||||
.SH AUTHOR
|
.SH SEE ALSO
|
||||||
Written by Michael Jumper <mike.jumper@guac-dev.org>
|
.BR guaclog (1)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user