From 97593958e4ecb5601f828a473ac451aa3b33304d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 19 May 2018 14:28:09 -0700 Subject: [PATCH] GUACAMOLE-574: Add support for reading STDIN from a pipe stream. --- src/terminal/Makefile.am | 1 + src/terminal/terminal-stdin-stream.c | 156 +++++++++++++++++++++++++++ src/terminal/terminal.c | 19 ++++ src/terminal/terminal/terminal.h | 151 ++++++++++++++++++++++++-- 4 files changed, 318 insertions(+), 9 deletions(-) create mode 100644 src/terminal/terminal-stdin-stream.c diff --git a/src/terminal/Makefile.am b/src/terminal/Makefile.am index 1fbded1a..1a609324 100644 --- a/src/terminal/Makefile.am +++ b/src/terminal/Makefile.am @@ -48,6 +48,7 @@ libguac_terminal_la_SOURCES = \ select.c \ terminal.c \ terminal_handlers.c \ + terminal-stdin-stream.c \ typescript.c \ xparsecolor.c diff --git a/src/terminal/terminal-stdin-stream.c b/src/terminal/terminal-stdin-stream.c new file mode 100644 index 00000000..1f662d67 --- /dev/null +++ b/src/terminal/terminal-stdin-stream.c @@ -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 +#include +#include + +/** + * 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; + +} + diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index ce89856e..b24ec8ea 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -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); diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 8d6b48ff..adadc4fe 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -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, ...);