diff --git a/Makefile.am b/Makefile.am index e9233760..91c8abec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,6 +52,10 @@ if ENABLE_PULSE SUBDIRS += src/pulse endif +if ENABLE_KUBERNETES +SUBDIRS += src/protocols/kubernetes +endif + if ENABLE_RDP SUBDIRS += src/protocols/rdp endif diff --git a/configure.ac b/configure.ac index 6b20c974..ae783244 100644 --- a/configure.ac +++ b/configure.ac @@ -1172,6 +1172,46 @@ fi AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"]) AC_SUBST(WEBP_LIBS) +# +# libwebsockets +# + +have_libwebsockets=disabled +WEBSOCKETS_LIBS= +AC_ARG_WITH([websockets], + [AS_HELP_STRING([--with-websockets], + [support WebSockets @<:@default=check@:>@])], + [], + [with_websockets=check]) + +if test "x$with_websockets" != "xno" +then + have_libwebsockets=yes + AC_CHECK_LIB([websockets], + [lws_create_context], + [WEBSOCKETS_LIBS="$WEBSOCKETS_LIBS -lwebsockets"], + [have_libwebsockets=no]) +fi + +AM_CONDITIONAL([ENABLE_WEBSOCKETS], + [test "x${have_libwebsockets}" = "xyes"]) + +AC_SUBST(WEBSOCKETS_LIBS) + +# +# Kubernetes +# + +AC_ARG_ENABLE([kubernetes], + [AS_HELP_STRING([--disable-kubernetes], + [do not build support for attaching to Kubernetes pods])], + [], + [enable_kubernetes=yes]) + +AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \ + -a "x${have_libwebsockets}" = "xyes" \ + -a "x${have_terminal}" = "xyes"]) + # # guacd # @@ -1230,6 +1270,7 @@ AC_CONFIG_FILES([Makefile src/guaclog/Makefile src/guaclog/man/guaclog.1 src/pulse/Makefile + src/protocols/kubernetes/Makefile src/protocols/rdp/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile @@ -1240,10 +1281,11 @@ AC_OUTPUT # Protocol build status # -AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) -AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) -AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) -AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) +AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no]) +AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) +AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) +AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) +AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) # # Service / tool build status @@ -1287,15 +1329,17 @@ $PACKAGE_NAME version $PACKAGE_VERSION libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} + libwebsockets ....... ${have_websockets} libwebp ............. ${have_webp} wsock32 ............. ${have_winsock} Protocol support: - RDP ....... ${build_rdp} - SSH ....... ${build_ssh} - Telnet .... ${build_telnet} - VNC ....... ${build_vnc} + Kubernetes .... ${build_kubernetes} + RDP ........... ${build_rdp} + SSH ........... ${build_ssh} + Telnet ........ ${build_telnet} + VNC ........... ${build_vnc} Services / tools: diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am new file mode 100644 index 00000000..d864967f --- /dev/null +++ b/src/protocols/kubernetes/Makefile.am @@ -0,0 +1,57 @@ +# +# 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 +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libguac-client-kubernetes.la + +libguac_client_kubernetes_la_SOURCES = \ + client.c \ + clipboard.c \ + input.c \ + pipe.c \ + settings.c \ + kubernetes.c \ + user.c + +noinst_HEADERS = \ + client.h \ + clipboard.h \ + input.h \ + pipe.h \ + settings.h \ + kubernetes.h \ + user.h + +libguac_client_kubernetes_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @LIBGUAC_INCLUDE@ \ + @TERMINAL_INCLUDE@ + +libguac_client_kubernetes_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ + +libguac_client_kubernetes_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ \ + @WEBSOCKETS_LIBS@ + diff --git a/src/protocols/kubernetes/client.c b/src/protocols/kubernetes/client.c new file mode 100644 index 00000000..1b5d175d --- /dev/null +++ b/src/protocols/kubernetes/client.c @@ -0,0 +1,91 @@ +/* + * 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 "client.h" +#include "common/recording.h" +#include "kubernetes.h" +#include "settings.h" +#include "terminal/terminal.h" +#include "user.h" + +#include +#include +#include +#include +#include + +#include + +int guac_client_init(guac_client* client) { + + /* Set client args */ + client->args = GUAC_KUBERNETES_CLIENT_ARGS; + + /* Allocate client instance data */ + guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client)); + client->data = kubernetes_client; + + /* Init clipboard */ + kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH); + + /* Set handlers */ + client->join_handler = guac_kubernetes_user_join_handler; + client->free_handler = guac_kubernetes_client_free_handler; + + /* Set locale and warn if not UTF-8 */ + setlocale(LC_CTYPE, ""); + if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) { + guac_client_log(client, GUAC_LOG_INFO, + "Current locale does not use UTF-8. Some characters may " + "not render correctly."); + } + + /* Success */ + return 0; + +} + +int guac_kubernetes_client_free_handler(guac_client* client) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Clean up recording, if in progress */ + if (kubernetes_client->recording != NULL) + guac_common_recording_free(kubernetes_client->recording); + + /* Kill terminal */ + guac_terminal_free(kubernetes_client->term); + + /* TODO: Wait for and free WebSocket session, if connected */ + /*if (kubernetes_client->websocket != NULL) { + pthread_join(kubernetes_client->client_thread, NULL); + }*/ + + /* Free settings */ + if (kubernetes_client->settings != NULL) + guac_kubernetes_settings_free(kubernetes_client->settings); + + guac_common_clipboard_free(kubernetes_client->clipboard); + free(kubernetes_client); + return 0; + +} + diff --git a/src/protocols/kubernetes/client.h b/src/protocols/kubernetes/client.h new file mode 100644 index 00000000..2e96d109 --- /dev/null +++ b/src/protocols/kubernetes/client.h @@ -0,0 +1,41 @@ +/* + * 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 GUAC_KUBERNETES_CLIENT_H +#define GUAC_KUBERNETES_CLIENT_H + +#include "config.h" +#include "terminal/terminal.h" + +#include +#include + +/** + * The maximum number of bytes to allow within the clipboard. + */ +#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144 + +/** + * Free handler. Required by libguac and called when the guac_client is + * disconnected and must be cleaned up. + */ +guac_client_free_handler guac_kubernetes_client_free_handler; + +#endif + diff --git a/src/protocols/kubernetes/clipboard.c b/src/protocols/kubernetes/clipboard.c new file mode 100644 index 00000000..87a34b04 --- /dev/null +++ b/src/protocols/kubernetes/clipboard.c @@ -0,0 +1,67 @@ +/* + * 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 "clipboard.h" +#include "common/clipboard.h" +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include +#include + +int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Clear clipboard and prepare for new data */ + guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype); + + /* Set handlers for clipboard stream */ + stream->blob_handler = guac_kubernetes_clipboard_blob_handler; + stream->end_handler = guac_kubernetes_clipboard_end_handler; + + return 0; +} + +int guac_kubernetes_clipboard_blob_handler(guac_user* user, + guac_stream* stream, void* data, int length) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Append new data */ + guac_common_clipboard_append(kubernetes_client->clipboard, data, length); + + return 0; +} + +int guac_kubernetes_clipboard_end_handler(guac_user* user, + guac_stream* stream) { + + /* Nothing to do - clipboard is implemented within client */ + + return 0; +} + diff --git a/src/protocols/kubernetes/clipboard.h b/src/protocols/kubernetes/clipboard.h new file mode 100644 index 00000000..009219cf --- /dev/null +++ b/src/protocols/kubernetes/clipboard.h @@ -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. + */ + +#ifndef GUAC_KUBERNETES_CLIPBOARD_H +#define GUAC_KUBERNETES_CLIPBOARD_H + +#include "config.h" + +#include + +/** + * Handler for inbound clipboard streams. + */ +guac_user_clipboard_handler guac_kubernetes_clipboard_handler; + +/** + * Handler for data received along clipboard streams. + */ +guac_user_blob_handler guac_kubernetes_clipboard_blob_handler; + +/** + * Handler for end-of-stream related to clipboard. + */ +guac_user_end_handler guac_kubernetes_clipboard_end_handler; + +#endif + diff --git a/src/protocols/kubernetes/input.c b/src/protocols/kubernetes/input.c new file mode 100644 index 00000000..9bf5b717 --- /dev/null +++ b/src/protocols/kubernetes/input.c @@ -0,0 +1,95 @@ +/* + * 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 "common/recording.h" +#include "kubernetes.h" +#include "input.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include + +int guac_kubernetes_user_mouse_handler(guac_user* user, + int x, int y, int mask) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Skip if terminal not yet ready */ + guac_terminal* term = kubernetes_client->term; + if (term == NULL) + return 0; + + /* Report mouse position within recording */ + if (kubernetes_client->recording != NULL) + guac_common_recording_report_mouse(kubernetes_client->recording, x, y, + mask); + + guac_terminal_send_mouse(term, user, x, y, mask); + return 0; + +} + +int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Report key state within recording */ + if (kubernetes_client->recording != NULL) + guac_common_recording_report_key(kubernetes_client->recording, + keysym, pressed); + + /* Skip if terminal not yet ready */ + guac_terminal* term = kubernetes_client->term; + if (term == NULL) + return 0; + + guac_terminal_send_key(term, keysym, pressed); + return 0; + +} + +int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) { + + /* Get terminal */ + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Skip if terminal not yet ready */ + guac_terminal* terminal = kubernetes_client->term; + if (terminal == NULL) + return 0; + + /* Resize terminal */ + guac_terminal_resize(terminal, width, height); + + /* TODO: Update Kubernetes terminal window size if connected */ + + return 0; +} + diff --git a/src/protocols/kubernetes/input.h b/src/protocols/kubernetes/input.h new file mode 100644 index 00000000..ac65835c --- /dev/null +++ b/src/protocols/kubernetes/input.h @@ -0,0 +1,46 @@ +/* + * 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 GUAC_KUBERNETES_INPUT_H +#define GUAC_KUBERNETES_INPUT_H + +#include "config.h" + +#include + +/** + * Handler for key events. Required by libguac and called whenever key events + * are received. + */ +guac_user_key_handler guac_kubernetes_user_key_handler; + +/** + * Handler for mouse events. Required by libguac and called whenever mouse + * events are received. + */ +guac_user_mouse_handler guac_kubernetes_user_mouse_handler; + +/** + * Handler for size events. Required by libguac and called whenever the remote + * display (window) is resized. + */ +guac_user_size_handler guac_kubernetes_user_size_handler; + +#endif + diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c new file mode 100644 index 00000000..231d78d3 --- /dev/null +++ b/src/protocols/kubernetes/kubernetes.c @@ -0,0 +1,136 @@ +/* + * 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 "common/recording.h" +#include "kubernetes.h" +#include "terminal/terminal.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Input thread, started by the main Kubernetes client thread. This thread + * continuously reads from the terminal's STDIN and transfers all read + * data to the Kubernetes connection. + * + * @param data + * The current guac_client instance. + * + * @return + * Always NULL. + */ +static void* guac_kubernetes_input_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + char buffer[8192]; + int bytes_read; + + /* Write all data read */ + while ((bytes_read = guac_terminal_read_stdin(kubernetes_client->term, buffer, sizeof(buffer))) > 0) { + + /* TODO: Send to Kubernetes */ + guac_terminal_write(kubernetes_client->term, buffer, bytes_read); + + } + + return NULL; + +} + +void* guac_kubernetes_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + guac_kubernetes_settings* settings = kubernetes_client->settings; + + pthread_t input_thread; + + /* Set up screen recording, if requested */ + if (settings->recording_path != NULL) { + kubernetes_client->recording = guac_common_recording_create(client, + settings->recording_path, + settings->recording_name, + settings->create_recording_path, + !settings->recording_exclude_output, + !settings->recording_exclude_mouse, + settings->recording_include_keys); + } + + /* Create terminal */ + kubernetes_client->term = guac_terminal_create(client, + kubernetes_client->clipboard, + settings->max_scrollback, settings->font_name, settings->font_size, + settings->resolution, settings->width, settings->height, + settings->color_scheme, settings->backspace); + + /* Fail if terminal init failed */ + if (kubernetes_client->term == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Terminal initialization failed"); + return NULL; + } + + /* Set up typescript, if requested */ + if (settings->typescript_path != NULL) { + guac_terminal_create_typescript(kubernetes_client->term, + settings->typescript_path, + settings->typescript_name, + settings->create_typescript_path); + } + + /* TODO: Open WebSocket connection to Kubernetes */ + + /* Logged in */ + guac_client_log(client, GUAC_LOG_INFO, + "Kubernetes connection successful."); + + /* Start input thread */ + if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread"); + return NULL; + } + + /* TODO: While data available, write to terminal */ + + /* Kill client and Wait for input thread to die */ + guac_client_stop(client); + pthread_join(input_thread, NULL); + + guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended."); + return NULL; + +} + diff --git a/src/protocols/kubernetes/kubernetes.h b/src/protocols/kubernetes/kubernetes.h new file mode 100644 index 00000000..f8035ae5 --- /dev/null +++ b/src/protocols/kubernetes/kubernetes.h @@ -0,0 +1,71 @@ +/* + * 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 GUAC_KUBERNETES_H +#define GUAC_KUBERNETES_H + +#include "config.h" +#include "common/clipboard.h" +#include "common/recording.h" +#include "settings.h" +#include "terminal/terminal.h" + +#include + +/** + * Kubernetes-specific client data. + */ +typedef struct guac_kubernetes_client { + + /** + * Kubernetes connection settings. + */ + guac_kubernetes_settings* settings; + + /** + * The Kubernetes client thread. + */ + pthread_t client_thread; + + /** + * The current clipboard contents. + */ + guac_common_clipboard* clipboard; + + /** + * The terminal which will render all output from the Kubernetes pod. + */ + guac_terminal* term; + + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_common_recording* recording; + +} guac_kubernetes_client; + +/** + * Main Kubernetes client thread, handling transfer of STDOUT/STDERR of an + * attached Kubernetes pod to STDOUT of the terminal. + */ +void* guac_kubernetes_client_thread(void* data); + +#endif + diff --git a/src/protocols/kubernetes/pipe.c b/src/protocols/kubernetes/pipe.c new file mode 100644 index 00000000..242105b1 --- /dev/null +++ b/src/protocols/kubernetes/pipe.c @@ -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. + */ + +#include "config.h" +#include "kubernetes.h" +#include "pipe.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include + +int guac_kubernetes_pipe_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* name) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Redirect STDIN if pipe has required name */ + if (strcmp(name, GUAC_KUBERNETES_STDIN_PIPE_NAME) == 0) { + guac_terminal_send_stream(kubernetes_client->term, user, stream); + return 0; + } + + /* No other inbound pipe streams are supported */ + guac_protocol_send_ack(user->socket, stream, "No such input stream.", + GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND); + guac_socket_flush(user->socket); + return 0; + +} + diff --git a/src/protocols/kubernetes/pipe.h b/src/protocols/kubernetes/pipe.h new file mode 100644 index 00000000..7acae3cb --- /dev/null +++ b/src/protocols/kubernetes/pipe.h @@ -0,0 +1,42 @@ +/* + * 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 GUAC_KUBERNETES_PIPE_H +#define GUAC_KUBERNETES_PIPE_H + +#include "config.h" + +#include + +/** + * The name reserved for the inbound pipe stream which forces the terminal + * emulator's STDIN to be received from the pipe. + */ +#define GUAC_KUBERNETES_STDIN_PIPE_NAME "STDIN" + +/** + * Handles an incoming stream from a Guacamole "pipe" instruction. If the pipe + * is named "STDIN", the the contents of the pipe stream are redirected to + * STDIN of the terminal emulator for as long as the pipe is open. + */ +guac_user_pipe_handler guac_kubernetes_pipe_handler; + +#endif + diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c new file mode 100644 index 00000000..1f04b401 --- /dev/null +++ b/src/protocols/kubernetes/settings.c @@ -0,0 +1,366 @@ +/* + * 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 "settings.h" + +#include + +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { + "hostname", + "port", + "use-ssl", + "client-cert-file", + "client-key-file", + "ca-cert-file", + "ignore-cert", + "font-name", + "font-size", + "color-scheme", + "typescript-path", + "typescript-name", + "create-typescript-path", + "recording-path", + "recording-name", + "recording-exclude-output", + "recording-exclude-mouse", + "recording-include-keys", + "create-recording-path", + "read-only", + "backspace", + "scrollback", + NULL +}; + +enum KUBERNETES_ARGS_IDX { + + /** + * The hostname to connect to. Required. + */ + IDX_HOSTNAME, + + /** + * The port to connect to. Optional. + */ + IDX_PORT, + + /** + * Whether SSL/TLS should be used. SSL is used by default. + */ + IDX_USE_SSL, + + /** + * The filename of the certificate to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + IDX_CLIENT_CERT_FILE, + + /** + * The filename of the key to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + IDX_CLIENT_KEY_FILE, + + /** + * The filename of the certificate of the certificate authority that signed + * the certificate of the Kubernetes server. + */ + IDX_CA_CERT_FILE, + + /** + * Whether the certificate used by the Kubernetes server for SSL/TLS should + * be ignored if it cannot be validated. + */ + IDX_IGNORE_CERT, + + /** + * The name of the font to use within the terminal. + */ + IDX_FONT_NAME, + + /** + * The size of the font to use within the terminal, in points. + */ + IDX_FONT_SIZE, + + /** + * The color scheme to use, as a series of semicolon-separated color-value + * pairs: "background: ", "foreground: ", or + * "color: ", where is a number from 0 to 255, and is + * "color" or an X11 color code (e.g. "aqua" or "rgb:12/34/56"). + * The color scheme can also be one of the special values: "black-white", + * "white-black", "gray-black", or "green-black". + */ + IDX_COLOR_SCHEME, + + /** + * The full absolute path to the directory in which typescripts should be + * written. + */ + IDX_TYPESCRIPT_PATH, + + /** + * The name that should be given to typescripts which are written in the + * given path. Each typescript will consist of two files: "NAME" and + * "NAME.timing". + */ + IDX_TYPESCRIPT_NAME, + + /** + * Whether the specified typescript path should automatically be created + * if it does not yet exist. + */ + IDX_CREATE_TYPESCRIPT_PATH, + + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recordings which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + IDX_RECORDING_EXCLUDE_OUTPUT, + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + IDX_RECORDING_EXCLUDE_MOUSE, + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_KEYS, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + + /** + * "true" if this connection should be read-only (user input should be + * dropped), "false" or blank otherwise. + */ + IDX_READ_ONLY, + + /** + * ASCII code, as an integer to use for the backspace key, or 127 + * if not specified. + */ + IDX_BACKSPACE, + + /** + * The maximum size of the scrollback buffer in rows. + */ + IDX_SCROLLBACK, + + KUBERNETES_ARGS_COUNT +}; + +guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != KUBERNETES_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + KUBERNETES_ARGS_COUNT, argc); + return NULL; + } + + guac_kubernetes_settings* settings = + calloc(1, sizeof(guac_kubernetes_settings)); + + /* Read parameters */ + settings->hostname = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_HOSTNAME, ""); + + /* Parse whether SSL should be used */ + settings->use_ssl = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_USE_SSL, true); + + /* Read SSL/TLS connection details only if enabled */ + if (settings->use_ssl) { + + settings->client_cert_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CLIENT_CERT_FILE, NULL); + + settings->client_key_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CLIENT_KEY_FILE, NULL); + + settings->ca_cert_file = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_CA_CERT_FILE, NULL); + + settings->ignore_cert = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, + argv, IDX_IGNORE_CERT, false); + + } + + /* Read-only mode */ + settings->read_only = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_READ_ONLY, false); + + /* Read maximum scrollback size */ + settings->max_scrollback = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_SCROLLBACK, GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK); + + /* Read font name */ + settings->font_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_FONT_NAME, GUAC_KUBERNETES_DEFAULT_FONT_NAME); + + /* Read font size */ + settings->font_size = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_FONT_SIZE, GUAC_KUBERNETES_DEFAULT_FONT_SIZE); + + /* Copy requested color scheme */ + settings->color_scheme = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_COLOR_SCHEME, ""); + + /* Pull width/height/resolution directly from user */ + settings->width = user->info.optimal_width; + settings->height = user->info.optimal_height; + settings->resolution = user->info.optimal_resolution; + + /* Read port */ + settings->port = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT); + + /* Read typescript path */ + settings->typescript_path = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_PATH, NULL); + + /* Read typescript name */ + settings->typescript_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_TYPESCRIPT_NAME, GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME); + + /* Parse path creation flag */ + settings->create_typescript_path = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_CREATE_TYPESCRIPT_PATH, false); + + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_KUBERNETES_DEFAULT_RECORDING_NAME); + + /* Parse output exclusion flag */ + settings->recording_exclude_output = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_OUTPUT, false); + + /* Parse mouse exclusion flag */ + settings->recording_exclude_mouse = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_MOUSE, false); + + /* Parse key event inclusion flag */ + settings->recording_include_keys = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_KEYS, false); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + + /* Parse backspace key code */ + settings->backspace = + guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_BACKSPACE, 127); + + /* Parsing was successful */ + return settings; + +} + +void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { + + /* Free network connection information */ + free(settings->hostname); + free(settings->port); + + /* Free SSL/TLS details */ + free(settings->client_cert_file); + free(settings->client_key_file); + free(settings->ca_cert_file); + + /* Free display preferences */ + free(settings->font_name); + free(settings->color_scheme); + + /* Free typescript settings */ + free(settings->typescript_name); + free(settings->typescript_path); + + /* Free screen recording settings */ + free(settings->recording_name); + free(settings->recording_path); + + /* Free overall structure */ + free(settings); + +} + diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h new file mode 100644 index 00000000..3e89ce53 --- /dev/null +++ b/src/protocols/kubernetes/settings.h @@ -0,0 +1,256 @@ +/* + * 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 GUAC_KUBERNETES_SETTINGS_H +#define GUAC_KUBERNETES_SETTINGS_H + +#include "config.h" + +#include + +#include +#include + +/** + * The name of the font to use for the terminal if no name is specified. + */ +#define GUAC_KUBERNETES_DEFAULT_FONT_NAME "monospace" + +/** + * The size of the font to use for the terminal if no font size is specified, + * in points. + */ +#define GUAC_KUBERNETES_DEFAULT_FONT_SIZE 12 + +/** + * The port to connect to when initiating any Kubernetes connection, if no + * other port is specified. + */ +#define GUAC_KUBERNETES_DEFAULT_PORT "8443" + +/** + * The filename to use for the typescript, if not specified. + */ +#define GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME "typescript" + +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_KUBERNETES_DEFAULT_RECORDING_NAME "recording" + +/** + * The default maximum scrollback size in rows. + */ +#define GUAC_KUBERNETES_DEFAULT_MAX_SCROLLBACK 1000 + +/** + * Settings for the Kubernetes connection. The values for this structure are + * parsed from the arguments given during the Guacamole protocol handshake + * using the guac_kubernetes_parse_args() function. + */ +typedef struct guac_kubernetes_settings { + + /** + * The hostname of the Kubernetes server to connect to. + */ + char* hostname; + + /** + * The port of the Kubernetes server to connect to. + */ + char* port; + + /** + * Whether SSL/TLS should be used. + */ + bool use_ssl; + + /** + * The filename of the certificate to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + char* client_cert_file; + + /** + * The filename of the key to use if performing SSL/TLS client + * authentication to authenticate with the Kubernetes server. If omitted, + * SSL client authentication will not be performed. + */ + char* client_key_file; + + /** + * The filename of the certificate of the certificate authority that signed + * the certificate of the Kubernetes server. + */ + char* ca_cert_file; + + /** + * Whether the certificate used by the Kubernetes server for SSL/TLS should + * be ignored if it cannot be validated. + */ + bool ignore_cert; + + /** + * Whether this connection is read-only, and user input should be dropped. + */ + bool read_only; + + /** + * The maximum size of the scrollback buffer in rows. + */ + int max_scrollback; + + /** + * The name of the font to use for display rendering. + */ + char* font_name; + + /** + * The size of the font to use, in points. + */ + int font_size; + + /** + * The name of the color scheme to use. + */ + char* color_scheme; + + /** + * The desired width of the terminal display, in pixels. + */ + int width; + + /** + * The desired height of the terminal display, in pixels. + */ + int height; + + /** + * The desired screen resolution, in DPI. + */ + int resolution; + + /** + * The path in which the typescript should be saved, if enabled. If no + * typescript should be saved, this will be NULL. + */ + char* typescript_path; + + /** + * The filename to use for the typescript, if enabled. + */ + char* typescript_name; + + /** + * Whether the typescript path should be automatically created if it does + * not already exist. + */ + bool create_typescript_path; + + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + bool recording_exclude_output; + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + bool recording_exclude_mouse; + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_keys; + + /** + * The ASCII code, as an integer, that the Kubernetes client will use when + * the backspace key is pressed. By default, this is 127, ASCII delete, if + * not specified in the client settings. + */ + int backspace; + +} guac_kubernetes_settings; + +/** + * Parses all given args, storing them in a newly-allocated settings object. If + * the args fail to parse, NULL is returned. + * + * @param user + * The user who submitted the given arguments while joining the + * connection. + * + * @param argc + * The number of arguments within the argv array. + * + * @param argv + * The values of all arguments provided by the user. + * + * @return + * A newly-allocated settings object which must be freed with + * guac_kubernetes_settings_free() when no longer needed. If the arguments + * fail to parse, NULL is returned. + */ +guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_kubernetes_settings object, having been previously + * allocated via guac_kubernetes_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_kubernetes_settings_free(guac_kubernetes_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_KUBERNETES_CLIENT_ARGS[]; + +#endif + diff --git a/src/protocols/kubernetes/user.c b/src/protocols/kubernetes/user.c new file mode 100644 index 00000000..62666cb5 --- /dev/null +++ b/src/protocols/kubernetes/user.c @@ -0,0 +1,116 @@ +/* + * 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 "clipboard.h" +#include "input.h" +#include "kubernetes.h" +#include "pipe.h" +#include "settings.h" +#include "terminal/terminal.h" +#include "user.h" + +#include +#include +#include + +#include +#include + +int guac_kubernetes_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_client* client = user->client; + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) client->data; + + /* Parse provided arguments */ + guac_kubernetes_settings* settings = guac_kubernetes_parse_args(user, + argc, (const char**) argv); + + /* Fail if settings cannot be parsed */ + if (settings == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Badly formatted client arguments."); + return 1; + } + + /* Store settings at user level */ + user->data = settings; + + /* Connect to Kubernetes if owner */ + if (user->owner) { + + /* Store owner's settings at client level */ + kubernetes_client->settings = settings; + + /* Start client thread */ + if (pthread_create(&(kubernetes_client->client_thread), NULL, + guac_kubernetes_client_thread, (void*) client)) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to start Kubernetes client thread"); + return 1; + } + + } + + /* If not owner, synchronize with current display */ + else { + guac_terminal_dup(kubernetes_client->term, user, user->socket); + guac_socket_flush(user->socket); + } + + /* Only handle events if not read-only */ + if (!settings->read_only) { + + /* General mouse/keyboard/clipboard events */ + user->key_handler = guac_kubernetes_user_key_handler; + user->mouse_handler = guac_kubernetes_user_mouse_handler; + user->clipboard_handler = guac_kubernetes_clipboard_handler; + + /* STDIN redirection */ + user->pipe_handler = guac_kubernetes_pipe_handler; + + /* Display size change events */ + user->size_handler = guac_kubernetes_user_size_handler; + + } + + return 0; + +} + +int guac_kubernetes_user_leave_handler(guac_user* user) { + + guac_kubernetes_client* kubernetes_client = + (guac_kubernetes_client*) user->client->data; + + /* Update shared cursor state */ + guac_common_cursor_remove_user(kubernetes_client->term->cursor, user); + + /* Free settings if not owner (owner settings will be freed with client) */ + if (!user->owner) { + guac_kubernetes_settings* settings = + (guac_kubernetes_settings*) user->data; + guac_kubernetes_settings_free(settings); + } + + return 0; +} + diff --git a/src/protocols/kubernetes/user.h b/src/protocols/kubernetes/user.h new file mode 100644 index 00000000..d235b2b7 --- /dev/null +++ b/src/protocols/kubernetes/user.h @@ -0,0 +1,38 @@ +/* + * 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 GUAC_KUBERNETES_USER_H +#define GUAC_KUBERNETES_USER_H + +#include "config.h" + +#include + +/** + * Handler for joining users. + */ +guac_user_join_handler guac_kubernetes_user_join_handler; + +/** + * Handler for leaving users. + */ +guac_user_leave_handler guac_kubernetes_user_leave_handler; + +#endif +