Compare commits

..

207 Commits

Author SHA1 Message Date
Virtually Nick
47b9360d46
GUACAMOLE-1714: Merge update guacenc for const parameters/values introduced in FFmpeg 5.0. 2023-02-02 20:36:32 -05:00
Mike Jumper
98c2a6adcb
GUACAMOLE-377: Merge correction ensuring users receive a proper frame boundary when joining. 2023-01-24 14:25:09 -08:00
Alex Leitner
3b0a9bac75 GUACAMOLE-377: Send a sync instruction to users when synchronizing surfaces. 2023-01-23 20:55:01 +00:00
Mike Jumper
f6893ed319 Merge 1.5.0 changes back to master. 2023-01-10 21:54:05 -08:00
James Muehlner
a5214c971a
GUACAMOLE-1604: Merge version number bumps for 1.5.0. 2023-01-10 17:16:43 -08:00
Mike Jumper
ccfcef8c0f GUACAMOLE-1604: Add explicit libtool version info for libguac-terminal. 2023-01-10 17:08:15 -08:00
Mike Jumper
1a7a57ed19 GUACAMOLE-1604: Update libtool version info for libguac (interfaces added and changed).
The only changed interface here is the guac_user_info struct, which now
has a "name" member.
2023-01-10 17:07:16 -08:00
Mike Jumper
eac064bde9 GUACAMOLE-1604: Bump version number to 1.5.0. 2023-01-10 17:02:06 -08:00
Virtually Nick
4afc1d85ce Merge 1.5.0 changes back to master. 2023-01-04 15:53:11 -05:00
Virtually Nick
818b5f79df
GUACAMOLE-1538: Merge add missing documentation for libguac-terminal. 2023-01-04 15:51:05 -05:00
Mike Jumper
8ef60bfa9d GUACAMOLE-1538: Document parameters of libguac-terminal handlers. 2023-01-04 12:40:21 -08:00
Mike Jumper
d90e0e97fe GUACAMOLE-1538: Add missing documentation for libguac-terminal functions. 2023-01-04 12:22:02 -08:00
Mike Jumper
ec7964e8fb GUACAMOLE-1538: Return number of bytes written for guac_terminal_write() and guac_terminal_printf(). 2023-01-04 12:05:02 -08:00
James Muehlner
add7ce361b Merge 1.5.0 changes back to master. 2022-11-29 03:46:57 +00:00
James Muehlner
7d16f67d6d
GUACAMOLE-1293: Merge fix for double acquisition/release of rwlock. 2022-11-28 14:31:44 -08:00
Mike Jumper
e3adb97085 GUACAMOLE-1293: Do not re-acquire __users_lock while already held for writing.
Per POSIX spec, the behavior of acquiring a read lock on a rwlock that's
already acquired for writing is undefined. From the documentation for
pthread_rwlock_rdlock():

"... Results are undefined if the calling thread holds a write lock on
rwlock at the time the call is made."
2022-11-28 13:37:41 -08:00
Mike Jumper
55941823ec Merge 1.5.0 changes back to master. 2022-11-25 23:24:08 -08:00
Mike Jumper
07acce8a76
GUACAMOLE-1293: Merge support for notifying when a user has joined/left a connection. 2022-11-25 23:23:13 -08:00
Virtually Nick
5b1677f21a GUACAMOLE-1293: Fix copy-pasta and style issues; add user ID to information passed to client. 2022-11-25 21:57:44 -05:00
Virtually Nick
623c398005 GUACAMOLE-1293: Change new user info member to simply "name" to clarify its purpose. 2022-11-24 18:13:06 -05:00
Virtually Nick
aa92239edd GUACAMOLE-1293: Rename new enum values to be more consistent with existing code. 2022-11-08 07:45:38 -05:00
Virtually Nick
897712c743 GUACAMOLE-1293: Update and add debug logging. 2022-11-08 07:45:38 -05:00
Virtually Nick
02b24d0101 GUACAMOLE-1293: Simplify the assignment of strings/constants. 2022-11-08 07:45:38 -05:00
Virtually Nick
26eadc37a3 GUACAMOLE-1293: Move to status code plus arguments for msg instruction. 2022-11-08 07:45:38 -05:00
Virtually Nick
6d7156bc70 GUACAMOLE-1293: Update struct member that stores human-readable name. 2022-11-08 07:45:38 -05:00
Virtually Nick
6312e1720d GUACAMOLE-1293: Add support for notifying owner of users joining and leaving. 2022-11-08 07:45:38 -05:00
Virtually Nick
cb7ae25177 GUACAMOLE-1293: Add support for the name handshake instruction. 2022-11-08 07:45:38 -05:00
Virtually Nick
a4adb3f5c0 GUACAMOLE-1293: Add protocol support for msg instruction. 2022-11-08 07:45:38 -05:00
Dan Fandrich
5cf408ebbb GUACAMOLE-1714: Adapt to const parameters of ffmpeg 5.0. 2022-11-07 12:16:35 -08:00
Mike Jumper
3ca6bb0a61
GUACAMOLE-1708: Merge correction to missing Czech keyboard character mapping. 2022-11-06 09:05:52 -08:00
Max
457a169c49 GUACAMOLE-1708: Added Czech keyboard keymap fix missing char 2022-11-06 12:17:21 +01:00
Mike Jumper
bad381cebe
GUACAMOLE-1708: Merge RDP support for Czech keyboard layout. 2022-11-05 08:56:56 -07:00
Max
6171da6d0b GUACAMOLE-1708: Added Czech keyboard keymap for RDP 2022-11-01 21:56:23 +01:00
Mike Jumper
067f2a91a0
GUACAMOLE-1682: Merge automatic newline normalization of terminal clipboard. 2022-10-18 12:41:48 -07:00
Alex Leitner
bc52485570 GUACAMOLE-1682: Normalize conflicting newline encodings in clipboards between Linux and Windows systems for ssh sessions. 2022-10-18 19:38:56 +00:00
Mike Jumper
b20afa275a
GUACAMOLE-1669: Merge fix for RSA key upgrade failure if FIPS mode is enabled. 2022-09-13 14:45:55 -07:00
James Muehlner
b096e47f57 GUACAMOLE-1669: Include ext-info-c in preferred KEX algorithms to ensure RSA key upgrades can happen. 2022-09-13 21:39:38 +00:00
Mike Jumper
4d211e0c9e
GUACAMOLE-1674: Merge changes removing NLA from negotiation if FIPS is enabled. 2022-09-08 09:44:47 -07:00
James Muehlner
dffbeac57a GUACAMOLE-1674: Warn about NLA mode if FIPS mode is enabled, or disable if possible. 2022-08-30 23:23:56 +00:00
Mike Jumper
0361adc01f
GUACAMOLE-1669: Merge FIPS support for SSH connections. 2022-08-24 15:29:46 -07:00
James Muehlner
1971a9dad2 GUACAMOLE-1669: Prefer FIPS compliant ciphers and algorithms when FIPS mode is enabled. 2022-08-24 22:23:46 +00:00
Virtually Nick
5dbf4820ab Merge 1.5.0 changes back to master. 2022-08-19 15:48:51 -04:00
Virtually Nick
b2eb13a178
GUACAMOLE-1540: Merge correct automated retrieval of Docker build dependencies. 2022-08-19 15:30:31 -04:00
Michael Jumper
2bc9d5ff01 GUACAMOLE-1540: Correct regex stripping of package version (major number may have multiple digits). 2022-08-19 12:12:29 -07:00
Michael Jumper
5918cc9f7c GUACAMOLE-1540: Ignore failures to find packages associated with libraries we build ourselves. 2022-08-19 12:12:29 -07:00
James Muehlner
15f6e9f678 Merge 1.5.0 changes back to master. 2022-08-16 18:48:31 +00:00
James Muehlner
b5addfe3da
GUACAMOLE-1540: Merge Alpine Linux docker base image with manual library builds. 2022-08-16 09:40:45 -07:00
Michael Jumper
7f4246b6d5 GUACAMOLE-1540: Manual build all core protocol libraries for Docker image using Alpine Linux base. 2022-08-16 08:39:54 -07:00
Virtually Nick
6ab82446bb
GUACAMOLE-1652: Merge only call SSL init functions when the library version requires it. 2022-07-30 07:36:37 -04:00
James Muehlner
9c93337d97 GUACAMOLE-1652: Migrate OpenSSL initialization to modern methods for OpenSSL >= 1.1.0. 2022-07-30 02:24:31 +00:00
James Muehlner
cdee93ae25 GUACAMOLE-1652: Only call SSL init functions when the library version requires it. 2022-07-30 02:22:36 +00:00
Mike Jumper
eee3ac092c
GUACAMOLE-1622: Merge correction to terminal resize regression. 2022-07-13 16:20:19 -07:00
Alex Leitner
5bb56ed5ba GUACAMOLE-1622: Restructured code to resolve scrollbar resizing bug where the scrollbar would clip off the side of the terminal. This fix also resolves the issue where the text would blur at certain intervals of resizing the window. 2022-07-13 21:57:47 +00:00
Mike Jumper
0aae5eeadb
GUACAMOLE-1636: Merge corrections to typos within RDP comments/documentation. 2022-07-13 13:55:05 -07:00
Jimmy
6d994db9d2 GUACAMOLE-1636: Fix a typo mistake invokved. 2022-07-13 23:47:13 +03:00
Jimmy
cba5484be0 GUACAMOLE-1636: Fix a typo mistake recieved. 2022-07-13 23:41:42 +03:00
Jimmy
4048dd4900 GUACAMOLE-1636: Fix a typo mistake assicated. 2022-07-13 23:32:12 +03:00
Jimmy
98556fbe2e GUACAMOLE-1636: Fix a typo mistake coordinare. 2022-07-13 23:24:06 +03:00
Jimmy
f438a36612 GUACAMOLE-1636: Fix a typo mistake synchonize. 2022-07-13 23:17:50 +03:00
Jimmy
e8d966aec6 GUACAMOLE-1636: Fix a typo mistake Versoin. 2022-07-13 23:10:36 +03:00
Jimmy
523532a52d GUACAMOLE-1636: Fix a typo mistake offscren. 2022-07-13 23:02:37 +03:00
Mike Jumper
51c640fdbd
GUACAMOLE-1436: Merge addition of missing FreeRDP winpr headers. 2022-07-05 12:09:00 -07:00
Virtually Nick
4cf1bfae0e
GUACAMOLE-377: Merge update unit tests for new prototype of guac_protocol_send_sync(). 2022-07-05 14:30:57 -04:00
Michael Jumper
9642afc468 GUACAMOLE-377: Update unit tests for new prototype of guac_protocol_send_sync().
The new guac_protocol_send_sync() requires an additional parameter: the
number of logical frames associated with the sync.
2022-07-05 10:58:38 -07:00
James Muehlner
ffb6c809be
GUACAMOLE-1622: Merge addition of margins to ssh sessions. 2022-06-22 09:32:00 -07:00
Alex Leitner
64ea9c4d1f GUACAMOLE-1622: Clarified comments to describe if param is a pointer. 2022-06-21 16:16:52 +00:00
Alex Leitner
a5834fd319 GUACAMOLE-1622: Separated logic into single responsibility functions. 2022-06-16 17:09:41 +00:00
Alex Leitner
1e9cd9137b GUACAMOLE-1622: Added margins to ssh sessions. 2022-06-15 16:59:20 +00:00
James Muehlner
d4cd9b3e3a
GUACAMOLE-377: Merge support for RemoteFX. 2022-06-09 17:41:23 -07:00
Michael Jumper
31f1b2c7c4 GUACAMOLE-377: Rename single-letter "e" event arguments variable to "args" for readability. 2022-06-09 09:02:11 -07:00
Michael Jumper
ce27936ed5 GUACAMOLE-377: Add frame boundaries around cursor set operations if otherwise absent. 2022-06-09 09:02:11 -07:00
Michael Jumper
b7f05b9e4f GUACAMOLE-377: Ensure backing surface of underlying FreeRDP GDI implementation is resized when desktop is resized. 2022-06-09 09:02:11 -07:00
Michael Jumper
d5761ad625 GUACAMOLE-377: Warn about required color depth only if actually overridden. 2022-06-09 09:02:11 -07:00
Michael Jumper
b26f9d64d6 GUACAMOLE-377: Clarify usage of EndPaint to detect frames. 2022-06-09 09:02:11 -07:00
Michael Jumper
da80163e24 GUACAMOLE-377: Enable graphics pipeline extension by default. 2022-06-09 09:02:11 -07:00
Michael Jumper
28396ae345 GUACAMOLE-377: Expect explicit RDP frame boundaries only after at least one frame boundary has been received. 2022-05-18 15:43:54 -07:00
Michael Jumper
a0e9f6ed9b GUACAMOLE-377: Leverage client timestamp tracking for RDP frame duration. 2022-05-18 15:43:54 -07:00
Michael Jumper
bde8cdee46 GUACAMOLE-377: Add general RDP support for frame markers. 2022-05-18 15:43:54 -07:00
Michael Jumper
669e02b4dc GUACAMOLE-377: Leverage RDPGFX to report remote frame statistics to the client. 2022-05-12 13:50:20 -07:00
Michael Jumper
52c8683bcf GUACAMOLE-377: Add protocol-level support for reporting remote frame statistics. 2022-05-12 13:50:20 -07:00
Michael Jumper
c19eab9691 GUACAMOLE-377: Revise processing lag calculations to consider cumulative processing lag. 2022-05-12 13:50:20 -07:00
Michael Jumper
dd85c54961 GUACAMOLE-377: Add handling for EndPaint required by software GDI implementation of RDPGFX. 2022-05-12 13:50:20 -07:00
Michael Jumper
c795bf9e4a GUACAMOLE-377: Control RemoteFX / GFX support with "enable-gfx" parameter. 2022-05-12 13:50:20 -07:00
Michael Jumper
c469300941 GUACAMOLE-377: Support for RDPGFX. 2022-05-12 13:50:20 -07:00
James Muehlner
81300052e0
GUACAMOLE-1595: Merge mouse mask initialization fix. 2022-05-02 17:24:58 -07:00
Michael Jumper
df4e5c6fdf GUACAMOLE-1595: Ensure all mouse buttons are initially released when terminal starts. 2022-05-03 00:20:08 +00:00
Virtually Nick
a175a3d902
GUACAMOLE-1312: Merge add Canadian French RDP keymap 2022-03-23 09:58:58 -04:00
Kaven Rousseau
9cbd768210 GUACAMOLE-1312: Added fr_ca keymap 2022-03-17 16:13:42 -04:00
Virtually Nick
c716a07abc Merge 1.5.0 changes back to master. 2022-03-17 15:24:48 -04:00
Virtually Nick
c880f02fe8
GUACAMOLE-1115: Merge ensure RDP print process does not block itself from completing. 2022-03-17 15:20:51 -04:00
Michael Jumper
ce88fa4d4a GUACAMOLE-1115: Forcibly kill any outstanding PDF filter job when cleaning up resources. 2022-03-17 18:35:38 +00:00
Michael Jumper
d734bac590 GUACAMOLE-1115: Do not hold general RDP message lock while waiting for print operations.
Holding the message lock will block handling of things like mouse and
keyboard events, as the message lock must be acquired before sending the
corresponding messages to the RDP server. If mouse and keyboard events
are blocked, then handling of further Guacamole instructions like "ack"
is also blocked. If the print job is blocked until an "ack" is received,
this results in deadlock.
2022-03-17 18:35:20 +00:00
Mike Jumper
75a11b05b2
GUACAMOLE-1543: Merge changes moving recording structures/functions to the public API. 2022-03-01 09:58:50 -08:00
James Muehlner
854b5ecbb8 GUACAMOLE-1543: Move recording functionality from common to libguac. 2022-03-01 04:01:44 +00:00
Mike Jumper
d8073f9b17
GUACAMOLE-1538: Merge corrections to libguac-terminal build and scope. 2022-02-28 16:56:04 -08:00
James Muehlner
46e813343e GUACAMOLE-1538: Only the core functionality of the terminal lib should be public. 2022-03-01 00:33:55 +00:00
James Muehlner
ad0155b5f5 GUACAMOLE-1538: Make it clear which functions are getters by adding _get_ to the name of each. 2022-02-24 12:02:36 -08:00
James Muehlner
0856e94ece GUACAMOLE-1538 Use dashes instead of underscores in filenames for consistency with libguac public API. 2022-02-24 11:12:05 -08:00
James Muehlner
1c97cdb115 GUACAMOLE-1538: Autogenerate docs for new library. 2022-02-23 13:49:32 -08:00
James Muehlner
ce2ffdf75f GUACAMOLE-1538: Improve code style and cleanliness. 2022-02-22 20:37:42 -08:00
James Muehlner
6dd33a8d90 GUACAMOLE-1538: Do not use terminal internals outside of terminal code. 2022-02-22 16:06:48 -08:00
James Muehlner
589e0ecd03 GUACAMOLE-1538 - Consolidate clipboard handling; opaque clipboard struct to avoid exposing internal guac_common_clipboard. 2022-02-22 14:04:47 -08:00
James Muehlner
037045a054 GUACAMOLE-1538: Explicitly include the common lib; ensure no undefined symbols. 2022-02-22 11:07:30 -08:00
James Muehlner
63bf5b329c GUACAMOLE-1538: Rename library to match conventions. 2022-02-22 09:41:08 -08:00
Mike Jumper
dc9dfe562f
GUACAMOLE-1540: Merge changes correcting Docker-specific search for FreeRDP install location. 2022-02-21 17:32:23 -08:00
James Muehlner
757928dfa1 GUACAMOLE-1540: Search for libfreerdp2 installation directly instead of checking links. 2022-02-21 16:57:48 -08:00
Virtually Nick
a0faa02616
GUACAMOLE-1538: Merge refactor libguac_terminal for easier extensibility, and migrate to shared library. 2022-02-21 14:30:40 -05:00
James Muehlner
44d76da21a GUACAMOLE-1538: Use an options struct instead of hardcoding params in constructor. 2022-02-21 11:27:20 -08:00
James Muehlner
05c6131cf3 GUACAMOLE-1538: Update libguac_terminal to be a shared library. 2022-02-21 11:27:13 -08:00
Mike Jumper
001347b4e8
GUACAMOLE-1540: Merge migration of guacd Docker image to Ubuntu 21.10. 2022-02-18 16:20:17 -08:00
James Muehlner
edce11fcb4 GUACAMOLE-1540: Build using Ubuntu 21.10 as a base instead of buster-slim. 2022-02-18 15:48:18 -08:00
James Muehlner
e78d589e25
GUACAMOLE-876: Merge null-check fix for RDP open file check. 2022-02-18 13:50:03 -08:00
Michael Jumper
06461cac53 GUACAMOLE-876: Test for open files only if filesystem has been allocated. 2022-02-18 13:40:15 -08:00
Virtually Nick
8e94b00587
GUACAMOLE-1495: Merge add keymap for Polish keyboard layout for RDP 2022-02-17 12:46:01 -05:00
Virtually Nick
9245d02bc5
GUACAMOLE-462: Merge create recordings/typescripts with group read permission. 2022-02-17 12:43:51 -05:00
Michael Jumper
4d41b38a24 GUACAMOLE-462: Create recordings/typescripts with group read permission.
Previously, all recordings/typescripts were strictly readable by the
service user that created them (guacd). This prevents reading by other
services like the Guacamole web application. Instead,
recordings/typescripts should at least be group-readable.
2022-02-17 09:25:28 -08:00
Mike Jumper
29535e6cb8
GUACAMOLE-876: Merge changes deferring reconnect-to-resize until active transfers are complete. 2022-02-02 09:56:41 -08:00
Virtually Nick
10126444bf GUACAMOLE-876: Avoid disrupting open files and active print jobs to update display. 2022-02-01 21:45:10 -05:00
Mike Jumper
23612720ce
GUACAMOLE-745: Merge support for OpenSSH-format private keys / Ed25519. 2022-01-12 11:38:12 -08:00
Joshua Roys
f84db7d166 GUACAMOLE-745: Support OpenSSH private keys & ED25519
Let libssh2 parse PEM and ssh-native keys. Requires libssh2 1.9.0+
compiled against a crypto backend supporting ed25519.
2022-01-12 09:02:11 -05:00
ClassicGOD
a1d0d0aea4
GUACAMOLE-1495: add entry for pl_pl_qwerty
Add pl_pl_qwerty.keymap to rdp_keymaps
2022-01-10 20:31:41 +01:00
ClassicGOD
534158c1cb
GUACAMOLE-1495: add pl_pl_qwerty keymap
Add keymap file for Polish keyboard layout
2022-01-10 20:28:11 +01:00
Mike Jumper
56dca9880d
GUACAMOLE-1435: Merge correction to FreeRDP plugin entrypoint return type. 2022-01-03 21:59:57 -08:00
Virtually Nick
703f258b06 GUACAMOLE-1435: Correctly return UINT for DVCPluginEntry 2022-01-03 20:15:11 -05:00
Virtually Nick
bce1d2a434 GUACAMOLE-1436: Add winpr file.h dependencies as required. 2021-12-27 09:42:57 -05:00
Virtually Nick
084ddaf81f Merge 1.4.0 changes back to master. 2021-12-25 10:21:40 -05:00
Virtually Nick
be9041fefd
GUACAMOLE-478: Merge add clipboard line ending normalization option for RDP. 2021-12-25 10:18:39 -05:00
Michael Jumper
09bd4af77e GUACAMOLE-478: Add optional clipboard line ending normalization for RDP. 2021-12-25 00:31:17 -08:00
Michael Jumper
7472310a03 GUACAMOLE-478: Implement encoding translation functions for normalizing newline sequences. 2021-12-25 00:07:47 -08:00
Virtually Nick
27db57f704 Merge 1.4.0 changes back to master. 2021-12-24 19:24:36 -05:00
Virtually Nick
1f6f45e6e9
GUACAMOLE-1190: Merge explicitly use "localhost" as guacd default bind host, matching default of webapp. 2021-12-24 19:23:53 -05:00
Michael Jumper
e3e75464fb GUACAMOLE-1190: Explicitly use "localhost" as guacd default bind host, matching default of webapp. 2021-12-24 15:45:28 -08:00
Virtually Nick
90322cd4d3 Merge 1.4.0 changes back to master. 2021-12-19 22:17:47 -05:00
Virtually Nick
b9cc76058b
GUACAMOLE-1047: Merge notify connecting client of invalid connection IDs. 2021-12-19 18:39:15 -05:00
Michael Jumper
daa052398e GUACAMOLE-1047: Remove unnecessary use of snprintf() in favor of guacd_log(). 2021-12-18 15:13:10 -08:00
Virtually Nick
6760974912 Merge 1.4.0 changes back to master. 2021-12-11 07:44:56 -05:00
Virtually Nick
e84317ff86
GUACAMOLE-1411: Merge bump version numbers to 1.4.0. 2021-12-11 07:44:19 -05:00
Michael Jumper
a1a758f13c GUACAMOLE-1411: Update libtool version info for libguac (interfaces added and changed). 2021-12-10 23:55:16 -08:00
Michael Jumper
8dc92e69ca GUACAMOLE-1411: Bump version numbers to 1.4.0. 2021-12-10 23:51:34 -08:00
Virtually Nick
73ac4fcdbe
GUACAMOLE-1330: Merge dynamically allocate AVPacket when possible 2021-11-08 18:47:03 -05:00
Michael Jumper
bc6b5cef25 GUACAMOLE-1330: Dynamically allocate AVPacket when supported (static allocation deprecated as of libavcodec 58.133.100). 2021-11-08 15:24:36 -08:00
Virtually Nick
491be8382a
GUACAMOLE-1416: Merge fix unreleased terminal lock in ssh_client_thread 2021-09-10 10:40:10 -04:00
ycaibb
329171a950 GUACAMOLE-1416: Fix unreleased the lock in the ssh_client_thread
GUACAMOLE-1416: Fix unreleased the lock ssh_client->term_channel_lock in the ssh_client_thread.
2021-09-10 22:26:32 +08:00
Virtually Nick
12b8eac514
GUACAMOLE-1388: Merge ensure RDP-specific resources are cleaned up after channel disconnect. 2021-07-29 19:54:35 -04:00
Michael Jumper
2524af80a9 GUACAMOLE-1388: Ensure RDP-specific resources are cleaned up after channel disconnect.
Without these changes, RDP-specific resources like the CLIPRDR and RDPEI
channels may remain from past connections if the RDP connection is
dynamically reconnected via the "Reconnect" display resize method,
resulting in assertion failures or memory errors if those stale
resources are reused after reconnect is completed.
2021-07-28 15:50:18 -07:00
Virtually Nick
91d79da49f
GUACAMOLE-1386: Merge add proper RDP mapping of "Meta" ("Windows") key. 2021-07-27 09:51:48 -04:00
Michael Jumper
eb52b4e258 GUACAMOLE-1386: Add proper RDP mapping of "Meta" ("Windows") key. 2021-07-26 19:59:12 -07:00
Mike Jumper
278745d6d8
GUACAMOLE-1064: Merge Norwegian keyboard layout for RDP. 2021-06-02 22:34:29 -07:00
Øyvind Harboe
910c87bda0 GUACAMOLE-1064: add Norwegian keyboard
Tested on top of Guacamole 1.3.0

The following works beyond a simple smoke-test:

- æøå
- \
- |
- dead acute á
- dead grave à
- dead umlaut ö
- dead cirumflex ê
- dead tilde ~
2021-06-03 07:02:05 +02:00
Mike Jumper
31b4246e18
GUACAMOLE-1350: Merge corrections for defined but unused leave_handlers. 2021-05-25 11:44:35 -07:00
Jimmy
a91c4b3869 GUACAMOLE-1350: Add code to join leave_handler when connecting in other protocols. 2021-05-25 02:03:07 +03:00
Jimmy
26d87aa5d3 GUACAMOLE-1350: Add code to join leave_handler when connecting in rdp. 2021-05-24 01:11:28 +03:00
Mike Jumper
44145f681a
GUACAMOLE-1215: Merge correction for escaping of backslashes in JSON strings. 2021-05-17 12:25:23 -07:00
Virtually Nick
0182de6d18 GUACAMOLE-1215: Add backslash to list of JSON-escaped characters. 2021-05-17 14:15:38 -04:00
Mike Jumper
df25aa9fdc
GUACAMOLE-1276: Merge correction for 32-bit truncation regression affecting RDP uploads. 2021-05-14 10:51:13 -07:00
Virtually Nick
68fc594247 GUACAMOLE-1276: Correct file upload offset type. 2021-05-14 09:19:28 -04:00
Virtually Nick
a6a7e8ac26
GUACAMOLE-1201: Merge throttle outbound audio data to avoid overflowing RDP server audio input buffer. 2021-05-01 22:10:11 -04:00
Michael Jumper
189d8fab30 GUACAMOLE-1201: Throttle outbound audio data to avoid overflowing RDP server audio input buffer.
The RDP specification for the AUDIO_INPUT channel requires that all
audio be sent in packets of a specific size. Guacamole does correctly
limit itself to sending packets of this size to the RDP server, but will
send quite a few of these packets all at once if it has received more
audio data than the RDP packet size. This is OK in principle (the
Guacamole client should be able to send audio in packets of whatever
size it chooses), but may overwhelm the software running within the RDP
server if the amount of data received exceeds the available buffer
space, resulting in dropped samples.

As there is no way to know the size of the remote audio buffer, we need
to instead ensure that audio is streamed as close to real time as
possible, with each audio packet of N bytes not being sent until roughly
the amount of time represented by those N bytes has elapsed since the
last packet. This throttling ensures that software expecting to process
audio in real time should never run out of buffer space.

That said, if we never exceed the per-packet data rate and occasionally
send a packet earlier than real time would dictate, unavoidable latency
in sending/receiving audio data would accumulate over time. For example,
if each audio packet represents 10ms of audio data, but we receive that
audio packet 10.1ms after the previous packet, we need to adjust the
timing of the next audio packet(s) to account for that additional 0.1ms.
Simply waiting 10ms after sending each packet would cause that 0.1ms to
accumulate each time it occurs, eventually resulting in noticable
latency and finally running out of buffer space.

Thus, these changes:

1. Leverage a flush thread and per-packet scheduling to ensure that each
   flushed audio packet does not exceed the equivalent real time rate.
2. Calculate the amount of additional latency from the amount of data
   received beyond the required packet size, and amortize scheduling
   corrections to account for that latency over the next several audio
   packets.

This ensures that audio is streamed exactly as it is received if the
audio matches the packet size of the RDP server, and audio that is
received in a different size or varying sizes is buffered and throttled
to keep things within the expectations of software running within the
RDP server.
2021-05-01 16:23:32 -07:00
Virtually Nick
e90e438cf6
GUACAMOLE-1283: Merge add synchronization around absolutely all outbound RDP messages. 2021-04-17 13:11:32 -04:00
Michael Jumper
bf6922b31e GUACAMOLE-1283: Remove redundant parameters from guac_rdp_audio_buffer callback.
The buffer and data parameters of the guac_rdp_audio_buffer flush
handler are redundant now that the guac_rdp_audio_buffer is being passed
to the handler. They can instead be referenced as audio_buffer->packet
and audio_buffer->data respectively.
2021-04-13 17:47:56 -07:00
Michael Jumper
27e762d06f GUACAMOLE-1283: Add synchronization around absolutely all outbound RDP messages.
The FreeRDP library is intended to be threadsafe, but is not reliably so
with respect to legacy RDP encryption and outbound messages. When
outbound messages are sent by multiple threads, the encryption key used
for legacy RDP encryption may not be updated correctly, resulting in a
fatal connection error like:

"ERRINFO_DECRYPT_FAILED (0x00001192):(a) Decryption using Standard RDP
Security mechanisms (section 5.3.6) failed. (b) Session key creation
using Standard RDP Security mechanisms (section 5.3.5) failed."
2021-04-08 15:43:15 -07:00
Virtually Nick
b2ae2fdf00
GUACAMOLE-1307: Merge use VerifyCertificateEx callback if supported. 2021-03-10 14:38:43 -05:00
Michael Jumper
1b78f611d3 GUACAMOLE-1307: Use VerifyCertificateEx callback if supported. 2021-03-09 22:53:11 -08:00
James Muehlner
e2a136f41e
GUACAMOLE-1302: Merge support for forcing lossless compression in VNC and RDP connections. 2021-03-03 19:31:25 -08:00
Michael Jumper
18a0362dab GUACAMOLE-1302: Add RDP support for forcing lossless compression. 2021-03-03 19:29:14 -08:00
Michael Jumper
c2b7e2d039 GUACAMOLE-1302: Add VNC support for forcing lossless compression. 2021-03-03 19:29:14 -08:00
Michael Jumper
27f8403178 GUACAMOLE-1302: Always use lossless compression for text-based protocols leveraging the terminal. 2021-03-03 19:29:14 -08:00
Michael Jumper
0729a6cc73 GUACAMOLE-1302: Add surface/display level support for forcing lossless compression. 2021-03-03 19:29:14 -08:00
Virtually Nick
d9d5c79644
GUACAMOLE-1305: Merge fix pt-br keyboard layout 2021-03-03 16:19:20 -05:00
Higor Cavalcanti
650e7ad90f GUACAMOLE-1305: Fix pt-br keyboard layout. Key being recognized as right shift. 2021-03-03 16:49:59 -03:00
Virtually Nick
7bbab0efdd
GUACAMOLE-1174: Merge correct handling of truncated parameters when appending to URLs. 2021-02-21 20:32:47 -05:00
Michael Jumper
a920932703 GUACAMOLE-1174: Correct logic detecting truncation of appended parameter.
The previous implementation passed `length - str_len` to `snprintf()`,
yet compared the return value to `length`. This is incorrect, as
`length` is not the buffer size provided to `snprintf()`.
2021-02-21 15:05:53 -08:00
Michael Jumper
c7935736da GUACAMOLE-1174: Add unit tests for URL utility functions. 2021-02-21 15:05:53 -08:00
Michael Jumper
7f55399304 GUACAMOLE-1174: Clarify behavior of URL parameter appending function. 2021-02-21 14:15:17 -08:00
Mike Jumper
5428ac5057
GUACAMOLE-1174: Merge support for Kubernetes "exec" API call. 2021-02-21 11:09:53 -08:00
Tomer Gabel
a8cf250c98 GUACAMOLE-1047: Changed returned status code per review 2021-02-14 16:21:54 +02:00
James Muehlner
ca1fbd5e98
GUACAMOLE-1204: Merge addition of server-side support for multi-touch events. 2021-02-11 20:53:22 -08:00
Michael Jumper
d16ba33dee GUACAMOLE-1204: Add support for including touch events within session recordings. 2021-02-11 20:12:21 -08:00
Michael Jumper
5eb2867733 GUACAMOLE-1204: Add RDP support for multi-touch events via RDPEI channel. 2021-02-11 20:12:21 -08:00
Michael Jumper
048a59310b GUACAMOLE-1204: Add support for declaring layer multi-touch capabilities. 2021-02-11 20:12:21 -08:00
Michael Jumper
c88c0d1c89 GUACAMOLE-1204: Add libguac support for processing the "touch" instruction. 2021-02-11 20:12:21 -08:00
Virtually Nick
4a38d39694
GUACAMOLE-1277: Merge unswap - and _ on fr-be-azerty keymap 2021-02-02 15:54:12 -05:00
Sander Vanheule
841dc28e9b GUACAMOLE-1277: Unswap -/_ on fr-be-azerty keymap
When using the fr-be-azerty remote keyboard layout on an RDP connection,
the dash ('-') and underscore ('_') are swapped.

Underscore and dash are located on the same key on a Belgian azerty
layout. Dash should be the normal/unshifted character, and underscore
should be the shifted character. The current mapping has this the other
way around, so let's fix this.

Signed-off-by: Sander Vanheule <Sander.Vanheule@UGent.be>
2021-02-02 19:53:51 +01:00
Mike Jumper
c122a5f14a
GUACAMOLE-1133: Merge build-time sanity check for libvncserver usage of gcrypt. 2021-01-24 14:15:35 -08:00
Nick Couchman
5cee64514f GUACAMOLE-1133: Add gcrypt build dependency for Docker image. 2021-01-23 22:16:58 -05:00
Nick Couchman
b40b7e7bf6 GUACAMOLE-1133: Add build check for headers when libvncserver includes gcrypt support. 2021-01-23 22:04:38 -05:00
Mike Jumper
6d526cb60f
GUACAMOLE-1133: Merge addition of GCrypt initialization to VNC startup process. 2021-01-22 21:33:10 -08:00
Nick Couchman
46bed49a43 GUACAMOLE-1133: initialize GCrypt in VNC protocol prior to client start-up. 2021-01-21 21:14:18 -05:00
Virtually Nick
c769d18cf6
GUACAMOLE-1263: Merge mark freed memory as freed prior to calling rfbClientCleanup(). 2021-01-15 14:51:36 -05:00
Michael Jumper
612c5eba20 GUACAMOLE-1263: Mark freed memory as freed prior to calling rfbClientCleanup().
Older versions of libvncclient did not free all memory within
rfbClientCleanup(), but this has been corrected as of their 0.9.12
release. As guacamole-server may well be built against older versions of
libvncclient, we can't simply remove the manual free() calls, but we
should be sure to set any memory that we free ourselves to NULL so that
rfbClientCleanup() does not attempt to free it again.
2021-01-15 11:46:16 -08:00
Virtually Nick
fe62300223
GUACAMOLE-1259: Merge include missing config.h header for sake of conditional Stream_Free(). 2021-01-10 13:09:39 -05:00
Michael Jumper
0b6b14b71e GUACAMOLE-1259: Include missing config.h header for sake of conditional Stream_Free().
The changes introduced by GUACAMOLE-1181 (commit 2c86e20) were made
conditional as older versions of FreeRDP will automatically free the
wStream, resulting in a double-free if we attempt to do so ourselves.

The macro controlling that conditional code is defined within config.h,
which is missing here. Without that macro, the call to Stream_Free()
always occurs, and we get a double-free with older FreeRDP.
2021-01-09 21:15:50 -08:00
Virtually Nick
1c3e86dc2c
GUACAMOLE-1191: Merge always disable the glyph cache due to stability issues. 2021-01-09 22:46:12 -05:00
Michael Jumper
9dc793b0e5 GUACAMOLE-1191: Always disable the glyph cache, as FreeRDP no longer considers the feature to be stable. 2021-01-09 17:49:32 -08:00
Virtually Nick
1e6c42594f
GUACAMOLE-1254: Merge add support for libuuid as alternative to OSSP UUID. 2021-01-07 12:21:11 -05:00
Michael Jumper
430182dce2 GUACAMOLE-1254: Add unit tests for unique ID generation. 2021-01-06 22:51:07 -08:00
Michael Jumper
f710e00d26 GUACAMOLE-1254: Use libuuid rather than OSSP UUID if available.
The libuuid library is widely available (part of util-linux) and much
more frequently updated. The OSSP UUID library works great, but was last
updated in 2008 and causes some confusion for users that have libuuid.
2021-01-06 20:58:22 -08:00
Mike Jumper
53f981f864
GUACAMOLE-1245: Merge support for specifying Wake-on-LAN port. 2020-12-30 21:06:13 -08:00
Nick Couchman
a37668e9f5 GUACAMOLE-1245: Add support for specifying Wake-on-LAN port. 2020-12-30 16:50:38 -05:00
Yaroslav Nikonorov
004f57e19a GUACAMOLE-1174: Add prototype and docstrings for guac_kubernetes_append_endpoint_param function. 2020-11-18 15:30:34 +02:00
Yaroslav Nikonorov
7809447c3f GUACAMOLE-1174: Add parameters to the endpoint path using function guac_kubernetes_append_endpoint_param. 2020-11-18 14:57:59 +02:00
Yaroslav Nikonorov
7a1ba51bae GUACAMOLE-1174: Determine parameter delimiter, compute the buffer string length, fix the buffer string length usage, verify buffer null terminated. 2020-11-18 14:52:24 +02:00
Yaroslav Nikonorov
79239e3be0 GUACAMOLE-1174: Create function for appending endpoint params, fix endpoint params overwriting. 2020-10-20 12:44:38 +03:00
Yaroslav Nikonorov
6b58e2e5a9 GUACAMOLE-1174: Free exec_command setting. 2020-10-20 12:44:38 +03:00
Yaroslav Nikonorov
164f792b86 GUACAMOLE-1174: Remove option use-exec, add snprintf result validation, fix code formatting. 2020-10-08 13:18:58 +03:00
Yaroslav Nikonorov
7683a17d69 GUACAMOLE-1174: Added exec call implementation for kubernetes protocol 2020-09-10 19:59:03 +03:00
Tomer Gabel
8838199f5c GUACAMOLE-1047: Notify connecting client on unrecognized connection ID 2020-04-23 16:53:17 +03:00
187 changed files with 6677 additions and 2615 deletions

View File

@ -56,5 +56,5 @@ tests/test_*
!tests/test_*.[ch] !tests/test_*.[ch]
# Generated docs # Generated docs
doc/doxygen-output doc/*/doxygen-output

2
.gitignore vendored
View File

@ -44,7 +44,7 @@ configure
stamp-h1 stamp-h1
# Generated docs # Generated docs
doc/doxygen-output doc/*/doxygen-output
# IDE metadata # IDE metadata
nbproject/ nbproject/

View File

@ -21,25 +21,32 @@
# Dockerfile for guacamole-server # Dockerfile for guacamole-server
# #
# The Debian image that should be used as the basis for the guacd image # The Alpine Linux image that should be used as the basis for the guacd image
ARG DEBIAN_BASE_IMAGE=buster-slim ARG ALPINE_BASE_IMAGE=latest
FROM alpine:${ALPINE_BASE_IMAGE} AS builder
# Use Debian as base for the build # Install build dependencies
FROM debian:${DEBIAN_BASE_IMAGE} AS builder RUN apk add --no-cache \
autoconf \
automake \
build-base \
cairo-dev \
cmake \
git \
grep \
libjpeg-turbo-dev \
libpng-dev \
libtool \
libwebp-dev \
make \
openssl-dev \
pango-dev \
pulseaudio-dev \
util-linux-dev
# # Copy source to container for sake of build
# The Debian repository that should be preferred for dependencies (this will be ARG BUILD_DIR=/tmp/guacamole-server
# added to /etc/apt/sources.list if not already present) COPY . ${BUILD_DIR}
#
# NOTE: Due to limitations of the Docker image build process, this value is
# duplicated in an ARG in the second stage of the build.
#
ARG DEBIAN_RELEASE=buster-backports
# Add repository for specified Debian release if not already present in
# sources.list
RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources.list \
"deb http://deb.debian.org/debian ${DEBIAN_RELEASE} main contrib non-free"
# #
# Base directory for installed build artifacts. # Base directory for installed build artifacts.
@ -47,69 +54,99 @@ RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources
# NOTE: Due to limitations of the Docker image build process, this value is # NOTE: Due to limitations of the Docker image build process, this value is
# duplicated in an ARG in the second stage of the build. # duplicated in an ARG in the second stage of the build.
# #
ARG PREFIX_DIR=/usr/local/guacamole ARG PREFIX_DIR=/opt/guacamole
# Build arguments #
ARG BUILD_DIR=/tmp/guacd-docker-BUILD # Automatically select the latest versions of each core protocol support
ARG BUILD_DEPENDENCIES=" \ # library (these can be overridden at build time if a specific version is
autoconf \ # needed)
automake \ #
freerdp2-dev \ ARG WITH_FREERDP='2(\.\d+)+'
gcc \ ARG WITH_LIBSSH2='libssh2-\d+(\.\d+)+'
libcairo2-dev \ ARG WITH_LIBTELNET='\d+(\.\d+)+'
libjpeg62-turbo-dev \ ARG WITH_LIBVNCCLIENT='LibVNCServer-\d+(\.\d+)+'
libossp-uuid-dev \ ARG WITH_LIBWEBSOCKETS='v\d+(\.\d+)+'
libpango1.0-dev \
libpulse-dev \
libssh2-1-dev \
libssl-dev \
libtelnet-dev \
libtool \
libvncserver-dev \
libwebsockets-dev \
libwebp-dev \
make"
# Do not require interaction during build #
ARG DEBIAN_FRONTEND=noninteractive # 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)
#
# Bring build environment up to date and install build dependencies ARG FREERDP_OPTS="\
RUN apt-get update && \ -DBUILTIN_CHANNELS=OFF \
apt-get install -t ${DEBIAN_RELEASE} -y $BUILD_DEPENDENCIES && \ -DCHANNEL_URBDRC=OFF \
rm -rf /var/lib/apt/lists/* -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"
# Add configuration scripts ARG GUACAMOLE_SERVER_OPTS="\
COPY src/guacd-docker/bin "${PREFIX_DIR}/bin/" --disable-guaclog"
# Copy source to container for sake of build ARG LIBSSH2_OPTS="\
COPY . "$BUILD_DIR" -DBUILD_EXAMPLES=OFF \
-DBUILD_SHARED_LIBS=ON"
# Build guacamole-server from local source ARG LIBTELNET_OPTS="\
RUN ${PREFIX_DIR}/bin/build-guacd.sh "$BUILD_DIR" "$PREFIX_DIR" --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 # Record the packages of all runtime library dependencies
RUN ${PREFIX_DIR}/bin/list-dependencies.sh \ RUN ${BUILD_DIR}/src/guacd-docker/bin/list-dependencies.sh \
${PREFIX_DIR}/sbin/guacd \ ${PREFIX_DIR}/sbin/guacd \
${PREFIX_DIR}/lib/libguac-client-*.so \ ${PREFIX_DIR}/lib/libguac-client-*.so \
${PREFIX_DIR}/lib/freerdp2/*guac*.so \ ${PREFIX_DIR}/lib/freerdp2/*guac*.so \
> ${PREFIX_DIR}/DEPENDENCIES > ${PREFIX_DIR}/DEPENDENCIES
# Use same Debian as the base for the runtime image # Use same Alpine version as the base for the runtime image
FROM debian:${DEBIAN_BASE_IMAGE} FROM alpine:${ALPINE_BASE_IMAGE}
#
# The Debian repository that should be preferred for dependencies (this will be
# added to /etc/apt/sources.list if not already present)
#
# 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 DEBIAN_RELEASE=buster-backports
# Add repository for specified Debian release if not already present in
# sources.list
RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources.list \
"deb http://deb.debian.org/debian ${DEBIAN_RELEASE} main contrib non-free"
# #
# Base directory for installed build artifacts. See also the # Base directory for installed build artifacts. See also the
@ -118,36 +155,27 @@ RUN grep " ${DEBIAN_RELEASE} " /etc/apt/sources.list || echo >> /etc/apt/sources
# NOTE: Due to limitations of the Docker image build process, this value is # NOTE: Due to limitations of the Docker image build process, this value is
# duplicated in an ARG in the first stage of the build. # duplicated in an ARG in the first stage of the build.
# #
ARG PREFIX_DIR=/usr/local/guacamole ARG PREFIX_DIR=/opt/guacamole
# Runtime environment # Runtime environment
ENV LC_ALL=C.UTF-8 ENV LC_ALL=C.UTF-8
ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib
ENV GUACD_LOG_LEVEL=info ENV GUACD_LOG_LEVEL=info
ARG RUNTIME_DEPENDENCIES=" \
netcat-openbsd \
ca-certificates \
ghostscript \
fonts-liberation \
fonts-dejavu \
xfonts-terminus"
# Do not require interaction during build
ARG DEBIAN_FRONTEND=noninteractive
# Copy build artifacts into this stage # Copy build artifacts into this stage
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR} COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
# Bring runtime environment up to date and install runtime dependencies # Bring runtime environment up to date and install runtime dependencies
RUN apt-get update && \ RUN apk add --no-cache \
apt-get install -t ${DEBIAN_RELEASE} -y --no-install-recommends $RUNTIME_DEPENDENCIES && \ ca-certificates \
apt-get install -t ${DEBIAN_RELEASE} -y --no-install-recommends $(cat "${PREFIX_DIR}"/DEPENDENCIES) && \ ghostscript \
rm -rf /var/lib/apt/lists/* netcat-openbsd \
shadow \
# Link FreeRDP plugins into proper path terminus-font \
RUN ${PREFIX_DIR}/bin/link-freerdp-plugins.sh \ ttf-dejavu \
${PREFIX_DIR}/lib/freerdp2/libguac*.so 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 # 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 HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
@ -156,7 +184,7 @@ HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
ARG UID=1000 ARG UID=1000
ARG GID=1000 ARG GID=1000
RUN groupadd --gid $GID guacd RUN groupadd --gid $GID guacd
RUN useradd --system --create-home --shell /usr/sbin/nologin --uid $UID --gid $GID guacd RUN useradd --system --create-home --shell /sbin/nologin --uid $UID --gid $GID guacd
# Run with user guacd # Run with user guacd
USER guacd USER guacd
@ -169,5 +197,5 @@ EXPOSE 4822
# Note the path here MUST correspond to the value specified in the # Note the path here MUST correspond to the value specified in the
# PREFIX_DIR build argument. # PREFIX_DIR build argument.
# #
CMD /usr/local/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f CMD /opt/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f

View File

@ -35,7 +35,6 @@ DIST_SUBDIRS = \
src/guacenc \ src/guacenc \
src/guaclog \ src/guaclog \
src/pulse \ src/pulse \
src/protocols/ball \
src/protocols/kubernetes \ src/protocols/kubernetes \
src/protocols/rdp \ src/protocols/rdp \
src/protocols/ssh \ src/protocols/ssh \
@ -44,8 +43,7 @@ DIST_SUBDIRS = \
SUBDIRS = \ SUBDIRS = \
src/libguac \ src/libguac \
src/common \ src/common
src/protocols/ball
if ENABLE_COMMON_SSH if ENABLE_COMMON_SSH
SUBDIRS += src/common-ssh SUBDIRS += src/common-ssh
@ -98,7 +96,8 @@ EXTRA_DIST = \
LICENSE \ LICENSE \
NOTICE \ NOTICE \
bin/guacctl \ bin/guacctl \
doc/Doxyfile.in \ doc/libguac/Doxyfile.in \
doc/libguac-terminal/Doxyfile.in \
src/guacd-docker \ src/guacd-docker \
util/generate-test-runner.pl util/generate-test-runner.pl

View File

@ -117,7 +117,7 @@ error() {
## ##
usage() { usage() {
cat >&2 <<END cat >&2 <<END
guacctl 1.3.0, Apache 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.

View File

@ -18,7 +18,7 @@
# #
AC_PREREQ([2.61]) AC_PREREQ([2.61])
AC_INIT([guacamole-server], [1.3.0]) AC_INIT([guacamole-server], [1.5.0])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
AM_SILENT_RULES([yes]) AM_SILENT_RULES([yes])
@ -75,14 +75,42 @@ AC_CHECK_LIB([dl], [dlopen],
AC_MSG_ERROR("libdl is required on systems which do not otherwise provide dlopen()"), AC_MSG_ERROR("libdl is required on systems which do not otherwise provide dlopen()"),
[#include <dlfcn.h>])]) [#include <dlfcn.h>])])
# OSSP UUID #
AC_CHECK_LIB([ossp-uuid], [uuid_make], [UUID_LIBS=-lossp-uuid], # libuuid
AC_CHECK_LIB([uuid], [uuid_make], [UUID_LIBS=-luuid], #
AC_MSG_ERROR("The OSSP UUID library is required")))
# Check for and validate OSSP uuid.h header have_libuuid=disabled
AC_CHECK_HEADERS([ossp/uuid.h]) AC_ARG_WITH([libuuid],
AC_CHECK_DECL([uuid_make],, [AS_HELP_STRING([--with-libuuid],
[use libuuid to generate unique identifiers @<:@default=check@:>@])],
[],
[with_libuuid=check])
if test "x$with_libuuid" != "xno"
then
have_libuuid=yes
AC_CHECK_LIB([uuid], [uuid_generate],
[UUID_LIBS=-luuid]
[AC_DEFINE([HAVE_LIBUUID],, [Whether libuuid is available])],
[have_libuuid=no])
fi
# OSSP UUID (if libuuid is unavilable)
if test "x${have_libuuid}" != "xyes"
then
AC_CHECK_LIB([ossp-uuid], [uuid_make], [UUID_LIBS=-lossp-uuid],
AC_CHECK_LIB([uuid], [uuid_make], [UUID_LIBS=-luuid],
AC_MSG_ERROR([
--------------------------------------------
Unable to find libuuid or the OSSP UUID library.
Either libuuid (from util-linux) or the OSSP UUID library is required for
guacamole-server to be built.
--------------------------------------------])))
# Check for and validate OSSP uuid.h header
AC_CHECK_HEADERS([ossp/uuid.h])
AC_CHECK_DECL([uuid_make],,
AC_MSG_ERROR("No OSSP uuid.h found in include path"), AC_MSG_ERROR("No OSSP uuid.h found in include path"),
[#ifdef HAVE_OSSP_UUID_H [#ifdef HAVE_OSSP_UUID_H
#include <ossp/uuid.h> #include <ossp/uuid.h>
@ -90,6 +118,7 @@ AC_CHECK_DECL([uuid_make],,
#include <uuid.h> #include <uuid.h>
#endif #endif
]) ])
fi
# cunit # cunit
AC_CHECK_LIB([cunit], [CU_run_test], [CUNIT_LIBS=-lcunit]) AC_CHECK_LIB([cunit], [CU_run_test], [CUNIT_LIBS=-lcunit])
@ -131,6 +160,11 @@ AC_CHECK_DECL([strlcat],
[Whether strlcat() is defined])],, [Whether strlcat() is defined])],,
[#include <string.h>]) [#include <string.h>])
AC_CHECK_DECL([strnstr],
[AC_DEFINE([HAVE_STRNSTR],,
[Whether strnstr() is defined])],,
[#include <string.h>])
# Typedefs # Typedefs
AC_TYPE_SIZE_T AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T AC_TYPE_SSIZE_T
@ -151,12 +185,16 @@ AC_SUBST([PULSE_INCLUDE], '-I$(top_srcdir)/src/pulse')
AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la') AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la')
AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh') AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh')
# Kubernetes support
AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_LTLIB], '$(top_builddir)/src/protocols/kubernetes/libguac-client-kubernetes.la')
AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_INCLUDE], '-I$(top_srcdir)/src/protocols/kubernetes')
# RDP support # RDP support
AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la') AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la')
AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp') AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp')
# Terminal emulator # Terminal emulator
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la') AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac-terminal.la')
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)') AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
# Init directory # Init directory
@ -287,30 +325,6 @@ then
else else
AC_DEFINE([ENABLE_SSL],, [Whether SSL-related support is enabled]) AC_DEFINE([ENABLE_SSL],, [Whether SSL-related support is enabled])
# OpenSSL 1.1 accessor function for DSA signature values
AC_CHECK_DECL([DSA_SIG_get0],
[AC_DEFINE([HAVE_DSA_SIG_GET0],,
[Whether libssl provides DSA_SIG_get0()])],,
[#include <openssl/dsa.h>])
# OpenSSL 1.1 accessor function for DSA public key parameters
AC_CHECK_DECL([DSA_get0_pqg],
[AC_DEFINE([HAVE_DSA_GET0_PQG],,
[Whether libssl provides DSA_get0_pqg()])],,
[#include <openssl/dsa.h>])
# OpenSSL 1.1 accessor function for DSA public/private key values
AC_CHECK_DECL([DSA_get0_key],
[AC_DEFINE([HAVE_DSA_GET0_KEY],,
[Whether libssl provides DSA_get0_key()])],,
[#include <openssl/dsa.h>])
# OpenSSL 1.1 accessor function for RSA public/private key values
AC_CHECK_DECL([RSA_get0_key],
[AC_DEFINE([HAVE_RSA_GET0_KEY],,
[Whether libssl provides RSA_get0_key()])],,
[#include <openssl/rsa.h>])
# OpenSSL 1.1 does away with explicit threading callbacks # OpenSSL 1.1 does away with explicit threading callbacks
AC_MSG_CHECKING([whether libssl requires threading callbacks]) AC_MSG_CHECKING([whether libssl requires threading callbacks])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
@ -490,6 +504,27 @@ then
AC_CHECK_LIB([vncclient], [rfbInitClient], [VNC_LIBS="$VNC_LIBS -lvncclient"], [have_libvncserver=no]) AC_CHECK_LIB([vncclient], [rfbInitClient], [VNC_LIBS="$VNC_LIBS -lvncclient"], [have_libvncserver=no])
fi fi
#
# Underlying libvncserver usage of gcrypt
#
if test "x${have_libvncserver}" = "xyes"
then
# Whether libvncserver was built against libgcrypt
AC_CHECK_DECL([LIBVNCSERVER_WITH_CLIENT_GCRYPT],
[AC_CHECK_HEADER(gcrypt.h,,
[AC_MSG_WARN([
--------------------------------------------
libvncserver appears to be built against
libgcrypt, but the libgcrypt headers
could not be found. VNC will be disabled.
--------------------------------------------])
have_libvncserver=no])],,
[[#include <rfb/rfbconfig.h>]])
fi
AM_CONDITIONAL([ENABLE_VNC], [test "x${have_libvncserver}" = "xyes"]) AM_CONDITIONAL([ENABLE_VNC], [test "x${have_libvncserver}" = "xyes"])
AC_SUBST(VNC_LIBS) AC_SUBST(VNC_LIBS)
@ -843,6 +878,14 @@ then
[[#include <freerdp/freerdp.h>]]) [[#include <freerdp/freerdp.h>]])
fi fi
# Updated certificate verification callback (introduced with 2.0.0, not present
# in 2.0.0-rc4 or earlier)
if test "x${have_freerdp2}" = "xyes"
then
AC_CHECK_MEMBERS([freerdp.VerifyCertificateEx],,,
[[#include <freerdp/freerdp.h>]])
fi
# Restore CPPFLAGS, removing FreeRDP-specific options needed for testing # Restore CPPFLAGS, removing FreeRDP-specific options needed for testing
CPPFLAGS="$OLDCPPFLAGS" CPPFLAGS="$OLDCPPFLAGS"
@ -870,7 +913,7 @@ if test "x$with_ssh" != "xno"
then then
have_libssh2=yes have_libssh2=yes
AC_CHECK_LIB([ssh2], [libssh2_session_init_ex], AC_CHECK_LIB([ssh2], [libssh2_userauth_publickey_frommemory],
[SSH_LIBS="$SSH_LIBS -lssh2"], [SSH_LIBS="$SSH_LIBS -lssh2"],
[have_libssh2=no]) [have_libssh2=no])
fi fi
@ -1124,7 +1167,8 @@ AM_CONDITIONAL([ENABLE_GUACLOG], [test "x${enable_guaclog}" = "xyes"])
# #
AC_CONFIG_FILES([Makefile AC_CONFIG_FILES([Makefile
doc/Doxyfile doc/libguac/Doxyfile
doc/libguac-terminal/Doxyfile
src/common/Makefile src/common/Makefile
src/common/tests/Makefile src/common/tests/Makefile
src/common-ssh/Makefile src/common-ssh/Makefile
@ -1140,8 +1184,8 @@ AC_CONFIG_FILES([Makefile
src/guaclog/Makefile src/guaclog/Makefile
src/guaclog/man/guaclog.1 src/guaclog/man/guaclog.1
src/pulse/Makefile src/pulse/Makefile
src/protocols/ball/Makefile
src/protocols/kubernetes/Makefile src/protocols/kubernetes/Makefile
src/protocols/kubernetes/tests/Makefile
src/protocols/rdp/Makefile src/protocols/rdp/Makefile
src/protocols/rdp/tests/Makefile src/protocols/rdp/tests/Makefile
src/protocols/ssh/Makefile src/protocols/ssh/Makefile

View File

@ -1,4 +1,3 @@
#!/bin/sh -e
# #
# 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
@ -18,32 +17,42 @@
# under the License. # 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.
##
## @param PREFIX_DIR
## The directory prefix into which the build artifacts should be installed
## in which the build should be performed. This is passed to the --prefix
## option of `configure`.
##
BUILD_DIR="$1"
PREFIX_DIR="$2"
# #
# Build guacamole-server # Project name / version
# #
cd "$BUILD_DIR" PROJECT_NAME = libguac-terminal
autoreconf -fi PROJECT_NUMBER = @PACKAGE_VERSION@
./configure --prefix="$PREFIX_DIR" --disable-guaclog --with-freerdp-plugin-dir="$PREFIX_DIR/lib/freerdp2"
make #
make install # Warn about undocumented parameters and return values, but do not fill output
ldconfig # 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

View File

@ -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

View File

@ -31,8 +31,6 @@ SUBDIRS = . tests
libguac_common_ssh_la_SOURCES = \ libguac_common_ssh_la_SOURCES = \
buffer.c \ buffer.c \
dsa-compat.c \
rsa-compat.c \
sftp.c \ sftp.c \
ssh.c \ ssh.c \
key.c \ key.c \
@ -40,8 +38,6 @@ libguac_common_ssh_la_SOURCES = \
noinst_HEADERS = \ noinst_HEADERS = \
common-ssh/buffer.h \ common-ssh/buffer.h \
common-ssh/dsa-compat.h \
common-ssh/rsa-compat.h \
common-ssh/key.h \ common-ssh/key.h \
common-ssh/sftp.h \ common-ssh/sftp.h \
common-ssh/ssh.h \ common-ssh/ssh.h \

View File

@ -1,61 +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_DSA_COMPAT_H
#define GUAC_COMMON_SSH_DSA_COMPAT_H
#include "config.h"
#include <openssl/bn.h>
#include <openssl/dsa.h>
#ifndef HAVE_DSA_GET0_PQG
/**
* DSA_get0_pqg() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_pqg.html
*/
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
const BIGNUM** q, const BIGNUM** g);
#endif
#ifndef HAVE_DSA_GET0_KEY
/**
* DSA_get0_key() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_key.html
*/
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
const BIGNUM** priv_key);
#endif
#ifndef HAVE_DSA_SIG_GET0
/**
* DSA_SIG_get0() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_SIG_get0.html
*/
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s);
#endif
#endif

View File

@ -25,75 +25,27 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <libssh2.h> #include <libssh2.h>
#include <openssl/ossl_typ.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 expected header of RSA private keys. * 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 SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" #define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB"
/**
* 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. * Abstraction of a key used for SSH authentication.
*/ */
typedef struct guac_common_ssh_key { 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. * The private key, encoded as necessary for SSH.
*/ */
@ -104,6 +56,11 @@ typedef struct guac_common_ssh_key {
*/ */
int private_key_length; int private_key_length;
/**
* The private key's passphrase, if any.
*/
char *passphrase;
} guac_common_ssh_key; } guac_common_ssh_key;
/** /**
@ -144,31 +101,6 @@ const char* guac_common_ssh_key_error();
*/ */
void guac_common_ssh_key_free(guac_common_ssh_key* key); 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);
/** /**
* Verifies the host key for the given hostname/port combination against * 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 * one or more known_hosts entries. The known_host entries can either be a

View File

@ -1,40 +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_RSA_COMPAT_H
#define GUAC_COMMON_SSH_RSA_COMPAT_H
#include "config.h"
#include <openssl/bn.h>
#include <openssl/rsa.h>
#ifndef HAVE_RSA_GET0_KEY
/**
* RSA_get0_key() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
*/
void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
const BIGNUM** e, const BIGNUM**d);
#endif
#endif

View File

@ -1,59 +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 <openssl/bn.h>
#include <openssl/dsa.h>
#include <stdlib.h>
#ifndef HAVE_DSA_GET0_PQG
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
const BIGNUM** q, const BIGNUM** g) {
/* Retrieve all requested internal values */
if (p != NULL) *p = dsa_key->p;
if (q != NULL) *q = dsa_key->q;
if (g != NULL) *g = dsa_key->g;
}
#endif
#ifndef HAVE_DSA_GET0_KEY
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
const BIGNUM** priv_key) {
/* Retrieve all requested internal values */
if (pub_key != NULL) *pub_key = dsa_key->pub_key;
if (priv_key != NULL) *priv_key = dsa_key->priv_key;
}
#endif
#ifndef HAVE_DSA_SIG_GET0
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s) {
/* Retrieve all requested internal values */
if (r != NULL) *r = dsa_sig->r;
if (s != NULL) *s = dsa_sig->s;
}
#endif

View File

@ -20,9 +20,9 @@
#include "config.h" #include "config.h"
#include "common-ssh/buffer.h" #include "common-ssh/buffer.h"
#include "common-ssh/dsa-compat.h"
#include "common-ssh/key.h" #include "common-ssh/key.h"
#include "common-ssh/rsa-compat.h"
#include <guacamole/string.h>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/bn.h> #include <openssl/bn.h>
@ -33,119 +33,118 @@
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.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, guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
char* passphrase) { char* passphrase) {
guac_common_ssh_key* key; /* Because libssh2 will do the actual key parsing (to let it deal with
BIO* key_bio; * 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.
*/
char* public_key; if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
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;
const BIGNUM* key_e;
const BIGNUM* key_n;
/* Read key */
rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase);
if (rsa_key == NULL)
return NULL; return NULL;
/* Allocate key */ guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_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;
/* Retrieve public key */
RSA_get0_key(rsa_key, &key_n, &key_e, NULL);
/* Send public key formatted for SSH */
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
guac_common_ssh_buffer_write_bignum(&pos, key_e);
guac_common_ssh_buffer_write_bignum(&pos, 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;
const BIGNUM* key_p;
const BIGNUM* key_q;
const BIGNUM* key_g;
const BIGNUM* pub_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;
/* Retrieve public key */
DSA_get0_pqg(dsa_key, &key_p, &key_q, &key_g);
DSA_get0_key(dsa_key, &pub_key, NULL);
/* Send public key formatted for SSH */
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
guac_common_ssh_buffer_write_bignum(&pos, key_p);
guac_common_ssh_buffer_write_bignum(&pos, key_q);
guac_common_ssh_buffer_write_bignum(&pos, key_g);
guac_common_ssh_buffer_write_bignum(&pos, 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 */ /* Copy private key to structure */
key->private_key_length = length; key->private_key_length = length;
key->private_key = malloc(length); key->private_key = malloc(length);
memcpy(key->private_key, data, length); memcpy(key->private_key, data, length);
key->passphrase = strdup(passphrase);
BIO_free(key_bio);
return key; return key;
} }
@ -159,93 +158,11 @@ const char* guac_common_ssh_key_error() {
void guac_common_ssh_key_free(guac_common_ssh_key* key) { 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->private_key);
free(key->public_key); free(key->passphrase);
free(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;
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int dlen, len;
/* Get SHA1 digest */
if ((md = EVP_get_digestbynid(NID_sha1)) == NULL)
return -1;
/* Allocate digest context */
EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
if (md_ctx == NULL)
return -1;
/* Digest data */
EVP_DigestInit(md_ctx, md);
EVP_DigestUpdate(md_ctx, data, length);
EVP_DigestFinal(md_ctx, digest, &dlen);
/* Digest context no longer needed */
EVP_MD_CTX_destroy(md_ctx);
/* 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) {
const BIGNUM* sig_r;
const BIGNUM* sig_s;
/* Retrieve DSA signature values */
DSA_SIG_get0(dsa_sig, &sig_r, &sig_s);
/* Compute size of each half of signature */
int rlen = BN_num_bytes(sig_r);
int slen = BN_num_bytes(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(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(sig_s, sig + DSA_SIG_SIZE - slen);
/* Done */
DSA_SIG_free(dsa_sig);
return DSA_SIG_SIZE;
}
}
}
return -1;
}
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client, 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 char* host_key, const char* hostname, int port, const char* remote_hostkey,
const size_t remote_hostkey_len) { const size_t remote_hostkey_len) {

View File

@ -1,38 +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 <openssl/bn.h>
#include <openssl/rsa.h>
#include <stdlib.h>
#ifndef HAVE_RSA_GET0_KEY
void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
const BIGNUM** e, const BIGNUM**d) {
/* Retrieve all requested internal values */
if (n != NULL) *n = rsa_key->n;
if (e != NULL) *e = rsa_key->e;
if (d != NULL) *d = rsa_key->d;
}
#endif

View File

@ -22,6 +22,7 @@
#include "common-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
@ -46,6 +47,20 @@
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 #ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
/** /**
* Array of mutexes, used by OpenSSL. * Array of mutexes, used by OpenSSL.
@ -140,12 +155,22 @@ static void guac_common_ssh_openssl_free_locks(int count) {
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 #ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
@ -155,9 +180,11 @@ int guac_common_ssh_init(guac_client* client) {
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback); CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
#endif #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);
@ -173,55 +200,6 @@ void guac_common_ssh_uninit() {
#endif #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;
}
/** /**
* Callback for the keyboard-interactive authentication method. Currently * Callback for the keyboard-interactive authentication method. Currently
* supports just one prompt for the password. This callback is invoked as * supports just one prompt for the password. This callback is invoked as
@ -314,8 +292,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
} }
/* Get list of supported authentication methods */ /* Get list of supported authentication methods */
size_t username_len = strlen(user->username);
char* user_authlist = libssh2_userauth_list(session, user->username, char* user_authlist = libssh2_userauth_list(session, user->username,
strlen(user->username)); username_len);
/* If auth list is NULL, then authentication has succeeded with NONE */ /* If auth list is NULL, then authentication has succeeded with NONE */
if (user_authlist == NULL) { if (user_authlist == NULL) {
@ -339,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, user->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;
@ -522,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,

View File

@ -42,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
@ -59,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

View File

@ -29,15 +29,15 @@
#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); pthread_mutex_init(&(clipboard->lock), NULL);

View File

@ -31,6 +31,11 @@
*/ */
#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.
*/ */
@ -66,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.

View File

@ -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

View File

@ -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

View File

@ -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
@ -486,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

View File

@ -166,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 */
@ -178,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);
} }
@ -287,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);
@ -308,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);
@ -354,4 +389,3 @@ void guac_common_display_free_buffer(guac_common_display* display,
pthread_mutex_unlock(&display->_lock); pthread_mutex_unlock(&display->_lock);
} }

View File

@ -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);
}

View File

@ -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);

View File

@ -103,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);
@ -521,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);
@ -1793,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_common_surface_suggest_quality(surface->client), 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;
@ -1981,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 */

View File

@ -33,8 +33,12 @@ ACLOCAL_AMFLAGS = -I m4
check_PROGRAMS = test_common check_PROGRAMS = test_common
TESTS = $(check_PROGRAMS) TESTS = $(check_PROGRAMS)
noinst_HEADERS = \
iconv/convert-test-data.h
test_common_SOURCES = \ test_common_SOURCES = \
iconv/convert.c \ iconv/convert.c \
iconv/convert-test-data.c \
rect/clip_and_split.c \ rect/clip_and_split.c \
rect/constrain.c \ rect/constrain.c \
rect/expand_to_grid.c \ rect/expand_to_grid.c \

View 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"
)
}
};

View 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];

View File

@ -18,48 +18,10 @@
*/ */
#include "common/iconv.h" #include "common/iconv.h"
#include "convert-test-data.h"
#include <CUnit/CUnit.h> #include <CUnit/CUnit.h>
#include <stdio.h>
/**
* UTF8 for "papà è bello".
*/
unsigned char test_string_utf8[] = {
'p', 'a', 'p', 0xC3, 0xA0, ' ',
0xC3, 0xA8, ' ',
'b', 'e', 'l', 'l', 'o',
0x00
};
/**
* UTF16 for "papà è bello".
*/
unsigned char test_string_utf16[] = {
'p', 0x00, 'a', 0x00, 'p', 0x00, 0xE0, 0x00, ' ', 0x00,
0xE8, 0x00, ' ', 0x00,
'b', 0x00, 'e', 0x00, 'l', 0x00, 'l', 0x00, 'o', 0x00,
0x00, 0x00
};
/**
* ISO-8859-1 for "papà è bello".
*/
unsigned char test_string_iso8859_1[] = {
'p', 'a', 'p', 0xE0, ' ',
0xE8, ' ',
'b', 'e', 'l', 'l', 'o',
0x00
};
/**
* CP1252 for "papà è bello".
*/
unsigned char test_string_cp1252[] = {
'p', 'a', 'p', 0xE0, ' ',
0xE8, ' ',
'b', 'e', 'l', 'l', 'o',
0x00
};
/** /**
* Tests that conversion between character sets using the given guac_iconv_read * Tests that conversion between character sets using the given guac_iconv_read
@ -69,25 +31,20 @@ unsigned char test_string_cp1252[] = {
* The guac_iconv_read implementation to use to read the input string. * The guac_iconv_read implementation to use to read the input string.
* *
* @param in_string * @param in_string
* A pointer to the beginning of the input string. * A pointer to the test_string structure describing the input string being
* * tested.
* @param in_length
* The size of the input string in bytes.
* *
* @param writer * @param writer
* The guac_iconv_write implementation to use to write the output string * The guac_iconv_write implementation to use to write the output string
* (the converted input string). * (the converted input string).
* *
* @param out_string * @param out_string
* A pointer to the beginning of a string which contains the expected * A pointer to the test_string structure describing the expected result of
* result of the conversion. * the conversion.
*
* @param out_length
* The size of the expected result in bytes.
*/ */
static void verify_conversion( static void verify_conversion(
guac_iconv_read* reader, unsigned char* in_string, int in_length, guac_iconv_read* reader, test_string* in_string,
guac_iconv_write* writer, unsigned char* out_string, int out_length) { guac_iconv_write* writer, test_string* out_string) {
char output[4096]; char output[4096];
char input[4096]; char input[4096];
@ -95,91 +52,78 @@ static void verify_conversion(
const char* current_input = input; const char* current_input = input;
char* current_output = output; char* current_output = output;
memcpy(input, in_string, in_length); memcpy(input, in_string->buffer, in_string->size);
guac_iconv(reader, &current_input, sizeof(input), guac_iconv(reader, &current_input, sizeof(input),
writer, &current_output, sizeof(output)); writer, &current_output, sizeof(output));
/* Verify output length */ /* Verify output length */
CU_ASSERT_EQUAL(out_length, current_output - output); CU_ASSERT_EQUAL(out_string->size, current_output - output);
/* Verify entire input read */ /* Verify entire input read */
CU_ASSERT_EQUAL(in_length, current_input - input); CU_ASSERT_EQUAL(in_string->size, current_input - input);
/* Verify output content */ /* Verify output content */
CU_ASSERT_EQUAL(0, memcmp(output, out_string, out_length)); CU_ASSERT_EQUAL(0, memcmp(output, out_string->buffer, out_string->size));
} }
/** /**
* Tests which verifies conversion of UTF-8 to itself. * 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__utf8_to_utf8() { void test_iconv__preserve() {
verify_conversion( for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8), for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
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);
}
}
} }
/** /**
* Tests which verifies conversion of UTF-16 to UTF-8. * 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__utf8_to_utf16() { void test_iconv__normalize_unix() {
verify_conversion( for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8), for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
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);
}
}
} }
/** /**
* Tests which verifies conversion of UTF-16 to itself. * 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__utf16_to_utf16() { void test_iconv__normalize_crlf() {
verify_conversion( for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
} encoding_test_parameters* from = &test_params[i];
encoding_test_parameters* to = &test_params[j];
/**
* Tests which verifies conversion of UTF-8 to UTF-16. printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
*/ verify_conversion(from->reader_normalized, &from->test_mixed,
void test_iconv__utf16_to_utf8() { to->writer_crlf, &to->test_windows);
verify_conversion(
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16), }
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8)); }
}
/**
* Tests which verifies conversion of UTF-16 to ISO 8859-1.
*/
void test_iconv__utf16_to_iso8859_1() {
verify_conversion(
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
GUAC_WRITE_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1));
}
/**
* Tests which verifies conversion of UTF-16 to CP1252.
*/
void test_iconv__utf16_to_cp1252() {
verify_conversion(
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
GUAC_WRITE_CP1252, test_string_cp1252, sizeof(test_string_cp1252));
}
/**
* Tests which verifies conversion of CP1252 to UTF-8.
*/
void test_iconv__cp1252_to_utf8() {
verify_conversion(
GUAC_READ_CP1252, test_string_cp1252, sizeof(test_string_cp1252),
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
}
/**
* Tests which verifies conversion of ISO 8859-1 to UTF-8.
*/
void test_iconv__iso8859_1_to_utf8() {
verify_conversion(
GUAC_READ_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1),
GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
} }

115
src/guacd-docker/bin/build-all.sh Executable file
View 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

View File

@ -1,86 +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 link-freerdp-plugins.sh
##
## Automatically creates any required symbolic links for the proper loading of
## the given FreeRDP plugins. If a given plugin is already in the correct
## directory, no link is created for that plugin.
##
## @param ...
## The FreeRDP plugins to add links for.
##
##
## Given the full path to a FreeRDP plugin, locates the base directory of the
## associated FreeRDP installation (where the FreeRDP library .so files are
## located), printing the result to STDOUT. If the directory cannot be
## determined, an error is printed.
##
## @param PLUGIN_FILE
## The full path to the FreeRDP plugin to check.
##
where_is_freerdp() {
PLUGIN_FILE="$1"
# Determine the location of all libfreerdp* libraries explicitly linked
# to given file
PATHS="$(ldd "$PLUGIN_FILE" \
| awk '/=>/{print $(NF-1)}' \
| grep 'libfreerdp' \
| xargs -r dirname \
| xargs -r realpath \
| sort -u)"
# Verify that exactly one location was found
if [ "$(echo "$PATHS" | wc -l)" != 1 ]; then
echo "$1: Unable to locate FreeRDP install location." >&2
return 1
fi
echo "$PATHS"
}
#
# Create symbolic links as necessary to include all given plugins within the
# search path of FreeRDP
#
while [ -n "$1" ]; do
# Determine correct install location for FreeRDP plugins
FREERDP_DIR="$(where_is_freerdp "$1")"
FREERDP_PLUGIN_DIR="${FREERDP_DIR}/freerdp2"
# Add symbolic link if necessary
if [ ! -e "$FREERDP_PLUGIN_DIR/$(basename "$1")" ]; then
mkdir -p "$FREERDP_PLUGIN_DIR"
ln -s "$1" "$FREERDP_PLUGIN_DIR"
else
echo "$1: Already in correct directory." >&2
fi
shift
done

View File

@ -21,7 +21,7 @@
## ##
## @fn list-dependencies.sh ## @fn list-dependencies.sh
## ##
## Lists the Debian/Ubuntu package names for all library dependencies of the ## Lists the Alpine Linux package names for all library dependencies of the
## given binaries. Each package is only listed once, even if multiple binaries ## given binaries. Each package is only listed once, even if multiple binaries
## provided by the same package are given. ## provided by the same package are given.
## ##
@ -35,14 +35,17 @@ while [ -n "$1" ]; do
ldd "$1" | grep -v 'libguac' | awk '/=>/{print $(NF-1)}' \ ldd "$1" | grep -v 'libguac' | awk '/=>/{print $(NF-1)}' \
| while read LIBRARY; do | while read LIBRARY; do
# Determine the Debian package which is associated with that # List the package providing that library, if any
# library, if any apk info -W "$LIBRARY" 2> /dev/null \
dpkg-query -S "$LIBRARY" 2> /dev/null || true | grep 'is owned by' | grep -o '[^ ]*$' || true
done done
# Next binary # Next binary
shift shift
done | cut -f1 -d: | sort -u # 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

View File

@ -176,8 +176,8 @@ 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->print_version = 0;

View File

@ -24,6 +24,18 @@
#include <guacamole/client.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. * The contents of a guacd configuration file.
*/ */

View File

@ -278,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);

View File

@ -381,10 +381,15 @@ int main(int argc, char* argv[]) {
CRYPTO_set_locking_callback(guacd_openssl_locking_callback); CRYPTO_set_locking_callback(guacd_openssl_locking_callback);
#endif #endif
/* Init SSL */ #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) {

View File

@ -135,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);
@ -143,9 +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.37.100: input/output was not decoupled */
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
/* 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) {
@ -176,19 +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, (void*) &packet, 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 */
@ -201,7 +213,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
#endif #endif
} }
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
int bitrate, int width, int height, int gop_size, int qmax, int qmin, int bitrate, int width, int height, int gop_size, int qmax, int qmin,
int pix_fmt, AVRational time_base) { int pix_fmt, AVRational time_base) {
@ -237,7 +249,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
} }
int guacenc_open_avcodec(AVCodecContext *avcodec_context, int guacenc_open_avcodec(AVCodecContext *avcodec_context,
AVCodec *codec, AVDictionary **options, const AVCodec *codec, AVDictionary **options,
AVStream* stream) { AVStream* stream) {
int ret = avcodec_open2(avcodec_context, codec, options); int ret = avcodec_open2(avcodec_context, codec, options);

View File

@ -128,7 +128,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
* The pointer to the configured AVCodecContext. * The pointer to the configured AVCodecContext.
* *
*/ */
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
int bitrate, int width, int height, int gop_size, int qmax, int qmin, int bitrate, int width, int height, int gop_size, int qmax, int qmin,
int pix_fmt, AVRational time_base); int pix_fmt, AVRational time_base);
@ -158,7 +158,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
* Zero on success, a negative value on error. * Zero on success, a negative value on error.
*/ */
int guacenc_open_avcodec(AVCodecContext *avcodec_context, int guacenc_open_avcodec(AVCodecContext *avcodec_context,
AVCodec *codec, AVDictionary **options, const AVCodec *codec, AVDictionary **options,
AVStream* stream); AVStream* stream);
#endif #endif

View File

@ -47,7 +47,7 @@
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int width, int height, int bitrate) { int width, int height, int bitrate) {
AVOutputFormat *container_format; const AVOutputFormat *container_format;
AVFormatContext *container_format_context; AVFormatContext *container_format_context;
AVStream *video_stream; AVStream *video_stream;
int ret; int ret;
@ -63,7 +63,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
container_format = container_format_context->oformat; container_format = container_format_context->oformat;
/* Pull codec based on name */ /* Pull codec based on name */
AVCodec* codec = avcodec_find_encoder_by_name(codec_name); const AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
if (codec == NULL) { if (codec == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".",
codec_name); codec_name);

View File

@ -44,6 +44,7 @@ libguacinc_HEADERS = \
guacamole/client-types.h \ guacamole/client-types.h \
guacamole/error.h \ guacamole/error.h \
guacamole/error-types.h \ guacamole/error-types.h \
guacamole/fips.h \
guacamole/hash.h \ guacamole/hash.h \
guacamole/layer.h \ guacamole/layer.h \
guacamole/layer-types.h \ guacamole/layer-types.h \
@ -59,6 +60,7 @@ libguacinc_HEADERS = \
guacamole/protocol.h \ guacamole/protocol.h \
guacamole/protocol-constants.h \ guacamole/protocol-constants.h \
guacamole/protocol-types.h \ guacamole/protocol-types.h \
guacamole/recording.h \
guacamole/socket-constants.h \ guacamole/socket-constants.h \
guacamole/socket.h \ guacamole/socket.h \
guacamole/socket-fntypes.h \ guacamole/socket-fntypes.h \
@ -92,6 +94,7 @@ libguac_la_SOURCES = \
encode-jpeg.c \ encode-jpeg.c \
encode-png.c \ encode-png.c \
error.c \ error.c \
fips.c \
hash.c \ hash.c \
id.c \ id.c \
palette.c \ palette.c \
@ -99,6 +102,7 @@ libguac_la_SOURCES = \
pool.c \ pool.c \
protocol.c \ protocol.c \
raw_encoder.c \ raw_encoder.c \
recording.c \
socket.c \ socket.c \
socket-broadcast.c \ socket-broadcast.c \
socket-fd.c \ socket-fd.c \
@ -135,7 +139,7 @@ libguac_la_CFLAGS = \
-Werror -Wall -pedantic -Werror -Wall -pedantic
libguac_la_LDFLAGS = \ libguac_la_LDFLAGS = \
-version-info 19:0:0 \ -version-info 21:0:0 \
-no-undefined \ -no-undefined \
@CAIRO_LIBS@ \ @CAIRO_LIBS@ \
@DL_LIBS@ \ @DL_LIBS@ \

View File

@ -307,6 +307,10 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
pthread_rwlock_unlock(&(client->__users_lock)); pthread_rwlock_unlock(&(client->__users_lock));
/* Notify owner of user joining connection. */
if (retval == 0 && !user->owner)
guac_client_owner_notify_join(client, user);
return retval; return retval;
} }
@ -333,6 +337,10 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
pthread_rwlock_unlock(&(client->__users_lock)); pthread_rwlock_unlock(&(client->__users_lock));
/* Update owner of user having left the connection. */
if (!user->owner)
guac_client_owner_notify_leave(client, user);
/* Call handler, if defined */ /* Call handler, if defined */
if (user->leave_handler) if (user->leave_handler)
user->leave_handler(user); user->leave_handler(user);
@ -413,15 +421,19 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
} }
int guac_client_end_frame(guac_client* client) { int guac_client_end_frame(guac_client* client) {
return guac_client_end_multiple_frames(client, 0);
}
int guac_client_end_multiple_frames(guac_client* client, int frames) {
/* Update and send timestamp */ /* Update and send timestamp */
client->last_sent_timestamp = guac_timestamp_current(); client->last_sent_timestamp = guac_timestamp_current();
/* Log received timestamp and calculated lag (at TRACE level only) */ /* Log received timestamp and calculated lag (at TRACE level only) */
guac_client_log(client, GUAC_LOG_TRACE, "Server completed " guac_client_log(client, GUAC_LOG_TRACE, "Server completed "
"frame %" PRIu64 "ms.", client->last_sent_timestamp); "frame %" PRIu64 "ms (%i logical frames)", client->last_sent_timestamp, frames);
return guac_protocol_send_sync(client->socket, client->last_sent_timestamp); return guac_protocol_send_sync(client->socket, client->last_sent_timestamp, frames);
} }
@ -671,6 +683,36 @@ static void* __webp_support_callback(guac_user* user, void* data) {
} }
#endif #endif
/**
* A callback function which is invoked by guac_client_owner_supports_msg()
* to determine if the owner of a client supports the "msg" instruction,
* returning zero if the user does not support the instruction or non-zero if
* the user supports it.
*
* @param user
* The guac_user that will be checked for "msg" instruction support.
*
* @param data
* Data provided to the callback. This value is never used within this
* callback.
*
* @return
* A non-zero integer if the provided user who owns the connection supports
* the "msg" instruction, or zero if the user does not. The integer is cast
* as a void*.
*/
static void* guac_owner_supports_msg_callback(guac_user* user, void* data) {
return (void*) ((intptr_t) guac_user_supports_msg(user));
}
int guac_client_owner_supports_msg(guac_client* client) {
return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_msg_callback, NULL));
}
/** /**
* A callback function which is invoked by guac_client_owner_supports_required() * A callback function which is invoked by guac_client_owner_supports_required()
* to determine if the owner of a client supports the "required" instruction, * to determine if the owner of a client supports the "required" instruction,
@ -701,6 +743,124 @@ int guac_client_owner_supports_required(guac_client* client) {
} }
/**
* A callback function that is invokved by guac_client_owner_notify_join() to
* notify the owner of a connection that another user has joined the
* connection, returning zero if the message is sent successfully, or non-zero
* if an error occurs.
*
* @param user
* The user to send the notification to, which will be the owner of the
* connection.
*
* @param data
* The data provided to the callback, which is the user that is joining the
* connection.
*
* @return
* Zero if the message is sent successfully to the owner, otherwise
* non-zero, cast as a void*.
*/
static void* guac_client_owner_notify_join_callback(guac_user* user, void* data) {
const guac_user* joiner = (const guac_user *) data;
if (user == NULL)
return (void*) ((intptr_t) -1);
char* log_owner = "owner";
if (user->info.name != NULL)
log_owner = (char *) user->info.name;
char* log_joiner = "anonymous";
char* send_joiner = "";
if (joiner->info.name != NULL) {
log_joiner = (char *) joiner->info.name;
send_joiner = (char *) joiner->info.name;
}
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" joining.",
log_owner, log_joiner);
/* Send user joined notification to owner. */
const char* args[] = { (const char*)joiner->user_id, (const char*)send_joiner, NULL };
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_JOINED, args));
}
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner) {
/* Don't send msg instruction if client does not support it. */
if (!guac_client_owner_supports_msg(client)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Client does not support the \"msg\" instruction and "
"will not be notified of the user joining the connection.");
return -1;
}
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_join_callback, joiner));
}
/**
* A callback function that is invokved by guac_client_owner_notify_leave() to
* notify the owner of a connection that another user has left the connection,
* returning zero if the message is sent successfully, or non-zero
* if an error occurs.
*
* @param user
* The user to send the notification to, which will be the owner of the
* connection.
*
* @param data
* The data provided to the callback, which is the user that is leaving the
* connection.
*
* @return
* Zero if the message is sent successfully to the owner, otherwise
* non-zero, cast as a void*.
*/
static void* guac_client_owner_notify_leave_callback(guac_user* user, void* data) {
const guac_user* quitter = (const guac_user *) data;
if (user == NULL)
return (void*) ((intptr_t) -1);
char* log_owner = "owner";
if (user->info.name != NULL)
log_owner = (char *) user->info.name;
char* log_quitter = "anonymous";
char* send_quitter = "";
if (quitter->info.name != NULL) {
log_quitter = (char *) quitter->info.name;
send_quitter = (char *) quitter->info.name;
}
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" leaving.",
log_owner, log_quitter);
/* Send user left notification to owner. */
const char* args[] = { (const char*)quitter->user_id, (const char*)send_quitter, NULL };
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_LEFT, args));
}
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter) {
/* Don't send msg instruction if client does not support it. */
if (!guac_client_owner_supports_msg(client)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Client does not support the \"msg\" instruction and "
"will not be notified of the user leaving the connection.");
return -1;
}
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_leave_callback, quitter));
}
int guac_client_supports_webp(guac_client* client) { int guac_client_supports_webp(guac_client* client) {
#ifdef ENABLE_WEBP #ifdef ENABLE_WEBP

51
src/libguac/fips.c Normal file
View File

@ -0,0 +1,51 @@
/*
* 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 "guacamole/fips.h"
/* If OpenSSL is available, include header for version numbers */
#ifdef ENABLE_SSL
#include <openssl/opensslv.h>
/* OpenSSL versions prior to 0.9.7e did not have FIPS support */
#if !defined(OPENSSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x00090705f)
#define GUAC_FIPS_ENABLED 0
/* OpenSSL 3+ uses EVP_default_properties_is_fips_enabled() */
#elif defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
#include <openssl/evp.h>
#define GUAC_FIPS_ENABLED EVP_default_properties_is_fips_enabled(NULL)
/* For OpenSSL versions between 0.9.7e and 3.0, use FIPS_mode() */
#else
#include <openssl/crypto.h>
#define GUAC_FIPS_ENABLED FIPS_mode()
#endif
/* FIPS support does not exist if OpenSSL is not available. */
#else
#define GUAC_FIPS_ENABLED 0
#endif
int guac_fips_enabled() {
return GUAC_FIPS_ENABLED;
}

View File

@ -509,18 +509,47 @@ void* guac_client_for_user(guac_client* client, guac_user* user,
guac_user_callback* callback, void* data); guac_user_callback* callback, void* data);
/** /**
* Marks the end of the current frame by sending a "sync" instruction to * Marks the end of the current frame by sending a "sync" instruction to all
* all connected users. This instruction will contain the current timestamp. * connected users, where the number of input frames that were considered in
* The last_sent_timestamp member of guac_client will be updated accordingly. * creating this frame is either unknown or inapplicable. This instruction will
* contain the current timestamp. The last_sent_timestamp member of guac_client
* will be updated accordingly.
* *
* If an error occurs sending the instruction, a non-zero value is * If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately. * returned, and guac_error is set appropriately.
* *
* @param client The guac_client which has finished a frame. * @param client
* @return Zero on success, non-zero on error. * The guac_client which has finished a frame.
*
* @return
* Zero on success, non-zero on error.
*/ */
int guac_client_end_frame(guac_client* client); int guac_client_end_frame(guac_client* client);
/**
* Marks the end of the current frame by sending a "sync" instruction to all
* connected users, where that frame may combine or otherwise represent the
* changes of an arbitrary number of input frames. This instruction will
* contain the current timestamp, as well as the number of frames that were
* considered in creating that frame. The last_sent_timestamp member of
* guac_client will be updated accordingly.
*
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @param client
* The guac_client which has finished a frame.
*
* @param frames
* The number of distinct frames that were considered or combined when
* generating the current frame, or zero if the boundaries of relevant
* frames are unknown.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_client_end_multiple_frames(guac_client* client, int frames);
/** /**
* Initializes the given guac_client using the initialization routine provided * Initializes the given guac_client using the initialization routine provided
* by the plugin corresponding to the named protocol. This will automatically * by the plugin corresponding to the named protocol. This will automatically
@ -708,6 +737,21 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y, guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface, int quality, int lossless); cairo_surface_t* surface, int quality, int lossless);
/**
* Returns whether the owner of the given client supports the "msg"
* instruction, returning non-zero if the client owner does support the
* instruction, or zero if the owner does not.
*
* @param client
* The Guacamole client whose owner should be checked for supporting
* the "msg" instruction.
*
* @return
* Non-zero if the owner of the given client supports the "msg"
* instruction, zero otherwise.
*/
int guac_client_owner_supports_msg(guac_client* client);
/** /**
* Returns whether the owner of the given client supports the "required" * Returns whether the owner of the given client supports the "required"
* instruction, returning non-zero if the client owner does support the * instruction, returning non-zero if the client owner does support the
@ -723,6 +767,42 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
*/ */
int guac_client_owner_supports_required(guac_client* client); int guac_client_owner_supports_required(guac_client* client);
/**
* Notifies the owner of the given client that a user has joined the connection,
* and returns zero if the message was sent successfully, or non-zero if the
* notification failed.
*
* @param client
* The Guacamole Client whose owner should be notified of a user joining
* the connection.
*
* @param joiner
* The Guacamole User who joined the connection.
*
* @return
* Zero if the notification to the owner was sent successfully, or non-zero
* if an error occurred.
*/
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner);
/**
* Notifies the owner of the given client that a user has left the connection,
* and returns zero if the message was sent successfully, or non-zero if the
* notification failed.
*
* @param client
* The Guacamole Client whose owner should be notified of a user leaving
* the connection.
*
* @param quitter
* The Guacamole User who left the connection.
*
* @return
* Zero if the notification to the owner was sent successfully, or non-zero
* if an error occurred.
*/
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter);
/** /**
* Returns whether all users of the given client support WebP. If any user does * Returns whether all users of the given client support WebP. If any user does
* not support WebP, or the server cannot encode WebP images, zero is returned. * not support WebP, or the server cannot encode WebP images, zero is returned.

View File

@ -17,26 +17,17 @@
* under the License. * under the License.
*/ */
#ifndef BALL_H #ifndef GUAC_FIPS_H
#define BALL_H #define GUAC_FIPS_H
#include <guacamole/layer.h> /**
* Returns a non-zero value if FIPS mode is enabled, or zero if FIPS mode
#include <pthread.h> * is not enabled.
*
typedef struct ball_client_data { * @return
* A non-zero value if FIPS mode is enabled, or zero if FIPS mode is
guac_layer* ball; * not enabled.
*/
int ball_x; int guac_fips_enabled();
int ball_y;
int ball_velocity_x;
int ball_velocity_y;
pthread_t render_thread;
} ball_client_data;
#endif #endif

View File

@ -38,7 +38,7 @@
* This version is passed by the __guac_protocol_send_args() function from the * This version is passed by the __guac_protocol_send_args() function from the
* server to the client during the client/server handshake. * server to the client during the client/server handshake.
*/ */
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_3_0" #define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_5_0"
/** /**
* The maximum number of bytes that should be sent in any one blob instruction * The maximum number of bytes that should be sent in any one blob instruction
@ -49,5 +49,20 @@
*/ */
#define GUAC_PROTOCOL_BLOB_MAX_LENGTH 6048 #define GUAC_PROTOCOL_BLOB_MAX_LENGTH 6048
/**
* The name of the layer parameter defining the number of simultaneous points
* of contact supported by a layer. This parameter should be set to a non-zero
* value if the associated layer should receive touch events ("touch"
* instructions).
*
* This value specified for this parameter 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.
*
* @see guac_protocol_send_set_int()
*/
#define GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH "multi-touch"
#endif #endif

View File

@ -306,9 +306,40 @@ typedef enum guac_protocol_version {
* allowing connections in guacd to request information from the client and * allowing connections in guacd to request information from the client and
* await a response. * await a response.
*/ */
GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300 GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300,
/**
* Protocol version 1.5.0, which supports the "msg" instruction, allowing
* messages to be sent to the client, and adds support for the "name"
* handshake instruction.
*/
GUAC_PROTOCOL_VERSION_1_5_0 = 0x010500
} guac_protocol_version; } guac_protocol_version;
/**
* A type that represents codes for human-readable messages sent by the "msg"
* instruction to the Client, that will be displayed in the client's browser.
* The codes will be interpreted by the client into translatable messages, and
* make take arguments, as noted below.
*/
typedef enum guac_message_type {
/**
* A message that notifies the owner of a connection that another user has
* joined their connection. There should be a single argument provided, the
* name of the user who has joined.
*/
GUAC_MESSAGE_USER_JOINED = 0x0001,
/**
* A message that notifies the owner of a connection that another user has
* left their connection. There should be a single argument provided, the
* name of the user who has left.
*/
GUAC_MESSAGE_USER_LEFT = 0x0002
} guac_message_type;
#endif #endif

View File

@ -171,6 +171,27 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...);
int vguac_protocol_send_log(guac_socket* socket, const char* format, int vguac_protocol_send_log(guac_socket* socket, const char* format,
va_list args); va_list args);
/**
* Sends the given string over the socket to be displayed on the client. Returns
* zero if the message was sent successfully or non-zero if an error occurs.
*
* @param socket
* The guac_socket connection to send the message to.
*
* @param msg
* The message code to send to the client.
*
* @param args
* A null-terminated array of strings that will be provided to the client
* as part of the message, that the client may then place in the message,
* or null if the message requires no arguments.
*
* @return
* Zero if the message is sent successfully; otherwise non-zero.
*/
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
const char** args);
/** /**
* Sends a mouse instruction over the given guac_socket connection. * Sends a mouse instruction over the given guac_socket connection.
* *
@ -209,6 +230,53 @@ int vguac_protocol_send_log(guac_socket* socket, const char* format,
int guac_protocol_send_mouse(guac_socket* socket, int x, int y, int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
int button_mask, guac_timestamp timestamp); int button_mask, guac_timestamp timestamp);
/**
* Sends a touch instruction over the given guac_socket connection.
*
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @param socket
* The guac_socket connection to use.
*
* @param id
* An arbitrary integer ID which uniquely identifies this contact relative
* to other active contacts.
*
* @param x
* The X coordinate of the center of the touch contact.
*
* @param y
* The Y coordinate of the center of the touch contact.
*
* @param x_radius
* The X radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param y_radius
* The Y radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param angle
* The rough angle of clockwise rotation of the general area of the touch
* contact, in degrees.
*
* @param force
* The relative force exerted by the touch contact, where 0 is no force
* (the touch has been lifted) and 1 is maximum force (the maximum amount
* of force representable by the device).
*
* @param timestamp
* The server timestamp (in milliseconds) at the point in time this touch
* event was acknowledged.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_protocol_send_touch(guac_socket* socket, int id, int x, int y,
int x_radius, int y_radius, double angle, double force,
guac_timestamp timestamp);
/** /**
* Sends a nest instruction over the given guac_socket connection. * Sends a nest instruction over the given guac_socket connection.
* *
@ -271,6 +339,32 @@ int guac_protocol_send_ready(guac_socket* socket, const char* id);
int guac_protocol_send_set(guac_socket* socket, const guac_layer* layer, int guac_protocol_send_set(guac_socket* socket, const guac_layer* layer,
const char* name, const char* value); const char* name, const char* value);
/**
* Sends a set instruction over the given guac_socket connection. This function
* behavies identically to guac_protocol_send_set() except that the provided
* parameter value is an integer, rather than a string.
*
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @param socket
* The guac_socket connection to use.
*
* @param layer
* The layer to set the parameter of.
*
* @param name
* The name of the parameter to set.
*
* @param value
* The value to set the parameter to.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_protocol_send_set_int(guac_socket* socket, const guac_layer* layer,
const char* name, int value);
/** /**
* Sends a select instruction over the given guac_socket connection. * Sends a select instruction over the given guac_socket connection.
* *
@ -290,11 +384,22 @@ int guac_protocol_send_select(guac_socket* socket, const char* protocol);
* If an error occurs sending the instruction, a non-zero value is * If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately. * returned, and guac_error is set appropriately.
* *
* @param socket The guac_socket connection to use. * @param socket
* @param timestamp The current timestamp (in milliseconds). * The guac_socket connection to use.
* @return Zero on success, non-zero on error. *
* @param timestamp
* The current timestamp (in milliseconds).
*
* @param frames
* The number of distinct frames that were considered or combined when
* generating the frame terminated by this instruction, or zero if the
* boundaries of relevant frames are unknown.
*
* @return
* Zero on success, non-zero on error.
*/ */
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp); int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp,
int frames);
/* OBJECT INSTRUCTIONS */ /* OBJECT INSTRUCTIONS */

View File

@ -17,11 +17,17 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_COMMON_RECORDING_H #ifndef GUAC_RECORDING_H
#define GUAC_COMMON_RECORDING_H #define GUAC_RECORDING_H
#include <guacamole/client.h> #include <guacamole/client.h>
/**
* Provides functions and structures to be use for session recording.
*
* @file recording.h
*/
/** /**
* The maximum numeric value allowed for the .1, .2, .3, etc. suffix appended * 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 * to the end of the session recording filename if a recording having the
@ -47,7 +53,7 @@
* that output Guacamole instructions may be dynamically intercepted and * that output Guacamole instructions may be dynamically intercepted and
* written to a file. * written to a file.
*/ */
typedef struct guac_common_recording { typedef struct guac_recording {
/** /**
* The guac_socket which writes directly to the recording file, rather than * The guac_socket which writes directly to the recording file, rather than
@ -71,6 +77,15 @@ typedef struct guac_common_recording {
*/ */
int include_mouse; int include_mouse;
/**
* Non-zero if multi-touch events should be included in the session
* recording, zero otherwise. Depending on whether the remote desktop will
* automatically provide graphical feedback for touches, including touch
* events may be necessary for multi-touch interactions to be rendered in
* any resulting video.
*/
int include_touch;
/** /**
* Non-zero if keys pressed and released should be included in the session * Non-zero if keys pressed and released should be included in the session
* recording, zero otherwise. Including key events within the recording may * recording, zero otherwise. Including key events within the recording may
@ -80,7 +95,7 @@ typedef struct guac_common_recording {
*/ */
int include_keys; int include_keys;
} guac_common_recording; } guac_recording;
/** /**
* Replaces the socket of the given client such that all further Guacamole * Replaces the socket of the given client such that all further Guacamole
@ -119,6 +134,13 @@ typedef struct guac_common_recording {
* otherwise. Including mouse state is necessary for the mouse cursor to be * otherwise. Including mouse state is necessary for the mouse cursor to be
* rendered in any resulting video. * rendered in any resulting video.
* *
* @param include_touch
* Non-zero if touch events should be included in the session recording,
* zero otherwise. Depending on whether the remote desktop will
* automatically provide graphical feedback for touches, including touch
* events may be necessary for multi-touch interactions to be rendered in
* any resulting video.
*
* @param include_keys * @param include_keys
* Non-zero if keys pressed and released should be included in the session * Non-zero if keys pressed and released should be included in the session
* recording, zero otherwise. Including key events within the recording may * recording, zero otherwise. Including key events within the recording may
@ -127,13 +149,14 @@ typedef struct guac_common_recording {
* passwords, credit card numbers, etc. * passwords, credit card numbers, etc.
* *
* @return * @return
* A new guac_common_recording structure representing the in-progress * A new guac_recording structure representing the in-progress
* recording if the recording file has been successfully created and a * recording if the recording file has been successfully created and a
* recording will be written, NULL otherwise. * recording will be written, NULL otherwise.
*/ */
guac_common_recording* guac_common_recording_create(guac_client* client, guac_recording* guac_recording_create(guac_client* client,
const char* path, const char* name, int create_path, const char* path, const char* name, int create_path,
int include_output, int include_mouse, int include_keys); int include_output, int include_mouse, int include_touch,
int include_keys);
/** /**
* Frees the resources associated with the given in-progress recording. Note * Frees the resources associated with the given in-progress recording. Note
@ -142,15 +165,15 @@ guac_common_recording* guac_common_recording_create(guac_client* client,
* freed when the guac_client is freed. * freed when the guac_client is freed.
* *
* @param recording * @param recording
* The guac_common_recording to free. * The guac_recording to free.
*/ */
void guac_common_recording_free(guac_common_recording* recording); void guac_recording_free(guac_recording* recording);
/** /**
* Reports the current mouse position and button state within the recording. * Reports the current mouse position and button state within the recording.
* *
* @param recording * @param recording
* The guac_common_recording associated with the mouse that has moved. * The guac_recording associated with the mouse that has moved.
* *
* @param x * @param x
* The new X coordinate of the mouse cursor, in pixels. * The new X coordinate of the mouse cursor, in pixels.
@ -171,14 +194,52 @@ void guac_common_recording_free(guac_common_recording* recording);
* @see GUAC_CLIENT_MOUSE_SCROLL_UP * @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN * @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*/ */
void guac_common_recording_report_mouse(guac_common_recording* recording, void guac_recording_report_mouse(guac_recording* recording,
int x, int y, int button_mask); int x, int y, int button_mask);
/**
* Reports the current state of a touch contact within the recording.
*
* @param recording
* The guac_recording associated with the touch contact that
* has changed state.
*
* @param id
* An arbitrary integer ID which uniquely identifies this contact relative
* to other active contacts.
*
* @param x
* The X coordinate of the center of the touch contact.
*
* @param y
* The Y coordinate of the center of the touch contact.
*
* @param x_radius
* The X radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param y_radius
* The Y radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param angle
* The rough angle of clockwise rotation of the general area of the touch
* contact, in degrees.
*
* @param force
* The relative force exerted by the touch contact, where 0 is no force
* (the touch has been lifted) and 1 is maximum force (the maximum amount
* of force representable by the device).
*/
void guac_recording_report_touch(guac_recording* recording,
int id, int x, int y, int x_radius, int y_radius,
double angle, double force);
/** /**
* Reports a change in the state of an individual key within the recording. * Reports a change in the state of an individual key within the recording.
* *
* @param recording * @param recording
* The guac_common_recording associated with the key that was pressed or * The guac_recording associated with the key that was pressed or
* released. * released.
* *
* @param keysym * @param keysym
@ -188,7 +249,7 @@ void guac_common_recording_report_mouse(guac_common_recording* recording,
* Non-zero if the key represented by the given keysym is currently * Non-zero if the key represented by the given keysym is currently
* pressed, zero if it is released. * pressed, zero if it is released.
*/ */
void guac_common_recording_report_key(guac_common_recording* recording, void guac_recording_report_key(guac_recording* recording,
int keysym, int pressed); int keysym, int pressed);
#endif #endif

View File

@ -109,6 +109,28 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n);
*/ */
size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n); size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n);
/**
* Search for the null-terminated string needle in the possibly null-
* terminated haystack, looking at no more than len bytes.
*
* @param haystack
* The string to search. It may or may not be null-terminated. Only the
* first len bytes are searched.
*
* @param needle
* The string to look for. It must be null-terminated.
*
* @param len
* The maximum number of bytes to examine in haystack.
*
* @return
* A pointer to the first instance of needle within haystack, or NULL if
* needle does not exist in haystack. If needle is the empty string,
* haystack is returned.
*
*/
char* guac_strnstr(const char *haystack, const char *needle, size_t len);
/** /**
* Simple wrapper for strdup() which behaves identically to standard strdup(), * Simple wrapper for strdup() which behaves identically to standard strdup(),
* except that NULL will be returned if the provided string is NULL. * except that NULL will be returned if the provided string is NULL.

View File

@ -95,6 +95,51 @@ typedef void* guac_user_callback(guac_user* user, void* data);
typedef int guac_user_mouse_handler(guac_user* user, int x, int y, typedef int guac_user_mouse_handler(guac_user* user, int x, int y,
int button_mask); int button_mask);
/**
* Handler for Guacamole touch events, invoked when a "touch" instruction has
* been received from a user.
*
* @param user
* The user that sent the touch event.
*
* @param id
* An arbitrary integer ID which uniquely identifies this contact relative
* to other active contacts.
*
* @param x
* The X coordinate of the center of the touch contact within the display
* when the event occurred, in pixels. This value is not guaranteed to be
* within the bounds of the display area.
*
* @param y
* The Y coordinate of the center of the touch contact within the display
* when the event occurred, in pixels. This value is not guaranteed to be
* within the bounds of the display area.
*
* @param x_radius
* The X radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param y_radius
* The Y radius of the ellipse covering the general area of the touch
* contact, in pixels.
*
* @param angle
* The rough angle of clockwise rotation of the general area of the touch
* contact, in degrees.
*
* @param force
* The relative force exerted by the touch contact, where 0 is no force
* (the touch has been lifted) and 1 is maximum force (the maximum amount
* of force representable by the device).
*
* @return
* Zero if the touch event was handled successfully, or non-zero if an
* error occurred.
*/
typedef int guac_user_touch_handler(guac_user* user, int id, int x, int y,
int x_radius, int y_radius, double angle, double force);
/** /**
* Handler for Guacamole key events, invoked when a "key" event has been * Handler for Guacamole key events, invoked when a "key" event has been
* received from a user. * received from a user.

View File

@ -102,6 +102,14 @@ struct guac_user_info {
*/ */
guac_protocol_version protocol_version; guac_protocol_version protocol_version;
/**
* The human-readable name of the Guacamole user, supplied by the client
* during the handshake. This is an arbitrary value, with no requirements or
* constraints, including that it need not uniquely identify the user.
* If the client does not provide a name then this will be NULL.
*/
const char* name;
}; };
struct guac_user { struct guac_user {
@ -509,6 +517,27 @@ struct guac_user {
*/ */
guac_user_argv_handler* argv_handler; guac_user_argv_handler* argv_handler;
/**
* Handler for touch events sent by the Guacamole web-client.
*
* The handler takes the integer X and Y coordinates representing the
* center of the touch contact, as well as several parameters describing
* the general shape of the contact area. The force parameter indicates the
* amount of force exerted by the contact, including whether the contact
* has been lifted.
*
* Example:
* @code
* int touch_handler(guac_user* user, int id, int x, int y,
* int x_radius, int y_radius, double angle, double force);
*
* int guac_user_init(guac_user* user, int argc, char** argv) {
* user->touch_handler = touch_handler;
* }
* @endcode
*/
guac_user_touch_handler* touch_handler;
}; };
/** /**
@ -829,6 +858,17 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y, guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface, int quality, int lossless); cairo_surface_t* surface, int quality, int lossless);
/**
* Returns whether the given user supports the "msg" instruction.
*
* @param user
* The Guacamole user to check for support of the "msg" instruction.
*
* @return
* Non-zero if the user supports the "msg" instruction, otherwise zero.
*/
int guac_user_supports_msg(guac_user* user);
/** /**
* Returns whether the given user supports the "required" instruction. * Returns whether the given user supports the "required" instruction.
* *

View File

@ -42,11 +42,15 @@
* @param broadcast_addr * @param broadcast_addr
* The broadcast address to which to send the magic Wake-on-LAN packet. * The broadcast address to which to send the magic Wake-on-LAN packet.
* *
* @param udp_port
* The UDP port to use when sending the WoL packet.
*
* @return * @return
* Zero if the packet is successfully sent to the destination; non-zero * Zero if the packet is successfully sent to the destination; non-zero
* if the packet cannot be sent. * if the packet cannot be sent.
*/ */
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr); int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
const unsigned short udp_port);
#endif /* GUAC_WOL_H */ #endif /* GUAC_WOL_H */

View File

@ -22,7 +22,9 @@
#include "guacamole/error.h" #include "guacamole/error.h"
#include "id.h" #include "id.h"
#ifdef HAVE_OSSP_UUID_H #if defined(HAVE_LIBUUID)
#include <uuid/uuid.h>
#elif defined(HAVE_OSSP_UUID_H)
#include <ossp/uuid.h> #include <ossp/uuid.h>
#else #else
#include <uuid.h> #include <uuid.h>
@ -30,54 +32,73 @@
#include <stdlib.h> #include <stdlib.h>
/**
* The length of a UUID in bytes. All UUIDs are guaranteed to be 36 1-byte
* characters long.
*/
#define GUAC_UUID_LEN 36
char* guac_generate_id(char prefix) { char* guac_generate_id(char prefix) {
char* buffer; char* buffer;
char* identifier; char* identifier;
size_t identifier_length;
/* Prepare object to receive generated UUID */
#ifdef HAVE_LIBUUID
uuid_t uuid;
#else
uuid_t* uuid; uuid_t* uuid;
/* Attempt to create UUID object */
if (uuid_create(&uuid) != UUID_RC_OK) { if (uuid_create(&uuid) != UUID_RC_OK) {
guac_error = GUAC_STATUS_NO_MEMORY; guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Could not allocate memory for UUID"; guac_error_message = "Could not allocate memory for UUID";
return NULL; return NULL;
} }
#endif
/* Generate random UUID */ /* Generate unique identifier */
#ifdef HAVE_LIBUUID
uuid_generate(uuid);
#else
if (uuid_make(uuid, UUID_MAKE_V4) != UUID_RC_OK) { if (uuid_make(uuid, UUID_MAKE_V4) != UUID_RC_OK) {
uuid_destroy(uuid); uuid_destroy(uuid);
guac_error = GUAC_STATUS_NO_MEMORY; guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "UUID generation failed"; guac_error_message = "UUID generation failed";
return NULL; return NULL;
} }
#endif
/* Allocate buffer for future formatted ID */ /* Allocate buffer for future formatted ID */
buffer = malloc(UUID_LEN_STR + 2); buffer = malloc(GUAC_UUID_LEN + 2);
if (buffer == NULL) { if (buffer == NULL) {
#ifndef HAVE_LIBUUID
uuid_destroy(uuid); uuid_destroy(uuid);
#endif
guac_error = GUAC_STATUS_NO_MEMORY; guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Could not allocate memory for connection ID"; guac_error_message = "Could not allocate memory for unique ID";
return NULL; return NULL;
} }
identifier = &(buffer[1]); identifier = &(buffer[1]);
identifier_length = UUID_LEN_STR + 1;
/* Build connection ID from UUID */ /* Convert UUID to string to produce unique identifier */
#ifdef HAVE_LIBUUID
uuid_unparse_lower(uuid, identifier);
#else
size_t identifier_length = GUAC_UUID_LEN + 1;
if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) { if (uuid_export(uuid, UUID_FMT_STR, &identifier, &identifier_length) != UUID_RC_OK) {
free(buffer); free(buffer);
uuid_destroy(uuid); uuid_destroy(uuid);
guac_error = GUAC_STATUS_INTERNAL_ERROR; guac_error = GUAC_STATUS_INTERNAL_ERROR;
guac_error_message = "Conversion of UUID to connection ID failed"; guac_error_message = "Conversion of UUID to unique ID failed";
return NULL; return NULL;
} }
/* Clean up generated UUID */
uuid_destroy(uuid); uuid_destroy(uuid);
#endif
buffer[0] = prefix; buffer[0] = prefix;
buffer[UUID_LEN_STR + 1] = '\0'; buffer[GUAC_UUID_LEN + 1] = '\0';
return buffer; return buffer;
} }

View File

@ -65,6 +65,7 @@ guac_protocol_version_mapping guac_protocol_version_table[] = {
{ GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" }, { GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" },
{ GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" }, { GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" },
{ GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" }, { GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" },
{ GUAC_PROTOCOL_VERSION_1_5_0, "VERSION_1_5_0" },
{ GUAC_PROTOCOL_VERSION_UNKNOWN, NULL } { GUAC_PROTOCOL_VERSION_UNKNOWN, NULL }
}; };
@ -658,6 +659,23 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...) {
} }
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
const char** args) {
int ret_val;
guac_socket_instruction_begin(socket);
ret_val =
guac_socket_write_string(socket, "3.msg,")
|| __guac_socket_write_length_int(socket, msg)
|| guac_socket_write_array(socket, args)
|| guac_socket_write_string(socket, ";");
guac_socket_instruction_end(socket);
return ret_val;
}
int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream, int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream,
const char* mimetype, const char* name) { const char* mimetype, const char* name) {
@ -820,6 +838,37 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
} }
int guac_protocol_send_touch(guac_socket* socket, int id, int x, int y,
int x_radius, int y_radius, double angle, double force,
guac_timestamp timestamp) {
int ret_val;
guac_socket_instruction_begin(socket);
ret_val =
guac_socket_write_string(socket, "5.touch,")
|| __guac_socket_write_length_int(socket, id)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, x)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, y)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, x_radius)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, y_radius)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_double(socket, angle)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_double(socket, force)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, timestamp)
|| guac_socket_write_string(socket, ";");
guac_socket_instruction_end(socket);
return ret_val;
}
int guac_protocol_send_move(guac_socket* socket, const guac_layer* layer, int guac_protocol_send_move(guac_socket* socket, const guac_layer* layer,
const guac_layer* parent, int x, int y, int z) { const guac_layer* parent, int x, int y, int z) {
@ -1057,6 +1106,26 @@ int guac_protocol_send_set(guac_socket* socket, const guac_layer* layer,
} }
int guac_protocol_send_set_int(guac_socket* socket, const guac_layer* layer,
const char* name, int value) {
int ret_val;
guac_socket_instruction_begin(socket);
ret_val =
guac_socket_write_string(socket, "3.set,")
|| __guac_socket_write_length_int(socket, layer->index)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_string(socket, name)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, value)
|| guac_socket_write_string(socket, ";");
guac_socket_instruction_end(socket);
return ret_val;
}
int guac_protocol_send_select(guac_socket* socket, const char* protocol) { int guac_protocol_send_select(guac_socket* socket, const char* protocol) {
int ret_val; int ret_val;
@ -1130,7 +1199,8 @@ int guac_protocol_send_start(guac_socket* socket, const guac_layer* layer,
} }
int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp) { int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp,
int frames) {
int ret_val; int ret_val;
@ -1138,6 +1208,8 @@ int guac_protocol_send_sync(guac_socket* socket, guac_timestamp timestamp) {
ret_val = ret_val =
guac_socket_write_string(socket, "4.sync,") guac_socket_write_string(socket, "4.sync,")
|| __guac_socket_write_length_int(socket, timestamp) || __guac_socket_write_length_int(socket, timestamp)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_int(socket, frames)
|| guac_socket_write_string(socket, ";"); || guac_socket_write_string(socket, ";");
guac_socket_instruction_end(socket); guac_socket_instruction_end(socket);

View File

@ -17,12 +17,11 @@
* under the License. * under the License.
*/ */
#include "common/recording.h" #include "guacamole/client.h"
#include "guacamole/protocol.h"
#include <guacamole/client.h> #include "guacamole/recording.h"
#include <guacamole/protocol.h> #include "guacamole/socket.h"
#include <guacamole/socket.h> #include "guacamole/timestamp.h"
#include <guacamole/timestamp.h>
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <direct.h> #include <direct.h>
@ -64,7 +63,7 @@
* The file descriptor of the open data file if open succeeded, or -1 on * The file descriptor of the open data file if open succeeded, or -1 on
* failure. * failure.
*/ */
static int guac_common_recording_open(const char* path, static int guac_recording_open(const char* path,
const char* name, char* basename, int basename_size) { const char* name, char* basename, int basename_size) {
int i; int i;
@ -84,7 +83,7 @@ static int guac_common_recording_open(const char* path,
/* Attempt to open recording */ /* Attempt to open recording */
int fd = open(basename, int fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY, O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR); S_IRUSR | S_IWUSR | S_IRGRP);
/* Continuously retry with alternate names on failure */ /* Continuously retry with alternate names on failure */
if (fd == -1) { if (fd == -1) {
@ -103,7 +102,7 @@ static int guac_common_recording_open(const char* path,
/* Retry with newly-suffixed filename */ /* Retry with newly-suffixed filename */
fd = open(basename, fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY, O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR); S_IRUSR | S_IWUSR | S_IRGRP);
} }
@ -135,15 +134,17 @@ static int guac_common_recording_open(const char* path,
} }
guac_common_recording* guac_common_recording_create(guac_client* client, guac_recording* guac_recording_create(guac_client* client,
const char* path, const char* name, int create_path, const char* path, const char* name, int create_path,
int include_output, int include_mouse, int include_keys) { int include_output, int include_mouse, int include_touch,
int include_keys) {
char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
/* Create path if it does not exist, fail if impossible */ /* Create path if it does not exist, fail if impossible */
#ifndef __MINGW32__ #ifndef __MINGW32__
if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) { if (create_path && mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP)
&& errno != EEXIST) {
#else #else
if (create_path && _mkdir(path) && errno != EEXIST) { if (create_path && _mkdir(path) && errno != EEXIST) {
#endif #endif
@ -153,7 +154,7 @@ guac_common_recording* guac_common_recording_create(guac_client* client,
} }
/* Attempt to open recording file */ /* Attempt to open recording file */
int fd = guac_common_recording_open(path, name, filename, sizeof(filename)); int fd = guac_recording_open(path, name, filename, sizeof(filename));
if (fd == -1) { if (fd == -1) {
guac_client_log(client, GUAC_LOG_ERROR, guac_client_log(client, GUAC_LOG_ERROR,
"Creation of recording failed: %s", strerror(errno)); "Creation of recording failed: %s", strerror(errno));
@ -161,10 +162,11 @@ guac_common_recording* guac_common_recording_create(guac_client* client,
} }
/* Create recording structure with reference to underlying socket */ /* Create recording structure with reference to underlying socket */
guac_common_recording* recording = malloc(sizeof(guac_common_recording)); guac_recording* recording = malloc(sizeof(guac_recording));
recording->socket = guac_socket_open(fd); recording->socket = guac_socket_open(fd);
recording->include_output = include_output; recording->include_output = include_output;
recording->include_mouse = include_mouse; recording->include_mouse = include_mouse;
recording->include_touch = include_touch;
recording->include_keys = include_keys; recording->include_keys = include_keys;
/* Replace client socket with wrapped recording socket only if including /* Replace client socket with wrapped recording socket only if including
@ -181,7 +183,7 @@ guac_common_recording* guac_common_recording_create(guac_client* client,
} }
void guac_common_recording_free(guac_common_recording* recording) { void guac_recording_free(guac_recording* recording) {
/* If not including broadcast output, the output socket is not associated /* If not including broadcast output, the output socket is not associated
* with the client, and must be freed manually */ * with the client, and must be freed manually */
@ -193,7 +195,7 @@ void guac_common_recording_free(guac_common_recording* recording) {
} }
void guac_common_recording_report_mouse(guac_common_recording* recording, void guac_recording_report_mouse(guac_recording* recording,
int x, int y, int button_mask) { int x, int y, int button_mask) {
/* Report mouse location only if recording should contain mouse events */ /* Report mouse location only if recording should contain mouse events */
@ -203,7 +205,18 @@ void guac_common_recording_report_mouse(guac_common_recording* recording,
} }
void guac_common_recording_report_key(guac_common_recording* recording, void guac_recording_report_touch(guac_recording* recording,
int id, int x, int y, int x_radius, int y_radius,
double angle, double force) {
/* Report touches only if recording should contain touch events */
if (recording->include_touch)
guac_protocol_send_touch(recording->socket, id, x, y,
x_radius, y_radius, angle, force, guac_timestamp_current());
}
void guac_recording_report_key(guac_recording* recording,
int keysym, int pressed) { int keysym, int pressed) {
/* Report key state only if recording should contain key events */ /* Report key state only if recording should contain key events */

View File

@ -81,6 +81,38 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) {
} }
char* guac_strnstr(const char *haystack, const char *needle, size_t len) {
#ifdef HAVE_STRNSTR
return strnstr(haystack, needle, len);
#else
char* chr;
size_t nlen = strlen(needle), off = 0;
/* Follow documented API: return haystack if needle is the empty string. */
if (nlen == 0)
return (char *)haystack;
/* Use memchr to find candidates. It might be optimized in asm. */
while (off < len && NULL != (chr = memchr(haystack + off, needle[0], len - off))) {
/* chr is guaranteed to be in bounds of and >= haystack. */
off = chr - haystack;
/* If needle would go beyond provided len, it doesn't exist in haystack. */
if (off + nlen > len)
return NULL;
/* Now that we know we have at least nlen bytes, compare them. */
if (!memcmp(chr, needle, nlen))
return chr;
/* Make sure we make progress. */
off += 1;
}
/* memchr ran out of candidates, needle wasn't found. */
return NULL;
#endif
}
char* guac_strdup(const char* str) { char* guac_strdup(const char* str) {
/* Return NULL if no string provided */ /* Return NULL if no string provided */

View File

@ -36,6 +36,7 @@ TESTS = $(check_PROGRAMS)
test_libguac_SOURCES = \ test_libguac_SOURCES = \
client/buffer_pool.c \ client/buffer_pool.c \
client/layer_pool.c \ client/layer_pool.c \
id/generate.c \
parser/append.c \ parser/append.c \
parser/read.c \ parser/read.c \
pool/next_free.c \ pool/next_free.c \
@ -47,6 +48,7 @@ test_libguac_SOURCES = \
string/strlcat.c \ string/strlcat.c \
string/strlcpy.c \ string/strlcpy.c \
string/strljoin.c \ string/strljoin.c \
string/strnstr.c \
unicode/charsize.c \ unicode/charsize.c \
unicode/read.c \ unicode/read.c \
unicode/strlen.c \ unicode/strlen.c \

View File

@ -0,0 +1,88 @@
/*
* 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 "id.h"
#include <CUnit/CUnit.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/**
* Test which verifies that each call to guac_generate_id() produces a
* different string.
*/
void test_id__unique() {
char* id1 = guac_generate_id('x');
char* id2 = guac_generate_id('x');
/* Neither string may be NULL */
CU_ASSERT_PTR_NOT_NULL_FATAL(id1);
CU_ASSERT_PTR_NOT_NULL_FATAL(id2);
/* Both strings should be different */
CU_ASSERT_STRING_NOT_EQUAL(id1, id2);
free(id1);
free(id2);
}
/**
* Test which verifies that guac_generate_id() produces strings are in the
* correc UUID-based format.
*/
void test_id__format() {
unsigned int ignore;
char* id = guac_generate_id('x');
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
int items_read = sscanf(id, "x%08x-%04x-%04x-%04x-%08x%04x",
&ignore, &ignore, &ignore, &ignore, &ignore, &ignore);
CU_ASSERT_EQUAL(items_read, 6);
CU_ASSERT_EQUAL(strlen(id), 37);
free(id);
}
/**
* Test which verifies that guac_generate_id() takes the specified prefix
* character into account when generating the ID string.
*/
void test_id__prefix() {
char* id;
id = guac_generate_id('a');
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
CU_ASSERT_EQUAL(id[0], 'a');
free(id);
id = guac_generate_id('b');
CU_ASSERT_PTR_NOT_NULL_FATAL(id);
CU_ASSERT_EQUAL(id[0], 'b');
free(id);
}

View File

@ -27,11 +27,11 @@
*/ */
void test_guac_protocol__version_to_string() { void test_guac_protocol__version_to_string() {
guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_3_0; guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_5_0;
guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0; guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0;
guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN; guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN;
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_3_0"); CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_5_0");
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0"); CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0");
CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c)); CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c));

View File

@ -54,7 +54,7 @@ static void write_instructions(int fd) {
/* Write instructions */ /* Write instructions */
guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c"); guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c");
guac_protocol_send_sync(socket, 12345); guac_protocol_send_sync(socket, 12345, 1);
guac_socket_flush(socket); guac_socket_flush(socket);
/* Close and free socket */ /* Close and free socket */
@ -76,7 +76,7 @@ static void read_expected_instructions(int fd) {
char expected[] = char expected[] =
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;" "4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
"4.sync,5.12345;"; "4.sync,5.12345,1.1;";
int numread; int numread;
char buffer[1024]; char buffer[1024];

View File

@ -65,7 +65,7 @@ static void write_instructions(int fd) {
/* Write instructions */ /* Write instructions */
guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c"); guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c");
guac_protocol_send_sync(nested_socket, 12345); guac_protocol_send_sync(nested_socket, 12345, 1);
/* Close and free sockets */ /* Close and free sockets */
guac_socket_free(nested_socket); guac_socket_free(nested_socket);
@ -86,9 +86,9 @@ static void write_instructions(int fd) {
static void read_expected_instructions(int fd) { static void read_expected_instructions(int fd) {
char expected[] = char expected[] =
"4.nest,3.123,37." "4.nest,3.123,41."
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;" "4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
"4.sync,5.12345;" "4.sync,5.12345,1.1;"
";"; ";";
int numread; int numread;

View File

@ -0,0 +1,73 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/string.h>
#include <stdlib.h>
#include <string.h>
/**
* Verify guac_strnstr() behaviors:
*/
void test_string__strnstr() {
char haystack[8] = {'a', 'h', 'i', ' ', 't', 'u', 'n', 'a'};
char* result;
/* needle exists at start of haystack */
result = guac_strnstr(haystack, "ah", sizeof(haystack));
CU_ASSERT_EQUAL(result, haystack);
/* needle exists in the middle of haystack */
result = guac_strnstr(haystack, "hi", sizeof(haystack));
CU_ASSERT_EQUAL(result, haystack + 1);
/* needle exists at end of haystack */
result = guac_strnstr(haystack, "tuna", sizeof(haystack));
CU_ASSERT_EQUAL(result, haystack + 4);
/* needle doesn't exist in haystack, needle[0] isn't in haystack */
result = guac_strnstr(haystack, "mahi", sizeof(haystack));
CU_ASSERT_EQUAL(result, NULL);
/*
* needle doesn't exist in haystack, needle[0] is in haystack,
* length wouldn't allow needle to exist
*/
result = guac_strnstr(haystack, "narwhal", sizeof(haystack));
CU_ASSERT_EQUAL(result, NULL);
/*
* needle doesn't exist in haystack, needle[0] is in haystack,
* length would allow needle to exist
*/
result = guac_strnstr(haystack, "taco", sizeof(haystack));
CU_ASSERT_EQUAL(result, NULL);
/*
* needle doesn't exist in haystack, needle[0] is in haystack
* multiple times
*/
result = guac_strnstr(haystack, "ahha", sizeof(haystack));
CU_ASSERT_EQUAL(result, NULL);
/* empty needle should return haystack according to API docs */
result = guac_strnstr(haystack, "", sizeof(haystack));
CU_ASSERT_EQUAL(result, haystack);
}

View File

@ -37,6 +37,7 @@
__guac_instruction_handler_mapping __guac_instruction_handler_map[] = { __guac_instruction_handler_mapping __guac_instruction_handler_map[] = {
{"sync", __guac_handle_sync}, {"sync", __guac_handle_sync},
{"touch", __guac_handle_touch},
{"mouse", __guac_handle_mouse}, {"mouse", __guac_handle_mouse},
{"key", __guac_handle_key}, {"key", __guac_handle_key},
{"clipboard", __guac_handle_clipboard}, {"clipboard", __guac_handle_clipboard},
@ -63,6 +64,7 @@ __guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
{"video", __guac_handshake_video_handler}, {"video", __guac_handshake_video_handler},
{"image", __guac_handshake_image_handler}, {"image", __guac_handshake_image_handler},
{"timezone", __guac_handshake_timezone_handler}, {"timezone", __guac_handshake_timezone_handler},
{"name", __guac_handshake_name_handler},
{NULL, NULL} {NULL, NULL}
}; };
@ -119,14 +121,27 @@ int __guac_handle_sync(guac_user* user, int argc, char** argv) {
/* Calculate length of frame, including network and processing lag */ /* Calculate length of frame, including network and processing lag */
frame_duration = current - timestamp; frame_duration = current - timestamp;
/* Update lag statistics if at least one frame has been rendered */ /* Calculate processing lag portion of length of frame */
int frame_processing_lag = 0;
if (user->last_frame_duration != 0) { if (user->last_frame_duration != 0) {
/* Calculate lag using the previous frame as a baseline */ /* Calculate lag using the previous frame as a baseline */
int processing_lag = frame_duration - user->last_frame_duration; frame_processing_lag = frame_duration - user->last_frame_duration;
/* Adjust back to zero if cumulative error leads to a negative /* Adjust back to zero if cumulative error leads to a negative
* value */ * value */
if (frame_processing_lag < 0)
frame_processing_lag = 0;
}
/* Record baseline duration of frame by excluding lag (this is the
* network round-trip time) */
int estimated_rtt = frame_duration - frame_processing_lag;
user->last_frame_duration = estimated_rtt;
/* Calculate cumulative accumulated processing lag relative to server timeline */
int processing_lag = current - user->last_received_timestamp - estimated_rtt;
if (processing_lag < 0) if (processing_lag < 0)
processing_lag = 0; processing_lag = 0;
@ -134,22 +149,32 @@ int __guac_handle_sync(guac_user* user, int argc, char** argv) {
} }
/* Record baseline duration of frame by excluding lag */
user->last_frame_duration = frame_duration - user->processing_lag;
}
/* Log received timestamp and calculated lag (at TRACE level only) */ /* Log received timestamp and calculated lag (at TRACE level only) */
guac_user_log(user, GUAC_LOG_TRACE, guac_user_log(user, GUAC_LOG_TRACE,
"User confirmation of frame %" PRIu64 "ms received " "User confirmation of frame %" PRIu64 "ms received "
"at %" PRIu64 "ms (processing_lag=%ims)", "at %" PRIu64 "ms (processing_lag=%ims, estimated_rtt=%ims)",
timestamp, current, user->processing_lag); timestamp, current, user->processing_lag, user->last_frame_duration);
if (user->sync_handler) if (user->sync_handler)
return user->sync_handler(user, timestamp); return user->sync_handler(user, timestamp);
return 0; return 0;
} }
int __guac_handle_touch(guac_user* user, int argc, char** argv) {
if (user->touch_handler)
return user->touch_handler(
user,
atoi(argv[0]), /* id */
atoi(argv[1]), /* x */
atoi(argv[2]), /* y */
atoi(argv[3]), /* x_radius */
atoi(argv[4]), /* y_radius */
atof(argv[5]), /* angle */
atof(argv[6]) /* force */
);
return 0;
}
int __guac_handle_mouse(guac_user* user, int argc, char** argv) { int __guac_handle_mouse(guac_user* user, int argc, char** argv) {
if (user->mouse_handler) if (user->mouse_handler)
return user->mouse_handler( return user->mouse_handler(
@ -660,6 +685,23 @@ int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
} }
int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) {
/* Free any past value for the user's name */
free((char *) user->info.name);
/* If a value is provided for the name, copy it into guac_user. */
if (argc > 0 && strcmp(argv[0], ""))
user->info.name = (const char*) strdup(argv[0]);
/* No or empty value was provided, so make sure this is NULLed out. */
else
user->info.name = NULL;
return 0;
}
int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) { int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
/* Free any past value */ /* Free any past value */

View File

@ -85,6 +85,13 @@ __guac_instruction_handler __guac_handle_sync;
*/ */
__guac_instruction_handler __guac_handle_mouse; __guac_instruction_handler __guac_handle_mouse;
/**
* Internal initial handler for the touch instruction. When a touch instruction
* is received, this handler will be called. The client's touch handler will
* be invoked if defined.
*/
__guac_instruction_handler __guac_handle_touch;
/** /**
* Internal initial handler for the key instruction. When a key instruction * Internal initial handler for the key instruction. When a key instruction
* is received, this handler will be called. The client's key handler will * is received, this handler will be called. The client's key handler will
@ -211,6 +218,13 @@ __guac_instruction_handler __guac_handshake_video_handler;
*/ */
__guac_instruction_handler __guac_handshake_image_handler; __guac_instruction_handler __guac_handshake_image_handler;
/**
* Internal handler function that is called when the name instruction is
* received during the handshake process, specifying the name of the Guacamole
* user establishing the connection.
*/
__guac_instruction_handler __guac_handshake_name_handler;
/** /**
* Internal handler function that is called when the timezone instruction is * Internal handler function that is called when the timezone instruction is
* received during the handshake process, specifying the timezone of the * received during the handshake process, specifying the timezone of the

View File

@ -296,6 +296,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
user->info.audio_mimetypes = NULL; user->info.audio_mimetypes = NULL;
user->info.image_mimetypes = NULL; user->info.image_mimetypes = NULL;
user->info.video_mimetypes = NULL; user->info.video_mimetypes = NULL;
user->info.name = NULL;
user->info.timezone = NULL; user->info.timezone = NULL;
/* Count number of arguments. */ /* Count number of arguments. */
@ -370,7 +371,8 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
guac_free_mimetypes((char **) user->info.image_mimetypes); guac_free_mimetypes((char **) user->info.image_mimetypes);
guac_free_mimetypes((char **) user->info.video_mimetypes); guac_free_mimetypes((char **) user->info.video_mimetypes);
/* Free timezone info. */ /* Free name and timezone info. */
free((char *) user->info.name);
free((char *) user->info.timezone); free((char *) user->info.timezone);
guac_parser_free(parser); guac_parser_free(parser);

View File

@ -316,6 +316,15 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
} }
int guac_user_supports_msg(guac_user* user) {
if (user == NULL)
return 0;
return (user->info.protocol_version >= GUAC_PROTOCOL_VERSION_1_5_0);
}
int guac_user_supports_required(guac_user* user) { int guac_user_supports_required(guac_user* user) {
if (user == NULL) if (user == NULL)

View File

@ -69,6 +69,9 @@ static void __guac_wol_create_magic_packet(unsigned char packet[],
* @param broadcast_addr * @param broadcast_addr
* The broadcast address to which to send the magic WoL packet. * The broadcast address to which to send the magic WoL packet.
* *
* @param udp_port
* The UDP port to use when sending the WoL packet.
*
* @param packet * @param packet
* The magic WoL packet to send. * The magic WoL packet to send.
* *
@ -76,13 +79,13 @@ static void __guac_wol_create_magic_packet(unsigned char packet[],
* The number of bytes sent, or zero if nothing could be sent. * The number of bytes sent, or zero if nothing could be sent.
*/ */
static ssize_t __guac_wol_send_packet(const char* broadcast_addr, static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
unsigned char packet[]) { const unsigned short udp_port, unsigned char packet[]) {
struct sockaddr_in wol_dest; struct sockaddr_in wol_dest;
int wol_socket; int wol_socket;
/* Determine the IP version, starting with IPv4. */ /* Determine the IP version, starting with IPv4. */
wol_dest.sin_port = htons(GUAC_WOL_PORT); wol_dest.sin_port = htons(udp_port);
wol_dest.sin_family = AF_INET; wol_dest.sin_family = AF_INET;
int retval = inet_pton(wol_dest.sin_family, broadcast_addr, &(wol_dest.sin_addr)); int retval = inet_pton(wol_dest.sin_family, broadcast_addr, &(wol_dest.sin_addr));
@ -165,7 +168,8 @@ static ssize_t __guac_wol_send_packet(const char* broadcast_addr,
} }
int guac_wol_wake(const char* mac_addr, const char* broadcast_addr) { int guac_wol_wake(const char* mac_addr, const char* broadcast_addr,
const unsigned short udp_port) {
unsigned char wol_packet[GUAC_WOL_PACKET_SIZE]; unsigned char wol_packet[GUAC_WOL_PACKET_SIZE];
unsigned int dest_mac[6]; unsigned int dest_mac[6];
@ -183,7 +187,8 @@ int guac_wol_wake(const char* mac_addr, const char* broadcast_addr) {
__guac_wol_create_magic_packet(wol_packet, dest_mac); __guac_wol_create_magic_packet(wol_packet, dest_mac);
/* Send the packet and record bytes sent. */ /* Send the packet and record bytes sent. */
int bytes_sent = __guac_wol_send_packet(broadcast_addr, wol_packet); int bytes_sent = __guac_wol_send_packet(broadcast_addr, udp_port,
wol_packet);
/* Return 0 if bytes were sent, otherwise return an error. */ /* Return 0 if bytes were sent, otherwise return an error. */
if (bytes_sent) if (bytes_sent)

View File

@ -1,22 +0,0 @@
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
AM_FLAGS = -Werror -Wall -pedantic
lib_LTLIBRARIES = libguac-client-ball.la
noinst_HEADERS = ball.h
libguac_client_ball_la_SOURCES = \
ball.c
libguac_client_ball_la_CFLAGS = \
-Werror -Wall -Iinclude \
@LIBGUAC_INCLUDE@
libguac_client_ball_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@
libguac_client_ball_la_LDFLAGS = \
-version-info 0:0:0

View File

@ -1,224 +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 "ball.h"
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <stdlib.h>
const char* TUTORIAL_ARGS[] = { NULL };
void* ball_render_thread(void* arg) {
/* Get data */
guac_client* client = (guac_client*) arg;
ball_client_data* data = (ball_client_data*) client->data;
/* Init time of last frame to current time */
guac_timestamp last_frame = guac_timestamp_current();
/* Update ball position as long as client is running */
while (client->state == GUAC_CLIENT_RUNNING) {
/* Default to 30ms frames */
int frame_duration = 30;
/* Lengthen frame duration if client is lagging */
int processing_lag = guac_client_get_processing_lag(client);
if (processing_lag > frame_duration)
frame_duration = processing_lag;
/* Sleep for duration of frame, then get timestamp */
usleep(frame_duration);
guac_timestamp current = guac_timestamp_current();
/* Calculate change in time */
int delta_t = current - last_frame;
/* Update position */
data->ball_x += data->ball_velocity_x * delta_t / 1000;
data->ball_y += data->ball_velocity_y * delta_t / 1000;
/* Bounce if necessary */
if (data->ball_x < 0) {
data->ball_x = -data->ball_x;
data->ball_velocity_x = -data->ball_velocity_x;
}
else if (data->ball_x >= 1024 - 128) {
data->ball_x = (2 * (1024 - 128)) - data->ball_x;
data->ball_velocity_x = -data->ball_velocity_x;
}
if (data->ball_y < 0) {
data->ball_y = -data->ball_y;
data->ball_velocity_y = -data->ball_velocity_y;
}
else if (data->ball_y >= 768 - 128) {
data->ball_y = (2 * (768 - 128)) - data->ball_y;
data->ball_velocity_y = -data->ball_velocity_y;
}
guac_protocol_send_move(client->socket, data->ball,
GUAC_DEFAULT_LAYER, data->ball_x, data->ball_y, 0);
/* End frame and flush socket */
guac_client_end_frame(client);
guac_socket_flush(client->socket);
/* Update timestamp */
last_frame = current;
}
return NULL;
}
int ball_join_handler(guac_user* user, int argc, char** argv) {
/* Get client associated with user */
guac_client* client = user->client;
/* Get ball layer from client data */
ball_client_data* data = (ball_client_data*) client->data;
guac_layer* ball = data->ball;
/* Get user-specific socket */
guac_socket* socket = user->socket;
/* Send the display size */
guac_protocol_send_size(socket, GUAC_DEFAULT_LAYER, 1024, 768);
/* Create background tile */
guac_layer* texture = guac_client_alloc_buffer(client);
guac_protocol_send_rect(socket, texture, 0, 0, 64, 64);
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
0x88, 0x88, 0x88, 0xFF);
guac_protocol_send_rect(socket, texture, 0, 0, 32, 32);
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
0xDD, 0xDD, 0xDD, 0xFF);
guac_protocol_send_rect(socket, texture, 32, 32, 32, 32);
guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
0xDD, 0xDD, 0xDD, 0xFF);
/* Prepare a curve which covers the entire layer */
guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER,
0, 0, 1024, 768);
/* Fill curve with texture */
guac_protocol_send_lfill(socket,
GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
texture);
/* Set up ball layer */
guac_protocol_send_size(socket, ball, 128, 128);
/* Prepare a circular curve */
guac_protocol_send_arc(socket, data->ball,
64, 64, 62, 0, 6.28, 0);
guac_protocol_send_close(socket, data->ball);
/* Draw a 4-pixel black border */
guac_protocol_send_cstroke(socket,
GUAC_COMP_OVER, data->ball,
GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4,
0x00, 0x00, 0x00, 0xFF);
/* Fill the circle with color */
guac_protocol_send_cfill(socket,
GUAC_COMP_OVER, data->ball,
0x00, 0x80, 0x80, 0x80);
/* Free texture (no longer needed) */
guac_client_free_buffer(client, texture);
/* Mark end-of-frame */
guac_protocol_send_sync(socket, client->last_sent_timestamp);
/* Flush buffer */
guac_socket_flush(socket);
/* User successfully initialized */
return 0;
}
int ball_free_handler(guac_client* client) {
ball_client_data* data = (ball_client_data*) client->data;
/* Wait for render thread to terminate */
pthread_join(data->render_thread, NULL);
/* Free client-level ball layer */
guac_client_free_layer(client, data->ball);
/* Free client-specific data */
free(data);
/* Data successfully freed */
return 0;
}
int guac_client_init(guac_client* client) {
/* Allocate storage for client-specific data */
ball_client_data* data = malloc(sizeof(ball_client_data));
/* Set up client data and handlers */
client->data = data;
/* Allocate layer at the client level */
data->ball = guac_client_alloc_layer(client);
/* This example does not implement any arguments */
client->args = TUTORIAL_ARGS;
/* Client-level handlers */
client->join_handler = ball_join_handler;
client->free_handler = ball_free_handler;
/* Start ball at upper left */
data->ball_x = 0;
data->ball_y = 0;
/* Move at a reasonable pace to the lower right */
data->ball_velocity_x = 200; /* pixels per second */
data->ball_velocity_y = 200; /* pixels per second */
/* Start render thread */
pthread_create(&data->render_thread, NULL, ball_render_thread, client);
return 0;
}

5
src/protocols/kubernetes/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Auto-generated test runner and binary
_generated_runner.c
test_kubernetes

View File

@ -27,6 +27,7 @@ AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac-client-kubernetes.la lib_LTLIBRARIES = libguac-client-kubernetes.la
SUBDIRS = . tests
libguac_client_kubernetes_la_SOURCES = \ libguac_client_kubernetes_la_SOURCES = \
argv.c \ argv.c \

View File

@ -53,8 +53,9 @@ int guac_kubernetes_argv_callback(guac_user* user, const char* mimetype,
} }
/* Update Kubernetes terminal size */ /* Update Kubernetes terminal size */
guac_kubernetes_resize(client, terminal->term_height, guac_kubernetes_resize(client,
terminal->term_width); guac_terminal_get_rows(terminal),
guac_terminal_get_columns(terminal));
return 0; return 0;
@ -67,20 +68,20 @@ void* guac_kubernetes_send_current_argv(guac_user* user, void* data) {
/* Send current color scheme */ /* Send current color scheme */
guac_user_stream_argv(user, user->socket, "text/plain", guac_user_stream_argv(user, user->socket, "text/plain",
GUAC_KUBERNETES_ARGV_COLOR_SCHEME, terminal->color_scheme); GUAC_KUBERNETES_ARGV_COLOR_SCHEME,
guac_terminal_get_color_scheme(terminal));
/* Send current font name */ /* Send current font name */
guac_user_stream_argv(user, user->socket, "text/plain", guac_user_stream_argv(user, user->socket, "text/plain",
GUAC_KUBERNETES_ARGV_FONT_NAME, terminal->font_name); GUAC_KUBERNETES_ARGV_FONT_NAME,
guac_terminal_get_font_name(terminal));
/* Send current font size */ /* Send current font size */
char font_size[64]; char font_size[64];
sprintf(font_size, "%i", terminal->font_size); sprintf(font_size, "%i", guac_terminal_get_font_size(terminal));
guac_user_stream_argv(user, user->socket, "text/plain", guac_user_stream_argv(user, user->socket, "text/plain",
GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size); GUAC_KUBERNETES_ARGV_FONT_SIZE, font_size);
return NULL; return NULL;
} }

View File

@ -19,7 +19,6 @@
#include "argv.h" #include "argv.h"
#include "client.h" #include "client.h"
#include "common/clipboard.h"
#include "kubernetes.h" #include "kubernetes.h"
#include "settings.h" #include "settings.h"
#include "user.h" #include "user.h"
@ -95,12 +94,10 @@ int guac_client_init(guac_client* client) {
guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client)); guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client));
client->data = kubernetes_client; client->data = kubernetes_client;
/* Init clipboard */
kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH);
/* Set handlers */ /* Set handlers */
client->join_handler = guac_kubernetes_user_join_handler; client->join_handler = guac_kubernetes_user_join_handler;
client->free_handler = guac_kubernetes_client_free_handler; client->free_handler = guac_kubernetes_client_free_handler;
client->leave_handler = guac_kubernetes_user_leave_handler;
/* Register handlers for argument values that may be sent after the handshake */ /* Register handlers for argument values that may be sent after the handshake */
guac_argv_register(GUAC_KUBERNETES_ARGV_COLOR_SCHEME, guac_kubernetes_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO); guac_argv_register(GUAC_KUBERNETES_ARGV_COLOR_SCHEME, guac_kubernetes_argv_callback, NULL, GUAC_ARGV_OPTION_ECHO);
@ -132,7 +129,6 @@ int guac_kubernetes_client_free_handler(guac_client* client) {
if (kubernetes_client->settings != NULL) if (kubernetes_client->settings != NULL)
guac_kubernetes_settings_free(kubernetes_client->settings); guac_kubernetes_settings_free(kubernetes_client->settings);
guac_common_clipboard_free(kubernetes_client->clipboard);
free(kubernetes_client); free(kubernetes_client);
return 0; return 0;

View File

@ -22,11 +22,6 @@
#include <guacamole/client.h> #include <guacamole/client.h>
/**
* The maximum number of bytes to allow within the clipboard.
*/
#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144
/** /**
* Static reference to the guac_client associated with the active Kubernetes * Static reference to the guac_client associated with the active Kubernetes
* connection. While libwebsockets provides some means of storing and * connection. While libwebsockets provides some means of storing and

View File

@ -18,8 +18,8 @@
*/ */
#include "clipboard.h" #include "clipboard.h"
#include "common/clipboard.h"
#include "kubernetes.h" #include "kubernetes.h"
#include "terminal/terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
@ -33,7 +33,7 @@ int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream,
(guac_kubernetes_client*) client->data; (guac_kubernetes_client*) client->data;
/* Clear clipboard and prepare for new data */ /* Clear clipboard and prepare for new data */
guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype); guac_terminal_clipboard_reset(kubernetes_client->term, mimetype);
/* Set handlers for clipboard stream */ /* Set handlers for clipboard stream */
stream->blob_handler = guac_kubernetes_clipboard_blob_handler; stream->blob_handler = guac_kubernetes_clipboard_blob_handler;
@ -50,7 +50,7 @@ int guac_kubernetes_clipboard_blob_handler(guac_user* user,
(guac_kubernetes_client*) client->data; (guac_kubernetes_client*) client->data;
/* Append new data */ /* Append new data */
guac_common_clipboard_append(kubernetes_client->clipboard, data, length); guac_terminal_clipboard_append(kubernetes_client->term, data, length);
return 0; return 0;
} }

View File

@ -17,12 +17,12 @@
* under the License. * under the License.
*/ */
#include "common/recording.h"
#include "input.h" #include "input.h"
#include "kubernetes.h" #include "kubernetes.h"
#include "terminal/terminal.h" #include "terminal/terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/recording.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <stdlib.h> #include <stdlib.h>
@ -41,7 +41,7 @@ int guac_kubernetes_user_mouse_handler(guac_user* user,
/* Report mouse position within recording */ /* Report mouse position within recording */
if (kubernetes_client->recording != NULL) if (kubernetes_client->recording != NULL)
guac_common_recording_report_mouse(kubernetes_client->recording, x, y, guac_recording_report_mouse(kubernetes_client->recording, x, y,
mask); mask);
guac_terminal_send_mouse(term, user, x, y, mask); guac_terminal_send_mouse(term, user, x, y, mask);
@ -57,7 +57,7 @@ int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) {
/* Report key state within recording */ /* Report key state within recording */
if (kubernetes_client->recording != NULL) if (kubernetes_client->recording != NULL)
guac_common_recording_report_key(kubernetes_client->recording, guac_recording_report_key(kubernetes_client->recording,
keysym, pressed); keysym, pressed);
/* Skip if terminal not yet ready */ /* Skip if terminal not yet ready */
@ -86,8 +86,9 @@ int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) {
guac_terminal_resize(terminal, width, height); guac_terminal_resize(terminal, width, height);
/* Update Kubernetes terminal window size if connected */ /* Update Kubernetes terminal window size if connected */
guac_kubernetes_resize(client, terminal->term_height, guac_kubernetes_resize(client,
terminal->term_width); guac_terminal_get_rows(terminal),
guac_terminal_get_columns(terminal));
return 0; return 0;
} }

View File

@ -21,7 +21,6 @@
#include "argv.h" #include "argv.h"
#include "client.h" #include "client.h"
#include "common/recording.h"
#include "io.h" #include "io.h"
#include "kubernetes.h" #include "kubernetes.h"
#include "ssl.h" #include "ssl.h"
@ -30,6 +29,7 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/recording.h>
#include <libwebsockets.h> #include <libwebsockets.h>
#include <pthread.h> #include <pthread.h>
@ -213,10 +213,11 @@ void* guac_kubernetes_client_thread(void* data) {
} }
/* Generate endpoint for attachment URL */ /* Generate endpoint for attachment URL */
if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path), if (guac_kubernetes_endpoint_uri(endpoint_path, sizeof(endpoint_path),
settings->kubernetes_namespace, settings->kubernetes_namespace,
settings->kubernetes_pod, settings->kubernetes_pod,
settings->kubernetes_container)) { settings->kubernetes_container,
settings->exec_command)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to generate path for Kubernetes API endpoint: " "Unable to generate path for Kubernetes API endpoint: "
"Resulting path too long"); "Resulting path too long");
@ -228,21 +229,33 @@ void* guac_kubernetes_client_thread(void* data) {
/* Set up screen recording, if requested */ /* Set up screen recording, if requested */
if (settings->recording_path != NULL) { if (settings->recording_path != NULL) {
kubernetes_client->recording = guac_common_recording_create(client, kubernetes_client->recording = guac_recording_create(client,
settings->recording_path, settings->recording_path,
settings->recording_name, settings->recording_name,
settings->create_recording_path, settings->create_recording_path,
!settings->recording_exclude_output, !settings->recording_exclude_output,
!settings->recording_exclude_mouse, !settings->recording_exclude_mouse,
0, /* Touch events not supported */
settings->recording_include_keys); settings->recording_include_keys);
} }
/* Create terminal options with required parameters */
guac_terminal_options* options = guac_terminal_options_create(
settings->width, settings->height, settings->resolution);
/* Set optional parameters */
options->disable_copy = settings->disable_copy;
options->max_scrollback = settings->max_scrollback;
options->font_name = settings->font_name;
options->font_size = settings->font_size;
options->color_scheme = settings->color_scheme;
options->backspace = settings->backspace;
/* Create terminal */ /* Create terminal */
kubernetes_client->term = guac_terminal_create(client, kubernetes_client->term = guac_terminal_create(client, options);
kubernetes_client->clipboard, settings->disable_copy,
settings->max_scrollback, settings->font_name, settings->font_size, /* Free options struct now that it's been used */
settings->resolution, settings->width, settings->height, free(options);
settings->color_scheme, settings->backspace);
/* Fail if terminal init failed */ /* Fail if terminal init failed */
if (kubernetes_client->term == NULL) { if (kubernetes_client->term == NULL) {
@ -356,7 +369,7 @@ fail:
/* Clean up recording, if in progress */ /* Clean up recording, if in progress */
if (kubernetes_client->recording != NULL) if (kubernetes_client->recording != NULL)
guac_common_recording_free(kubernetes_client->recording); guac_recording_free(kubernetes_client->recording);
/* Free WebSocket context if successfully allocated */ /* Free WebSocket context if successfully allocated */
if (kubernetes_client->context != NULL) if (kubernetes_client->context != NULL)
@ -400,8 +413,8 @@ void guac_kubernetes_force_redraw(guac_client* client) {
/* Get current terminal dimensions */ /* Get current terminal dimensions */
guac_terminal* term = kubernetes_client->term; guac_terminal* term = kubernetes_client->term;
int rows = term->term_height; int rows = guac_terminal_get_rows(term);
int columns = term->term_width; int columns = guac_terminal_get_columns(term);
/* Force a redraw by increasing the terminal size by one character in /* Force a redraw by increasing the terminal size by one character in
* each dimension and then resizing it back to normal (the same technique * each dimension and then resizing it back to normal (the same technique

View File

@ -21,12 +21,12 @@
#define GUAC_KUBERNETES_H #define GUAC_KUBERNETES_H
#include "common/clipboard.h" #include "common/clipboard.h"
#include "common/recording.h"
#include "io.h" #include "io.h"
#include "settings.h" #include "settings.h"
#include "terminal/terminal.h" #include "terminal/terminal.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/recording.h>
#include <libwebsockets.h> #include <libwebsockets.h>
#include <pthread.h> #include <pthread.h>
@ -102,11 +102,6 @@ typedef struct guac_kubernetes_client {
*/ */
pthread_t client_thread; pthread_t client_thread;
/**
* The current clipboard contents.
*/
guac_common_clipboard* clipboard;
/** /**
* The terminal which will render all output from the Kubernetes pod. * The terminal which will render all output from the Kubernetes pod.
*/ */
@ -128,7 +123,7 @@ typedef struct guac_kubernetes_client {
* The in-progress session recording, or NULL if no recording is in * The in-progress session recording, or NULL if no recording is in
* progress. * progress.
*/ */
guac_common_recording* recording; guac_recording* recording;
} guac_kubernetes_client; } guac_kubernetes_client;

View File

@ -19,6 +19,7 @@
#include "argv.h" #include "argv.h"
#include "settings.h" #include "settings.h"
#include "terminal/terminal.h"
#include <guacamole/user.h> #include <guacamole/user.h>
@ -31,6 +32,7 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
"namespace", "namespace",
"pod", "pod",
"container", "container",
"exec-command",
"use-ssl", "use-ssl",
"client-cert", "client-cert",
"client-key", "client-key",
@ -86,6 +88,11 @@ enum KUBERNETES_ARGS_IDX {
*/ */
IDX_CONTAINER, IDX_CONTAINER,
/**
* The command used by exec call. If omitted, attach call will be used.
*/
IDX_EXEC_COMMAND,
/** /**
* Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used. * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used.
*/ */
@ -209,8 +216,8 @@ enum KUBERNETES_ARGS_IDX {
IDX_READ_ONLY, IDX_READ_ONLY,
/** /**
* ASCII code, as an integer to use for the backspace key, or 127 * ASCII code, as an integer to use for the backspace key, or
* if not specified. * GUAC_TERMINAL_DEFAULT_BACKSPACE if not specified.
*/ */
IDX_BACKSPACE, IDX_BACKSPACE,
@ -275,6 +282,11 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_CONTAINER, NULL); IDX_CONTAINER, NULL);
/* Read exec command (optional) */
settings->exec_command =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_EXEC_COMMAND, NULL);
/* Parse whether SSL should be used */ /* Parse whether SSL should be used */
settings->use_ssl = settings->use_ssl =
guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
@ -309,22 +321,22 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
/* Read maximum scrollback size */ /* Read maximum scrollback size */
settings->max_scrollback = settings->max_scrollback =
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_SCROLLBACK, GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK); IDX_SCROLLBACK, GUAC_TERMINAL_DEFAULT_MAX_SCROLLBACK);
/* Read font name */ /* Read font name */
settings->font_name = settings->font_name =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_FONT_NAME, GUAC_KUBERNETES_DEFAULT_FONT_NAME); IDX_FONT_NAME, GUAC_TERMINAL_DEFAULT_FONT_NAME);
/* Read font size */ /* Read font size */
settings->font_size = settings->font_size =
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_FONT_SIZE, GUAC_KUBERNETES_DEFAULT_FONT_SIZE); IDX_FONT_SIZE, GUAC_TERMINAL_DEFAULT_FONT_SIZE);
/* Copy requested color scheme */ /* Copy requested color scheme */
settings->color_scheme = settings->color_scheme =
guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_COLOR_SCHEME, ""); IDX_COLOR_SCHEME, GUAC_TERMINAL_DEFAULT_COLOR_SCHEME);
/* Pull width/height/resolution directly from user */ /* Pull width/height/resolution directly from user */
settings->width = user->info.optimal_width; settings->width = user->info.optimal_width;
@ -379,7 +391,7 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
/* Parse backspace key code */ /* Parse backspace key code */
settings->backspace = settings->backspace =
guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
IDX_BACKSPACE, 127); IDX_BACKSPACE, GUAC_TERMINAL_DEFAULT_BACKSPACE);
/* Parse clipboard copy disable flag */ /* Parse clipboard copy disable flag */
settings->disable_copy = settings->disable_copy =
@ -406,6 +418,9 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) {
free(settings->kubernetes_pod); free(settings->kubernetes_pod);
free(settings->kubernetes_container); free(settings->kubernetes_container);
/* Free Kubernetes exec command */
free(settings->exec_command);
/* Free SSL/TLS details */ /* Free SSL/TLS details */
free(settings->client_cert); free(settings->client_cert);
free(settings->client_key); free(settings->client_key);

View File

@ -24,17 +24,6 @@
#include <stdbool.h> #include <stdbool.h>
/**
* The name of the font to use for the terminal if no name is specified.
*/
#define GUAC_KUBERNETES_DEFAULT_FONT_NAME "monospace"
/**
* The size of the font to use for the terminal if no font size is specified,
* in points.
*/
#define GUAC_KUBERNETES_DEFAULT_FONT_SIZE 12
/** /**
* The port to connect to when initiating any Kubernetes connection, if no * The port to connect to when initiating any Kubernetes connection, if no
* other port is specified. * other port is specified.
@ -57,11 +46,6 @@
*/ */
#define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording" #define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording"
/**
* The default maximum scrollback size in rows.
*/
#define GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK 1000
/** /**
* Settings for the Kubernetes connection. The values for this structure are * Settings for the Kubernetes connection. The values for this structure are
* parsed from the arguments given during the Guacamole protocol handshake * parsed from the arguments given during the Guacamole protocol handshake
@ -97,6 +81,12 @@ typedef struct guac_kubernetes_settings {
*/ */
char* kubernetes_container; char* kubernetes_container;
/**
* The command to generate api endpoint for call exec.
* If omitted call attach will be used.
*/
char* exec_command;
/** /**
* Whether SSL/TLS should be used. * Whether SSL/TLS should be used.
*/ */

View File

@ -0,0 +1,66 @@
#
# 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 Kubernetes support
#
check_PROGRAMS = test_kubernetes
TESTS = $(check_PROGRAMS)
test_kubernetes_SOURCES = \
url/append.c \
url/escape.c
test_kubernetes_CFLAGS = \
-Werror -Wall -pedantic \
@LIBGUAC_CLIENT_KUBERNETES_INCLUDE@ \
@LIBGUAC_INCLUDE@
test_kubernetes_LDADD = \
@CUNIT_LIBS@ \
@LIBGUAC_CLIENT_KUBERNETES_LTLIB@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_kubernetes_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_kubernetes_SOURCES) > $@
nodist_test_kubernetes_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

View File

@ -0,0 +1,74 @@
/*
* 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 "url.h"
#include <CUnit/CUnit.h>
#include <stdio.h>
#include <stdlib.h>
/**
* Verifies that guac_kubernetes_append_endpoint_param() correctly appends
* parameters to URLs that do not already have a query string.
*/
void test_url__append_no_query() {
char url[256] = "http://example.net";
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, sizeof(url), "foo", "100% test value"));
CU_ASSERT_STRING_EQUAL(url, "http://example.net?foo=100%25%20test%20value");
}
/**
* Verifies that guac_kubernetes_append_endpoint_param() correctly appends
* parameters to URLs that already have a query string.
*/
void test_url__append_existing_query() {
char url[256] = "http://example.net?foo=test%20value";
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, sizeof(url), "foo2", "yet&another/test\\value"));
CU_ASSERT_STRING_EQUAL(url, "http://example.net?foo=test%20value&foo2=yet%26another%2Ftest%5Cvalue");
}
/**
* Verifies that guac_kubernetes_append_endpoint_param() refuses to overflow
* the bounds of the provided buffer.
*/
void test_url__append_bounds() {
char url[256];
/* Appending "?a=1" to the 18-character string "http://example.net" should
* fail for all buffer sizes with 22 bytes or less, with a 22-byte buffer
* lacking space for the null terminator */
for (int length = 18; length <= 22; length++) {
strcpy(url, "http://example.net");
printf("Testing buffer with length %i ...\n", length);
CU_ASSERT(guac_kubernetes_append_endpoint_param(url, length, "a", "1"));
}
/* A 23-byte buffer should be sufficient */
strcpy(url, "http://example.net");
CU_ASSERT(!guac_kubernetes_append_endpoint_param(url, 23, "a", "1"));
}

View File

@ -0,0 +1,72 @@
/*
* 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 "url.h"
#include <CUnit/CUnit.h>
#include <stdlib.h>
/**
* Verifies that guac_kubernetes_escape_url_component() correctly escapes
* characters that would otherwise have special meaning within URLs.
*/
void test_url__escape_special() {
char value[256];
CU_ASSERT(!guac_kubernetes_escape_url_component(value, sizeof(value), "?foo%20bar\\1/2&3=4"));
CU_ASSERT_STRING_EQUAL(value, "%3Ffoo%2520bar%5C1%2F2%263%3D4");
}
/**
* Verifies that guac_kubernetes_escape_url_component() leaves strings
* untouched if they contain no characters requiring escaping.
*/
void test_url__escape_nospecial() {
char value[256];
CU_ASSERT(!guac_kubernetes_escape_url_component(value, sizeof(value), "potato"));
CU_ASSERT_STRING_EQUAL(value, "potato");
}
/**
* Verifies that guac_kubernetes_escape_url_component() refuses to overflow the
* bounds of the provided buffer.
*/
void test_url__escape_bounds() {
char value[256];
/* Escaping "?potato" (or "potato?") should fail for all buffer sizes with
* 9 bytes or less, with a 9-byte buffer lacking space for the null
* terminator */
for (int length = 0; length <= 9; length++) {
printf("Testing buffer with length %i ...\n", length);
CU_ASSERT(guac_kubernetes_escape_url_component(value, length, "?potato"));
CU_ASSERT(guac_kubernetes_escape_url_component(value, length, "potato?"));
}
/* A 10-byte buffer should be sufficient */
CU_ASSERT(!guac_kubernetes_escape_url_component(value, 10, "?potato"));
}

View File

@ -89,15 +89,60 @@ int guac_kubernetes_escape_url_component(char* output, int length,
} }
int guac_kubernetes_endpoint_attach(char* buffer, int length, int guac_kubernetes_append_endpoint_param(char* buffer, int length,
const char* kubernetes_namespace, const char* kubernetes_pod, const char* param_name, const char* param_value) {
const char* kubernetes_container) {
int written; char escaped_param_value[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
/* Escape value */
if (guac_kubernetes_escape_url_component(escaped_param_value,
sizeof(escaped_param_value), param_value))
return 1;
char* str = buffer;
int str_len = 0;
int qmark = 0;
while (*str != '\0') {
/* Look for a question mark */
if (*str=='?') qmark = 1;
/* Compute the buffer string length */
str_len++;
/* Verify the buffer null terminated */
if (str_len >= length) return 1;
/* Next character */
str++;
}
/* Determine the parameter delimiter */
char delimiter = '?';
if (qmark) delimiter = '&';
/* Advance to end of buffer, where the new parameter and delimiter need to
* be appended */
buffer += str_len;
length -= str_len;
/* Write the parameter and delimiter to the buffer */
int written = snprintf(buffer, length, "%c%s=%s", delimiter,
param_name, escaped_param_value);
/* The parameter was successfully added if it was written to the given
* buffer without truncation */
return (written < 0 || written >= length);
}
int guac_kubernetes_endpoint_uri(char* buffer, int length,
const char* kubernetes_namespace, const char* kubernetes_pod,
const char* kubernetes_container, const char* exec_command) {
char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
char escaped_container[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
/* Escape Kubernetes namespace */ /* Escape Kubernetes namespace */
if (guac_kubernetes_escape_url_component(escaped_namespace, if (guac_kubernetes_escape_url_component(escaped_namespace,
@ -109,29 +154,38 @@ int guac_kubernetes_endpoint_attach(char* buffer, int length,
sizeof(escaped_pod), kubernetes_pod)) sizeof(escaped_pod), kubernetes_pod))
return 1; return 1;
/* Generate attachment endpoint URL */ /* Determine the call type */
if (kubernetes_container != NULL) { char* call = "attach";
if (exec_command != NULL)
call = "exec";
/* Escape container name */ int written;
if (guac_kubernetes_escape_url_component(escaped_container,
sizeof(escaped_container), kubernetes_container)) /* Generate the endpoint path and write to the buffer */
written = snprintf(buffer, length,
"/api/v1/namespaces/%s/pods/%s/%s", escaped_namespace, escaped_pod, call);
/* Operation successful if the endpoint path was written to the given
* buffer without truncation */
if (written < 0 || written >= length)
return 1; return 1;
written = snprintf(buffer, length, /* Append exec command parameter */
"/api/v1/namespaces/%s/pods/%s/attach" if (exec_command != NULL) {
"?container=%s&stdin=true&stdout=true&tty=true", if (guac_kubernetes_append_endpoint_param(buffer,
escaped_namespace, escaped_pod, escaped_container); length, "command", exec_command))
} return 1;
else {
written = snprintf(buffer, length,
"/api/v1/namespaces/%s/pods/%s/attach"
"?stdin=true&stdout=true&tty=true",
escaped_namespace, escaped_pod);
} }
/* Endpoint URL was successfully generated if it was written to the given /* Append kubernetes container parameter */
* buffer without truncation */ if (kubernetes_container != NULL) {
return !(written < length - 1); if (guac_kubernetes_append_endpoint_param(buffer,
length, "container", kubernetes_container))
return 1;
}
/* Append stdin, stdout and tty parameters */
return (guac_kubernetes_append_endpoint_param(buffer, length, "stdin", "true"))
|| (guac_kubernetes_append_endpoint_param(buffer, length, "stdout", "true"))
|| (guac_kubernetes_append_endpoint_param(buffer, length, "tty", "true"));
} }

View File

@ -49,6 +49,35 @@
int guac_kubernetes_escape_url_component(char* output, int length, int guac_kubernetes_escape_url_component(char* output, int length,
const char* str); const char* str);
/**
* Appends the given query parameter and value to the given buffer. If the
* buffer does not already contain the '?' character denoting the start of the
* query string, it will be added. If the buffer already contains a query
* string, a '&' character will be added before the new parameter. The
* parameter value will automatically be URL-escaped as necessary.
*
* @param buffer
* The buffer which should receive the parameter. It could contain the endpoint path.
* The parameter will be written to the end of the buffer.
*
* @param length
* The number of bytes available in the given buffer.
*
* @param param_name
* The name of the parameter. If the parameter name contains characters
* with special meaning to URLs, it must already be URL-escaped.
*
* @param param_value
* The value of the parameter.
*
* @return
* Zero if the parameter was successfully attached to the buffer,
* non-zero if insufficient space exists within the buffer or
* buffer not null terminated.
*/
int guac_kubernetes_append_endpoint_param(char* buffer, int length,
const char* param_name, const char* param_value);
/** /**
* Generates the full path to the Kubernetes API endpoint which handles * Generates the full path to the Kubernetes API endpoint which handles
* attaching to running containers within specific pods. Values within the path * attaching to running containers within specific pods. Values within the path
@ -73,13 +102,17 @@ int guac_kubernetes_escape_url_component(char* output, int length,
* The name of the container to attach to, or NULL to arbitrarily attach * The name of the container to attach to, or NULL to arbitrarily attach
* to the first container in the pod. * to the first container in the pod.
* *
* @param exec_command
* The command used to run a new process and attach to it,
* instead of the main container process.
*
* @return * @return
* Zero if the endpoint path was successfully written to the provided * Zero if the endpoint path was successfully written to the provided
* buffer, non-zero if insufficient space exists within the buffer. * buffer, non-zero if insufficient space exists within the buffer.
*/ */
int guac_kubernetes_endpoint_attach(char* buffer, int length, int guac_kubernetes_endpoint_uri(char* buffer, int length,
const char* kubernetes_namespace, const char* kubernetes_pod, const char* kubernetes_namespace, const char* kubernetes_pod,
const char* kubernetes_container); const char* kubernetes_container, const char* exec_command);
#endif #endif

View File

@ -109,8 +109,8 @@ int guac_kubernetes_user_leave_handler(guac_user* user) {
guac_kubernetes_client* kubernetes_client = guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) user->client->data; (guac_kubernetes_client*) user->client->data;
/* Update shared cursor state */ /* Remove the user from the terminal */
guac_common_cursor_remove_user(kubernetes_client->term->cursor, user); guac_terminal_remove_user(kubernetes_client->term, user);
/* Free settings if not owner (owner settings will be freed with client) */ /* Free settings if not owner (owner settings will be freed with client) */
if (!user->owner) { if (!user->owner) {

View File

@ -56,6 +56,8 @@ libguac_client_rdp_la_SOURCES = \
channels/rdpdr/rdpdr-messages.c \ channels/rdpdr/rdpdr-messages.c \
channels/rdpdr/rdpdr-printer.c \ channels/rdpdr/rdpdr-printer.c \
channels/rdpdr/rdpdr.c \ channels/rdpdr/rdpdr.c \
channels/rdpei.c \
channels/rdpgfx.c \
channels/rdpsnd/rdpsnd-messages.c \ channels/rdpsnd/rdpsnd-messages.c \
channels/rdpsnd/rdpsnd.c \ channels/rdpsnd/rdpsnd.c \
client.c \ client.c \
@ -101,6 +103,8 @@ noinst_HEADERS = \
channels/rdpdr/rdpdr-messages.h \ channels/rdpdr/rdpdr-messages.h \
channels/rdpdr/rdpdr-printer.h \ channels/rdpdr/rdpdr-printer.h \
channels/rdpdr/rdpdr.h \ channels/rdpdr/rdpdr.h \
channels/rdpei.h \
channels/rdpgfx.h \
channels/rdpsnd/rdpsnd-messages.h \ channels/rdpsnd/rdpsnd-messages.h \
channels/rdpsnd/rdpsnd.h \ channels/rdpsnd/rdpsnd.h \
client.h \ client.h \
@ -226,6 +230,7 @@ BUILT_SOURCES = \
rdp_keymaps = \ rdp_keymaps = \
$(srcdir)/keymaps/base.keymap \ $(srcdir)/keymaps/base.keymap \
$(srcdir)/keymaps/failsafe.keymap \ $(srcdir)/keymaps/failsafe.keymap \
$(srcdir)/keymaps/cs-cz-qwertz.keymap \
$(srcdir)/keymaps/de_de_qwertz.keymap \ $(srcdir)/keymaps/de_de_qwertz.keymap \
$(srcdir)/keymaps/de_ch_qwertz.keymap \ $(srcdir)/keymaps/de_ch_qwertz.keymap \
$(srcdir)/keymaps/en_gb_qwerty.keymap \ $(srcdir)/keymaps/en_gb_qwerty.keymap \
@ -233,11 +238,14 @@ rdp_keymaps = \
$(srcdir)/keymaps/es_es_qwerty.keymap \ $(srcdir)/keymaps/es_es_qwerty.keymap \
$(srcdir)/keymaps/es_latam_qwerty.keymap \ $(srcdir)/keymaps/es_latam_qwerty.keymap \
$(srcdir)/keymaps/fr_be_azerty.keymap \ $(srcdir)/keymaps/fr_be_azerty.keymap \
$(srcdir)/keymaps/fr_ca_qwerty.keymap \
$(srcdir)/keymaps/fr_ch_qwertz.keymap \ $(srcdir)/keymaps/fr_ch_qwertz.keymap \
$(srcdir)/keymaps/fr_fr_azerty.keymap \ $(srcdir)/keymaps/fr_fr_azerty.keymap \
$(srcdir)/keymaps/hu_hu_qwertz.keymap \ $(srcdir)/keymaps/hu_hu_qwertz.keymap \
$(srcdir)/keymaps/it_it_qwerty.keymap \ $(srcdir)/keymaps/it_it_qwerty.keymap \
$(srcdir)/keymaps/ja_jp_qwerty.keymap \ $(srcdir)/keymaps/ja_jp_qwerty.keymap \
$(srcdir)/keymaps/no_no_qwerty.keymap \
$(srcdir)/keymaps/pl_pl_qwerty.keymap \
$(srcdir)/keymaps/pt_br_qwerty.keymap \ $(srcdir)/keymaps/pt_br_qwerty.keymap \
$(srcdir)/keymaps/sv_se_qwerty.keymap \ $(srcdir)/keymaps/sv_se_qwerty.keymap \
$(srcdir)/keymaps/da_dk_qwerty.keymap \ $(srcdir)/keymaps/da_dk_qwerty.keymap \

View File

@ -149,7 +149,7 @@ BOOL guac_rdp_bitmap_setsurface(rdpContext* context, rdpBitmap* bitmap, BOOL pri
else { else {
/* Make sure that the recieved bitmap is not NULL before processing */ /* Make sure that the received bitmap is not NULL before processing */
if (bitmap == NULL) { if (bitmap == NULL) {
guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in bitmap_setsurface instruction."); guac_client_log(client, GUAC_LOG_INFO, "NULL bitmap found in bitmap_setsurface instruction.");
return TRUE; return TRUE;

View File

@ -24,16 +24,253 @@
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/timestamp.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <pthread.h> #include <pthread.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
/**
* The number of nanoseconds in one second.
*/
#define NANOS_PER_SECOND 1000000000L
/**
* Returns whether the given timespec represents a point in time in the future
* relative to the current system time.
*
* @param ts
* The timespec to test.
*
* @return
* Non-zero if the given timespec is in the future relative to the current
* system time, zero otherwise.
*/
static int guac_rdp_audio_buffer_is_future(const struct timespec* ts) {
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
if (now.tv_sec != ts->tv_sec)
return now.tv_sec < ts->tv_sec;
return now.tv_nsec < ts->tv_nsec;
}
/**
* Returns whether the given audio buffer may be flushed. An audio buffer may
* be flushed if the audio buffer is not currently being freed, at least one
* packet of audio data is available within the buffer, and flushing the next
* packet of audio data now would not violate scheduling/throttling rules for
* outbound audio data.
*
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
* invoking this function.
*
* @param audio_buffer
* The guac_rdp_audio_buffer to test.
*
* @return
* Non-zero if the given audio buffer may be flushed, zero if the audio
* buffer cannot be flushed for any reason.
*/
static int guac_rdp_audio_buffer_may_flush(guac_rdp_audio_buffer* audio_buffer) {
return !audio_buffer->stopping
&& audio_buffer->packet_size > 0
&& audio_buffer->bytes_written >= audio_buffer->packet_size
&& !guac_rdp_audio_buffer_is_future(&audio_buffer->next_flush);
}
/**
* Returns the duration of the given quantity of audio data in milliseconds.
*
* @param format
* The format of the audio data in question.
*
* @param length
* The number of bytes of audio data.
*
* @return
* The duration of the audio data in milliseconds.
*/
static int guac_rdp_audio_buffer_duration(const guac_rdp_audio_format* format, int length) {
return length * 1000 / format->rate / format->bps / format->channels;
}
/**
* Returns the number of bytes required to store audio data in the given format
* covering the given length of time.
*
* @param format
* The format of the audio data in question.
*
* @param duration
* The duration of the audio data in milliseconds.
*
* @return
* The number of bytes required to store audio data in the given format
* covering the given length of time.
*/
static int guac_rdp_audio_buffer_length(const guac_rdp_audio_format* format, int duration) {
return duration * format->rate * format->bps * format->channels / 1000;
}
/**
* Notifies the given guac_rdp_audio_buffer that a single packet of audio data
* has just been flushed, updating the scheduled time of the next flush. The
* timing of the next flush will be set such that the overall real time audio
* generation rate is not exceeded, but will be adjusted as necessary to
* compensate for latency induced by differences in audio packet size/duration.
*
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
* invoking this function.
*
* @param audio_buffer
* The guac_rdp_audio_buffer to update.
*/
static void guac_rdp_audio_buffer_schedule_flush(guac_rdp_audio_buffer* audio_buffer) {
struct timespec next_flush;
clock_gettime(CLOCK_REALTIME, &next_flush);
/* Calculate the point in time that the next flush would be allowed,
* assuming that the remote server processes data no faster than
* real time */
uint64_t delta_nsecs = audio_buffer->packet_size * NANOS_PER_SECOND
/ audio_buffer->out_format.rate
/ audio_buffer->out_format.bps
/ audio_buffer->out_format.channels;
/* Amortize the additional latency from packet data buffered beyond the
* desired packet size over each remaining packet such that we gradually
* approach an effective additional latency of 0 */
int packets_remaining = audio_buffer->bytes_written / audio_buffer->packet_size;
if (packets_remaining > 1)
delta_nsecs = delta_nsecs * (packets_remaining - 1) / packets_remaining;
uint64_t nsecs = next_flush.tv_nsec + delta_nsecs;
next_flush.tv_sec += nsecs / NANOS_PER_SECOND;
next_flush.tv_nsec = nsecs % NANOS_PER_SECOND;
audio_buffer->next_flush = next_flush;
}
/**
* Waits for additional data to be available for flush within the given audio
* buffer. If data is available but insufficient time has elapsed since the
* last flush, this function may block until sufficient time has elapsed. If
* the state of the audio buffer changes in any way while waiting for
* additional data, or if the audio buffer is being freed, this function will
* return immediately.
*
* It is the responsibility of the caller to check the state of the audio
* buffer after this function returns to verify whether the desired state
* change has occurred and re-invoke the function if needed.
*
* @param audio_buffer
* The guac_rdp_audio_buffer to wait for.
*/
static void guac_rdp_audio_buffer_wait(guac_rdp_audio_buffer* audio_buffer) {
pthread_mutex_lock(&(audio_buffer->lock));
/* Do not wait if audio_buffer is already closed */
if (!audio_buffer->stopping) {
/* If sufficient data exists for a flush, wait until next possible
* flush OR until some other state change occurs (such as the buffer
* being closed) */
if (audio_buffer->bytes_written && audio_buffer->bytes_written >= audio_buffer->packet_size)
pthread_cond_timedwait(&audio_buffer->modified, &audio_buffer->lock,
&audio_buffer->next_flush);
/* If sufficient data DOES NOT exist, we should wait indefinitely */
else
pthread_cond_wait(&audio_buffer->modified, &audio_buffer->lock);
}
pthread_mutex_unlock(&(audio_buffer->lock));
}
/**
* Regularly and automatically flushes audio packets by invoking the flush
* handler of the associated audio buffer. Packets are scheduled automatically
* to avoid potentially exceeding the processing and buffering capabilities of
* the software running within the RDP server. Once started, this thread runs
* until the associated audio buffer is freed via guac_rdp_audio_buffer_free().
*
* @param data
* A pointer to the guac_rdp_audio_buffer that should be flushed.
*
* @return
* Always NULL.
*/
static void* guac_rdp_audio_buffer_flush_thread(void* data) {
guac_rdp_audio_buffer* audio_buffer = (guac_rdp_audio_buffer*) data;
while (!audio_buffer->stopping) {
pthread_mutex_lock(&(audio_buffer->lock));
if (!guac_rdp_audio_buffer_may_flush(audio_buffer)) {
pthread_mutex_unlock(&(audio_buffer->lock));
/* Wait for additional data if we aren't able to flush */
guac_rdp_audio_buffer_wait(audio_buffer);
/* We might still not be able to flush (buffer might be closed,
* some other state change might occur that isn't receipt of data,
* data might be received but not enough for a flush, etc.) */
continue;
}
guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Current audio input latency: %i ms (%i bytes waiting in buffer)",
guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->bytes_written),
audio_buffer->bytes_written);
/* Only actually invoke if defined */
if (audio_buffer->flush_handler) {
guac_rdp_audio_buffer_schedule_flush(audio_buffer);
audio_buffer->flush_handler(audio_buffer,
audio_buffer->packet_size);
}
/* Shift buffer back by one packet */
audio_buffer->bytes_written -= audio_buffer->packet_size;
memmove(audio_buffer->packet, audio_buffer->packet + audio_buffer->packet_size, audio_buffer->bytes_written);
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock));
}
return NULL;
}
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) {
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer)); guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
pthread_mutex_init(&(buffer->lock), NULL); pthread_mutex_init(&(buffer->lock), NULL);
pthread_cond_init(&(buffer->modified), NULL);
buffer->client = client;
/* Begin automated, throttled flush of future data */
pthread_create(&(buffer->flush_thread), NULL,
guac_rdp_audio_buffer_flush_thread, (void*) buffer);
return buffer; return buffer;
} }
@ -45,6 +282,9 @@ guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
* instruction nor been associated with an "ack" having an error code), and is * instruction nor been associated with an "ack" having an error code), and is
* associated with an active RDP AUDIO_INPUT channel. * associated with an active RDP AUDIO_INPUT channel.
* *
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
* invoking this function.
*
* @param audio_buffer * @param audio_buffer
* The audio buffer associated with the guac_stream for which the "ack" * The audio buffer associated with the guac_stream for which the "ack"
* instruction should be sent, if any. If there is no associated * instruction should be sent, if any. If there is no associated
@ -97,6 +337,7 @@ void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer,
audio_buffer->in_format.rate, audio_buffer->in_format.rate,
audio_buffer->in_format.bps); audio_buffer->in_format.bps);
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
} }
@ -111,6 +352,7 @@ void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer,
audio_buffer->out_format.channels = channels; audio_buffer->out_format.channels = channels;
audio_buffer->out_format.bps = bps; audio_buffer->out_format.bps = bps;
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
} }
@ -131,14 +373,30 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
* audio_buffer->out_format.channels * audio_buffer->out_format.channels
* audio_buffer->out_format.bps; * audio_buffer->out_format.bps;
/* Ensure outbound buffer includes enough space for at least 250ms of
* audio */
int ideal_size = guac_rdp_audio_buffer_length(&audio_buffer->out_format,
GUAC_RDP_AUDIO_BUFFER_MIN_DURATION);
/* Round up to nearest whole packet */
int ideal_packets = (ideal_size + audio_buffer->packet_size - 1) / audio_buffer->packet_size;
/* Allocate new buffer */ /* Allocate new buffer */
free(audio_buffer->packet); audio_buffer->packet_buffer_size = ideal_packets * audio_buffer->packet_size;
audio_buffer->packet = malloc(audio_buffer->packet_size); audio_buffer->packet = malloc(audio_buffer->packet_buffer_size);
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Output buffer for "
"audio input is %i bytes (up to %i ms).", audio_buffer->packet_buffer_size,
guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->packet_buffer_size));
/* Next flush can occur as soon as data is received */
clock_gettime(CLOCK_REALTIME, &audio_buffer->next_flush);
/* Acknowledge stream creation (if stream is ready to receive) */ /* Acknowledge stream creation (if stream is ready to receive) */
guac_rdp_audio_buffer_ack(audio_buffer, guac_rdp_audio_buffer_ack(audio_buffer,
"OK", GUAC_PROTOCOL_STATUS_SUCCESS); "OK", GUAC_PROTOCOL_STATUS_SUCCESS);
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
} }
@ -151,6 +409,9 @@ void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
* input and output formats, the number of bytes sent thus far, and the * input and output formats, the number of bytes sent thus far, and the
* number of bytes received (excluding the contents of the buffer). * number of bytes received (excluding the contents of the buffer).
* *
* IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when
* invoking this function.
*
* @param audio_buffer * @param audio_buffer
* The audio buffer dictating the format of the given data buffer, as * The audio buffer dictating the format of the given data buffer, as
* well as the offset from which the sample should be read. * well as the offset from which the sample should be read.
@ -237,12 +498,26 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
pthread_mutex_lock(&(audio_buffer->lock)); pthread_mutex_lock(&(audio_buffer->lock));
guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Received %i bytes (%i ms) of audio data",
length, guac_rdp_audio_buffer_duration(&audio_buffer->in_format, length));
/* Ignore packet if there is no buffer */ /* Ignore packet if there is no buffer */
if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) { if (audio_buffer->packet_buffer_size == 0 || audio_buffer->packet == NULL) {
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Dropped %i "
"bytes of received audio data (buffer full or closed).", length);
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
return; return;
} }
/* Truncate received samples if exceeding size of buffer */
int available = audio_buffer->packet_buffer_size - audio_buffer->bytes_written;
if (length > available) {
guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Truncating %i "
"bytes of received audio data to %i bytes (insufficient space "
"in buffer).", length, available);
length = available;
}
int out_bps = audio_buffer->out_format.bps; int out_bps = audio_buffer->out_format.bps;
/* Continuously write packets until no data remains */ /* Continuously write packets until no data remains */
@ -265,24 +540,12 @@ void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
audio_buffer->bytes_written += out_bps; audio_buffer->bytes_written += out_bps;
audio_buffer->total_bytes_sent += out_bps; audio_buffer->total_bytes_sent += out_bps;
/* Invoke flush handler if full */
if (audio_buffer->bytes_written == audio_buffer->packet_size) {
/* Only actually invoke if defined */
if (audio_buffer->flush_handler)
audio_buffer->flush_handler(audio_buffer->packet,
audio_buffer->bytes_written, audio_buffer->data);
/* Reset buffer in all cases */
audio_buffer->bytes_written = 0;
}
} /* end packet write loop */ } /* end packet write loop */
/* Track current position in audio stream */ /* Track current position in audio stream */
audio_buffer->total_bytes_received += length; audio_buffer->total_bytes_received += length;
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
} }
@ -291,6 +554,12 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
pthread_mutex_lock(&(audio_buffer->lock)); pthread_mutex_lock(&(audio_buffer->lock));
/* Ignore if stream is already closed */
if (audio_buffer->stream == NULL) {
pthread_mutex_unlock(&(audio_buffer->lock));
return;
}
/* The stream is now closed */ /* The stream is now closed */
guac_rdp_audio_buffer_ack(audio_buffer, guac_rdp_audio_buffer_ack(audio_buffer,
"CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED); "CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
@ -302,6 +571,7 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
/* Reset buffer state */ /* Reset buffer state */
audio_buffer->bytes_written = 0; audio_buffer->bytes_written = 0;
audio_buffer->packet_size = 0; audio_buffer->packet_size = 0;
audio_buffer->packet_buffer_size = 0;
audio_buffer->flush_handler = NULL; audio_buffer->flush_handler = NULL;
/* Reset I/O counters */ /* Reset I/O counters */
@ -312,13 +582,27 @@ void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
free(audio_buffer->packet); free(audio_buffer->packet);
audio_buffer->packet = NULL; audio_buffer->packet = NULL;
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock)); pthread_mutex_unlock(&(audio_buffer->lock));
} }
void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) { void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) {
guac_rdp_audio_buffer_end(audio_buffer);
/* Signal termination of flush thread */
pthread_mutex_lock(&(audio_buffer->lock));
audio_buffer->stopping = 1;
pthread_cond_broadcast(&(audio_buffer->modified));
pthread_mutex_unlock(&(audio_buffer->lock));
/* Clean up flush thread */
pthread_join(audio_buffer->flush_thread, NULL);
pthread_mutex_destroy(&(audio_buffer->lock)); pthread_mutex_destroy(&(audio_buffer->lock));
free(audio_buffer->packet); pthread_cond_destroy(&(audio_buffer->modified));
free(audio_buffer); free(audio_buffer);
} }

View File

@ -23,25 +23,38 @@
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <pthread.h> #include <pthread.h>
#include <time.h>
/**
* The minimum number of milliseconds of audio data that each instance of
* guac_rdp_audio_buffer should provide storage for. This buffer space does not
* induce additional latency, but is required to compensate for latency and
* functions as an upper limit on the amount of latency the buffer will
* compensate for.
*/
#define GUAC_RDP_AUDIO_BUFFER_MIN_DURATION 250
/**
* A buffer of arbitrary audio data. Received audio data can be written to this
* buffer, and will automatically be flushed via a given handler once the
* internal buffer reaches capacity.
*/
typedef struct guac_rdp_audio_buffer guac_rdp_audio_buffer;
/** /**
* Handler which is invoked when a guac_rdp_audio_buffer's internal packet * Handler which is invoked when a guac_rdp_audio_buffer's internal packet
* buffer has reached capacity and must be flushed. * buffer has reached capacity and must be flushed.
* *
* @param buffer * @param audio_buffer
* The buffer which needs to be flushed as an audio packet. * The guac_rdp_audio_buffer that has reached capacity and needs to be
* flushed.
* *
* @param length * @param length
* The number of bytes stored within the buffer. This is guaranteed to be * The number of bytes stored within the buffer. This is guaranteed to be
* identical to the packet_size value specified when the audio buffer was * identical to the packet_size value specified when the audio buffer was
* initialized. * initialized.
*
* @param data
* The arbitrary data pointer provided when the audio buffer was
* initialized.
*/ */
typedef void guac_rdp_audio_buffer_flush_handler(char* buffer, int length, typedef void guac_rdp_audio_buffer_flush_handler(guac_rdp_audio_buffer* audio_buffer, int length);
void* data);
/** /**
* A description of an arbitrary PCM audio format. * A description of an arbitrary PCM audio format.
@ -66,28 +79,38 @@ typedef struct guac_rdp_audio_format {
} guac_rdp_audio_format; } guac_rdp_audio_format;
/** struct guac_rdp_audio_buffer {
* A buffer of arbitrary audio data. Received audio data can be written to this
* buffer, and will automatically be flushed via a given handler once the
* internal buffer reaches capacity.
*/
typedef struct guac_rdp_audio_buffer {
/** /**
* Lock which is acquired/released to ensure accesses to the audio buffer * Lock which is acquired/released to ensure accesses to the audio buffer
* are atomic. * are atomic. This lock is also bound to the modified pthread_cond_t,
* which should be signalled whenever the audio buffer structure has been
* modified.
*/ */
pthread_mutex_t lock; pthread_mutex_t lock;
/**
* Condition which is signalled when any part of the audio buffer structure
* has been modified.
*/
pthread_cond_t modified;
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/** /**
* The user from which this audio buffer will receive data. If no user has * The user from which this audio buffer will receive data. If no user has
* yet opened an associated audio stream, this will be NULL. * yet opened an associated audio stream, or if the audio stream has been
* closed, this will be NULL.
*/ */
guac_user* user; guac_user* user;
/** /**
* The stream from which this audio buffer will receive data. If no user * The stream from which this audio buffer will receive data. If no user
* has yet opened an associated audio stream, this will be NULL. * has yet opened an associated audio stream, or if the audio stream has
* been closed, this will be NULL.
*/ */
guac_stream* stream; guac_stream* stream;
@ -111,6 +134,11 @@ typedef struct guac_rdp_audio_buffer {
*/ */
int packet_size; int packet_size;
/**
* The total number of bytes available within the packet buffer.
*/
int packet_buffer_size;
/** /**
* The number of bytes currently stored within the packet buffer. * The number of bytes currently stored within the packet buffer.
*/ */
@ -133,6 +161,20 @@ typedef struct guac_rdp_audio_buffer {
*/ */
char* packet; char* packet;
/**
* Thread which flushes the audio buffer at a rate that does not exceed the
* the audio sample rate (which might result in dropped samples due to
* overflow of the remote audio buffer).
*/
pthread_t flush_thread;
/**
* The absolute point in time that the next packet of audio data sould be
* flushed. Another packet of received data should not be flushed prior to
* this time.
*/
struct timespec next_flush;
/** /**
* Handler function which will be invoked when a full audio packet is * Handler function which will be invoked when a full audio packet is
* ready to be flushed to the AUDIO_INPUT channel, if defined. If NULL, * ready to be flushed to the AUDIO_INPUT channel, if defined. If NULL,
@ -140,22 +182,31 @@ typedef struct guac_rdp_audio_buffer {
*/ */
guac_rdp_audio_buffer_flush_handler* flush_handler; guac_rdp_audio_buffer_flush_handler* flush_handler;
/**
* Whether guac_rdp_audio_buffer_free() has been invoked and the audio
* buffer is being cleaned up.
*/
int stopping;
/** /**
* Arbitrary data assigned by the AUDIO_INPUT plugin implementation. * Arbitrary data assigned by the AUDIO_INPUT plugin implementation.
*/ */
void* data; void* data;
} guac_rdp_audio_buffer; };
/** /**
* Allocates a new audio buffer. The new audio buffer will ignore any received * Allocates a new audio buffer. The new audio buffer will ignore any received
* data until guac_rdp_audio_buffer_begin() is invoked, and will resume * data until guac_rdp_audio_buffer_begin() is invoked, and will resume
* ignoring received data once guac_rdp_audio_buffer_end() is invoked. * ignoring received data once guac_rdp_audio_buffer_end() is invoked.
* *
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return * @return
* A newly-allocated audio buffer. * A newly-allocated audio buffer.
*/ */
guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(); guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client);
/** /**
* Associates the given audio buffer with the underlying audio stream which * Associates the given audio buffer with the underlying audio stream which

View File

@ -76,6 +76,9 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* We support CP-1252 and UTF-16 text */ /* We support CP-1252 and UTF-16 text */
CLIPRDR_FORMAT_LIST format_list = { CLIPRDR_FORMAT_LIST format_list = {
.msgType = CB_FORMAT_LIST, .msgType = CB_FORMAT_LIST,
@ -86,10 +89,12 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
.numFormats = 2 .numFormats = 2
}; };
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format list");
"format list");
return cliprdr->ClientFormatList(cliprdr, &format_list); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientFormatList(cliprdr, &format_list);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -107,6 +112,17 @@ static UINT guac_rdp_cliprdr_send_format_list(CliprdrClientContext* cliprdr) {
*/ */
static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) { static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
/* This function is only invoked within FreeRDP-specific handlers for
* CLIPRDR, which are not assigned, and thus not callable, until after the
* relevant guac_rdp_clipboard structure is allocated and associated with
* the CliprdrClientContext */
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* We support CP-1252 and UTF-16 text */
CLIPRDR_GENERAL_CAPABILITY_SET cap_set = { CLIPRDR_GENERAL_CAPABILITY_SET cap_set = {
.capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */ .capabilitySetType = CB_CAPSTYPE_GENERAL, /* CLIPRDR specification requires that this is CB_CAPSTYPE_GENERAL, the only defined set type */
.capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */ .capabilitySetLength = 12, /* The size of the capability set within the PDU - for CB_CAPSTYPE_GENERAL, this is ALWAYS 12 bytes */
@ -119,7 +135,11 @@ static UINT guac_rdp_cliprdr_send_capabilities(CliprdrClientContext* cliprdr) {
.capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set .capabilitySets = (CLIPRDR_CAPABILITY_SET*) &cap_set
}; };
return cliprdr->ClientCapabilities(cliprdr, &caps); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientCapabilities(cliprdr, &caps);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -190,6 +210,9 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
/* Create new data request */ /* Create new data request */
CLIPRDR_FORMAT_DATA_REQUEST data_request = { CLIPRDR_FORMAT_DATA_REQUEST data_request = {
.requestedFormatId = format .requestedFormatId = format
@ -199,11 +222,14 @@ static UINT guac_rdp_cliprdr_send_format_data_request(
* data is received via a Format Data Response PDU */ * data is received via a Format Data Response PDU */
clipboard->requested_format = format; clipboard->requested_format = format;
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data request.");
"format data request.");
/* Send request */ /* Send request */
return cliprdr->ClientFormatDataRequest(cliprdr, &data_request); pthread_mutex_lock(&(rdp_client->message_lock));
int retval = cliprdr->ClientFormatDataRequest(cliprdr, &data_request);
pthread_mutex_unlock(&(rdp_client->message_lock));
return retval;
} }
@ -265,15 +291,19 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received " guac_client* client = clipboard->client;
"format list."); guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format list.");
CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = { CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {
.msgFlags = CB_RESPONSE_OK .msgFlags = CB_RESPONSE_OK
}; };
/* Report successful processing of format list */ /* Report successful processing of format list */
pthread_mutex_lock(&(rdp_client->message_lock));
cliprdr->ClientFormatListResponse(cliprdr, &format_list_response); cliprdr->ClientFormatListResponse(cliprdr, &format_list_response);
pthread_mutex_unlock(&(rdp_client->message_lock));
/* Prefer Unicode (in this case, UTF-16) */ /* Prefer Unicode (in this case, UTF-16) */
if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT)) if (guac_rdp_cliprdr_format_supported(format_list, CF_UNICODETEXT))
@ -284,9 +314,9 @@ static UINT guac_rdp_cliprdr_format_list(CliprdrClientContext* cliprdr,
return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT); return guac_rdp_cliprdr_send_format_data_request(cliprdr, CF_TEXT);
/* Ignore any unsupported data */ /* Ignore any unsupported data */
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring unsupported " guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring unsupported clipboard "
"clipboard data. Only Unicode and text clipboard formats are " "data. Only Unicode and text clipboard formats are currently "
"currently supported."); "supported.");
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
@ -320,32 +350,35 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom; guac_rdp_clipboard* clipboard = (guac_rdp_clipboard*) cliprdr->custom;
assert(clipboard != NULL); assert(clipboard != NULL);
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Received " guac_client* client = clipboard->client;
"format data request."); guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_settings* settings = rdp_client->settings;
guac_iconv_write* writer; guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data request.");
guac_iconv_write* remote_writer;
const char* input = clipboard->clipboard->buffer; const char* input = clipboard->clipboard->buffer;
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); char* output = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
/* Map requested clipboard format to a guac_iconv writer */ /* Map requested clipboard format to a guac_iconv writer */
switch (format_data_request->requestedFormatId) { switch (format_data_request->requestedFormatId) {
case CF_TEXT: case CF_TEXT:
writer = GUAC_WRITE_CP1252; remote_writer = settings->clipboard_crlf ? GUAC_WRITE_CP1252_CRLF : GUAC_WRITE_CP1252;
break; break;
case CF_UNICODETEXT: case CF_UNICODETEXT:
writer = GUAC_WRITE_UTF16; remote_writer = settings->clipboard_crlf ? GUAC_WRITE_UTF16_CRLF : GUAC_WRITE_UTF16;
break; break;
/* Warn if clipboard data cannot be sent as intended due to a violation /* Warn if clipboard data cannot be sent as intended due to a violation
* of the CLIPRDR spec */ * of the CLIPRDR spec */
default: default:
guac_client_log(clipboard->client, GUAC_LOG_WARNING, "Received " guac_client_log(client, GUAC_LOG_WARNING, "Received clipboard "
"clipboard data cannot be sent to the RDP server because " "data cannot be sent to the RDP server because the RDP "
"the RDP server has requested a clipboard format which " "server has requested a clipboard format which was not "
"was not declared as available. This violates the " "declared as available. This violates the specification "
"specification for the CLIPRDR channel."); "for the CLIPRDR channel.");
free(output); free(output);
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
@ -354,8 +387,9 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
/* Send received clipboard data to the RDP server in the format /* Send received clipboard data to the RDP server in the format
* requested */ * requested */
BYTE* start = (BYTE*) output; BYTE* start = (BYTE*) output;
guac_iconv(GUAC_READ_UTF8, &input, clipboard->clipboard->length, guac_iconv_read* local_reader = settings->normalize_clipboard ? GUAC_READ_UTF8_NORMALIZED : GUAC_READ_UTF8;
writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH); guac_iconv(local_reader, &input, clipboard->clipboard->length,
remote_writer, &output, GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
CLIPRDR_FORMAT_DATA_RESPONSE data_response = { CLIPRDR_FORMAT_DATA_RESPONSE data_response = {
.requestedFormatData = (BYTE*) start, .requestedFormatData = (BYTE*) start,
@ -363,10 +397,12 @@ static UINT guac_rdp_cliprdr_format_data_request(CliprdrClientContext* cliprdr,
.msgFlags = CB_RESPONSE_OK .msgFlags = CB_RESPONSE_OK
}; };
guac_client_log(clipboard->client, GUAC_LOG_TRACE, "CLIPRDR: Sending " guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Sending format data response.");
"format data response.");
pthread_mutex_lock(&(rdp_client->message_lock));
UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response); UINT result = cliprdr->ClientFormatDataResponse(cliprdr, &data_response);
pthread_mutex_unlock(&(rdp_client->message_lock));
free(start); free(start);
return result; return result;
@ -407,15 +443,15 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
/* Ignore received data if copy has been disabled */ /* Ignore received data if copy has been disabled */
if (settings->disable_copy) { if (settings->disable_copy) {
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Ignoring received " guac_client_log(client, GUAC_LOG_DEBUG, "Ignoring received clipboard "
"clipboard data as copying from within the remote desktop has " "data as copying from within the remote desktop has been "
"been explicitly disabled."); "explicitly disabled.");
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
} }
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH]; char received_data[GUAC_COMMON_CLIPBOARD_MAX_LENGTH];
guac_iconv_read* reader; guac_iconv_read* remote_reader;
const char* input = (char*) format_data_response->requestedFormatData; const char* input = (char*) format_data_response->requestedFormatData;
char* output = received_data; char* output = received_data;
@ -424,12 +460,12 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
/* Non-Unicode (Windows CP-1252) */ /* Non-Unicode (Windows CP-1252) */
case CF_TEXT: case CF_TEXT:
reader = GUAC_READ_CP1252; remote_reader = settings->normalize_clipboard ? GUAC_READ_CP1252_NORMALIZED : GUAC_READ_CP1252;
break; break;
/* Unicode (UTF-16) */ /* Unicode (UTF-16) */
case CF_UNICODETEXT: case CF_UNICODETEXT:
reader = GUAC_READ_UTF16; remote_reader = settings->normalize_clipboard ? GUAC_READ_UTF16_NORMALIZED : GUAC_READ_UTF16;
break; break;
/* If the format ID stored within the guac_rdp_clipboard structure is actually /* If the format ID stored within the guac_rdp_clipboard structure is actually
@ -439,21 +475,20 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
* here. The values which may be stored within requested_format are * here. The values which may be stored within requested_format are
* completely within our control. */ * completely within our control. */
default: default:
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Requested " guac_client_log(client, GUAC_LOG_DEBUG, "Requested clipboard data "
"clipboard data in unsupported format (0x%X).", "in unsupported format (0x%X).", clipboard->requested_format);
clipboard->requested_format);
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
} }
/* Convert, store, and forward the clipboard data received from RDP /* Convert, store, and forward the clipboard data received from RDP
* server */ * server */
if (guac_iconv(reader, &input, format_data_response->dataLen, if (guac_iconv(remote_reader, &input, format_data_response->dataLen,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) { GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data)); int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(clipboard->clipboard, "text/plain"); guac_common_clipboard_reset(clipboard->clipboard, "text/plain");
guac_common_clipboard_append(clipboard->clipboard, received_data, length); guac_common_clipboard_append(clipboard->clipboard, received_data, length);
guac_common_clipboard_send(clipboard->clipboard, clipboard->client); guac_common_clipboard_send(clipboard->clipboard, client);
} }
return CHANNEL_RC_OK; return CHANNEL_RC_OK;
@ -474,12 +509,12 @@ static UINT guac_rdp_cliprdr_format_data_response(CliprdrClientContext* cliprdr,
* @param context * @param context
* The rdpContext associated with the active RDP session. * The rdpContext associated with the active RDP session.
* *
* @param e * @param args
* Event-specific arguments, mainly the name of the channel, and a * Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP. * reference to the associated plugin loaded for that channel by FreeRDP.
*/ */
static void guac_rdp_cliprdr_channel_connected(rdpContext* context, static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) { ChannelConnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
@ -491,12 +526,12 @@ static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
assert(clipboard != NULL); assert(clipboard != NULL);
/* Ignore connection event if it's not for the CLIPRDR channel */ /* Ignore connection event if it's not for the CLIPRDR channel */
if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) != 0) if (strcmp(args->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
return; return;
/* The structure pointed to by pInterface is guaranteed to be a /* The structure pointed to by pInterface is guaranteed to be a
* CliprdrClientContext if the channel is CLIPRDR */ * CliprdrClientContext if the channel is CLIPRDR */
CliprdrClientContext* cliprdr = (CliprdrClientContext*) e->pInterface; CliprdrClientContext* cliprdr = (CliprdrClientContext*) args->pInterface;
/* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with /* Associate FreeRDP CLIPRDR context and its Guacamole counterpart with
* eachother */ * eachother */
@ -513,12 +548,54 @@ static void guac_rdp_cliprdr_channel_connected(rdpContext* context,
} }
/**
* Callback which disassociates Guacamole from the CliprdrClientContext
* instance that was originally allocated by FreeRDP and is about to be
* deallocated.
*
* This function is called whenever a channel disconnects via the PubSub event
* system within FreeRDP, but only has any effect if the disconnected channel
* is the CLIPRDR channel. This specific callback is registered with the PubSub
* system of the relevant rdpContext when guac_rdp_clipboard_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_cliprdr_channel_disconnected(rdpContext* context,
ChannelDisconnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_clipboard* clipboard = rdp_client->clipboard;
/* FreeRDP-specific handlers for CLIPRDR are not assigned, and thus not
* callable, until after the relevant guac_rdp_clipboard structure is
* allocated and associated with the guac_rdp_client */
assert(clipboard != NULL);
/* Ignore disconnection event if it's not for the CLIPRDR channel */
if (strcmp(args->name, CLIPRDR_SVC_CHANNEL_NAME) != 0)
return;
/* Channel is no longer connected */
clipboard->cliprdr = NULL;
guac_client_log(client, GUAC_LOG_DEBUG, "CLIPRDR (clipboard redirection) "
"channel disconnected.");
}
guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) { guac_rdp_clipboard* guac_rdp_clipboard_alloc(guac_client* client) {
/* Allocate clipboard and underlying storage */ /* Allocate clipboard and underlying storage */
guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard)); guac_rdp_clipboard* clipboard = calloc(1, sizeof(guac_rdp_clipboard));
clipboard->client = client; clipboard->client = client;
clipboard->clipboard = guac_common_clipboard_alloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH); clipboard->clipboard = guac_common_clipboard_alloc();
clipboard->requested_format = CF_TEXT; clipboard->requested_format = CF_TEXT;
return clipboard; return clipboard;
@ -542,6 +619,10 @@ void guac_rdp_clipboard_load_plugin(guac_rdp_clipboard* clipboard,
PubSub_SubscribeChannelConnected(context->pubSub, PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected); (pChannelConnectedEventHandler) guac_rdp_cliprdr_channel_connected);
/* Clean up any RDP-specific resources when channel is disconnected */
PubSub_SubscribeChannelDisconnected(context->pubSub,
(pChannelDisconnectedEventHandler) guac_rdp_cliprdr_channel_disconnected);
guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR " guac_client_log(clipboard->client, GUAC_LOG_DEBUG, "Support for CLIPRDR "
"(clipboard redirection) registered. Awaiting channel " "(clipboard redirection) registered. Awaiting channel "
"connection."); "connection.");

View File

@ -89,12 +89,16 @@ void guac_rdp_common_svc_write(guac_rdp_common_svc* svc,
return; return;
} }
guac_rdp_client* rdp_client = (guac_rdp_client*) svc->client->data;
/* NOTE: The wStream sent via pVirtualChannelWriteEx will automatically be /* NOTE: The wStream sent via pVirtualChannelWriteEx will automatically be
* freed later with a call to Stream_Free() when handling the * freed later with a call to Stream_Free() when handling the
* corresponding write cancel/completion event. */ * corresponding write cancel/completion event. */
pthread_mutex_lock(&(rdp_client->message_lock));
svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle, svc->_entry_points.pVirtualChannelWriteEx(svc->_init_handle,
svc->_open_handle, Stream_Buffer(output_stream), svc->_open_handle, Stream_Buffer(output_stream),
Stream_GetPosition(output_stream), output_stream); Stream_GetPosition(output_stream), output_stream);
pthread_mutex_unlock(&(rdp_client->message_lock));
} }

View File

@ -115,7 +115,7 @@ struct guac_rdp_common_svc {
guac_rdp_common_svc_receive_handler* _receive_handler; guac_rdp_common_svc_receive_handler* _receive_handler;
/** /**
* Handler which is invokved when the SVC has been disconnected and is * Handler which is involved when the SVC has been disconnected and is
* about to be freed. * about to be freed.
*/ */
guac_rdp_common_svc_terminate_handler* _terminate_handler; guac_rdp_common_svc_terminate_handler* _terminate_handler;

View File

@ -19,6 +19,7 @@
#include "channels/disp.h" #include "channels/disp.h"
#include "plugins/channels.h" #include "plugins/channels.h"
#include "fs.h"
#include "rdp.h" #include "rdp.h"
#include "settings.h" #include "settings.h"
@ -31,9 +32,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
guac_rdp_disp* guac_rdp_disp_alloc() { guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) {
guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp)); guac_rdp_disp* disp = malloc(sizeof(guac_rdp_disp));
disp->client = client;
/* Not yet connected */ /* Not yet connected */
disp->disp = NULL; disp->disp = NULL;
@ -66,19 +68,19 @@ void guac_rdp_disp_free(guac_rdp_disp* disp) {
* @param context * @param context
* The rdpContext associated with the active RDP session. * The rdpContext associated with the active RDP session.
* *
* @param e * @param args
* Event-specific arguments, mainly the name of the channel, and a * Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP. * reference to the associated plugin loaded for that channel by FreeRDP.
*/ */
static void guac_rdp_disp_channel_connected(rdpContext* context, static void guac_rdp_disp_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) { ChannelConnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_disp* guac_disp = rdp_client->disp; guac_rdp_disp* guac_disp = rdp_client->disp;
/* Ignore connection event if it's not for the Display Update channel */ /* Ignore connection event if it's not for the Display Update channel */
if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) != 0) if (strcmp(args->name, DISP_DVC_CHANNEL_NAME) != 0)
return; return;
/* Init module with current display size */ /* Init module with current display size */
@ -87,7 +89,7 @@ static void guac_rdp_disp_channel_connected(rdpContext* context,
guac_rdp_get_height(context->instance)); guac_rdp_get_height(context->instance));
/* Store reference to the display update plugin once it's connected */ /* Store reference to the display update plugin once it's connected */
DispClientContext* disp = (DispClientContext*) e->pInterface; DispClientContext* disp = (DispClientContext*) args->pInterface;
guac_disp->disp = disp; guac_disp->disp = disp;
guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel " guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel "
@ -95,12 +97,52 @@ static void guac_rdp_disp_channel_connected(rdpContext* context,
} }
/**
* Callback which disassociates Guacamole from the DispClientContext instance
* that was originally allocated by FreeRDP and is about to be deallocated.
*
* This function is called whenever a channel disconnects via the PubSub event
* system within FreeRDP, but only has any effect if the disconnected channel
* is the Display Update channel. This specific callback is registered with the
* PubSub system of the relevant rdpContext when guac_rdp_disp_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_disp_channel_disconnected(rdpContext* context,
ChannelDisconnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_disp* guac_disp = rdp_client->disp;
/* Ignore disconnection event if it's not for the Display Update channel */
if (strcmp(args->name, DISP_DVC_CHANNEL_NAME) != 0)
return;
/* Channel is no longer connected */
guac_disp->disp = NULL;
guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel "
"disconnected.");
}
void guac_rdp_disp_load_plugin(rdpContext* context) { void guac_rdp_disp_load_plugin(rdpContext* context) {
/* Subscribe to and handle channel connected events */ /* Subscribe to and handle channel connected events */
PubSub_SubscribeChannelConnected(context->pubSub, PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_disp_channel_connected); (pChannelConnectedEventHandler) guac_rdp_disp_channel_connected);
/* Subscribe to and handle channel disconnected events */
PubSub_SubscribeChannelDisconnected(context->pubSub,
(pChannelDisconnectedEventHandler) guac_rdp_disp_channel_disconnected);
/* Add "disp" channel */ /* Add "disp" channel */
guac_freerdp_dynamic_channel_collection_add(context->settings, "disp", NULL); guac_freerdp_dynamic_channel_collection_add(context->settings, "disp", NULL);
@ -220,13 +262,34 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp,
}}; }};
/* Send display update notification if display channel is connected */ /* Send display update notification if display channel is connected */
if (disp->disp != NULL) if (disp->disp != NULL) {
guac_client* client = disp->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
pthread_mutex_lock(&(rdp_client->message_lock));
disp->disp->SendMonitorLayout(disp->disp, 1, monitors); disp->disp->SendMonitorLayout(disp->disp, 1, monitors);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
} }
} }
int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp) { int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp) {
guac_rdp_client* rdp_client = (guac_rdp_client*) disp->client->data;
/* Do not reconnect if files are open. */
if (rdp_client->filesystem != NULL
&& rdp_client->filesystem->open_files > 0)
return 0;
/* Do not reconnect if an active print job is present */
if (rdp_client->active_job != NULL)
return 0;
return disp->reconnect_needed; return disp->reconnect_needed;
} }

View File

@ -48,6 +48,11 @@
*/ */
typedef struct guac_rdp_disp { typedef struct guac_rdp_disp {
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/** /**
* Display control interface. * Display control interface.
*/ */
@ -81,10 +86,13 @@ typedef struct guac_rdp_disp {
* Allocates a new display update module, which will ultimately control the * Allocates a new display update module, which will ultimately control the
* display update channel once connected. * display update channel once connected.
* *
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return * @return
* A newly-allocated display update module. * A newly-allocated display update module.
*/ */
guac_rdp_disp* guac_rdp_disp_alloc(); guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client);
/** /**
* Frees the resources associated with support for the RDP Display Update * Frees the resources associated with support for the RDP Display Update
@ -101,7 +109,7 @@ void guac_rdp_disp_free(guac_rdp_disp* disp);
/** /**
* Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins * Adds FreeRDP's "disp" plugin to the list of dynamic virtual channel plugins
* to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will * to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
* automatically be assicated with the guac_rdp_disp instance pointed to by the * automatically be associated with the guac_rdp_disp instance pointed to by the
* current guac_rdp_client. The plugin will only be loaded once the "drdynvc" * current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
* plugin is loaded. The "disp" plugin ultimately adds support for the Display * plugin is loaded. The "disp" plugin ultimately adds support for the Display
* Update channel. * Update channel.

View File

@ -86,7 +86,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client handshake response */ /* Send client handshake response */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientHandshake(rail, &handshake); status = rail->ClientHandshake(rail, &handshake);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -95,7 +98,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client status */ /* Send client status */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientInformation(rail, &client_status); status = rail->ClientInformation(rail, &client_status);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -139,7 +145,10 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Send client system parameters */ /* Send client system parameters */
pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientSystemParam(rail, &sysparam); status = rail->ClientSystemParam(rail, &sysparam);
pthread_mutex_unlock(&(rdp_client->message_lock));
if (status != CHANNEL_RC_OK) if (status != CHANNEL_RC_OK)
return status; return status;
@ -151,7 +160,11 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) {
}; };
/* Execute desired RemoteApp command */ /* Execute desired RemoteApp command */
return rail->ClientExecute(rail, &exec); pthread_mutex_lock(&(rdp_client->message_lock));
status = rail->ClientExecute(rail, &exec);
pthread_mutex_unlock(&(rdp_client->message_lock));
return status;
} }
@ -217,22 +230,22 @@ static UINT guac_rdp_rail_handshake_ex(RailClientContext* rail,
* @param context * @param context
* The rdpContext associated with the active RDP session. * The rdpContext associated with the active RDP session.
* *
* @param e * @param args
* Event-specific arguments, mainly the name of the channel, and a * Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP. * reference to the associated plugin loaded for that channel by FreeRDP.
*/ */
static void guac_rdp_rail_channel_connected(rdpContext* context, static void guac_rdp_rail_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* e) { ChannelConnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client; guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Ignore connection event if it's not for the RAIL channel */ /* Ignore connection event if it's not for the RAIL channel */
if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) != 0) if (strcmp(args->name, RAIL_SVC_CHANNEL_NAME) != 0)
return; return;
/* The structure pointed to by pInterface is guaranteed to be a /* The structure pointed to by pInterface is guaranteed to be a
* RailClientContext if the channel is RAIL */ * RailClientContext if the channel is RAIL */
RailClientContext* rail = (RailClientContext*) e->pInterface; RailClientContext* rail = (RailClientContext*) args->pInterface;
/* Init FreeRDP RAIL context, ensuring the guac_client can be accessed from /* Init FreeRDP RAIL context, ensuring the guac_client can be accessed from
* within any RAIL-specific callbacks */ * within any RAIL-specific callbacks */

View File

@ -0,0 +1,224 @@
/*
* 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 "channels/rdpei.h"
#include "common/surface.h"
#include "plugins/channels.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/client/rdpei.h>
#include <freerdp/freerdp.h>
#include <freerdp/event.h>
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
#include <stdlib.h>
#include <string.h>
guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) {
guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei));
rdpei->client = client;
/* Not yet connected */
rdpei->rdpei = NULL;
/* No active touches */
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++)
rdpei->touch[i].active = 0;
return rdpei;
}
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei) {
free(rdpei);
}
/**
* Callback which associates handlers specific to Guacamole with the
* RdpeiClientContext instance allocated by FreeRDP to deal with received
* RDPEI (multi-touch input) messages.
*
* This function is called whenever a channel connects via the PubSub event
* system within FreeRDP, but only has any effect if the connected channel is
* the RDPEI channel. This specific callback is registered with the
* PubSub system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_rdpei_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei;
/* Ignore connection event if it's not for the RDPEI channel */
if (strcmp(args->name, RDPEI_DVC_CHANNEL_NAME) != 0)
return;
/* Store reference to the RDPEI plugin once it's connected */
RdpeiClientContext* rdpei = (RdpeiClientContext*) args->pInterface;
guac_rdpei->rdpei = rdpei;
/* Declare level of multi-touch support */
guac_common_surface_set_multitouch(rdp_client->display->default_surface,
GUAC_RDP_RDPEI_MAX_TOUCHES);
guac_client_log(client, GUAC_LOG_DEBUG, "RDPEI channel will be used for "
"multi-touch support.");
}
/**
* Callback which disassociates Guacamole from the RdpeiClientContext instance
* that was originally allocated by FreeRDP and is about to be deallocated.
*
* This function is called whenever a channel disconnects via the PubSub event
* system within FreeRDP, but only has any effect if the disconnected channel
* is the RDPEI channel. This specific callback is registered with the PubSub
* system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_rdpei_channel_disconnected(rdpContext* context,
ChannelDisconnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei;
/* Ignore disconnection event if it's not for the RDPEI channel */
if (strcmp(args->name, RDPEI_DVC_CHANNEL_NAME) != 0)
return;
/* Channel is no longer connected */
guac_rdpei->rdpei = NULL;
guac_client_log(client, GUAC_LOG_DEBUG, "RDPDI channel disconnected.");
}
void guac_rdp_rdpei_load_plugin(rdpContext* context) {
/* Subscribe to and handle channel connected events */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_rdpei_channel_connected);
/* Subscribe to and handle channel disconnected events */
PubSub_SubscribeChannelDisconnected(context->pubSub,
(pChannelDisconnectedEventHandler) guac_rdp_rdpei_channel_disconnected);
/* Add "rdpei" channel */
guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpei", NULL);
}
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
double force) {
guac_client* client = rdpei->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
int contact_id; /* Ignored */
/* Track touches only if channel is connected */
RdpeiClientContext* context = rdpei->rdpei;
if (context == NULL)
return 1;
/* Locate active touch having provided ID */
guac_rdp_rdpei_touch* touch = NULL;
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
if (rdpei->touch[i].active && rdpei->touch[i].id == id) {
touch = &rdpei->touch[i];
break;
}
}
/* If no such touch exists, add it */
if (touch == NULL) {
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
if (!rdpei->touch[i].active) {
touch = &rdpei->touch[i];
touch->id = id;
break;
}
}
}
/* If the touch couldn't be added, we're already at maximum touch capacity.
* Drop the event. */
if (touch == NULL)
return 1;
/* Signal the end of an established touch if touch force has become zero
* (this should be a safe comparison, as zero has an exact representation
* in floating point, and the client side will use an exact value to
* represent the absence of a touch) */
if (force == 0.0) {
/* Ignore release of touches that we aren't tracking */
if (!touch->active)
return 1;
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchEnd(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 0;
}
/* Signal the start of a touch if this is the first we've seen it */
else if (!touch->active) {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchBegin(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 1;
}
/* Established touches need only be updated */
else {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchUpdate(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
return 0;
}

View File

@ -0,0 +1,162 @@
/*
* 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_RDP_CHANNELS_RDPEI_H
#define GUAC_RDP_CHANNELS_RDPEI_H
#include "settings.h"
#include <freerdp/client/rdpei.h>
#include <freerdp/freerdp.h>
#include <guacamole/client.h>
#include <guacamole/timestamp.h>
/**
* The maximum number of simultaneously-tracked touches.
*/
#define GUAC_RDP_RDPEI_MAX_TOUCHES 10
/**
* A single, tracked touch contact.
*/
typedef struct guac_rdp_rdpei_touch {
/**
* Whether this touch is active (1) or inactive (0). An active touch is
* being tracked, while an inactive touch is simple an empty space awaiting
* use by some future touch event.
*/
int active;
/**
* The unique ID representing this touch contact.
*/
int id;
/**
* The X-coordinate of this touch, in pixels.
*/
int x;
/**
* The Y-coordinate of this touch, in pixels.
*/
int y;
} guac_rdp_rdpei_touch;
/**
* Multi-touch input module.
*/
typedef struct guac_rdp_rdpei {
/**
* The guac_client instance handling the relevant RDP connection.
*/
guac_client* client;
/**
* RDPEI control interface.
*/
RdpeiClientContext* rdpei;
/**
* All currently-tracked touches.
*/
guac_rdp_rdpei_touch touch[GUAC_RDP_RDPEI_MAX_TOUCHES];
} guac_rdp_rdpei;
/**
* Allocates a new RDPEI module, which will ultimately control the RDPEI
* channel once connected. The RDPEI channel allows multi-touch input
* events to be sent to the RDP server.
*
* @param client
* The guac_client instance handling the relevant RDP connection.
*
* @return
* A newly-allocated RDPEI module.
*/
guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client);
/**
* Frees the resources associated with support for the RDPEI channel. Only
* resources specific to Guacamole are freed. Resources specific to FreeRDP's
* handling of the RDPEI channel will be freed by FreeRDP. If no resources are
* currently allocated for RDPEI, this function has no effect.
*
* @param rdpei
* The RDPEI module to free.
*/
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei);
/**
* Adds FreeRDP's "rdpei" plugin to the list of dynamic virtual channel plugins
* to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
* automatically be associated with the guac_rdp_rdpei instance pointed to by the
* current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
* plugin is loaded. The "rdpei" plugin ultimately adds support for multi-touch
* input via the RDPEI channel.
*
* If failures occur, messages noting the specifics of those failures will be
* logged, and the RDP side of multi-touch support will not be functional.
*
* This MUST be called within the PreConnect callback of the freerdp instance
* for multi-touch support to be loaded.
*
* @param context
* The rdpContext associated with the active RDP session.
*/
void guac_rdp_rdpei_load_plugin(rdpContext* context);
/**
* Reports to the RDP server that the status of a single touch contact has
* changed. Depending on the amount of force associated with the touch and
* whether the touch has been encountered before, this will result a new touch
* contact, updates to an existing contact, or removal of an existing contact.
* If the RDPEI channel has not yet been connected, touches will be ignored and
* dropped until it is connected.
*
* @param rdpei
* The RDPEI module associated with the RDP session.
*
* @param id
* An arbitrary integer ID unique to the touch being updated.
*
* @param x
* The X-coordinate of the touch, in pixels.
*
* @param y
* The Y-coordinate of the touch, in pixels.
*
* @param force
* The amount of force currently being exerted on the device by the touch
* contact in question, where 1.0 is the maximum amount of force
* representable and 0.0 indicates the contact has been lifted.
*
* @return
* Zero if the touch event was successfully processed, non-zero if the
* touch event had to be dropped.
*/
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
double force);
#endif

View File

@ -0,0 +1,122 @@
/*
* 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 "channels/rdpgfx.h"
#include "plugins/channels.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/client/rdpgfx.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gfx.h>
#include <freerdp/event.h>
#include <guacamole/client.h>
#include <stdlib.h>
#include <string.h>
/**
* Callback which associates handlers specific to Guacamole with the
* RdpgfxClientContext instance allocated by FreeRDP to deal with received
* RDPGFX (Graphics Pipeline) messages.
*
* This function is called whenever a channel connects via the PubSub event
* system within FreeRDP, but only has any effect if the connected channel is
* the RDPGFX channel. This specific callback is registered with the
* PubSub system of the relevant rdpContext when guac_rdp_rdpgfx_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_rdpgfx_channel_connected(rdpContext* context,
ChannelConnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Ignore connection event if it's not for the RDPGFX channel */
if (strcmp(args->name, RDPGFX_DVC_CHANNEL_NAME) != 0)
return;
/* Init GDI-backed support for the Graphics Pipeline */
RdpgfxClientContext* rdpgfx = (RdpgfxClientContext*) args->pInterface;
rdpGdi* gdi = context->gdi;
if (!gdi_graphics_pipeline_init(gdi, rdpgfx))
guac_client_log(client, GUAC_LOG_WARNING, "Rendering backend for RDPGFX "
"channel could not be loaded. Graphics may not render at all!");
else
guac_client_log(client, GUAC_LOG_DEBUG, "RDPGFX channel will be used for "
"the RDP Graphics Pipeline Extension.");
}
/**
* Callback which handles any RDPGFX cleanup specific to Guacamole.
*
* This function is called whenever a channel disconnects via the PubSub event
* system within FreeRDP, but only has any effect if the disconnected channel
* is the RDPGFX channel. This specific callback is registered with the PubSub
* system of the relevant rdpContext when guac_rdp_rdpgfx_load_plugin() is
* called.
*
* @param context
* The rdpContext associated with the active RDP session.
*
* @param args
* Event-specific arguments, mainly the name of the channel, and a
* reference to the associated plugin loaded for that channel by FreeRDP.
*/
static void guac_rdp_rdpgfx_channel_disconnected(rdpContext* context,
ChannelDisconnectedEventArgs* args) {
guac_client* client = ((rdp_freerdp_context*) context)->client;
/* Ignore disconnection event if it's not for the RDPGFX channel */
if (strcmp(args->name, RDPGFX_DVC_CHANNEL_NAME) != 0)
return;
/* Un-init GDI-backed support for the Graphics Pipeline */
RdpgfxClientContext* rdpgfx = (RdpgfxClientContext*) args->pInterface;
rdpGdi* gdi = context->gdi;
gdi_graphics_pipeline_uninit(gdi, rdpgfx);
guac_client_log(client, GUAC_LOG_DEBUG, "RDPGFX channel support unloaded.");
}
void guac_rdp_rdpgfx_load_plugin(rdpContext* context) {
/* Subscribe to and handle channel connected events */
PubSub_SubscribeChannelConnected(context->pubSub,
(pChannelConnectedEventHandler) guac_rdp_rdpgfx_channel_connected);
/* Subscribe to and handle channel disconnected events */
PubSub_SubscribeChannelDisconnected(context->pubSub,
(pChannelDisconnectedEventHandler) guac_rdp_rdpgfx_channel_disconnected);
/* Add "rdpgfx" channel */
guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpgfx", NULL);
}

Some files were not shown because too many files have changed in this diff Show More