/** * @file common.c * @brief Implements shared helpers declared in common.h. */ #include "common.h" #include #include #include #include #include #include #include #include /* ======================================================================== * File Loading * ======================================================================== */ UA_ByteString loadFile (const char *const path) { UA_ByteString fileContents = UA_STRING_NULL; FILE *fp = fopen (path, "rb"); if (!fp) { /* fopen sets errno on failure. Callers like createServer use loadFile for optional trustlist entries where a missing file is not an error. Clear errno so open62541's logging does not pick up a stale value and emit misleading error messages. */ errno = 0; return fileContents; } fseek (fp, 0, SEEK_END); fileContents.length = (size_t)ftell (fp); fileContents.data = (UA_Byte *)UA_malloc (fileContents.length * sizeof (UA_Byte)); if (fileContents.data) { fseek (fp, 0, SEEK_SET); size_t read = fread (fileContents.data, sizeof (UA_Byte), fileContents.length, fp); if (read != fileContents.length) UA_ByteString_clear (&fileContents); } else { fileContents.length = 0; } fclose (fp); 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; } int rc = -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"); goto cleanup; } 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; 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"); goto cleanup; } paths = tmp; } /* 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"); goto cleanup; } snprintf (full, fullLen, "%s/%s", dirPath, name); paths[count++] = full; } rc = 0; if (count > 0) { *outPaths = paths; *outSize = count; paths = NULL; count = 0; } cleanup: for (size_t i = 0; i < count; i++) free (paths[i]); free (paths); closedir (dir); return rc; } void freeTrustStore (char **paths, size_t size) { for (size_t i = 0; i < size; i++) free (paths[i]); free (paths); } /* ======================================================================== * Parsing Helpers * ======================================================================== */ int parseLogLevel (const char *name) { static const struct { const char *name; UA_LogLevel level; } levels[] = { { "trace", UA_LOGLEVEL_TRACE }, { "debug", UA_LOGLEVEL_DEBUG }, { "info", UA_LOGLEVEL_INFO }, { "warning", UA_LOGLEVEL_WARNING }, { "error", UA_LOGLEVEL_ERROR }, { "fatal", UA_LOGLEVEL_FATAL }, }; for (size_t i = 0; i < sizeof (levels) / sizeof (levels[0]); i++) { if (strcmp (name, levels[i].name) == 0) return (int)levels[i].level; } return -1; } int parseAuthConfig (const Config *cfg, const char *program, UA_Boolean *allowAnonymous, const char **username, const char **password) { const char *authMode = configRequire (cfg, "authMode", program); if (!authMode) return -1; *username = NULL; *password = NULL; if (strcmp (authMode, "anonymous") == 0) { if (allowAnonymous) *allowAnonymous = true; return 0; } if (strcmp (authMode, "user") == 0) { if (allowAnonymous) *allowAnonymous = false; *username = configRequire (cfg, "username", program); *password = configRequire (cfg, "password", program); if (!*username || !*password) return -1; return 0; } UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: unknown auth mode '%s' (expected 'anonymous' or 'user')", program, authMode); return -1; } UA_MessageSecurityMode parseSecurityMode (const char *name) { if (strcmp (name, "None") == 0) return UA_MESSAGESECURITYMODE_NONE; if (strcmp (name, "Sign") == 0) return UA_MESSAGESECURITYMODE_SIGN; if (strcmp (name, "SignAndEncrypt") == 0) return UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; return UA_MESSAGESECURITYMODE_INVALID; } const char * resolveSecurityPolicyUri (const char *shortName) { static const struct { const char *name; const char *uri; } policies[] = { { "None", "http://opcfoundation.org/UA/SecurityPolicy#None" }, { "Basic256Sha256", "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256" }, { "Aes256_Sha256_RsaPss", "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss" }, { "Aes128_Sha256_RsaOaep", "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep" }, { "ECC_nistP256", "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256" }, }; for (size_t i = 0; i < sizeof (policies) / sizeof (policies[0]); i++) { if (strcmp (shortName, policies[i].name) == 0) return policies[i].uri; } return NULL; } /* ======================================================================== * Output Formatting * ======================================================================== */ void printApplicationDescription (const UA_ApplicationDescription *description, size_t index) { const char *type = "Unknown"; switch (description->applicationType) { case UA_APPLICATIONTYPE_SERVER: type = "Server"; break; case UA_APPLICATIONTYPE_CLIENT: type = "Client"; break; case UA_APPLICATIONTYPE_CLIENTANDSERVER: type = "Client and Server"; break; case UA_APPLICATIONTYPE_DISCOVERYSERVER: type = "Discovery Server"; break; default: break; } UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "Server[%lu]: %.*s", (unsigned long)index, (int)description->applicationUri.length, description->applicationUri.data); UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Name: %.*s", (int)description->applicationName.text.length, description->applicationName.text.data); UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Application URI: %.*s", (int)description->applicationUri.length, description->applicationUri.data); UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Product URI: %.*s", (int)description->productUri.length, description->productUri.data); UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Type: %s", type); for (size_t j = 0; j < description->discoveryUrlsSize; j++) { UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " Discovery URL[%lu]: %.*s", (unsigned long)j, (int)description->discoveryUrls[j].length, description->discoveryUrls[j].data); } } void printEndpoint (const UA_EndpointDescription *endpoint, size_t index) { const char *mode = "Unknown"; switch (endpoint->securityMode) { case UA_MESSAGESECURITYMODE_NONE: mode = "None"; break; case UA_MESSAGESECURITYMODE_SIGN: mode = "Sign"; break; case UA_MESSAGESECURITYMODE_SIGNANDENCRYPT: mode = "SignAndEncrypt"; break; default: break; } /* Extract policy name after the '#' */ const char *policy = (const char *)endpoint->securityPolicyUri.data; size_t policyLen = endpoint->securityPolicyUri.length; for (size_t k = 0; k < endpoint->securityPolicyUri.length; k++) { if (endpoint->securityPolicyUri.data[k] == '#') { policy = (const char *)&endpoint->securityPolicyUri.data[k + 1]; policyLen = endpoint->securityPolicyUri.length - k - 1; break; } } UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, " [%4lu] %.*s | Level: %3d | %-14s | %.*s", (unsigned long)index, (int)endpoint->endpointUrl.length, endpoint->endpointUrl.data, endpoint->securityLevel, mode, (int)policyLen, policy); } /* ======================================================================== * Factory Functions * ======================================================================== */ UA_Server * createServer (UA_UInt16 port, const char *applicationUri, const char *certPath, const char *keyPath, char **trustPaths, size_t trustSize, UA_StatusCode *retval) { UA_Server *server = UA_Server_new (); UA_ServerConfig *config = UA_Server_getConfig (server); if (certPath) { UA_ByteString certificate = loadFile (certPath); UA_ByteString privateKey = loadFile (keyPath); /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */ UA_STACKARRAY (UA_ByteString, trustList, trustSize + 1); for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); *retval = UA_ServerConfig_setDefaultWithSecureSecurityPolicies ( config, port, &certificate, &privateKey, trustList, trustSize, NULL, 0, NULL, 0); /* Also offer SecurityPolicy#None, but restricted to discovery services (FindServers, GetEndpoints) so that unencrypted clients can still discover the server without being able to open a full session. We must add both the security *policy* (so the server accepts None SecureChannels) and the *endpoint* (so the None endpoint appears in GetEndpoints responses — required by the open62541 client's internal endpoint negotiation). */ if (*retval == UA_STATUSCODE_GOOD) { UA_ServerConfig_addSecurityPolicyNone (config, &certificate); UA_ServerConfig_addEndpoint (config, UA_SECURITY_POLICY_NONE_URI, UA_MESSAGESECURITYMODE_NONE); config->securityPolicyNoneDiscoveryOnly = true; } UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); for (size_t i = 0; i < trustSize; i++) UA_ByteString_clear (&trustList[i]); } else { *retval = UA_ServerConfig_setMinimal (config, port, NULL); } if (*retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); return NULL; } UA_String_clear (&config->applicationDescription.applicationUri); config->applicationDescription.applicationUri = UA_String_fromChars (applicationUri); return server; } UA_StatusCode createSecureClientConfig (UA_ClientConfig *cc, const char *applicationUri, const char *certPath, const char *keyPath, char **trustPaths, size_t trustSize, UA_MessageSecurityMode securityMode, const char *securityPolicyUri) { UA_ByteString certificate = loadFile (certPath); UA_ByteString privateKey = loadFile (keyPath); /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */ UA_STACKARRAY (UA_ByteString, trustList, trustSize + 1); for (size_t i = 0; i < trustSize; i++) trustList[i] = loadFile (trustPaths[i]); UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption ( cc, certificate, privateKey, trustList, trustSize, NULL, 0); UA_ByteString_clear (&certificate); UA_ByteString_clear (&privateKey); for (size_t i = 0; i < trustSize; i++) UA_ByteString_clear (&trustList[i]); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "Failed to set client encryption. StatusCode %s", UA_StatusCode_name (retval)); return retval; } UA_String_clear (&cc->clientDescription.applicationUri); cc->clientDescription.applicationUri = UA_String_fromChars (applicationUri); cc->securityMode = securityMode; cc->securityPolicyUri = UA_String_fromChars (securityPolicyUri); return retval; }