From 827e90e0daabe32e058e08dd2a253425898a7e7a Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Tue, 17 Feb 2026 19:06:22 +0100 Subject: Replace ClientFindServers with unified Client, use trust store directories Replace the single-purpose ClientFindServers program with a unified Client that supports three operations via CLI: find-servers, get-endpoints, and read-time. This simplifies the architecture by using one client binary with a single config file instead of a monolithic program that did everything in one run. Split the ServerRegister config into separate server and client config files so the LDS-registration credentials are isolated from the server's own settings. The discovery URL moves from config to a CLI argument. Replace repeated trustList config entries with a single trustStore directory path. Each program now points to a directory under certs/trust/ containing .der files, so adding or removing trust is a file-copy operation rather than editing every config file. Add loadTrustStore()/freeTrustStore() to common.c and remove the now-unused configGetAll() from the config parser. Simplify the test matrix from 6 to 4 cases (security and auth are orthogonal, so the full 3x2 matrix is unnecessary). Update run_test.sh to invoke the new Client three times and use port-polling instead of sleep. --- src/client.c | 328 ++++++++++++++++++++++++++++++++++ src/client_find_servers.c | 438 ---------------------------------------------- src/common.c | 114 ++++++++++-- src/common.h | 25 +++ src/config.c | 40 ----- src/config.h | 21 +-- src/server_lds.c | 19 +- src/server_register.c | 227 ++++++++++++++++-------- 8 files changed, 621 insertions(+), 591 deletions(-) create mode 100644 src/client.c delete mode 100644 src/client_find_servers.c (limited to 'src') diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..8234963 --- /dev/null +++ b/src/client.c @@ -0,0 +1,328 @@ +/** + * @file client.c + * @brief Unified OPC UA client for discovery and server interaction. + * + * Supports three operations selected via CLI: + * find-servers — queries a server's FindServers service + * get-endpoints — queries a server's GetEndpoints service + * read-time — connects to a server and reads the current time + */ + +#include "common.h" +#include "config.h" + +#include +#include + +#include +#include + +/* ======================================================================== + * Operation Dispatch + * ======================================================================== */ + +typedef enum +{ + OP_FIND_SERVERS, + OP_GET_ENDPOINTS, + OP_READ_TIME, + OP_INVALID +} Operation; + +static Operation +parseOperation (const char *name) +{ + if (strcmp (name, "find-servers") == 0) + return OP_FIND_SERVERS; + if (strcmp (name, "get-endpoints") == 0) + return OP_GET_ENDPOINTS; + if (strcmp (name, "read-time") == 0) + return OP_READ_TIME; + return OP_INVALID; +} + +/* ======================================================================== + * Operations + * ======================================================================== */ + +/** + * Calls the FindServers service and prints all discovered servers. + * + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opFindServers (UA_Client *client, const char *url) +{ + size_t arraySize = 0; + UA_ApplicationDescription *array = NULL; + + UA_StatusCode retval = UA_Client_findServers (client, url, 0, NULL, 0, NULL, + &arraySize, &array); + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "FindServers failed: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < arraySize; i++) + printApplicationDescription (&array[i], i); + + UA_Array_delete (array, arraySize, + &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); + return EXIT_SUCCESS; +} + +/** + * Calls the GetEndpoints service and prints all endpoints. + * + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opGetEndpoints (UA_Client *client, const char *url) +{ + size_t arraySize = 0; + UA_EndpointDescription *array = NULL; + + UA_StatusCode retval + = UA_Client_getEndpoints (client, url, &arraySize, &array); + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "GetEndpoints failed: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < arraySize; i++) + printEndpoint (&array[i], i); + + UA_Array_delete (array, arraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); + return EXIT_SUCCESS; +} + +/** + * Connects to a server and reads the current time node. + * + * @param username Username for session auth, or NULL for anonymous. + * @param password Password for session auth (ignored when username is NULL). + * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise. + */ +static int +opReadTime (UA_Client *client, const char *url, const char *username, + const char *password) +{ + UA_StatusCode retval; + if (username) + retval = UA_Client_connectUsername (client, url, username, password); + else + retval = UA_Client_connect (client, url); + + if (retval != UA_STATUSCODE_GOOD) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Could not connect: %s", UA_StatusCode_name (retval)); + return EXIT_FAILURE; + } + + UA_Variant value; + UA_Variant_init (&value); + + const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); + retval = UA_Client_readValueAttribute (client, nodeId, &value); + + int rc = EXIT_SUCCESS; + if (retval == UA_STATUSCODE_GOOD + && UA_Variant_hasScalarType (&value, &UA_TYPES[UA_TYPES_DATETIME])) + { + UA_DateTime raw_date = *(UA_DateTime *)value.data; + UA_DateTimeStruct dts = UA_DateTime_toStruct (raw_date); + UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, + "date is: %u-%u-%u %u:%u:%u.%03u", dts.day, dts.month, + dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); + } + else + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, + "Could not read current time: %s", + UA_StatusCode_name (retval)); + rc = EXIT_FAILURE; + } + + UA_Variant_clear (&value); + UA_Client_disconnect (client); + return rc; +} + +/* ======================================================================== + * Main + * ======================================================================== */ + +int +main (int argc, char **argv) +{ + if (argc < 4 || argc > 5) + { + UA_LOG_FATAL ( + UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Usage: %s [log-level]", + argv[0]); + return EXIT_FAILURE; + } + + Operation op = parseOperation (argv[2]); + if (op == OP_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown operation: %s " + "(expected find-servers, get-endpoints, read-time)", + argv[2]); + return EXIT_FAILURE; + } + + const char *endpointUrl = argv[3]; + + const char *logLevelStr = (argc == 5) ? argv[4] : "info"; + int logLevel = parseLogLevel (logLevelStr); + if (logLevel < 0) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown log level: %s " + "(expected trace, debug, info, warning, error, fatal)", + logLevelStr); + return EXIT_FAILURE; + } + + Config cfg; + if (configLoad (argv[1], &cfg) != 0) + return EXIT_FAILURE; + + /* ---- Common config keys ---- */ + + const char *applicationUri + = configRequire (&cfg, "applicationUri", "Client"); + const char *certPath = configRequire (&cfg, "certificate", "Client"); + const char *keyPath = configRequire (&cfg, "privateKey", "Client"); + const char *secModeStr = configRequire (&cfg, "securityMode", "Client"); + const char *secPolStr = configRequire (&cfg, "securityPolicy", "Client"); + + if (!applicationUri || !certPath || !keyPath || !secModeStr || !secPolStr) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + UA_MessageSecurityMode secMode = parseSecurityMode (secModeStr); + if (secMode == UA_MESSAGESECURITYMODE_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security mode: %s", secModeStr); + configFree (&cfg); + return EXIT_FAILURE; + } + + const char *secPolUri = resolveSecurityPolicyUri (secPolStr); + if (!secPolUri) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security policy: %s", secPolStr); + configFree (&cfg); + return EXIT_FAILURE; + } + + /* ---- Auth config (read-time only) ---- */ + + const char *username = NULL, *password = NULL; + + if (op == OP_READ_TIME) + { + const char *authMode = configRequire (&cfg, "authMode", "Client"); + if (!authMode) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + if (strcmp (authMode, "anonymous") == 0) + { + /* No credentials needed. */ + } + else if (strcmp (authMode, "user") == 0) + { + username = configRequire (&cfg, "username", "Client"); + password = configRequire (&cfg, "password", "Client"); + if (!username || !password) + { + configFree (&cfg); + return EXIT_FAILURE; + } + } + else + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown auth mode: %s " + "(expected 'anonymous' or 'user')", + authMode); + configFree (&cfg); + return EXIT_FAILURE; + } + } + + /* ---- Trust store ---- */ + + const char *trustStore = configRequire (&cfg, "trustStore", "Client"); + if (!trustStore) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + char **trustPaths = NULL; + size_t trustSize = 0; + if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0) + { + configFree (&cfg); + return EXIT_FAILURE; + } + + /* ---- Create client ---- */ + + UA_Client *client = UA_Client_new (); + UA_StatusCode retval = createSecureClientConfig ( + UA_Client_getConfig (client), applicationUri, certPath, keyPath, + trustPaths, trustSize, secMode, secPolUri); + if (retval != UA_STATUSCODE_GOOD) + { + UA_Client_delete (client); + freeTrustStore (trustPaths, trustSize); + configFree (&cfg); + return EXIT_FAILURE; + } + UA_Client_getConfig (client)->logging->context = (void *)(uintptr_t)logLevel; + + /* ---- Dispatch operation ---- */ + + int rc; + switch (op) + { + case OP_FIND_SERVERS: + rc = opFindServers (client, endpointUrl); + break; + case OP_GET_ENDPOINTS: + rc = opGetEndpoints (client, endpointUrl); + break; + case OP_READ_TIME: + rc = opReadTime (client, endpointUrl, username, password); + break; + default: + rc = EXIT_FAILURE; + break; + } + + /* ---- Cleanup ---- */ + + UA_Client_delete (client); + freeTrustStore (trustPaths, trustSize); + configFree (&cfg); + + return rc; +} diff --git a/src/client_find_servers.c b/src/client_find_servers.c deleted file mode 100644 index 2212026..0000000 --- a/src/client_find_servers.c +++ /dev/null @@ -1,438 +0,0 @@ -/** - * @file client_find_servers.c - * @brief OPC UA client that queries a Local Discovery Server for registered - * servers. - * - * This program connects to an LDS and calls the FindServers service to - * retrieve all registered servers. It then queries each server's endpoints - * using the GetEndpoints service and displays the results in a human-readable - * format. - */ - -#include "common.h" -#include "config.h" - -#include -#include - -#include -#include - -/* ======================================================================== - * Discovery Service Calls - * ======================================================================== */ - -/** - * Calls the FindServers service on the LDS and prints all discovered servers. - * - * @param client The OPC UA client instance. - * @param discoveryServerEndpoint The LDS endpoint URL. - * @param applicationDescriptionArraySize Output: number of servers found. - * @param applicationDescriptionArray Output: array of server descriptions. - * @return UA_STATUSCODE_GOOD on success, error code otherwise. - */ -static UA_StatusCode -findServers (UA_Client *client, const char *discoveryServerEndpoint, - size_t *applicationDescriptionArraySize, - UA_ApplicationDescription **applicationDescriptionArray) -{ - UA_StatusCode retval = UA_Client_findServers ( - client, discoveryServerEndpoint, 0, NULL, 0, NULL, - applicationDescriptionArraySize, applicationDescriptionArray); - - if (retval != UA_STATUSCODE_GOOD) - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, - "Could not call FindServers service. " - "Is the discovery server started? StatusCode %s", - UA_StatusCode_name (retval)); - return retval; - } - - for (size_t i = 0; i < *applicationDescriptionArraySize; i++) - printApplicationDescription (&(*applicationDescriptionArray)[i], i); - - return UA_STATUSCODE_GOOD; -} - -/** - * Queries endpoints for each discovered server using the GetEndpoints service. - * - * For each server in the applicationDescriptionArray, this function extracts - * the first discovery URL and calls GetEndpoints to retrieve all available - * endpoints. Results are logged via UA_LOG_INFO. - * - * @param client The OPC UA client instance. - * @param applicationDescriptionArray Array of server descriptions from - * FindServers. - * @param applicationDescriptionArraySize Number of servers in the array. - */ -static void -getServersEndpoints (UA_Client *client, - UA_ApplicationDescription *applicationDescriptionArray, - size_t applicationDescriptionArraySize) -{ - for (size_t i = 0; i < applicationDescriptionArraySize; i++) - { - UA_ApplicationDescription *description = &applicationDescriptionArray[i]; - if (description->discoveryUrlsSize == 0) - { - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "[GetEndpoints] Server %.*s did not provide any " - "discovery urls. Skipping.", - (int)description->applicationUri.length, - description->applicationUri.data); - continue; - } - - /* UA_String is not null-terminated; build a C string for the API. */ - char *discoveryUrl = (char *)UA_malloc ( - sizeof (char) * description->discoveryUrls[0].length + 1); - memcpy (discoveryUrl, description->discoveryUrls[0].data, - description->discoveryUrls[0].length); - discoveryUrl[description->discoveryUrls[0].length] = '\0'; - - UA_EndpointDescription *endpointArray = NULL; - size_t endpointArraySize = 0; - UA_StatusCode retval = UA_Client_getEndpoints ( - client, discoveryUrl, &endpointArraySize, &endpointArray); - UA_free (discoveryUrl); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_disconnect (client); - break; - } - - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, - "Endpoints for Server[%lu]: %.*s", (unsigned long)i, - (int)description->applicationUri.length, - description->applicationUri.data); - for (size_t j = 0; j < endpointArraySize; j++) - printEndpoint (&endpointArray[j], j); - - UA_Array_delete (endpointArray, endpointArraySize, - &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); - } -} - -/* ======================================================================== - * Node Reading - * ======================================================================== */ - -/** - * Connects to each non-discovery server and reads the current time node. - * - * For each server that is not a DiscoveryServer, this function establishes a - * secure session, reads the Server_ServerStatus_CurrentTime variable, prints - * the result, and disconnects. - * - * @param client The OPC UA client instance. - * @param applicationDescriptionArray Array of server descriptions from - * FindServers. - * @param applicationDescriptionArraySize Number of servers in the array. - * @param username Username for session auth, or NULL for anonymous. - * @param password Password for session auth (ignored when username is NULL). - */ -static void -readServerTime (UA_Client *client, - UA_ApplicationDescription *applicationDescriptionArray, - size_t applicationDescriptionArraySize, const char *username, - const char *password) -{ - for (size_t i = 0; i < applicationDescriptionArraySize; i++) - { - UA_ApplicationDescription *desc = &applicationDescriptionArray[i]; - - if (desc->applicationType == UA_APPLICATIONTYPE_DISCOVERYSERVER) - continue; - - if (desc->discoveryUrlsSize == 0) - { - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "[ReadTime] Server %.*s has no discovery URLs. " - "Skipping.", - (int)desc->applicationUri.length, - desc->applicationUri.data); - continue; - } - - /* UA_String is not null-terminated; build a C string for the API. */ - char *url = (char *)UA_malloc (desc->discoveryUrls[0].length + 1); - memcpy (url, desc->discoveryUrls[0].data, desc->discoveryUrls[0].length); - url[desc->discoveryUrls[0].length] = '\0'; - - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Connecting to %s to read current time...", url); - - UA_StatusCode retval; - if (username) - retval = UA_Client_connectUsername (client, url, username, password); - else - retval = UA_Client_connect (client, url); - UA_free (url); - if (retval != UA_STATUSCODE_GOOD) - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Could not connect: %s", UA_StatusCode_name (retval)); - continue; - } - - UA_Variant value; - UA_Variant_init (&value); - - const UA_NodeId nodeId = UA_NS0ID (SERVER_SERVERSTATUS_CURRENTTIME); - retval = UA_Client_readValueAttribute (client, nodeId, &value); - - if (retval == UA_STATUSCODE_GOOD - && UA_Variant_hasScalarType (&value, &UA_TYPES[UA_TYPES_DATETIME])) - { - UA_DateTime raw_date = *(UA_DateTime *)value.data; - UA_DateTimeStruct dts = UA_DateTime_toStruct (raw_date); - UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, - "date is: %u-%u-%u %u:%u:%u.%03u", dts.day, dts.month, - dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); - } - else - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_CLIENT, - "Could not read current time: %s", - UA_StatusCode_name (retval)); - } - - UA_Variant_clear (&value); - UA_Client_disconnect (client); - } -} - -/* ======================================================================== - * Main - * ======================================================================== */ - -int -main (int argc, char **argv) -{ - if (argc < 2 || argc > 3) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s [log-level]", argv[0]); - return EXIT_FAILURE; - } - - const char *logLevelStr = (argc == 3) ? argv[2] : "info"; - int logLevel = parseLogLevel (logLevelStr); - if (logLevel < 0) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown log level: %s " - "(expected trace, debug, info, warning, error, fatal)", - logLevelStr); - return EXIT_FAILURE; - } - - Config cfg; - if (configLoad (argv[1], &cfg) != 0) - return EXIT_FAILURE; - - /* ---- Shared keys ---- */ - - const char *discoveryServerEndpoint - = configRequire (&cfg, "discoveryEndpoint", "ClientFindServers"); - const char *applicationUri - = configRequire (&cfg, "applicationUri", "ClientFindServers"); - - if (!discoveryServerEndpoint || !applicationUri) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Discovery-side config (LDS connection) ---- */ - - const char *discCertPath - = configRequire (&cfg, "discoveryCertificate", "ClientFindServers"); - const char *discKeyPath - = configRequire (&cfg, "discoveryPrivateKey", "ClientFindServers"); - const char *discSecModeStr - = configRequire (&cfg, "discoverySecurityMode", "ClientFindServers"); - const char *discSecPolStr - = configRequire (&cfg, "discoverySecurityPolicy", "ClientFindServers"); - const char *discAuthMode - = configRequire (&cfg, "discoveryAuthMode", "ClientFindServers"); - - if (!discCertPath || !discKeyPath || !discSecModeStr || !discSecPolStr - || !discAuthMode) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - UA_MessageSecurityMode discSecMode = parseSecurityMode (discSecModeStr); - if (discSecMode == UA_MESSAGESECURITYMODE_INVALID) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown discovery security mode: %s", discSecModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *discSecPolUri = resolveSecurityPolicyUri (discSecPolStr); - if (!discSecPolUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown discovery security policy: %s", discSecPolStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Server-side config (connections to discovered servers) ---- */ - - const char *srvCertPath - = configRequire (&cfg, "serverCertificate", "ClientFindServers"); - const char *srvKeyPath - = configRequire (&cfg, "serverPrivateKey", "ClientFindServers"); - const char *srvSecModeStr - = configRequire (&cfg, "serverSecurityMode", "ClientFindServers"); - const char *srvSecPolStr - = configRequire (&cfg, "serverSecurityPolicy", "ClientFindServers"); - const char *srvAuthMode - = configRequire (&cfg, "serverAuthMode", "ClientFindServers"); - - if (!srvCertPath || !srvKeyPath || !srvSecModeStr || !srvSecPolStr - || !srvAuthMode) - { - configFree (&cfg); - return EXIT_FAILURE; - } - - UA_MessageSecurityMode srvSecMode = parseSecurityMode (srvSecModeStr); - if (srvSecMode == UA_MESSAGESECURITYMODE_INVALID) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server security mode: %s", srvSecModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *srvSecPolUri = resolveSecurityPolicyUri (srvSecPolStr); - if (!srvSecPolUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server security policy: %s", srvSecPolStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Server-side auth ---- */ - - const char *srvUsername = NULL, *srvPassword = NULL; - - if (strcmp (srvAuthMode, "anonymous") == 0) - { - /* No credentials needed */ - } - else if (strcmp (srvAuthMode, "user") == 0) - { - srvUsername - = configRequire (&cfg, "serverUsername", "ClientFindServers"); - srvPassword - = configRequire (&cfg, "serverPassword", "ClientFindServers"); - if (!srvUsername || !srvPassword) - { - configFree (&cfg); - return EXIT_FAILURE; - } - } - else - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown server auth mode: %s " - "(expected 'anonymous' or 'user')", - srvAuthMode); - configFree (&cfg); - return EXIT_FAILURE; - } - - /* ---- Trust lists ---- */ - - char **discTrustPaths = NULL; - size_t discTrustSize = 0; - configGetAll (&cfg, "discoveryTrustList", &discTrustPaths, &discTrustSize); - - char **srvTrustPaths = NULL; - size_t srvTrustSize = 0; - configGetAll (&cfg, "serverTrustList", &srvTrustPaths, &srvTrustSize); - - /* ---- Create discovery client (LDS) ---- */ - - UA_Client *discoveryClient = UA_Client_new (); - UA_StatusCode retval = createSecureClientConfig ( - UA_Client_getConfig (discoveryClient), applicationUri, discCertPath, - discKeyPath, discTrustPaths, discTrustSize, discSecMode, discSecPolUri); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - UA_Client_getConfig (discoveryClient)->logging->context - = (void *)(uintptr_t)logLevel; - - /* ---- Create server client (discovered servers) ---- */ - - UA_Client *serverClient = UA_Client_new (); - retval = createSecureClientConfig ( - UA_Client_getConfig (serverClient), applicationUri, srvCertPath, - srvKeyPath, srvTrustPaths, srvTrustSize, srvSecMode, srvSecPolUri); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - UA_Client_getConfig (serverClient)->logging->context - = (void *)(uintptr_t)logLevel; - - /* ---- Discovery calls (use discoveryClient) ---- */ - - UA_ApplicationDescription *applicationDescriptionArray = NULL; - size_t applicationDescriptionArraySize = 0; - - retval = findServers (discoveryClient, discoveryServerEndpoint, - &applicationDescriptionArraySize, - &applicationDescriptionArray); - if (retval != UA_STATUSCODE_GOOD) - { - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - return EXIT_FAILURE; - } - - getServersEndpoints (discoveryClient, applicationDescriptionArray, - applicationDescriptionArraySize); - - /* ---- Server calls (use serverClient) ---- */ - - readServerTime (serverClient, applicationDescriptionArray, - applicationDescriptionArraySize, srvUsername, srvPassword); - - /* ---- Cleanup ---- */ - - UA_Client_delete (discoveryClient); - UA_Client_delete (serverClient); - UA_Array_delete (applicationDescriptionArray, - applicationDescriptionArraySize, - &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); - free (discTrustPaths); - free (srvTrustPaths); - configFree (&cfg); - - return EXIT_SUCCESS; -} diff --git a/src/common.c b/src/common.c index 7d378f1..568e4d0 100644 --- a/src/common.c +++ b/src/common.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -55,6 +56,102 @@ loadFile (const char *const path) return fileContents; } +/* ======================================================================== + * Trust Store + * ======================================================================== */ + +int +loadTrustStore (const char *dirPath, char ***outPaths, size_t *outSize) +{ + *outPaths = NULL; + *outSize = 0; + + DIR *dir = opendir (dirPath); + if (!dir) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Cannot open trust store directory '%s'", dirPath); + return -1; + } + + size_t capacity = 8; + size_t count = 0; + char **paths = malloc (capacity * sizeof (char *)); + if (!paths) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + closedir (dir); + return -1; + } + + struct dirent *entry; + while ((entry = readdir (dir)) != NULL) + { + const char *name = entry->d_name; + size_t nameLen = strlen (name); + /* Skip entries that are not *.der files. 5 = strlen("x.der"). */ + if (nameLen < 5 || strcmp (name + nameLen - 4, ".der") != 0) + continue; + + /* Build full path: dirPath/name */ + size_t dirLen = strlen (dirPath); + size_t fullLen = dirLen + 1 + nameLen + 1; + char *full = malloc (fullLen); + if (!full) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + for (size_t i = 0; i < count; i++) + free (paths[i]); + free (paths); + closedir (dir); + return -1; + } + snprintf (full, fullLen, "%s/%s", dirPath, name); + + if (count == capacity) + { + capacity *= 2; + char **tmp = realloc (paths, capacity * sizeof (char *)); + if (!tmp) + { + UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "loadTrustStore: out of memory"); + free (full); + for (size_t i = 0; i < count; i++) + free (paths[i]); + free (paths); + closedir (dir); + return -1; + } + paths = tmp; + } + + paths[count++] = full; + } + + closedir (dir); + + if (count == 0) + { + free (paths); + return 0; + } + + *outPaths = paths; + *outSize = count; + return 0; +} + +void +freeTrustStore (char **paths, size_t size) +{ + for (size_t i = 0; i < size; i++) + free (paths[i]); + free (paths); +} + /* ======================================================================== * Security Helpers * ======================================================================== */ @@ -223,18 +320,12 @@ createSecureServer (UA_UInt16 port, const char *applicationUri, for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); - /* Issuer and revocation lists are unused in this demo. */ - size_t issuerListSize = 0; - UA_ByteString *issuerList = NULL; - UA_ByteString *revocationList = NULL; - size_t revocationListSize = 0; - UA_Server *server = UA_Server_new (); UA_ServerConfig *config = UA_Server_getConfig (server); *retval = UA_ServerConfig_setDefaultWithSecurityPolicies ( - config, port, &certificate, &privateKey, trustList, trustSize, - issuerList, issuerListSize, revocationList, revocationListSize); + config, port, &certificate, &privateKey, trustList, trustSize, NULL, 0, + NULL, 0); UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); @@ -269,13 +360,8 @@ createSecureClientConfig (UA_ClientConfig *cc, const char *applicationUri, for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); - /* Revocation list is unused in this demo. */ - UA_ByteString *revocationList = NULL; - size_t revocationListSize = 0; - UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption ( - cc, certificate, privateKey, trustList, trustSize, revocationList, - revocationListSize); + cc, certificate, privateKey, trustList, trustSize, NULL, 0); UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); diff --git a/src/common.h b/src/common.h index e8c0c78..7290181 100644 --- a/src/common.h +++ b/src/common.h @@ -13,6 +13,8 @@ #include #include +#include + /** * @brief Loads a DER-encoded certificate or key file into a UA_ByteString. * @@ -21,6 +23,29 @@ */ UA_ByteString loadFile (const char *const path); +/** + * @brief Collects all *.der file paths from a trust store directory. + * + * Opens the directory, finds every file ending in ".der", and builds + * heap-allocated full paths (dirPath/filename). The caller must free + * the result with freeTrustStore(). + * + * @param dirPath Path to the trust store directory. + * @param outPaths Output: heap-allocated array of heap-allocated strings. + * Set to NULL when the directory is empty. + * @param outSize Output: number of entries in outPaths. + * @return 0 on success, -1 on error (logged via UA_LOG_ERROR). + */ +int loadTrustStore (const char *dirPath, char ***outPaths, size_t *outSize); + +/** + * @brief Frees the array returned by loadTrustStore(). + * + * @param paths The array of strings (may be NULL). + * @param size Number of entries. + */ +void freeTrustStore (char **paths, size_t size); + /** * @brief Creates a UA_Server configured with security policies and encryption. * diff --git a/src/config.c b/src/config.c index 163f601..2165821 100644 --- a/src/config.c +++ b/src/config.c @@ -199,46 +199,6 @@ configRequireInt (const Config *cfg, const char *key, const char *program) return (int)num; } -void -configGetAll (const Config *cfg, const char *key, char ***out, size_t *size) -{ - /* First pass: count matches. */ - size_t count = 0; - for (size_t i = 0; i < cfg->count; i++) - { - if (strcmp (cfg->entries[i].key, key) == 0) - count++; - } - - if (count == 0) - { - *out = NULL; - *size = 0; - return; - } - - /* Second pass: collect pointers. */ - char **arr = malloc (count * sizeof (char *)); - if (!arr) - { - UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Config: out of memory"); - *out = NULL; - *size = 0; - return; - } - - size_t idx = 0; - for (size_t i = 0; i < cfg->count; i++) - { - if (strcmp (cfg->entries[i].key, key) == 0) - arr[idx++] = cfg->entries[i].value; - } - - *out = arr; - *size = count; -} - void configFree (Config *cfg) { diff --git a/src/config.h b/src/config.h index 649278d..2c5e364 100644 --- a/src/config.h +++ b/src/config.h @@ -7,8 +7,6 @@ * * Parses configuration files with one key=value pair per line. * Lines starting with '#' are comments. Blank lines are ignored. - * Repeated keys are allowed (used for list-valued settings like - * trustList). */ #include @@ -28,8 +26,7 @@ typedef struct /** * @brief A parsed configuration file. * - * Holds a dynamic array of ConfigEntry items. Duplicate keys are - * allowed (used for list-valued settings like trustList). + * Holds a dynamic array of ConfigEntry items. */ typedef struct { @@ -91,22 +88,6 @@ const char *configRequire (const Config *cfg, const char *key, */ int configRequireInt (const Config *cfg, const char *key, const char *program); -/** - * @brief Collects all values for a repeated key. - * - * Allocates an array of char* pointers to the values stored - * in @p cfg. The caller must free the array itself (but not - * the strings, which are owned by cfg). - * - * @param cfg The parsed configuration. - * @param key The key to collect (e.g. "trustList"). - * @param out Output: heap-allocated array of string pointers. - * Set to NULL if the key is not present. - * @param size Output: number of entries in @p out. - */ -void configGetAll (const Config *cfg, const char *key, char ***out, - size_t *size); - /** * @brief Frees all memory owned by a Config structure. * diff --git a/src/server_lds.c b/src/server_lds.c index 2fe508f..a9a68bc 100644 --- a/src/server_lds.c +++ b/src/server_lds.c @@ -111,9 +111,20 @@ main (int argc, char *argv[]) return EXIT_FAILURE; } + const char *trustStore = configRequire (&cfg, "trustStore", "ServerLDS"); + if (!trustStore) + { + configFree (&cfg); + return EXIT_FAILURE; + } + char **trustPaths = NULL; size_t trustSize = 0; - configGetAll (&cfg, "trustList", &trustPaths, &trustSize); + if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0) + { + configFree (&cfg); + return EXIT_FAILURE; + } UA_StatusCode retval; UA_Server *server @@ -121,7 +132,7 @@ main (int argc, char *argv[]) trustPaths, trustSize, &retval); if (!server) { - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } @@ -147,7 +158,7 @@ main (int argc, char *argv[]) if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } @@ -164,7 +175,7 @@ main (int argc, char *argv[]) retval = UA_Server_run (server, &running); UA_Server_delete (server); - free (trustPaths); + freeTrustStore (trustPaths, trustSize); configFree (&cfg); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/server_register.c b/src/server_register.c index d259961..6e1eb6d 100644 --- a/src/server_register.c +++ b/src/server_register.c @@ -42,14 +42,18 @@ main (int argc, char **argv) signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); - if (argc < 2 || argc > 3) + if (argc < 4 || argc > 5) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Usage: %s [log-level]", argv[0]); + "Usage: %s " + " [log-level]", + argv[0]); return EXIT_FAILURE; } - const char *logLevelStr = (argc == 3) ? argv[2] : "info"; + const char *discoveryEndpoint = argv[3]; + + const char *logLevelStr = (argc == 5) ? argv[4] : "info"; int logLevel = parseLogLevel (logLevelStr); if (logLevel < 0) { @@ -60,58 +64,28 @@ main (int argc, char **argv) return EXIT_FAILURE; } - Config cfg; - if (configLoad (argv[1], &cfg) != 0) + /* ── Load server config ─────────────────────────────────────── */ + + Config serverCfg; + if (configLoad (argv[1], &serverCfg) != 0) return EXIT_FAILURE; - int port = configRequireInt (&cfg, "port", "ServerRegister"); + int port = configRequireInt (&serverCfg, "port", "ServerRegister"); const char *applicationUri - = configRequire (&cfg, "applicationUri", "ServerRegister"); + = configRequire (&serverCfg, "applicationUri", "ServerRegister"); const char *serverCertPath - = configRequire (&cfg, "serverCertificate", "ServerRegister"); + = configRequire (&serverCfg, "certificate", "ServerRegister"); const char *serverKeyPath - = configRequire (&cfg, "serverPrivateKey", "ServerRegister"); - const char *clientCertPath - = configRequire (&cfg, "clientCertificate", "ServerRegister"); - const char *clientKeyPath - = configRequire (&cfg, "clientPrivateKey", "ServerRegister"); - const char *discoveryEndpoint - = configRequire (&cfg, "discoveryEndpoint", "ServerRegister"); + = configRequire (&serverCfg, "privateKey", "ServerRegister"); int registerInterval - = configRequireInt (&cfg, "registerInterval", "ServerRegister"); - const char *securityModeStr - = configRequire (&cfg, "securityMode", "ServerRegister"); - const char *securityPolicyStr - = configRequire (&cfg, "securityPolicy", "ServerRegister"); + = configRequireInt (&serverCfg, "registerInterval", "ServerRegister"); const char *serverAuthMode - = configRequire (&cfg, "serverAuthMode", "ServerRegister"); - const char *clientAuthMode - = configRequire (&cfg, "clientAuthMode", "ServerRegister"); - - if (!applicationUri || !serverCertPath || !serverKeyPath || !clientCertPath - || !clientKeyPath || !discoveryEndpoint || !securityModeStr - || !securityPolicyStr || !serverAuthMode || !clientAuthMode || port < 0 - || registerInterval < 0) - { - configFree (&cfg); - return EXIT_FAILURE; - } + = configRequire (&serverCfg, "authMode", "ServerRegister"); - UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr); - if (securityMode == UA_MESSAGESECURITYMODE_INVALID) + if (!applicationUri || !serverCertPath || !serverKeyPath || !serverAuthMode + || port < 0 || registerInterval < 0) { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown security mode: %s", securityModeStr); - configFree (&cfg); - return EXIT_FAILURE; - } - - const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr); - if (!securityPolicyUri) - { - UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, - "Unknown security policy: %s", securityPolicyStr); - configFree (&cfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -129,12 +103,12 @@ main (int argc, char **argv) { serverAllowAnonymous = false; serverUsername - = configRequire (&cfg, "serverUsername", "ServerRegister"); + = configRequire (&serverCfg, "username", "ServerRegister"); serverPassword - = configRequire (&cfg, "serverPassword", "ServerRegister"); + = configRequire (&serverCfg, "password", "ServerRegister"); if (!serverUsername || !serverPassword) { - configFree (&cfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -144,7 +118,78 @@ main (int argc, char **argv) "Unknown server auth mode: %s " "(expected 'anonymous' or 'user')", serverAuthMode); - configFree (&cfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *serverTrustStore + = configRequire (&serverCfg, "trustStore", "ServerRegister"); + if (!serverTrustStore) + { + configFree (&serverCfg); + return EXIT_FAILURE; + } + + char **serverTrustPaths = NULL; + size_t serverTrustSize = 0; + if (loadTrustStore (serverTrustStore, &serverTrustPaths, &serverTrustSize) + != 0) + { + configFree (&serverCfg); + return EXIT_FAILURE; + } + + /* ── Load client config ─────────────────────────────────────── */ + + Config clientCfg; + if (configLoad (argv[2], &clientCfg) != 0) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *clientAppUri + = configRequire (&clientCfg, "applicationUri", "ServerRegister"); + const char *clientCertPath + = configRequire (&clientCfg, "certificate", "ServerRegister"); + const char *clientKeyPath + = configRequire (&clientCfg, "privateKey", "ServerRegister"); + const char *securityModeStr + = configRequire (&clientCfg, "securityMode", "ServerRegister"); + const char *securityPolicyStr + = configRequire (&clientCfg, "securityPolicy", "ServerRegister"); + const char *clientAuthMode + = configRequire (&clientCfg, "authMode", "ServerRegister"); + + if (!clientAppUri || !clientCertPath || !clientKeyPath || !securityModeStr + || !securityPolicyStr || !clientAuthMode) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr); + if (securityMode == UA_MESSAGESECURITYMODE_INVALID) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security mode: %s", securityModeStr); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr); + if (!securityPolicyUri) + { + UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, + "Unknown security policy: %s", securityPolicyStr); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -159,12 +204,14 @@ main (int argc, char **argv) else if (strcmp (clientAuthMode, "user") == 0) { clientUsername - = configRequire (&cfg, "clientUsername", "ServerRegister"); + = configRequire (&clientCfg, "username", "ServerRegister"); clientPassword - = configRequire (&cfg, "clientPassword", "ServerRegister"); + = configRequire (&clientCfg, "password", "ServerRegister"); if (!clientUsername || !clientPassword) { - configFree (&cfg); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -174,22 +221,45 @@ main (int argc, char **argv) "Unknown client auth mode: %s " "(expected 'anonymous' or 'user')", clientAuthMode); - configFree (&cfg); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + const char *clientTrustStore + = configRequire (&clientCfg, "trustStore", "ServerRegister"); + if (!clientTrustStore) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); + return EXIT_FAILURE; + } + + char **clientTrustPaths = NULL; + size_t clientTrustSize = 0; + if (loadTrustStore (clientTrustStore, &clientTrustPaths, &clientTrustSize) + != 0) + { + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } - char **trustPaths = NULL; - size_t trustSize = 0; - configGetAll (&cfg, "trustList", &trustPaths, &trustSize); + /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; - UA_Server *server - = createSecureServer ((UA_UInt16)port, applicationUri, serverCertPath, - serverKeyPath, trustPaths, trustSize, &retval); + UA_Server *server = createSecureServer ( + (UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath, + serverTrustPaths, serverTrustSize, &retval); if (!server) { - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } @@ -209,8 +279,10 @@ main (int argc, char **argv) if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } } @@ -227,14 +299,16 @@ main (int argc, char **argv) UA_ClientConfig clientConfig; memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths, - trustSize, securityMode, securityPolicyUri); + &clientConfig, clientAppUri, clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_FAILURE; } clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -263,9 +337,10 @@ main (int argc, char **argv) if (now - lastRegister >= registerInterval) { memset (&clientConfig, 0, sizeof (UA_ClientConfig)); - retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, - trustPaths, trustSize, securityMode, securityPolicyUri); + retval = createSecureClientConfig (&clientConfig, clientAppUri, + clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, + securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -289,8 +364,8 @@ main (int argc, char **argv) our entry immediately rather than waiting for the cleanup timeout. */ memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( - &clientConfig, applicationUri, clientCertPath, clientKeyPath, trustPaths, - trustSize, securityMode, securityPolicyUri); + &clientConfig, clientAppUri, clientCertPath, clientKeyPath, + clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; @@ -308,7 +383,9 @@ main (int argc, char **argv) UA_Server_run_shutdown (server); UA_Server_delete (server); - free (trustPaths); - configFree (&cfg); + freeTrustStore (clientTrustPaths, clientTrustSize); + freeTrustStore (serverTrustPaths, serverTrustSize); + configFree (&clientCfg); + configFree (&serverCfg); return EXIT_SUCCESS; } -- cgit v1.2.3