Skip to content

Instantly share code, notes, and snippets.

@jimklimov
Last active April 8, 2026 02:11
Show Gist options
  • Select an option

  • Save jimklimov/14509edd0f62c54d75235cbcbcc24bce to your computer and use it in GitHub Desktop.

Select an option

Save jimklimov/14509edd0f62c54d75235cbcbcc24bce to your computer and use it in GitHub Desktop.

Completeness Audit of Client Implementations

The client implementations in libupsclient (C), libnutclient (C++), PyNUT (Python), Nut.pm (Perl), and jNut (Java) were audited against the NUT network protocol defined in docs/net-protocol.txt.

1. C Library: clients/upsclient.c (libupsclient)

The C library is the most mature and complete implementation, used by most core NUT utilities (upsc, upscmd, upsrw, upsmon).

  • Commands: Supports all protocol commands: GET, LIST, SET, INSTCMD, LOGIN, LOGOUT, USERNAME, PASSWORD, VER, NETVER, HELP, STARTTLS, PRIMARY/MASTER, FSD.
  • Sub-commands: Full support for GET (VAR, TYPE, DESC, CMDDESC, UPSDESC, NUMLOGINS) and LIST (UPS, VAR, RW, CMD, ENUM, RANGE, CLIENT).
  • SSL/TLS: Comprehensive support for both OpenSSL and Mozilla NSS backends via upscli_sslinit and upscli_tryconnect.
  • Tracking: Implements protocol 1.3 TRACKING support (GET/SET TRACKING).
  • Completeness: ~100%. This is the reference implementation.

2. C++ Library: clients/nutclient.cpp (libnutclient)

The C++ library provides a high-level, object-oriented abstraction of the protocol.

  • Commands: Supports all primary commands. It maps protocol actions to C++ methods (e.g., Device::executeCommand, Device::setVariable).
  • Sub-commands: Supports GET and LIST for all standard resources.
  • SSL/TLS: Supports OpenSSL and NSS, matching libupsclient capabilities.
  • Tracking: Fully implements protocol 1.3 TRACKING support, including sendTrackingQuery and getTrackingResult.
  • Alias Support: Correctly handles PRIMARY/MASTER aliases and PROTVER/NETVER.
  • Completeness: ~100%. Highly modern and feature-parity with the C library.

3. Python Module: scripts/python/module/PyNUT.py.in (PyNUTClient)

The Python implementation is a single-class abstraction (PyNUTClient). While functional for most tasks, it lags slightly behind the C/C++ versions in protocol depth.

  • Commands Supported: GET UPSLIST/UPSVars, LIST UPS/VAR/RW/CMD/CLIENT, SET VAR, INSTCMD, LOGIN, FSD (handles PRIMARY/MASTER), HELP, VER.
  • SSL/TLS: Supports STARTTLS using the Python ssl module (since version 1.9.0 in the code).
  • Missing Features:
    • Tracking: No direct implementation of protocol 1.3 TRACKING commands (though they can be sent via raw queries if needed).
    • Sub-commands: Lacks dedicated methods for GET TYPE, GET DESC, GET CMDDESC, and LIST ENUM/RANGE.
    • NUMLOGINS: Implemented as GetDeviceNumLogins (added in recent versions).
  • Completeness: ~85%. Excellent for automation and basic monitoring, but lacks native wrappers for advanced protocol 1.3 features like execution tracking.

4. Perl Module: scripts/perl/Nut.pm

The Perl implementation provides a unique TIEHASH interface, allowing the UPS state to be accessed as a standard Perl hash.

  • Commands Supported: GET, LIST, SET, INSTCMD, LOGIN, LOGOUT, USERNAME, PASSWORD, VER.
  • Interface: Uses a socket-based communication but is primarily designed around the TIEHASH mechanism for monitoring.
  • Missing Features:
    • SSL/TLS: No support for STARTTLS.
    • Tracking: No support for Protocol 1.3 TRACKING.
    • Sub-commands: Missing several newer sub-commands like UPSDESC, RANGE, and CLIENT.
  • Completeness: ~75%. Best suited for legacy Perl environments and simple monitoring scripts.

5. Java Library: ../jNut

The Java client is a standalone library (available in a separate repository/directory) that provides an object-oriented API for NUT.

  • Commands Supported: GET, LIST, SET, INSTCMD, LOGIN, LOGOUT, USERNAME, PASSWORD, MASTER, FSD.
  • Missing Features:
    • SSL/TLS: Confirmed absence of STARTTLS support in StringLineSocket.java.
    • Tracking: No Protocol 1.3 TRACKING support.
    • Sub-commands: Lacks support for TYPE, ENUM, and RANGE sub-commands.
  • Completeness: ~70%. Functional for core operations but lacks modern security and advanced protocol features.

Summary Table

Feature C (libupsclient) C++ (libnutclient) Python (PyNUT) Perl (Nut.pm) Java (jNut)
Core Commands Yes Yes Yes Yes Yes
SSL/TLS (STARTTLS) Yes (OpenSSL/NSS) Yes (OpenSSL/NSS) Yes (Python ssl) No No
GET (All sub-cmds) Yes Yes Partial Partial Partial
LIST (All sub-cmds) Yes Yes Partial Partial Partial
TRACKING (Prot 1.3) Yes Yes No No No
PRIMARY/MASTER Alias Yes Yes Yes Yes Yes
Status Tracking Yes Yes No No No

Protocol Completion Proposal for Python, Perl, and Java Clients

Following the audit of the NUT client implementations, here is a detailed proposal to bring the Python, Perl, and Java clients to protocol parity with the C/C++ reference implementations.


1. Python (PyNUT.py.in)

The Python implementation is mature but lacks native support for Protocol 1.3 features and several GET/LIST sub-commands.

Proposed Changes:

  • Tracking Support (Protocol 1.3):
    • Implement GetTrackingResult(self, tracking_id): Sends GET TRACKING <id> and parses the TRACKING <id> <status> response.
    • Update SetRWVar and RunUPSCommand to optionally accept/return a tracking ID if the server supports it.
  • Missing GET Sub-commands:
    • Implement GetVariableType(self, ups, var): Sends GET TYPE <ups> <var>.
    • Implement GetVariableDescription(self, ups, var): Sends GET DESC <ups> <var>.
  • Missing LIST Sub-commands:
    • Implement GetEnumList(self, ups, var): Sends LIST ENUM <ups> <var>.
    • Implement GetRangeList(self, ups, var): Sends LIST RANGE <ups> <var>.
  • Administrative:
    • Implement GetNumLogins(self, ups): Sends GET NUMLOGINS <ups>.

2. Perl (scripts/perl/Nut.pm)

The Perl module is primarily focused on the TIEHASH interface and lacks modern security and command tracking.

Proposed Changes:

  • SSL/TLS Support:
    • Integrate IO::Socket::SSL.
    • Implement a StartTLS() method that sends the STARTTLS command and upgrades the existing IO::Socket::INET connection to SSL.
  • Tracking Support:
    • Implement GetTracking(<id>) to return the status of an asynchronous command.
  • Missing Sub-commands:
    • Implement ListClient(): Wrapper for LIST CLIENT <ups>.
    • Implement GetUPSDesc(): Wrapper for GET UPSDESC <ups>.
    • Implement ListRange($var): Wrapper for LIST RANGE <ups> <var>.
  • Alias Handling:
    • Update Master() to support Primary() as a native alias to match NUT 2.8.0+ standards.

3. Java (jNut)

The Java library is the least complete, lacking both TLS security and advanced Protocol 1.3 features.

Proposed Changes:

  • SSL/TLS Support (STARTTLS):
    • Modify StringLineSocket.java to include a startTLS(SSLContext context) method. This would use SSLSocketFactory to wrap the existing plain socket after receiving OK STARTTLS.
    • Update Client.connect() to trigger startTLS() if requested.
  • Protocol 1.3 Tracking:
    • Add a Tracking class to represent an execution ID.
    • Add getTrackingResult(String id) to the Client class.
  • Enhanced Device and Variable Classes:
    • Variable class: Add getType() and getRange() methods (using GET TYPE and LIST RANGE).
    • Device class: Add getEnumList(String var) and getNumLogins().
  • Missing Sub-commands:
    • Implement listClients(String ups) in Client.java to support LIST CLIENT.

Summary of Priority Actions

Feature Python Priority Perl Priority Java Priority
STARTTLS Done (1.9.0) High (Security) High (Security)
TRACKING Medium Low Medium
LIST CLIENT Done Medium High
TYPE/ENUM/RANGE Medium Low Medium
PRIMARY Alias Done Low Done

Implementing these changes will ensure that users of all three languages have access to the full suite of NUT capabilities, including secure communication and asynchronous command verification.

Refined STARTTLS Proposal for Java (jNut)

Following an audit of the reference C implementation (libupsclient) and considering the idiomatic approaches in Java, here is the refined proposal for implementing STARTTLS in jNut.

1. Architectural Approach

The Java implementation will use the Java Secure Socket Extension (JSSE). To maintain parity with the C reference implementations:

  • Reference Parity (NSS-style): Java's use of KeyStore and TrustStore (typically JKS or PKCS12) is conceptually closest to the NSS backend in NUT, which uses a centralized "certificate database" (DB directory) rather than individual PEM files.
  • Implementation Goal: Provide a mechanism that allows the client to either use the system-default stores or provide custom paths, mimicking the upscli_init() behavior.

2. Component-Level Changes

A. StringLineSocket.java (Communication Layer)

  • New Method: public void startTLS(SSLContext context) throws IOException
  • Logic:
    1. After the NUT server responds with OK STARTTLS, this method is called.
    2. It uses SSLSocketFactory from the provided SSLContext to wrap the existing socket.
    3. It performs the TLS handshake using sslSocket.startHandshake().
    4. It replaces the internal reader and writer to use the encrypted streams from the new SSLSocket.

B. Client.java (Configuration & Session Layer)

  • New Fields:
    • String trustStorePath, String trustStorePassword
    • String keyStorePath, String keyStorePassword
    • boolean verifyCert (default true)
  • Config Strategy:
    • NSS-like (Primary): The user provides a path to a KeyStore/TrustStore file. This is the recommended "NSS-style" approach for Java.
    • OpenSSL-like (Fallback/Manual): If users have PEM files (common in OpenSSL setups), they must be imported into a KeyStore (using keytool or a helper utility) before use in jNut.
  • Method authenticate() update:
    • If useSSL is set, send STARTTLS before USERNAME/PASSWORD.
    • If the server returns ERR, fail the connection if forceSSL is true (matching upsclient.c logic).

3. Mapping NUT C Parameters to Java jNut

NUT C Parameter (upscli_init) Java jNut Equivalent Note
certpath (NSS) trustStorePath Directory in NSS vs File in Java.
certname (NSS) clientAlias Alias for the private key in the KeyStore.
certpasswd (NSS) keyStorePassword Password to unlock the KeyStore/Key.
certpath (OpenSSL) (Manual Conversion) OpenSSL PEMs should be loaded into a KeyStore.
certverify TrustManager config X509TrustManager that either validates or ignores certs.

4. Implementation Recommendation

To keep the API clean, the Client should provide a helper method to build the SSLContext:

public void setSSLOptions(String trustStore, String password, boolean verify) {
    // Logic to initialize a KeyStore and build an SSLContext
}

This ensures jNut remains lightweight while providing the security features expected of a modern NUT client.

Advanced SSL Abstraction Proposal for Java (jNut)

To align the Java (jNut) implementation with the modern C++ reference (libnutclient), which uses specialized subclasses for different SSL backends (OpenSSL and NSS), the following object-oriented architecture is proposed for Java.

1. Abstraction Layer: SSLConfig

Instead of adding numerous fields to the Client class, we introduce an SSLConfig hierarchy. This allows jNut to cleanly handle different security models (e.g., standard Java KeyStores vs. manual PEM-style configurations).

Proposed Class Hierarchy:

  • abstract class SSLConfig: Base class containing shared options (forceSSL, certVerify).
  • class SSLConfig_JKS extends SSLConfig: Tailored for standard Java KeyStore/TrustStore files (conceptually closest to NSS).
  • class SSLConfig_PEM extends SSLConfig: A helper class that accepts paths to PEM files (CA, cert, key) and internally handles the conversion/loading into an in-memory KeyStore (conceptually closest to OpenSSL).

2. Technical Implementation Detail

A. Mapping C++ SSLConfig to Java jNut

C++ Class (libnutclient) Java jNut Proposed Class Java Backend Mechanism
SSLConfig_NSS SSLConfig_JKS Uses KeyStore.getInstance("JKS") or "PKCS12".
SSLConfig_OpenSSL SSLConfig_PEM Uses X509Certificate and KeyFactory to load PEMs.
apply(TcpClient&) createContext() Returns a standard javax.net.ssl.SSLContext.

B. Integration with StringLineSocket The StringLineSocket should be updated to accept an SSLContext directly, ensuring it remains agnostic of how the context was built:

public void startTLS(SSLContext sslContext) throws IOException {
    SSLSocketFactory factory = sslContext.getSocketFactory();
    this.socket = factory.createSocket(this.socket, host, port, true);
    ((SSLSocket)this.socket).startHandshake();
    // Refresh reader/writer with encrypted streams...
}

3. Updated Client API

The Client class will now hold a single SSLConfig object, mirroring the C++ setSSLConfig() pattern:

public class Client {
    private SSLConfig sslConfig;
    
    public void setSSLConfig(SSLConfig config) {
        this.sslConfig = config;
    }

    public void connect() throws IOException {
        // ... socket connect ...
        if (sslConfig != null) {
            // Send STARTTLS, then:
            socket.startTLS(sslConfig.createContext());
        }
    }
}

4. Advantages of this Approach

  • Reference Parity: Matches the C++ library's flexibility in supporting multiple configuration styles.
  • Extensibility: Future backends (like BouncyCastle for older Java versions or hardware security modules) can be added as new SSLConfig subclasses without changing the core Client logic.
  • Maintainability: Keeps the Client class clean and focused on protocol logic rather than security configuration boilerplate.

This structured approach brings jNut to a professional level of parity with the core NUT project while respecting Java's specific security idioms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment