/** * @file common.c * @brief Implements shared helpers declared in common.h. */ #include "common.h" #include #include #include #include #include #include #include #include #include /* ======================================================================== * File Loading * ======================================================================== */ UA_ByteString load_file (const char *const path) { UA_ByteString file_contents = UA_STRING_NULL; FILE *fp = fopen (path, "rb"); if (!fp) { /* fopen sets errno on failure. Callers like create_server use load_file 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 file_contents; } fseek (fp, 0, SEEK_END); file_contents.length = (size_t)ftell (fp); file_contents.data = (UA_Byte *)UA_malloc (file_contents.length * sizeof (UA_Byte)); if (file_contents.data) { fseek (fp, 0, SEEK_SET); size_t read = fread (file_contents.data, sizeof (UA_Byte), file_contents.length, fp); if (read != file_contents.length) UA_ByteString_clear (&file_contents); } else { file_contents.length = 0; } fclose (fp); return file_contents; } /* ======================================================================== * Trust Store * ======================================================================== */ int load_trust_store (const char *dir_path, char ***out_paths, size_t *out_size) { *out_paths = NULL; *out_size = 0; DIR *dir = opendir (dir_path); if (!dir) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Cannot open trust store directory '%s'", dir_path); 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, "load_trust_store: out of memory"); goto cleanup; } struct dirent *entry; while ((entry = readdir (dir)) != NULL) { const char *name = entry->d_name; size_t name_len = strlen (name); /* Skip entries that are not *.der files. 5 = strlen("x.der"). */ if (name_len < 5 || strcmp (name + name_len - 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, "load_trust_store: out of memory"); goto cleanup; } paths = tmp; } /* Build full path: dir_path/name */ size_t dir_len = strlen (dir_path); size_t full_len = dir_len + 1 + name_len + 1; char *full = malloc (full_len); if (!full) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "load_trust_store: out of memory"); goto cleanup; } snprintf (full, full_len, "%s/%s", dir_path, name); paths[count++] = full; } rc = 0; if (count > 0) { *out_paths = paths; *out_size = count; paths = NULL; count = 0; } cleanup: for (size_t i = 0; i < count; i++) free (paths[i]); free (paths); closedir (dir); return rc; } void free_trust_store (char **paths, size_t size) { for (size_t i = 0; i < size; i++) free (paths[i]); free (paths); } /* ======================================================================== * Parsing Helpers * ======================================================================== */ int parse_log_level (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 parse_auth_config (const config *cfg, const char *program, auth_config *auth) { const char *auth_mode_str = config_require (cfg, "authMode", program); if (!auth_mode_str) return -1; memset (auth, 0, sizeof (*auth)); if (strcmp (auth_mode_str, "anonymous") == 0) { auth->mode = AUTH_ANONYMOUS; return 0; } if (strcmp (auth_mode_str, "user") == 0) { auth->mode = AUTH_USER; auth->user.username = config_require (cfg, "username", program); auth->user.password = config_require (cfg, "password", program); if (!auth->user.username || !auth->user.password) return -1; return 0; } if (strcmp (auth_mode_str, "cert") == 0) { auth->mode = AUTH_CERT; return 0; } UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: unknown auth mode '%s' " "(expected 'anonymous', 'user', or 'cert')", program, auth_mode_str); return -1; } int parse_security_config (const config *cfg, const char *program, UA_Boolean needs_mode_policy, security_config *sec) { memset (sec, 0, sizeof (*sec)); const char *cert_path = config_get (cfg, "certificate"); const char *key_path = config_get (cfg, "privateKey"); const char *trust_store = config_get (cfg, "trustStore"); UA_Boolean secure = (cert_path != NULL || key_path != NULL || trust_store != NULL); if (!secure) return 0; if (!cert_path || !key_path || !trust_store) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: incomplete security config: certificate, privateKey, " "and trustStore must all be set, or all omitted", program); return -1; } sec->cert_path = cert_path; sec->key_path = key_path; if (needs_mode_policy) { const char *sec_mode_str = config_require (cfg, "securityMode", program); const char *sec_pol_str = config_require (cfg, "securityPolicy", program); if (!sec_mode_str || !sec_pol_str) return -1; sec->security_mode = parse_security_mode (sec_mode_str); if (sec->security_mode == UA_MESSAGESECURITYMODE_INVALID) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: unknown security mode: %s", program, sec_mode_str); return -1; } sec->security_policy_uri = resolve_security_policy_uri (sec_pol_str); if (!sec->security_policy_uri) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: unknown security policy: %s", program, sec_pol_str); return -1; } } if (load_trust_store (trust_store, &sec->trust_paths, &sec->trust_size) != 0) return -1; return 0; } UA_MessageSecurityMode parse_security_mode (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 * resolve_security_policy_uri (const char *short_name) { 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 (short_name, policies[i].name) == 0) return policies[i].uri; } return NULL; } /* ======================================================================== * Access Control * ======================================================================== */ UA_StatusCode configure_access_control (UA_ServerConfig *srv_config, const auth_config *auth) { switch (auth->mode) { case AUTH_ANONYMOUS: return UA_AccessControl_default (srv_config, true, NULL, 0, NULL); case AUTH_USER: { UA_UsernamePasswordLogin logins[1]; logins[0].username = UA_STRING ((char *)auth->user.username); logins[0].password = UA_STRING ((char *)auth->user.password); return UA_AccessControl_default (srv_config, false, NULL, 1, logins); } case AUTH_CERT: return UA_AccessControl_default (srv_config, false, NULL, 0, NULL); } return UA_STATUSCODE_BADINTERNALERROR; } /* ======================================================================== * Output Formatting * ======================================================================== */ void print_application_description (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, " 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 print_endpoint (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 policy_len = 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]; policy_len = 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)policy_len, policy); } /* ======================================================================== * Factory Functions * ======================================================================== */ UA_Server * create_server (UA_UInt16 port, const char *application_uri, const security_config *sec, UA_Boolean discovery, UA_StatusCode *retval) { UA_Server *server = UA_Server_new (); UA_ServerConfig *srv_config = UA_Server_getConfig (server); if (sec && sec->cert_path) { UA_ByteString certificate = load_file (sec->cert_path); UA_ByteString private_key = load_file (sec->key_path); /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */ UA_STACKARRAY (UA_ByteString, trust_list, sec->trust_size + 1); for (size_t i = 0; i < sec->trust_size; i++) trust_list[i] = load_file (sec->trust_paths[i]); *retval = UA_ServerConfig_setDefaultWithSecureSecurityPolicies ( srv_config, port, &certificate, &private_key, trust_list, sec->trust_size, NULL, 0, NULL, 0); /* When discovery is true (LDS) add SecurityPolicy#None restricted to discovery services so that unencrypted clients can still call FindServers / GetEndpoints. A matching None endpoint is required because the open62541 client's internal endpoint negotiation needs it in the GetEndpoints response. When discovery is false the server is purely secure — no None security policy, no None endpoint. */ if (*retval == UA_STATUSCODE_GOOD && discovery) { UA_ServerConfig_addSecurityPolicyNone (srv_config, &certificate); UA_ServerConfig_addEndpoint (srv_config, UA_SECURITY_POLICY_NONE_URI, UA_MESSAGESECURITYMODE_NONE); srv_config->securityPolicyNoneDiscoveryOnly = true; } UA_ByteString_clear (&certificate); UA_ByteString_clear (&private_key); for (size_t i = 0; i < sec->trust_size; i++) UA_ByteString_clear (&trust_list[i]); } else { *retval = UA_ServerConfig_setMinimal (srv_config, port, NULL); } if (*retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); return NULL; } UA_String_clear (&srv_config->applicationDescription.applicationUri); srv_config->applicationDescription.applicationUri = UA_String_fromChars (application_uri); return server; } UA_StatusCode create_unsecure_client_config (UA_ClientConfig *cc, const char *application_uri, const auth_config *auth) { if (auth && auth->mode == AUTH_CERT) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_APPLICATION, "Certificate authentication requires encryption"); return UA_STATUSCODE_BADINVALIDARGUMENT; } UA_StatusCode retval = UA_ClientConfig_setDefault (cc); if (retval != UA_STATUSCODE_GOOD) return retval; UA_String_clear (&cc->clientDescription.applicationUri); cc->clientDescription.applicationUri = UA_String_fromChars (application_uri); cc->securityMode = UA_MESSAGESECURITYMODE_NONE; UA_String_clear (&cc->securityPolicyUri); UA_String_copy (&UA_SECURITY_POLICY_NONE_URI, &cc->securityPolicyUri); if (auth && auth->mode == AUTH_USER) UA_ClientConfig_setAuthenticationUsername (cc, auth->user.username, auth->user.password); return UA_STATUSCODE_GOOD; } UA_StatusCode create_secure_client_config (UA_ClientConfig *cc, const char *application_uri, const security_config *sec, const auth_config *auth) { UA_ByteString certificate = load_file (sec->cert_path); UA_ByteString private_key = load_file (sec->key_path); /* +1: UA_STACKARRAY requires a strictly positive size for VLA. */ UA_STACKARRAY (UA_ByteString, trust_list, sec->trust_size + 1); for (size_t i = 0; i < sec->trust_size; i++) trust_list[i] = load_file (sec->trust_paths[i]); UA_StatusCode retval = UA_ClientConfig_setDefaultEncryption ( cc, certificate, private_key, trust_list, sec->trust_size, NULL, 0); /* X509 identity token: reuse the application certificate. open62541 requires that the identity cert matches the SecureChannel cert, so a separate user cert cannot be used. Call before clearing the local buffers since setAuthenticationCert makes its own copy. */ if (retval == UA_STATUSCODE_GOOD && auth && auth->mode == AUTH_CERT) retval = UA_ClientConfig_setAuthenticationCert (cc, certificate, private_key); UA_ByteString_clear (&certificate); UA_ByteString_clear (&private_key); for (size_t i = 0; i < sec->trust_size; i++) UA_ByteString_clear (&trust_list[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 (application_uri); cc->securityMode = sec->security_mode; cc->securityPolicyUri = UA_String_fromChars (sec->security_policy_uri); if (auth && auth->mode == AUTH_USER) UA_ClientConfig_setAuthenticationUsername (cc, auth->user.username, auth->user.password); return retval; }