From ebc731aaf390ca06019af58bb6c512faa4605ee6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 15:44:03 -0800 Subject: [PATCH 01/10] GUACAMOLE-313: Add guaclog utility with stubbed interpretation of key events. --- Makefile.am | 5 ++ configure.ac | 16 ++++ src/guaclog/.gitignore | 5 ++ src/guaclog/Makefile.am | 51 ++++++++++++ src/guaclog/guaclog.c | 119 ++++++++++++++++++++++++++ src/guaclog/guaclog.h | 31 +++++++ src/guaclog/instruction-key.c | 43 ++++++++++ src/guaclog/instructions.c | 62 ++++++++++++++ src/guaclog/instructions.h | 108 ++++++++++++++++++++++++ src/guaclog/interpret.c | 152 ++++++++++++++++++++++++++++++++++ src/guaclog/interpret.h | 51 ++++++++++++ src/guaclog/log.c | 85 +++++++++++++++++++ src/guaclog/log.h | 73 ++++++++++++++++ src/guaclog/man/guaclog.1.in | 60 ++++++++++++++ src/guaclog/state.c | 97 ++++++++++++++++++++++ src/guaclog/state.h | 89 ++++++++++++++++++++ 16 files changed, 1047 insertions(+) create mode 100644 src/guaclog/.gitignore create mode 100644 src/guaclog/Makefile.am create mode 100644 src/guaclog/guaclog.c create mode 100644 src/guaclog/guaclog.h create mode 100644 src/guaclog/instruction-key.c create mode 100644 src/guaclog/instructions.c create mode 100644 src/guaclog/instructions.h create mode 100644 src/guaclog/interpret.c create mode 100644 src/guaclog/interpret.h create mode 100644 src/guaclog/log.c create mode 100644 src/guaclog/log.h create mode 100644 src/guaclog/man/guaclog.1.in create mode 100644 src/guaclog/state.c create mode 100644 src/guaclog/state.h diff --git a/Makefile.am b/Makefile.am index 8dfa9d1fad..e9233760c9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,6 +27,7 @@ DIST_SUBDIRS = \ src/terminal \ src/guacd \ src/guacenc \ + src/guaclog \ src/pulse \ src/protocols/rdp \ src/protocols/ssh \ @@ -75,6 +76,10 @@ if ENABLE_GUACENC SUBDIRS += src/guacenc endif +if ENABLE_GUACLOG +SUBDIRS += src/guaclog +endif + EXTRA_DIST = \ .dockerignore \ CONTRIBUTING \ diff --git a/configure.ac b/configure.ac index 1906dc861a..8ae0743037 100644 --- a/configure.ac +++ b/configure.ac @@ -1191,6 +1191,18 @@ AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \ -a "x${have_libavutil}" = "xyes" \ -a "x${have_libswscale}" = "xyes"]) +# +# guaclog +# + +AC_ARG_ENABLE([guaclog], + [AS_HELP_STRING([--disable-guaclog], + [do not build the Guacamole input logging tool])], + [], + [enable_guaclog=yes]) + +AM_CONDITIONAL([ENABLE_GUACLOG], [test "x${enable_guaclog}" = "xyes"]) + # # Output Makefiles # @@ -1207,6 +1219,8 @@ AC_CONFIG_FILES([Makefile src/guacd/man/guacd.conf.5 src/guacenc/Makefile src/guacenc/man/guacenc.1 + src/guaclog/Makefile + src/guaclog/man/guaclog.1 src/pulse/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile @@ -1229,6 +1243,7 @@ AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) AM_COND_IF([ENABLE_GUACD], [build_guacd=yes], [build_guacd=no]) AM_COND_IF([ENABLE_GUACENC], [build_guacenc=yes], [build_guacenc=no]) +AM_COND_IF([ENABLE_GUACLOG], [build_guaclog=yes], [build_guaclog=no]) # # Init scripts @@ -1272,6 +1287,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION guacd ...... ${build_guacd} guacenc .... ${build_guacenc} + guaclog .... ${build_guaclog} Init scripts: ${build_init} diff --git a/src/guaclog/.gitignore b/src/guaclog/.gitignore new file mode 100644 index 0000000000..4865f64557 --- /dev/null +++ b/src/guaclog/.gitignore @@ -0,0 +1,5 @@ + +# Compiled guaclog +guaclog +guaclog.exe + diff --git a/src/guaclog/Makefile.am b/src/guaclog/Makefile.am new file mode 100644 index 0000000000..f1ad15218b --- /dev/null +++ b/src/guaclog/Makefile.am @@ -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. +# + +AUTOMAKE_OPTIONS = foreign + +bin_PROGRAMS = guaclog + +man_MANS = \ + man/guaclog.1 + +noinst_HEADERS = \ + guaclog.h \ + instructions.h \ + interpret.h \ + log.h \ + state.h + +guaclog_SOURCES = \ + guaclog.c \ + instructions.c \ + instruction-key.c \ + interpret.c \ + log.c \ + state.c + +guaclog_CFLAGS = \ + -Werror -Wall \ + @LIBGUAC_INCLUDE@ + +guaclog_LDADD = \ + @LIBGUAC_LTLIB@ + +EXTRA_DIST = \ + man/guaclog.1.in + diff --git a/src/guaclog/guaclog.c b/src/guaclog/guaclog.c new file mode 100644 index 0000000000..43502897ac --- /dev/null +++ b/src/guaclog/guaclog.c @@ -0,0 +1,119 @@ +/* + * 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 "guaclog.h" +#include "interpret.h" +#include "log.h" + +#include +#include +#include + +int main(int argc, char* argv[]) { + + int i; + + /* Load defaults */ + bool force = false; + + /* Parse arguments */ + int opt; + while ((opt = getopt(argc, argv, "s:r:f")) != -1) { + + /* -f: Force */ + if (opt == 'f') + force = true; + + /* Invalid option */ + else { + goto invalid_options; + } + + } + + /* Log start */ + guaclog_log(GUAC_LOG_INFO, "Guacamole input log interpreter (guaclog) " + "version " VERSION); + + /* Track number of overall failures */ + int total_files = argc - optind; + int failures = 0; + + /* Abort if no files given */ + if (total_files <= 0) { + guaclog_log(GUAC_LOG_INFO, "No input files specified. Nothing to do."); + return 0; + } + + guaclog_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files); + + /* Interpret all input files */ + for (i = optind; i < argc; i++) { + + /* Get current filename */ + const char* path = argv[i]; + + /* Generate output filename */ + char out_path[4096]; + int len = snprintf(out_path, sizeof(out_path), "%s.txt", path); + + /* Do not write if filename exceeds maximum length */ + if (len >= sizeof(out_path)) { + guaclog_log(GUAC_LOG_ERROR, "Cannot write output file for \"%s\": " + "Name too long", path); + continue; + } + + /* Attempt interpreting, log granular success/failure at debug level */ + if (guaclog_interpret(path, out_path, force)) { + failures++; + guaclog_log(GUAC_LOG_DEBUG, + "%s was NOT successfully interpreted.", path); + } + else + guaclog_log(GUAC_LOG_DEBUG, "%s was successfully " + "interpreted.", path); + + } + + /* Warn if at least one file failed */ + if (failures != 0) + guaclog_log(GUAC_LOG_WARNING, "Interpreting failed for %i of %i " + "file(s).", failures, total_files); + + /* Notify of success */ + else + guaclog_log(GUAC_LOG_INFO, "All files interpreted successfully."); + + /* Interpreting complete */ + return 0; + + /* Display usage and exit with error if options are invalid */ +invalid_options: + + fprintf(stderr, "USAGE: %s" + " [-f]" + " [FILE]...\n", argv[0]); + + return 1; + +} + diff --git a/src/guaclog/guaclog.h b/src/guaclog/guaclog.h new file mode 100644 index 0000000000..7390314eb5 --- /dev/null +++ b/src/guaclog/guaclog.h @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUACLOG_H +#define GUACLOG_H + +#include "config.h" + +/** + * The default log level below which no messages should be logged. + */ +#define GUACLOG_DEFAULT_LOG_LEVEL GUAC_LOG_INFO + +#endif + diff --git a/src/guaclog/instruction-key.c b/src/guaclog/instruction-key.c new file mode 100644 index 0000000000..0e8501b03e --- /dev/null +++ b/src/guaclog/instruction-key.c @@ -0,0 +1,43 @@ +/* + * 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 "log.h" +#include "state.h" + +#include +#include + +int guaclog_handle_key(guaclog_state* state, int argc, char** argv) { + + /* Verify argument count */ + if (argc < 2) { + guaclog_log(GUAC_LOG_WARNING, "\"key\" instruction incomplete"); + return 1; + } + + /* Parse arguments */ + int keysym = atoi(argv[0]); + bool pressed = (atoi(argv[1]) != 0); + + /* Update interpreter state accordingly */ + return guaclog_state_update_key(state, keysym, pressed); + +} + diff --git a/src/guaclog/instructions.c b/src/guaclog/instructions.c new file mode 100644 index 0000000000..257e1343bf --- /dev/null +++ b/src/guaclog/instructions.c @@ -0,0 +1,62 @@ +/* + * 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 "state.h" +#include "instructions.h" +#include "log.h" + +#include + +guaclog_instruction_handler_mapping guaclog_instruction_handler_map[] = { + {"key", guaclog_handle_key}, + {NULL, NULL} +}; + +int guaclog_handle_instruction(guaclog_state* state, const char* opcode, + int argc, char** argv) { + + /* Search through mapping for instruction handler having given opcode */ + guaclog_instruction_handler_mapping* current = guaclog_instruction_handler_map; + while (current->opcode != NULL) { + + /* Invoke handler if opcode matches (if defined) */ + if (strcmp(current->opcode, opcode) == 0) { + + /* Invoke defined handler */ + guaclog_instruction_handler* handler = current->handler; + if (handler != NULL) + return handler(state, argc, argv); + + /* Log defined but unimplemented instructions */ + guaclog_log(GUAC_LOG_DEBUG, "\"%s\" not implemented", opcode); + return 0; + + } + + /* Next candidate handler */ + current++; + + } /* end opcode search */ + + /* Ignore any unknown instructions */ + return 0; + +} + diff --git a/src/guaclog/instructions.h b/src/guaclog/instructions.h new file mode 100644 index 0000000000..0c901f53c9 --- /dev/null +++ b/src/guaclog/instructions.h @@ -0,0 +1,108 @@ +/* + * 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 GUACLOG_INSTRUCTIONS_H +#define GUACLOG_INSTRUCTIONS_H + +#include "config.h" +#include "state.h" + +/** + * A callback function which, when invoked, handles a particular Guacamole + * instruction. The opcode of the instruction is implied (as it is expected + * that there will be a 1:1 mapping of opcode to callback function), while the + * arguments for that instruction are included in the parameters given to the + * callback. + * + * @param state + * The current state of the Guacamole input log interpreter. + * + * @param argc + * The number of arguments (excluding opcode) passed to the instruction + * being handled by the callback. + * + * @param argv + * All arguments (excluding opcode) associated with the instruction being + * handled by the callback. + * + * @return + * Zero if the instruction was handled successfully, non-zero if an error + * occurs. + */ +typedef int guaclog_instruction_handler(guaclog_state* state, + int argc, char** argv); + +/** + * Mapping of instruction opcode to corresponding handler function. + */ +typedef struct guaclog_instruction_handler_mapping { + + /** + * The opcode of the instruction that the associated handler function + * should be invoked for. + */ + const char* opcode; + + /** + * The handler function to invoke whenever an instruction having the + * associated opcode is parsed. + */ + guaclog_instruction_handler* handler; + +} guaclog_instruction_handler_mapping; + +/** + * Array of all opcode/handler mappings for all supported opcodes, terminated + * by an entry with a NULL opcode. All opcodes not listed here can be safely + * ignored. + */ +extern guaclog_instruction_handler_mapping guaclog_instruction_handler_map[]; + +/** + * Handles the instruction having the given opcode and arguments, updating + * the state of the interpreter accordingly. + * + * @param state + * The current state of the Guacamole input log interpreter. + * + * @param opcode + * The opcode of the instruction being handled. + * + * @param argc + * The number of arguments (excluding opcode) passed to the instruction + * being handled by the callback. + * + * @param argv + * All arguments (excluding opcode) associated with the instruction being + * handled by the callback. + * + * @return + * Zero if the instruction was handled successfully, non-zero if an error + * occurs. + */ +int guaclog_handle_instruction(guaclog_state* state, + const char* opcode, int argc, char** argv); + +/** + * Handler for the Guacamole "key" instruction. + */ +guaclog_instruction_handler guaclog_handle_key; + +#endif + diff --git a/src/guaclog/interpret.c b/src/guaclog/interpret.c new file mode 100644 index 0000000000..d996c24551 --- /dev/null +++ b/src/guaclog/interpret.c @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "instructions.h" +#include "log.h" +#include "state.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Reads and handles all Guacamole instructions from the given guac_socket + * until end-of-stream is reached. + * + * @param state + * The current state of the Guacamole input log interpreter. + * + * @param path + * The name of the file being parsed (for logging purposes). This file + * must already be open and available through the given socket. + * + * @param socket + * The guac_socket through which instructions should be read. + * + * @return + * Zero on success, non-zero if parsing of Guacamole protocol data through + * the given socket fails. + */ +static int guaclog_read_instructions(guaclog_state* state, + const char* path, guac_socket* socket) { + + /* Obtain Guacamole protocol parser */ + guac_parser* parser = guac_parser_alloc(); + if (parser == NULL) + return 1; + + /* Continuously read and handle all instructions */ + while (!guac_parser_read(parser, socket, -1)) { + guaclog_handle_instruction(state, parser->opcode, + parser->argc, parser->argv); + } + + /* Fail on read/parse error */ + if (guac_error != GUAC_STATUS_CLOSED) { + guaclog_log(GUAC_LOG_ERROR, "%s: %s", + path, guac_status_string(guac_error)); + guac_parser_free(parser); + return 1; + } + + /* Parse complete */ + guac_parser_free(parser); + return 0; + +} + +int guaclog_interpret(const char* path, const char* out_path, bool force) { + + /* Open input file */ + int fd = open(path, O_RDONLY); + if (fd < 0) { + guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, strerror(errno)); + return 1; + } + + /* Lock entire input file for reading by the current process */ + struct flock file_lock = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + .l_pid = getpid() + }; + + /* Abort if file cannot be locked for reading */ + if (!force && fcntl(fd, F_SETLK, &file_lock) == -1) { + + /* Warn if lock cannot be acquired */ + if (errno == EACCES || errno == EAGAIN) + guaclog_log(GUAC_LOG_WARNING, "Refusing to interpret log of " + "in-progress session \"%s\" (specify the -f option to " + "override this behavior).", path); + + /* Log an error if locking fails in an unexpected way */ + else + guaclog_log(GUAC_LOG_ERROR, "Cannot lock \"%s\" for reading: %s", + path, strerror(errno)); + + close(fd); + return 1; + } + + /* Allocate input state for interpreting process */ + guaclog_state* state = guaclog_state_alloc(out_path); + if (state == NULL) { + close(fd); + return 1; + } + + /* Obtain guac_socket wrapping file descriptor */ + guac_socket* socket = guac_socket_open(fd); + if (socket == NULL) { + guaclog_log(GUAC_LOG_ERROR, "%s: %s", path, + guac_status_string(guac_error)); + close(fd); + guaclog_state_free(state); + return 1; + } + + guaclog_log(GUAC_LOG_INFO, "Writing input events from \"%s\" " + "to \"%s\" ...", path, out_path); + + /* Attempt to read all instructions in the file */ + if (guaclog_read_instructions(state, path, socket)) { + guac_socket_free(socket); + guaclog_state_free(state); + return 1; + } + + /* Close input and finish interpreting process */ + guac_socket_free(socket); + return guaclog_state_free(state); + +} + diff --git a/src/guaclog/interpret.h b/src/guaclog/interpret.h new file mode 100644 index 0000000000..c65764e33c --- /dev/null +++ b/src/guaclog/interpret.h @@ -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. + */ + +#ifndef GUACLOG_INTERPRET_H +#define GUACLOG_INTERPRET_H + +#include "config.h" + +#include + +/** + * Interprets all input events within the given Guacamole protocol dump, + * producing a human-readable log of those input events. A read lock will be + * acquired on the input file to ensure that in-progress logs are not + * interpreted. This behavior can be overridden by specifying true for the + * force parameter. + * + * @param path + * The path to the file containing the raw Guacamole protocol dump. + * + * @param out_path + * The full path to the file in which interpreted log should be written. + * + * @param force + * Interpret even if the input file appears to be an in-progress log (has + * an associated lock). + * + * @return + * Zero on success, non-zero if an error prevented successful + * interpretation of the log. + */ +int guaclog_interpret(const char* path, const char* out_path, bool force); + +#endif + diff --git a/src/guaclog/log.c b/src/guaclog/log.c new file mode 100644 index 0000000000..e99abacc21 --- /dev/null +++ b/src/guaclog/log.c @@ -0,0 +1,85 @@ +/* + * 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 "guaclog.h" +#include "log.h" + +#include +#include + +#include +#include + +int guaclog_log_level = GUACLOG_DEFAULT_LOG_LEVEL; + +void vguaclog_log(guac_client_log_level level, const char* format, + va_list args) { + + const char* priority_name; + char message[2048]; + + /* Don't bother if the log level is too high */ + if (level > guaclog_log_level) + return; + + /* Copy log message into buffer */ + vsnprintf(message, sizeof(message), format, args); + + /* Convert log level to human-readable name */ + switch (level) { + + /* Error log level */ + case GUAC_LOG_ERROR: + priority_name = "ERROR"; + break; + + /* Warning log level */ + case GUAC_LOG_WARNING: + priority_name = "WARNING"; + break; + + /* Informational log level */ + case GUAC_LOG_INFO: + priority_name = "INFO"; + break; + + /* Debug log level */ + case GUAC_LOG_DEBUG: + priority_name = "DEBUG"; + break; + + /* Any unknown/undefined log level */ + default: + priority_name = "UNKNOWN"; + break; + } + + /* Log to STDERR */ + fprintf(stderr, GUACLOG_LOG_NAME ": %s: %s\n", priority_name, message); + +} + +void guaclog_log(guac_client_log_level level, const char* format, ...) { + va_list args; + va_start(args, format); + vguaclog_log(level, format, args); + va_end(args); +} + diff --git a/src/guaclog/log.h b/src/guaclog/log.h new file mode 100644 index 0000000000..bcf90a1cad --- /dev/null +++ b/src/guaclog/log.h @@ -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. + */ + +#ifndef GUACLOG_LOG_H +#define GUACLOG_LOG_H + +#include "config.h" + +#include + +#include + +/** + * The maximum level at which to log messages. All other messages will be + * dropped. + */ +extern int guaclog_log_level; + +/** + * The string to prepend to all log messages. + */ +#define GUACLOG_LOG_NAME "guaclog" + +/** + * Writes a message to guaclog's logs. This function takes a format and + * va_list, similar to vprintf. + * + * @param level + * The level at which to log this message. + * + * @param format + * A printf-style format string to log. + * + * @param args + * The va_list containing the arguments to be used when filling the format + * string for printing. + */ +void vguaclog_log(guac_client_log_level level, const char* format, + va_list args); + +/** + * Writes a message to guaclog's logs. This function accepts parameters + * identically to printf. + * + * @param level + * The level at which to log this message. + * + * @param format + * A printf-style format string to log. + * + * @param ... + * Arguments to use when filling the format string for printing. + */ +void guaclog_log(guac_client_log_level level, const char* format, ...); + +#endif + diff --git a/src/guaclog/man/guaclog.1.in b/src/guaclog/man/guaclog.1.in new file mode 100644 index 0000000000..11896d591b --- /dev/null +++ b/src/guaclog/man/guaclog.1.in @@ -0,0 +1,60 @@ +.\" +.\" 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. +.\" +.TH guaclog 1 "26 Jan 2018" "version @PACKAGE_VERSION@" "Apache Guacamole" +. +.SH NAME +guaclog \- Guacamole input log interpreter +. +.SH SYNOPSIS +.B guaclog +[\fB-f\fR] +[\fIFILE\fR]... +. +.SH DESCRIPTION +.B guaclog +is an interpreter which accepts Guacamole protocol dumps, such as those saved +when input logging is enabled on a Guacamole connection, writing human-readable +text logs as output. +.B guaclog +is essentially an implementation of a Guacamole client which accepts +its input from files instead of a network connection, however unlike +.B guacenc +it only handles instructions related to user input. +.P +Each \fIFILE\fR specified will be translated into a new human-readable text +file named \fIFILE\fR.txt. Existing files will not be overwritten; the +interpreting process for any input file will be aborted if it would result in +overwriting an existing file. +.P +Guacamole acquires a write lock on input logs as they are being written. By +default, +.B guaclog +will check whether the each input file is locked and will refuse to read and +interpret an input file if it appears to be an in-progress log. This behavior +can be overridden by specifying the \fB-f\fR option. Interpreting an +in-progress log will still work; the resulting human-readable text file will +simply cover the user's session only up to the current point in time. +. +.SH OPTIONS +.TP +\fB-f\fR +Overrides the default behavior of +.B guaclog +such that input files will be interpreted even if they appear to be logs of +in-progress Guacamole sessions. diff --git a/src/guaclog/state.c b/src/guaclog/state.c new file mode 100644 index 0000000000..6167bd30be --- /dev/null +++ b/src/guaclog/state.c @@ -0,0 +1,97 @@ +/* + * 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 "log.h" +#include "state.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +guaclog_state* guaclog_state_alloc(const char* path) { + + /* Open output file */ + int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd == -1) { + guaclog_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s", + path, strerror(errno)); + goto fail_output_fd; + } + + /* Create stream for output file */ + FILE* output = fdopen(fd, "wb"); + if (output == NULL) { + guaclog_log(GUAC_LOG_ERROR, "Failed to allocate stream for output " + "file \"%s\": %s", path, strerror(errno)); + goto fail_output_file; + } + + /* Allocate state */ + guaclog_state* state = (guaclog_state*) calloc(1, sizeof(guaclog_state)); + if (state == NULL) { + goto fail_state; + } + + /* Associate state with output file */ + state->output = output; + + return state; + + /* Free all allocated data in case of failure */ +fail_state: + fclose(output); + +fail_output_file: + close(fd); + +fail_output_fd: + return NULL; + +} + +int guaclog_state_free(guaclog_state* state) { + + /* Ignore NULL state */ + if (state == NULL) + return 0; + + /* Close output file */ + fclose(state->output); + + free(state); + return 0; + +} + +int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { + + /* STUB */ + fprintf(state->output, "STUB: keysym=0x%X, pressed=%s\n", + keysym, pressed ? "true" : "false"); + + return 0; + +} + diff --git a/src/guaclog/state.h b/src/guaclog/state.h new file mode 100644 index 0000000000..5891cb56a9 --- /dev/null +++ b/src/guaclog/state.h @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUACLOG_STATE_H +#define GUACLOG_STATE_H + +#include "config.h" + +#include +#include + +/** + * The current state of the Guacamole input log interpreter. + */ +typedef struct guaclog_state { + + /** + * Output file stream. + */ + FILE* output; + +} guaclog_state; + +/** + * Allocates a new state structure for the Guacamole input log interpreter. + * This structure serves as the representation of interpreter state as + * input-related instructions are read and handled. + * + * @param path + * The full path to the file in which interpreted, human-readable should be + * written. + * + * @return + * The newly-allocated Guacamole input log interpreter state, or NULL if + * the state could not be allocated. + */ +guaclog_state* guaclog_state_alloc(const char* path); + +/** + * Frees all memory associated with the given Guacamole input log interpreter + * state, and finishes any remaining interpreting process. If the given state + * is NULL, this function has no effect. + * + * @param state + * The Guacamole input log interpreter state to free, which may be NULL. + * + * @return + * Zero if the interpreting process completed successfully, non-zero + * otherwise. + */ +int guaclog_state_free(guaclog_state* state); + +/** + * Updates the given Guacamole input log interpreter state, marking the given + * key as pressed or released. + * + * @param state + * The Guacamole input log interpreter state being updated. + * + * @param keysym + * The X11 keysym of the key being pressed or released. + * + * @param pressed + * true if the key is being pressed, false if the key is being released. + * + * @return + * Zero if the interpreter state was updated successfully, non-zero + * otherwise. + */ +int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed); + +#endif + From d39757b4dc09ff7f255761ca9c2f18e105be626f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 17:24:49 -0800 Subject: [PATCH 02/10] GUACAMOLE-313: Continuously track key press/release. --- src/guaclog/state.c | 106 ++++++++++++++++++++++++++++++++++++++++++-- src/guaclog/state.h | 35 +++++++++++++++ 2 files changed, 138 insertions(+), 3 deletions(-) diff --git a/src/guaclog/state.c b/src/guaclog/state.c index 6167bd30be..cc6f7637b7 100644 --- a/src/guaclog/state.c +++ b/src/guaclog/state.c @@ -57,6 +57,9 @@ guaclog_state* guaclog_state_alloc(const char* path) { /* Associate state with output file */ state->output = output; + /* No keys are initially tracked */ + state->active_keys = 0; + return state; /* Free all allocated data in case of failure */ @@ -85,11 +88,108 @@ int guaclog_state_free(guaclog_state* state) { } +/** + * Adds the given key state to the array of tracked keys. If the key is already + * being tracked, its corresponding entry within the array of tracked keys is + * updated, and the number of tracked keys remains the same. If the key is not + * already being tracked, it is added to the end of the array of tracked keys + * providing there is space available, and the number of tracked keys is + * updated. Failures to add keys will be automatically logged. + * + * @param state + * The Guacamole input log interpreter state being updated. + * + * @param keysym + * The X11 keysym of the key being pressed or released. + * + * @param pressed + * true if the key is being pressed, false if the key is being released. + * + * @return + * Zero if the key state was successfully added, non-zero otherwise. + */ +static int guaclog_state_add_key(guaclog_state* state, int keysym, bool pressed) { + + int i; + + /* Update existing key, if already tracked */ + for (i = 0; i < state->active_keys; i++) { + guaclog_key_state* key = &state->key_states[i]; + if (key->keysym == keysym) { + key->pressed = pressed; + return 0; + } + } + + /* If not already tracked, we need space to add it */ + if (state->active_keys == GUACLOG_MAX_KEYS) { + guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many " + "active keys.", keysym); + return 1; + } + + /* Add key to state */ + guaclog_key_state* key = &state->key_states[state->active_keys++]; + key->keysym = keysym; + key->pressed = pressed; + return 0; + +} + +/** + * Removes released keys from the end of the array of tracked keys, such that + * the last key in the array is a pressed key. This function should be invoked + * after changes have been made to the interpreter state, to ensure that the + * array of tracked keys does not grow longer than necessary. + * + * @param state + * The Guacamole input log interpreter state to trim. + */ +static void guaclog_state_trim_keys(guaclog_state* state) { + + int i; + + /* Reset active_keys to contain only up to the last pressed key */ + for (i = state->active_keys - 1; i >= 0; i--) { + guaclog_key_state* key = &state->key_states[i]; + if (key->pressed) { + state->active_keys = i + 1; + return; + } + } + + /* No keys are active */ + state->active_keys = 0; + +} + int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { - /* STUB */ - fprintf(state->output, "STUB: keysym=0x%X, pressed=%s\n", - keysym, pressed ? "true" : "false"); + int i; + + /* Update tracked keysysm state */ + guaclog_state_add_key(state, keysym, pressed); + guaclog_state_trim_keys(state); + + /* Output new log entries only when keys are pressed */ + if (pressed) { + + /* STUB: Output raw hex log entry */ + for (i = 0; i < state->active_keys; i++) { + + if (i != 0) + fprintf(state->output, " "); + + guaclog_key_state* key = &state->key_states[i]; + fprintf(state->output, "0x%X:%s", key->keysym, + key->pressed ? "*" : " "); + + } + + /* Terminate log entry with newline */ + fprintf(state->output, "\n"); + + } return 0; diff --git a/src/guaclog/state.h b/src/guaclog/state.h index 5891cb56a9..0dd2f2f9db 100644 --- a/src/guaclog/state.h +++ b/src/guaclog/state.h @@ -25,6 +25,29 @@ #include #include +/** + * The maximum number of keys which may be tracked at any one time before + * newly-pressed keys are ignored. + */ +#define GUACLOG_MAX_KEYS 256 + +/** + * The current state of a single key. + */ +typedef struct guaclog_key_state { + + /** + * The X11 keysym of the key. + */ + int keysym; + + /** + * Whether the key is currently pressed (true) or released (false). + */ + bool pressed; + +} guaclog_key_state; + /** * The current state of the Guacamole input log interpreter. */ @@ -35,6 +58,18 @@ typedef struct guaclog_state { */ FILE* output; + /** + * The number of keys currently being tracked within the key_states array. + */ + int active_keys; + + /** + * Array of all keys currently being tracked. A key is added to the array + * when it is pressed for the first time. Released keys at the end of the + * array are automatically removed from tracking. + */ + guaclog_key_state key_states[GUACLOG_MAX_KEYS]; + } guaclog_state; /** From df29735c83c210900ba55d60c1e1a29b3c3ef03e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 18:00:29 -0800 Subject: [PATCH 03/10] GUACAMOLE-313: Separate naming logic for keysyms. Align previously-pressed keys. --- src/guaclog/Makefile.am | 2 ++ src/guaclog/key-name.c | 43 +++++++++++++++++++++++++++++++++++ src/guaclog/key-name.h | 50 +++++++++++++++++++++++++++++++++++++++++ src/guaclog/state.c | 22 ++++++++++++++---- 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/guaclog/key-name.c create mode 100644 src/guaclog/key-name.h diff --git a/src/guaclog/Makefile.am b/src/guaclog/Makefile.am index f1ad15218b..5007708c43 100644 --- a/src/guaclog/Makefile.am +++ b/src/guaclog/Makefile.am @@ -28,6 +28,7 @@ noinst_HEADERS = \ guaclog.h \ instructions.h \ interpret.h \ + key-name.h \ log.h \ state.h @@ -36,6 +37,7 @@ guaclog_SOURCES = \ instructions.c \ instruction-key.c \ interpret.c \ + key-name.c \ log.c \ state.c diff --git a/src/guaclog/key-name.c b/src/guaclog/key-name.c new file mode 100644 index 0000000000..ffb622d13f --- /dev/null +++ b/src/guaclog/key-name.c @@ -0,0 +1,43 @@ +/* + * 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 "key-name.h" +#include "log.h" + +#include + +int guaclog_key_name(char* key_name, int keysym) { + + /* Fallback to using hex keysym as name */ + int name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, + "0x%X", keysym); + + /* Truncate name if necessary */ + if (name_length >= GUACLOG_MAX_KEY_NAME_LENGTH) { + name_length = GUACLOG_MAX_KEY_NAME_LENGTH - 1; + key_name[name_length] = '\0'; + guaclog_log(GUAC_LOG_DEBUG, "Name for key 0x%X was " + "truncated.", keysym); + } + + return name_length; + +} + diff --git a/src/guaclog/key-name.h b/src/guaclog/key-name.h new file mode 100644 index 0000000000..a033b36c30 --- /dev/null +++ b/src/guaclog/key-name.h @@ -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. + */ + +#ifndef GUACLOG_KEY_NAME_H +#define GUACLOG_KEY_NAME_H + +#include "config.h" + +/** + * The maximum size of the name of any key, in bytes. + */ +#define GUACLOG_MAX_KEY_NAME_LENGTH 64 + +/** + * Copies the name of the key having the given keysym into the given buffer, + * which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long. This function + * always succeeds, ultimately resorting to using the hex value of the keysym + * as the name if no other human-readable name is known. + * + * @param key_name + * The buffer to copy the key name into, which must be at least + * GUACLOG_MAX_KEY_NAME_LENGTH. + * + * @param keysym + * The X11 keysym of the key whose name should be stored in + * key_name. + * + * @return + * The length of the name, in bytes, excluding null terminator. + */ +int guaclog_key_name(char* key_name, int keysym); + +#endif + diff --git a/src/guaclog/state.c b/src/guaclog/state.c index cc6f7637b7..616d04391a 100644 --- a/src/guaclog/state.c +++ b/src/guaclog/state.c @@ -18,6 +18,7 @@ */ #include "config.h" +#include "key-name.h" #include "log.h" #include "state.h" @@ -174,15 +175,28 @@ int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { /* Output new log entries only when keys are pressed */ if (pressed) { - /* STUB: Output raw hex log entry */ + /* Compose log entry by inspecting the state of each tracked key */ for (i = 0; i < state->active_keys; i++) { + guaclog_key_state* key = &state->key_states[i]; + + /* Translate keysym into human-readable name */ + char key_name[GUACLOG_MAX_KEY_NAME_LENGTH]; + int name_length = guaclog_key_name(key_name, key->keysym); + + /* If not the final key, omit the name (it was printed earlier) */ + if (i < state->active_keys - 1) { + memset(key_name, ' ', name_length); + if (key->pressed) + key_name[name_length / 2] = '*'; + } + + /* Separate each key by a single space */ if (i != 0) fprintf(state->output, " "); - guaclog_key_state* key = &state->key_states[i]; - fprintf(state->output, "0x%X:%s", key->keysym, - key->pressed ? "*" : " "); + /* Print name of key */ + fprintf(state->output, "%s", key_name); } From 3633af5e418876932ab086b910ca5a43df425cbe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 18:54:59 -0800 Subject: [PATCH 04/10] GUACAMOLE-313: Use binary search to find human-readable names for known keys. --- src/guaclog/key-name.c | 99 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/src/guaclog/key-name.c b/src/guaclog/key-name.c index ffb622d13f..f3485a2295 100644 --- a/src/guaclog/key-name.c +++ b/src/guaclog/key-name.c @@ -22,12 +22,107 @@ #include "log.h" #include +#include + +/** + * A mapping of X11 keysym to its corresponding human-readable name. + */ +typedef struct guaclog_known_key { + + /** + * The X11 keysym of the key. + */ + const int keysym; + + /** + * A human-readable name for the key. + */ + const char* name; + +} guaclog_known_key; + +/** + * All known keys. + */ +const guaclog_known_key known_keys[] = { + { 0xFFE1, "Shift" } +}; + +/** + * Comparator for the standard bsearch() function which compares an integer + * keysym against the keysym associated with a guaclog_known_key. + * + * @param key + * The key value being compared against the member. This MUST be the + * keysym value, passed through typecasting to an intptr_t (NOT a pointer + * to the int itself). + * + * @param member + * The member within the known_keys array being compared against the given + * key. + * + * @return + * Zero if the given keysym is equal to that of the given member, a + * positive value if the given keysym is greater than that of the given + * member, or a negative value if the given keysym is less than that of the + * given member. + */ +static int guaclog_known_key_bsearch_compare(const void* key, + const void* member) { + + int keysym = (int) ((intptr_t) key); + guaclog_known_key* current = (guaclog_known_key*) member; + + /* Compare given keysym to keysym of current member */ + return keysym - current->keysym; + +} + +/** + * Searches through the known_keys array of known keys for the name of the key + * having the given keysym. If found, the name of the keysym is copied into the + * given buffer, which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long. + * + * @param key_name + * The buffer to copy the key name into, which must be at least + * GUACLOG_MAX_KEY_NAME_LENGTH. + * + * @param keysym + * The X11 keysym of the key whose name should be stored in + * key_name. + * + * @return + * The length of the name, in bytes, excluding null terminator, or zero if + * the key could not be found. + */ +static int guaclog_locate_key_name(char* key_name, int keysym) { + + /* Search through known keys for given keysym */ + guaclog_known_key* found = bsearch((void*) ((intptr_t) keysym), + known_keys, sizeof(known_keys) / sizeof(known_keys[0]), + sizeof(known_keys[0]), guaclog_known_key_bsearch_compare); + + /* If found, format name and return length of result */ + if (found != NULL) + return snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, + "[ %s ]", found->name); + + /* Key not found */ + return 0; + +} int guaclog_key_name(char* key_name, int keysym) { + int name_length; + + /* Search for name within list of known keys */ + name_length = guaclog_locate_key_name(key_name, keysym); + /* Fallback to using hex keysym as name */ - int name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, - "0x%X", keysym); + if (name_length == 0) + name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, + "0x%X", keysym); /* Truncate name if necessary */ if (name_length >= GUACLOG_MAX_KEY_NAME_LENGTH) { From 86b09c8cf7fefd1f2dd7f368f1369f19d430bf7c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 19:35:47 -0800 Subject: [PATCH 05/10] GUACAMOLE-313: Add remaining key names. Use Unicode where possible. --- src/guaclog/key-name.c | 144 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 3 deletions(-) diff --git a/src/guaclog/key-name.c b/src/guaclog/key-name.c index f3485a2295..5ae73b5dd0 100644 --- a/src/guaclog/key-name.c +++ b/src/guaclog/key-name.c @@ -45,7 +45,65 @@ typedef struct guaclog_known_key { * All known keys. */ const guaclog_known_key known_keys[] = { - { 0xFFE1, "Shift" } + { 0x0020, "Space" }, + { 0xFE03, "AltGr" }, + { 0xFF08, "Backspace" }, + { 0xFF09, "Tab" }, + { 0xFF0B, "Clear" }, + { 0xFF0D, "Return" }, + { 0xFF13, "Pause" }, + { 0xFF1B, "Escape" }, + { 0xFF51, "Left" }, + { 0xFF52, "Up" }, + { 0xFF53, "Right" }, + { 0xFF54, "Down" }, + { 0xFF55, "Page Up" }, + { 0xFF56, "Page Down" }, + { 0xFF63, "Insert" }, + { 0xFF65, "Undo" }, + { 0xFF6A, "Help" }, + { 0xFF80, "Space" }, + { 0xFF8D, "Enter" }, + { 0xFFBD, "Equals" }, + { 0xFFBE, "F1" }, + { 0xFFBF, "F2" }, + { 0xFFC0, "F3" }, + { 0xFFC1, "F4" }, + { 0xFFC2, "F5" }, + { 0xFFC3, "F6" }, + { 0xFFC4, "F7" }, + { 0xFFC5, "F8" }, + { 0xFFC6, "F9" }, + { 0xFFC7, "F10" }, + { 0xFFC8, "F11" }, + { 0xFFC9, "F12" }, + { 0xFFCA, "F13" }, + { 0xFFCB, "F14" }, + { 0xFFCC, "F15" }, + { 0xFFCD, "F16" }, + { 0xFFCE, "F17" }, + { 0xFFCF, "F18" }, + { 0xFFD0, "F19" }, + { 0xFFD1, "F20" }, + { 0xFFD2, "F21" }, + { 0xFFD3, "F22" }, + { 0xFFD4, "F23" }, + { 0xFFD5, "F24" }, + { 0xFFE1, "Shift" }, + { 0xFFE2, "Shift" }, + { 0xFFE3, "Ctrl" }, + { 0xFFE4, "Ctrl" }, + { 0xFFE5, "Caps" }, + { 0xFFE7, "Meta" }, + { 0xFFE8, "Meta" }, + { 0xFFE9, "Alt" }, + { 0xFFEA, "Alt" }, + { 0xFFEB, "Super" }, + { 0xFFEB, "Win" }, + { 0xFFEC, "Super" }, + { 0xFFED, "Hyper" }, + { 0xFFEE, "Hyper" }, + { 0xFFFF, "Delete" } }; /** @@ -112,12 +170,92 @@ static int guaclog_locate_key_name(char* key_name, int keysym) { } +/** + * Produces a name for the key having the given keysym using its corresponding + * Unicode character. If possible, the name of the keysym is copied into the + * given buffer, which must be at least GUAC_MAX_KEY_NAME_LENGTH bytes long. + * + * @param key_name + * The buffer to copy the key name into, which must be at least + * GUACLOG_MAX_KEY_NAME_LENGTH. + * + * @param keysym + * The X11 keysym of the key whose name should be stored in + * key_name. + * + * @return + * The length of the name, in bytes, excluding null terminator, or zero if + * a readable name cannot be directly produced via Unicode alone. + */ +static int guaclog_unicode_key_name(char* key_name, int keysym) { + + int i; + int mask, bytes; + + /* Translate only if keysym maps to Unicode */ + if (keysym < 0x00 || (keysym > 0xFF && (keysym & 0xFFFF0000) != 0x01000000)) + return 0; + + /* Do not translate whitespace - it will be unreadable */ + if (keysym == 0x20) + return 0; + + int codepoint = keysym & 0xFFFF; + + /* Determine size and initial byte mask */ + if (codepoint <= 0x007F) { + mask = 0x00; + bytes = 1; + } + else if (codepoint <= 0x7FF) { + mask = 0xC0; + bytes = 2; + } + else if (codepoint <= 0xFFFF) { + mask = 0xE0; + bytes = 3; + } + else if (codepoint <= 0x1FFFFF) { + mask = 0xF0; + bytes = 4; + } + + /* Otherwise, invalid codepoint */ + else { + *(key_name++) = '?'; + return 1; + } + + /* Offset buffer by size */ + key_name += bytes; + + /* Add null terminator */ + *(key_name--) = '\0'; + + /* Add trailing bytes, if any */ + for (i=1; i>= 6; + } + + /* Set initial byte */ + *key_name = mask | codepoint; + + /* Done */ + return bytes; + +} + int guaclog_key_name(char* key_name, int keysym) { int name_length; - /* Search for name within list of known keys */ - name_length = guaclog_locate_key_name(key_name, keysym); + /* Attempt to translate straight into a Unicode character */ + name_length = guaclog_unicode_key_name(key_name, keysym); + + /* If not Unicode, search for name within list of known keys */ + if (name_length == 0) + name_length = guaclog_locate_key_name(key_name, keysym); /* Fallback to using hex keysym as name */ if (name_length == 0) From 5b612b856afc29073595d7ce450560b0bff65415 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 6 Dec 2017 21:25:58 -0800 Subject: [PATCH 06/10] GUACAMOLE-313: Refactor guaclog to produce simpler, greppable output. --- src/guaclog/Makefile.am | 4 +- src/guaclog/key-name.h | 50 ------- src/guaclog/{key-name.c => keydef.c} | 188 +++++++++++++-------------- src/guaclog/keydef.h | 73 +++++++++++ src/guaclog/state.c | 111 +++++++++++----- src/guaclog/state.h | 5 +- 6 files changed, 252 insertions(+), 179 deletions(-) delete mode 100644 src/guaclog/key-name.h rename src/guaclog/{key-name.c => keydef.c} (55%) create mode 100644 src/guaclog/keydef.h diff --git a/src/guaclog/Makefile.am b/src/guaclog/Makefile.am index 5007708c43..6acced3073 100644 --- a/src/guaclog/Makefile.am +++ b/src/guaclog/Makefile.am @@ -28,7 +28,7 @@ noinst_HEADERS = \ guaclog.h \ instructions.h \ interpret.h \ - key-name.h \ + keydef.h \ log.h \ state.h @@ -37,7 +37,7 @@ guaclog_SOURCES = \ instructions.c \ instruction-key.c \ interpret.c \ - key-name.c \ + keydef.c \ log.c \ state.c diff --git a/src/guaclog/key-name.h b/src/guaclog/key-name.h deleted file mode 100644 index a033b36c30..0000000000 --- a/src/guaclog/key-name.h +++ /dev/null @@ -1,50 +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 GUACLOG_KEY_NAME_H -#define GUACLOG_KEY_NAME_H - -#include "config.h" - -/** - * The maximum size of the name of any key, in bytes. - */ -#define GUACLOG_MAX_KEY_NAME_LENGTH 64 - -/** - * Copies the name of the key having the given keysym into the given buffer, - * which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long. This function - * always succeeds, ultimately resorting to using the hex value of the keysym - * as the name if no other human-readable name is known. - * - * @param key_name - * The buffer to copy the key name into, which must be at least - * GUACLOG_MAX_KEY_NAME_LENGTH. - * - * @param keysym - * The X11 keysym of the key whose name should be stored in - * key_name. - * - * @return - * The length of the name, in bytes, excluding null terminator. - */ -int guaclog_key_name(char* key_name, int keysym); - -#endif - diff --git a/src/guaclog/key-name.c b/src/guaclog/keydef.c similarity index 55% rename from src/guaclog/key-name.c rename to src/guaclog/keydef.c index 5ae73b5dd0..96165004b9 100644 --- a/src/guaclog/key-name.c +++ b/src/guaclog/keydef.c @@ -18,39 +18,23 @@ */ #include "config.h" -#include "key-name.h" +#include "keydef.h" #include "log.h" +#include #include #include - -/** - * A mapping of X11 keysym to its corresponding human-readable name. - */ -typedef struct guaclog_known_key { - - /** - * The X11 keysym of the key. - */ - const int keysym; - - /** - * A human-readable name for the key. - */ - const char* name; - -} guaclog_known_key; +#include /** * All known keys. */ -const guaclog_known_key known_keys[] = { - { 0x0020, "Space" }, +const guaclog_keydef known_keys[] = { { 0xFE03, "AltGr" }, { 0xFF08, "Backspace" }, - { 0xFF09, "Tab" }, + { 0xFF09, "Tab", "" }, { 0xFF0B, "Clear" }, - { 0xFF0D, "Return" }, + { 0xFF0D, "Return", "\n" }, { 0xFF13, "Pause" }, { 0xFF1B, "Escape" }, { 0xFF51, "Left" }, @@ -62,9 +46,8 @@ const guaclog_known_key known_keys[] = { { 0xFF63, "Insert" }, { 0xFF65, "Undo" }, { 0xFF6A, "Help" }, - { 0xFF80, "Space" }, - { 0xFF8D, "Enter" }, - { 0xFFBD, "Equals" }, + { 0xFF80, "Space", " " }, + { 0xFF8D, "Enter", "\n" }, { 0xFFBE, "F1" }, { 0xFFBF, "F2" }, { 0xFFC0, "F3" }, @@ -89,8 +72,8 @@ const guaclog_known_key known_keys[] = { { 0xFFD3, "F22" }, { 0xFFD4, "F23" }, { 0xFFD5, "F24" }, - { 0xFFE1, "Shift" }, - { 0xFFE2, "Shift" }, + { 0xFFE1, "Shift", "" }, + { 0xFFE2, "Shift", "" }, { 0xFFE3, "Ctrl" }, { 0xFFE4, "Ctrl" }, { 0xFFE5, "Caps" }, @@ -108,7 +91,7 @@ const guaclog_known_key known_keys[] = { /** * Comparator for the standard bsearch() function which compares an integer - * keysym against the keysym associated with a guaclog_known_key. + * keysym against the keysym associated with a guaclog_keydef. * * @param key * The key value being compared against the member. This MUST be the @@ -125,11 +108,11 @@ const guaclog_known_key known_keys[] = { * member, or a negative value if the given keysym is less than that of the * given member. */ -static int guaclog_known_key_bsearch_compare(const void* key, +static int guaclog_keydef_bsearch_compare(const void* key, const void* member) { int keysym = (int) ((intptr_t) key); - guaclog_known_key* current = (guaclog_known_key*) member; + guaclog_keydef* current = (guaclog_keydef*) member; /* Compare given keysym to keysym of current member */ return keysym - current->keysym; @@ -138,67 +121,50 @@ static int guaclog_known_key_bsearch_compare(const void* key, /** * Searches through the known_keys array of known keys for the name of the key - * having the given keysym. If found, the name of the keysym is copied into the - * given buffer, which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long. - * - * @param key_name - * The buffer to copy the key name into, which must be at least - * GUACLOG_MAX_KEY_NAME_LENGTH. + * having the given keysym, returning a pointer to the static guaclog_keydef + * within the array if found. * * @param keysym - * The X11 keysym of the key whose name should be stored in - * key_name. + * The X11 keysym of the key. * * @return - * The length of the name, in bytes, excluding null terminator, or zero if - * the key could not be found. + * A pointer to the static guaclog_keydef associated with the given keysym, + * or NULL if the key could not be found. */ -static int guaclog_locate_key_name(char* key_name, int keysym) { +static guaclog_keydef* guaclog_get_known_key(int keysym) { /* Search through known keys for given keysym */ - guaclog_known_key* found = bsearch((void*) ((intptr_t) keysym), + return bsearch((void*) ((intptr_t) keysym), known_keys, sizeof(known_keys) / sizeof(known_keys[0]), - sizeof(known_keys[0]), guaclog_known_key_bsearch_compare); - - /* If found, format name and return length of result */ - if (found != NULL) - return snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, - "[ %s ]", found->name); - - /* Key not found */ - return 0; + sizeof(known_keys[0]), guaclog_keydef_bsearch_compare); } /** - * Produces a name for the key having the given keysym using its corresponding - * Unicode character. If possible, the name of the keysym is copied into the - * given buffer, which must be at least GUAC_MAX_KEY_NAME_LENGTH bytes long. - * - * @param key_name - * The buffer to copy the key name into, which must be at least - * GUACLOG_MAX_KEY_NAME_LENGTH. + * Returns a statically-allocated guaclog_keydef representing the key + * associated with the given keysym, deriving the name and value of the key + * using its corresponding Unicode character. * * @param keysym - * The X11 keysym of the key whose name should be stored in - * key_name. + * The X11 keysym of the key. * * @return - * The length of the name, in bytes, excluding null terminator, or zero if - * a readable name cannot be directly produced via Unicode alone. + * A statically-allocated guaclog_keydef representing the key associated + * with the given keysym, or NULL if the given keysym has no corresponding + * Unicode character. */ -static int guaclog_unicode_key_name(char* key_name, int keysym) { +static guaclog_keydef* guaclog_get_unicode_key(int keysym) { + + static char unicode_keydef_name[8]; + + static guaclog_keydef unicode_keydef; int i; int mask, bytes; /* Translate only if keysym maps to Unicode */ if (keysym < 0x00 || (keysym > 0xFF && (keysym & 0xFFFF0000) != 0x01000000)) - return 0; - - /* Do not translate whitespace - it will be unreadable */ - if (keysym == 0x20) - return 0; + return NULL; int codepoint = keysym & 0xFFFF; @@ -221,13 +187,11 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) { } /* Otherwise, invalid codepoint */ - else { - *(key_name++) = '?'; - return 1; - } + else + return NULL; /* Offset buffer by size */ - key_name += bytes; + char* key_name = unicode_keydef_name + bytes; /* Add null terminator */ *(key_name--) = '\0'; @@ -241,36 +205,72 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) { /* Set initial byte */ *key_name = mask | codepoint; - /* Done */ - return bytes; + /* Return static key definition */ + unicode_keydef.keysym = keysym; + unicode_keydef.name = unicode_keydef.value = unicode_keydef_name; + return &unicode_keydef; + +} + +/** + * Copies the given guaclog_keydef into a newly-allocated guaclog_keydef + * structure. The resulting guaclog_keydef must eventually be freed through a + * call to guaclog_keydef_free(). + * + * @param keydef + * The guaclog_keydef to copy. + * + * @return + * A newly-allocated guaclog_keydef structure copied from the given + * guaclog_keydef. + */ +static guaclog_keydef* guaclog_copy_key(guaclog_keydef* keydef) { + + guaclog_keydef* copy = malloc(sizeof(guaclog_keydef)); + + /* Always copy keysym and name */ + copy->keysym = keydef->keysym; + copy->name = strdup(keydef->name); + + /* Copy value only if defined */ + if (keydef->value != NULL) + copy->value = strdup(keydef->value); + else + copy->value = NULL; + + return copy; } -int guaclog_key_name(char* key_name, int keysym) { +guaclog_keydef* guaclog_keydef_alloc(int keysym) { - int name_length; + guaclog_keydef* keydef; - /* Attempt to translate straight into a Unicode character */ - name_length = guaclog_unicode_key_name(key_name, keysym); + /* Check list of known keys first */ + keydef = guaclog_get_known_key(keysym); + if (keydef != NULL) + return guaclog_copy_key(keydef); - /* If not Unicode, search for name within list of known keys */ - if (name_length == 0) - name_length = guaclog_locate_key_name(key_name, keysym); + /* Failing that, attempt to translate straight into a Unicode character */ + keydef = guaclog_get_unicode_key(keysym); + if (keydef != NULL) + return guaclog_copy_key(keydef); - /* Fallback to using hex keysym as name */ - if (name_length == 0) - name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, - "0x%X", keysym); + /* Key not known */ + guaclog_log(GUAC_LOG_DEBUG, "Definition not found for key 0x%X.", keysym); + return NULL; - /* Truncate name if necessary */ - if (name_length >= GUACLOG_MAX_KEY_NAME_LENGTH) { - name_length = GUACLOG_MAX_KEY_NAME_LENGTH - 1; - key_name[name_length] = '\0'; - guaclog_log(GUAC_LOG_DEBUG, "Name for key 0x%X was " - "truncated.", keysym); - } +} + +void guaclog_keydef_free(guaclog_keydef* keydef) { + + /* Ignore NULL keydef */ + if (keydef == NULL) + return; - return name_length; + free(keydef->name); + free(keydef->value); + free(keydef); } diff --git a/src/guaclog/keydef.h b/src/guaclog/keydef.h new file mode 100644 index 0000000000..d0b2720a48 --- /dev/null +++ b/src/guaclog/keydef.h @@ -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. + */ + +#ifndef GUACLOG_KEYDEF_H +#define GUACLOG_KEYDEF_H + +#include "config.h" + +/** + * A mapping of X11 keysym to its corresponding human-readable name. + */ +typedef struct guaclog_keydef { + + /** + * The X11 keysym of the key. + */ + int keysym; + + /** + * A human-readable name for the key. + */ + char* name; + + /** + * The value which would be typed in a typical text editor, if any. If the + * key is not associated with any typable value, or if the typable value is + * not generally useful in an auditing context, this will be NULL. + */ + char* value; + +} guaclog_keydef; + +/** + * Creates a new guaclog_keydef which represents the key having the given + * keysym. The resulting guaclog_keydef must eventually be freed through a + * call to guaclog_keydef_free(). + * + * @param keysym + * The X11 keysym of the key. + * + * @return + * A new guaclog_keydef which represents the key having the given keysym, + * or NULL if no such key is known. + */ +guaclog_keydef* guaclog_keydef_alloc(int keysym); + +/** + * Frees all resources associated with the given guaclog_keydef. If the given + * guaclog_keydef is NULL, this function has no effect. + * + * @param keydef + * The guaclog_keydef to free, which may be NULL. + */ +void guaclog_keydef_free(guaclog_keydef* keydef); + +#endif + diff --git a/src/guaclog/state.c b/src/guaclog/state.c index 616d04391a..4965985dac 100644 --- a/src/guaclog/state.c +++ b/src/guaclog/state.c @@ -18,7 +18,7 @@ */ #include "config.h" -#include "key-name.h" +#include "keydef.h" #include "log.h" #include "state.h" @@ -77,10 +77,16 @@ guaclog_state* guaclog_state_alloc(const char* path) { int guaclog_state_free(guaclog_state* state) { + int i; + /* Ignore NULL state */ if (state == NULL) return 0; + /* Free keydefs of all tracked keys */ + for (i = 0; i < state->active_keys; i++) + guaclog_keydef_free(state->key_states[i].keydef); + /* Close output file */ fclose(state->output); @@ -100,8 +106,11 @@ int guaclog_state_free(guaclog_state* state) { * @param state * The Guacamole input log interpreter state being updated. * - * @param keysym - * The X11 keysym of the key being pressed or released. + * @param keydef + * The guaclog_keydef of the key being pressed or released. This + * guaclog_keydef will automatically be freed along with the guaclog_state + * if the key state was successfully added, and must be manually freed + * otherwise. * * @param pressed * true if the key is being pressed, false if the key is being released. @@ -109,14 +118,17 @@ int guaclog_state_free(guaclog_state* state) { * @return * Zero if the key state was successfully added, non-zero otherwise. */ -static int guaclog_state_add_key(guaclog_state* state, int keysym, bool pressed) { +static int guaclog_state_add_key(guaclog_state* state, guaclog_keydef* keydef, + bool pressed) { int i; /* Update existing key, if already tracked */ for (i = 0; i < state->active_keys; i++) { guaclog_key_state* key = &state->key_states[i]; - if (key->keysym == keysym) { + if (key->keydef->keysym == keydef->keysym) { + guaclog_keydef_free(key->keydef); + key->keydef = keydef; key->pressed = pressed; return 0; } @@ -125,13 +137,13 @@ static int guaclog_state_add_key(guaclog_state* state, int keysym, bool pressed) /* If not already tracked, we need space to add it */ if (state->active_keys == GUACLOG_MAX_KEYS) { guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many " - "active keys.", keysym); + "active keys.", keydef->keysym); return 1; } /* Add key to state */ guaclog_key_state* key = &state->key_states[state->active_keys++]; - key->keysym = keysym; + key->keydef = keydef; key->pressed = pressed; return 0; @@ -152,11 +164,16 @@ static void guaclog_state_trim_keys(guaclog_state* state) { /* Reset active_keys to contain only up to the last pressed key */ for (i = state->active_keys - 1; i >= 0; i--) { + guaclog_key_state* key = &state->key_states[i]; if (key->pressed) { state->active_keys = i + 1; return; } + + /* Free all trimmed states */ + guaclog_keydef_free(key->keydef); + } /* No keys are active */ @@ -164,44 +181,76 @@ static void guaclog_state_trim_keys(guaclog_state* state) { } +/** + * Returns whether the current tracked key state represents an in-progress + * keyboard shortcut. + * + * @param state + * The Guacamole input log interpreter state to test. + * + * @return + * true if the given state represents an in-progress keyboard shortcut, + * false otherwise. + */ +static bool guaclog_state_is_shortcut(guaclog_state* state) { + + int i; + + /* We are in a shortcut if at least one key is non-printable */ + for (i = 0; i < state->active_keys; i++) { + guaclog_key_state* key = &state->key_states[i]; + if (key->keydef->value == NULL) + return true; + } + + /* All keys are printable - no shortcut */ + return false; + +} + int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { int i; - /* Update tracked keysysm state */ - guaclog_state_add_key(state, keysym, pressed); - guaclog_state_trim_keys(state); + /* Determine nature of key */ + guaclog_keydef* keydef = guaclog_keydef_alloc(keysym); + if (keydef == NULL) + return 0; - /* Output new log entries only when keys are pressed */ - if (pressed) { + /* Update tracked key state */ + if (guaclog_state_add_key(state, keydef, pressed)) + guaclog_keydef_free(keydef); + else + guaclog_state_trim_keys(state); - /* Compose log entry by inspecting the state of each tracked key */ - for (i = 0; i < state->active_keys; i++) { + /* Output key states only for printable keys */ + if (pressed && keydef->value != NULL) { - guaclog_key_state* key = &state->key_states[i]; + if (guaclog_state_is_shortcut(state)) { - /* Translate keysym into human-readable name */ - char key_name[GUACLOG_MAX_KEY_NAME_LENGTH]; - int name_length = guaclog_key_name(key_name, key->keysym); + fprintf(state->output, "<"); - /* If not the final key, omit the name (it was printed earlier) */ - if (i < state->active_keys - 1) { - memset(key_name, ' ', name_length); - if (key->pressed) - key_name[name_length / 2] = '*'; - } + /* Compose log entry by inspecting the state of each tracked key */ + for (i = 0; i < state->active_keys; i++) { - /* Separate each key by a single space */ - if (i != 0) - fprintf(state->output, " "); + /* Translate keysym into human-readable name */ + guaclog_key_state* key = &state->key_states[i]; + + /* Print name of key */ + if (i == 0) + fprintf(state->output, "%s", key->keydef->name); + else + fprintf(state->output, "+%s", key->keydef->name); + + } - /* Print name of key */ - fprintf(state->output, "%s", key_name); + fprintf(state->output, ">"); } - /* Terminate log entry with newline */ - fprintf(state->output, "\n"); + /* Print the key itself */ + else + fprintf(state->output, "%s", keydef->value); } diff --git a/src/guaclog/state.h b/src/guaclog/state.h index 0dd2f2f9db..b476ddeb13 100644 --- a/src/guaclog/state.h +++ b/src/guaclog/state.h @@ -21,6 +21,7 @@ #define GUACLOG_STATE_H #include "config.h" +#include "keydef.h" #include #include @@ -37,9 +38,9 @@ typedef struct guaclog_key_state { /** - * The X11 keysym of the key. + * The definition of the key. */ - int keysym; + guaclog_keydef* keydef; /** * Whether the key is currently pressed (true) or released (false). From 5e5f1fcb3e0d5d2fb2c1c679aacd337142f47393 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 8 Dec 2017 12:56:27 -0800 Subject: [PATCH 07/10] GUACAMOLE-313: Add missing keysyms. Track modifier keys only. --- src/guaclog/keydef.c | 58 ++++++++++++++++++++++++++++++++------------ src/guaclog/keydef.h | 8 ++++++ src/guaclog/state.c | 24 +++++++++++------- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/guaclog/keydef.c b/src/guaclog/keydef.c index 96165004b9..427e3cae14 100644 --- a/src/guaclog/keydef.c +++ b/src/guaclog/keydef.c @@ -30,24 +30,53 @@ * All known keys. */ const guaclog_keydef known_keys[] = { - { 0xFE03, "AltGr" }, + { 0xFE03, "AltGr", "", true }, { 0xFF08, "Backspace" }, - { 0xFF09, "Tab", "" }, + { 0xFF09, "Tab" }, { 0xFF0B, "Clear" }, { 0xFF0D, "Return", "\n" }, { 0xFF13, "Pause" }, + { 0xFF14, "Scroll" }, + { 0xFF15, "SysReq" }, { 0xFF1B, "Escape" }, + { 0xFF50, "Home" }, { 0xFF51, "Left" }, { 0xFF52, "Up" }, { 0xFF53, "Right" }, { 0xFF54, "Down" }, { 0xFF55, "Page Up" }, { 0xFF56, "Page Down" }, + { 0xFF57, "End" }, { 0xFF63, "Insert" }, { 0xFF65, "Undo" }, { 0xFF6A, "Help" }, + { 0xFF7F, "Num" }, { 0xFF80, "Space", " " }, { 0xFF8D, "Enter", "\n" }, + { 0xFF95, "Home" }, + { 0xFF96, "Left" }, + { 0xFF97, "Up" }, + { 0xFF98, "Right" }, + { 0xFF99, "Down" }, + { 0xFF9A, "Page Up" }, + { 0xFF9B, "Page Down" }, + { 0xFF9C, "End" }, + { 0xFF9E, "Insert" }, + { 0xFFAA, "*", "*" }, + { 0xFFAB, "+", "+" }, + { 0xFFAD, "-", "-" }, + { 0xFFAE, ".", "." }, + { 0xFFAF, "/", "/" }, + { 0xFFB0, "0", "0" }, + { 0xFFB1, "1", "1" }, + { 0xFFB2, "2", "2" }, + { 0xFFB3, "3", "3" }, + { 0xFFB4, "4", "4" }, + { 0xFFB5, "5", "5" }, + { 0xFFB6, "6", "6" }, + { 0xFFB7, "7", "7" }, + { 0xFFB8, "8", "8" }, + { 0xFFB9, "9", "9" }, { 0xFFBE, "F1" }, { 0xFFBF, "F2" }, { 0xFFC0, "F3" }, @@ -72,20 +101,19 @@ const guaclog_keydef known_keys[] = { { 0xFFD3, "F22" }, { 0xFFD4, "F23" }, { 0xFFD5, "F24" }, - { 0xFFE1, "Shift", "" }, - { 0xFFE2, "Shift", "" }, - { 0xFFE3, "Ctrl" }, - { 0xFFE4, "Ctrl" }, + { 0xFFE1, "Shift", "", true }, + { 0xFFE2, "Shift", "", true }, + { 0xFFE3, "Ctrl", NULL, true }, + { 0xFFE4, "Ctrl", NULL, true }, { 0xFFE5, "Caps" }, - { 0xFFE7, "Meta" }, - { 0xFFE8, "Meta" }, - { 0xFFE9, "Alt" }, - { 0xFFEA, "Alt" }, - { 0xFFEB, "Super" }, - { 0xFFEB, "Win" }, - { 0xFFEC, "Super" }, - { 0xFFED, "Hyper" }, - { 0xFFEE, "Hyper" }, + { 0xFFE7, "Meta", NULL, true }, + { 0xFFE8, "Meta", NULL, true }, + { 0xFFE9, "Alt", NULL, true }, + { 0xFFEA, "Alt", NULL, true }, + { 0xFFEB, "Super", NULL, true }, + { 0xFFEC, "Super", NULL, true }, + { 0xFFED, "Hyper", NULL, true }, + { 0xFFEE, "Hyper", NULL, true }, { 0xFFFF, "Delete" } }; diff --git a/src/guaclog/keydef.h b/src/guaclog/keydef.h index d0b2720a48..147fe20a05 100644 --- a/src/guaclog/keydef.h +++ b/src/guaclog/keydef.h @@ -22,6 +22,8 @@ #include "config.h" +#include + /** * A mapping of X11 keysym to its corresponding human-readable name. */ @@ -44,6 +46,12 @@ typedef struct guaclog_keydef { */ char* value; + /** + * Whether this key is a modifier which may affect the interpretation of + * other keys, and thus should be tracked as it is held down. + */ + bool modifier; + } guaclog_keydef; /** diff --git a/src/guaclog/state.c b/src/guaclog/state.c index 4965985dac..5e18708790 100644 --- a/src/guaclog/state.c +++ b/src/guaclog/state.c @@ -217,14 +217,16 @@ int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { if (keydef == NULL) return 0; - /* Update tracked key state */ - if (guaclog_state_add_key(state, keydef, pressed)) - guaclog_keydef_free(keydef); - else - guaclog_state_trim_keys(state); + /* Update tracked key state for modifiers */ + if (keydef->modifier) { + if (guaclog_state_add_key(state, keydef, pressed)) + guaclog_keydef_free(keydef); + else + guaclog_state_trim_keys(state); + } /* Output key states only for printable keys */ - if (pressed && keydef->value != NULL) { + else if (pressed) { if (guaclog_state_is_shortcut(state)) { @@ -244,13 +246,17 @@ int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { } - fprintf(state->output, ">"); + fprintf(state->output, "%s>", keydef->value); } /* Print the key itself */ - else - fprintf(state->output, "%s", keydef->value); + else { + if (keydef->value != NULL) + fprintf(state->output, "%s", keydef->value); + else + fprintf(state->output, "<%s>", keydef->name); + } } From b7257d9ae45287a8a9e1b80920c677d75863698c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 8 Dec 2017 13:10:32 -0800 Subject: [PATCH 08/10] GUACAMOLE-313: Include unknown keys within log. --- src/guaclog/keydef.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/guaclog/keydef.c b/src/guaclog/keydef.c index 427e3cae14..6b979faa5b 100644 --- a/src/guaclog/keydef.c +++ b/src/guaclog/keydef.c @@ -21,6 +21,7 @@ #include "keydef.h" #include "log.h" +#include #include #include #include @@ -168,6 +169,36 @@ static guaclog_keydef* guaclog_get_known_key(int keysym) { } +/** + * Returns a statically-allocated guaclog_keydef representing an unknown key, + * deriving the name of the key from the hexadecimal value of the keysym. + * + * @param keysym + * The X11 keysym of the key. + * + * @return + * A statically-allocated guaclog_keydef representing the key associated + * with the given keysym. + */ +static guaclog_keydef* guaclog_get_unknown_key(int keysym) { + + static char unknown_keydef_name[64]; + static guaclog_keydef unknown_keydef; + + /* Write keysym as hex */ + int size = snprintf(unknown_keydef_name, sizeof(unknown_keydef_name), + "0x%X", keysym); + + /* Hex string is guaranteed to fit within the provided 64 bytes */ + assert(size < sizeof(unknown_keydef_name)); + + /* Return static key definition */ + unknown_keydef.keysym = keysym; + unknown_keydef.name = unknown_keydef_name; + return &unknown_keydef; + +} + /** * Returns a statically-allocated guaclog_keydef representing the key * associated with the given keysym, deriving the name and value of the key @@ -286,7 +317,7 @@ guaclog_keydef* guaclog_keydef_alloc(int keysym) { /* Key not known */ guaclog_log(GUAC_LOG_DEBUG, "Definition not found for key 0x%X.", keysym); - return NULL; + return guaclog_copy_key(guaclog_get_unknown_key(keysym)); } From c0b2871b31a6093627247c0fe084ee843f634896 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 Dec 2017 12:40:01 -0800 Subject: [PATCH 09/10] GUACAMOLE-313: Document log format. --- src/guaclog/man/guaclog.1.in | 55 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/guaclog/man/guaclog.1.in b/src/guaclog/man/guaclog.1.in index 11896d591b..e5dfa9d09b 100644 --- a/src/guaclog/man/guaclog.1.in +++ b/src/guaclog/man/guaclog.1.in @@ -29,8 +29,8 @@ guaclog \- Guacamole input log interpreter .SH DESCRIPTION .B guaclog is an interpreter which accepts Guacamole protocol dumps, such as those saved -when input logging is enabled on a Guacamole connection, writing human-readable -text logs as output. +when input logging is enabled for a Guacamole session recording, writing +human-readable text logs as output. .B guaclog is essentially an implementation of a Guacamole client which accepts its input from files instead of a network connection, however unlike @@ -42,19 +42,56 @@ file named \fIFILE\fR.txt. Existing files will not be overwritten; the interpreting process for any input file will be aborted if it would result in overwriting an existing file. .P -Guacamole acquires a write lock on input logs as they are being written. By +Guacamole acquires a write lock on recordings as they are being written. By default, .B guaclog will check whether the each input file is locked and will refuse to read and -interpret an input file if it appears to be an in-progress log. This behavior -can be overridden by specifying the \fB-f\fR option. Interpreting an -in-progress log will still work; the resulting human-readable text file will -simply cover the user's session only up to the current point in time. +interpret an input file if it appears to be an in-progress recording. This +behavior can be overridden by specifying the \fB-f\fR option. Interpreting an +in-progress recording will still work; the resulting human-readable text file +will simply cover the user's session only up to the current point in time. . .SH OPTIONS .TP \fB-f\fR Overrides the default behavior of .B guaclog -such that input files will be interpreted even if they appear to be logs of -in-progress Guacamole sessions. +such that input files will be interpreted even if they appear to be recordings +of in-progress Guacamole sessions. +. +.SH OUTPUT FORMAT +The output format of +.B guaclog +is meant to match what the user would have typed within a typical text editor +as closely as possible, while also representing non-printable characters and +keyboard shortcuts in a human-readable way. +.P +All output is on one line, with new lines started only as a result of the user +pressing enter/return. Keys which produce printable characters are translated +into their corresponding Unicode codepoints and encoded as UTF-8, while +non-printable characters are enclosed within angle brackets and represented +with their human-readable names. Keyboard shortcuts which are made up of more +than one key are enclosed within angle brackets, with each key within the +shortcut separated by plus signs. +.P +Spaces and newlines are included as their Unicode character, except when +represented within a keyboard shortcut, in which case their human-readable +names are used instead. As the output of pressing tab can be easily mistaken +for spaces, and as pressing tab frequently has special meaning within +applications, tab is always represented by its human-readable name. +.P +Modifiers are output as part of keyboard shortcuts only. Simple pressing and +releasing of a modifier will be ignored, as are presses of shift or AltGr while +typing. +.P +For example, if the user typed "Hello WORLD!", selected everything by pressing +Ctrl+a, copied the selected text by pressing Ctrl+c, switched to another +application by pressing Alt+Shift+Tab, and then pasted the previously-copied +text by pressing Ctrl+v, the resulting log from +.B +guaclog +would look like: +.PP +.RS 0 +Hello WORLD! + From fdd17e30422dd91337d044ecff02db52aab32691 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 26 Jan 2018 16:16:40 -0800 Subject: [PATCH 10/10] GUACAMOLE-313: Note that guacenc/guaclog are related. --- src/guacenc/man/guacenc.1.in | 5 ++++- src/guaclog/man/guaclog.1.in | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/guacenc/man/guacenc.1.in b/src/guacenc/man/guacenc.1.in index edb75f6cb1..4f007875da 100644 --- a/src/guacenc/man/guacenc.1.in +++ b/src/guacenc/man/guacenc.1.in @@ -16,7 +16,7 @@ .\" specific language governing permissions and limitations .\" under the License. .\" -.TH guacenc 1 "1 Jun 2017" "version @PACKAGE_VERSION@" "Apache Guacamole" +.TH guacenc 1 "26 Jan 2018" "version @PACKAGE_VERSION@" "Apache Guacamole" . .SH NAME guacenc \- Guacamole video encoder @@ -75,3 +75,6 @@ Overrides the default behavior of .B guacenc such that input files will be encoded even if they appear to be recordings of in-progress Guacamole sessions. +. +.SH SEE ALSO +.BR guaclog (1) diff --git a/src/guaclog/man/guaclog.1.in b/src/guaclog/man/guaclog.1.in index e5dfa9d09b..de145590b8 100644 --- a/src/guaclog/man/guaclog.1.in +++ b/src/guaclog/man/guaclog.1.in @@ -94,4 +94,6 @@ would look like: .PP .RS 0 Hello WORLD! - +. +.SH SEE ALSO +.BR guacenc (1)