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 \
instructions.h \
interpret.h \
key-name.h \
keydef.h \
log.h \
state.h
@ -37,7 +37,7 @@ guaclog_SOURCES = \
instructions.c \
instruction-key.c \
interpret.c \
key-name.c \
keydef.c \
log.c \
state.c

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 "key-name.h"
#include "keydef.h"
#include "log.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.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;
#include <string.h>
/**
* All known keys.
*/
const guaclog_known_key known_keys[] = {
{ 0x0020, "Space" },
const guaclog_keydef known_keys[] = {
{ 0xFE03, "AltGr" },
{ 0xFF08, "Backspace" },
{ 0xFF09, "Tab" },
{ 0xFF09, "Tab", "<Tab>" },
{ 0xFF0B, "Clear" },
{ 0xFF0D, "Return" },
{ 0xFF0D, "Return", "\n" },
{ 0xFF13, "Pause" },
{ 0xFF1B, "Escape" },
{ 0xFF51, "Left" },
@ -62,9 +46,8 @@ const guaclog_known_key known_keys[] = {
{ 0xFF63, "Insert" },
{ 0xFF65, "Undo" },
{ 0xFF6A, "Help" },
{ 0xFF80, "Space" },
{ 0xFF8D, "Enter" },
{ 0xFFBD, "Equals" },
{ 0xFF80, "Space", " " },
{ 0xFF8D, "Enter", "\n" },
{ 0xFFBE, "F1" },
{ 0xFFBF, "F2" },
{ 0xFFC0, "F3" },
@ -89,8 +72,8 @@ const guaclog_known_key known_keys[] = {
{ 0xFFD3, "F22" },
{ 0xFFD4, "F23" },
{ 0xFFD5, "F24" },
{ 0xFFE1, "Shift" },
{ 0xFFE2, "Shift" },
{ 0xFFE1, "Shift", "" },
{ 0xFFE2, "Shift", "" },
{ 0xFFE3, "Ctrl" },
{ 0xFFE4, "Ctrl" },
{ 0xFFE5, "Caps" },
@ -108,7 +91,7 @@ const guaclog_known_key known_keys[] = {
/**
* Comparator for the standard bsearch() function which compares an integer
* keysym against the keysym associated with a guaclog_known_key.
* keysym against the keysym associated with a guaclog_keydef.
*
* @param key
* The key value being compared against the member. This MUST be the
@ -125,11 +108,11 @@ const guaclog_known_key known_keys[] = {
* member, or a negative value if the given keysym is less than that of the
* given member.
*/
static int guaclog_known_key_bsearch_compare(const void* key,
static int guaclog_keydef_bsearch_compare(const void* key,
const void* member) {
int keysym = (int) ((intptr_t) key);
guaclog_known_key* current = (guaclog_known_key*) member;
guaclog_keydef* current = (guaclog_keydef*) member;
/* Compare given keysym to keysym of current member */
return keysym - current->keysym;
@ -138,67 +121,50 @@ static int guaclog_known_key_bsearch_compare(const void* key,
/**
* Searches through the known_keys array of known keys for the name of the key
* having the given keysym. If found, the name of the keysym is copied into the
* given buffer, which must be at least GUACLOG_MAX_KEY_NAME_LENGTH bytes long.
*
* @param key_name
* The buffer to copy the key name into, which must be at least
* GUACLOG_MAX_KEY_NAME_LENGTH.
* having the given keysym, returning a pointer to the static guaclog_keydef
* within the array if found.
*
* @param keysym
* The X11 keysym of the key whose name should be stored in
* key_name.
* The X11 keysym of the key.
*
* @return
* The length of the name, in bytes, excluding null terminator, or zero if
* the key could not be found.
* A pointer to the static guaclog_keydef associated with the given keysym,
* or NULL if the key could not be found.
*/
static int guaclog_locate_key_name(char* key_name, int keysym) {
static guaclog_keydef* guaclog_get_known_key(int keysym) {
/* Search through known keys for given keysym */
guaclog_known_key* found = bsearch((void*) ((intptr_t) keysym),
return bsearch((void*) ((intptr_t) keysym),
known_keys, sizeof(known_keys) / sizeof(known_keys[0]),
sizeof(known_keys[0]), guaclog_known_key_bsearch_compare);
/* If found, format name and return length of result */
if (found != NULL)
return snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH,
"[ %s ]", found->name);
/* Key not found */
return 0;
sizeof(known_keys[0]), guaclog_keydef_bsearch_compare);
}
/**
* Produces a name for the key having the given keysym using its corresponding
* Unicode character. If possible, the name of the keysym is copied into the
* given buffer, which must be at least GUAC_MAX_KEY_NAME_LENGTH bytes long.
*
* @param key_name
* The buffer to copy the key name into, which must be at least
* GUACLOG_MAX_KEY_NAME_LENGTH.
* Returns a statically-allocated guaclog_keydef representing the key
* associated with the given keysym, deriving the name and value of the key
* using its corresponding Unicode character.
*
* @param keysym
* The X11 keysym of the key whose name should be stored in
* key_name.
* The X11 keysym of the key.
*
* @return
* The length of the name, in bytes, excluding null terminator, or zero if
* a readable name cannot be directly produced via Unicode alone.
* A statically-allocated guaclog_keydef representing the key associated
* with the given keysym, or NULL if the given keysym has no corresponding
* Unicode character.
*/
static int guaclog_unicode_key_name(char* key_name, int keysym) {
static guaclog_keydef* guaclog_get_unicode_key(int keysym) {
static char unicode_keydef_name[8];
static guaclog_keydef unicode_keydef;
int i;
int mask, bytes;
/* Translate only if keysym maps to Unicode */
if (keysym < 0x00 || (keysym > 0xFF && (keysym & 0xFFFF0000) != 0x01000000))
return 0;
/* Do not translate whitespace - it will be unreadable */
if (keysym == 0x20)
return 0;
return NULL;
int codepoint = keysym & 0xFFFF;
@ -221,13 +187,11 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) {
}
/* Otherwise, invalid codepoint */
else {
*(key_name++) = '?';
return 1;
}
else
return NULL;
/* Offset buffer by size */
key_name += bytes;
char* key_name = unicode_keydef_name + bytes;
/* Add null terminator */
*(key_name--) = '\0';
@ -241,36 +205,72 @@ static int guaclog_unicode_key_name(char* key_name, int keysym) {
/* Set initial byte */
*key_name = mask | codepoint;
/* Done */
return bytes;
/* Return static key definition */
unicode_keydef.keysym = keysym;
unicode_keydef.name = unicode_keydef.value = unicode_keydef_name;
return &unicode_keydef;
}
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 */
name_length = guaclog_unicode_key_name(key_name, keysym);
/* Always copy keysym and name */
copy->keysym = keydef->keysym;
copy->name = strdup(keydef->name);
/* If not Unicode, search for name within list of known keys */
if (name_length == 0)
name_length = guaclog_locate_key_name(key_name, keysym);
/* Copy value only if defined */
if (keydef->value != NULL)
copy->value = strdup(keydef->value);
else
copy->value = NULL;
/* Fallback to using hex keysym as name */
if (name_length == 0)
name_length = snprintf(key_name, GUACLOG_MAX_KEY_NAME_LENGTH,
"0x%X", keysym);
/* Truncate name if necessary */
if (name_length >= GUACLOG_MAX_KEY_NAME_LENGTH) {
name_length = GUACLOG_MAX_KEY_NAME_LENGTH - 1;
key_name[name_length] = '\0';
guaclog_log(GUAC_LOG_DEBUG, "Name for key 0x%X was "
"truncated.", keysym);
}
return name_length;
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 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 "key-name.h"
#include "keydef.h"
#include "log.h"
#include "state.h"
@ -77,10 +77,16 @@ fail_output_fd:
int guaclog_state_free(guaclog_state* state) {
int i;
/* Ignore NULL state */
if (state == NULL)
return 0;
/* Free keydefs of all tracked keys */
for (i = 0; i < state->active_keys; i++)
guaclog_keydef_free(state->key_states[i].keydef);
/* Close output file */
fclose(state->output);
@ -100,8 +106,11 @@ int guaclog_state_free(guaclog_state* state) {
* @param state
* The Guacamole input log interpreter state being updated.
*
* @param keysym
* The X11 keysym of the key being pressed or released.
* @param keydef
* The guaclog_keydef of the key being pressed or released. This
* guaclog_keydef will automatically be freed along with the guaclog_state
* if the key state was successfully added, and must be manually freed
* otherwise.
*
* @param pressed
* true if the key is being pressed, false if the key is being released.
@ -109,14 +118,17 @@ int guaclog_state_free(guaclog_state* state) {
* @return
* Zero if the key state was successfully added, non-zero otherwise.
*/
static int guaclog_state_add_key(guaclog_state* state, int keysym, bool pressed) {
static int guaclog_state_add_key(guaclog_state* state, guaclog_keydef* keydef,
bool pressed) {
int i;
/* Update existing key, if already tracked */
for (i = 0; i < state->active_keys; i++) {
guaclog_key_state* key = &state->key_states[i];
if (key->keysym == keysym) {
if (key->keydef->keysym == keydef->keysym) {
guaclog_keydef_free(key->keydef);
key->keydef = keydef;
key->pressed = pressed;
return 0;
}
@ -125,13 +137,13 @@ static int guaclog_state_add_key(guaclog_state* state, int keysym, bool pressed)
/* If not already tracked, we need space to add it */
if (state->active_keys == GUACLOG_MAX_KEYS) {
guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many "
"active keys.", keysym);
"active keys.", keydef->keysym);
return 1;
}
/* Add key to state */
guaclog_key_state* key = &state->key_states[state->active_keys++];
key->keysym = keysym;
key->keydef = keydef;
key->pressed = pressed;
return 0;
@ -152,11 +164,16 @@ static void guaclog_state_trim_keys(guaclog_state* state) {
/* Reset active_keys to contain only up to the last pressed key */
for (i = state->active_keys - 1; i >= 0; i--) {
guaclog_key_state* key = &state->key_states[i];
if (key->pressed) {
state->active_keys = i + 1;
return;
}
/* Free all trimmed states */
guaclog_keydef_free(key->keydef);
}
/* No keys are active */
@ -164,44 +181,76 @@ static void guaclog_state_trim_keys(guaclog_state* state) {
}
/**
* Returns whether the current tracked key state represents an in-progress
* keyboard shortcut.
*
* @param state
* The Guacamole input log interpreter state to test.
*
* @return
* true if the given state represents an in-progress keyboard shortcut,
* false otherwise.
*/
static bool guaclog_state_is_shortcut(guaclog_state* state) {
int i;
/* We are in a shortcut if at least one key is non-printable */
for (i = 0; i < state->active_keys; i++) {
guaclog_key_state* key = &state->key_states[i];
if (key->keydef->value == NULL)
return true;
}
/* All keys are printable - no shortcut */
return false;
}
int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) {
int i;
/* Update tracked keysysm state */
guaclog_state_add_key(state, keysym, pressed);
guaclog_state_trim_keys(state);
/* Determine nature of key */
guaclog_keydef* keydef = guaclog_keydef_alloc(keysym);
if (keydef == NULL)
return 0;
/* Output new log entries only when keys are pressed */
if (pressed) {
/* Update tracked key state */
if (guaclog_state_add_key(state, keydef, pressed))
guaclog_keydef_free(keydef);
else
guaclog_state_trim_keys(state);
/* Compose log entry by inspecting the state of each tracked key */
for (i = 0; i < state->active_keys; i++) {
/* Output key states only for printable keys */
if (pressed && keydef->value != NULL) {
guaclog_key_state* key = &state->key_states[i];
if (guaclog_state_is_shortcut(state)) {
/* Translate keysym into human-readable name */
char key_name[GUACLOG_MAX_KEY_NAME_LENGTH];
int name_length = guaclog_key_name(key_name, key->keysym);
fprintf(state->output, "<");
/* 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);
/* 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 */
fprintf(state->output, "%s", key_name);
fprintf(state->output, ">");
}
/* Terminate log entry with newline */
fprintf(state->output, "\n");
/* Print the key itself */
else
fprintf(state->output, "%s", keydef->value);
}

View File

@ -21,6 +21,7 @@
#define GUACLOG_STATE_H
#include "config.h"
#include "keydef.h"
#include <stdbool.h>
#include <stdio.h>
@ -37,9 +38,9 @@
typedef struct guaclog_key_state {
/**
* The X11 keysym of the key.
* The definition of the key.
*/
int keysym;
guaclog_keydef* keydef;
/**
* Whether the key is currently pressed (true) or released (false).