-
Notifications
You must be signed in to change notification settings - Fork 620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GUACAMOLE-269: Allow backspace key behavior to be configured #156
Conversation
src/terminal/terminal.c
Outdated
@@ -1594,7 +1599,7 @@ static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed | |||
/* Non-printable keys */ | |||
else { | |||
|
|||
if (keysym == 0xFF08) return guac_terminal_send_string(term, "\x7F"); /* Backspace */ | |||
if (keysym == 0xFF08) return guac_terminal_send_string(term, &term->backspace); /* Backspace */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guac_terminal_send_string()
requires a null-terminated string. As &term->backspace
is a pointer to a single character, there is no guarantee of null terminator, and this may read without bound, resulting in connection closure or dumping of memory data to the remote terminal.
src/protocols/ssh/settings.h
Outdated
@@ -223,6 +223,11 @@ typedef struct guac_ssh_settings { | |||
*/ | |||
int server_alive_interval; | |||
|
|||
/** | |||
* The decismal ASCII code of the command to send for backspace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"decismal" is a typo, but that may be moot as this is technically incorrect. While the string from which this int
was parsed may have been decimal, at this point it's just an int
.
src/protocols/ssh/ttymode.c
Outdated
#include <string.h> | ||
|
||
guac_ssh_ttymodes init_ttymodes() { | ||
// Simple allocation for a placeholder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments for C source within Guacamole should use /* ... */
syntax, even if only a single line.
src/protocols/ssh/ttymode.c
Outdated
0 | ||
}; | ||
|
||
return empty_modes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assigning/returning a struct by value like this actually does an implicit copy in C.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this to a pointer - not sure if that's what you had in mind, but I think it works better any way with what I was trying to do in the other functions.
Before I continue with this review, do you think that |
You're probably right - it definitely felt a little overkill-ish while writing it; however, I was trying to both solve the problem of reading the new SSH setting into the array while also making it so that this could be expanded and reused easily in the future. While this particular JIRA issue deals with the behavior of the backspace key, there are several other TTY Mode Encoding options specified in the SSH RFC, and I didn't want to make the assumption that this is the only one we'd ever be interested in implementing. However, if you think that's a problem we should worry about when the second opcode comes along, that's fine with me - I can back off this and just write a function that generates the pointer array with the VERASE opcode and value and the ending opcode. Let me know which route you think is best and I'll be happy to do it. |
Future-proofing these changes is not a bad idea, but let's do so without complex dynamic allocation. In this case:
For the sake of simplicity, I'd say we should really just use a statically-allocated array for now. If you feel that dynamic allocation would be more appropriate, then we should make use of our knowledge of the necessary size and allocate the necessary space ahead of time (without resizing) and be careful to free the allocated data once it is no longer needed. |
@mike-jumper I also added some calls to free the data structures - I think I got everything, but worth double-checking to make sure. |
Hm ... there has to be a cleaner way. The current implementation is:
I have a few problems with this. We are dynamically allocating an intermediate representation which really has no purpose other than to be converted into another dynamically-allocated representation, both of which have the same lifespan. Abstraction is a great thing, but the complexity of this abstraction seems to outweigh the intended benefit. I suggest:
|
Oh, I have no doubt at all there is a cleaner way! :-)
Yeah, I'll see what I can do about this. I was initially trying to create a data structure that could be cast into a
Would result in memory being allocated in such a way that it could then be cast into |
Unfortunately, that isn't safe across different architectures. A C There are also no guarantees regarding byte order for things like a |
ae1664d
to
40e3508
Compare
Okay, I've removed all of the dynamic allocation and most of the functions, aside from init. I'm using a variadic function, with a custom data structure to simplify counting and passing arguments through to that function. Let me know how this looks. As usual, Mike, thanks for the patience and help working through this... |
40e3508
to
848bd1e
Compare
848bd1e
to
45b832b
Compare
@mike-jumper Any other changes needed to wrap this one up? |
src/protocols/ssh/ttymode.h
Outdated
#define GUAC_SSH_TTY_OP_VERASE 3 | ||
|
||
/** | ||
* Simple structure that defines a opcode and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way you could describe this which provides information/context beyond the members of the structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a stab at some more thorough documentation.
src/protocols/ssh/ttymode.h
Outdated
*/ | ||
typedef struct guac_ssh_ttymode { | ||
char opcode; | ||
uint32_t value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Structure members need to be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented.
src/protocols/ssh/ttymode.h
Outdated
} guac_ssh_ttymode; | ||
|
||
/** | ||
* Initialize an array with the specified opcode/value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initializes an array ... why? What's special about the array? Anything you can think of that might make the use of this function clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beefed up this documentation.
src/terminal/terminal.c
Outdated
if (keysym == 0xFF08) return guac_terminal_send_string(term, "\x7F"); /* Backspace */ | ||
/* Backspace can vary based on configuration of terminal by client. */ | ||
if (keysym == 0xFF08) { | ||
char* backspace_str = malloc(sizeof(char) * 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that dynamic allocation incurs overhead. In some cases, it makes good sense to do this, but it's hard to justify for a 2-byte string. A static buffer would be more appropriate here, particularly since you can initialize things cleanly and clearly inline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to static.
src/terminal/terminal.c
Outdated
char* backspace_str = malloc(sizeof(char) * 2); | ||
backspace_str[0] = term->backspace; | ||
backspace_str[1] = '\0'; | ||
return guac_terminal_send_string(term, backspace_str); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning here without freeing the memory allocated above will leak memory each time backspace is pressed. Probably shouldn't be dynamically allocating at all, though (see above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Staticized.
src/protocols/ssh/ttymode.c
Outdated
/* Put the end opcode in the last opcode space */ | ||
opcode_array[num_opcodes * GUAC_SSH_TTY_OPCODE_SIZE] = GUAC_SSH_TTY_OP_END; | ||
|
||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states that this function returns the size of the array (or rather, I assume, the number of bytes written to the array), but this isn't happening here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation has been fixed.
src/protocols/ssh/ttymode.h
Outdated
|
||
/** | ||
* Initialize an array with the specified opcode/value | ||
* pairs, and return the size, in bytes, of the array. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do mean the number of bytes written to the array? Ignoring that the size of the array is already known to the caller, returning the size of the array is not particularly useful, especially since the array may be larger than necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation updated.
src/protocols/ssh/ttymode.c
Outdated
int offset = i * GUAC_SSH_TTY_OPCODE_SIZE; | ||
|
||
/* Get the next argument to this function */ | ||
guac_ssh_ttymode ttymode = va_arg(args, guac_ssh_ttymode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The structure in this case is rather small, so this does not make much of a difference in this case, but beware that passing structures by value will result in a full copy of that structure, with this assignment resulting in another full copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to do this by pointer/reference rather than value? I'm not familiar with the va_arg macro and how to use it - do I just change the argument to &guac_ssh_ttymode
and the assignment to guac_ssh_ttymode* ttymode
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I got this going, too - again, probably worth someone taking a look just to make sure. Seems to work, though.
src/protocols/ssh/ttymode.c
Outdated
|
||
/* Place opcode and value in array */ | ||
opcode_array[offset] = ttymode.opcode; | ||
opcode_array[offset + 1] = (ttymode.value >> 24) & 0xFF; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to manually calculate the offset for each byte written. If you have a pointer to the next byte to be written within the array, this could be as simple as:
*(current++) = whatever_you_want_to_write;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, took a stab at this, and I think I got it working correctly - at least, the code runs and behaves as expected. Might be worth someone looking it over, though, just to make sure I did it in a way that isn't going to hit some corner case at some point down the road.
src/protocols/ssh/ttymode.c
Outdated
} | ||
|
||
/* Put the end opcode in the last opcode space */ | ||
opcode_array[num_opcodes * GUAC_SSH_TTY_OPCODE_SIZE] = GUAC_SSH_TTY_OP_END; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To confirm: the final "END" does not need the full 5-byte opcode and value? Just the opcode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct - as soon as it encounters the 0 opcode in one of the expected positions (5 byte offsets, basically) it stops.
Okay, @mike-jumper, next round of updates done - let me know what else needs attention! |
src/protocols/ssh/ttymode.c
Outdated
/* Put the end opcode in the last opcode space */ | ||
*(current) = GUAC_SSH_TTY_OP_END; | ||
|
||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend instead returning the size of the data written to the array, with zero indicating failure. This would remove the need for the caller to know ahead of time the size of the data this function will write (which is an odd requirement for what is otherwise an abstraction).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good - is there a more elegant way than just adding an integer byte counter and incrementing along the way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gone the byte counter route - if you have a suggestion for another way to do it, let me know.
src/terminal/terminal.c
Outdated
if (keysym == 0xFF08) { | ||
char backspace_str[2]; | ||
backspace_str[0] = term->backspace; | ||
backspace_str[1] = '\0'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can clean this up as:
char backspace_str[2] = { term->backspace, '\0' };
or even:
char backspace_str[] = { term->backspace, '\0' };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course. Done & thanks.
src/protocols/ssh/ssh.c
Outdated
/* Set up the ttymode array prior to requesting the PTY */ | ||
if (guac_ssh_ttymodes_init(ssh_ttymodes, sizeof(ssh_ttymodes), | ||
num_tty_opcodes, (guac_ssh_ttymode){ GUAC_SSH_TTY_OP_VERASE, settings->backspace})) | ||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error configuring TTY mode encoding."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "configuring TTY mode encoding"? Is there a way to phrase this which would better represent what is failing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to clean up this error message bit.
src/protocols/ssh/ttymode.c
Outdated
|
||
/* Initialize the variable argument list. */ | ||
va_list args; | ||
va_start(args, num_opcodes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There must be a matching call to va_end()
before the end of this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
src/protocols/ssh/ssh.c
Outdated
@@ -296,9 +301,15 @@ void* ssh_client_thread(void* data) { | |||
|
|||
} | |||
|
|||
/* Set up the ttymode array prior to requesting the PTY */ | |||
if (guac_ssh_ttymodes_init(ssh_ttymodes, sizeof(ssh_ttymodes), | |||
num_tty_opcodes, (guac_ssh_ttymode){ GUAC_SSH_TTY_OP_VERASE, settings->backspace})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the source for guac_ssh_ttymodes_init()
, doesn't this need to be a pointer to a guac_ssh_ttymode
? ie: &((guac_ssh_ttymode) { GUAC_SSH_TTY_OP_VERASE, settings->backspace })
?
Alternatively, there's no need for all these parameters to be of the same type. It would also be possible to do something like:
if (guac_ssh_ttymodes_init(ssh_ttymodes, sizeof(ssh_ttymodes),
GUAC_SSH_TTY_OP_VERASE, settings->backspace, GUAC_SSH_TTY_OP_END))
The code is your oyster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored the guac_ssh_ttymodes_init()
function. I may have introduced some other bugs or conditions that need to be handled, so let me know what you think about it.
src/protocols/ssh/ssh.c
Outdated
@@ -191,6 +192,10 @@ void* ssh_client_thread(void* data) { | |||
return NULL; | |||
} | |||
|
|||
/* Initialize a ttymode array */ | |||
const int num_tty_opcodes = 1; | |||
char ssh_ttymodes[(GUAC_SSH_TTY_OPCODE_SIZE * num_tty_opcodes) + 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a dealbreaker, but if you want to ensure this is a constant expression while also abstracting away the calculations required, this could be handled via a macro defined in ttymode.h
. For example:
#define GUAC_SSH_TTYMODES_SIZE(num_opcodes) ((GUAC_SSH_TTY_OPCODE_SIZE * num_opcodes) + 1)
You could then rely upon:
char ssh_ttymodes[GUAC_SSH_TTYMODES_SIZE(1)];
... and, of course, documenting within guac_ssh_ttymodes_init()
how the minimum size for the provided buffer should be calculated.
Magic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brilliant. I've used your macro and refactored code and comments with that.
src/protocols/ssh/ssh.c
Outdated
@@ -191,6 +192,10 @@ void* ssh_client_thread(void* data) { | |||
return NULL; | |||
} | |||
|
|||
/* Initialize a ttymode array */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point of clarification - nothing is being initialized here, only declared. With that much being obvious (that the array is being declared), rather than change "Initialize" to "Declare", I'd say just kill this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
Cleaned those items up. |
a91dfe2
to
b441181
Compare
src/protocols/ssh/ssh.c
Outdated
int ttymodeBytes = guac_ssh_ttymodes_init(ssh_ttymodes, | ||
GUAC_SSH_TTY_OP_VERASE, settings->backspace, GUAC_SSH_TTY_OP_END); | ||
if (ttymodeBytes < 1) | ||
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error storing TTY mode encoding \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will actually log:
Error storing TTY mode encoding opcodes and values in array.
as the whitespace at the beginning of the following line is part of the string.
To split strings in C, all you need to do is place two strings next to each other. They're automatically concatenated by the compiler:
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Error storing TTY mode encoding "
"opcodes and values in array.");
As this message is meant for the user/admin, I suggest rephrasing to describe things from that perspective. For example:
Unable to set TTY modes. Backspace may not work as expected.
at the warning level may make more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to warning.
src/protocols/ssh/ttymode.c
Outdated
/* We're done processing arguments. */ | ||
va_end(args); | ||
|
||
return bytes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI - you can actually do:
current - opcode_array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, of course.
src/protocols/ssh/ttymode.h
Outdated
* the size of the number of opcodes plus the single byte | ||
* end opcode. | ||
*/ | ||
#define GUAC_SSH_TTYMODES_SIZE(num_opcodes) ((GUAC_SSH_TTY_OPCODE_SIZE * num_opcodes) + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document num_opcodes
with @param
, just as you would for a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented that, and also documented the return value with @returns
- not sure if that should be done, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, definitely. Good catch.
Next round done. |
Server-side changes that allow the backspace key to be configured, and, for the SSH protocol, also pass through that behavior to the SSH server via the TTY Mode Encoding options. This replaces #153.