diff --git a/Makefile.am b/Makefile.am index 8dfa9d1f..e9233760 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 1906dc86..8ae07430 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/guacenc/man/guacenc.1.in b/src/guacenc/man/guacenc.1.in index edb75f6c..4f007875 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/.gitignore b/src/guaclog/.gitignore new file mode 100644 index 00000000..4865f645 --- /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 00000000..6acced30 --- /dev/null +++ b/src/guaclog/Makefile.am @@ -0,0 +1,53 @@ +# +# 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 \ + keydef.h \ + log.h \ + state.h + +guaclog_SOURCES = \ + guaclog.c \ + instructions.c \ + instruction-key.c \ + interpret.c \ + keydef.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 00000000..43502897 --- /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 00000000..7390314e --- /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 00000000..0e8501b0 --- /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 00000000..257e1343 --- /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 00000000..0c901f53 --- /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 00000000..d996c245 --- /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 00000000..c65764e3 --- /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/keydef.c b/src/guaclog/keydef.c new file mode 100644 index 00000000..6b979faa --- /dev/null +++ b/src/guaclog/keydef.c @@ -0,0 +1,335 @@ +/* + * 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 "keydef.h" +#include "log.h" + +#include +#include +#include +#include +#include + +/** + * All known keys. + */ +const guaclog_keydef known_keys[] = { + { 0xFE03, "AltGr", "", true }, + { 0xFF08, "Backspace" }, + { 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" }, + { 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", "", true }, + { 0xFFE2, "Shift", "", true }, + { 0xFFE3, "Ctrl", NULL, true }, + { 0xFFE4, "Ctrl", NULL, true }, + { 0xFFE5, "Caps" }, + { 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" } +}; + +/** + * Comparator for the standard bsearch() function which compares an integer + * keysym against the keysym associated with a guaclog_keydef. + * + * @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_keydef_bsearch_compare(const void* key, + const void* member) { + + int keysym = (int) ((intptr_t) key); + guaclog_keydef* current = (guaclog_keydef*) 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, returning a pointer to the static guaclog_keydef + * within the array if found. + * + * @param keysym + * The X11 keysym of the key. + * + * @return + * A pointer to the static guaclog_keydef associated with the given keysym, + * or NULL if the key could not be found. + */ +static guaclog_keydef* guaclog_get_known_key(int keysym) { + + /* Search through known keys for given keysym */ + return bsearch((void*) ((intptr_t) keysym), + known_keys, sizeof(known_keys) / sizeof(known_keys[0]), + sizeof(known_keys[0]), guaclog_keydef_bsearch_compare); + +} + +/** + * 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 + * using its corresponding Unicode character. + * + * @param keysym + * The X11 keysym of the key. + * + * @return + * 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 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 NULL; + + 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 + return NULL; + + /* Offset buffer by size */ + char* key_name = unicode_keydef_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; + + /* 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; + +} + +guaclog_keydef* guaclog_keydef_alloc(int keysym) { + + guaclog_keydef* keydef; + + /* Check list of known keys first */ + keydef = guaclog_get_known_key(keysym); + if (keydef != NULL) + return guaclog_copy_key(keydef); + + /* Failing that, attempt to translate straight into a Unicode character */ + keydef = guaclog_get_unicode_key(keysym); + if (keydef != NULL) + return guaclog_copy_key(keydef); + + /* Key not known */ + guaclog_log(GUAC_LOG_DEBUG, "Definition not found for key 0x%X.", keysym); + return guaclog_copy_key(guaclog_get_unknown_key(keysym)); + +} + +void guaclog_keydef_free(guaclog_keydef* keydef) { + + /* Ignore NULL keydef */ + if (keydef == NULL) + return; + + 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 00000000..147fe20a --- /dev/null +++ b/src/guaclog/keydef.h @@ -0,0 +1,81 @@ +/* + * 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" + +#include + +/** + * 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; + + /** + * 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; + +/** + * 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/log.c b/src/guaclog/log.c new file mode 100644 index 00000000..e99abacc --- /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 00000000..bcf90a1c --- /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 00000000..de145590 --- /dev/null +++ b/src/guaclog/man/guaclog.1.in @@ -0,0 +1,99 @@ +.\" +.\" 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 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 +.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 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 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 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! +. +.SH SEE ALSO +.BR guacenc (1) diff --git a/src/guaclog/state.c b/src/guaclog/state.c new file mode 100644 index 00000000..5e187087 --- /dev/null +++ b/src/guaclog/state.c @@ -0,0 +1,266 @@ +/* + * 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 "keydef.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; + + /* No keys are initially tracked */ + state->active_keys = 0; + + 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) { + + 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); + + free(state); + return 0; + +} + +/** + * 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 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. + * + * @return + * Zero if the key state was successfully added, non-zero otherwise. + */ +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->keydef->keysym == keydef->keysym) { + guaclog_keydef_free(key->keydef); + key->keydef = keydef; + 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.", keydef->keysym); + return 1; + } + + /* Add key to state */ + guaclog_key_state* key = &state->key_states[state->active_keys++]; + key->keydef = keydef; + 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; + } + + /* Free all trimmed states */ + guaclog_keydef_free(key->keydef); + + } + + /* No keys are active */ + state->active_keys = 0; + +} + +/** + * 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; + + /* Determine nature of key */ + guaclog_keydef* keydef = guaclog_keydef_alloc(keysym); + if (keydef == NULL) + return 0; + + /* 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 */ + else if (pressed) { + + if (guaclog_state_is_shortcut(state)) { + + fprintf(state->output, "<"); + + /* Compose log entry by inspecting the state of each tracked key */ + for (i = 0; i < state->active_keys; i++) { + + /* 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); + + } + + fprintf(state->output, "%s>", keydef->value); + + } + + /* Print the key itself */ + else { + if (keydef->value != NULL) + fprintf(state->output, "%s", keydef->value); + else + fprintf(state->output, "<%s>", keydef->name); + } + + } + + return 0; + +} + diff --git a/src/guaclog/state.h b/src/guaclog/state.h new file mode 100644 index 00000000..b476ddeb --- /dev/null +++ b/src/guaclog/state.h @@ -0,0 +1,125 @@ +/* + * 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 "keydef.h" + +#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 definition of the key. + */ + guaclog_keydef* keydef; + + /** + * Whether the key is currently pressed (true) or released (false). + */ + bool pressed; + +} guaclog_key_state; + +/** + * The current state of the Guacamole input log interpreter. + */ +typedef struct guaclog_state { + + /** + * Output file stream. + */ + 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; + +/** + * 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 +