/** * @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 < 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; int port = configRequireInt (&cfg, "port", "ServerRegister"); const char *applicationUri = configRequire (&cfg, "applicationUri", "ServerRegister"); const char *serverCertPath = configRequire (&cfg, "serverCertificate", "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"); int registerInterval = configRequireInt (&cfg, "registerInterval", "ServerRegister"); const char *securityModeStr = configRequire (&cfg, "securityMode", "ServerRegister"); const char *securityPolicyStr = configRequire (&cfg, "securityPolicy", "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; } UA_MessageSecurityMode securityMode = parseSecurityMode (securityModeStr); if (securityMode == UA_MESSAGESECURITYMODE_INVALID) { 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); 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 (&cfg, "serverUsername", "ServerRegister"); serverPassword = configRequire (&cfg, "serverPassword", "ServerRegister"); if (!serverUsername || !serverPassword) { configFree (&cfg); return EXIT_FAILURE; } } else { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown server auth mode: %s " "(expected 'anonymous' or 'user')", serverAuthMode); configFree (&cfg); 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) { } else if (strcmp (clientAuthMode, "user") == 0) { clientUsername = configRequire (&cfg, "clientUsername", "ServerRegister"); clientPassword = configRequire (&cfg, "clientPassword", "ServerRegister"); if (!clientUsername || !clientPassword) { configFree (&cfg); return EXIT_FAILURE; } } else { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown client auth mode: %s " "(expected 'anonymous' or 'user')", clientAuthMode); configFree (&cfg); return EXIT_FAILURE; } char **trustPaths = NULL; size_t trustSize = 0; configGetAll (&cfg, "trustList", &trustPaths, &trustSize); UA_StatusCode retval; UA_Server *server = createSecureServer ((UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath, trustPaths, trustSize, &retval); if (!server) { free (trustPaths); configFree (&cfg); 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); free (trustPaths); configFree (&cfg); 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, applicationUri, clientCertPath, clientKeyPath, trustPaths, trustSize, securityMode, securityPolicyUri); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); UA_Server_delete (server); free (trustPaths); configFree (&cfg); 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, applicationUri, clientCertPath, clientKeyPath, trustPaths, trustSize, 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, applicationUri, clientCertPath, clientKeyPath, trustPaths, trustSize, 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); free (trustPaths); configFree (&cfg); return EXIT_SUCCESS; }