/src/open62541_15/src/server/ua_services_discovery.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2014-2016 (c) Sten GrĂ¼ner |
7 | | * Copyright 2014, 2017 (c) Florian Palm |
8 | | * Copyright 2016 (c) Oleksiy Vasylyev |
9 | | * Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH |
10 | | * Copyright 2017 (c) frax2222 |
11 | | * Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB |
12 | | * Copyright 2026 (c) o6 Automation GmbH (Author: Andreas Ebner) |
13 | | */ |
14 | | |
15 | | #include "ua_server_internal.h" |
16 | | #include "ua_discovery.h" |
17 | | #include "ua_services.h" |
18 | | |
19 | | #ifdef UA_ENABLE_DISCOVERY |
20 | | |
21 | | #include <open62541/client.h> |
22 | | |
23 | | static UA_StatusCode |
24 | | setApplicationDescriptionFromRegisteredServer(const UA_FindServersRequest *request, |
25 | | UA_ApplicationDescription *target, |
26 | 0 | const UA_RegisteredServer *rs) { |
27 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
28 | 0 | target->applicationType = rs->serverType; |
29 | 0 | retval |= UA_String_copy(&rs->serverUri, &target->applicationUri); |
30 | 0 | retval |= UA_String_copy(&rs->productUri, &target->productUri); |
31 | 0 | retval |= UA_String_copy(&rs->gatewayServerUri, &target->gatewayServerUri); |
32 | 0 | if(retval != UA_STATUSCODE_GOOD) |
33 | 0 | return retval; |
34 | | |
35 | | /* If the client requests a specific locale, select the corresponding server |
36 | | * name */ |
37 | 0 | if(request->localeIdsSize) { |
38 | 0 | UA_Boolean appNameFound = false; |
39 | 0 | for(size_t i = 0; i < request->localeIdsSize && !appNameFound; i++) { |
40 | 0 | for(size_t j =0; j < rs->serverNamesSize; j++) { |
41 | 0 | if(UA_String_equal(&request->localeIds[i], |
42 | 0 | &rs->serverNames[j].locale)) { |
43 | 0 | retval = UA_LocalizedText_copy(&rs->serverNames[j], |
44 | 0 | &target->applicationName); |
45 | 0 | if(retval != UA_STATUSCODE_GOOD) |
46 | 0 | return retval; |
47 | 0 | appNameFound = true; |
48 | 0 | break; |
49 | 0 | } |
50 | 0 | } |
51 | 0 | } |
52 | | |
53 | | /* Server does not have the requested local, therefore we can select the |
54 | | * most suitable one */ |
55 | 0 | if(!appNameFound && rs->serverNamesSize) { |
56 | 0 | retval = UA_LocalizedText_copy(&rs->serverNames[0], |
57 | 0 | &target->applicationName); |
58 | 0 | if(retval != UA_STATUSCODE_GOOD) |
59 | 0 | return retval; |
60 | 0 | } |
61 | 0 | } else if(rs->serverNamesSize) { |
62 | | /* Just take the first name */ |
63 | 0 | retval = UA_LocalizedText_copy(&rs->serverNames[0], |
64 | 0 | &target->applicationName); |
65 | 0 | if(retval != UA_STATUSCODE_GOOD) |
66 | 0 | return retval; |
67 | 0 | } |
68 | | |
69 | | /* TODO: Where do we get the discoveryProfileUri for application data? */ |
70 | | |
71 | 0 | if(rs->discoveryUrlsSize > 0) { |
72 | 0 | target->discoveryUrls = (UA_String *) |
73 | 0 | UA_calloc(rs->discoveryUrlsSize, sizeof(UA_String)); |
74 | 0 | if(!target->discoveryUrls) |
75 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
76 | 0 | target->discoveryUrlsSize = rs->discoveryUrlsSize; |
77 | 0 | for(size_t i = 0; i < rs->discoveryUrlsSize; i++) |
78 | 0 | retval |= UA_String_copy(&rs->discoveryUrls[i], |
79 | 0 | &target->discoveryUrls[i]); |
80 | 0 | } |
81 | | |
82 | 0 | return retval; |
83 | 0 | } |
84 | | #endif |
85 | | |
86 | | UA_Boolean |
87 | | Service_FindServers(UA_Server *server, UA_Session *session, |
88 | | const UA_FindServersRequest *request, |
89 | 46 | UA_FindServersResponse *response) { |
90 | 46 | UA_ServerConfig *sc = &server->config; |
91 | 46 | UA_LOG_DEBUG_SESSION(sc->logging, session, "Processing FindServersRequest"); |
92 | 46 | UA_LOCK_ASSERT(&server->serviceMutex); |
93 | | |
94 | | /* Return the server itself? */ |
95 | 46 | UA_Boolean foundSelf = false; |
96 | 46 | if(request->serverUrisSize) { |
97 | 1.70k | for(size_t i = 0; i < request->serverUrisSize; i++) { |
98 | 1.66k | if(UA_String_equal(&request->serverUris[i], |
99 | 1.66k | &sc->applicationDescription.applicationUri)) { |
100 | 0 | foundSelf = true; |
101 | 0 | break; |
102 | 0 | } |
103 | 1.66k | } |
104 | 31 | } else { |
105 | 15 | foundSelf = true; |
106 | 15 | } |
107 | | |
108 | | #ifndef UA_ENABLE_DISCOVERY |
109 | | if(!foundSelf) |
110 | | return true; |
111 | | |
112 | | response->responseHeader.serviceResult = |
113 | | UA_Array_copy(&sc->applicationDescription, 1, (void**)&response->servers, |
114 | | &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); |
115 | | if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) |
116 | | return true; |
117 | | |
118 | | response->serversSize = 1; |
119 | | #else |
120 | 46 | UA_DiscoveryManager *dm = (UA_DiscoveryManager*) |
121 | 46 | getServerComponentByName(server, UA_STRING("discovery")); |
122 | 46 | if(!dm) { |
123 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
124 | 0 | return true; |
125 | 0 | } |
126 | | |
127 | | /* Allocate enough memory, including memory for the "self" response */ |
128 | 46 | size_t maxResults = dm->registeredServersSize + 1; |
129 | 46 | response->servers = (UA_ApplicationDescription*) |
130 | 46 | UA_Array_new(maxResults, &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); |
131 | 46 | if(!response->servers) { |
132 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
133 | 0 | return true; |
134 | 0 | } |
135 | | |
136 | 46 | size_t pos = 0; |
137 | 46 | registeredServer *current; |
138 | | |
139 | | /* Copy "self" ApplicationDescriptions into the response */ |
140 | 46 | if(foundSelf) { |
141 | 15 | response->responseHeader.serviceResult = |
142 | 15 | UA_ApplicationDescription_copy(&sc->applicationDescription, |
143 | 15 | &response->servers[pos]); |
144 | 15 | pos++; |
145 | 15 | if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) |
146 | 0 | goto cleanup; |
147 | 15 | } |
148 | | |
149 | | /* Copy registered ApplicationDescriptions into the response */ |
150 | 46 | LIST_FOREACH(current, &dm->registeredServers, pointers) { |
151 | 0 | UA_Boolean usable = (request->serverUrisSize == 0); |
152 | 0 | if(!usable) { |
153 | | /* If client only requested a specific set of servers */ |
154 | 0 | for(size_t i = 0; i < request->serverUrisSize; i++) { |
155 | 0 | if(UA_String_equal(¤t->registeredServer.serverUri, |
156 | 0 | &request->serverUris[i])) { |
157 | 0 | usable = true; |
158 | 0 | break; |
159 | 0 | } |
160 | 0 | } |
161 | 0 | } |
162 | |
|
163 | 0 | if(!usable) |
164 | 0 | continue; |
165 | | |
166 | 0 | response->responseHeader.serviceResult |= |
167 | 0 | setApplicationDescriptionFromRegisteredServer(request, |
168 | 0 | &response->servers[pos], |
169 | 0 | ¤t->registeredServer); |
170 | 0 | pos++; |
171 | 0 | } |
172 | | |
173 | 46 | cleanup: |
174 | | |
175 | | /* Set the final size */ |
176 | 46 | if(pos == 0) { |
177 | 31 | UA_free(response->servers); |
178 | 31 | response->servers = NULL; |
179 | 31 | } |
180 | 46 | response->serversSize = pos; |
181 | 46 | #endif |
182 | | |
183 | | /* Mirror back the expected EndpointUrl */ |
184 | 46 | if(request->endpointUrl.length > 0) { |
185 | 24 | for(size_t i = 0; i < response->serversSize; i++) { |
186 | 11 | UA_ApplicationDescription *ad = &response->servers[i]; |
187 | 11 | UA_Array_delete(ad->discoveryUrls, ad->discoveryUrlsSize, |
188 | 11 | &UA_TYPES[UA_TYPES_STRING]); |
189 | 11 | ad->discoveryUrls = NULL; |
190 | 11 | ad->discoveryUrlsSize = 0; |
191 | 11 | response->responseHeader.serviceResult = |
192 | 11 | UA_Array_copy(&request->endpointUrl, 1, |
193 | 11 | (void**)&ad->discoveryUrls, |
194 | 11 | &UA_TYPES[UA_TYPES_STRING]); |
195 | 11 | if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) |
196 | 0 | break; |
197 | 11 | ad->discoveryUrlsSize = 1; |
198 | 11 | } |
199 | 13 | } |
200 | | |
201 | 46 | return true; |
202 | 46 | } |
203 | | |
204 | | #if defined(UA_ENABLE_DISCOVERY) && defined(UA_ENABLE_DISCOVERY_MULTICAST) |
205 | | /* All filter criteria must be fulfilled in the list entry. The comparison is |
206 | | * case insensitive. Returns true if the entry matches the filter. */ |
207 | | static UA_Boolean |
208 | | entryMatchesCapabilityFilter(size_t serverCapabilityFilterSize, |
209 | | UA_String *serverCapabilityFilter, |
210 | 0 | UA_ServerOnNetwork *current) { |
211 | | /* If the entry has less capabilities defined than the filter, there's no match */ |
212 | 0 | if(serverCapabilityFilterSize > current->serverCapabilitiesSize) |
213 | 0 | return false; |
214 | 0 | for(size_t i = 0; i < serverCapabilityFilterSize; i++) { |
215 | 0 | UA_Boolean capabilityFound = false; |
216 | 0 | for(size_t j = 0; j < current->serverCapabilitiesSize; j++) { |
217 | 0 | if(UA_String_equal_ignorecase(&serverCapabilityFilter[i], |
218 | 0 | ¤t->serverCapabilities[j])) { |
219 | 0 | capabilityFound = true; |
220 | 0 | break; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | if(!capabilityFound) |
224 | 0 | return false; |
225 | 0 | } |
226 | 0 | return true; |
227 | 0 | } |
228 | | |
229 | | UA_Boolean |
230 | | Service_FindServersOnNetwork(UA_Server *server, UA_Session *session, |
231 | | const UA_FindServersOnNetworkRequest *request, |
232 | 2 | UA_FindServersOnNetworkResponse *response) { |
233 | 2 | UA_LOCK_ASSERT(&server->serviceMutex); |
234 | | |
235 | 2 | UA_DiscoveryManager *dm = (UA_DiscoveryManager*) |
236 | 2 | getServerComponentByName(server, UA_STRING("discovery")); |
237 | 2 | if(!dm) { |
238 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
239 | 0 | return true; |
240 | 0 | } |
241 | | |
242 | 2 | if(!server->config.mdnsEnabled) { |
243 | 2 | response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTIMPLEMENTED; |
244 | 2 | return true; |
245 | 2 | } |
246 | | |
247 | | /* Set LastCounterResetTime */ |
248 | 0 | response->lastCounterResetTime = |
249 | 0 | UA_DiscoveryManager_getServerOnNetworkCounterResetTime(dm); |
250 | | |
251 | | /* Compute the max number of records to return */ |
252 | 0 | UA_UInt32 recordCount = 0; |
253 | 0 | UA_UInt32 serverOnNetworkRecordIdCounter = |
254 | 0 | UA_DiscoveryManager_getServerOnNetworkRecordIdCounter(dm); |
255 | 0 | if(request->startingRecordId < serverOnNetworkRecordIdCounter) |
256 | 0 | recordCount = serverOnNetworkRecordIdCounter - request->startingRecordId; |
257 | 0 | if(request->maxRecordsToReturn && recordCount > request->maxRecordsToReturn) |
258 | 0 | recordCount = UA_MIN(recordCount, request->maxRecordsToReturn); |
259 | 0 | if(recordCount == 0) { |
260 | 0 | response->serversSize = 0; |
261 | 0 | return true; |
262 | 0 | } |
263 | | |
264 | | /* Iterate over all records and add to filtered list */ |
265 | 0 | UA_UInt32 filteredCount = 0; |
266 | 0 | UA_STACKARRAY(UA_ServerOnNetwork*, filtered, recordCount); |
267 | 0 | UA_ServerOnNetwork *current = UA_DiscoveryManager_getServerOnNetworkList(dm); |
268 | 0 | if(!current) { |
269 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
270 | 0 | return true; |
271 | 0 | } |
272 | 0 | for(size_t i = 0; i < recordCount; i++) { |
273 | 0 | if(filteredCount >= recordCount) |
274 | 0 | break; |
275 | 0 | if(current->recordId < request->startingRecordId) |
276 | 0 | continue; |
277 | 0 | if(!entryMatchesCapabilityFilter(request->serverCapabilityFilterSize, |
278 | 0 | request->serverCapabilityFilter, current)) |
279 | 0 | continue; |
280 | 0 | filtered[filteredCount++] = current; |
281 | 0 | current = UA_DiscoveryManager_getNextServerOnNetworkRecord(dm, current); |
282 | 0 | if(!current) |
283 | 0 | break; |
284 | 0 | } |
285 | |
|
286 | 0 | if(filteredCount == 0) |
287 | 0 | return true; |
288 | | |
289 | | /* Allocate the array for the response */ |
290 | 0 | response->servers = (UA_ServerOnNetwork*) |
291 | 0 | UA_malloc(sizeof(UA_ServerOnNetwork)*filteredCount); |
292 | 0 | if(!response->servers) { |
293 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
294 | 0 | return true; |
295 | 0 | } |
296 | 0 | response->serversSize = filteredCount; |
297 | | |
298 | | /* Copy the server names */ |
299 | 0 | for(size_t i = 0; i < filteredCount; i++) { |
300 | 0 | UA_ServerOnNetwork_copy(filtered[i], &response->servers[filteredCount-i-1]); |
301 | 0 | } |
302 | 0 | return true; |
303 | 0 | } |
304 | | #endif |
305 | | |
306 | | static UA_String basic256Sha256Uri = UA_STRING_STATIC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"); |
307 | | |
308 | | /* Get an encrypted policy or NULL if no encrypted policy is defined */ |
309 | | UA_SecurityPolicy * |
310 | | getDefaultEncryptedSecurityPolicy(UA_Server *server, |
311 | 7 | UA_SecurityPolicyType type) { |
312 | 7 | UA_SecurityPolicy *best = NULL; |
313 | 7 | UA_Byte securityLevel = 0; |
314 | | |
315 | 14 | for(size_t i = 0; i < server->config.securityPoliciesSize; i++) { |
316 | 7 | UA_SecurityPolicy *sp = &server->config.securityPolicies[i]; |
317 | 7 | if(sp->policyType == UA_SECURITYPOLICYTYPE_NONE) |
318 | 7 | continue; |
319 | 0 | if(sp->policyType == UA_SECURITYPOLICYTYPE_RSA && |
320 | 0 | type == UA_SECURITYPOLICYTYPE_ECC) |
321 | 0 | continue; |
322 | 0 | if(sp->policyType == UA_SECURITYPOLICYTYPE_ECC && |
323 | 0 | type == UA_SECURITYPOLICYTYPE_RSA) |
324 | 0 | continue; |
325 | | /* Return early with Basic256Sha256 when available. "Secure enough" and |
326 | | * most clients support it.*/ |
327 | 0 | if(UA_String_equal(&basic256Sha256Uri, &sp->policyUri)) |
328 | 0 | return sp; |
329 | 0 | if(sp->securityLevel >= securityLevel) { |
330 | 0 | best = sp; |
331 | 0 | securityLevel = sp->securityLevel; |
332 | 0 | } |
333 | 0 | } |
334 | 7 | return best; |
335 | 7 | } |
336 | | |
337 | | static const char *securityModeStrs[4] = {"-invalid", "-none", "-sign", "-sign+encrypt"}; |
338 | | |
339 | | UA_String |
340 | 7 | securityPolicyUriPostfix(const UA_String uri) { |
341 | 35 | for(UA_Byte *b = uri.data + uri.length - 1; b >= uri.data; b--) { |
342 | 35 | if(*b != '#') |
343 | 28 | continue; |
344 | 7 | UA_String postfix = {uri.length - (size_t)(b - uri.data), b}; |
345 | 7 | return postfix; |
346 | 35 | } |
347 | 0 | return uri; |
348 | 7 | } |
349 | | |
350 | | static UA_StatusCode |
351 | | updateEndpointUserIdentityToken(UA_Server *server, |
352 | | UA_SecurityPolicyType policyType, |
353 | 7 | UA_EndpointDescription *ed) { |
354 | | /* Don't modify the UserIdentityTokens if there are manually configured |
355 | | * entries */ |
356 | 7 | if(ed->userIdentityTokensSize > 0) |
357 | 0 | return UA_STATUSCODE_GOOD; |
358 | | |
359 | | /* Copy the UserTokenPolicies from the AccessControl plugin, but only the |
360 | | * matching ones to the securityPolicyUri. |
361 | | * TODO: Different instances of the AccessControl plugin per Endpoint */ |
362 | 7 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
363 | 7 | UA_ServerConfig *sc = &server->config; |
364 | 21 | for(size_t i = 0; i < sc->accessControl.userTokenPoliciesSize; i++) { |
365 | 14 | UA_UserTokenPolicy *utp = &sc->accessControl.userTokenPolicies[i]; |
366 | | |
367 | | /* Append the UserTokenPolicy from the AccesssControl plugin */ |
368 | 14 | res = UA_Array_appendCopy((void**)&ed->userIdentityTokens, |
369 | 14 | &ed->userIdentityTokensSize, utp, |
370 | 14 | &UA_TYPES[UA_TYPES_USERTOKENPOLICY]); |
371 | 14 | if(res != UA_STATUSCODE_GOOD) |
372 | 0 | return res; |
373 | | |
374 | | /* Now we modify the freshly copied last entry and ignore whatever |
375 | | * SecurityPolicy was set in sc->accessControl.userTokenPolicies and |
376 | | * choose something appropriate. If empty, the SecurityPolicy of the |
377 | | * SecureChannel is used. */ |
378 | 14 | utp = &ed->userIdentityTokens[ed->userIdentityTokensSize - 1]; |
379 | 14 | UA_String_clear(&utp->securityPolicyUri); |
380 | | |
381 | 14 | #ifdef UA_ENABLE_ENCRYPTION |
382 | | /* Anonymous tokens don't need encryption. All other tokens require |
383 | | * encryption with the exception of Username/Password if also the |
384 | | * allowNonePolicyPassword option has been set. The same logic is used |
385 | | * in selectEndpointAndTokenPolicy (ua_services_session.c). */ |
386 | 14 | if(utp->tokenType != UA_USERTOKENTYPE_ANONYMOUS && |
387 | 7 | UA_String_equal(&ed->securityPolicyUri, &UA_SECURITY_POLICY_NONE_URI) && |
388 | 7 | (!sc->allowNonePolicyPassword || utp->tokenType != UA_USERTOKENTYPE_USERNAME)) { |
389 | | /* Use the SecurityPolicy for the SecureChannel also for the |
390 | | * username/password. Otherwise pick the "bĂ«st" SecurityPolicĂ¿. */ |
391 | 7 | UA_SecurityPolicy *encSP; |
392 | 7 | if(ed->securityMode == UA_MESSAGESECURITYMODE_NONE) |
393 | 7 | encSP = getDefaultEncryptedSecurityPolicy(server, policyType); |
394 | 0 | else |
395 | 0 | encSP = getSecurityPolicyByUri(server, &ed->securityPolicyUri); |
396 | 7 | if(!encSP) { |
397 | | /* No encrypted SecurityPolicy available */ |
398 | 7 | UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT, |
399 | 7 | "Removing a UserTokenPolicy that would allow the " |
400 | 7 | "password to be transmitted without encryption " |
401 | 7 | "(Can be enabled via config->allowNonePolicyPassword)"); |
402 | 7 | UA_StatusCode res2 = |
403 | 7 | UA_Array_resize((void **)&ed->userIdentityTokens, |
404 | 7 | &ed->userIdentityTokensSize, |
405 | 7 | ed->userIdentityTokensSize - 1, |
406 | 7 | &UA_TYPES[UA_TYPES_USERTOKENPOLICY]); |
407 | 7 | (void)res2; |
408 | 7 | continue; |
409 | 7 | } |
410 | 0 | res |= UA_String_copy(&encSP->policyUri, &utp->securityPolicyUri); |
411 | 0 | } |
412 | 7 | #endif |
413 | | |
414 | | /* Append the SecurityMode and SecurityPolicy postfix to the PolicyId to |
415 | | * make it unique */ |
416 | 7 | UA_String postfix; |
417 | 7 | if(utp->securityPolicyUri.length > 0) |
418 | 0 | postfix = securityPolicyUriPostfix(utp->securityPolicyUri); |
419 | 7 | else |
420 | 7 | postfix = securityPolicyUriPostfix(ed->securityPolicyUri); |
421 | 7 | size_t newLen = utp->policyId.length + postfix.length + |
422 | 7 | strlen(securityModeStrs[ed->securityMode]); |
423 | 7 | UA_Byte *newString = (UA_Byte*)UA_realloc(utp->policyId.data, newLen); |
424 | 7 | if(!newString) |
425 | 0 | continue; |
426 | 7 | size_t pos = utp->policyId.length; |
427 | 7 | memcpy(&newString[pos], securityModeStrs[ed->securityMode], |
428 | 7 | strlen(securityModeStrs[ed->securityMode])); |
429 | 7 | pos += strlen(securityModeStrs[ed->securityMode]); |
430 | 7 | memcpy(&newString[pos], postfix.data, postfix.length); |
431 | 7 | utp->policyId.data = newString; |
432 | 7 | utp->policyId.length = newLen; |
433 | 7 | } |
434 | | |
435 | 7 | return res; |
436 | 7 | } |
437 | | |
438 | | /* Also reused to create the EndpointDescription array in the |
439 | | * CreateSessionResponse */ |
440 | | UA_StatusCode |
441 | | setCurrentEndPointsArray(UA_Server *server, UA_SecureChannel *channel, |
442 | | const UA_String endpointUrl, |
443 | | UA_String *profileUris, size_t profileUrisSize, |
444 | 20 | UA_EndpointDescription **arr, size_t *arrSize) { |
445 | 20 | UA_ServerConfig *sc = &server->config; |
446 | | |
447 | | /* Clone the endpoint for each discoveryURL? */ |
448 | 20 | size_t clone_times = 1; |
449 | 20 | if(endpointUrl.length == 0) |
450 | 11 | clone_times = sc->applicationDescription.discoveryUrlsSize; |
451 | | |
452 | | /* Allocate the array */ |
453 | 20 | *arr = (UA_EndpointDescription*) |
454 | 20 | UA_Array_new(sc->endpointsSize * clone_times, |
455 | 20 | &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); |
456 | 20 | if(!*arr) |
457 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
458 | | |
459 | 20 | size_t pos = 0; |
460 | 20 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
461 | 40 | for(size_t j = 0; j < sc->endpointsSize; ++j) { |
462 | 20 | const UA_EndpointDescription *ep = &sc->endpoints[j]; |
463 | | |
464 | | /* Test if the supported binary profile shall be returned */ |
465 | 20 | UA_Boolean usable = (profileUrisSize == 0); |
466 | 20 | if(!usable) { |
467 | 295 | for(size_t i = 0; i < profileUrisSize; ++i) { |
468 | 284 | if(!UA_String_equal(&profileUris[i], &ep->transportProfileUri)) |
469 | 284 | continue; |
470 | 0 | usable = true; |
471 | 0 | break; |
472 | 284 | } |
473 | 11 | } |
474 | 20 | if(!usable) |
475 | 11 | continue; |
476 | | |
477 | | /* Get the SecurityPolicy */ |
478 | 9 | UA_SecurityPolicy *sp = |
479 | 9 | getSecurityPolicyByUri(server, &ep->securityPolicyUri); |
480 | 9 | if(!sp) { |
481 | 0 | UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER, |
482 | 0 | "GetEndpoints: Endpoint defines SecurityPolicy " |
483 | 0 | "%S which is not available", ep->securityPolicyUri); |
484 | 0 | continue; |
485 | 0 | } |
486 | | |
487 | | /* Coming from CreateSession we have a channel already. Only return |
488 | | * Endpoints where SecurityPolicy is an exact match. */ |
489 | 9 | if(channel && channel->securityPolicy != sp) |
490 | 0 | continue; |
491 | | |
492 | | /* Copy into the results */ |
493 | 16 | for(size_t i = 0; i < clone_times; ++i) { |
494 | | /* Copy the endpoint with a current ApplicationDescription */ |
495 | 7 | UA_EndpointDescription *ed = &(*arr)[pos]; |
496 | 7 | retval |= UA_EndpointDescription_copy(&sc->endpoints[j], ed); |
497 | 7 | UA_ApplicationDescription_clear(&ed->server); |
498 | 7 | retval |= UA_ApplicationDescription_copy(&sc->applicationDescription, |
499 | 7 | &ed->server); |
500 | | |
501 | | /* Set the local certificate configured for the SecurityPolicy */ |
502 | 7 | UA_ByteString_clear(&ed->serverCertificate); |
503 | 7 | retval |= UA_ByteString_copy(&sp->localCertificate, |
504 | 7 | &ed->serverCertificate); |
505 | | |
506 | | /* Set the User Identity Token list from the AccessControl plugin. |
507 | | * This also selects an appropriate SecurityPolicy for the |
508 | | * AuthenticationToken. */ |
509 | 7 | retval |= updateEndpointUserIdentityToken(server, sp->policyType, ed); |
510 | | |
511 | | /* OPC UA Part 4 §5.4.2: |
512 | | * |
513 | | * If the endpoint uses None security but a token policy requires |
514 | | * encryption, the client needs a certificate to encrypt the token. |
515 | | * Set serverCertificate from the first token policy's encryption |
516 | | * SecurityPolicy so the client can encrypt the credential. */ |
517 | 7 | if(ed->serverCertificate.length == 0) { |
518 | 14 | for(size_t ti = 0; ti < ed->userIdentityTokensSize; ti++) { |
519 | 7 | UA_UserTokenPolicy *utp = &ed->userIdentityTokens[ti]; |
520 | 7 | if(utp->securityPolicyUri.length == 0) |
521 | 7 | continue; |
522 | 0 | UA_SecurityPolicy *encSP = |
523 | 0 | getSecurityPolicyByUri(server, &utp->securityPolicyUri); |
524 | 0 | if(!encSP || encSP->localCertificate.length == 0) |
525 | 0 | continue; |
526 | 0 | retval |= UA_ByteString_copy(&encSP->localCertificate, |
527 | 0 | &ed->serverCertificate); |
528 | 0 | break; |
529 | 0 | } |
530 | 7 | } |
531 | | |
532 | | /* Set the EndpointURL */ |
533 | 7 | UA_String_clear(&ed->endpointUrl); |
534 | 7 | if(endpointUrl.length == 0) { |
535 | 0 | retval |= UA_String_copy(&sc->applicationDescription.discoveryUrls[i], |
536 | 0 | &ed->endpointUrl); |
537 | 7 | } else { |
538 | | /* Mirror back the requested EndpointUrl and also add it to the |
539 | | * array of discovery urls */ |
540 | 7 | retval |= UA_String_copy(&endpointUrl, &ed->endpointUrl); |
541 | | |
542 | | /* Check if the ServerUrl is already present in the DiscoveryUrl |
543 | | * array */ |
544 | 7 | size_t k = 0; |
545 | 7 | for(; k < ed->server.discoveryUrlsSize; k++) { |
546 | 0 | if(UA_String_equal(&ed->endpointUrl, &ed->server.discoveryUrls[k])) |
547 | 0 | break; |
548 | 0 | } |
549 | 7 | if(k == ed->server.discoveryUrlsSize) { |
550 | 7 | retval |= UA_Array_appendCopy((void **)&ed->server.discoveryUrls, |
551 | 7 | &ed->server.discoveryUrlsSize, |
552 | 7 | &endpointUrl, |
553 | 7 | &UA_TYPES[UA_TYPES_STRING]); |
554 | 7 | } |
555 | 7 | } |
556 | 7 | if(retval != UA_STATUSCODE_GOOD) |
557 | 0 | goto error; |
558 | | |
559 | 7 | pos++; |
560 | 7 | } |
561 | 9 | } |
562 | | |
563 | 20 | *arrSize = pos; |
564 | 20 | return UA_STATUSCODE_GOOD; |
565 | | |
566 | 0 | error: |
567 | 0 | UA_Array_delete(*arr, sc->endpointsSize * clone_times, |
568 | 0 | &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); |
569 | 0 | *arr = NULL; |
570 | 0 | return retval; |
571 | 20 | } |
572 | | |
573 | | UA_Boolean |
574 | | Service_GetEndpoints(UA_Server *server, UA_Session *session, |
575 | | const UA_GetEndpointsRequest *request, |
576 | 20 | UA_GetEndpointsResponse *response) { |
577 | 20 | UA_LOCK_ASSERT(&server->serviceMutex); |
578 | | |
579 | 20 | UA_LOG_DEBUG_SESSION(server->config.logging, session, |
580 | 20 | "Processing GetEndpointsRequest with endpointUrl %S", |
581 | 20 | request->endpointUrl); |
582 | | |
583 | | /* If the client expects to see a specific endpointurl, mirror it back. If |
584 | | * not, clone the endpoints with the discovery url of all networklayers. */ |
585 | 20 | response->responseHeader.serviceResult = |
586 | 20 | setCurrentEndPointsArray(server, NULL, request->endpointUrl, |
587 | 20 | request->profileUris, request->profileUrisSize, |
588 | 20 | &response->endpoints, &response->endpointsSize); |
589 | 20 | return true; |
590 | 20 | } |
591 | | |
592 | | #ifdef UA_ENABLE_DISCOVERY |
593 | | |
594 | | static void |
595 | | process_RegisterServer(UA_Server *server, UA_Session *session, |
596 | | const UA_RequestHeader* requestHeader, |
597 | | const UA_RegisteredServer *requestServer, |
598 | | const size_t requestDiscoveryConfigurationSize, |
599 | | const UA_ExtensionObject *requestDiscoveryConfiguration, |
600 | | UA_ResponseHeader* responseHeader, |
601 | | size_t *responseConfigurationResultsSize, |
602 | | UA_StatusCode **responseConfigurationResults, |
603 | | size_t *responseDiagnosticInfosSize, |
604 | 18 | UA_DiagnosticInfo *responseDiagnosticInfos) { |
605 | 18 | UA_LOCK_ASSERT(&server->serviceMutex); |
606 | | |
607 | 18 | UA_DiscoveryManager *dm = (UA_DiscoveryManager*) |
608 | 18 | getServerComponentByName(server, UA_STRING("discovery")); |
609 | 18 | if(!dm) |
610 | 0 | return; |
611 | | |
612 | 18 | UA_ServerConfig *sc = &server->config; |
613 | 18 | if(sc->applicationDescription.applicationType != UA_APPLICATIONTYPE_DISCOVERYSERVER) { |
614 | 18 | responseHeader->serviceResult = UA_STATUSCODE_BADSERVICEUNSUPPORTED; |
615 | 18 | return; |
616 | 18 | } |
617 | | |
618 | | /* Find the server from the request in the registered list */ |
619 | 0 | registeredServer *rs = NULL; |
620 | 0 | LIST_FOREACH(rs, &dm->registeredServers, pointers) { |
621 | 0 | if(UA_String_equal(&rs->registeredServer.serverUri, &requestServer->serverUri)) |
622 | 0 | break; |
623 | 0 | } |
624 | |
|
625 | 0 | UA_MdnsDiscoveryConfiguration *mdnsConfig = NULL; |
626 | |
|
627 | 0 | const UA_String* mdnsServerName = NULL; |
628 | 0 | if(requestDiscoveryConfigurationSize) { |
629 | 0 | *responseConfigurationResults = |
630 | 0 | (UA_StatusCode *)UA_Array_new(requestDiscoveryConfigurationSize, |
631 | 0 | &UA_TYPES[UA_TYPES_STATUSCODE]); |
632 | 0 | if(!(*responseConfigurationResults)) { |
633 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
634 | 0 | return; |
635 | 0 | } |
636 | 0 | *responseConfigurationResultsSize = requestDiscoveryConfigurationSize; |
637 | |
|
638 | 0 | for(size_t i = 0; i < requestDiscoveryConfigurationSize; i++) { |
639 | 0 | const UA_ExtensionObject *object = &requestDiscoveryConfiguration[i]; |
640 | 0 | if(!mdnsConfig && (object->encoding == UA_EXTENSIONOBJECT_DECODED || |
641 | 0 | object->encoding == UA_EXTENSIONOBJECT_DECODED_NODELETE) && |
642 | 0 | (object->content.decoded.type == &UA_TYPES[UA_TYPES_MDNSDISCOVERYCONFIGURATION])) { |
643 | 0 | mdnsConfig = (UA_MdnsDiscoveryConfiguration *)object->content.decoded.data; |
644 | 0 | mdnsServerName = &mdnsConfig->mdnsServerName; |
645 | 0 | (*responseConfigurationResults)[i] = UA_STATUSCODE_GOOD; |
646 | 0 | } else { |
647 | 0 | (*responseConfigurationResults)[i] = UA_STATUSCODE_BADNOTSUPPORTED; |
648 | 0 | } |
649 | 0 | } |
650 | 0 | } |
651 | | |
652 | 0 | if(!mdnsServerName && requestServer->serverNamesSize) |
653 | 0 | mdnsServerName = &requestServer->serverNames[0].text; |
654 | |
|
655 | 0 | if(!mdnsServerName) { |
656 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADSERVERNAMEMISSING; |
657 | 0 | return; |
658 | 0 | } |
659 | | |
660 | 0 | if(requestServer->discoveryUrlsSize == 0) { |
661 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADDISCOVERYURLMISSING; |
662 | 0 | return; |
663 | 0 | } |
664 | | |
665 | 0 | if(requestServer->semaphoreFilePath.length) { |
666 | 0 | #ifdef UA_ENABLE_DISCOVERY_SEMAPHORE |
667 | 0 | char* filePath = (char*) |
668 | 0 | UA_malloc(sizeof(char)*requestServer->semaphoreFilePath.length+1); |
669 | 0 | if(!filePath) { |
670 | 0 | UA_LOG_ERROR_SESSION(sc->logging, session, |
671 | 0 | "Cannot allocate memory for semaphore path. " |
672 | 0 | "Out of memory."); |
673 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
674 | 0 | return; |
675 | 0 | } |
676 | 0 | memcpy(filePath, requestServer->semaphoreFilePath.data, |
677 | 0 | requestServer->semaphoreFilePath.length ); |
678 | 0 | filePath[requestServer->semaphoreFilePath.length] = '\0'; |
679 | 0 | if(!UA_fileExists( filePath )) { |
680 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADSEMAPHOREFILEMISSING; |
681 | 0 | UA_free(filePath); |
682 | 0 | return; |
683 | 0 | } |
684 | 0 | UA_free(filePath); |
685 | | #else |
686 | | UA_LOG_WARNING(sc->logging, UA_LOGCATEGORY_CLIENT, |
687 | | "Ignoring semaphore file path. open62541 not compiled " |
688 | | "with UA_ENABLE_DISCOVERY_SEMAPHORE=ON"); |
689 | | #endif |
690 | 0 | } |
691 | | |
692 | 0 | #ifdef UA_ENABLE_DISCOVERY_MULTICAST |
693 | 0 | if(sc->mdnsEnabled) { |
694 | 0 | for(size_t i = 0; i < requestServer->discoveryUrlsSize; i++) { |
695 | | /* create TXT if is online and first index, delete TXT if is offline |
696 | | * and last index */ |
697 | 0 | UA_Boolean updateTxt = (requestServer->isOnline && i==0) || |
698 | 0 | (!requestServer->isOnline && i==requestServer->discoveryUrlsSize); |
699 | 0 | UA_Discovery_updateMdnsForDiscoveryUrl(dm, *mdnsServerName, mdnsConfig, |
700 | 0 | requestServer->discoveryUrls[i], |
701 | 0 | requestServer->isOnline, updateTxt); |
702 | 0 | } |
703 | 0 | } |
704 | 0 | #endif |
705 | |
|
706 | 0 | if(!requestServer->isOnline) { |
707 | | /* Server is shutting down. Remove it from the registered servers list */ |
708 | 0 | if(!rs) { |
709 | | /* Server not found, show warning */ |
710 | 0 | UA_LOG_WARNING_SESSION(sc->logging, session, |
711 | 0 | "Could not unregister server %S. Not registered.", |
712 | 0 | requestServer->serverUri); |
713 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADNOTHINGTODO; |
714 | 0 | return; |
715 | 0 | } |
716 | | |
717 | 0 | if(dm->registerServerCallback) |
718 | 0 | dm->registerServerCallback(requestServer, dm->registerServerCallbackData); |
719 | | |
720 | | /* Server found, remove from list */ |
721 | 0 | LIST_REMOVE(rs, pointers); |
722 | 0 | UA_RegisteredServer_clear(&rs->registeredServer); |
723 | 0 | UA_free(rs); |
724 | 0 | dm->registeredServersSize--; |
725 | 0 | responseHeader->serviceResult = UA_STATUSCODE_GOOD; |
726 | 0 | return; |
727 | 0 | } |
728 | | |
729 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
730 | 0 | if(!rs) { |
731 | | /* Server not yet registered, register it by adding it to the list */ |
732 | 0 | UA_LOG_DEBUG_SESSION(sc->logging, session, |
733 | 0 | "Registering new server: %S", |
734 | 0 | requestServer->serverUri); |
735 | |
|
736 | 0 | rs = (registeredServer*)UA_malloc(sizeof(registeredServer)); |
737 | 0 | if(!rs) { |
738 | 0 | responseHeader->serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
739 | 0 | return; |
740 | 0 | } |
741 | | |
742 | 0 | LIST_INSERT_HEAD(&dm->registeredServers, rs, pointers); |
743 | 0 | dm->registeredServersSize++; |
744 | 0 | } else { |
745 | 0 | UA_RegisteredServer_clear(&rs->registeredServer); |
746 | 0 | } |
747 | | |
748 | | /* Always call the callback, if it is set. Previously we only called it if |
749 | | * it was a new register call. It may be the case that this endpoint |
750 | | * registered before, then crashed, restarts and registeres again. In that |
751 | | * case the entry is not deleted and the callback would not be called. */ |
752 | 0 | if(dm->registerServerCallback) |
753 | 0 | dm->registerServerCallback(requestServer, |
754 | 0 | dm->registerServerCallbackData); |
755 | | |
756 | | /* Ccopy the data from the request into the list */ |
757 | 0 | UA_EventLoop *el = sc->eventLoop; |
758 | 0 | UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el); |
759 | 0 | UA_RegisteredServer_copy(requestServer, &rs->registeredServer); |
760 | 0 | rs->lastSeen = nowMonotonic; |
761 | 0 | responseHeader->serviceResult = retval; |
762 | 0 | } |
763 | | |
764 | | UA_Boolean |
765 | | Service_RegisterServer(UA_Server *server, UA_Session *session, |
766 | | const UA_RegisterServerRequest *request, |
767 | 9 | UA_RegisterServerResponse *response) { |
768 | 9 | UA_LOG_DEBUG_SESSION(server->config.logging, session, |
769 | 9 | "Processing RegisterServerRequest"); |
770 | 9 | UA_LOCK_ASSERT(&server->serviceMutex); |
771 | 9 | process_RegisterServer(server, session, &request->requestHeader, |
772 | 9 | &request->server, 0, NULL, &response->responseHeader, |
773 | 9 | 0, NULL, 0, NULL); |
774 | 9 | return true; |
775 | 9 | } |
776 | | |
777 | | UA_Boolean |
778 | | Service_RegisterServer2(UA_Server *server, UA_Session *session, |
779 | | const UA_RegisterServer2Request *request, |
780 | 9 | UA_RegisterServer2Response *response) { |
781 | 9 | UA_LOG_DEBUG_SESSION(server->config.logging, session, |
782 | 9 | "Processing RegisterServer2Request"); |
783 | 9 | UA_LOCK_ASSERT(&server->serviceMutex); |
784 | 9 | process_RegisterServer(server, session, &request->requestHeader, |
785 | 9 | &request->server, |
786 | 9 | request->discoveryConfigurationSize, |
787 | 9 | request->discoveryConfiguration, |
788 | 9 | &response->responseHeader, |
789 | 9 | &response->configurationResultsSize, |
790 | 9 | &response->configurationResults, |
791 | 9 | &response->diagnosticInfosSize, |
792 | 9 | response->diagnosticInfos); |
793 | | return true; |
794 | 9 | } |
795 | | |
796 | | #endif /* UA_ENABLE_DISCOVERY */ |