From ebc731aaf390ca06019af58bb6c512faa4605ee6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Nov 2017 15:44:03 -0800 Subject: [PATCH] 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 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/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..f1ad1521 --- /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 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/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..11896d59 --- /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 00000000..6167bd30 --- /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 00000000..5891cb56 --- /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 +