/** * @file server_lds.c * @brief Local Discovery Server implementation. * * This program runs an OPC UA Local Discovery Server (LDS) with a configurable * cleanup timeout. Encryption is optional: when certificate, privateKey, and * trustStore are provided, the server offers all security policies; otherwise * it runs with SecurityPolicy#None only. Other OPC UA servers register * with this LDS using the RegisterServer2 service. Clients can query this LDS * using the FindServers service to discover registered servers. */ #include "common.h" #include "config.h" #include #include #include #include #include #include #include UA_Boolean running = true; static void stopHandler (int sig) { UA_LOG_INFO (UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); running = false; } 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", "ServerLDS"); const char *applicationUri = configRequire (&cfg, "applicationUri", "ServerLDS"); int cleanupTimeout = configRequireInt (&cfg, "cleanupTimeout", "ServerLDS"); const char *authMode = configRequire (&cfg, "authMode", "ServerLDS"); if (!applicationUri || !authMode || port < 0 || cleanupTimeout < 0) { configFree (&cfg); return EXIT_FAILURE; } /* 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 *certPath = configGet (&cfg, "certificate"); const char *keyPath = configGet (&cfg, "privateKey"); const char *trustStore = configGet (&cfg, "trustStore"); UA_Boolean secure = (certPath != NULL || keyPath != NULL || trustStore != NULL); if (secure && (!certPath || !keyPath || !trustStore)) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Incomplete security config: certificate, privateKey, and " "trustStore must all be set, or all omitted"); configFree (&cfg); return EXIT_FAILURE; } /* The OPC UA specification requires the cleanup timeout to exceed the register-server interval. open62541 enforces a floor of 10 seconds. */ if (cleanupTimeout <= 10) { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Cleanup timeout must be > 10 seconds (got %d)", cleanupTimeout); configFree (&cfg); return EXIT_FAILURE; } UA_Boolean allowAnonymous; const char *username = NULL, *password = NULL; if (strcmp (authMode, "anonymous") == 0) { allowAnonymous = true; } else if (strcmp (authMode, "user") == 0) { allowAnonymous = false; username = configRequire (&cfg, "username", "ServerLDS"); password = configRequire (&cfg, "password", "ServerLDS"); if (!username || !password) { configFree (&cfg); return EXIT_FAILURE; } } else { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Unknown auth mode: %s " "(expected 'anonymous' or 'user')", authMode); configFree (&cfg); return EXIT_FAILURE; } char **trustPaths = NULL; size_t trustSize = 0; UA_StatusCode retval; UA_Server *server; if (secure) { if (loadTrustStore (trustStore, &trustPaths, &trustSize) != 0) { configFree (&cfg); return EXIT_FAILURE; } server = createSecureServer ((UA_UInt16)port, applicationUri, certPath, keyPath, trustPaths, trustSize, &retval); if (!server) { freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } } else { server = UA_Server_new (); UA_ServerConfig *config = UA_Server_getConfig (server); retval = UA_ServerConfig_setMinimal (config, (UA_UInt16)port, NULL); if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); configFree (&cfg); return EXIT_FAILURE; } UA_String_clear (&config->applicationDescription.applicationUri); config->applicationDescription.applicationUri = UA_String_fromChars (applicationUri); } UA_ServerConfig *serverConfig = UA_Server_getConfig (server); serverConfig->logging->context = (void *)(uintptr_t)logLevel; /* Some OPC UA stacks omit the timestamp in the request header. The default behaviour rejects these requests with BadInvalidTimestamp. Downgrade to a warning so third-party servers can still register. */ serverConfig->verifyRequestTimestamp = UA_RULEHANDLING_WARN; /* Configure access control after server creation because both UA_ServerConfig_setDefaultWithSecurityPolicies and UA_ServerConfig_setMinimal reset the access control plugin. The credential list is deep-copied by UA_AccessControl_default. */ if (!allowAnonymous) { UA_UsernamePasswordLogin logins[1]; logins[0].username = UA_STRING ((char *)username); logins[0].password = UA_STRING ((char *)password); retval = UA_AccessControl_default (serverConfig, false, NULL, 1, logins); if (retval != UA_STATUSCODE_GOOD) { UA_Server_delete (server); freeTrustStore (trustPaths, trustSize); configFree (&cfg); return EXIT_FAILURE; } } /* Mark this server as a Discovery Server so clients can identify it. */ serverConfig->applicationDescription.applicationType = UA_APPLICATIONTYPE_DISCOVERYSERVER; /* Time (seconds) after which stale registrations are removed. Must exceed the registering server's re-register interval. */ serverConfig->discoveryCleanupTimeout = cleanupTimeout; retval = UA_Server_run (server, &running); UA_Server_delete (server); freeTrustStore (trustPaths, trustSize); configFree (&cfg); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }