diff --git a/Makefile.am b/Makefile.am index 6ce3814a2..87c37090b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,6 +39,7 @@ DIST_SUBDIRS = \ src/protocols/rdp \ src/protocols/ssh \ src/protocols/telnet \ + src/protocols/ipmi \ src/protocols/vnc SUBDIRS = \ @@ -73,6 +74,10 @@ if ENABLE_TELNET SUBDIRS += src/protocols/telnet endif +if ENABLE_IPMI +SUBDIRS += src/protocols/ipmi +endif + if ENABLE_VNC SUBDIRS += src/protocols/vnc endif diff --git a/configure.ac b/configure.ac index 2f2560dd8..51fe5dd2a 100644 --- a/configure.ac +++ b/configure.ac @@ -210,6 +210,10 @@ AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh') AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_LTLIB], '$(top_builddir)/src/protocols/kubernetes/libguac-client-kubernetes.la') AC_SUBST([LIBGUAC_CLIENT_KUBERNETES_INCLUDE], '-I$(top_srcdir)/src/protocols/kubernetes') +# IPMI support +AC_SUBST([LIBGUAC_CLIENT_IPMI_LTLIB], '$(top_builddir)/src/protocols/ipmi/libguac-client-ipmi.la') +AC_SUBST([LIBGUAC_CLIENT_IPMI_INCLUDE], '-I$(top_srcdir)/src/protocols/ipmi') + # RDP support AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la') AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp') @@ -1304,6 +1308,38 @@ AM_CONDITIONAL([ENABLE_TELNET], [test "x${have_libtelnet}" = "xyes" \ AC_SUBST(TELNET_LIBS) +# +# FreeIPMI (libipmiconsole + libfreeipmi) +# + +have_freeipmi=disabled +IPMI_LIBS= +IPMI_CFLAGS= +AC_ARG_WITH([ipmi], + [AS_HELP_STRING([--with-ipmi], + [support IPMI Serial-over-LAN @<:@default=check@:>@])], + [], + [with_ipmi=check]) + +if test "x$with_ipmi" != "xno" +then + have_freeipmi=yes + + # The SOL console is driven by libipmiconsole, while chassis power + # management is driven by libfreeipmi. + AC_CHECK_HEADER([ipmiconsole.h],, [have_freeipmi=no]) + AC_CHECK_LIB([ipmiconsole], [ipmiconsole_ctx_create], + [IPMI_LIBS="$IPMI_LIBS -lipmiconsole"], [have_freeipmi=no]) + AC_CHECK_LIB([freeipmi], [ipmi_ctx_create], + [IPMI_LIBS="$IPMI_LIBS -lfreeipmi"], [have_freeipmi=no]) +fi + +AM_CONDITIONAL([ENABLE_IPMI], [test "x${have_freeipmi}" = "xyes" \ + -a "x${have_terminal}" = "xyes"]) + +AC_SUBST(IPMI_CFLAGS) +AC_SUBST(IPMI_LIBS) + # # libwebp # @@ -1489,6 +1525,8 @@ AC_CONFIG_FILES([Makefile src/protocols/rdp/tests/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile + src/protocols/ipmi/Makefile + src/protocols/ipmi/tests/Makefile src/protocols/vnc/Makefile]) AC_OUTPUT @@ -1500,6 +1538,7 @@ 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_IPMI], [build_ipmi=yes], [build_ipmi=no]) AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) # @@ -1549,6 +1588,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION libssl .............. ${have_ssl} libswscale .......... ${have_libswscale} libtelnet ........... ${have_libtelnet} + freeipmi ............ ${have_freeipmi} libVNCServer ........ ${have_libvncserver} libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} @@ -1562,6 +1602,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION RDP ........... ${build_rdp} SSH ........... ${build_ssh} Telnet ........ ${build_telnet} + IPMI .......... ${build_ipmi} VNC ........... ${build_vnc} Services / tools: diff --git a/src/protocols/ipmi/Makefile.am b/src/protocols/ipmi/Makefile.am new file mode 100644 index 000000000..9992c0cf2 --- /dev/null +++ b/src/protocols/ipmi/Makefile.am @@ -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. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = . tests + +lib_LTLIBRARIES = libguac-client-ipmi.la + +libguac_client_ipmi_la_SOURCES = \ + argv.c \ + chassis.c \ + client.c \ + clipboard.c \ + control.c \ + input.c \ + ipmi.c \ + menu.c \ + pipe.c \ + settings.c \ + user.c + +noinst_HEADERS = \ + argv.h \ + chassis.h \ + client.h \ + clipboard.h \ + control.h \ + input.h \ + ipmi.h \ + menu.h \ + pipe.h \ + settings.h \ + user.h + +libguac_client_ipmi_la_CFLAGS = \ + -Werror -Wall -Iinclude \ + @LIBGUAC_INCLUDE@ \ + @IPMI_CFLAGS@ \ + @TERMINAL_INCLUDE@ + +libguac_client_ipmi_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ \ + @TERMINAL_LTLIB@ \ + @IPMI_LIBS@ + +libguac_client_ipmi_la_LDFLAGS = \ + -version-info 0:0:0 \ + @PTHREAD_LIBS@ diff --git a/src/protocols/ipmi/argv.c b/src/protocols/ipmi/argv.c new file mode 100644 index 000000000..9ec7fcb7a --- /dev/null +++ b/src/protocols/ipmi/argv.c @@ -0,0 +1,96 @@ +/* + * 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 "argv.h" +#include "ipmi.h" +#include "terminal/terminal.h" + +#include +#include +#include + +#include +#include +#include + +int guac_ipmi_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data) { + + guac_client* client = user->client; + guac_ipmi_client* ipmi_client = (guac_ipmi_client*) client->data; + guac_terminal* terminal = ipmi_client->term; + + /* Skip if terminal not yet ready */ + if (terminal == NULL) + return 0; + + /* Update color scheme */ + if (strcmp(name, GUAC_IPMI_ARGV_COLOR_SCHEME) == 0) + guac_terminal_apply_color_scheme(terminal, value); + + /* Update font name */ + else if (strcmp(name, GUAC_IPMI_ARGV_FONT_NAME) == 0) + guac_terminal_apply_font(terminal, value, -1, 0); + + /* Update only if font size is sane */ + else if (strcmp(name, GUAC_IPMI_ARGV_FONT_SIZE) == 0) { + int size = atoi(value); + if (size > 0) + guac_terminal_apply_font(terminal, NULL, size, + ipmi_client->settings->resolution); + } + + return 0; + +} + +void* guac_ipmi_send_current_argv(guac_user* user, void* data) { + + /* Defer to the batch handler, using the user's socket to send the data */ + guac_ipmi_send_current_argv_batch(user->client, user->socket); + + return NULL; + +} + +void guac_ipmi_send_current_argv_batch( + guac_client* client, guac_socket* socket) { + + guac_ipmi_client* ipmi_client = (guac_ipmi_client*) client->data; + guac_terminal* terminal = ipmi_client->term; + + /* Send current color scheme */ + guac_client_stream_argv(client, socket, "text/plain", + GUAC_IPMI_ARGV_COLOR_SCHEME, + guac_terminal_get_color_scheme(terminal)); + + /* Send current font name */ + guac_client_stream_argv(client, socket, "text/plain", + GUAC_IPMI_ARGV_FONT_NAME, + guac_terminal_get_font_name(terminal)); + + /* Send current font size */ + char font_size[64]; + snprintf(font_size, sizeof(font_size), "%i", + guac_terminal_get_font_size(terminal)); + guac_client_stream_argv(client, socket, "text/plain", + GUAC_IPMI_ARGV_FONT_SIZE, font_size); + +} diff --git a/src/protocols/ipmi/argv.h b/src/protocols/ipmi/argv.h new file mode 100644 index 000000000..fef15cf06 --- /dev/null +++ b/src/protocols/ipmi/argv.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 GUAC_IPMI_ARGV_H +#define GUAC_IPMI_ARGV_H + +#include +#include + +/** + * The name of the parameter that specifies/updates the color scheme used by + * the terminal emulator. + */ +#define GUAC_IPMI_ARGV_COLOR_SCHEME "color-scheme" + +/** + * The name of the parameter that specifies/updates the name of the font used + * by the terminal emulator. + */ +#define GUAC_IPMI_ARGV_FONT_NAME "font-name" + +/** + * The name of the parameter that specifies/updates the font size used by the + * terminal emulator. + */ +#define GUAC_IPMI_ARGV_FONT_SIZE "font-size" + +/** + * Handles a received argument value from a Guacamole "argv" instruction, + * updating the given connection parameter. + */ +guac_argv_callback guac_ipmi_argv_callback; + +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the given user. Note that the user + * receiving these values will not necessarily be able to set new values + * themselves if their connection is read-only. This function can be used as + * the callback for guac_client_foreach_user() and guac_client_for_owner() + * + * @param user + * The user that should receive the values of all non-sensitive parameters + * which may be set while the connection is running. + * + * @param data + * The guac_ipmi_client instance associated with the current connection. + * + * @return + * Always NULL. + */ +void* guac_ipmi_send_current_argv(guac_user* user, void* data); + +/** + * Sends the current values of all non-sensitive parameters which may be set + * while the connection is running to the users associated with the provided + * socket. Note that the users receiving these values will not necessarily be + * able to set new values themselves if their connection is read-only. + * + * @param client + * The client associated with the users that should receive the values of + * all non-sensitive parameters which may be set while the connection is + * running. + * + * @param socket + * The socket to send the arguments to the batch of users along. + * + * @return + * Always NULL. + */ +void guac_ipmi_send_current_argv_batch( + guac_client* client, guac_socket* socket); + +#endif diff --git a/src/protocols/ipmi/chassis.c b/src/protocols/ipmi/chassis.c new file mode 100644 index 000000000..b1744c78b --- /dev/null +++ b/src/protocols/ipmi/chassis.c @@ -0,0 +1,497 @@ +/* + * 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 "chassis.h" +#include "ipmi.h" +#include "settings.h" + +#include + +#include + +#include +#include +#include + +/** + * The IPMI session timeout, in milliseconds, used for the short-lived + * out-of-band sessions opened to issue chassis commands. Chassis commands are + * simple request/response operations, so a bounded timeout (rather than the + * library default) is used to limit how long a chassis command can stall when + * the BMC is slow or unreachable. This matters because chassis commands + * triggered from the in-terminal control menu run synchronously within the + * user input handler. + */ +#define GUAC_IPMI_CHASSIS_SESSION_TIMEOUT 8000 + +/** + * The IPMI packet retransmission timeout, in milliseconds, used for chassis + * command sessions. + */ +#define GUAC_IPMI_CHASSIS_RETRANSMISSION_TIMEOUT 1000 + +/** + * Maps a guac_ipmi_privilege_level to the corresponding libfreeipmi + * IPMI_PRIVILEGE_LEVEL_* constant. + * + * @param privilege_level + * The privilege level to map. + * + * @return + * The equivalent IPMI_PRIVILEGE_LEVEL_* constant. + */ +static uint8_t guac_ipmi_map_privilege_level( + guac_ipmi_privilege_level privilege_level) { + + switch (privilege_level) { + case GUAC_IPMI_PRIVILEGE_USER: + return IPMI_PRIVILEGE_LEVEL_USER; + case GUAC_IPMI_PRIVILEGE_OPERATOR: + return IPMI_PRIVILEGE_LEVEL_OPERATOR; + case GUAC_IPMI_PRIVILEGE_ADMIN: + default: + return IPMI_PRIVILEGE_LEVEL_ADMIN; + } + +} + +/** + * Maps a guac_ipmi_power_action to the corresponding libfreeipmi + * IPMI_CHASSIS_CONTROL_* constant. + * + * @param action + * The power action to map. Must not be GUAC_IPMI_POWER_NONE. + * + * @return + * The equivalent IPMI_CHASSIS_CONTROL_* constant. + */ +static uint8_t guac_ipmi_map_power_action(guac_ipmi_power_action action) { + + switch (action) { + case GUAC_IPMI_POWER_ON: + return IPMI_CHASSIS_CONTROL_POWER_UP; + case GUAC_IPMI_POWER_OFF: + return IPMI_CHASSIS_CONTROL_POWER_DOWN; + case GUAC_IPMI_POWER_CYCLE: + return IPMI_CHASSIS_CONTROL_POWER_CYCLE; + case GUAC_IPMI_POWER_RESET: + return IPMI_CHASSIS_CONTROL_HARD_RESET; + case GUAC_IPMI_POWER_SOFT_SHUTDOWN: + return IPMI_CHASSIS_CONTROL_INITIATE_SOFT_SHUTDOWN; + case GUAC_IPMI_POWER_DIAGNOSTIC_INTERRUPT: + return IPMI_CHASSIS_CONTROL_PULSE_DIAGNOSTIC_INTERRUPT; + default: + return IPMI_CHASSIS_CONTROL_POWER_DOWN; + } + +} + +/** + * Maps a guac_ipmi_boot_device to the corresponding libfreeipmi + * IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_FORCE_* constant. + * + * @param device + * The boot device to map. Must not be GUAC_IPMI_BOOT_NONE. + * + * @return + * The equivalent boot device constant. + */ +static uint8_t guac_ipmi_map_boot_device(guac_ipmi_boot_device device) { + + switch (device) { + case GUAC_IPMI_BOOT_PXE: + return IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_FORCE_PXE; + case GUAC_IPMI_BOOT_DISK: + return IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_FORCE_HARD_DRIVE; + case GUAC_IPMI_BOOT_CDROM: + return IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_FORCE_CD_DVD; + case GUAC_IPMI_BOOT_BIOS: + return IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_FORCE_BIOS_SETUP; + default: + return IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_DEVICE_NO_OVERRIDE; + } + +} + +/** + * Opens a short-lived IPMI 2.0 out-of-band session against the BMC described + * by the given client's settings. The returned context must be closed with + * ipmi_ctx_close() and freed with ipmi_ctx_destroy() when no longer needed. + * + * @param client + * The guac_client whose settings describe the BMC to connect to. + * + * @return + * A connected ipmi_ctx_t on success, or NULL on failure (with an error + * logged on behalf of the client). + */ +static ipmi_ctx_t guac_ipmi_chassis_connect(guac_client* client) { + + guac_ipmi_client* ipmi_client = (guac_ipmi_client*) client->data; + guac_ipmi_settings* settings = ipmi_client->settings; + + ipmi_ctx_t ctx = ipmi_ctx_create(); + if (ctx == NULL) { + guac_client_log(client, GUAC_LOG_ERROR, + "Unable to allocate IPMI context for chassis command."); + return NULL; + } + + /* The BMC key (K_g) is optional; its length is stored separately as it may + * legitimately contain null bytes. */ + const unsigned char* k_g = (const unsigned char*) settings->k_g; + unsigned int k_g_len = settings->k_g_length; + + /* Open an IPMI 2.0 (RMCP+) out-of-band session. Session and retransmission + * timeouts of 0 request the library defaults. */ + if (ipmi_ctx_open_outofband_2_0(ctx, + settings->hostname, + settings->username, + settings->password, + k_g, k_g_len, + guac_ipmi_map_privilege_level(settings->privilege_level), + (uint8_t) settings->cipher_suite, + GUAC_IPMI_CHASSIS_SESSION_TIMEOUT, + GUAC_IPMI_CHASSIS_RETRANSMISSION_TIMEOUT, + settings->chassis_workaround_flags, + 0 /* flags */) < 0) { + guac_client_log(client, GUAC_LOG_ERROR, + "Unable to open IPMI session for chassis command: %s", + ipmi_ctx_errormsg(ctx)); + ipmi_ctx_destroy(ctx); + return NULL; + } + + return ctx; + +} + +/** + * Closes and destroys the given IPMI context. + * + * @param ctx + * The context to close and destroy. + */ +static void guac_ipmi_chassis_disconnect(ipmi_ctx_t ctx) { + ipmi_ctx_close(ctx); + ipmi_ctx_destroy(ctx); +} + +int guac_ipmi_chassis_power(guac_client* client, + guac_ipmi_power_action action) { + + /* A request for no action always succeeds */ + if (action == GUAC_IPMI_POWER_NONE) + return 0; + + ipmi_ctx_t ctx = guac_ipmi_chassis_connect(client); + if (ctx == NULL) + return 1; + + int result = 1; + fiid_obj_t obj_cmd_rs = fiid_obj_create(tmpl_cmd_chassis_control_rs); + if (obj_cmd_rs != NULL) { + + if (ipmi_cmd_chassis_control(ctx, + guac_ipmi_map_power_action(action), obj_cmd_rs) < 0) + guac_client_log(client, GUAC_LOG_ERROR, + "Chassis control command failed: %s", + ipmi_ctx_errormsg(ctx)); + else + result = 0; + + fiid_obj_destroy(obj_cmd_rs); + } + + guac_ipmi_chassis_disconnect(ctx); + return result; + +} + +int guac_ipmi_chassis_set_boot_device(guac_client* client, + guac_ipmi_boot_device device, bool persistent) { + + /* A request for no override always succeeds */ + if (device == GUAC_IPMI_BOOT_NONE) + return 0; + + ipmi_ctx_t ctx = guac_ipmi_chassis_connect(client); + if (ctx == NULL) + return 1; + + int result = 1; + fiid_obj_t obj_cmd_rs = + fiid_obj_create(tmpl_cmd_set_system_boot_options_rs); + if (obj_cmd_rs != NULL) { + + /* Issue a boot flags override. All boot-flag fields other than the + * boot device, persistence, and "valid" markers are left at their + * default/disabled (0) values. */ + if (ipmi_cmd_set_system_boot_options_boot_flags(ctx, + IPMI_SYSTEM_BOOT_OPTIONS_PARAMETER_VALID_UNLOCKED, + IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_BOOT_TYPE_PC_COMPATIBLE, + persistent + ? IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_VALID_PERSISTENTLY + : IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_VALID_FOR_NEXT_BOOT, + IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_VALID, + 0 /* lock out reset button */, + 0 /* screen blank */, + guac_ipmi_map_boot_device(device), + 0 /* lock keyboard */, + 0 /* CMOS clear */, + IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_CONSOLE_REDIRECTION_DEFAULT, + 0 /* lock out sleep button */, + 0 /* user password bypass */, + 0 /* force progress event traps */, + IPMI_SYSTEM_BOOT_OPTION_BOOT_FLAG_FIRMWARE_BIOS_VERBOSITY_DEFAULT, + 0 /* lock out via power button */, + 0 /* BIOS mux control override (recommended setting) */, + 0 /* BIOS shared mode override (recommended setting) */, + 0 /* device instance selector */, + obj_cmd_rs) < 0) + guac_client_log(client, GUAC_LOG_ERROR, + "Set system boot options command failed: %s", + ipmi_ctx_errormsg(ctx)); + else + result = 0; + + fiid_obj_destroy(obj_cmd_rs); + } + + guac_ipmi_chassis_disconnect(ctx); + return result; + +} + +int guac_ipmi_chassis_status(guac_client* client, char* buffer, int size, + guac_ipmi_power_state* power) { + + if (power != NULL) + *power = GUAC_IPMI_POWER_STATE_UNKNOWN; + + ipmi_ctx_t ctx = guac_ipmi_chassis_connect(client); + if (ctx == NULL) + return 1; + + int result = 1; + fiid_obj_t obj_cmd_rs = fiid_obj_create(tmpl_cmd_get_chassis_status_rs); + if (obj_cmd_rs != NULL) { + + if (ipmi_cmd_get_chassis_status(ctx, obj_cmd_rs) < 0) + guac_client_log(client, GUAC_LOG_ERROR, + "Get chassis status command failed: %s", + ipmi_ctx_errormsg(ctx)); + + else { + + uint64_t power_is_on = 0; + uint64_t power_fault = 0; + uint64_t power_overload = 0; + + /* The power state field is mandatory; treat its absence as a + * failure rather than silently reporting a fabricated "OFF". The + * fault/overload fields are informational and tolerated if + * unreadable. */ + if (fiid_obj_get(obj_cmd_rs, "current_power_state.power_is_on", + &power_is_on) < 0) { + guac_client_log(client, GUAC_LOG_ERROR, + "Chassis status response missing power state."); + } + else { + + fiid_obj_get(obj_cmd_rs, "current_power_state.power_fault", + &power_fault); + fiid_obj_get(obj_cmd_rs, "current_power_state.power_overload", + &power_overload); + + snprintf(buffer, size, "Power: %s%s%s", + power_is_on ? "ON" : "OFF", + power_fault ? " (fault)" : "", + power_overload ? " (overload)" : ""); + + if (power != NULL) + *power = power_is_on + ? GUAC_IPMI_POWER_STATE_ON + : GUAC_IPMI_POWER_STATE_OFF; + + result = 0; + } + } + + fiid_obj_destroy(obj_cmd_rs); + } + + guac_ipmi_chassis_disconnect(ctx); + return result; + +} + +int guac_ipmi_chassis_identify(guac_client* client, int interval) { + + ipmi_ctx_t ctx = guac_ipmi_chassis_connect(client); + if (ctx == NULL) + return 1; + + int result = 1; + fiid_obj_t obj_cmd_rs = fiid_obj_create(tmpl_cmd_chassis_identify_rs); + if (obj_cmd_rs != NULL) { + + uint8_t interval_value = (uint8_t) interval; + uint8_t force_value = IPMI_CHASSIS_FORCE_IDENTIFY_OFF; + + if (ipmi_cmd_chassis_identify(ctx, &interval_value, + &force_value, obj_cmd_rs) < 0) + guac_client_log(client, GUAC_LOG_ERROR, + "Chassis identify command failed: %s", + ipmi_ctx_errormsg(ctx)); + else + result = 0; + + fiid_obj_destroy(obj_cmd_rs); + } + + guac_ipmi_chassis_disconnect(ctx); + return result; + +} + +/** + * The maximum number of System Event Log entries retained by the viewer (the + * most recent entries). + */ +#define GUAC_IPMI_SEL_MAX_ENTRIES 20 + +/** + * The maximum length, in bytes, of a single formatted SEL entry line. + */ +#define GUAC_IPMI_SEL_LINE_LENGTH 160 + +/** + * State accumulated while parsing the System Event Log. Formatted entries are + * stored in a ring buffer such that, after parsing, the buffer holds the most + * recent GUAC_IPMI_SEL_MAX_ENTRIES entries. + */ +typedef struct guac_ipmi_sel_state { + + /** + * Ring buffer of formatted SEL entry lines. + */ + char lines[GUAC_IPMI_SEL_MAX_ENTRIES][GUAC_IPMI_SEL_LINE_LENGTH]; + + /** + * The total number of SEL records parsed so far. + */ + int total; + +} guac_ipmi_sel_state; + +/** + * libfreeipmi SEL parse callback. Formats the current SEL record into the ring + * buffer maintained by the given guac_ipmi_sel_state. + * + * @param sel_ctx + * The SEL parse context, positioned at the record to format. + * + * @param data + * A pointer to the guac_ipmi_sel_state accumulating formatted entries. + * + * @return + * Zero to continue parsing. + */ +static int guac_ipmi_sel_callback(ipmi_sel_ctx_t sel_ctx, void* data) { + + guac_ipmi_sel_state* state = (guac_ipmi_sel_state*) data; + char* slot = state->lines[state->total % GUAC_IPMI_SEL_MAX_ENTRIES]; + + /* Format "