aboutsummaryrefslogtreecommitdiffstats
path: root/src/server_lds.c
blob: 99c1e8c2b689c29caece568ad788b533635bb469 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * @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 <open62541/plugin/accesscontrol_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>

#include <signal.h>
#include <stdlib.h>

volatile UA_Boolean running = true;

static void
stopHandler (int sig)
{
  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 <config-file> [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");

  if (!applicationUri || 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 (parseAuthConfig (&cfg, "ServerLDS", &allowAnonymous, &username,
                       &password)
      != 0)
    {
      configFree (&cfg);
      return EXIT_FAILURE;
    }

  char **trustPaths = NULL;
  size_t trustSize = 0;
  if (secure && loadTrustStore (trustStore, &trustPaths, &trustSize) != 0)
    {
      configFree (&cfg);
      return EXIT_FAILURE;
    }

  UA_StatusCode retval;
  UA_Server *server
      = createServer ((UA_UInt16)port, applicationUri, certPath, keyPath,
                      trustPaths, trustSize, true, &retval);
  if (!server)
    {
      freeTrustStore (trustPaths, trustSize);
      configFree (&cfg);
      return EXIT_FAILURE;
    }

  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.  UA_ServerConfig_setDefaultWithSecure-
     SecurityPolicies sets certificate-only auth by default, so we must
     always call UA_AccessControl_default to get the desired policy. */
  if (allowAnonymous)
    {
      retval = UA_AccessControl_default (serverConfig, true, NULL, 0, NULL);
    }
  else
    {
      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;
}