/** * @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_Boolean discoveryOnly, 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); /* Always add SecurityPolicy#None so that clients can open an initial unencrypted SecureChannel for the GetEndpoints handshake, then reconnect with the selected secure policy. Restrict None channels to discovery services only so that nobody can open a full session without encryption. When discoveryOnly is true (LDS) we also register a None *endpoint* so that purely unencrypted clients can discover the server — the open62541 client's internal endpoint negotiation requires a matching endpoint in the GetEndpoints response. */ if (*retval == UA_STATUSCODE_GOOD) { UA_ServerConfig_addSecurityPolicyNone (config, &certificate); if (discoveryOnly) 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; }