/** * @file server_register.c * @brief OPC UA Server that registers with a Local Discovery Server. * * This program runs an OPC UA server configured with security and periodically * registers itself with a remote LDS using the RegisterServer2 service. It * uses separate certificate pairs for the server and for the client connection * to the LDS. On shutdown, it deregisters from the LDS. */ #include "common.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include UA_Boolean running = true; static void stopHandler (int sign) { UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); running = false; } /* ======================================================================== * Main * ======================================================================== */ int main (int argc, char **argv) { signal (SIGINT, stopHandler); signal (SIGTERM, stopHandler); if (argc < 4 || argc > 5) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Usage: %s " " [log-level]", argv[0]); return EXIT_FAILURE; } const char *discoveryEndpoint = 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; } /* ── Load server config ─────────────────────────────────────── */ Config serverCfg; if (configLoad (argv[1], &serverCfg) != 0) return EXIT_FAILURE; int port = configRequireInt (&serverCfg, "port", "ServerRegister"); const char *applicationUri = configRequire (&serverCfg, "applicationUri", "ServerRegister"); const char *serverCertPath = configRequire (&serverCfg, "certificate", "ServerRegister"); const char *serverKeyPath = configRequire (&serverCfg, "privateKey", "ServerRegister"); int registerInterval = configRequireInt (&serverCfg, "registerInterval", "ServerRegister"); const char *serverAuthMode = configRequire (&serverCfg, "authMode", "ServerRegister"); if (!applicationUri || !serverCertPath || !serverKeyPath || !serverAuthMode || port < 0 || registerInterval < 0) { configFree (&serverCfg); return EXIT_FAILURE; } /* Parse server-side auth mode (what clients connecting to this server need). "anonymous" allows unauthenticated sessions; "user" requires a username/password pair. */ UA_Boolean serverAllowAnonymous; const char *serverUsername = NULL, *serverPassword = NULL; if (strcmp (serverAuthMode, "anonymous") == 0) { serverAllowAnonymous = true; } else if (strcmp (serverAuthMode, "user") == 0) { serverAllowAnonymous = false; serverUsername = configRequire (&serverCfg, "username", "ServerRegister"); serverPassword = configRequire (&serverCfg, "password", "ServerRegister"); if (!serverUsername || !serverPassword) { configFree (&serverCfg); return EXIT_FAILURE; } } else { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown server auth mode: %s " "(expected 'anonymous' or 'user')", serverAuthMode); 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; } /* Parse client-side auth mode (how this server authenticates to the LDS when registering). */ const char *clientUsername = NULL, *clientPassword = NULL; if (strcmp (clientAuthMode, "anonymous") == 0) { /* No credentials needed. */ } else if (strcmp (clientAuthMode, "user") == 0) { clientUsername = configRequire (&clientCfg, "username", "ServerRegister"); clientPassword = configRequire (&clientCfg, "password", "ServerRegister"); if (!clientUsername || !clientPassword) { freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return EXIT_FAILURE; } } else { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown client auth mode: %s " "(expected 'anonymous' or 'user')", clientAuthMode); 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; } /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; UA_Server *server = createSecureServer ( (UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath, serverTrustPaths, serverTrustSize, &retval); if (!server) { freeTrustStore (clientTrustPaths, clientTrustSize); freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return EXIT_FAILURE; } UA_ServerConfig *serverConfig = UA_Server_getConfig (server); serverConfig->logging->context = (void *)(uintptr_t)logLevel; /* Configure access control after server creation because UA_ServerConfig_setDefaultWithSecurityPolicies (called by createSecureServer) resets the access control plugin. The credential list is deep-copied by UA_AccessControl_default. */ if (!serverAllowAnonymous) { UA_UsernamePasswordLogin logins[1]; logins[0].username = UA_STRING ((char *)serverUsername); logins[0].password = UA_STRING ((char *)serverPassword); retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins); if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); freeTrustStore (clientTrustPaths, clientTrustSize); freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return EXIT_FAILURE; } } serverConfig->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER; /* Use run_startup + manual event loop (instead of UA_Server_run) so we can periodically re-register with the LDS between iterations. */ UA_Server_run_startup (server); /* UA_Server_registerDiscovery consumes (clears) the client config, so a fresh zero-initialized config is needed for every call. */ UA_ClientConfig clientConfig; memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( &clientConfig, clientAppUri, clientCertPath, clientKeyPath, clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); UA_Server_delete (server); freeTrustStore (clientTrustPaths, clientTrustSize); freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return EXIT_FAILURE; } clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername (&clientConfig, clientUsername, clientPassword); UA_String discoveryUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_registerDiscovery (server, &clientConfig, discoveryUrl, UA_STRING_NULL); UA_String_clear (&discoveryUrl); if (retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Initial register failed: %s", UA_StatusCode_name (retval)); /* Periodic re-registration loop. Re-register with the LDS every registerInterval seconds so the LDS does not purge us. */ time_t lastRegister = time (NULL); while (running) { UA_Server_run_iterate (server, true); time_t now = time (NULL); if (now - lastRegister >= registerInterval) { memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig (&clientConfig, clientAppUri, clientCertPath, clientKeyPath, clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername ( &clientConfig, clientUsername, clientPassword); UA_String reregUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_registerDiscovery (server, &clientConfig, reregUrl, UA_STRING_NULL); UA_String_clear (&reregUrl); if (retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Re-register failed: %s", UA_StatusCode_name (retval)); } lastRegister = now; } } /* Deregister from the LDS before shutting down so the LDS removes our entry immediately rather than waiting for the cleanup timeout. */ memset (&clientConfig, 0, sizeof (UA_ClientConfig)); retval = createSecureClientConfig ( &clientConfig, clientAppUri, clientCertPath, clientKeyPath, clientTrustPaths, clientTrustSize, securityMode, securityPolicyUri); if (retval == UA_STATUSCODE_GOOD) { clientConfig.logging->context = (void *)(uintptr_t)logLevel; if (clientUsername) UA_ClientConfig_setAuthenticationUsername ( &clientConfig, clientUsername, clientPassword); UA_String deregUrl = UA_STRING_ALLOC (discoveryEndpoint); retval = UA_Server_deregisterDiscovery (server, &clientConfig, deregUrl); UA_String_clear (&deregUrl); if (retval != UA_STATUSCODE_GOOD) UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not unregister from discovery server: %s", UA_StatusCode_name (retval)); } UA_Server_run_shutdown (server); UA_Server_delete (server); freeTrustStore (clientTrustPaths, clientTrustSize); freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return EXIT_SUCCESS; }