GUACAMOLE-2300: Add IPMI Serial-over-LAN protocol support - #692
GUACAMOLE-2300: Add IPMI Serial-over-LAN protocol support#692ciroiriarte wants to merge 16 commits into
Conversation
…management Implement a native `ipmi` protocol module (src/protocols/ipmi/) providing IPMI 2.0 Serial-over-LAN console access via FreeIPMI libipmiconsole, modeled on the terminal-based telnet module (guac_terminal for rendering, scrollback, input; recording and typescript support). Add standard chassis power management via libfreeipmi: power on/off/cycle/ reset/soft-shutdown/diagnostic-interrupt, next-boot device override, chassis identify LED, and power status. These are exposed as pre-connect parameters (power-on-connect, boot-device) applied after the SOL session attaches so the user can watch the system boot, and on demand via an in-terminal control menu (Ctrl+]) with y/N confirmation for destructive actions. Wire the module into the autotools build behind --with-ipmi (ENABLE_IPMI), detecting libipmiconsole and libfreeipmi, and add it to the configure summary. Companion web-UI work is tracked in ciroiriarte/guacamole-client#1. Implements #1.
Move @IPMI_LIBS@ (-lipmiconsole -lfreeipmi) from _LDFLAGS to _LIBADD so the libraries follow the object files on the link line. With the GNU linker's --as-needed behavior (default on openSUSE, and modern Debian/Ubuntu/Fedora), libraries listed in LDFLAGS precede the objects that reference them and are dropped, leaving them out of DT_NEEDED. The .so still builds, but guacd's dlopen() then fails at runtime with 'undefined symbol: tmpl_cmd_set_system_boot_options_rs' and reports the protocol as not installed.
…ine refcount, hex K_g M1 hardening for real-world multivendor BMC support: - workaround-flags parameter: comma-separated FreeIPMI workaround tokens and vendor presets (supermicro/intel/sun/dell/hpe/lenovo), resolved into separate libipmiconsole (SOL) and libfreeipmi (chassis) masks and applied to both sessions, replacing the hardcoded 0. - encryption-policy parameter (required default / preferred / none): validates that the configured cipher suite provides confidentiality before connecting; aborts under 'required' for non-encrypting suites (0,1,2,6,7,8,11,15). - Reference-counted, mutex-guarded libipmiconsole engine init/teardown so concurrent IPMI connections no longer tear down the shared engine out from under each other. - K_g now accepts a 0x-prefixed hex value decoded to a raw byte buffer with an explicit length, so binary BMC keys (which may contain NUL bytes) are no longer truncated by strlen().
…al parameters - boot-device-persistent: applies a boot device override persistently across all subsequent boots rather than only the next boot (the underlying chassis call already supported persistence; it was previously hardcoded to one-time). - keepalive-interval: sets the SOL keepalive interval (seconds), mapped to libipmiconsole's keepalive_timeout_len. Prevents BMCs that silently drop idle sessions (e.g. Supermicro after ~60s) from tearing down the console.
…n control menu - SEL viewer: guac_ipmi_chassis_sel() reads the most recent System Event Log entries via the libfreeipmi SEL parse API and formats them as text; exposed in the Ctrl+] control menu as [e] View Event Log. Invaluable for diagnosing crash/no-boot conditions while on the serial console. - Alternate screen: the control menu now renders on the terminal's alternate screen buffer (CSI ?1049h/l), so it no longer pollutes the serial console's scrollback and session recording, nor corrupts a full-screen application drawn on the primary screen. Command output is held on the alternate screen until dismissed with a keypress, then the console is restored intact.
Chassis operations (power, status, identify, SEL) opened a fresh IPMI session synchronously from the user input thread, freezing that user's input for the 3-5s RMCP+ handshake. They now run on a background worker thread: the menu prints a 'working...' message immediately and renders the result when the worker completes, keeping the console responsive. Only one operation runs at a time (guarded by chassis_busy under menu_lock); control-menu keystrokes are ignored while an operation is in flight. The worker thread is joined in the free handler before the terminal is destroyed, so an abrupt disconnect mid-operation cannot cause a use-after-free (verified by a disconnect-during-op stress test). Serial break remains synchronous as it is fast and operates on the existing SOL context.
…ic control/status Implements the out-of-band control/status channel (#28) that keeps chassis control and telemetry off the SOL console stream, so any client (browser panel, native TUI) can render it natively. The in-terminal Ctrl+] menu remains a portable fallback. A client opens an inbound pipe named 'ipmi-control' and sends newline-delimited JSON commands; the server pushes JSON state/result/SEL messages back on outbound 'ipmi-control' pipes: {"id":"..","type":"command","command":"power-cycle"} {"type":"state","power":"on","health":"sol-connected"} {"id":"..","type":"result","ok":true,"message":".."} {"type":"sel","text":".."} Commands: power on/off/cycle/hard-reset/soft-shutdown/diagnostic-interrupt, identify, send-break, refresh-status, read-sel. Chassis operations serialize with the Ctrl+] menu via the shared chassis lock, and the channel is only available to non-read-only users (the pipe handler is not registered for read-only). Validated end-to-end against a Supermicro BMC (state query, identify, SEL read, error handling). Follow-ups: structured per-entry SEL, async command dispatch, periodic state polling / push-on-change, passive state broadcast to read-only viewers.
The out-of-band control channel reported SOL health as a one-shot snapshot taken when the panel first opened the ipmi-control pipe. Since the browser connects and opens that pipe while the SOL session is still being established (the blocking ipmiconsole_engine_submit_block on the client thread), the snapshot captured console_ctx == NULL and the panel showed "SOL DISCONNECTED" even while the console streamed live output — correcting only after a manual Refresh. Track SOL liveness in a dedicated, mutex-guarded sol_connected flag (separate from console_ctx, which stays non-NULL until teardown) and broadcast the state to all connected users via guac_client_foreach_user() immediately after the session establishes and again when the read loop exits. The push is health-only (omits power) so it refreshes the connection badge without regressing the last-known power state to "unknown"; an actively-queried power value is still sent for refresh-status. Server-only change; the existing client handler already ignores an absent power field. Verified against a Supermicro BMC: the badge reaches SOL CONNECTED on its own with power intact, no manual refresh.
…nd EAGAIN __guac_ipmi_write_all() treated any write() result <= 0 as fatal and broke the input-forwarding loop, which would silently stop forwarding terminal keystrokes to the SOL session on a transient condition. Handle partial writes by continuing with the remainder, and, should the libipmiconsole descriptor ever be non-blocking, wait for POLLOUT and retry on EAGAIN/EWOULDBLOCK rather than dropping input. Any real error or EOF remains fatal. No behavioural change for the current blocking descriptor.
Add CUnit tests (wired into 'make check' like the Kubernetes/RDP module tests) for the two pure helpers that parse untrusted, browser-supplied control-channel messages: guac_ipmi_control_json_get() and guac_ipmi_control_json_escape(), which are now exposed via control.h for testing. Coverage includes key-vs-value matching (a token appearing only as a value must not be read as a key), whitespace tolerance, escape handling/unescaping, dropping of non-printable control characters, and output-buffer bounds. 12 cases, all passing.
…terminal guac_ipmi_client_free_handler() unconditionally called guac_terminal_free() on ipmi_client->term, but the terminal is created by the client thread; if the connection is torn down before that thread reaches terminal creation, term is still NULL and guac_terminal_free() -> guac_terminal_stop() dereferences it, crashing the connection process. Guard the free with a NULL check. Found via an AddressSanitizer run over a rapid connect/teardown of the ipmi-control channel; fix confirmed clean under ASAN.
…fore freeing the terminal The connection free handler freed the terminal before joining the SOL worker thread, and the join was gated on console_ctx being non-NULL. The worker's read loop writes SOL output to the terminal (holding term->lock), so freeing the terminal first could destroy the lock/buffers while the worker was mid-write -> use-after-free. The conditional join also leaked the joinable worker thread on every session-setup failure path (terminal init, encryption-required abort, engine-ref failure, SOL failure), which all leave console_ctx NULL. Mirror the SSH module's ordering: close the SOL fd and guac_terminal_stop() to unblock both threads without freeing the terminal, join the worker unconditionally (tracked by a new client_thread_valid flag, like chassis_thread_valid), then destroy the SOL context and finally free the terminal. Found by independent code + security review; verified clean under AddressSanitizer.
…ak, bounded printf Address remaining code/security review findings: - guac_ipmi_settings_free() now explicit_bzero()s the password and the decoded K_g BMC key before freeing, so these secrets do not linger in freed heap. - send-break (control channel and Ctrl+] menu) now tests sol_connected under state_lock before dereferencing console_ctx, closing a narrow race with SOL teardown. - argv.c uses snprintf() instead of sprintf() into the fixed font-size buffer. Menu-state cross-user locking, alt-screen-vs-live-SOL suppression, and the dead identify 'force' parameter are noted as follow-ups.
Thread-safety and cleanup items surfaced by the code/security review: - Serialize the shared control-menu state (menu_open, menu_pending_action, menu_awaiting_dismiss) under menu_lock: the key handler now snapshots it in one short critical section and the menu open/close/confirm paths mutate it under the lock, eliminating the data race across concurrent user key threads and the chassis worker. Critical sections are kept short (no lock held across thread creation or terminal writes) to avoid deadlock. - Suppress SOL console output while the control menu owns the alternate screen, so live serial data no longer scrambles the rendered menu. - Serialize status/SEL reads with the other chassis operations (they now take the chassis lock too) so a single connection cannot open many concurrent RMCP+ sessions and exhaust the BMC's session slots. - guac_ipmi_chassis_status() now outputs a decoded guac_ipmi_power_state enum; the control channel uses it instead of substring-matching the human-readable status string. - Drop the always-false 'force' parameter from guac_ipmi_chassis_identify(). Built, unit-tested (12/12), functionally verified (status/identify/SEL), and re-checked clean under AddressSanitizer (no UAF/leak/deadlock).
necouchman
left a comment
There was a problem hiding this comment.
@ciroiriarte This looks really great, I'm excited to have this in the Guacamole code! I do have some initial comments - don't despair, most of these are style and documentation clean-up, although some deal with implementation details. Generally:
- In several places you've defined Guacamole-specific data structures or enums that mirror ones provided by the IPMI libraries. Is there any reason to do this? It seems like it would simplify things and cut down the amount of code to just reuse what is already there, unless there's a technical or implementation-specific reason why not?
- Make sure you've fully-documented all of the functions and/or function prototypes. There are several places where thinks like
@paramand@returntags are missing, and a handful where there was no documentation. - There are a few places where I think some more verbose comments within the code would help people more easily understand what's being done and why.
- I see what you're doing with the IPMI menu, but I wonder if there's a way to use Guacamole's built-in menu to handle these functions rather than implementing the text-based menu you have and then having to implement the JSON-related code around it? I haven't looked at this in any detail, it just seems to me that that's what our existing hidden Guacamole menu is meant for, and there ought to be a way to handle things like chassis commands and the like similar to how we handle file transfers, terminal parameters, etc.
| /** | ||
| * Verifies that a non-string value (e.g. a number) is not matched, as only | ||
| * quoted string values are extracted. | ||
| */ | ||
| void test_json_get__non_string_value(void) { | ||
| char out[64]; | ||
| CU_ASSERT_FALSE(guac_ipmi_control_json_get( | ||
| "{\"n\":123}", "n", out, sizeof(out))); | ||
| } |
There was a problem hiding this comment.
Why are only string values allowed? My experience in general with JSON is that string values are expected for strings, but other values - boolean, numeric, etc. - are perfectly valid JSON?
There was a problem hiding this comment.
guac_ipmi_control_json_get() bails if the value isn't quoted, so {"id":123} from a third-party client parses as absent. Given your point about independent client implementations, I'll extend it to accept numeric and boolean scalars.
| * under the License. | ||
| */ | ||
|
|
||
| #include "config.h" |
There was a problem hiding this comment.
Mike recently submitted a PR that makes including config.h unnecessary - see #691.
There was a problem hiding this comment.
Removed in 86f6677 — config.h is auto-included in every .c file since GUACAMOLE-2182, so the explicit include is redundant.
| /** | ||
| * 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; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Is there a particular reason we need to define new values for these privilege levels as GUAC_ constants and shouldn't just re-use the IPMI_PRIVILEGE_LEVEL_ values throughout the code?
There was a problem hiding this comment.
Same privilege question — answered in full under the enum definition in settings.h (#692 (comment)). Short version: libipmiconsole and libfreeipmi disagree on the privilege values (0/1/2 vs 0x02/0x03/0x04), so reusing either is silently wrong on the other path. I'll keep an internal enum but give it arbitrary values and map explicitly on both paths.
| /** | ||
| * 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; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Similar to the privilege levels - is there a particular reason that we need to create and map them rather than just using the ones provided by FreeIPMI?
There was a problem hiding this comment.
Different rationale from the privilege levels here, and a weaker one — I'd rather be upfront about that than stretch the same argument to cover it. There's no conflicting-constants problem for power actions or boot devices; these enums are less a wrapper around the FreeIPMI constants than internal command state. Each carries a NONE member that has no IPMI equivalent (IPMI_CHASSIS_CONTROL_* can't express "no action requested"), and they're used well away from this mapping — parsing the client's command strings, the menu's display labels, and the pending-confirmation state in guac_ipmi_client. The translation to the FreeIPMI constant happens only here, at the boundary.
NONE could be modelled as optional/nullable state instead, which would let these collapse into the raw constants. I lean toward keeping them as internal state enums for the parsing and menu use, but if you'd prefer the nullable approach I'll switch.
| /** | ||
| * 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; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
And, similar to above two - why do we need to redefine these?
There was a problem hiding this comment.
Same rationale as the power-action enum just above (#692 (comment)): these are internal command state with a NONE sentinel that has no IPMI_CHASSIS_CONTROL_* / boot-device equivalent, mapped to the FreeIPMI constant only here at the boundary.
| /** | ||
| * Parses the raw K_g argument into a newly-allocated byte buffer, returning | ||
| * the buffer and storing its length in *length. If the value begins with "0x", | ||
| * it is decoded as a hexadecimal byte string (allowing binary keys that may | ||
| * contain NUL bytes); otherwise it is treated as a raw passphrase. Returns NULL | ||
| * (with *length set to 0) if the value is NULL or invalid. | ||
| */ | ||
| static char* guac_ipmi_parse_k_g(guac_user* user, const char* raw, | ||
| int* length) { |
There was a problem hiding this comment.
Please document parameters and return value.
| if (raw == NULL || *raw == '\0') | ||
| return NULL; |
There was a problem hiding this comment.
Might be good to log this at WARN or DEBUG level?
| typedef enum guac_ipmi_privilege_level { | ||
| GUAC_IPMI_PRIVILEGE_USER = 0, | ||
| GUAC_IPMI_PRIVILEGE_OPERATOR = 1, | ||
| GUAC_IPMI_PRIVILEGE_ADMIN = 2 | ||
| } guac_ipmi_privilege_level; |
There was a problem hiding this comment.
Please document each of the values. Also, as mentioned in one of the other files, is there any reason not to just use the IPMICONSOLE_PRIVILEGE_* values?? Why redefine them?
There was a problem hiding this comment.
Two things here — the "why redefine" question and the request to document the values.
On reuse: this is the one enum I'd argue for keeping, because the module talks to two libraries whose privilege constants don't agree. SOL goes through libipmiconsole (IPMICONSOLE_PRIVILEGE_USER/OPERATOR/ADMIN = 0/1/2); chassis goes through libfreeipmi (IPMI_PRIVILEGE_LEVEL_USER/OPERATOR/ADMIN = 0x02/0x03/0x04). So "just reuse the library's values" is ambiguous, and a value picked for one library is silently wrong on the other path — an IPMICONSOLE_PRIVILEGE_ADMIN (2) reaching the chassis code reads as IPMI_PRIVILEGE_LEVEL_USER (0x02), a privilege downgrade with nothing to flag it.
The current code does invite the question fairly, though: the enum is defined as USER=0, OPERATOR=1, ADMIN=2 — hard-coded to the libipmiconsole numbering — and ipmi.c assigns it straight into ipmiconsole_ipmi_config.privilege_level with no mapping, while only the chassis side maps (via guac_ipmi_map_privilege_level()). So on the SOL path the enum really is just a copy of libipmiconsole's constants, which is why it reads as redundant. I'll give the values arbitrary numbering (not 0/1/2) and add a companion guac_ipmi_map_privilege_level_ipmiconsole() mirroring the chassis switch, so neither back-end ever receives the raw enum value.
To be straight about it: two conflicting constant sets don't prove a third enum is the only option — parsing directly to each library's constant at its boundary would work too. The internal enum just keeps the parsed setting semantic between the settings layer and the two back-ends instead of committing to one library's numbering at parse time. If you'd rather drop it and map at the boundary, I'll do that instead.
On the documentation: I'll add a doc comment to each value as part of that change.
There was a problem hiding this comment.
Follow-up from above: this is now done in c29f725. The enum values are library-independent (INVALID = 0, then USER/OPERATOR/ADMIN), and the SOL path maps through a new guac_ipmi_map_privilege_level_ipmiconsole() mirroring the chassis-side guac_ipmi_map_privilege_level() — so neither back-end receives the raw enum value anymore.
| /** | ||
| * Lock guarding the process-global libipmiconsole engine reference count. The | ||
| * libipmiconsole engine is a single process-wide resource; initializing or | ||
| * tearing it down per connection would race with, and disrupt, any other IPMI | ||
| * connections active within the same guacd process. | ||
| */ | ||
| static pthread_mutex_t guac_ipmi_engine_lock = PTHREAD_MUTEX_INITIALIZER; |
There was a problem hiding this comment.
When you say "any other IPMI connections active within the same guacd process" - does this mean:
- Within the same guacd parent process?
- Within the same guacd child/connection process?
guacd forks a new process for each connection, and, since this particular lock would only be initialized in the context of a single connection, and that would be after the child connection has been forked by guacd, there actually should not be any issue with initializing it or tearing it down...
...UNLESS, you also mean that the IPMI chassis connections would interfere with the SOL connection, in which case, this would still make sense.
There was a problem hiding this comment.
The comment is misleading, not just unclear. This lock has nothing to do with chassis-vs-SOL interference; that's handled separately by menu_lock/chassis_busy in control.c. All this guards is the refcount around ipmiconsole_engine_init()/ipmiconsole_engine_teardown(), and your reading of the fork model is the right one: guacd forks per connection, the refcount lives in that child, so it can only ever go 0→1 and the lock never contends. As written it's scaffolding for a sharing scenario that can't occur.
Let me simplify it rather than justify it. If it's worth keeping any of it, the argument would be that libipmiconsole's engine is process-global and the refcount is cheap insurance if guacd ever ran connections in threads rather than forks — but that's speculative, and I'd rather not carry speculative concurrency machinery. I'll cut it back to a plain init/teardown and fix the comment.
| /** | ||
| * Displays a "press any key to return" prompt and marks the current control | ||
| * menu operation complete. Forward-declared for use by the chassis worker. | ||
| * | ||
| * @param client | ||
| * The guac_client associated with the IPMI connection. | ||
| */ | ||
| static void guac_ipmi_menu_await_dismiss(guac_client* client); |
There was a problem hiding this comment.
Our practice throughout the code for static functions is generally just to implement them where they need to be in the code and not prototype them.
There was a problem hiding this comment.
Done in 86f6677 — dropped the prototype and moved the definition ahead of its first use.
Addresses the review on apache#692: Behavior: * Accept non-string JSON scalars (numbers, true/false/null) in the control-channel parser, so a valid message from a third-party client is no longer misread as a missing key. * Return guac_ipmi_power_state directly from guac_ipmi_chassis_status() instead of a result code plus an out-parameter. * Reject unknown control commands explicitly via a final else branch rather than falling through to the power-action path. * Drop the vestigial libipmiconsole engine reference count and its mutex; guacd forks per connection, so the count could only ever be 0->1. Init/teardown the engine directly. Settings parsers: * Make the privilege-level, power-action, boot-device, and encryption-policy parsers pure string-to-enum converters: no logging and no default policy. Each returns a new *_INVALID sentinel for NULL/empty/unrecognized input, and the default plus warning is applied by the caller. This also fixes a small leak where the parsed argument strings were never freed. * Collapse the redundant "administrator" privilege alias to "admin". * Remove the unused RMCP "port" setting entirely; both libipmiconsole and libfreeipmi hardwire 623/UDP, so it was read but never honored. Documentation and style: * Complete @param/@return documentation across control.c and settings.c, document the workaround struct members and each enum value, and drop the doc blocks duplicated from control.h. * Remove the now-redundant "config.h" includes (auto-included since GUACAMOLE-2182). * One case label per line, move file-level defines/typedefs to the top of chassis.c, and inline the menu.c static function rather than forward-declaring it. * Add DEBUG log lines when no workaround flags or K_g key are given.
|
Thanks again for the thorough review, @necouchman — I've pushed 86f6677 addressing the feedback, and replied inline on each thread. A summary of what changed: Documentation
Style
Settings parsers
Behavior (discussed inline)
Still to come, from the enum discussion above: giving the privilege enum library-independent values and adding the symmetric |
guac_ipmi_privilege_level previously took the same numeric values as libipmiconsole's IPMICONSOLE_PRIVILEGE_* constants, and the SOL path passed the enum straight into the libipmiconsole config struct. That was correct only by coincidence: libfreeipmi (used by the chassis path) numbers its privilege constants differently, and any future reordering of the enum or renumbering by the library would have silently sent the wrong privilege level. Give the enum library-independent values (INVALID = 0, so a zero-initialized field is safe) and add guac_ipmi_map_privilege_level_ ipmiconsole(), mirroring the existing chassis-side mapper, so each back-end is handed its own constant via an explicit, compiler-checked switch rather than the raw enum value.
This adds a new
ipmiprotocol module to guacamole-server providing Serial-over-LAN (SOL) console access and chassis power management for BMCs (baseboard management controllers), built on FreeIPMI (libipmiconsolefor SOL,libfreeipmifor chassis control).JIRA: https://issues.apache.org/jira/browse/GUACAMOLE-2300
What's included
libguac-client-ipmiprotocol module — SOL console rendered through the sharedguac_terminal.ipmi-controlpipe channel (newline-delimited JSON): power on/off/cycle/reset, soft shutdown, diagnostic interrupt (NMI), chassis identify, System Event Log (SEL) viewer, and live power/SOL status.Ctrl+]) with an alternate-screen UI, run asynchronously so BMC round-trips never block the console.K_g, encryption policy. Verified end-to-end against Supermicro (ATEN), Lenovo XCC, and Dell iDRAC 9.make check.Testing
Related PRs