GUACAMOLE-574: Merge allow SSH/Telnet input from STDIN pipe.

This commit is contained in:
Nick Couchman 2018-06-18 20:17:42 -04:00
commit c120aa0274
12 changed files with 514 additions and 9 deletions

View File

@ -26,6 +26,7 @@ libguac_client_ssh_la_SOURCES = \
client.c \
clipboard.c \
input.c \
pipe.c \
settings.c \
sftp.c \
ssh.c \
@ -36,6 +37,7 @@ noinst_HEADERS = \
client.h \
clipboard.h \
input.h \
pipe.h \
settings.h \
sftp.h \
ssh.h \

50
src/protocols/ssh/pipe.c Normal file
View File

@ -0,0 +1,50 @@
/*
* 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 "pipe.h"
#include "ssh.h"
#include "terminal/terminal.h"
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <string.h>
int guac_ssh_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name) {
guac_client* client = user->client;
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
/* Redirect STDIN if pipe has required name */
if (strcmp(name, GUAC_SSH_STDIN_PIPE_NAME) == 0) {
guac_terminal_send_stream(ssh_client->term, user, stream);
return 0;
}
/* No other inbound pipe streams are supported */
guac_protocol_send_ack(user->socket, stream, "No such input stream.",
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
guac_socket_flush(user->socket);
return 0;
}

42
src/protocols/ssh/pipe.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_SSH_PIPE_H
#define GUAC_SSH_PIPE_H
#include "config.h"
#include <guacamole/user.h>
/**
* The name reserved for the inbound pipe stream which forces the terminal
* emulator's STDIN to be received from the pipe.
*/
#define GUAC_SSH_STDIN_PIPE_NAME "STDIN"
/**
* Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe
* is named "STDIN", the the contents of the pipe stream are redirected to
* STDIN of the terminal emulator for as long as the pipe is open.
*/
guac_user_pipe_handler guac_ssh_pipe_handler;
#endif

View File

@ -23,6 +23,7 @@
#include "common/display.h"
#include "input.h"
#include "user.h"
#include "pipe.h"
#include "sftp.h"
#include "ssh.h"
#include "settings.h"
@ -83,6 +84,9 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
user->mouse_handler = guac_ssh_user_mouse_handler;
user->clipboard_handler = guac_ssh_clipboard_handler;
/* STDIN redirection */
user->pipe_handler = guac_ssh_pipe_handler;
/* Display size change events */
user->size_handler = guac_ssh_user_size_handler;

View File

@ -26,6 +26,7 @@ libguac_client_telnet_la_SOURCES = \
client.c \
clipboard.c \
input.c \
pipe.c \
settings.c \
telnet.c \
user.c
@ -34,6 +35,7 @@ noinst_HEADERS = \
client.h \
clipboard.h \
input.h \
pipe.h \
settings.h \
telnet.h \
user.h

View File

@ -0,0 +1,50 @@
/*
* 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 "pipe.h"
#include "telnet.h"
#include "terminal/terminal.h"
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <string.h>
int guac_telnet_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name) {
guac_client* client = user->client;
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
/* Redirect STDIN if pipe has required name */
if (strcmp(name, GUAC_TELNET_STDIN_PIPE_NAME) == 0) {
guac_terminal_send_stream(telnet_client->term, user, stream);
return 0;
}
/* No other inbound pipe streams are supported */
guac_protocol_send_ack(user->socket, stream, "No such input stream.",
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
guac_socket_flush(user->socket);
return 0;
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_TELNET_PIPE_H
#define GUAC_TELNET_PIPE_H
#include "config.h"
#include <guacamole/user.h>
/**
* The name reserved for the inbound pipe stream which forces the terminal
* emulator's STDIN to be received from the pipe.
*/
#define GUAC_TELNET_STDIN_PIPE_NAME "STDIN"
/**
* Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe
* is named "STDIN", the the contents of the pipe stream are redirected to
* STDIN of the terminal emulator for as long as the pipe is open.
*/
guac_user_pipe_handler guac_telnet_pipe_handler;
#endif

View File

@ -21,6 +21,7 @@
#include "clipboard.h"
#include "input.h"
#include "pipe.h"
#include "settings.h"
#include "telnet.h"
#include "terminal/terminal.h"
@ -82,6 +83,9 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
user->mouse_handler = guac_telnet_user_mouse_handler;
user->clipboard_handler = guac_telnet_clipboard_handler;
/* STDIN redirection */
user->pipe_handler = guac_telnet_pipe_handler;
/* Display size change events */
user->size_handler = guac_telnet_user_size_handler;

View File

@ -48,6 +48,7 @@ libguac_terminal_la_SOURCES = \
select.c \
terminal.c \
terminal_handlers.c \
terminal-stdin-stream.c \
typescript.c \
xparsecolor.c

View File

@ -0,0 +1,156 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "terminal/common.h"
#include "terminal/terminal.h"
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
/**
* Handler for "blob" instructions which writes the data of received
* blobs to STDIN of the terminal associated with the stream.
*
* @see guac_user_blob_handler
*/
static int guac_terminal_input_stream_blob_handler(guac_user* user,
guac_stream* stream, void* data, int length) {
guac_terminal* term = (guac_terminal*) stream->data;
/* Attempt to write received data */
guac_terminal_lock(term);
int result = guac_terminal_write_all(term->stdin_pipe_fd[1], data, length);
guac_terminal_unlock(term);
/* Acknowledge receipt of data and result of write attempt */
if (result <= 0) {
guac_user_log(user, GUAC_LOG_DEBUG,
"Attempt to write to STDIN via an inbound stream failed.");
guac_protocol_send_ack(user->socket, stream,
"Attempt to write to STDIN failed.",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
else {
guac_user_log(user, GUAC_LOG_DEBUG,
"%i bytes successfully written to STDIN from an inbound stream.",
length);
guac_protocol_send_ack(user->socket, stream,
"Data written to STDIN.",
GUAC_PROTOCOL_STATUS_SUCCESS);
}
guac_socket_flush(user->socket);
return 0;
}
/**
* Handler for "end" instructions which disassociates the given
* stream from the terminal, allowing user input to resume.
*
* @see guac_user_end_handler
*/
static int guac_terminal_input_stream_end_handler(guac_user* user,
guac_stream* stream) {
guac_terminal* term = (guac_terminal*) stream->data;
/* Reset input stream, unblocking user input */
guac_terminal_lock(term);
term->input_stream = NULL;
guac_terminal_unlock(term);
guac_user_log(user, GUAC_LOG_DEBUG, "Inbound stream closed. User input "
"will now resume affecting STDIN.");
return 0;
}
/**
* Internal implementation of guac_terminal_send_stream() which assumes
* that the guac_terminal has already been locked through a call to
* guac_terminal_lock(). The semantics of all parameters and the return
* value are identical to guac_terminal_send_stream().
*
* @see guac_terminal_send_stream()
*/
static int __guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream) {
/* If a stream is already being used for STDIN, deny creation of
* further streams */
if (term->input_stream != NULL) {
guac_user_log(user, GUAC_LOG_DEBUG, "Attempt to direct the contents "
"of an inbound stream to STDIN denied. STDIN is already "
"being read from an inbound stream.");
guac_protocol_send_ack(user->socket, stream,
"STDIN is already being read from a stream.",
GUAC_PROTOCOL_STATUS_RESOURCE_CONFLICT);
guac_socket_flush(user->socket);
return 1;
}
guac_user_log(user, GUAC_LOG_DEBUG, "Now reading STDIN from inbound "
"stream. User input will no longer affect STDIN until the "
"stream is closed.");
stream->blob_handler = guac_terminal_input_stream_blob_handler;
stream->end_handler = guac_terminal_input_stream_end_handler;
stream->data = term;
/* Block user input until stream is ended */
term->input_stream = stream;
/* Acknowledge redirection from stream */
guac_protocol_send_ack(user->socket, stream,
"Now reading STDIN from stream.",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
int guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream) {
int result;
guac_terminal_lock(term);
result = __guac_terminal_send_stream(term, user, stream);
guac_terminal_unlock(term);
return result;
}

View File

@ -604,6 +604,9 @@ guac_terminal* guac_terminal_create(guac_client* client,
return NULL;
}
/* Read input from keyboard by default */
term->input_stream = NULL;
/* Init pipe stream (output to display by default) */
term->pipe_stream = NULL;
@ -1565,11 +1568,23 @@ void guac_terminal_unlock(guac_terminal* terminal) {
}
int guac_terminal_send_data(guac_terminal* term, const char* data, int length) {
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
return guac_terminal_write_all(term->stdin_pipe_fd[1], data, length);
}
int guac_terminal_send_string(guac_terminal* term, const char* data) {
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
return guac_terminal_write_all(term->stdin_pipe_fd[1], data, strlen(data));
}
static int __guac_terminal_send_key(guac_terminal* term, int keysym, int pressed) {
@ -1867,6 +1882,10 @@ int guac_terminal_sendf(guac_terminal* term, const char* format, ...) {
va_list ap;
char buffer[1024];
/* Block all other sources of input if input is coming from a stream */
if (term->input_stream != NULL)
return 0;
/* Print to buffer */
va_start(ap, format);
written = vsnprintf(buffer, sizeof(buffer)-1, format, ap);

View File

@ -200,6 +200,14 @@ struct guac_terminal {
*/
int stdin_pipe_fd[2];
/**
* The currently-open pipe stream from which all terminal input should be
* read, if any. If no pipe stream is open, terminal input will be received
* through keyboard, clipboard, and mouse events, and this value will be
* NULL.
*/
guac_stream* input_stream;
/**
* The currently-open pipe stream to which all terminal output should be
* written, if any. If no pipe stream is open, terminal output will be
@ -518,9 +526,9 @@ int guac_terminal_render_frame(guac_terminal* terminal);
/**
* Reads from this terminal's STDIN. Input comes from key and mouse events
* supplied by calls to guac_terminal_send_key() and
* guac_terminal_send_mouse(). If input is not yet available, this function
* will block.
* supplied by calls to guac_terminal_send_key(),
* guac_terminal_send_mouse(), and guac_terminal_send_stream(). If input is not
* yet available, this function will block.
*/
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
@ -576,17 +584,94 @@ int guac_terminal_printf(guac_terminal* terminal, const char* format, ...);
/**
* Handles the given key event, sending data, scrolling, pasting clipboard
* data, etc. as necessary.
* data, etc. as necessary. If terminal input is currently coming from a
* stream due to a prior call to guac_terminal_send_stream(), any input
* which would normally result from the key event is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param keysym
* The X11 keysym of the key that was pressed or released.
*
* @param pressed
* Non-zero if the key represented by the given keysym is currently
* pressed, zero if it is released.
*
* @return
* Zero if the key event was handled successfully, non-zero otherwise.
*/
int guac_terminal_send_key(guac_terminal* term, int keysym, int pressed);
/**
* Handles the given mouse event, sending data, scrolling, pasting clipboard
* data, etc. as necessary.
* data, etc. as necessary. If terminal input is currently coming from a
* stream due to a prior call to guac_terminal_send_stream(), any input
* which would normally result from the mouse event is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param user
* The user that originated the mouse event.
*
* @param x
* The X coordinate of the mouse 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 mouse within the display when the event
* occurred, in pixels. This value is not guaranteed to be within the
* bounds of the display area.
*
* @param mask
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*
* @return
* Zero if the mouse event was handled successfully, non-zero otherwise.
*/
int guac_terminal_send_mouse(guac_terminal* term, guac_user* user,
int x, int y, int mask);
/**
* Initializes the handlers of the given guac_stream such that it serves as the
* source of input to the terminal. Other input sources will be temporarily
* ignored until the stream is closed through receiving an "end" instruction.
* If input is already being read from a stream due to a prior call to
* guac_terminal_send_stream(), the prior call will remain in effect and this
* call will fail.
*
* Calling this function will overwrite the data member of the given
* guac_stream.
*
* @param term
* The terminal emulator which should receive input from the given stream.
*
* @param user
* The user that opened the stream.
*
* @param stream
* The guac_stream which should serve as the source of input for the
* terminal.
*
* @return
* Zero if the terminal input has successfully been configured to read from
* the given stream, non-zero otherwise.
*/
int guac_terminal_send_stream(guac_terminal* term, guac_user* user,
guac_stream* stream);
/**
* Handles a scroll event received from the scrollbar associated with a
* terminal.
@ -740,18 +825,66 @@ int guac_terminal_resize(guac_terminal* term, int width, int height);
void guac_terminal_flush(guac_terminal* terminal);
/**
* Sends the given string as if typed by the user.
* Sends the given string as if typed by the user. If terminal input is
* currently coming from a stream due to a prior call to
* guac_terminal_send_stream(), any input which would normally result from
* invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param data
* The data the terminal should receive on STDIN.
*
* @param length
* The size of the given data, in bytes.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/
int guac_terminal_send_data(guac_terminal* term, const char* data, int length);
/**
* Sends the given string as if typed by the user.
* Sends the given string as if typed by the user. If terminal input is
* currently coming from a stream due to a prior call to
* guac_terminal_send_stream(), any input which would normally result from
* invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param data
* The data the terminal should receive on STDIN.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/
int guac_terminal_send_string(guac_terminal* term, const char* data);
/**
* Sends data through STDIN as if typed by the user, using the format
* string given and any args (similar to printf).
* Sends data through STDIN as if typed by the user, using the format string
* given and any args (similar to printf). If terminal input is currently
* coming from a stream due to a prior call to guac_terminal_send_stream(), any
* input which would normally result from invoking this function is dropped.
*
* @param term
* The terminal which should receive the given data on STDIN.
*
* @param format
* A printf-style format string describing the data to be received on
* STDIN.
*
* @param ...
* Any srguments to use when filling the format string.
*
* @return
* The number of bytes written to STDIN, or a negative value if an error
* occurs preventing the data from being written. This should always be
* the size of the data given unless data is intentionally dropped.
*/
int guac_terminal_sendf(guac_terminal* term, const char* format, ...);