/** * @file server_register.c * @brief OPC UA Server that registers with a Local Discovery Server. * * This program runs an OPC UA server that periodically registers itself with * a remote LDS using the RegisterServer2 service. Encryption is optional for * the server; the client connection to the LDS uses a separate certificate * pair. On shutdown, it deregisters from the LDS. */ #include "common.h" #include "config.h" #include #include #include #include #include #include #include #include #include volatile UA_Boolean running = true; static void stopHandler (int sign) { running = false; } /* ======================================================================== * LDS Client Config Helper * ======================================================================== */ /** * Parameters for building a client config that connects to the LDS. * Gathered once in main, then reused for every register/deregister call. */ typedef struct { const char *appUri; const char *certPath; const char *keyPath; char **trustPaths; size_t trustSize; UA_MessageSecurityMode securityMode; const char *securityPolicyUri; int logLevel; const char *username; const char *password; } LdsClientParams; /** * Builds a fresh UA_ClientConfig from LdsClientParams. * * UA_Server_registerDiscovery / deregisterDiscovery consume (clear) the * client config, so a new one must be created before every call. */ static UA_StatusCode makeLdsClientConfig (UA_ClientConfig *cc, const LdsClientParams *p) { memset (cc, 0, sizeof (UA_ClientConfig)); UA_StatusCode rv; if (p->certPath) { rv = createSecureClientConfig (cc, p->appUri, p->certPath, p->keyPath, p->trustPaths, p->trustSize, p->securityMode, p->securityPolicyUri); } else { rv = UA_ClientConfig_setDefault (cc); if (rv == UA_STATUSCODE_GOOD) { UA_String_clear (&cc->clientDescription.applicationUri); cc->clientDescription.applicationUri = UA_String_fromChars (p->appUri); } } if (rv != UA_STATUSCODE_GOOD) return rv; cc->logging->context = (void *)(uintptr_t)p->logLevel; if (p->username) UA_ClientConfig_setAuthenticationUsername (cc, p->username, p->password); return UA_STATUSCODE_GOOD; } /* ======================================================================== * 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 ─────────────────────────────────────── */ int rc = EXIT_FAILURE; Config serverCfg = { 0 }; Config clientCfg = { 0 }; char **serverTrustPaths = NULL; size_t serverTrustSize = 0; char **clientTrustPaths = NULL; size_t clientTrustSize = 0; UA_Server *server = NULL; if (configLoad (argv[1], &serverCfg) != 0) goto cleanup; int port = configRequireInt (&serverCfg, "port", "ServerRegister"); const char *applicationUri = configRequire (&serverCfg, "applicationUri", "ServerRegister"); int registerInterval = configRequireInt (&serverCfg, "registerInterval", "ServerRegister"); if (!applicationUri || port < 0 || registerInterval < 0) goto cleanup; /* Security configuration (optional). When certificate, privateKey, and trustStore are all omitted the server runs with SecurityPolicy#None only. When any of the three is present, all three are required. */ const char *serverCertPath = configGet (&serverCfg, "certificate"); const char *serverKeyPath = configGet (&serverCfg, "privateKey"); const char *serverTrustStore = configGet (&serverCfg, "trustStore"); UA_Boolean serverSecure = (serverCertPath != NULL || serverKeyPath != NULL || serverTrustStore != NULL); if (serverSecure && (!serverCertPath || !serverKeyPath || !serverTrustStore)) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Incomplete server security config: certificate, " "privateKey, and trustStore must all be set, or all " "omitted"); goto cleanup; } UA_Boolean serverAllowAnonymous; const char *serverUsername = NULL, *serverPassword = NULL; if (parseAuthConfig (&serverCfg, "ServerRegister", &serverAllowAnonymous, &serverUsername, &serverPassword) != 0) goto cleanup; if (serverSecure && loadTrustStore (serverTrustStore, &serverTrustPaths, &serverTrustSize) != 0) goto cleanup; /* ── Load client config ─────────────────────────────────────── */ if (configLoad (argv[2], &clientCfg) != 0) goto cleanup; const char *clientAppUri = configRequire (&clientCfg, "applicationUri", "ServerRegister"); if (!clientAppUri) goto cleanup; /* Security configuration (optional). When certificate, privateKey, and trustStore are all omitted the client connects without encryption. When any of the three is present, all three are required. */ const char *clientCertPath = configGet (&clientCfg, "certificate"); const char *clientKeyPath = configGet (&clientCfg, "privateKey"); const char *clientTrustStore = configGet (&clientCfg, "trustStore"); UA_Boolean clientSecure = (clientCertPath != NULL || clientKeyPath != NULL || clientTrustStore != NULL); if (clientSecure && (!clientCertPath || !clientKeyPath || !clientTrustStore)) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Incomplete client security config: certificate, " "privateKey, and trustStore must all be set, or all " "omitted"); goto cleanup; } UA_MessageSecurityMode securityMode = UA_MESSAGESECURITYMODE_NONE; const char *securityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None"; if (clientSecure) { const char *securityModeStr = configRequire (&clientCfg, "securityMode", "ServerRegister"); const char *securityPolicyStr = configRequire (&clientCfg, "securityPolicy", "ServerRegister"); if (!securityModeStr || !securityPolicyStr) goto cleanup; securityMode = parseSecurityMode (securityModeStr); if (securityMode == UA_MESSAGESECURITYMODE_INVALID) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown security mode: %s", securityModeStr); goto cleanup; } securityPolicyUri = resolveSecurityPolicyUri (securityPolicyStr); if (!securityPolicyUri) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown security policy: %s", securityPolicyStr); goto cleanup; } if (loadTrustStore (clientTrustStore, &clientTrustPaths, &clientTrustSize) != 0) goto cleanup; } const char *clientUsername = NULL, *clientPassword = NULL; if (parseAuthConfig (&clientCfg, "ServerRegister", NULL, &clientUsername, &clientPassword) != 0) goto cleanup; /* ── Create and configure server ────────────────────────────── */ UA_StatusCode retval; server = createServer ((UA_UInt16)port, applicationUri, serverCertPath, serverKeyPath, serverTrustPaths, serverTrustSize, false, &retval); if (!server) goto cleanup; UA_ServerConfig *serverConfig = UA_Server_getConfig (server); serverConfig->logging->context = (void *)(uintptr_t)logLevel; /* Configure access control. UA_ServerConfig_setDefaultWithSecure- SecurityPolicies sets certificate-only auth by default, so we must always call UA_AccessControl_default to get the desired policy. */ if (serverAllowAnonymous) { retval = UA_AccessControl_default (serverConfig, true, NULL, 0, NULL); } else { 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) goto cleanup; serverConfig->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER; LdsClientParams ldsParams = { .appUri = clientAppUri, .certPath = clientCertPath, .keyPath = clientKeyPath, .trustPaths = clientTrustPaths, .trustSize = clientTrustSize, .securityMode = securityMode, .securityPolicyUri = securityPolicyUri, .logLevel = logLevel, .username = clientUsername, .password = clientPassword, }; /* 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 makeLdsClientConfig builds a fresh one for every call. */ UA_ClientConfig clientConfig; retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval != UA_STATUSCODE_GOOD) { UA_Server_run_shutdown (server); goto cleanup; } 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) { retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval == UA_STATUSCODE_GOOD) { 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. */ retval = makeLdsClientConfig (&clientConfig, &ldsParams); if (retval == UA_STATUSCODE_GOOD) { 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); rc = EXIT_SUCCESS; cleanup: if (server) UA_Server_delete (server); freeTrustStore (clientTrustPaths, clientTrustSize); freeTrustStore (serverTrustPaths, serverTrustSize); configFree (&clientCfg); configFree (&serverCfg); return rc; }