GUACAMOLE-313: Refactor guaclog to produce simpler, greppable output.

This commit is contained in:
Michael Jumper 2017-12-06 21:25:58 -08:00
parent 86b09c8cf7
commit 5b612b856a
6 changed files with 254 additions and 181 deletions

View File

@ -28,7 +28,7 @@ noinst_HEADERS = \
guaclog.h \ guaclog.h \
instructions.h \ instructions.h \
interpret.h \ interpret.h \
key-name.h \ keydef.h \
log.h \ log.h \
state.h state.h
@ -37,7 +37,7 @@ guaclog_SOURCES = \
instructions.c \ instructions.c \
instruction-key.c \ instruction-key.c \
interpret.c \ interpret.c \
key-name.c \ keydef.c \
log.c \ log.c \
state.c state.c

View File

@ -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

View File

@ -18,39 +18,23 @@
*/ */
#include "config.h" #include "config.h"
#include "key-name.h" #include "keydef.h"
#include "log.h" #include "log.h"
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
/**
* 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. * All known keys.
*/ */
const guaclog_known_key known_keys[] = { const guaclog_keydef known_keys[] = {
{ 0x0020, "Space" },
{ 0xFE03, "AltGr" }, { 0xFE03, "AltGr" },
{ 0xFF08, "Backspace" }, { 0xFF08, "Backspace" },
{ 0xFF09, "Tab" }, { 0xFF09, "Tab", "<Tab>" },
{ 0xFF0B, "Clear" }, { 0xFF0B, "Clear" },
{ 0xFF0D, "Return" }, { 0xFF0D, "Return", "\n" },
{ 0xFF13, "Pause" }, { 0xFF13, "Pause" },
{ 0xFF1B, "Escape" }, { 0xFF1B, "Escape" },
{ 0xFF51, "Left" }, { 0xFF51, "Left" },
@ -62,9 +46,8 @@ const guaclog_known_key known_keys[] = {
{ 0xFF63, "Insert" }, { 0xFF63, "Insert" },
{ 0xFF65, "Undo" }, { 0xFF65, "Undo" },
{ 0xFF6A, "Help" }, { 0xFF6A, "Help" },
{ 0xFF80, "Space" }, { 0xFF80, "Space", " " },
{ 0xFF8D, "Enter" }, { 0xFF8D, "Enter", "\n" },
{ 0xFFBD, "Equals" },
{ 0xFFBE, "F1" }, { 0xFFBE, "F1" },
{ 0xFFBF, "F2" }, { 0xFFBF, "F2" },
{ 0xFFC0, "F3" }, { 0xFFC0, "F3" },
@ -89,8 +72,8 @@ const guaclog_known_key known_keys[] = {
{ 0xFFD3, "F22" }, { 0xFFD3, "F22" },
{ 0xFFD4, "F23" }, { 0xFFD4, "F23" },
{ 0xFFD5, "F24" }, { 0xFFD5, "F24" },
{ 0xFFE1, "Shift" }, { 0xFFE1, "Shift", "" },
{ 0xFFE2, "Shift" }, { 0xFFE2, "Shift", "" },
{ 0xFFE3, "Ctrl" }, { 0xFFE3, "Ctrl" },
{ 0xFFE4, "Ctrl" }, { 0xFFE4, "Ctrl" },
{ 0xFFE5, "Caps" }, { 0xFFE5, "Caps" },
@ -108,7 +91,7 @@ const guaclog_known_key known_keys[] = {
/** /**
* Comparator for the standard bsearch() function which compares an integer * 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 * @param key
* The key value being compared against the member. This MUST be the * 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 * member, or a negative value if the given keysym is less than that of the
* given member. * given member.
*/ */
static int guaclog_known_key_bsearch_compare(const void* key, static int guaclog_keydef_bsearch_compare(const void* key,
const void* member) { const void* member) {
int keysym = (int) ((intptr_t) key); 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 */ /* Compare given keysym to keysym of current member */
return keysym - current->keysym; 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 * 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 * having the given keysym, returning a pointer to the static guaclog_keydef
* given buffer, which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long. * within the array if found.
*
* @param key_name
* The buffer to copy the key name into, which must be at least
* GUACLOG_MAX_KEY_NAME_LENGTH.
* *
* @param keysym * @param keysym
* The X11 keysym of the key whose name should be stored in * The X11 keysym of the key.
* key_name.
* *
* @return * @return
* The length of the name, in bytes, excluding null terminator, or zero if * A pointer to the static guaclog_keydef associated with the given keysym,
* the key could not be found. * 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 */ /* 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]), known_keys, sizeof(known_keys) / sizeof(known_keys[0]),
sizeof(known_keys[0]), guaclog_known_key_bsearch_compare); sizeof(known_keys[0]), guaclog_keydef_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;
} }
/** /**
* Produces a name for the key having the given keysym using its corresponding * Returns a statically-allocated guaclog_keydef representing the key
* Unicode character. If possible, the name of the keysym is copied into the * associated with the given keysym, deriving the name and value of the key
* given buffer, which must be at least GUAC_MAX_KEY_NAME_LENGTH bytes long. * using its corresponding Unicode character.
*
* @param key_name
* The buffer to copy the key name into, which must be at least
* GUACLOG_MAX_KEY_NAME_LENGTH.
* *
* @param keysym * @param keysym
* The X11 keysym of the key whose name should be stored in * The X11 keysym of the key.
* key_name.
* *
* @return * @return
* The length of the name, in bytes, excluding null terminator, or zero if * A statically-allocated guaclog_keydef representing the key associated
* a readable name cannot be directly produced via Unicode alone. * 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 i;
int mask, bytes; int mask, bytes;
/* Translate only if keysym maps to Unicode */ /* Translate only if keysym maps to Unicode */
if (keysym < 0x00 || (keysym > 0xFF && (keysym & 0xFFFF0000) != 0x01000000)) if (keysym < 0x00 || (keysym > 0xFF && (keysym & 0xFFFF0000) != 0x01000000))
return 0; return NULL;
/* Do not translate whitespace - it will be unreadable */
if (keysym == 0x20)
return 0;
int codepoint = keysym & 0xFFFF; int codepoint = keysym & 0xFFFF;
@ -221,13 +187,11 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) {
} }
/* Otherwise, invalid codepoint */ /* Otherwise, invalid codepoint */
else { else
*(key_name++) = '?'; return NULL;
return 1;
}
/* Offset buffer by size */ /* Offset buffer by size */
key_name += bytes; char* key_name = unicode_keydef_name + bytes;
/* Add null terminator */ /* Add null terminator */
*(key_name--) = '\0'; *(key_name--) = '\0';
@ -241,36 +205,72 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) {
/* Set initial byte */ /* Set initial byte */
*key_name = mask | codepoint; *key_name = mask | codepoint;
/* Done */ /* Return static key definition */
return bytes; unicode_keydef.keysym = keysym;
unicode_keydef.name = unicode_keydef.value = unicode_keydef_name;
return &unicode_keydef;
} }
int guaclog_key_name(char* key_name, int keysym) { /**
* 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) {
int name_length; guaclog_keydef* copy = malloc(sizeof(guaclog_keydef));
/* Attempt to translate straight into a Unicode character */ /* Always copy keysym and name */
name_length = guaclog_unicode_key_name(key_name, keysym); copy->keysym = keydef->keysym;
copy->name = strdup(keydef->name);
/* If not Unicode, search for name within list of known keys */ /* Copy value only if defined */
if (name_length == 0) if (keydef->value != NULL)
name_length = guaclog_locate_key_name(key_name, keysym); copy->value = strdup(keydef->value);
else
copy->value = NULL;
/* Fallback to using hex keysym as name */ return copy;
if (name_length == 0)
name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH, }
"0x%X", keysym);
guaclog_keydef* guaclog_keydef_alloc(int keysym) {
/* Truncate name if necessary */
if (name_length >= GUACLOG_MAX_KEY_NAME_LENGTH) { guaclog_keydef* keydef;
name_length = GUACLOG_MAX_KEY_NAME_LENGTH - 1;
key_name[name_length] = '\0'; /* Check list of known keys first */
guaclog_log(GUAC_LOG_DEBUG, "Name for key 0x%X was " keydef = guaclog_get_known_key(keysym);
"truncated.", keysym); if (keydef != NULL)
} return guaclog_copy_key(keydef);
return name_length; /* 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 NULL;
}
void guaclog_keydef_free(guaclog_keydef* keydef) {
/* Ignore NULL keydef */
if (keydef == NULL)
return;
free(keydef->name);
free(keydef->value);
free(keydef);
} }

73
src/guaclog/keydef.h Normal file
View File

@ -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

View File

@ -18,7 +18,7 @@
*/ */
#include "config.h" #include "config.h"
#include "key-name.h" #include "keydef.h"
#include "log.h" #include "log.h"
#include "state.h" #include "state.h"
@ -77,10 +77,16 @@ fail_output_fd:
int guaclog_state_free(guaclog_state* state) { int guaclog_state_free(guaclog_state* state) {
int i;
/* Ignore NULL state */ /* Ignore NULL state */
if (state == NULL) if (state == NULL)
return 0; 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 */ /* Close output file */
fclose(state->output); fclose(state->output);
@ -100,8 +106,11 @@ int guaclog_state_free(guaclog_state* state) {
* @param state * @param state
* The Guacamole input log interpreter state being updated. * The Guacamole input log interpreter state being updated.
* *
* @param keysym * @param keydef
* The X11 keysym of the key being pressed or released. * 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 * @param pressed
* true if the key is being pressed, false if the key is being released. * 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 * @return
* Zero if the key state was successfully added, non-zero otherwise. * 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; int i;
/* Update existing key, if already tracked */ /* Update existing key, if already tracked */
for (i = 0; i < state->active_keys; i++) { for (i = 0; i < state->active_keys; i++) {
guaclog_key_state* key = &state->key_states[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; key->pressed = pressed;
return 0; 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 not already tracked, we need space to add it */
if (state->active_keys == GUACLOG_MAX_KEYS) { if (state->active_keys == GUACLOG_MAX_KEYS) {
guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many " guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many "
"active keys.", keysym); "active keys.", keydef->keysym);
return 1; return 1;
} }
/* Add key to state */ /* Add key to state */
guaclog_key_state* key = &state->key_states[state->active_keys++]; guaclog_key_state* key = &state->key_states[state->active_keys++];
key->keysym = keysym; key->keydef = keydef;
key->pressed = pressed; key->pressed = pressed;
return 0; 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 */ /* Reset active_keys to contain only up to the last pressed key */
for (i = state->active_keys - 1; i >= 0; i--) { for (i = state->active_keys - 1; i >= 0; i--) {
guaclog_key_state* key = &state->key_states[i]; guaclog_key_state* key = &state->key_states[i];
if (key->pressed) { if (key->pressed) {
state->active_keys = i + 1; state->active_keys = i + 1;
return; return;
} }
/* Free all trimmed states */
guaclog_keydef_free(key->keydef);
} }
/* No keys are active */ /* 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 guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) {
int i; int i;
/* Update tracked keysysm state */ /* Determine nature of key */
guaclog_state_add_key(state, keysym, pressed); guaclog_keydef* keydef = guaclog_keydef_alloc(keysym);
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); guaclog_state_trim_keys(state);
/* Output new log entries only when keys are pressed */ /* Output key states only for printable keys */
if (pressed) { if (pressed && keydef->value != NULL) {
if (guaclog_state_is_shortcut(state)) {
fprintf(state->output, "<");
/* Compose log entry by inspecting the state of each tracked key */ /* Compose log entry by inspecting the state of each tracked key */
for (i = 0; i < state->active_keys; i++) { for (i = 0; i < state->active_keys; i++) {
/* Translate keysym into human-readable name */
guaclog_key_state* key = &state->key_states[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, " ");
/* Print name of key */ /* Print name of key */
fprintf(state->output, "%s", key_name); if (i == 0)
fprintf(state->output, "%s", key->keydef->name);
else
fprintf(state->output, "+%s", key->keydef->name);
} }
/* Terminate log entry with newline */ fprintf(state->output, ">");
fprintf(state->output, "\n");
}
/* Print the key itself */
else
fprintf(state->output, "%s", keydef->value);
} }

View File

@ -21,6 +21,7 @@
#define GUACLOG_STATE_H #define GUACLOG_STATE_H
#include "config.h" #include "config.h"
#include "keydef.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@ -37,9 +38,9 @@
typedef struct guaclog_key_state { 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). * Whether the key is currently pressed (true) or released (false).