/src/open62541_15/src/client/ua_client_connect.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 2017-2022 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2017-2019 (c) Fraunhofer IOSB (Author: Mark Giraud) |
7 | | * Copyright 2025 (c) Siemens AG (Author: Tin Raic) |
8 | | * Copyright 2025 (c) o6 Automation GmbH (Author: Julius Pfrommer) |
9 | | */ |
10 | | |
11 | | #include <open62541/types.h> |
12 | | |
13 | | #include "open62541/transport_generated.h" |
14 | | #include "ua_client_internal.h" |
15 | | #include "../ua_types_encoding_binary.h" |
16 | | #include "mp_printf.h" |
17 | | |
18 | | /* Some OPC UA servers only return all Endpoints if the EndpointURL used during |
19 | | * the HEL/ACK handshake exactly matches -- including the path following the |
20 | | * address and port! Hence for the first connection we only call FindServers and |
21 | | * reopen a new TCP connection using then EndpointURL found there. |
22 | | * |
23 | | * The overall process is this: |
24 | | * - Connect with the EndpointURL provided by the user (HEL/ACK) |
25 | | * - Call FindServers |
26 | | * - If one of the results has an exactly matching EndpointUrl, continue. |
27 | | * - Otherwise select a matching server, update the endpointURL member of |
28 | | * UA_Client and reconnect. |
29 | | * - Call GetEndpoints and select an Endpoint |
30 | | * - Open a SecureChannel and Session for that Endpoint |
31 | | * - Read the namespaces array of the server (and create the namespaces mapping) |
32 | | */ |
33 | | |
34 | 0 | #define UA_MINMESSAGESIZE 8192 |
35 | 0 | #define MAX_DATA_SIZE 4096 |
36 | | |
37 | | static void initConnect(UA_Client *client); |
38 | | static UA_StatusCode createSessionAsync(UA_Client *client); |
39 | | static UA_UserTokenPolicy * |
40 | | findUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint, |
41 | | char *logPrefix); |
42 | | |
43 | | /* Get the EndpointUrl to be used right now. |
44 | | * This is adjusted during the discovery process. |
45 | | * We fall back if connecting to an EndpointUrl fails. */ |
46 | | static UA_String |
47 | 0 | getEndpointUrl(UA_Client *client) { |
48 | 0 | if(client->endpoint.endpointUrl.length > 0) |
49 | 0 | return client->endpoint.endpointUrl; |
50 | 0 | if(client->discoveryUrl.length > 0) |
51 | 0 | return client->discoveryUrl; |
52 | 0 | return client->config.endpointUrl; |
53 | 0 | } |
54 | | |
55 | | /* If an EndpointUrl doesn't work (TCP connection fails), fall back to the |
56 | | * initial EndpointUrl */ |
57 | | static UA_StatusCode |
58 | 0 | fallbackEndpointUrl(UA_Client* client) { |
59 | | /* Cannot fallback, the initial EndpointUrl is already in use */ |
60 | 0 | UA_String currentUrl = getEndpointUrl(client); |
61 | 0 | if(UA_String_equal(¤tUrl, &client->config.endpointUrl)) { |
62 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
63 | 0 | "Could not open a TCP connection to the Endpoint at %S", |
64 | 0 | client->config.endpointUrl); |
65 | 0 | return UA_STATUSCODE_BADCONNECTIONREJECTED; |
66 | 0 | } |
67 | | |
68 | 0 | if(client->endpoint.endpointUrl.length > 0) { |
69 | | /* Overwrite the EndpointUrl of the Endpoint */ |
70 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
71 | 0 | "Could not open a TCP connection to the Endpoint at %S. " |
72 | 0 | "Overriding the endpoint description with the initial " |
73 | 0 | "EndpointUrl at %S.", |
74 | 0 | client->config.endpoint.endpointUrl, |
75 | 0 | client->config.endpointUrl); |
76 | 0 | UA_String_clear(&client->endpoint.endpointUrl); |
77 | 0 | return UA_String_copy(&client->config.endpointUrl, |
78 | 0 | &client->endpoint.endpointUrl); |
79 | 0 | } else { |
80 | | /* Overwrite the DiscoveryUrl returned by FindServers */ |
81 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
82 | 0 | "The DiscoveryUrl returned by the FindServers service (%S) " |
83 | 0 | "could not be connected. Continuing with the initial EndpointUrl " |
84 | 0 | "%S for the GetEndpoints service.", |
85 | 0 | client->config.endpointUrl, client->config.endpointUrl); |
86 | 0 | UA_String_clear(&client->discoveryUrl); |
87 | 0 | return UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl); |
88 | 0 | } |
89 | 0 | } |
90 | | |
91 | | static UA_SecurityPolicy * |
92 | 0 | getSecurityPolicy(UA_Client *client, UA_String policyUri) { |
93 | 0 | for(size_t i = 0; i < client->config.securityPoliciesSize; i++) { |
94 | 0 | if(UA_String_equal(&policyUri, &client->config.securityPolicies[i].policyUri)) |
95 | 0 | return &client->config.securityPolicies[i]; |
96 | 0 | } |
97 | 0 | return NULL; |
98 | 0 | } |
99 | | |
100 | | /* Match PolicyUri and certificate. The returned SecurityPolicy instance carries |
101 | | * the private key to sign with the given certificate. When the certificate is |
102 | | * NULL, only the PolicyUri is matched (used for non-X509 token types). */ |
103 | | static UA_SecurityPolicy * |
104 | | getAuthSecurityPolicy(UA_Client *client, const UA_String policyUri, |
105 | 0 | const UA_ByteString *certificate) { |
106 | 0 | for(size_t i = 0; i < client->config.authSecurityPoliciesSize; i++) { |
107 | 0 | UA_SecurityPolicy *sp = &client->config.authSecurityPolicies[i]; |
108 | 0 | if(!UA_String_equal(&policyUri, &sp->policyUri)) |
109 | 0 | continue; |
110 | 0 | if(certificate && !UA_ByteString_equal(certificate, &sp->localCertificate)) |
111 | 0 | continue; |
112 | 0 | return sp; |
113 | 0 | } |
114 | 0 | return NULL; |
115 | 0 | } |
116 | | |
117 | | /* The endpoint is unconfigured if the description is all zeroed-out */ |
118 | | static UA_Boolean |
119 | 0 | endpointUnconfigured(const UA_EndpointDescription *endpoint) { |
120 | 0 | UA_EndpointDescription tmp; |
121 | 0 | UA_EndpointDescription_init(&tmp); |
122 | 0 | return UA_equal(&tmp, endpoint, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); |
123 | 0 | } |
124 | | |
125 | | UA_Boolean |
126 | 0 | isFullyConnected(UA_Client *client) { |
127 | | /* No SecureChannel */ |
128 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_OPEN) |
129 | 0 | return false; |
130 | | |
131 | | /* GetEndpoints handshake ongoing or not yet done */ |
132 | 0 | if(client->endpointsHandshake || endpointUnconfigured(&client->endpoint)) |
133 | 0 | return false; |
134 | | |
135 | | /* FindServers handshake ongoing or not yet done */ |
136 | 0 | if(client->findServersHandshake || client->discoveryUrl.length == 0) |
137 | 0 | return false; |
138 | | |
139 | 0 | if(!client->config.noSession) { |
140 | | /* No Session, but is required */ |
141 | 0 | if(client->sessionState != UA_SESSIONSTATE_ACTIVATED) |
142 | 0 | return false; |
143 | | |
144 | | /* NamespaceArray not yet read */ |
145 | 0 | if(client->namespacesHandshake || !client->haveNamespaces) |
146 | 0 | return false; |
147 | 0 | } |
148 | | |
149 | 0 | return true; |
150 | 0 | } |
151 | | |
152 | | static UA_StatusCode |
153 | | initUserTokenPolicy(UA_Client *client, const UA_UserTokenPolicy **outUtp, |
154 | 0 | char *logPrefix) { |
155 | 0 | const UA_UserTokenPolicy *utp = |
156 | 0 | findUserTokenPolicy(client, &client->endpoint, logPrefix); |
157 | 0 | if(!utp) { |
158 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
159 | 0 | "%s: Could not find a matching UserTokenPolicy " |
160 | 0 | "in the endpoint", logPrefix); |
161 | 0 | return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; |
162 | 0 | } |
163 | | |
164 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
165 | 0 | "%s: Using UserTokenPolicy %S", logPrefix, utp->policyId); |
166 | | |
167 | | /* If not specifically defined in the UserTokenPolicy, then the |
168 | | * SecurityPolicy of the underlying endpoint (SecureChannel) is used. */ |
169 | 0 | UA_String tokenSecurityPolicyUri = (utp->securityPolicyUri.length > 0) ? |
170 | 0 | utp->securityPolicyUri : client->endpoint.securityPolicyUri; |
171 | | |
172 | | /* Get the SecurityPolicy for the UserIdentityToken. X509 tokens sign with |
173 | | * a private key, so they must use an authSecurityPolicy (matched by |
174 | | * PolicyUri and certificate). Other tokens only encrypt with the server |
175 | | * certificate, so fall back to the SecureChannel securityPolicies. */ |
176 | 0 | UA_SecurityPolicy *utsp; |
177 | 0 | if(utp->tokenType == UA_USERTOKENTYPE_CERTIFICATE) { |
178 | 0 | UA_X509IdentityToken *token = (UA_X509IdentityToken*) |
179 | 0 | client->config.userIdentityToken.content.decoded.data; |
180 | 0 | utsp = getAuthSecurityPolicy(client, tokenSecurityPolicyUri, |
181 | 0 | &token->certificateData); |
182 | 0 | } else { |
183 | 0 | utsp = getAuthSecurityPolicy(client, tokenSecurityPolicyUri, NULL); |
184 | 0 | if(!utsp) |
185 | 0 | utsp = getSecurityPolicy(client, tokenSecurityPolicyUri); |
186 | 0 | } |
187 | 0 | if(!utsp) { |
188 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
189 | 0 | "%s: SecurityPolicy %S not available for the " |
190 | 0 | "UserTokenPolicy", logPrefix, tokenSecurityPolicyUri); |
191 | 0 | return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; |
192 | 0 | } |
193 | | |
194 | | /* ExistingSecurityPolicy matches? */ |
195 | 0 | if(client->utpSp) { |
196 | 0 | if(!UA_String_equal(&client->utpSp->policyUri, |
197 | 0 | &tokenSecurityPolicyUri)) { |
198 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
199 | 0 | "%s: SecurityPolicy %S cannot be instantiated. " |
200 | 0 | "A different SecurityPolicy %S is in place already", |
201 | 0 | logPrefix, tokenSecurityPolicyUri, |
202 | 0 | client->utpSp->policyUri); |
203 | 0 | return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; |
204 | 0 | } |
205 | | |
206 | | /* Set the output and return */ |
207 | 0 | *outUtp = utp; |
208 | 0 | return UA_STATUSCODE_GOOD; |
209 | 0 | } |
210 | | |
211 | | /* Create an instance of the SecurityPolicy using the server certificate |
212 | | * from the endpoint */ |
213 | 0 | UA_StatusCode res = |
214 | 0 | utsp->newChannelContext(utsp, &client->endpoint.serverCertificate, |
215 | 0 | &client->utpSpContext); |
216 | 0 | if(res != UA_STATUSCODE_GOOD) { |
217 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
218 | 0 | "%s: UserTokenPolicy %S could not be instantiated with the " |
219 | 0 | "server certificate", logPrefix, tokenSecurityPolicyUri); |
220 | 0 | return UA_STATUSCODE_BADSECURITYPOLICYREJECTED; |
221 | 0 | } |
222 | 0 | client->utpSp = utsp; |
223 | | |
224 | | /* Set the output and return */ |
225 | 0 | *outUtp = utp; |
226 | 0 | return UA_STATUSCODE_GOOD; |
227 | 0 | } |
228 | | |
229 | | /* Legacy (pre-v1.05.07) signature: the leaf of the remote (peer) certificate |
230 | | * concatenated with the server's session nonce, signed with the given |
231 | | * SecurityPolicy/context. Shared by the application ClientSignature and the |
232 | | * X.509 user-token signature. The leaf is used (not the raw, possibly-chained |
233 | | * remoteCertificate) so the bytes match the server's verify, which is computed |
234 | | * over its single localCertificate. */ |
235 | | static UA_StatusCode |
236 | | signLegacyCertNonce(const UA_SecurityPolicy *sp, void *spContext, |
237 | | const UA_ByteString *remoteCertificate, |
238 | 0 | const UA_ByteString *serverNonce, UA_ByteString *outSignature) { |
239 | 0 | UA_ByteString leaf = getLeafCertificate(*remoteCertificate); |
240 | 0 | size_t signDataSize = leaf.length + serverNonce->length; |
241 | 0 | if(signDataSize > MAX_DATA_SIZE) |
242 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
243 | 0 | UA_Byte buf[MAX_DATA_SIZE]; |
244 | 0 | UA_ByteString signData = {signDataSize, buf}; |
245 | 0 | memcpy(buf, leaf.data, leaf.length); |
246 | 0 | memcpy(buf + leaf.length, serverNonce->data, serverNonce->length); |
247 | 0 | return sp->asymSignatureAlgorithm.sign(sp, spContext, &signData, outSignature); |
248 | 0 | } |
249 | | |
250 | | /* Function to create a signature using remote certificate and nonce. |
251 | | * This uses the SecurityPolicy of the SecureChannel. */ |
252 | | static UA_StatusCode |
253 | 0 | signClientSignature(UA_Client *client, UA_ActivateSessionRequest *request) { |
254 | 0 | UA_SecureChannel *channel = &client->channel; |
255 | 0 | const UA_SecurityPolicy *sp = channel->securityPolicy; |
256 | 0 | void *cc = channel->channelContext; |
257 | |
|
258 | 0 | UA_SignatureData *sd = &request->clientSignature; |
259 | 0 | const UA_SecurityPolicySignatureAlgorithm *signAlg = &sp->asymSignatureAlgorithm; |
260 | | |
261 | | /* OPC UA Part 4 v1.05.07: for SecurityPolicies with |
262 | | * secureChannelEnhancements = true, the algorithm field in |
263 | | * SignatureData shall be NULL or empty and receivers shall |
264 | | * ignore the value. */ |
265 | 0 | UA_StatusCode retval = UA_STATUSCODE_GOOD; |
266 | 0 | if(!UA_SecurityPolicy_isEnhancedSecurity(sp)) |
267 | 0 | retval = UA_String_copy(&signAlg->uri, &sd->algorithm); |
268 | 0 | if(retval != UA_STATUSCODE_GOOD) |
269 | 0 | return retval; |
270 | | |
271 | | /* Allocate memory for the signature */ |
272 | 0 | size_t signatureSize = signAlg->getLocalSignatureSize(sp, cc); |
273 | 0 | retval = UA_ByteString_allocBuffer(&sd->signature, signatureSize); |
274 | 0 | if(retval != UA_STATUSCODE_GOOD) |
275 | 0 | return retval; |
276 | | |
277 | 0 | if(UA_SecurityPolicy_isEnhancedSecurity(sp)) { |
278 | | /* Channel-bound v1.05.07 ClientSignature (see the cert-role table at |
279 | | * UA_SecureChannel_buildActivateSessionSignatureData). */ |
280 | 0 | UA_ByteString signData = UA_BYTESTRING_NULL; |
281 | 0 | retval = UA_SecureChannel_buildActivateSessionSignatureData( |
282 | 0 | channel, &client->serverSessionNonce, &client->clientSessionNonce, |
283 | 0 | &channel->remoteCertificate, &channel->remoteCertificate, |
284 | 0 | &sp->localCertificate, &signData); |
285 | 0 | if(retval != UA_STATUSCODE_GOOD) |
286 | 0 | return retval; |
287 | 0 | retval = signAlg->sign(sp, cc, &signData, &sd->signature); |
288 | 0 | UA_ByteString_clear(&signData); |
289 | 0 | return retval; |
290 | 0 | } |
291 | | |
292 | | /* Legacy (v1.05.06): leaf(serverCert) + serverNonce. */ |
293 | 0 | return signLegacyCertNonce(sp, cc, &channel->remoteCertificate, |
294 | 0 | &client->serverSessionNonce, &sd->signature); |
295 | 0 | } |
296 | | |
297 | | /* This uses the SecurityPolicy of the UserTokenPolicy */ |
298 | | static UA_StatusCode |
299 | | signUserTokenSignature(UA_Client *client, |
300 | 0 | UA_ActivateSessionRequest *request) { |
301 | 0 | UA_assert(client->config.userIdentityToken.content.decoded.type == |
302 | 0 | &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]); |
303 | | |
304 | 0 | UA_SecurityPolicy *utpSp = client->utpSp; |
305 | 0 | UA_assert(utpSp); |
306 | | |
307 | 0 | UA_SecureChannel *channel = &client->channel; |
308 | 0 | UA_SecurityPolicySignatureAlgorithm *signAlg = &utpSp->asymSignatureAlgorithm; |
309 | 0 | UA_SignatureData *utsd = &request->userTokenSignature; |
310 | | |
311 | | /* Stricter servers reject an empty user-token algorithm - so always send |
312 | | * the policy's signature URI (the AesGcm policy carries its SecurityPolicy |
313 | | * URI there; RSA carries the RSA URI; plain ECC carries an empty |
314 | | * string). */ |
315 | 0 | UA_StatusCode retval = UA_String_copy(&signAlg->uri, &utsd->algorithm); |
316 | 0 | if(retval != UA_STATUSCODE_GOOD) |
317 | 0 | return retval; |
318 | | |
319 | | /* Allocate memory for the signature */ |
320 | 0 | size_t sigLen = signAlg->getLocalSignatureSize(utpSp, client->utpSpContext); |
321 | 0 | retval = UA_ByteString_allocBuffer(&utsd->signature, sigLen); |
322 | 0 | if(retval != UA_STATUSCODE_GOOD) |
323 | 0 | return retval; |
324 | | |
325 | | /* Channel-bound v1.05.07 user-token signature (see the cert-role table at |
326 | | * UA_SecureChannel_buildUserTokenSignatureData), produced with the user's |
327 | | * private key (utpSp context). Used only when BOTH the SecureChannel AND the |
328 | | * user-token SecurityPolicy are enhanced; a legacy RSA / old-ECC user token |
329 | | * uses the legacy layout even over an enhanced (AesGcm) SecureChannel. */ |
330 | 0 | if(UA_SecurityPolicy_isEnhancedSecurity(channel->securityPolicy) && |
331 | 0 | UA_SecurityPolicy_isEnhancedSecurity(utpSp)) { |
332 | 0 | const UA_ByteString *clientCert = &channel->securityPolicy->localCertificate; |
333 | 0 | UA_ByteString signData = UA_BYTESTRING_NULL; |
334 | 0 | retval = UA_SecureChannel_buildUserTokenSignatureData( |
335 | 0 | channel, &client->serverSessionNonce, &client->clientSessionNonce, |
336 | 0 | &channel->remoteCertificate, &channel->remoteCertificate, |
337 | 0 | clientCert, clientCert, &signData); |
338 | 0 | if(retval == UA_STATUSCODE_GOOD) |
339 | 0 | retval = signAlg->sign(utpSp, client->utpSpContext, &signData, &utsd->signature); |
340 | 0 | UA_ByteString_clear(&signData); |
341 | 0 | return retval; |
342 | 0 | } |
343 | | |
344 | | /* Legacy (v1.05.06): leaf(serverCert) + serverNonce (same as the |
345 | | * application ClientSignature), signed with the user's key. */ |
346 | 0 | return signLegacyCertNonce(utpSp, client->utpSpContext, |
347 | 0 | &channel->remoteCertificate, |
348 | 0 | &client->serverSessionNonce, &utsd->signature); |
349 | 0 | } |
350 | | |
351 | | /* UserName and IssuedIdentity can be encrypted. This uses the SecurityPolicy |
352 | | * instance for the UserTokenPolicy. So it can keep the ECC ephemeral keys |
353 | | * across multiple SecureChannels. */ |
354 | | static UA_StatusCode |
355 | 0 | encryptUserIdentityToken(UA_Client *client, UA_ExtensionObject *userIdentityToken) { |
356 | 0 | UA_IssuedIdentityToken *iit = NULL; |
357 | 0 | UA_UserNameIdentityToken *unit = NULL; |
358 | 0 | UA_String *encryptionAlg; |
359 | 0 | UA_ByteString *tokenData; |
360 | |
|
361 | 0 | const UA_DataType *tokenType = userIdentityToken->content.decoded.type; |
362 | 0 | if(tokenType == &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) { |
363 | 0 | iit = (UA_IssuedIdentityToken*)userIdentityToken->content.decoded.data; |
364 | 0 | tokenData = &iit->tokenData; |
365 | 0 | encryptionAlg = &iit->encryptionAlgorithm; |
366 | 0 | } else { /* tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN] */ |
367 | 0 | UA_assert(tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]); |
368 | 0 | unit = (UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data; |
369 | 0 | tokenData = &unit->password; |
370 | 0 | encryptionAlg = &unit->encryptionAlgorithm; |
371 | 0 | } |
372 | | |
373 | | /* The sessionSp is instantiated during CreateSession */ |
374 | 0 | UA_SecurityPolicy *utpSp = client->utpSp; |
375 | 0 | UA_assert(utpSp); |
376 | | |
377 | | /* Nothing to encrypt */ |
378 | 0 | if(utpSp->policyType == UA_SECURITYPOLICYTYPE_NONE) { |
379 | | /* Warn if a token is transmitted in cleartext. This needs to be allowed |
380 | | * with the allowNonePolicyPassword setting and is checked in |
381 | | * findUserTokenPolicy. */ |
382 | 0 | if(client->channel.securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
383 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
384 | 0 | "!!! Warning !!! AuthenticationToken is transmitted " |
385 | 0 | "without encryption"); |
386 | 0 | return UA_STATUSCODE_GOOD; |
387 | 0 | } |
388 | | |
389 | | /* Encrypt the TokenData */ |
390 | 0 | UA_StatusCode res = UA_STATUSCODE_GOOD; |
391 | 0 | if(UA_SecurityPolicy_isEcc(utpSp)) { |
392 | 0 | res = encryptUserIdentityTokenEcc(client->config.logging, &client->channel, |
393 | 0 | utpSp, client->utpSpContext, tokenData, |
394 | 0 | client->serverSessionNonce, |
395 | 0 | client->serverEphemeralPubKey); |
396 | | /* Don't reuse the Ephemeral Public Key */ |
397 | 0 | UA_ByteString_clear(&client->serverEphemeralPubKey); |
398 | | |
399 | | /* The EccEncryptedSecret identifies its algorithm via the SecurityPolicy |
400 | | * URI embedded in the secret header, so the UserIdentityToken's |
401 | | * encryptionAlgorithm field is left EMPTY (matching the OPC UA reference |
402 | | * stack, which sets it to null for the EncryptedSecret format). Sending |
403 | | * the policy's asymEncryptionAlgorithm URI here makes strict servers |
404 | | * reject the token with BadIdentityTokenInvalid */ |
405 | 0 | return res; |
406 | 0 | } |
407 | | |
408 | 0 | res = encryptSecretLegacy(utpSp, client->utpSpContext, |
409 | 0 | client->serverSessionNonce, tokenData); |
410 | | |
411 | | /* Legacy RSA EncryptedData: carry the asymmetric encryption algorithm URI */ |
412 | 0 | return res | UA_String_copy(&utpSp->asymEncryptionAlgorithm.uri, encryptionAlg); |
413 | 0 | } |
414 | | |
415 | | /* Verifies the signature corresponds to ClientNonce using the SecurityPolicy of |
416 | | * the SecureChannel */ |
417 | | static UA_StatusCode |
418 | | checkCreateSessionSignature(UA_Client *client, const UA_SecureChannel *channel, |
419 | 0 | const UA_CreateSessionResponse *response) { |
420 | 0 | if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
421 | 0 | channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
422 | 0 | return UA_STATUSCODE_GOOD; |
423 | | |
424 | 0 | if(!channel->securityPolicy) |
425 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
426 | | |
427 | 0 | const UA_SecurityPolicy *sp = channel->securityPolicy; |
428 | 0 | const UA_ByteString *lc = &sp->localCertificate; |
429 | |
|
430 | 0 | UA_ByteString dataToVerify = UA_BYTESTRING_NULL; |
431 | 0 | UA_StatusCode retval; |
432 | 0 | if(UA_SecurityPolicy_isEnhancedSecurity(sp)) { |
433 | | /* Channel-bound v1.05.07 CreateSession ServerSignature (see the |
434 | | * cert-role table at UA_SecureChannel_buildCreateSessionSignatureData). */ |
435 | 0 | retval = UA_SecureChannel_buildCreateSessionSignatureData( |
436 | 0 | channel, &client->clientSessionNonce, &response->serverNonce, |
437 | 0 | &channel->remoteCertificate, lc, &dataToVerify); |
438 | 0 | } else { |
439 | 0 | retval = UA_ByteString_allocBuffer(&dataToVerify, |
440 | 0 | lc->length + client->clientSessionNonce.length); |
441 | 0 | if(retval == UA_STATUSCODE_GOOD) { |
442 | 0 | memcpy(dataToVerify.data, lc->data, lc->length); |
443 | 0 | memcpy(dataToVerify.data + lc->length, client->clientSessionNonce.data, |
444 | 0 | client->clientSessionNonce.length); |
445 | 0 | } |
446 | 0 | } |
447 | 0 | if(retval != UA_STATUSCODE_GOOD) |
448 | 0 | return retval; |
449 | | |
450 | 0 | const UA_SecurityPolicySignatureAlgorithm *signAlg = &sp->asymSignatureAlgorithm; |
451 | 0 | retval = signAlg->verify(sp, channel->channelContext, &dataToVerify, |
452 | 0 | &response->serverSignature.signature); |
453 | 0 | UA_ByteString_clear(&dataToVerify); |
454 | 0 | return retval; |
455 | 0 | } |
456 | | |
457 | | /***********************/ |
458 | | /* Open the Connection */ |
459 | | /***********************/ |
460 | | |
461 | | void |
462 | 0 | processERRResponse(UA_Client *client, const UA_ByteString *chunk) { |
463 | 0 | size_t offset = 0; |
464 | 0 | UA_TcpErrorMessage errMessage; |
465 | 0 | UA_StatusCode res = |
466 | 0 | UA_decodeBinaryInternal(chunk, &offset, &errMessage, |
467 | 0 | &UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE], NULL); |
468 | 0 | if(res != UA_STATUSCODE_GOOD) { |
469 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel, |
470 | 0 | "Received an ERR response that could not be decoded " |
471 | 0 | "with StatusCode %s", UA_StatusCode_name(res)); |
472 | 0 | setConnectStatus(client, res); |
473 | 0 | return; |
474 | 0 | } |
475 | | |
476 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel, |
477 | 0 | "Received an ERR response with StatusCode %s and " |
478 | 0 | "the following reason: \"%S\"", |
479 | 0 | UA_StatusCode_name(errMessage.error), |
480 | 0 | errMessage.reason); |
481 | 0 | setConnectStatus(client, errMessage.error); |
482 | 0 | UA_TcpErrorMessage_clear(&errMessage); |
483 | 0 | } |
484 | | |
485 | | void |
486 | 0 | processACKResponse(UA_Client *client, const UA_ByteString *chunk) { |
487 | 0 | UA_SecureChannel *channel = &client->channel; |
488 | 0 | if(channel->state != UA_SECURECHANNELSTATE_HEL_SENT) { |
489 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, channel, |
490 | 0 | "SecureChannel not in the HEL-sent state"); |
491 | 0 | setConnectStatus(client, UA_STATUSCODE_BADSECURECHANNELCLOSED); |
492 | 0 | return; |
493 | 0 | } |
494 | | |
495 | | /* Decode the message */ |
496 | 0 | size_t offset = 0; |
497 | 0 | UA_TcpAcknowledgeMessage ackMessage; |
498 | 0 | UA_StatusCode res = |
499 | 0 | UA_decodeBinaryInternal(chunk, &offset, &ackMessage, |
500 | 0 | &UA_TRANSPORT[UA_TRANSPORT_TCPACKNOWLEDGEMESSAGE], NULL); |
501 | 0 | if(res != UA_STATUSCODE_GOOD) { |
502 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_NETWORK, |
503 | 0 | "Decoding ACK message failed"); |
504 | 0 | setConnectStatus(client, res); |
505 | 0 | return; |
506 | 0 | } |
507 | | |
508 | 0 | res = UA_SecureChannel_processHELACK(channel, &ackMessage); |
509 | 0 | if(res != UA_STATUSCODE_GOOD) { |
510 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_NETWORK, |
511 | 0 | "Processing the ACK message failed with StatusCode %s", |
512 | 0 | UA_StatusCode_name(res)); |
513 | 0 | setConnectStatus(client, res); |
514 | 0 | return; |
515 | 0 | } |
516 | | |
517 | 0 | client->channel.state = UA_SECURECHANNELSTATE_ACK_RECEIVED; |
518 | 0 | } |
519 | | |
520 | | static UA_StatusCode |
521 | 0 | sendHELMessage(UA_Client *client) { |
522 | 0 | UA_ConnectionManager *cm = client->channel.connectionManager; |
523 | 0 | if(!UA_SecureChannel_isConnected(&client->channel)) |
524 | 0 | return UA_STATUSCODE_BADNOTCONNECTED; |
525 | | |
526 | | /* Get a buffer */ |
527 | 0 | UA_ByteString message; |
528 | 0 | UA_StatusCode retval = cm->allocNetworkBuffer(cm, client->channel.connectionId, |
529 | 0 | &message, UA_MINMESSAGESIZE); |
530 | 0 | if(retval != UA_STATUSCODE_GOOD) |
531 | 0 | return retval; |
532 | | |
533 | | /* Prepare the HEL message and encode at offset 8 */ |
534 | 0 | UA_TcpHelloMessage hello; |
535 | 0 | hello.protocolVersion = 0; |
536 | 0 | hello.receiveBufferSize = client->config.localConnectionConfig.recvBufferSize; |
537 | 0 | hello.sendBufferSize = client->config.localConnectionConfig.sendBufferSize; |
538 | 0 | hello.maxMessageSize = client->config.localConnectionConfig.localMaxMessageSize; |
539 | 0 | hello.maxChunkCount = client->config.localConnectionConfig.localMaxChunkCount; |
540 | 0 | hello.endpointUrl = getEndpointUrl(client); |
541 | |
|
542 | 0 | UA_Byte *bufPos = &message.data[8]; /* skip the header */ |
543 | 0 | const UA_Byte *bufEnd = &message.data[message.length]; |
544 | 0 | retval = UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE], |
545 | 0 | &bufPos, &bufEnd, NULL, NULL, NULL); |
546 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
547 | 0 | cm->freeNetworkBuffer(cm, client->channel.connectionId, &message); |
548 | 0 | return retval; |
549 | 0 | } |
550 | | |
551 | | /* Encode the message header at offset 0 */ |
552 | 0 | UA_TcpMessageHeader messageHeader; |
553 | 0 | messageHeader.messageTypeAndChunkType = UA_CHUNKTYPE_FINAL + UA_MESSAGETYPE_HEL; |
554 | 0 | messageHeader.messageSize = (UA_UInt32) ((uintptr_t)bufPos - (uintptr_t)message.data); |
555 | 0 | bufPos = message.data; |
556 | 0 | retval = UA_encodeBinaryInternal(&messageHeader, |
557 | 0 | &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], |
558 | 0 | &bufPos, &bufEnd, NULL, NULL, NULL); |
559 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
560 | 0 | cm->freeNetworkBuffer(cm, client->channel.connectionId, &message); |
561 | 0 | return retval; |
562 | 0 | } |
563 | | |
564 | | /* Send the HEL message */ |
565 | 0 | message.length = messageHeader.messageSize; |
566 | 0 | retval = cm->sendWithConnection(cm, client->channel.connectionId, |
567 | 0 | &UA_KEYVALUEMAP_NULL, &message); |
568 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
569 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
570 | 0 | "Sending HEL failed"); |
571 | 0 | setConnectStatus(client, retval); |
572 | 0 | return retval; |
573 | 0 | } |
574 | | |
575 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
576 | 0 | "Sent HEL message"); |
577 | |
|
578 | 0 | client->channel.state = UA_SECURECHANNELSTATE_HEL_SENT; |
579 | 0 | return UA_STATUSCODE_GOOD; |
580 | 0 | } |
581 | | |
582 | 0 | void processRHEMessage(UA_Client *client, const UA_ByteString *chunk) { |
583 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, "RHE received"); |
584 | |
|
585 | 0 | size_t offset = 0; /* Go to the beginning of the TcpHelloMessage */ |
586 | 0 | UA_TcpReverseHelloMessage rheMessage; |
587 | 0 | static const UA_DataType *rheType = &UA_TRANSPORT[UA_TRANSPORT_TCPREVERSEHELLOMESSAGE]; |
588 | 0 | UA_StatusCode res = UA_decodeBinaryInternal(chunk, &offset, &rheMessage, rheType, NULL); |
589 | 0 | if(res != UA_STATUSCODE_GOOD) { |
590 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_NETWORK, |
591 | 0 | "Decoding RHE message failed"); |
592 | 0 | setConnectStatus(client, res); |
593 | 0 | return; |
594 | 0 | } |
595 | | |
596 | | /* Set the internal DisocveryUrl */ |
597 | 0 | UA_String_clear(&client->discoveryUrl); |
598 | 0 | client->discoveryUrl = rheMessage.endpointUrl; |
599 | 0 | UA_String_init(&rheMessage.endpointUrl); |
600 | 0 | UA_TcpReverseHelloMessage_clear(&rheMessage); |
601 | | |
602 | | /* Send the HEL message */ |
603 | 0 | setConnectStatus(client, sendHELMessage(client)); |
604 | 0 | } |
605 | | |
606 | | void |
607 | 0 | processOPNResponse(UA_Client *client, const UA_ByteString *message) { |
608 | 0 | size_t offset = 0; |
609 | 0 | UA_NodeId responseId; |
610 | 0 | UA_OpenSecureChannelResponse response; |
611 | 0 | UA_NodeId expectedId = UA_NS0ID(OPENSECURECHANNELRESPONSE_ENCODING_DEFAULTBINARY); |
612 | | |
613 | | /* Decode the type NodeId */ |
614 | 0 | UA_StatusCode res = UA_NodeId_decodeBinary(message, &offset, &responseId); |
615 | 0 | if(res != UA_STATUSCODE_GOOD) |
616 | 0 | goto finish_decode; |
617 | | |
618 | | /* Is the response of the expected type? responseId is only needed for |
619 | | * this check -- clear it right away regardless of the outcome so a |
620 | | * heap-backed type (String/GUID/ByteString NodeId) can never leak. */ |
621 | 0 | UA_Boolean typeMatches = UA_NodeId_equal(&responseId, &expectedId); |
622 | 0 | UA_NodeId_clear(&responseId); |
623 | 0 | if(!typeMatches) { |
624 | 0 | res = UA_STATUSCODE_BADDECODINGERROR; |
625 | 0 | goto finish_decode; |
626 | 0 | } |
627 | | |
628 | | /* Decode the response */ |
629 | 0 | res = UA_decodeBinaryInternal(message, &offset, &response, |
630 | 0 | &UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE], NULL); |
631 | |
|
632 | 0 | finish_decode: |
633 | 0 | if(res != UA_STATUSCODE_GOOD) { |
634 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel, |
635 | 0 | "Could not decode the OpenSecureChannelResponse"); |
636 | 0 | setConnectStatus(client, res); |
637 | 0 | return; |
638 | 0 | } |
639 | | |
640 | | /* From here on, response has been decoded and must be cleared on every |
641 | | * rejection as well as on success. */ |
642 | | |
643 | | /* Check whether the nonce was reused */ |
644 | 0 | if(client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE && |
645 | 0 | UA_ByteString_equal(&client->channel.remoteNonce, &response.serverNonce)) { |
646 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel, |
647 | 0 | "The server reused the last nonce"); |
648 | 0 | UA_OpenSecureChannelResponse_clear(&response); |
649 | 0 | setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED); |
650 | 0 | return; |
651 | 0 | } |
652 | | |
653 | | /* Check the nonce length */ |
654 | 0 | if(response.serverNonce.length < client->channel.securityPolicy->nonceLength) { |
655 | 0 | UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel, |
656 | 0 | "The server nonce is too short"); |
657 | 0 | UA_OpenSecureChannelResponse_clear(&response); |
658 | 0 | setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED); |
659 | 0 | return; |
660 | 0 | } |
661 | | |
662 | | /* Move the nonce out of the response */ |
663 | 0 | UA_ByteString_clear(&client->channel.remoteNonce); |
664 | 0 | client->channel.remoteNonce = response.serverNonce; |
665 | 0 | UA_ByteString_init(&response.serverNonce); |
666 | 0 | UA_ResponseHeader_clear(&response.responseHeader); |
667 | | |
668 | | /* Replace the token. Keep the current token as the old token. Messages |
669 | | * might still arrive for the old token. */ |
670 | 0 | client->channel.altSecurityToken = client->channel.securityToken; |
671 | 0 | client->channel.securityToken = response.securityToken; |
672 | 0 | client->channel.renewState = UA_SECURECHANNELRENEWSTATE_NEWTOKEN_CLIENT; |
673 | | |
674 | | /* Log a warning if the SecurityToken is not "fresh". Use the normal system |
675 | | * clock to do the comparison. */ |
676 | 0 | UA_EventLoop *el = client->config.eventLoop; |
677 | 0 | UA_DateTime wallClockNow = el->dateTime_now(el); |
678 | 0 | UA_ChannelSecurityToken *st = &client->channel.securityToken; |
679 | 0 | if(wallClockNow - st->createdAt >= UA_DATETIME_SEC * 10 || |
680 | 0 | wallClockNow - st->createdAt <= -UA_DATETIME_SEC * 10) |
681 | 0 | UA_LOG_WARNING_CHANNEL(client->config.logging, &client->channel, |
682 | 0 | "The \"CreatedAt\" timestamp of the received " |
683 | 0 | "ChannelSecurityToken does not match " |
684 | 0 | "with the local system clock"); |
685 | | |
686 | | /* The internal "monotonic" clock is used by the SecureChannel to validate |
687 | | * that the SecurityToken is still valid. The monotonic clock is independent |
688 | | * from the system clock getting changed or synchronized to a master clock |
689 | | * during runtime. */ |
690 | 0 | client->channel.securityToken.createdAt = el->dateTime_nowMonotonic(el); |
691 | | |
692 | | /* Response.securityToken.revisedLifetime is UInt32, we need to cast it to |
693 | | * DateTime=Int64. After 75% of the lifetime the renewal takes place as |
694 | | * described in standard */ |
695 | 0 | client->nextChannelRenewal = client->channel.securityToken.createdAt + |
696 | 0 | (UA_DateTime) (response.securityToken.revisedLifetime * |
697 | 0 | (UA_Double) UA_DATETIME_MSEC * 0.75); |
698 | | |
699 | | /* Compute the new local keys. The remote keys are updated when a message |
700 | | * with the new SecurityToken is received. */ |
701 | 0 | res = UA_SecureChannel_generateLocalKeys(&client->channel); |
702 | 0 | if(res != UA_STATUSCODE_GOOD) { |
703 | 0 | UA_OpenSecureChannelResponse_clear(&response); |
704 | 0 | setConnectStatus(client, res); |
705 | 0 | return; |
706 | 0 | } |
707 | | |
708 | 0 | UA_Float lifetime = (UA_Float)response.securityToken.revisedLifetime / 1000; |
709 | 0 | UA_Boolean renew = (client->channel.state == UA_SECURECHANNELSTATE_OPEN); |
710 | 0 | if(renew) { |
711 | 0 | UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel, "SecureChannel " |
712 | 0 | "renewed with a revised lifetime of %.2fs", lifetime); |
713 | 0 | } else { |
714 | 0 | UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel, |
715 | 0 | "SecureChannel opened with SecurityMode %s for " |
716 | 0 | "SecurityPolicy %S and a revised lifetime of %.2fs", |
717 | 0 | securityModeNames[client->channel.securityMode], |
718 | 0 | client->channel.securityPolicy->policyUri, |
719 | 0 | lifetime); |
720 | 0 | } |
721 | |
|
722 | 0 | client->channel.state = UA_SECURECHANNELSTATE_OPEN; |
723 | 0 | UA_OpenSecureChannelResponse_clear(&response); |
724 | 0 | } |
725 | | |
726 | | /* OPN messges to renew the channel are sent asynchronous */ |
727 | | static UA_StatusCode |
728 | 0 | sendOPNAsync(UA_Client *client, UA_Boolean renew) { |
729 | 0 | if(!UA_SecureChannel_isConnected(&client->channel)) |
730 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
731 | | |
732 | 0 | UA_StatusCode res = UA_SecureChannel_generateLocalNonce(&client->channel); |
733 | 0 | if(res != UA_STATUSCODE_GOOD) |
734 | 0 | return res; |
735 | | |
736 | 0 | UA_EventLoop *el = client->config.eventLoop; |
737 | | |
738 | | /* Prepare the OpenSecureChannelRequest */ |
739 | 0 | UA_OpenSecureChannelRequest opnSecRq; |
740 | 0 | UA_OpenSecureChannelRequest_init(&opnSecRq); |
741 | 0 | opnSecRq.requestHeader.timestamp = el->dateTime_now(el); |
742 | 0 | opnSecRq.requestHeader.authenticationToken = client->authenticationToken; |
743 | 0 | opnSecRq.securityMode = client->channel.securityMode; |
744 | 0 | opnSecRq.clientNonce = client->channel.localNonce; |
745 | 0 | opnSecRq.requestedLifetime = client->config.secureChannelLifeTime; |
746 | 0 | if(renew) { |
747 | 0 | opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_RENEW; |
748 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
749 | 0 | "Requesting to renew the SecureChannel"); |
750 | 0 | } else { |
751 | 0 | opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_ISSUE; |
752 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
753 | 0 | "Requesting to open a SecureChannel"); |
754 | 0 | } |
755 | | |
756 | | /* Prepare the entry for the linked list */ |
757 | 0 | UA_UInt32 requestId = ++client->requestId; |
758 | | |
759 | | /* Send the OPN message */ |
760 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL, |
761 | 0 | "Requesting to open a SecureChannel"); |
762 | 0 | static const UA_DataType *opnReqType = &UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST]; |
763 | 0 | res = UA_SecureChannel_sendOPN(&client->channel, requestId, &opnSecRq, opnReqType); |
764 | 0 | if(res != UA_STATUSCODE_GOOD) { |
765 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL, |
766 | 0 | "Sending OPN message failed with error %s", |
767 | 0 | UA_StatusCode_name(res)); |
768 | 0 | return res; |
769 | 0 | } |
770 | | |
771 | | /* Update the state */ |
772 | 0 | client->channel.renewState = UA_SECURECHANNELRENEWSTATE_SENT; |
773 | 0 | if(client->channel.state < UA_SECURECHANNELSTATE_OPN_SENT) |
774 | 0 | client->channel.state = UA_SECURECHANNELSTATE_OPN_SENT; |
775 | |
|
776 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL, |
777 | 0 | "OPN message sent"); |
778 | |
|
779 | 0 | return UA_STATUSCODE_GOOD; |
780 | 0 | } |
781 | | |
782 | | UA_StatusCode |
783 | 0 | __Client_renewSecureChannel(UA_Client *client) { |
784 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
785 | |
|
786 | 0 | UA_EventLoop *el = client->config.eventLoop; |
787 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
788 | | |
789 | | /* Check if OPN has been sent or the SecureChannel is still valid */ |
790 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_OPEN || |
791 | 0 | client->channel.renewState == UA_SECURECHANNELRENEWSTATE_SENT || |
792 | 0 | client->nextChannelRenewal > now) |
793 | 0 | return UA_STATUSCODE_GOODCALLAGAIN; |
794 | | |
795 | 0 | UA_StatusCode res = sendOPNAsync(client, true); |
796 | 0 | setConnectStatus(client, res); |
797 | |
|
798 | 0 | return res; |
799 | 0 | } |
800 | | |
801 | | UA_StatusCode |
802 | 0 | UA_Client_renewSecureChannel(UA_Client *client) { |
803 | 0 | lockClient(client); |
804 | 0 | UA_StatusCode res = __Client_renewSecureChannel(client); |
805 | 0 | unlockClient(client); |
806 | 0 | return res; |
807 | 0 | } |
808 | | |
809 | | static void |
810 | | responseReadNamespacesArray(UA_Client *client, void *userdata, |
811 | 0 | UA_UInt32 requestId, void *response) { |
812 | 0 | client->namespacesHandshake = false; |
813 | 0 | client->haveNamespaces = true; |
814 | |
|
815 | 0 | UA_ReadResponse *resp = (UA_ReadResponse *)response; |
816 | | |
817 | | /* Validate the response before dereferencing results[0]. An |
818 | | * empty-array encoding yields UA_EMPTY_ARRAY_SENTINEL (0x1, non-NULL), |
819 | | * bypassing a bare "!resp->results" check. */ |
820 | 0 | if(resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD || |
821 | 0 | resp->resultsSize < 1 || |
822 | 0 | !resp->results || |
823 | 0 | !resp->results[0].hasValue || |
824 | 0 | !resp->results[0].value.data || |
825 | 0 | resp->results[0].value.data == UA_EMPTY_ARRAY_SENTINEL) { |
826 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
827 | 0 | "No result in the read namespace array response"); |
828 | 0 | return; |
829 | 0 | } |
830 | 0 | if(!UA_Variant_hasArrayType(&resp->results[0].value, &UA_TYPES[UA_TYPES_STRING])) { |
831 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
832 | 0 | "Read NamespaceArray returned an unexpected type"); |
833 | 0 | return; |
834 | 0 | } |
835 | 0 | UA_String *ns = (UA_String *)resp->results[0].value.data; |
836 | 0 | size_t nsSize = resp->results[0].value.arrayLength; |
837 | 0 | if(nsSize < 2) { |
838 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
839 | 0 | "Read NamespaceArray returned too few entries"); |
840 | 0 | return; |
841 | 0 | } |
842 | 0 | UA_String_copy(&ns[1], &client->namespaces[1]); |
843 | 0 | for(size_t i = 2; i < nsSize; ++i) { |
844 | 0 | UA_UInt16 nsIndex = 0; |
845 | 0 | UA_Client_addNamespace(client, ns[i], &nsIndex); |
846 | 0 | } |
847 | | |
848 | | /* Set up the mapping. */ |
849 | 0 | UA_NamespaceMapping *nsMapping = (UA_NamespaceMapping*) |
850 | 0 | UA_calloc(1, sizeof(UA_NamespaceMapping)); |
851 | 0 | if(!nsMapping) { |
852 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
853 | 0 | "Namespace mapping creation failed. Out of Memory."); |
854 | 0 | return; |
855 | 0 | } |
856 | 0 | UA_StatusCode retval = |
857 | 0 | UA_Array_copy(client->namespaces, client->namespacesSize, |
858 | 0 | (void**)&nsMapping->namespaceUris, |
859 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
860 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
861 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
862 | 0 | "Failed to copy the namespaces with StatusCode %s.", |
863 | 0 | UA_StatusCode_name(retval)); |
864 | 0 | UA_NamespaceMapping_delete(nsMapping); |
865 | 0 | return; |
866 | 0 | } |
867 | 0 | nsMapping->namespaceUrisSize = client->namespacesSize; |
868 | |
|
869 | 0 | nsMapping->remote2local = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16)); |
870 | 0 | if(!nsMapping->remote2local) { |
871 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
872 | 0 | "Namespace mapping creation failed. Out of Memory."); |
873 | 0 | UA_NamespaceMapping_delete(nsMapping); |
874 | 0 | return; |
875 | 0 | } |
876 | 0 | nsMapping->remote2localSize = nsSize; |
877 | 0 | nsMapping->remote2local[0] = 0; |
878 | 0 | nsMapping->remote2local[1] = 1; |
879 | |
|
880 | 0 | for(size_t i = 2; i < nsSize; ++i) { |
881 | 0 | UA_UInt16 nsIndex = 0; |
882 | 0 | UA_Client_getNamespaceIndex(client, ns[i], &nsIndex); |
883 | 0 | nsMapping->remote2local[i] = nsIndex; |
884 | 0 | } |
885 | |
|
886 | 0 | size_t l2rSize = client->namespacesSize > nsSize ? client->namespacesSize : nsSize; |
887 | 0 | nsMapping->local2remote = (UA_UInt16*)UA_calloc(l2rSize, sizeof(UA_UInt16)); |
888 | 0 | if(!nsMapping->local2remote) { |
889 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
890 | 0 | "Namespace mapping creation failed. Out of Memory."); |
891 | 0 | UA_NamespaceMapping_delete(nsMapping); |
892 | 0 | return; |
893 | 0 | } |
894 | 0 | nsMapping->local2remoteSize = l2rSize; |
895 | 0 | nsMapping->local2remote[0] = 0; |
896 | 0 | nsMapping->local2remote[1] = 1; |
897 | |
|
898 | 0 | for(size_t i = 2; i < nsMapping->remote2localSize; ++i) { |
899 | 0 | UA_UInt16 localIndex = nsMapping->remote2local[i]; |
900 | 0 | nsMapping->local2remote[localIndex] = (UA_UInt16)i; |
901 | 0 | } |
902 | |
|
903 | 0 | client->channel.namespaceMapping = nsMapping; |
904 | 0 | } |
905 | | |
906 | | /* We read the namespaces right after the session has opened. The user might |
907 | | * already requests other services in parallel. That leaves a short time where |
908 | | * requests can be made before the namespace mapping is configured. */ |
909 | | static void |
910 | 0 | readNamespacesArrayAsync(UA_Client *client) { |
911 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
912 | | |
913 | | /* Check the connection status */ |
914 | 0 | if(client->sessionState != UA_SESSIONSTATE_CREATED && |
915 | 0 | client->sessionState != UA_SESSIONSTATE_ACTIVATED) { |
916 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
917 | 0 | "Cannot read the namespaces array, session neither created nor " |
918 | 0 | "activated. Actual state: '%u'", client->sessionState); |
919 | 0 | return; |
920 | 0 | } |
921 | | |
922 | | /* Set up the read request */ |
923 | 0 | UA_ReadRequest rr; |
924 | 0 | UA_ReadRequest_init(&rr); |
925 | |
|
926 | 0 | UA_ReadValueId nodesToRead; |
927 | 0 | UA_ReadValueId_init(&nodesToRead); |
928 | 0 | nodesToRead.nodeId = UA_NS0ID(SERVER_NAMESPACEARRAY); |
929 | 0 | nodesToRead.attributeId = UA_ATTRIBUTEID_VALUE; |
930 | |
|
931 | 0 | rr.nodesToRead = &nodesToRead; |
932 | 0 | rr.nodesToReadSize = 1; |
933 | | |
934 | | /* Send the async read request */ |
935 | 0 | UA_StatusCode res = |
936 | 0 | __Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST], |
937 | 0 | (UA_ClientAsyncServiceCallback)responseReadNamespacesArray, |
938 | 0 | &UA_TYPES[UA_TYPES_READRESPONSE], |
939 | 0 | NULL, NULL); |
940 | |
|
941 | 0 | if(res == UA_STATUSCODE_GOOD) |
942 | 0 | client->namespacesHandshake = true; |
943 | 0 | else |
944 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
945 | 0 | "Could not read the namespace array with error code %s", |
946 | 0 | UA_StatusCode_name(res)); |
947 | 0 | } |
948 | | |
949 | | static void |
950 | 0 | extractEphemeralKeyFromAddHeader(UA_Client *client, UA_ExtensionObject *ah) { |
951 | 0 | const UA_DataType *ahType = &UA_TYPES[UA_TYPES_ADDITIONALPARAMETERSTYPE]; |
952 | 0 | if(!UA_ExtensionObject_hasDecodedType(ah, ahType)) |
953 | 0 | return; |
954 | | |
955 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SESSION, |
956 | 0 | "Server Ephemeral Key in the response"); |
957 | | |
958 | | /* KeyValueMap has the identical structure the UA_AdditionalParametersType */ |
959 | 0 | UA_KeyValueMap *map = (UA_KeyValueMap*)ah->content.decoded.data; |
960 | | |
961 | | /* TODO: Handle the ECDHPolicyUri */ |
962 | | |
963 | | /* Get the Ephemeral Key */ |
964 | 0 | UA_EphemeralKeyType *ephKey = (UA_EphemeralKeyType*)(uintptr_t) |
965 | 0 | UA_KeyValueMap_getScalar(map, UA_QUALIFIEDNAME(0, "ECDHKey"), |
966 | 0 | &UA_TYPES[UA_TYPES_EPHEMERALKEYTYPE]); |
967 | 0 | if(!ephKey) |
968 | 0 | return; /* TODO: Security error? */ |
969 | | |
970 | | /* Move the Ephemeral Key into the client */ |
971 | 0 | UA_ByteString_clear(&client->serverEphemeralPubKey); |
972 | 0 | client->serverEphemeralPubKey = ephKey->publicKey; |
973 | 0 | UA_ByteString_init(&ephKey->publicKey); |
974 | | |
975 | | /* TODO: Validate the signature of the ephemeral key */ |
976 | 0 | } |
977 | | |
978 | | static void |
979 | | responseActivateSession(UA_Client *client, void *userdata, |
980 | 0 | UA_UInt32 requestId, void *response) { |
981 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
982 | |
|
983 | 0 | UA_ActivateSessionResponse *ar = (UA_ActivateSessionResponse*)response; |
984 | 0 | if(ar->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
985 | | /* Activating the Session failed */ |
986 | 0 | cleanupSession(client); |
987 | | |
988 | | /* Configuration option to not create a new Session */ |
989 | 0 | if(client->config.noNewSession) { |
990 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
991 | 0 | "Session cannot be activated with StatusCode %s. " |
992 | 0 | "The client is configured not to create a new Session.", |
993 | 0 | UA_StatusCode_name(ar->responseHeader.serviceResult)); |
994 | 0 | setConnectStatus(client, ar->responseHeader.serviceResult); |
995 | 0 | return; |
996 | 0 | } |
997 | | |
998 | | /* The Session is no longer usable. Create a brand new one. */ |
999 | 0 | if(ar->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID || |
1000 | 0 | ar->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONCLOSED) { |
1001 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1002 | 0 | "Session to be activated no longer exists. Create a new Session."); |
1003 | 0 | setConnectStatus(client, createSessionAsync(client)); |
1004 | 0 | return; |
1005 | 0 | } |
1006 | | |
1007 | | /* Something else is wrong. Maybe the credentials no longer work. Give up. */ |
1008 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1009 | 0 | "Session cannot be activated with StatusCode %s. " |
1010 | 0 | "The client cannot recover from this, closing the connection.", |
1011 | 0 | UA_StatusCode_name(ar->responseHeader.serviceResult)); |
1012 | 0 | setConnectStatus(client, ar->responseHeader.serviceResult); |
1013 | 0 | return; |
1014 | 0 | } |
1015 | | |
1016 | | /* Check the (session) nonce length. The session nonce is the application |
1017 | | * nonce (>= 32 bytes), independent of the SecurityPolicy's SecureChannel |
1018 | | * nonce length (e.g. the 64-byte ECC ephemeral key). */ |
1019 | 0 | if(ar->serverNonce.length < 32) { |
1020 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1021 | 0 | "Session cannot be activated with a nonce " |
1022 | 0 | "that is too short"); |
1023 | 0 | setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED); |
1024 | 0 | return; |
1025 | 0 | } |
1026 | | |
1027 | | /* Replace the nonce */ |
1028 | 0 | UA_ByteString_clear(&client->serverSessionNonce); |
1029 | 0 | client->serverSessionNonce = ar->serverNonce; |
1030 | 0 | UA_ByteString_init(&ar->serverNonce); |
1031 | | |
1032 | | /* Extract the server's ECC ephemeral public key from the ActivateSession |
1033 | | * response */ |
1034 | 0 | extractEphemeralKeyFromAddHeader(client, &ar->responseHeader.additionalHeader); |
1035 | |
|
1036 | 0 | client->sessionState = UA_SESSIONSTATE_ACTIVATED; |
1037 | 0 | notifyClientState(client); |
1038 | | |
1039 | | /* Read the namespaces array if we don't already have it */ |
1040 | 0 | if(!client->haveNamespaces) |
1041 | 0 | readNamespacesArrayAsync(client); |
1042 | | |
1043 | | /* Immediately check if publish requests are outstanding - for example when |
1044 | | * an existing Session has been reattached / activated. */ |
1045 | 0 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
1046 | 0 | __Client_Subscriptions_backgroundPublish(client); |
1047 | 0 | #endif |
1048 | 0 | } |
1049 | | |
1050 | | static UA_StatusCode |
1051 | 0 | activateSessionAsync(UA_Client *client) { |
1052 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1053 | | |
1054 | | /* We should not try to activate in the current state */ |
1055 | 0 | if(client->sessionState != UA_SESSIONSTATE_CREATED && |
1056 | 0 | client->sessionState != UA_SESSIONSTATE_ACTIVATED) { |
1057 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1058 | 0 | "Can not activate session, session neither created nor activated. " |
1059 | 0 | "Actual state: '%u'", client->sessionState); |
1060 | 0 | return UA_STATUSCODE_BADSESSIONCLOSED; |
1061 | 0 | } |
1062 | | |
1063 | | /* Select the UserTokenPolicy based on the client configuration and the |
1064 | | * endpoint information. Also instantiates the UserTokenPolicy if not |
1065 | | * already there. This can happen when an existing Session is |
1066 | | * transferred. */ |
1067 | 0 | const UA_UserTokenPolicy *utp = NULL; |
1068 | 0 | UA_StatusCode retval = initUserTokenPolicy(client, &utp, "ActivateSession"); |
1069 | 0 | if(retval != UA_STATUSCODE_GOOD) |
1070 | 0 | return retval; |
1071 | | |
1072 | | /* Initialize the request */ |
1073 | 0 | UA_ActivateSessionRequest request; |
1074 | 0 | UA_ActivateSessionRequest_init(&request); |
1075 | | |
1076 | | /* Set the requested LocaleIds */ |
1077 | 0 | if(client->config.sessionLocaleIdsSize && client->config.sessionLocaleIds) { |
1078 | 0 | retval = UA_Array_copy(client->config.sessionLocaleIds, |
1079 | 0 | client->config.sessionLocaleIdsSize, |
1080 | 0 | (void **)&request.localeIds, &UA_TYPES[UA_TYPES_LOCALEID]); |
1081 | 0 | if(retval != UA_STATUSCODE_GOOD) |
1082 | 0 | return retval; |
1083 | 0 | request.localeIdsSize = client->config.sessionLocaleIdsSize; |
1084 | 0 | } |
1085 | | |
1086 | | /* Set the User Identity Token. If not defined use an anonymous token. Use |
1087 | | * the PolicyId from the UserTokenPolicy. All token types have the PolicyId |
1088 | | * string as the first element. */ |
1089 | 0 | UA_AnonymousIdentityToken anonToken; |
1090 | 0 | retval = UA_ExtensionObject_copy(&client->config.userIdentityToken, |
1091 | 0 | &request.userIdentityToken); |
1092 | 0 | if(request.userIdentityToken.encoding != UA_EXTENSIONOBJECT_ENCODED_NOBODY) { |
1093 | 0 | UA_String *policyId = (UA_String*)request.userIdentityToken.content.decoded.data; |
1094 | 0 | UA_String_clear(policyId); |
1095 | 0 | retval = UA_String_copy(&utp->policyId, policyId); |
1096 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
1097 | 0 | UA_ActivateSessionRequest_clear(&request); |
1098 | 0 | return retval; |
1099 | 0 | } |
1100 | 0 | } else { |
1101 | 0 | UA_AnonymousIdentityToken_init(&anonToken); |
1102 | 0 | UA_ExtensionObject_setValueNoDelete(&request.userIdentityToken, &anonToken, |
1103 | 0 | &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]); |
1104 | 0 | anonToken.policyId = utp->policyId; |
1105 | 0 | } |
1106 | | |
1107 | 0 | switch(utp->tokenType) { |
1108 | 0 | case UA_USERTOKENTYPE_ANONYMOUS: |
1109 | 0 | break; /* The anonymous token is not encrypted */ |
1110 | 0 | case UA_USERTOKENTYPE_USERNAME: |
1111 | 0 | case UA_USERTOKENTYPE_ISSUEDTOKEN: |
1112 | | /* Encrypt the UserIdentityToken */ |
1113 | 0 | retval = encryptUserIdentityToken(client, &request.userIdentityToken); |
1114 | 0 | break; |
1115 | 0 | case UA_USERTOKENTYPE_CERTIFICATE: |
1116 | | /* Create the UserTokenSignature */ |
1117 | 0 | retval = signUserTokenSignature(client, &request); |
1118 | 0 | break; |
1119 | 0 | default: |
1120 | 0 | retval = UA_STATUSCODE_BADINTERNALERROR; |
1121 | 0 | } |
1122 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
1123 | 0 | UA_ActivateSessionRequest_clear(&request); |
1124 | 0 | return retval; |
1125 | 0 | } |
1126 | | |
1127 | | /* Create the client signature with the SecurityPolicy of the SecureChannel */ |
1128 | 0 | UA_SecureChannel *channel = &client->channel; |
1129 | 0 | if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
1130 | 0 | channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
1131 | 0 | retval |= signClientSignature(client, &request); |
1132 | | |
1133 | | /* Send the request */ |
1134 | 0 | if(UA_LIKELY(retval == UA_STATUSCODE_GOOD)) |
1135 | 0 | retval = __Client_AsyncService(client, &request, |
1136 | 0 | &UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST], |
1137 | 0 | (UA_ClientAsyncServiceCallback)responseActivateSession, |
1138 | 0 | &UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE], |
1139 | 0 | NULL, NULL); |
1140 | | |
1141 | | /* On success, advance the session state */ |
1142 | 0 | if(retval == UA_STATUSCODE_GOOD) |
1143 | 0 | client->sessionState = UA_SESSIONSTATE_ACTIVATE_REQUESTED; |
1144 | 0 | else |
1145 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1146 | 0 | "ActivateSession failed when sending the request with error code %s", |
1147 | 0 | UA_StatusCode_name(retval)); |
1148 | | |
1149 | | /* Clean up */ |
1150 | 0 | UA_ActivateSessionRequest_clear(&request); |
1151 | 0 | return retval; |
1152 | 0 | } |
1153 | | |
1154 | | static const UA_String binaryTransport = |
1155 | | UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary"); |
1156 | | |
1157 | | /* Find a matching endpoint -- the UserTokenPolicy is matched later */ |
1158 | | static UA_Boolean |
1159 | 0 | matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigned i) { |
1160 | | /* Matching ApplicationUri if defined */ |
1161 | 0 | if(client->config.applicationUri.length > 0 && |
1162 | 0 | !UA_String_equal(&client->config.applicationUri, |
1163 | 0 | &endpoint->server.applicationUri)) { |
1164 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1165 | 0 | "Endpoint %u: Rejected, the server's ApplicationUri %S " |
1166 | 0 | "does not match the client configuration", i, |
1167 | 0 | endpoint->server.applicationUri); |
1168 | 0 | return false; |
1169 | 0 | } |
1170 | | |
1171 | | /* Look out for binary transport endpoints. |
1172 | | * Note: Siemens returns empty ProfileUrl, we will accept it as binary. */ |
1173 | 0 | if(endpoint->transportProfileUri.length != 0 && |
1174 | 0 | !UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) { |
1175 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1176 | 0 | "Endpoint %u: Rejected, the TransportProfileUri %S " |
1177 | 0 | "is not supported", i, endpoint->transportProfileUri); |
1178 | 0 | return false; |
1179 | 0 | } |
1180 | | |
1181 | | /* Valid SecurityMode? */ |
1182 | 0 | if(endpoint->securityMode < 1 || endpoint->securityMode > 3) { |
1183 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1184 | 0 | "Endpoint %u: Rejected, invalid SecurityMode %u", |
1185 | 0 | i, (unsigned)endpoint->securityMode); |
1186 | 0 | return false; |
1187 | 0 | } |
1188 | | |
1189 | 0 | UA_MessageSecurityMode configuredSM = client->config.securityMode; |
1190 | 0 | if(configuredSM > UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { |
1191 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1192 | 0 | "Bad SecurityMode set in the client config"); |
1193 | 0 | return false; |
1194 | 0 | } |
1195 | | |
1196 | | /* Selected SecurityMode? */ |
1197 | 0 | if(configuredSM > 0 && configuredSM != endpoint->securityMode) { |
1198 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1199 | 0 | "Endpoint %u: Rejected, the SecurityMode %s " |
1200 | 0 | "does not match the client configuration (%s)", |
1201 | 0 | i, securityModeNames[endpoint->securityMode], |
1202 | 0 | securityModeNames[configuredSM]); |
1203 | 0 | return false; |
1204 | 0 | } |
1205 | | |
1206 | | /* Matching SecurityPolicy? */ |
1207 | 0 | if(client->config.securityPolicyUri.length > 0 && |
1208 | 0 | !UA_String_equal(&client->config.securityPolicyUri, |
1209 | 0 | &endpoint->securityPolicyUri)) { |
1210 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1211 | 0 | "Endpoint %u: Rejected, the SecurityPolicy %S does not " |
1212 | 0 | "match the configuration", i, endpoint->securityPolicyUri); |
1213 | 0 | return false; |
1214 | 0 | } |
1215 | | |
1216 | | /* SecurityPolicy available? */ |
1217 | 0 | if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) { |
1218 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1219 | 0 | "Endpoint %u: Rejected, the SecurityPolicy %S not supported", |
1220 | 0 | i, endpoint->securityPolicyUri); |
1221 | 0 | return false; |
1222 | 0 | } |
1223 | | |
1224 | 0 | return true; |
1225 | 0 | } |
1226 | | |
1227 | | /* Match the policy with the configured user token */ |
1228 | | static UA_Boolean |
1229 | | matchUserToken(UA_Client *client, |
1230 | 0 | const UA_UserTokenPolicy *tokenPolicy) { |
1231 | 0 | const UA_DataType *tokenType = |
1232 | 0 | client->config.userIdentityToken.content.decoded.type; |
1233 | |
|
1234 | 0 | if(tokenPolicy->tokenType == UA_USERTOKENTYPE_ANONYMOUS && |
1235 | 0 | (tokenType == &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] || !tokenType)) |
1236 | 0 | return true; |
1237 | | |
1238 | 0 | if(tokenPolicy->tokenType == UA_USERTOKENTYPE_USERNAME && |
1239 | 0 | tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) |
1240 | 0 | return true; |
1241 | | |
1242 | 0 | if(tokenPolicy->tokenType == UA_USERTOKENTYPE_CERTIFICATE && |
1243 | 0 | tokenType == &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) |
1244 | 0 | return true; |
1245 | | |
1246 | 0 | if(tokenPolicy->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN && |
1247 | 0 | tokenType == &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) |
1248 | 0 | return true; |
1249 | | |
1250 | 0 | return false; |
1251 | 0 | } |
1252 | | |
1253 | | static UA_Boolean |
1254 | | matchUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint, |
1255 | 0 | UA_UserTokenPolicy *utp, char *logPrefix) { |
1256 | | /* Match with the configured UserToken */ |
1257 | 0 | if(!matchUserToken(client, utp)) { |
1258 | 0 | if(logPrefix) { |
1259 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1260 | 0 | "%s: UserTokenPolicy %S rejected -- does not match " |
1261 | 0 | "the type of UserIdentityToken configured in the client", |
1262 | 0 | logPrefix, utp->policyId); |
1263 | 0 | } |
1264 | 0 | return false; |
1265 | 0 | } |
1266 | | |
1267 | | /* If not defined, use the SecurityPolicy of the SecureChannel */ |
1268 | 0 | UA_String tokenPolicyUri = |
1269 | 0 | (UA_String_isEmpty(&utp->securityPolicyUri)) ? |
1270 | 0 | endpoint->securityPolicyUri : utp->securityPolicyUri; |
1271 | | |
1272 | | /* SecurityPolicyUri matches the configuration? */ |
1273 | 0 | if(!UA_String_isEmpty(&client->config.authSecurityPolicyUri) && |
1274 | 0 | !UA_String_equal(&client->config.authSecurityPolicyUri, |
1275 | 0 | &tokenPolicyUri)) { |
1276 | 0 | if(logPrefix) { |
1277 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1278 | 0 | "%s: UserTokenPolicy %S rejected -- uses SecurityPolicy %S, " |
1279 | 0 | "but the client configuration requires SecurityPolicy %S", |
1280 | 0 | logPrefix, utp->policyId, |
1281 | 0 | tokenPolicyUri, client->config.authSecurityPolicyUri); |
1282 | 0 | } |
1283 | 0 | return false; |
1284 | 0 | } |
1285 | | |
1286 | | /* Anoymous authentication */ |
1287 | 0 | if(utp->tokenType == UA_USERTOKENTYPE_ANONYMOUS) |
1288 | 0 | return true; |
1289 | | |
1290 | | /* Get the SecurityPolicy for the UserIdentityToken (see |
1291 | | * initUserTokenPolicy for the authSecurityPolicies/securityPolicies |
1292 | | * lookup rationale). */ |
1293 | 0 | UA_SecurityPolicy *utsp; |
1294 | 0 | if(utp->tokenType == UA_USERTOKENTYPE_CERTIFICATE) { |
1295 | 0 | UA_X509IdentityToken *token = (UA_X509IdentityToken*) |
1296 | 0 | client->config.userIdentityToken.content.decoded.data; |
1297 | 0 | utsp = getAuthSecurityPolicy(client, tokenPolicyUri, |
1298 | 0 | &token->certificateData); |
1299 | 0 | } else { |
1300 | 0 | utsp = getAuthSecurityPolicy(client, tokenPolicyUri, NULL); |
1301 | 0 | if(!utsp) |
1302 | 0 | utsp = getSecurityPolicy(client, tokenPolicyUri); |
1303 | 0 | } |
1304 | 0 | if(!utsp) { |
1305 | 0 | if(logPrefix) { |
1306 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1307 | 0 | "%s: UserTokenPolicy %S rejected -- the " |
1308 | 0 | "SecurityPolicy %S is not available", |
1309 | 0 | logPrefix, utp->policyId, tokenPolicyUri); |
1310 | 0 | } |
1311 | 0 | return false; |
1312 | 0 | } |
1313 | | |
1314 | | /* Check if a secret is transmitted in cleartext */ |
1315 | 0 | if(endpoint->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT && |
1316 | 0 | utsp->policyType == UA_SECURITYPOLICYTYPE_NONE) { |
1317 | 0 | if(!client->config.allowNonePolicyPassword) { |
1318 | 0 | if(logPrefix) { |
1319 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1320 | 0 | "%s: UserTokenPolicy %S rejected -- the " |
1321 | 0 | "AuthenticationToken must not be transmitted " |
1322 | 0 | "without encryption (override with" |
1323 | 0 | "allowNonePolicyPassword setting)", |
1324 | 0 | logPrefix, utp->policyId); |
1325 | 0 | } |
1326 | 0 | return false; |
1327 | 0 | } |
1328 | 0 | } |
1329 | | |
1330 | | /* Found a match */ |
1331 | 0 | return true; |
1332 | 0 | } |
1333 | | |
1334 | | /* Returns a matching UserTokenPolicy from the EndpointDescription. If a |
1335 | | * UserTokenPolicy is configured in the client config, then we need an exact |
1336 | | * match. */ |
1337 | | static UA_UserTokenPolicy * |
1338 | | findUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint, |
1339 | 0 | char *logPrefix) { |
1340 | | /* Was a UserTokenPolicy configured? Then we need an exact match. */ |
1341 | 0 | UA_UserTokenPolicy *requiredTokenPolicy = NULL; |
1342 | 0 | UA_UserTokenPolicy tmp; |
1343 | 0 | UA_UserTokenPolicy_init(&tmp); |
1344 | 0 | if(!UA_equal(&tmp, &client->config.userTokenPolicy, |
1345 | 0 | &UA_TYPES[UA_TYPES_USERTOKENPOLICY])) |
1346 | 0 | requiredTokenPolicy = &client->config.userTokenPolicy; |
1347 | | |
1348 | | /* Return the first UserTokenPolicy matching the config */ |
1349 | 0 | for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) { |
1350 | 0 | UA_UserTokenPolicy *tokenPolicy = &endpoint->userIdentityTokens[j]; |
1351 | | |
1352 | | /* Need the extact configured policy */ |
1353 | 0 | if(requiredTokenPolicy && |
1354 | 0 | !UA_equal(requiredTokenPolicy, tokenPolicy, |
1355 | 0 | &UA_TYPES[UA_TYPES_USERTOKENPOLICY])) { |
1356 | 0 | if(logPrefix) { |
1357 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1358 | 0 | "%s: UserTokenPolicy %S rejected -- a different " |
1359 | 0 | "UserTokenPolicy %S is specified in the client config", |
1360 | 0 | logPrefix, tokenPolicy->policyId, |
1361 | 0 | requiredTokenPolicy->policyId); |
1362 | 0 | } |
1363 | 0 | continue; |
1364 | 0 | } |
1365 | | |
1366 | | /* Match with the client configured */ |
1367 | 0 | if(matchUserTokenPolicy(client, endpoint, tokenPolicy, logPrefix)) |
1368 | 0 | return tokenPolicy; |
1369 | 0 | } |
1370 | | |
1371 | 0 | return NULL; |
1372 | 0 | } |
1373 | | |
1374 | | /* Combination of UA_Client_getEndpointsInternal and getEndpoints */ |
1375 | | static void |
1376 | | responseGetEndpoints(UA_Client *client, void *userdata, |
1377 | 0 | UA_UInt32 requestId, void *response) { |
1378 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1379 | |
|
1380 | 0 | client->endpointsHandshake = false; |
1381 | |
|
1382 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1383 | 0 | "Received GetEndpointsResponse"); |
1384 | |
|
1385 | 0 | UA_GetEndpointsResponse *resp = (UA_GetEndpointsResponse*)response; |
1386 | | |
1387 | | /* GetEndpoints not possible. Fail the connection */ |
1388 | 0 | if(resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
1389 | | /* Fail the connection attempt if the SecureChannel is still connected. |
1390 | | * If the SecureChannel is (intentionally or unintentionally) closed, |
1391 | | * the connectStatus should come from there. */ |
1392 | 0 | if(UA_SecureChannel_isConnected(&client->channel)) { |
1393 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1394 | 0 | "GetEndpointRequest failed with error code %s", |
1395 | 0 | UA_StatusCode_name(resp->responseHeader.serviceResult)); |
1396 | 0 | setConnectStatus(client, resp->responseHeader.serviceResult); |
1397 | 0 | } |
1398 | |
|
1399 | 0 | UA_GetEndpointsResponse_clear(resp); |
1400 | 0 | return; |
1401 | 0 | } |
1402 | | |
1403 | | /* Warn if the Endpoints look incomplete / don't match the EndpointUrl */ |
1404 | 0 | Client_warnEndpointsResult(client, resp, &client->discoveryUrl); |
1405 | |
|
1406 | 0 | const size_t notFound = (size_t)-1; |
1407 | 0 | size_t bestEndpointIndex = notFound; |
1408 | 0 | UA_Byte bestEndpointLevel = 0; |
1409 | 0 | UA_UserTokenPolicy *utp = NULL; |
1410 | | |
1411 | | /* Find a matching combination of Endpoint and UserTokenPolicy */ |
1412 | 0 | for(size_t i = 0; i < resp->endpointsSize; ++i) { |
1413 | 0 | UA_EndpointDescription* endpoint = &resp->endpoints[i]; |
1414 | | |
1415 | | /* Do we already have a better candidate? */ |
1416 | 0 | if(endpoint->securityLevel < bestEndpointLevel) { |
1417 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1418 | 0 | "Endpoint %u: Rejected, better SecurityLevel found before", |
1419 | 0 | (unsigned)i); |
1420 | 0 | continue; |
1421 | 0 | } |
1422 | | |
1423 | | /* Does the endpoint match the client configuration? */ |
1424 | 0 | if(!matchEndpoint(client, endpoint, (unsigned)i)) |
1425 | 0 | continue; |
1426 | | |
1427 | | /* Do we want a session? If yes, then the endpoint needs to have a |
1428 | | * UserTokenPolicy that matches the configuration. */ |
1429 | 0 | if(!client->config.noSession) { |
1430 | 0 | char logPrefix[32]; |
1431 | 0 | mp_snprintf(logPrefix, 32, "Endpoint %u", (unsigned)i); |
1432 | 0 | utp = findUserTokenPolicy(client, endpoint, logPrefix); |
1433 | 0 | if(!utp) { |
1434 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1435 | 0 | "Endpoint %u: Rejected, no matching UserTokenPolicy", |
1436 | 0 | (unsigned)i); |
1437 | 0 | continue; |
1438 | 0 | } |
1439 | 0 | } |
1440 | | |
1441 | | /* Best endpoint so far */ |
1442 | 0 | bestEndpointLevel = endpoint->securityLevel; |
1443 | 0 | bestEndpointIndex = i; |
1444 | 0 | if(utp) { |
1445 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1446 | 0 | "Endpoint %u: Best endpoint so far with " |
1447 | 0 | "UserTokenPolicy %S", (unsigned)i, utp->policyId); |
1448 | 0 | } else { |
1449 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1450 | 0 | "Endpoint %u: Best endpoint so far", (unsigned)i); |
1451 | 0 | } |
1452 | 0 | } |
1453 | | |
1454 | | /* No matching endpoint found */ |
1455 | 0 | if(bestEndpointIndex == notFound) { |
1456 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1457 | 0 | "No suitable endpoint found"); |
1458 | 0 | setConnectStatus(client, UA_STATUSCODE_BADIDENTITYTOKENREJECTED); |
1459 | 0 | return; |
1460 | 0 | } |
1461 | | |
1462 | | /* Store the endpoint description in the client. It contains the |
1463 | | * ApplicationDescription and the UserTokenPolicies. We continue to look up |
1464 | | * the matching UserTokenPolicy from there. */ |
1465 | 0 | UA_EndpointDescription_clear(&client->endpoint); |
1466 | 0 | client->endpoint = resp->endpoints[bestEndpointIndex]; |
1467 | 0 | UA_EndpointDescription_init(&resp->endpoints[bestEndpointIndex]); |
1468 | |
|
1469 | 0 | if(utp) { |
1470 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1471 | 0 | "Endpoint %u selected with SecurityMode " |
1472 | 0 | "%s, SecurityPolicy %S and UserTokenPolicy %S", |
1473 | 0 | bestEndpointIndex, |
1474 | 0 | securityModeNames[client->endpoint.securityMode], |
1475 | 0 | client->endpoint.securityPolicyUri, |
1476 | 0 | utp->policyId); |
1477 | 0 | } else { |
1478 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1479 | 0 | "Endpoint %u selected with SecurityMode " |
1480 | 0 | "%s and SecurityPolicy %S", bestEndpointIndex, |
1481 | 0 | securityModeNames[client->endpoint.securityMode], |
1482 | 0 | client->endpoint.securityPolicyUri); |
1483 | 0 | } |
1484 | | |
1485 | | /* A different SecurityMode or SecurityPolicy is defined by the Endpoint. |
1486 | | * Close the SecureChannel and reconnect. */ |
1487 | 0 | UA_SecurityPolicy *sp = client->channel.securityPolicy; |
1488 | 0 | if(client->endpoint.securityMode != client->channel.securityMode || |
1489 | 0 | !UA_String_equal(&client->endpoint.securityPolicyUri, &sp->policyUri)) { |
1490 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1491 | 0 | "A different SecurityMode or SecurityPolicy is defined " |
1492 | 0 | "by the selected Endpoint. Close the SecureChannel " |
1493 | 0 | "and reconnect."); |
1494 | 0 | closeSecureChannel(client); |
1495 | 0 | return; |
1496 | 0 | } |
1497 | | |
1498 | | /* We were using a distinct discovery URL and we are switching away from it. |
1499 | | * Close the SecureChannel to reopen with the EndpointUrl. If an endpoint |
1500 | | * was selected, then we use the endpointUrl for the HEL message. */ |
1501 | 0 | if(client->discoveryUrl.length > 0 && |
1502 | 0 | !UA_String_equal(&client->discoveryUrl, &client->endpoint.endpointUrl)) { |
1503 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1504 | 0 | "The selected endpoint defines an EndpointUrl %S different " |
1505 | 0 | "from the Url %S used to connect before calling " |
1506 | 0 | "GetEndpoints. Close the SecureChannel and reconnect with " |
1507 | 0 | "the new EndpointUrl to ensure the Endpoint is available.", |
1508 | 0 | client->discoveryUrl, client->endpoint.endpointUrl); |
1509 | 0 | closeSecureChannel(client); |
1510 | 0 | return; |
1511 | 0 | } |
1512 | | |
1513 | | /* We have selected an endpoint that we can use to open a Session on the |
1514 | | * current SecureChannel. The remote certificate was validated during |
1515 | | * processing of the OPN response. But if client->endpoint.serverCertificate |
1516 | | * is empty we do not compare against the expectation from the endpoint. */ |
1517 | | |
1518 | | /* Is the same certificate used for the SecureChannel and the selected |
1519 | | * endpoint? |
1520 | | * |
1521 | | * Both the clientCertificate of this request and the remoteCertificate of |
1522 | | * the channel may contain a partial or a complete certificate chain. The |
1523 | | * compareCertificate function will compare the first certificate of each |
1524 | | * chain. The end certificate shall be located first in the chain according |
1525 | | * to the OPC UA specification Part 6 (1.04), chapter 6.2.3.*/ |
1526 | 0 | if(client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE) { |
1527 | 0 | void *cc = client->channel.channelContext; |
1528 | 0 | UA_StatusCode res = sp->compareCertificate(sp, cc, |
1529 | 0 | &client->endpoint.serverCertificate); |
1530 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1531 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1532 | 0 | "The selected endpoint defines a different server certificate " |
1533 | 0 | "from then one used by the server for the initial SecureChannel " |
1534 | 0 | "(to call GetEndpoints). Close the SecureChannel and reconnect."); |
1535 | 0 | closeSecureChannel(client); |
1536 | 0 | return; |
1537 | 0 | } |
1538 | 0 | } |
1539 | | |
1540 | | /* Continue with the current SecureChannel */ |
1541 | 0 | } |
1542 | | |
1543 | | static UA_StatusCode |
1544 | 0 | requestGetEndpoints(UA_Client *client) { |
1545 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1546 | |
|
1547 | 0 | UA_GetEndpointsRequest request; |
1548 | 0 | UA_GetEndpointsRequest_init(&request); |
1549 | 0 | request.endpointUrl = getEndpointUrl(client); |
1550 | |
|
1551 | 0 | UA_StatusCode retval = |
1552 | 0 | __Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST], |
1553 | 0 | (UA_ClientAsyncServiceCallback) responseGetEndpoints, |
1554 | 0 | &UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE], NULL, NULL); |
1555 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
1556 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1557 | 0 | "RequestGetEndpoints failed when sending the request with error code %s", |
1558 | 0 | UA_StatusCode_name(retval)); |
1559 | 0 | return retval; |
1560 | 0 | } |
1561 | | |
1562 | 0 | client->endpointsHandshake = true; |
1563 | 0 | return UA_STATUSCODE_GOOD; |
1564 | 0 | } |
1565 | | |
1566 | | static void |
1567 | | responseFindServers(UA_Client *client, void *userdata, |
1568 | 0 | UA_UInt32 requestId, void *response) { |
1569 | 0 | client->findServersHandshake = false; |
1570 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1571 | 0 | "Received FindServersResponse"); |
1572 | | |
1573 | | /* Error handling. Log the error but continue to connect with the current |
1574 | | * EndpointURL. */ |
1575 | 0 | UA_FindServersResponse *fsr = (UA_FindServersResponse*)response; |
1576 | 0 | if(fsr->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
1577 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1578 | 0 | "FindServers failed with error code %s. Continue with the " |
1579 | 0 | "EndpointURL %S.", |
1580 | 0 | UA_StatusCode_name(fsr->responseHeader.serviceResult), |
1581 | 0 | client->config.endpointUrl); |
1582 | 0 | UA_String_clear(&client->discoveryUrl); |
1583 | 0 | UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl); |
1584 | 0 | return; |
1585 | 0 | } |
1586 | | |
1587 | | /* Check if one of the returned servers matches the EndpointURL already used */ |
1588 | 0 | for(size_t i = 0; i < fsr->serversSize; i++) { |
1589 | 0 | UA_ApplicationDescription *server = &fsr->servers[i]; |
1590 | | |
1591 | | /* Filter by the ApplicationUri if defined */ |
1592 | 0 | if(client->config.applicationUri.length > 0 && |
1593 | 0 | !UA_String_equal(&client->config.applicationUri, &server->applicationUri)) |
1594 | 0 | continue; |
1595 | | |
1596 | 0 | for(size_t j = 0; j < server->discoveryUrlsSize; j++) { |
1597 | 0 | if(UA_String_equal(&client->config.endpointUrl, &server->discoveryUrls[j])) { |
1598 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1599 | 0 | "The initially defined EndpointURL %S " |
1600 | 0 | "is valid for the server", client->config.endpointUrl); |
1601 | 0 | UA_String_clear(&client->discoveryUrl); |
1602 | 0 | client->discoveryUrl = server->discoveryUrls[j]; |
1603 | 0 | UA_String_init(&server->discoveryUrls[j]); |
1604 | 0 | return; |
1605 | 0 | } |
1606 | 0 | } |
1607 | 0 | } |
1608 | | |
1609 | | /* The current EndpointURL is not usable. Pick the first "opc.tcp" DiscoveryUrl of a |
1610 | | * returned server. */ |
1611 | 0 | for(size_t i = 0; i < fsr->serversSize; i++) { |
1612 | 0 | UA_ApplicationDescription *server = &fsr->servers[i]; |
1613 | 0 | if(server->applicationType != UA_APPLICATIONTYPE_SERVER && |
1614 | 0 | server->applicationType != UA_APPLICATIONTYPE_CLIENTANDSERVER && |
1615 | 0 | server->applicationType != UA_APPLICATIONTYPE_DISCOVERYSERVER) |
1616 | 0 | continue; |
1617 | | |
1618 | | /* Filter by the ApplicationUri if defined */ |
1619 | 0 | if(client->config.applicationUri.length > 0 && |
1620 | 0 | !UA_String_equal(&client->config.applicationUri, &server->applicationUri)) |
1621 | 0 | continue; |
1622 | | |
1623 | 0 | for(size_t j = 0; j < server->discoveryUrlsSize; j++) { |
1624 | | /* Try to parse the DiscoveryUrl. This weeds out http schemas (etc.) |
1625 | | * and invalid DiscoveryUrls in general. */ |
1626 | 0 | UA_String hostname, path; |
1627 | 0 | UA_UInt16 port; |
1628 | 0 | UA_StatusCode res = |
1629 | 0 | UA_parseEndpointUrl(&server->discoveryUrls[j], &hostname, &port, &path); |
1630 | 0 | if(res != UA_STATUSCODE_GOOD) |
1631 | 0 | continue; |
1632 | | |
1633 | | /* Use this DiscoveryUrl in the client */ |
1634 | 0 | UA_String_clear(&client->discoveryUrl); |
1635 | 0 | client->discoveryUrl = server->discoveryUrls[j]; |
1636 | 0 | UA_String_init(&server->discoveryUrls[j]); |
1637 | |
|
1638 | 0 | UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1639 | 0 | "Use the EndpointURL %S returned from FindServers and reconnect", |
1640 | 0 | client->discoveryUrl); |
1641 | | |
1642 | | /* Close the SecureChannel to build it up new with the correct |
1643 | | * EndpointURL in the HEL/ACK handshake. In closeSecureChannel the |
1644 | | * received client->endpoint is reset also. */ |
1645 | 0 | closeSecureChannel(client); |
1646 | 0 | return; |
1647 | 0 | } |
1648 | 0 | } |
1649 | | |
1650 | | /* Could not find a suitable server. Try to continue with the |
1651 | | * original EndpointURL. */ |
1652 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1653 | 0 | "FindServers did not returned a suitable DiscoveryURL. " |
1654 | 0 | "Continue with the EndpointURL %S.", client->config.endpointUrl); |
1655 | 0 | UA_String_clear(&client->discoveryUrl); |
1656 | 0 | UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl); |
1657 | 0 | } |
1658 | | |
1659 | | static UA_StatusCode |
1660 | 0 | requestFindServers(UA_Client *client) { |
1661 | 0 | UA_FindServersRequest request; |
1662 | 0 | UA_FindServersRequest_init(&request); |
1663 | 0 | request.requestHeader.timeoutHint = 10000; |
1664 | 0 | request.endpointUrl = client->config.endpointUrl; |
1665 | 0 | UA_StatusCode retval = |
1666 | 0 | __Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST], |
1667 | 0 | (UA_ClientAsyncServiceCallback) responseFindServers, |
1668 | 0 | &UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], NULL, NULL); |
1669 | 0 | if(retval != UA_STATUSCODE_GOOD) { |
1670 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1671 | 0 | "FindServers failed when sending the request with error code %s", |
1672 | 0 | UA_StatusCode_name(retval)); |
1673 | 0 | return retval; |
1674 | 0 | } |
1675 | | |
1676 | 0 | client->findServersHandshake = true; |
1677 | 0 | return UA_STATUSCODE_GOOD; |
1678 | 0 | } |
1679 | | |
1680 | | static void |
1681 | | createSessionCallback(UA_Client *client, void *userdata, |
1682 | 0 | UA_UInt32 requestId, void *response) { |
1683 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1684 | |
|
1685 | 0 | UA_CreateSessionResponse *csr = (UA_CreateSessionResponse*)response; |
1686 | 0 | UA_StatusCode res = csr->responseHeader.serviceResult; |
1687 | 0 | if(res != UA_STATUSCODE_GOOD) |
1688 | 0 | goto cleanup; |
1689 | | |
1690 | 0 | if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN || |
1691 | 0 | client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { |
1692 | | /* Verify the session response was created with the same certificate as |
1693 | | * the SecureChannel */ |
1694 | 0 | if(!UA_ByteString_equal(&csr->serverCertificate, |
1695 | 0 | &client->channel.remoteCertificate)) { |
1696 | 0 | res = UA_STATUSCODE_BADCERTIFICATEINVALID; |
1697 | 0 | goto cleanup; |
1698 | 0 | } |
1699 | | |
1700 | | /* Verify the client signature */ |
1701 | 0 | res = checkCreateSessionSignature(client, &client->channel, csr); |
1702 | 0 | if(res != UA_STATUSCODE_GOOD) |
1703 | 0 | goto cleanup; |
1704 | 0 | } |
1705 | | |
1706 | | /* Copy the SessionId */ |
1707 | 0 | UA_NodeId_clear(&client->sessionId); |
1708 | 0 | res |= UA_NodeId_copy(&csr->sessionId, &client->sessionId); |
1709 | | |
1710 | | /* Check the (session) nonce length. The session nonce is the application |
1711 | | * nonce (>= 32 bytes), independent of the SecurityPolicy's SecureChannel |
1712 | | * nonce length (e.g. the 64-byte ECC ephemeral key). */ |
1713 | 0 | if(csr->serverNonce.length < 32) { |
1714 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1715 | 0 | "Session cannot be created with a nonce " |
1716 | 0 | "that is too short"); |
1717 | 0 | res = UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
1718 | 0 | goto cleanup; |
1719 | 0 | } |
1720 | | |
1721 | | /* Copy nonce and AuthenticationToken */ |
1722 | 0 | UA_ByteString_clear(&client->serverSessionNonce); |
1723 | 0 | UA_NodeId_clear(&client->authenticationToken); |
1724 | 0 | res |= UA_ByteString_copy(&csr->serverNonce, &client->serverSessionNonce); |
1725 | 0 | res |= UA_NodeId_copy(&csr->authenticationToken, &client->authenticationToken); |
1726 | 0 | if(res != UA_STATUSCODE_GOOD) |
1727 | 0 | goto cleanup; |
1728 | | |
1729 | | /* Extract the server's ECC ephemeral public key from the CreateSession |
1730 | | * response */ |
1731 | 0 | extractEphemeralKeyFromAddHeader(client, &csr->responseHeader.additionalHeader); |
1732 | | |
1733 | | /* Activate the new Session */ |
1734 | 0 | client->sessionState = UA_SESSIONSTATE_CREATED; |
1735 | |
|
1736 | 0 | cleanup: |
1737 | 0 | setConnectStatus(client, res); |
1738 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
1739 | 0 | client->sessionState = UA_SESSIONSTATE_CLOSED; |
1740 | 0 | } |
1741 | | |
1742 | | /* OPC UA Part 6 ECC: request an ephemeral ECDH key from the server by adding an |
1743 | | * "ECDHPolicyUri" entry (value = the user-token SecurityPolicy URI) to the |
1744 | | * request's AdditionalHeader. The server returns its ephemeral public key under |
1745 | | * "ECDHKey" in the response AdditionalHeader, which the client needs to encrypt |
1746 | | * an ECC UserIdentityToken. For non-ECC user-token policies this is a no-op. |
1747 | | * The caller must clear the AdditionalHeader after sending. */ |
1748 | | static UA_StatusCode |
1749 | 0 | requestServerEphemeralKey(UA_Client *client, UA_RequestHeader *rh) { |
1750 | 0 | UA_SecurityPolicy *utpSp = client->utpSp; |
1751 | 0 | if(!UA_SecurityPolicy_isEcc(utpSp)) |
1752 | 0 | return UA_STATUSCODE_GOOD; |
1753 | | |
1754 | 0 | UA_AdditionalParametersType *ap = UA_AdditionalParametersType_new(); |
1755 | 0 | if(!ap) |
1756 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1757 | 0 | ap->parameters = (UA_KeyValuePair*) |
1758 | 0 | UA_Array_new(1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); |
1759 | 0 | if(!ap->parameters) { |
1760 | 0 | UA_AdditionalParametersType_delete(ap); |
1761 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
1762 | 0 | } |
1763 | 0 | ap->parametersSize = 1; |
1764 | 0 | ap->parameters[0].key = UA_QUALIFIEDNAME_ALLOC(0, "ECDHPolicyUri"); |
1765 | 0 | UA_StatusCode res = |
1766 | 0 | UA_Variant_setScalarCopy(&ap->parameters[0].value, &utpSp->policyUri, |
1767 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
1768 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1769 | 0 | UA_AdditionalParametersType_delete(ap); |
1770 | 0 | return res; |
1771 | 0 | } |
1772 | 0 | UA_ExtensionObject_setValue(&rh->additionalHeader, ap, |
1773 | 0 | &UA_TYPES[UA_TYPES_ADDITIONALPARAMETERSTYPE]); |
1774 | 0 | return UA_STATUSCODE_GOOD; |
1775 | 0 | } |
1776 | | |
1777 | | static UA_StatusCode |
1778 | 0 | createSessionAsync(UA_Client *client) { |
1779 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1780 | | |
1781 | | /* Select the UserTokenPolicy based on the client configuration and the |
1782 | | * endpoint information. Also instantiates the UserTokenPolicy if not |
1783 | | * already there. */ |
1784 | 0 | const UA_UserTokenPolicy *utp = NULL; |
1785 | 0 | UA_StatusCode res = initUserTokenPolicy(client, &utp, "CreateSession"); |
1786 | 0 | if(res != UA_STATUSCODE_GOOD) |
1787 | 0 | return res; |
1788 | 0 | (void)utp; |
1789 | | |
1790 | | /* Create the CreateSession ClientNonce: a 32-byte random nonce when the |
1791 | | * SecureChannel is secured. For ECC SecurityPolicies the ClientNonce is |
1792 | | * NOT the session ephemeral key — the ephemeral keys are exchanged |
1793 | | * separately (the server's arrives in the response AdditionalHeader; the |
1794 | | * client's, when needed for UserIdentityToken encryption, is carried in |
1795 | | * the EccEncryptedSecret). Sending the ephemeral key as the nonce is |
1796 | | * rejected by conformant servers with BadNonceInvalid. */ |
1797 | 0 | UA_SecurityPolicy *sp = client->channel.securityPolicy; |
1798 | 0 | if(sp && client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE) { |
1799 | 0 | size_t nonceLength = 32; |
1800 | 0 | if(client->clientSessionNonce.length != nonceLength) { |
1801 | 0 | UA_ByteString_clear(&client->clientSessionNonce); |
1802 | 0 | res = UA_ByteString_allocBuffer(&client->clientSessionNonce, nonceLength); |
1803 | 0 | if(res != UA_STATUSCODE_GOOD) |
1804 | 0 | return res; |
1805 | 0 | } |
1806 | | /* Ensure data[0] is not the 'e' of the "eph" ECC ephemeral-key |
1807 | | * trigger, so generateNonce produces plain random data. */ |
1808 | 0 | client->clientSessionNonce.data[0] = 0; |
1809 | 0 | res = sp->generateNonce(sp, client->channel.channelContext, |
1810 | 0 | &client->clientSessionNonce); |
1811 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1812 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1813 | 0 | "CreateSession could not create the client nonce"); |
1814 | 0 | return res; |
1815 | 0 | } |
1816 | 0 | } |
1817 | | |
1818 | | /* Prepare the request */ |
1819 | 0 | UA_CreateSessionRequest request; |
1820 | 0 | UA_CreateSessionRequest_init(&request); |
1821 | 0 | request.clientNonce = client->clientSessionNonce; |
1822 | 0 | request.requestedSessionTimeout = client->config.requestedSessionTimeout; |
1823 | 0 | request.maxResponseMessageSize = UA_INT32_MAX; |
1824 | 0 | request.endpointUrl = client->endpoint.endpointUrl; |
1825 | 0 | request.clientDescription = client->config.clientDescription; |
1826 | 0 | request.sessionName = client->config.sessionName; |
1827 | | |
1828 | | /* Send the certificate that is used for the UserIdentiyToken as the |
1829 | | * ApplicationCertificate. (sp == client->channel.securityPolicy; guarded |
1830 | | * so static analysis sees it cannot be a NULL dereference.) */ |
1831 | 0 | if(sp) |
1832 | 0 | request.clientCertificate = sp->localCertificate; |
1833 | | |
1834 | | /* For ECC policies, request the server's ephemeral ECDH key (needed to |
1835 | | * encrypt the UserIdentityToken in ActivateSession). */ |
1836 | 0 | res = requestServerEphemeralKey(client, &request.requestHeader); |
1837 | 0 | if(res != UA_STATUSCODE_GOOD) |
1838 | 0 | return res; |
1839 | | |
1840 | | /* Send the request */ |
1841 | 0 | res = __Client_AsyncService(client, &request, |
1842 | 0 | &UA_TYPES[UA_TYPES_CREATESESSIONREQUEST], |
1843 | 0 | (UA_ClientAsyncServiceCallback)createSessionCallback, |
1844 | 0 | &UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL, NULL); |
1845 | | |
1846 | | /* The request is encoded+sent synchronously; release the AdditionalHeader. */ |
1847 | 0 | UA_ExtensionObject_clear(&request.requestHeader.additionalHeader); |
1848 | |
|
1849 | 0 | if(res == UA_STATUSCODE_GOOD) |
1850 | 0 | client->sessionState = UA_SESSIONSTATE_CREATE_REQUESTED; |
1851 | 0 | else |
1852 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1853 | 0 | "CreateSession failed when sending the request with " |
1854 | 0 | "error code %s", UA_StatusCode_name(res)); |
1855 | |
|
1856 | 0 | return res; |
1857 | 0 | } |
1858 | | |
1859 | | static UA_StatusCode |
1860 | 0 | initSecurityPolicy(UA_Client *client) { |
1861 | | /* Find the SecurityPolicy. Use #None if the endpoint is not (yet) |
1862 | | * configured. */ |
1863 | 0 | UA_String secPolicyUri = client->endpoint.securityPolicyUri; |
1864 | 0 | if(secPolicyUri.length == 0) |
1865 | 0 | secPolicyUri = UA_SECURITY_POLICY_NONE_URI; |
1866 | 0 | UA_SecurityPolicy *sp = getSecurityPolicy(client, secPolicyUri); |
1867 | | |
1868 | | /* Unknown SecurityPolicy */ |
1869 | 0 | if(!sp) { |
1870 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1871 | 0 | "SecurityPolicy %S not available", secPolicyUri); |
1872 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
1873 | 0 | } |
1874 | | |
1875 | | /* Already initialized -- check we are using the same SecurityPolicy */ |
1876 | 0 | if(client->channel.securityPolicy) |
1877 | 0 | return (client->channel.securityPolicy == sp) ? |
1878 | 0 | UA_STATUSCODE_GOOD : UA_STATUSCODE_BADINTERNALERROR; |
1879 | | |
1880 | | /* Verify the remote server certificate */ |
1881 | 0 | UA_StatusCode res; |
1882 | 0 | if(client->endpoint.serverCertificate.length > 0) { |
1883 | 0 | res = client->config.certificateVerification. |
1884 | 0 | verifyCertificate(&client->config.certificateVerification, |
1885 | 0 | &client->endpoint.serverCertificate); |
1886 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1887 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1888 | 0 | "Cannot validate the server certificate " |
1889 | 0 | "defined for the selected endpoint"); |
1890 | 0 | return res; |
1891 | 0 | } |
1892 | 0 | } |
1893 | | |
1894 | | /* If the sender provides a chain of certificates then we shall extract the |
1895 | | * ApplicationInstanceCertificate and ignore the extra bytes. See also: OPC |
1896 | | * UA Part 6, V1.04, 6.7.2.3 Security Header, Table 42 - Asymmetric |
1897 | | * algorithm Security header */ |
1898 | 0 | UA_ByteString appInstCert = |
1899 | 0 | getLeafCertificate(client->endpoint.serverCertificate); |
1900 | | |
1901 | | /* Instantiate the SecurityPolicy context with the remote certificate */ |
1902 | 0 | res = UA_SecureChannel_setSecurityPolicy(&client->channel, sp, &appInstCert); |
1903 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1904 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1905 | 0 | "Cannot instantiate the SecurityPolicy %S " |
1906 | 0 | "with the supplied server certificate", sp->policyUri); |
1907 | 0 | return res; |
1908 | 0 | } |
1909 | | |
1910 | | /* Set the SecurityMode. Replace INVALID with NONE as a workaround for |
1911 | | * misconfigured endpoints. */ |
1912 | 0 | UA_MessageSecurityMode securityMode = client->endpoint.securityMode; |
1913 | 0 | if(securityMode == UA_MESSAGESECURITYMODE_INVALID) |
1914 | 0 | securityMode = UA_MESSAGESECURITYMODE_NONE; |
1915 | 0 | res = UA_SecureChannel_setSecurityMode(&client->channel, securityMode); |
1916 | 0 | if(res != UA_STATUSCODE_GOOD) { |
1917 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1918 | 0 | "Client configuration uses mismatching " |
1919 | 0 | "MessageSecurityMode==%u for SecurityPolicy %S", |
1920 | 0 | securityMode, sp->policyUri); |
1921 | 0 | } |
1922 | 0 | return res; |
1923 | 0 | } |
1924 | | |
1925 | | static void |
1926 | 0 | connectActivity(UA_Client *client) { |
1927 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
1928 | 0 | UA_LOG_TRACE(client->config.logging, UA_LOGCATEGORY_CLIENT, |
1929 | 0 | "Client connect iterate"); |
1930 | | |
1931 | | /* Could not connect with an error that canot be recovered from */ |
1932 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
1933 | 0 | return; |
1934 | | |
1935 | | /* Already connected */ |
1936 | 0 | if(client->sessionState == UA_SESSIONSTATE_ACTIVATED) |
1937 | 0 | return; |
1938 | | |
1939 | | /* Switch on the SecureChannel state */ |
1940 | 0 | switch(client->channel.state) { |
1941 | | /* Nothing to do if the connection has not opened fully */ |
1942 | 0 | case UA_SECURECHANNELSTATE_CONNECTING: |
1943 | 0 | case UA_SECURECHANNELSTATE_REVERSE_CONNECTED: |
1944 | 0 | case UA_SECURECHANNELSTATE_CLOSING: |
1945 | 0 | case UA_SECURECHANNELSTATE_HEL_SENT: |
1946 | 0 | case UA_SECURECHANNELSTATE_OPN_SENT: |
1947 | 0 | return; |
1948 | | |
1949 | | /* Send HEL */ |
1950 | 0 | case UA_SECURECHANNELSTATE_CONNECTED: |
1951 | 0 | setConnectStatus(client, sendHELMessage(client)); |
1952 | 0 | return; |
1953 | | |
1954 | | /* ACK receieved. Send OPN. */ |
1955 | 0 | case UA_SECURECHANNELSTATE_ACK_RECEIVED: |
1956 | 0 | setConnectStatus(client, sendOPNAsync(client, false)); /* Send OPN */ |
1957 | 0 | return; |
1958 | | |
1959 | | /* The channel is open -> continue with the Session handling */ |
1960 | 0 | case UA_SECURECHANNELSTATE_OPEN: |
1961 | 0 | break; |
1962 | | |
1963 | | /* The connection is closed. Reset the SecureChannel and open a new TCP |
1964 | | * connection */ |
1965 | 0 | case UA_SECURECHANNELSTATE_CLOSED: |
1966 | 0 | if(client->config.noReconnect) |
1967 | 0 | setConnectStatus(client, UA_STATUSCODE_BADNOTCONNECTED); |
1968 | 0 | else |
1969 | 0 | initConnect(client); /* Sets the connectStatus internally */ |
1970 | 0 | return; |
1971 | | |
1972 | | /* These states should never occur for the client */ |
1973 | 0 | default: |
1974 | 0 | setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR); |
1975 | 0 | return; |
1976 | 0 | } |
1977 | | |
1978 | | /* <-- The SecureChannel is open --> */ |
1979 | | |
1980 | | /* Ongoing handshake -> Waiting for a response */ |
1981 | 0 | if(client->endpointsHandshake || client->findServersHandshake || |
1982 | 0 | client->namespacesHandshake) |
1983 | 0 | return; |
1984 | | |
1985 | | /* Call FindServers to see if we should reconnect with another EndpointUrl. |
1986 | | * This needs to be done before GetEndpoints, as the set of returned |
1987 | | * endpoints may depend on the EndpointUrl used during the initial HEL/ACK |
1988 | | * handshake. */ |
1989 | 0 | if(client->discoveryUrl.length == 0) { |
1990 | 0 | setConnectStatus(client, requestFindServers(client)); |
1991 | 0 | return; |
1992 | 0 | } |
1993 | | |
1994 | | /* GetEndpoints to identify the remote side and/or reset the SecureChannel |
1995 | | * with encryption */ |
1996 | 0 | if(endpointUnconfigured(&client->endpoint)) { |
1997 | 0 | setConnectStatus(client, requestGetEndpoints(client)); |
1998 | 0 | return; |
1999 | 0 | } |
2000 | | |
2001 | | /* Have the final SecureChannel but no session */ |
2002 | 0 | if(client->config.noSession) |
2003 | 0 | return; |
2004 | | |
2005 | | /* Create and Activate the Session */ |
2006 | 0 | switch(client->sessionState) { |
2007 | | /* Send a CreateSessionRequest */ |
2008 | 0 | case UA_SESSIONSTATE_CLOSED: |
2009 | 0 | setConnectStatus(client, createSessionAsync(client)); |
2010 | 0 | return; |
2011 | | |
2012 | | /* Activate the Session */ |
2013 | 0 | case UA_SESSIONSTATE_CREATED: |
2014 | 0 | setConnectStatus(client, activateSessionAsync(client)); |
2015 | 0 | return; |
2016 | | |
2017 | 0 | case UA_SESSIONSTATE_CREATE_REQUESTED: |
2018 | 0 | case UA_SESSIONSTATE_ACTIVATE_REQUESTED: |
2019 | 0 | case UA_SESSIONSTATE_ACTIVATED: |
2020 | 0 | case UA_SESSIONSTATE_CLOSING: |
2021 | 0 | return; /* Nothing to do */ |
2022 | | |
2023 | | /* These states should never occur for the client */ |
2024 | 0 | default: |
2025 | 0 | setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR); |
2026 | 0 | break; |
2027 | 0 | } |
2028 | 0 | } |
2029 | | |
2030 | | /* Verify the OPN response. Note that the client knows the remote server |
2031 | | * certificate before opening the SecureChannel (from the EndpointDescription). |
2032 | | * |
2033 | | * The server certificate is used to initialize the SecureChannel/SecurityPolicy |
2034 | | * before connecting in initSecurityPolicy. The certificate from the endpoint is |
2035 | | * checked to be the same used in the AsymHeader. */ |
2036 | | static UA_StatusCode |
2037 | | verifyClientSecureChannelHeader(void *application, UA_SecureChannel *channel, |
2038 | 0 | const UA_AsymmetricAlgorithmSecurityHeader *asymHeader) { |
2039 | 0 | UA_Client *client = (UA_Client*)application; |
2040 | 0 | const UA_SecurityPolicy *sp = channel->securityPolicy; |
2041 | 0 | UA_assert(sp != NULL); |
2042 | | |
2043 | | /* Check the SecurityPolicyUri if it is defined. If the SecurityMode is not |
2044 | | * None, then the SecurityPolicyUri must be defined. For None we allow an |
2045 | | * empty SecurityPolicyUri to support all (historical) server behaviors. */ |
2046 | 0 | if((asymHeader->securityPolicyUri.length > 0 || |
2047 | 0 | channel->securityMode != UA_MESSAGESECURITYMODE_NONE) && |
2048 | 0 | !UA_String_equal(&sp->policyUri, &asymHeader->securityPolicyUri)) { |
2049 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2050 | 0 | "The server uses a different SecurityPolicy than " |
2051 | 0 | "the SecureChannel/Endpoint configured in the client"); |
2052 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
2053 | 0 | } |
2054 | | |
2055 | | /* In initSecurityPolicy we already verified |
2056 | | * client->endpoint.serverCertificate and loaded it into the channel context |
2057 | | * of the SecurityPolicy. |
2058 | | * |
2059 | | * If the securityPolicyUri is None (== SecurityMode is None), the server |
2060 | | * shall ignore the ApplicationInstanceCertificate. Otherwise compare that |
2061 | | * the certificate in OPN is the same as in the endpoint. |
2062 | | * |
2063 | | * Both the clientCertificate of this request and of the endpoint may |
2064 | | * contain a partial or a complete certificate chain. The compareCertificate |
2065 | | * function will compare the first certificate of each chain. The end |
2066 | | * certificate shall be located first in the chain according to the OPC UA |
2067 | | * specification Part 6 (1.04), chapter 6.2.3.*/ |
2068 | 0 | UA_StatusCode res; |
2069 | 0 | if(channel->securityMode != UA_MESSAGESECURITYMODE_NONE) { |
2070 | 0 | void *cc = channel->channelContext; |
2071 | 0 | res = sp->compareCertificate(sp, cc, &asymHeader->senderCertificate); |
2072 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2073 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2074 | 0 | "The server certificate in the OPN message is " |
2075 | 0 | "different from the EndpointDescription"); |
2076 | 0 | return UA_STATUSCODE_BADSECURITYCHECKSFAILED; |
2077 | 0 | } |
2078 | 0 | } |
2079 | | |
2080 | | /* Verify the certificate the server assumes on our end */ |
2081 | 0 | res = sp->compareCertThumbprint(sp, &asymHeader->receiverCertificateThumbprint); |
2082 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2083 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2084 | 0 | "The client certificate thumprint in the OPN response " |
2085 | 0 | "is incorrect"); |
2086 | 0 | return res; |
2087 | 0 | } |
2088 | | |
2089 | 0 | return UA_STATUSCODE_GOOD; |
2090 | 0 | } |
2091 | | |
2092 | | /* The local ApplicationUri has to match the certificates of the |
2093 | | * SecurityPolicies */ |
2094 | | static void |
2095 | 0 | verifyClientApplicationUri(const UA_Client *client) { |
2096 | | #if UA_LOGLEVEL <= 400 |
2097 | | const UA_ClientConfig *cc = &client->config; |
2098 | | for(size_t i = 0; i < cc->securityPoliciesSize; i++) { |
2099 | | UA_SecurityPolicy *sp = &cc->securityPolicies[i]; |
2100 | | if(sp->policyType == UA_SECURITYPOLICYTYPE_NONE && |
2101 | | !sp->localCertificate.data) |
2102 | | continue; |
2103 | | UA_StatusCode retval = |
2104 | | UA_CertificateUtils_verifyApplicationUri(&sp->localCertificate, |
2105 | | &cc->clientDescription.applicationUri); |
2106 | | if(retval != UA_STATUSCODE_GOOD) { |
2107 | | UA_LOG_WARNING(cc->logging, UA_LOGCATEGORY_CLIENT, |
2108 | | "The ApplicationUri %S in the client's ApplicationDescription " |
2109 | | "does not match the URI specified in the certificate " |
2110 | | "for the SecurityPolicy %S", |
2111 | | cc->clientDescription.applicationUri, sp->policyUri); |
2112 | | } |
2113 | | } |
2114 | | #endif |
2115 | 0 | } |
2116 | | |
2117 | | static void |
2118 | | delayedNetworkCallback(void *application, void *context); |
2119 | | |
2120 | | static void |
2121 | | __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
2122 | | void *application, void **connectionContext, |
2123 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
2124 | 0 | UA_ByteString msg) { |
2125 | | /* Take the client lock */ |
2126 | 0 | UA_Client *client = (UA_Client*)application; |
2127 | 0 | lockClient(client); |
2128 | |
|
2129 | 0 | UA_LOG_TRACE(client->config.logging, UA_LOGCATEGORY_CLIENT, "Client network callback"); |
2130 | | |
2131 | | /* A new connection with no context pointer attached */ |
2132 | 0 | if(!*connectionContext) { |
2133 | | /* Inconsistent SecureChannel state. Has to be fresh for a new |
2134 | | * connection. */ |
2135 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED && |
2136 | 0 | client->channel.state != UA_SECURECHANNELSTATE_REVERSE_LISTENING) { |
2137 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2138 | 0 | "Cannot open a connection, the SecureChannel is already used"); |
2139 | 0 | setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR); |
2140 | 0 | notifyClientState(client); |
2141 | 0 | unlockClient(client); |
2142 | 0 | return; |
2143 | 0 | } |
2144 | | |
2145 | | /* Initialize the client connection and attach to the EventLoop connection */ |
2146 | 0 | client->channel.connectionManager = cm; |
2147 | 0 | client->channel.connectionId = connectionId; |
2148 | 0 | *connectionContext = &client->channel; |
2149 | 0 | } |
2150 | | |
2151 | | /* The connection is closing in the EventLoop. This is the last callback |
2152 | | * from that connection. Clean up the SecureChannel in the client. */ |
2153 | 0 | if(state == UA_CONNECTIONSTATE_CLOSING) { |
2154 | 0 | UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel, |
2155 | 0 | "SecureChannel closed"); |
2156 | | |
2157 | | /* Set to closing (could be done already in UA_SecureChannel_shutdown). |
2158 | | * This impacts the handling of cancelled requests below. */ |
2159 | 0 | UA_SecureChannelState oldState = client->channel.state; |
2160 | 0 | client->channel.state = UA_SECURECHANNELSTATE_CLOSING; |
2161 | | |
2162 | | /* Set the Session to CREATED if it was ACTIVATED */ |
2163 | 0 | if(client->sessionState == UA_SESSIONSTATE_ACTIVATED) |
2164 | 0 | client->sessionState = UA_SESSIONSTATE_CREATED; |
2165 | | |
2166 | | /* Delete outstanding async services - the RequestId is no longer valid. Do |
2167 | | * this after setting the Session state. Otherwise we send out new Publish |
2168 | | * Requests immediately. */ |
2169 | 0 | __Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSECURECHANNELCLOSED); |
2170 | | |
2171 | | /* Clean up the channel and set the status to CLOSED */ |
2172 | 0 | UA_SecureChannel_clear(&client->channel); |
2173 | | |
2174 | | /* The connection closed before it actually opened. Since we are |
2175 | | * connecting asynchronously, this happens when the TCP connection |
2176 | | * fails. Try to fall back on the initial EndpointUrl. */ |
2177 | 0 | if(oldState == UA_SECURECHANNELSTATE_CONNECTING && |
2178 | 0 | client->connectStatus == UA_STATUSCODE_GOOD) |
2179 | 0 | setConnectStatus(client, fallbackEndpointUrl(client)); |
2180 | | |
2181 | | /* Try to reconnect. Duplicate the code instead of continue_connect to |
2182 | | * pacify compiler warnings. */ |
2183 | | /* goto continue_connect; */ |
2184 | 0 | if(!isFullyConnected(client)) |
2185 | 0 | connectActivity(client); |
2186 | 0 | notifyClientState(client); |
2187 | 0 | unlockClient(client); |
2188 | 0 | return; |
2189 | 0 | } |
2190 | | |
2191 | | /* Update the SecureChannel state */ |
2192 | 0 | if(UA_LIKELY(state == UA_CONNECTIONSTATE_ESTABLISHED)) { |
2193 | | /* The connection is now open on the TCP level. Set the SecureChannel |
2194 | | * state to reflect this. Otherwise later consistency checks for the |
2195 | | * received messages fail. */ |
2196 | 0 | if(client->channel.state < UA_SECURECHANNELSTATE_CONNECTED) |
2197 | 0 | client->channel.state = UA_SECURECHANNELSTATE_CONNECTED; |
2198 | 0 | } else /* state == UA_CONNECTIONSTATE_OPENING */ { |
2199 | | /* The connection was opened on our end only. Waiting for the TCP handshake |
2200 | | * to complete. */ |
2201 | 0 | client->channel.state = UA_SECURECHANNELSTATE_CONNECTING; |
2202 | 0 | } |
2203 | |
|
2204 | 0 | UA_EventLoop *el = client->config.eventLoop; |
2205 | 0 | UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el); |
2206 | | |
2207 | | /* Received a message. Process the message with the SecureChannel. */ |
2208 | 0 | UA_StatusCode res = UA_SecureChannel_loadBuffer(&client->channel, msg); |
2209 | 0 | while(UA_LIKELY(res == UA_STATUSCODE_GOOD)) { |
2210 | 0 | UA_MessageType messageType; |
2211 | 0 | UA_UInt32 requestId = 0; |
2212 | 0 | UA_ByteString payload = UA_BYTESTRING_NULL; |
2213 | 0 | UA_Boolean copied = false; |
2214 | 0 | res = UA_SecureChannel_getCompleteMessage(&client->channel, &messageType, &requestId, |
2215 | 0 | &payload, &copied, nowMonotonic); |
2216 | 0 | if(res != UA_STATUSCODE_GOOD || payload.length == 0) |
2217 | 0 | break; |
2218 | 0 | res = processServiceResponse(client, &client->channel, |
2219 | 0 | messageType, requestId, &payload); |
2220 | 0 | if(copied) |
2221 | 0 | UA_ByteString_clear(&payload); |
2222 | | |
2223 | | /* Abort after synchronous processing of a message. |
2224 | | * Add a delayed callback to process the remaining buffer ASAP. */ |
2225 | 0 | if(res == UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY) { |
2226 | 0 | if(client->channel.unprocessed.length > client->channel.unprocessedOffset && |
2227 | 0 | client->channel.unprocessedDelayed.callback == NULL) { |
2228 | 0 | client->channel.unprocessedDelayed.callback = delayedNetworkCallback; |
2229 | 0 | client->channel.unprocessedDelayed.application = client; |
2230 | 0 | client->channel.unprocessedDelayed.context = &client->channel; |
2231 | 0 | el->addDelayedCallback(el, &client->channel.unprocessedDelayed); |
2232 | 0 | } |
2233 | 0 | res = UA_STATUSCODE_GOOD; |
2234 | 0 | break; |
2235 | 0 | } |
2236 | 0 | } |
2237 | 0 | res |= UA_SecureChannel_persistBuffer(&client->channel); |
2238 | |
|
2239 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2240 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2241 | 0 | "Processing the message returned the error code %s", |
2242 | 0 | UA_StatusCode_name(res)); |
2243 | | |
2244 | | /* If processing the buffer fails before the SecureChannel has opened, |
2245 | | * then the client cannot recover. Set the connectStatus to reflect |
2246 | | * this. The application is notified when the socket has closed. */ |
2247 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_OPEN) |
2248 | 0 | setConnectStatus(client, res); |
2249 | | |
2250 | | /* Close the SecureChannel, but don't notify the client right away. |
2251 | | * Return immediately. notifyClientState will be called in the next |
2252 | | * callback from the ConnectionManager when the connection closes with a |
2253 | | * StatusCode. |
2254 | | * |
2255 | | * If the connectStatus is still good (the SecureChannel was fully |
2256 | | * opened before), then a reconnect is attempted. */ |
2257 | 0 | closeSecureChannel(client); |
2258 | 0 | unlockClient(client); |
2259 | 0 | return; |
2260 | 0 | } |
2261 | | |
2262 | | /* continue_connect: */ |
2263 | | /* Trigger the next action from our end to fully open up the connection */ |
2264 | 0 | if(!isFullyConnected(client)) |
2265 | 0 | connectActivity(client); |
2266 | 0 | notifyClientState(client); |
2267 | 0 | unlockClient(client); |
2268 | 0 | } |
2269 | | |
2270 | | static void |
2271 | 0 | delayedNetworkCallback(void *application, void *context) { |
2272 | 0 | UA_Client *client = (UA_Client*)application; |
2273 | 0 | client->channel.unprocessedDelayed.callback = NULL; |
2274 | 0 | if(client->channel.state >= UA_SECURECHANNELSTATE_CONNECTING) |
2275 | 0 | __Client_networkCallback(client->channel.connectionManager, |
2276 | 0 | client->channel.connectionId, |
2277 | 0 | client, &context, |
2278 | 0 | UA_CONNECTIONSTATE_ESTABLISHED, |
2279 | 0 | &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); |
2280 | 0 | } |
2281 | | |
2282 | | /* Initialize a TCP connection. Writes the result to client->connectStatus. */ |
2283 | | static void |
2284 | 0 | initConnect(UA_Client *client) { |
2285 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
2286 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) { |
2287 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2288 | 0 | "Client connection already initiated"); |
2289 | 0 | return; |
2290 | 0 | } |
2291 | | |
2292 | 0 | UA_StatusCode res; |
2293 | | |
2294 | | /* An exact endpoint was configured. Use it. */ |
2295 | 0 | if(!endpointUnconfigured(&client->config.endpoint)) { |
2296 | 0 | UA_EndpointDescription_clear(&client->endpoint); |
2297 | 0 | res = UA_EndpointDescription_copy(&client->config.endpoint, &client->endpoint); |
2298 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2299 | 0 | setConnectStatus(client, res); |
2300 | 0 | return; |
2301 | 0 | } |
2302 | 0 | } |
2303 | | |
2304 | | /* Start the EventLoop if not already started */ |
2305 | 0 | setConnectStatus(client, __UA_Client_startup(client)); |
2306 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
2307 | 0 | return; |
2308 | | |
2309 | | /* Consistency check the client's own ApplicationUri. |
2310 | | * Problems are only logged. */ |
2311 | 0 | verifyClientApplicationUri(client); |
2312 | | |
2313 | | /* Initialize the SecureChannel */ |
2314 | 0 | UA_SecureChannel_clear(&client->channel); |
2315 | 0 | client->channel.config = client->config.localConnectionConfig; |
2316 | 0 | client->channel.processOPNHeader = verifyClientSecureChannelHeader; |
2317 | 0 | client->channel.processOPNHeaderApplication = client; |
2318 | | |
2319 | | /* Initialize the SecurityPolicy */ |
2320 | 0 | setConnectStatus(client, initSecurityPolicy(client)); |
2321 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
2322 | 0 | return; |
2323 | | |
2324 | | /* Extract hostname and port from the URL */ |
2325 | 0 | UA_String hostname = UA_STRING_NULL; |
2326 | 0 | UA_String path = UA_STRING_NULL; |
2327 | 0 | UA_UInt16 port = 4840; |
2328 | |
|
2329 | 0 | res = UA_parseEndpointUrl(&client->config.endpointUrl, &hostname, &port, &path); |
2330 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2331 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_NETWORK, |
2332 | 0 | "Endpoint URL is invalid: %S", client->config.endpointUrl); |
2333 | 0 | setConnectStatus(client, res); |
2334 | 0 | return; |
2335 | 0 | } |
2336 | | |
2337 | | /* Initialize the TCP connection */ |
2338 | 0 | UA_String tcpString = UA_STRING("tcp"); |
2339 | 0 | for(UA_EventSource *es = client->config.eventLoop->eventSources; |
2340 | 0 | es != NULL; es = es->next) { |
2341 | | /* Is this a usable connection manager? */ |
2342 | 0 | if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) |
2343 | 0 | continue; |
2344 | 0 | UA_ConnectionManager *cm = (UA_ConnectionManager*)es; |
2345 | 0 | if(!UA_String_equal(&tcpString, &cm->protocol)) |
2346 | 0 | continue; |
2347 | | |
2348 | | /* Set up the parameters */ |
2349 | 0 | UA_KeyValuePair params[3]; |
2350 | 0 | params[0].key = UA_QUALIFIEDNAME(0, "port"); |
2351 | 0 | UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]); |
2352 | 0 | params[1].key = UA_QUALIFIEDNAME(0, "address"); |
2353 | 0 | UA_Variant_setScalar(¶ms[1].value, &hostname, &UA_TYPES[UA_TYPES_STRING]); |
2354 | 0 | params[2].key = UA_QUALIFIEDNAME(0, "reuse"); |
2355 | 0 | UA_Variant_setScalar(¶ms[2].value, &client->config.tcpReuseAddr, |
2356 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
2357 | |
|
2358 | 0 | UA_KeyValueMap paramMap; |
2359 | 0 | paramMap.map = params; |
2360 | 0 | paramMap.mapSize = 3; |
2361 | | |
2362 | | /* Open the client TCP connection */ |
2363 | 0 | res = cm->openConnection(cm, ¶mMap, client, NULL, __Client_networkCallback); |
2364 | 0 | if(res == UA_STATUSCODE_GOOD) |
2365 | 0 | break; |
2366 | 0 | } |
2367 | | |
2368 | | /* The channel has not opened */ |
2369 | 0 | if(client->channel.state == UA_SECURECHANNELSTATE_CLOSED) |
2370 | 0 | res = UA_STATUSCODE_BADINTERNALERROR; |
2371 | | |
2372 | | /* Opening the TCP connection failed */ |
2373 | 0 | if(res != UA_STATUSCODE_GOOD || client->connectStatus != UA_STATUSCODE_GOOD) { |
2374 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2375 | 0 | "Could not open a TCP connection to %S", |
2376 | 0 | client->config.endpointUrl); |
2377 | 0 | setConnectStatus(client, UA_STATUSCODE_BADCONNECTIONCLOSED); |
2378 | 0 | } |
2379 | 0 | } |
2380 | | |
2381 | | void |
2382 | 0 | connectSync(UA_Client *client) { |
2383 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
2384 | | |
2385 | | /* Initialize the connection */ |
2386 | 0 | initConnect(client); |
2387 | 0 | notifyClientState(client); |
2388 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
2389 | 0 | return; |
2390 | | |
2391 | | /* EventLoop is started. Otherwise initConnect would have failed. */ |
2392 | 0 | UA_EventLoop *el = client->config.eventLoop; |
2393 | 0 | UA_assert(el); |
2394 | | |
2395 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
2396 | 0 | UA_DateTime maxDate = now + ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC); |
2397 | | |
2398 | | /* Run the EventLoop until connected, connect fail or timeout. Continue |
2399 | | * to iterate when the channel is in CLOSING state so that the pending |
2400 | | * close callback is processed and the channel fully transitions to CLOSED |
2401 | | * before returning to the caller. */ |
2402 | 0 | while((client->connectStatus == UA_STATUSCODE_GOOD && !isFullyConnected(client)) || |
2403 | 0 | client->channel.state == UA_SECURECHANNELSTATE_CLOSING) { |
2404 | | /* Timeout -> abort */ |
2405 | 0 | now = el->dateTime_nowMonotonic(el); |
2406 | 0 | if(maxDate < now) { |
2407 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2408 | 0 | "The connection has timed out before it could be fully opened"); |
2409 | 0 | setConnectStatus(client, UA_STATUSCODE_BADTIMEOUT); |
2410 | | /* Continue to run. The loop continues while the channel is |
2411 | | * CLOSING so the close callback is processed. */ |
2412 | 0 | } |
2413 | | |
2414 | | /* Drop into the EventLoop */ |
2415 | 0 | UA_StatusCode res = el->run(el, (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC)); |
2416 | 0 | if(res != UA_STATUSCODE_GOOD) |
2417 | 0 | setConnectStatus(client, res); |
2418 | 0 | } |
2419 | 0 | } |
2420 | | |
2421 | | UA_StatusCode |
2422 | 0 | connectInternal(UA_Client *client, UA_Boolean async) { |
2423 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
2424 | | |
2425 | | /* Reset the connectStatus. This should be the only place where we can |
2426 | | * recover from a bad connectStatus. */ |
2427 | 0 | client->connectStatus = UA_STATUSCODE_GOOD; |
2428 | |
|
2429 | 0 | if(async) |
2430 | 0 | initConnect(client); |
2431 | 0 | else |
2432 | 0 | connectSync(client); |
2433 | 0 | notifyClientState(client); |
2434 | 0 | return client->connectStatus; |
2435 | 0 | } |
2436 | | |
2437 | | UA_StatusCode |
2438 | 0 | connectSecureChannel(UA_Client *client, const char *endpointUrl) { |
2439 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
2440 | |
|
2441 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
2442 | 0 | cc->noSession = true; |
2443 | 0 | UA_String_clear(&cc->endpointUrl); |
2444 | 0 | cc->endpointUrl = UA_STRING_ALLOC(endpointUrl); |
2445 | 0 | return connectInternal(client, false); |
2446 | 0 | } |
2447 | | |
2448 | | UA_StatusCode |
2449 | 0 | __UA_Client_connect(UA_Client *client, UA_Boolean async, const char *endpointUrl) { |
2450 | 0 | lockClient(client); |
2451 | |
|
2452 | 0 | UA_ClientConfig *cc = UA_Client_getConfig(client); |
2453 | 0 | if(endpointUrl) { |
2454 | 0 | UA_String_clear(&cc->endpointUrl); |
2455 | 0 | cc->endpointUrl = UA_STRING_ALLOC(endpointUrl); |
2456 | 0 | } |
2457 | |
|
2458 | 0 | connectInternal(client, async); |
2459 | |
|
2460 | 0 | unlockClient(client); |
2461 | 0 | return client->connectStatus; |
2462 | 0 | } |
2463 | | |
2464 | | static UA_StatusCode |
2465 | 0 | activateSessionSync(UA_Client *client) { |
2466 | 0 | UA_LOCK_ASSERT(&client->clientMutex); |
2467 | | |
2468 | | /* EventLoop is started. Otherwise activateSessionAsync would have failed. */ |
2469 | 0 | UA_EventLoop *el = client->config.eventLoop; |
2470 | 0 | UA_assert(el); |
2471 | | |
2472 | 0 | UA_DateTime now = el->dateTime_nowMonotonic(el); |
2473 | 0 | UA_DateTime maxDate = now + ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC); |
2474 | | |
2475 | | /* Try to activate */ |
2476 | 0 | UA_StatusCode res = activateSessionAsync(client); |
2477 | 0 | if(res != UA_STATUSCODE_GOOD) |
2478 | 0 | return res; |
2479 | | |
2480 | | /* Continue iterating while the session is being activated or the |
2481 | | * channel is draining (CLOSING) after a timeout. */ |
2482 | 0 | while((client->sessionState != UA_SESSIONSTATE_ACTIVATED && |
2483 | 0 | client->connectStatus == UA_STATUSCODE_GOOD) || |
2484 | 0 | client->channel.state == UA_SECURECHANNELSTATE_CLOSING) { |
2485 | | /* Timeout -> abort */ |
2486 | 0 | now = el->dateTime_nowMonotonic(el); |
2487 | 0 | if(maxDate < now) { |
2488 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2489 | 0 | "The connection has timed out before it could be fully opened"); |
2490 | 0 | setConnectStatus(client, UA_STATUSCODE_BADTIMEOUT); |
2491 | | /* Continue to run. The loop continues while the channel is |
2492 | | * CLOSING so the close callback is processed. */ |
2493 | 0 | } |
2494 | | |
2495 | | /* Drop into the EventLoop */ |
2496 | 0 | res = el->run(el, (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC)); |
2497 | 0 | if(res != UA_STATUSCODE_GOOD) |
2498 | 0 | setConnectStatus(client, res); |
2499 | 0 | } |
2500 | |
|
2501 | 0 | return client->connectStatus; |
2502 | 0 | } |
2503 | | |
2504 | | UA_StatusCode |
2505 | 0 | UA_Client_activateCurrentSession(UA_Client *client) { |
2506 | 0 | lockClient(client); |
2507 | 0 | UA_StatusCode res = activateSessionSync(client); |
2508 | 0 | notifyClientState(client); |
2509 | 0 | unlockClient(client); |
2510 | 0 | return res != UA_STATUSCODE_GOOD ? res : client->connectStatus; |
2511 | 0 | } |
2512 | | |
2513 | | UA_StatusCode |
2514 | 0 | UA_Client_activateCurrentSessionAsync(UA_Client *client) { |
2515 | 0 | lockClient(client); |
2516 | 0 | UA_StatusCode res = activateSessionAsync(client); |
2517 | 0 | notifyClientState(client); |
2518 | 0 | unlockClient(client); |
2519 | 0 | return res != UA_STATUSCODE_GOOD ? res : client->connectStatus; |
2520 | 0 | } |
2521 | | |
2522 | | UA_StatusCode |
2523 | | UA_Client_getSessionAuthenticationToken(UA_Client *client, |
2524 | | UA_NodeId *authenticationToken, |
2525 | 0 | UA_ByteString *serverNonce) { |
2526 | 0 | lockClient(client); |
2527 | 0 | if(client->sessionState != UA_SESSIONSTATE_CREATED && |
2528 | 0 | client->sessionState != UA_SESSIONSTATE_ACTIVATED) { |
2529 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2530 | 0 | "There is no current session"); |
2531 | 0 | unlockClient(client); |
2532 | 0 | return UA_STATUSCODE_BADSESSIONCLOSED; |
2533 | 0 | } |
2534 | | |
2535 | 0 | UA_StatusCode res = |
2536 | 0 | UA_NodeId_copy(&client->authenticationToken, authenticationToken); |
2537 | 0 | res |= UA_ByteString_copy(&client->serverSessionNonce, serverNonce); |
2538 | 0 | unlockClient(client); |
2539 | 0 | return res; |
2540 | 0 | } |
2541 | | |
2542 | | static UA_StatusCode |
2543 | | switchSession(UA_Client *client, |
2544 | | const UA_NodeId authenticationToken, |
2545 | 0 | const UA_ByteString serverNonce) { |
2546 | | /* Check that there is no pending session in the client */ |
2547 | 0 | if(client->sessionState != UA_SESSIONSTATE_CLOSED) { |
2548 | 0 | UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2549 | 0 | "Cannot activate a session with a different AuthenticationToken " |
2550 | 0 | "when the client already has a Session."); |
2551 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
2552 | 0 | } |
2553 | | |
2554 | | /* Replace token and nonce */ |
2555 | 0 | UA_NodeId_clear(&client->authenticationToken); |
2556 | 0 | UA_ByteString_clear(&client->serverSessionNonce); |
2557 | 0 | UA_StatusCode res = UA_NodeId_copy(&authenticationToken, |
2558 | 0 | &client->authenticationToken); |
2559 | 0 | res |= UA_ByteString_copy(&serverNonce, &client->serverSessionNonce); |
2560 | 0 | if(res != UA_STATUSCODE_GOOD) |
2561 | 0 | return res; |
2562 | | |
2563 | | /* Notify that we have now a created session */ |
2564 | 0 | client->sessionState = UA_SESSIONSTATE_CREATED; |
2565 | 0 | notifyClientState(client); |
2566 | |
|
2567 | 0 | return UA_STATUSCODE_GOOD; |
2568 | 0 | } |
2569 | | |
2570 | | UA_StatusCode |
2571 | | UA_Client_activateSession(UA_Client *client, |
2572 | | const UA_NodeId authenticationToken, |
2573 | 0 | const UA_ByteString serverNonce) { |
2574 | 0 | lockClient(client); |
2575 | 0 | UA_StatusCode res = switchSession(client, authenticationToken, serverNonce); |
2576 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2577 | 0 | unlockClient(client); |
2578 | 0 | return res; |
2579 | 0 | } |
2580 | 0 | res = activateSessionSync(client); |
2581 | 0 | notifyClientState(client); |
2582 | 0 | unlockClient(client); |
2583 | 0 | return res != UA_STATUSCODE_GOOD ? res : client->connectStatus; |
2584 | 0 | } |
2585 | | |
2586 | | UA_StatusCode |
2587 | | UA_Client_activateSessionAsync(UA_Client *client, |
2588 | | const UA_NodeId authenticationToken, |
2589 | 0 | const UA_ByteString serverNonce) { |
2590 | 0 | lockClient(client); |
2591 | 0 | UA_StatusCode res = switchSession(client, authenticationToken, serverNonce); |
2592 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2593 | 0 | unlockClient(client); |
2594 | 0 | return res; |
2595 | 0 | } |
2596 | 0 | res = activateSessionAsync(client); |
2597 | 0 | notifyClientState(client); |
2598 | 0 | unlockClient(client); |
2599 | 0 | return res != UA_STATUSCODE_GOOD ? res : client->connectStatus; |
2600 | 0 | } |
2601 | | |
2602 | | static void |
2603 | 0 | disconnectListenSockets(UA_Client *client) { |
2604 | 0 | UA_ConnectionManager *cm = client->reverseConnectionCM; |
2605 | 0 | for(size_t i = 0; i < 16; i++) { |
2606 | 0 | if(client->reverseConnectionIds[i] != 0) |
2607 | 0 | cm->closeConnection(cm, client->reverseConnectionIds[i]); |
2608 | 0 | } |
2609 | 0 | } |
2610 | | |
2611 | | /* ConnectionContext meaning: |
2612 | | * - NULL: New listen connection |
2613 | | * - &client->channel: Established active socket to a server |
2614 | | * - &client->reverseConnectionIds[*] == connectionId: Established listen socket |
2615 | | * - &client->reverseConnectionIds[*] != connectionId: New active socket */ |
2616 | | static void |
2617 | | __Client_reverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId, |
2618 | | void *application, void **connectionContext, |
2619 | | UA_ConnectionState state, const UA_KeyValueMap *params, |
2620 | 0 | UA_ByteString msg) { |
2621 | 0 | UA_Client *client = (UA_Client*)application; |
2622 | 0 | lockClient(client); |
2623 | |
|
2624 | 0 | if(!*connectionContext) { |
2625 | | /* Store the new listen connection */ |
2626 | 0 | size_t i = 0; |
2627 | 0 | for(; i < 16; i++) { |
2628 | 0 | if(client->reverseConnectionIds[i] == 0) { |
2629 | 0 | client->reverseConnectionIds[i] = connectionId; |
2630 | 0 | client->reverseConnectionCM = cm; |
2631 | 0 | *connectionContext = &client->reverseConnectionIds[i]; |
2632 | 0 | if(client->channel.state == UA_SECURECHANNELSTATE_CLOSED) |
2633 | 0 | client->channel.state = UA_SECURECHANNELSTATE_REVERSE_LISTENING; |
2634 | 0 | break; |
2635 | 0 | } |
2636 | 0 | } |
2637 | | /* All slots are full, close */ |
2638 | 0 | if(i == 16) { |
2639 | 0 | cm->closeConnection(cm, connectionId); |
2640 | 0 | unlockClient(client); |
2641 | 0 | return; |
2642 | 0 | } |
2643 | 0 | } else if(*connectionContext == &client->channel || |
2644 | 0 | *(uintptr_t*)*connectionContext != connectionId) { |
2645 | | /* Active socket */ |
2646 | | |
2647 | | /* New active socket */ |
2648 | 0 | if(*connectionContext != &client->channel) { |
2649 | | /* The client already has an active connection */ |
2650 | 0 | if(client->channel.connectionId) { |
2651 | 0 | cm->closeConnection(cm, connectionId); |
2652 | 0 | unlockClient(client); |
2653 | 0 | return; |
2654 | 0 | } |
2655 | | |
2656 | | /* Set the connection the SecureChannel */ |
2657 | 0 | client->channel.connectionId = connectionId; |
2658 | 0 | client->channel.connectionManager = cm; |
2659 | 0 | *connectionContext = &client->channel; |
2660 | | |
2661 | | /* Don't keep the listen sockets when an active connection is open */ |
2662 | 0 | disconnectListenSockets(client); |
2663 | | |
2664 | | /* Set the channel state. The notification callback is called within |
2665 | | * __Client_reverseConnectCallback. */ |
2666 | 0 | if(client->channel.state == UA_SECURECHANNELSTATE_REVERSE_LISTENING) |
2667 | 0 | client->channel.state = UA_SECURECHANNELSTATE_REVERSE_CONNECTED; |
2668 | 0 | } |
2669 | | |
2670 | | /* Handle the active connection in the normal network callback */ |
2671 | 0 | unlockClient(client); |
2672 | 0 | __Client_networkCallback(cm, connectionId, application, |
2673 | 0 | connectionContext, state, params, msg); |
2674 | 0 | return; |
2675 | 0 | } |
2676 | | |
2677 | | /* Close the listen socket. Was this the last one? */ |
2678 | 0 | if(state == UA_CONNECTIONSTATE_CLOSING) { |
2679 | 0 | UA_Byte count = 0; |
2680 | 0 | for(size_t i = 0; i < 16; i++) { |
2681 | 0 | if(client->reverseConnectionIds[i] == connectionId) |
2682 | 0 | client->reverseConnectionIds[i] = 0; |
2683 | 0 | if(client->reverseConnectionIds[i] != 0) |
2684 | 0 | count++; |
2685 | 0 | } |
2686 | | /* The last connection was closed */ |
2687 | 0 | if(count == 0 && client->channel.connectionId == 0) |
2688 | 0 | client->channel.state = UA_SECURECHANNELSTATE_CLOSED; |
2689 | 0 | } |
2690 | |
|
2691 | 0 | notifyClientState(client); |
2692 | 0 | unlockClient(client); |
2693 | 0 | } |
2694 | | |
2695 | | UA_StatusCode |
2696 | | UA_Client_startListeningForReverseConnect(UA_Client *client, |
2697 | | const UA_String *listenHostnames, |
2698 | | size_t listenHostnamesLength, |
2699 | 0 | UA_UInt16 port) { |
2700 | 0 | lockClient(client); |
2701 | |
|
2702 | 0 | if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) { |
2703 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2704 | 0 | "Unable to listen for reverse connect while the client " |
2705 | 0 | "is connected or already listening"); |
2706 | 0 | unlockClient(client); |
2707 | 0 | return UA_STATUSCODE_BADINVALIDSTATE; |
2708 | 0 | } |
2709 | | |
2710 | 0 | const UA_String tcpString = UA_STRING_STATIC("tcp"); |
2711 | 0 | UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR; |
2712 | |
|
2713 | 0 | client->connectStatus = UA_STATUSCODE_GOOD; |
2714 | 0 | client->channel.renewState = UA_SECURECHANNELRENEWSTATE_NORMAL; |
2715 | |
|
2716 | 0 | UA_SecureChannel_init(&client->channel); |
2717 | 0 | client->channel.config = client->config.localConnectionConfig; |
2718 | 0 | client->channel.processOPNHeader = verifyClientSecureChannelHeader; |
2719 | 0 | client->channel.processOPNHeaderApplication = client; |
2720 | 0 | client->channel.connectionId = 0; |
2721 | |
|
2722 | 0 | setConnectStatus(client, initSecurityPolicy(client)); |
2723 | 0 | if(client->connectStatus != UA_STATUSCODE_GOOD) |
2724 | 0 | return client->connectStatus; |
2725 | | |
2726 | 0 | UA_EventLoop *el = client->config.eventLoop; |
2727 | 0 | if(!el) { |
2728 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2729 | 0 | "No EventLoop configured"); |
2730 | 0 | unlockClient(client); |
2731 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
2732 | 0 | } |
2733 | | |
2734 | 0 | if(el->state != UA_EVENTLOOPSTATE_STARTED) { |
2735 | 0 | res = el->start(el); |
2736 | 0 | UA_CHECK_STATUS(res, unlockClient(client); return res); |
2737 | 0 | } |
2738 | | |
2739 | 0 | UA_ConnectionManager *cm = NULL; |
2740 | 0 | for(UA_EventSource *es = el->eventSources; es != NULL; es = es->next) { |
2741 | 0 | if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER) |
2742 | 0 | continue; |
2743 | 0 | cm = (UA_ConnectionManager*)es; |
2744 | 0 | if(UA_String_equal(&tcpString, &cm->protocol)) |
2745 | 0 | break; |
2746 | 0 | cm = NULL; |
2747 | 0 | } |
2748 | |
|
2749 | 0 | if(!cm) { |
2750 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2751 | 0 | "Could not find a TCP connection manager, unable to " |
2752 | 0 | "listen for reverse connect"); |
2753 | 0 | unlockClient(client); |
2754 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
2755 | 0 | } |
2756 | | |
2757 | 0 | client->channel.connectionManager = cm; |
2758 | |
|
2759 | 0 | UA_KeyValuePair params[4]; |
2760 | 0 | bool booleanTrue = true; |
2761 | 0 | params[0].key = UA_QUALIFIEDNAME(0, "port"); |
2762 | 0 | UA_Variant_setScalar(¶ms[0].value, &port, |
2763 | 0 | &UA_TYPES[UA_TYPES_UINT16]); |
2764 | 0 | params[1].key = UA_QUALIFIEDNAME(0, "address"); |
2765 | 0 | UA_Variant_setArray(¶ms[1].value, (void *)(uintptr_t)listenHostnames, |
2766 | 0 | listenHostnamesLength, &UA_TYPES[UA_TYPES_STRING]); |
2767 | 0 | params[2].key = UA_QUALIFIEDNAME(0, "listen"); |
2768 | 0 | UA_Variant_setScalar(¶ms[2].value, &booleanTrue, |
2769 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
2770 | 0 | params[3].key = UA_QUALIFIEDNAME(0, "reuse"); |
2771 | 0 | UA_Variant_setScalar(¶ms[3].value, &client->config.tcpReuseAddr, |
2772 | 0 | &UA_TYPES[UA_TYPES_BOOLEAN]); |
2773 | |
|
2774 | 0 | UA_KeyValueMap paramMap; |
2775 | 0 | paramMap.map = params; |
2776 | 0 | paramMap.mapSize = 4; |
2777 | |
|
2778 | 0 | res = cm->openConnection(cm, ¶mMap, client, NULL, |
2779 | 0 | __Client_reverseConnectCallback); |
2780 | | |
2781 | | /* Opening the TCP connection failed */ |
2782 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2783 | 0 | UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2784 | 0 | "Failed to open a listening TCP socket for " |
2785 | 0 | "reverse connect"); |
2786 | 0 | res = UA_STATUSCODE_BADCONNECTIONCLOSED; |
2787 | 0 | } |
2788 | |
|
2789 | 0 | unlockClient(client); |
2790 | 0 | return res; |
2791 | 0 | } |
2792 | | |
2793 | | /************************/ |
2794 | | /* Close the Connection */ |
2795 | | /************************/ |
2796 | | |
2797 | | void |
2798 | 40 | closeSecureChannel(UA_Client *client) { |
2799 | | /* If we close SecureChannel when the Session is still active, set to |
2800 | | * created. Otherwise the Session would remain active until the connection |
2801 | | * callback is called for the closing connection. */ |
2802 | 40 | if(client->sessionState == UA_SESSIONSTATE_ACTIVATED) |
2803 | 0 | client->sessionState = UA_SESSIONSTATE_CREATED; |
2804 | | |
2805 | | /* Prevent recursion */ |
2806 | 40 | if(client->channel.state == UA_SECURECHANNELSTATE_CLOSING || |
2807 | 0 | client->channel.state == UA_SECURECHANNELSTATE_CLOSED) |
2808 | 40 | return; |
2809 | | |
2810 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
2811 | 0 | "Closing the channel"); |
2812 | |
|
2813 | 0 | disconnectListenSockets(client); |
2814 | | |
2815 | | /* Send CLO if the SecureChannel is open */ |
2816 | 0 | if(client->channel.state == UA_SECURECHANNELSTATE_OPEN) { |
2817 | 0 | UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel, |
2818 | 0 | "Sending the CLO message"); |
2819 | |
|
2820 | 0 | UA_EventLoop *el = client->config.eventLoop; |
2821 | | |
2822 | | /* Manually set up the header (otherwise done in sendRequest) */ |
2823 | 0 | UA_CloseSecureChannelRequest request; |
2824 | 0 | UA_CloseSecureChannelRequest_init(&request); |
2825 | 0 | request.requestHeader.requestHandle = ++client->requestHandle; |
2826 | 0 | request.requestHeader.timestamp = el->dateTime_now(el); |
2827 | 0 | request.requestHeader.timeoutHint = client->config.timeout; |
2828 | 0 | request.requestHeader.authenticationToken = client->authenticationToken; |
2829 | 0 | UA_SecureChannel_sendCLO(&client->channel, ++client->requestId, &request); |
2830 | 0 | } |
2831 | | |
2832 | | /* The connection is eventually closed in the next callback from the |
2833 | | * ConnectionManager with the appropriate status code. Don't set the |
2834 | | * connection closed right away! */ |
2835 | 0 | UA_SecureChannel_shutdown(&client->channel, UA_SHUTDOWNREASON_CLOSE); |
2836 | 0 | } |
2837 | | |
2838 | | static void |
2839 | 40 | sendCloseSession(UA_Client *client) { |
2840 | 40 | UA_CloseSessionRequest request; |
2841 | 40 | UA_CloseSessionRequest_init(&request); |
2842 | 40 | request.deleteSubscriptions = true; |
2843 | 40 | UA_CloseSessionResponse response; |
2844 | 40 | __Client_Service(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], |
2845 | 40 | &response, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE]); |
2846 | 40 | UA_CloseSessionRequest_clear(&request); |
2847 | 40 | UA_CloseSessionResponse_clear(&response); |
2848 | | |
2849 | | /* Set after sending the message to prevent immediate reoping during the |
2850 | | * service call */ |
2851 | 40 | client->sessionState = UA_SESSIONSTATE_CLOSING; |
2852 | 40 | } |
2853 | | |
2854 | | void |
2855 | 80 | cleanupSession(UA_Client *client) { |
2856 | 80 | UA_NodeId_clear(&client->sessionId); |
2857 | 80 | UA_NodeId_clear(&client->authenticationToken); |
2858 | 80 | client->requestHandle = 0; |
2859 | | |
2860 | 80 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
2861 | | /* We need to clean up the subscriptions */ |
2862 | 80 | __Client_Subscriptions_clear(client); |
2863 | 80 | #endif |
2864 | | |
2865 | | /* Delete outstanding async services */ |
2866 | 80 | __Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSESSIONCLOSED); |
2867 | | |
2868 | 80 | #ifdef UA_ENABLE_SUBSCRIPTIONS |
2869 | 80 | client->currentlyOutStandingPublishRequests = 0; |
2870 | 80 | #endif |
2871 | | |
2872 | 80 | client->sessionState = UA_SESSIONSTATE_CLOSED; |
2873 | | |
2874 | | /* Clean the latest server's ephemeral public key */ |
2875 | 80 | UA_ByteString_clear(&client->serverEphemeralPubKey); |
2876 | 80 | if(client->utpSp && client->utpSpContext) |
2877 | 0 | client->utpSp->deleteChannelContext(client->utpSp, client->utpSpContext); |
2878 | | /* The None policy has no context, but its policy reference must still be |
2879 | | * reset so a new Session can select a different authentication policy. */ |
2880 | 80 | client->utpSp = NULL; |
2881 | 80 | client->utpSpContext = NULL; |
2882 | 80 | } |
2883 | | |
2884 | | static void |
2885 | 80 | disconnectSecureChannel(UA_Client *client, UA_Boolean sync) { |
2886 | | /* Clean the DiscoveryUrl and endpoint description when the connection is |
2887 | | * explicitly closed */ |
2888 | 80 | UA_String_clear(&client->discoveryUrl); |
2889 | 80 | UA_EndpointDescription_clear(&client->endpoint); |
2890 | | |
2891 | | /* Close the SecureChannel */ |
2892 | 80 | closeSecureChannel(client); |
2893 | | |
2894 | | /* Manually set the status to closed to prevent an automatic reconnection. |
2895 | | * Notify the client at the end. */ |
2896 | 80 | if(client->connectStatus == UA_STATUSCODE_GOOD) |
2897 | 40 | client->connectStatus = UA_STATUSCODE_BADCONNECTIONCLOSED; |
2898 | | |
2899 | | /* In the synchronous case, loop until the client has actually closed. */ |
2900 | 80 | UA_EventLoop *el = client->config.eventLoop; |
2901 | 80 | if(sync && el && |
2902 | 80 | el->state != UA_EVENTLOOPSTATE_FRESH && |
2903 | 0 | el->state != UA_EVENTLOOPSTATE_STOPPED) { |
2904 | 0 | while(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) { |
2905 | 0 | UA_StatusCode runStatus = el->run(el, 100); |
2906 | 0 | if(runStatus != UA_STATUSCODE_GOOD) { |
2907 | 0 | UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT, |
2908 | 0 | "EventLoop run returned %s during synchronous disconnect, " |
2909 | 0 | "stopping wait loop", UA_StatusCode_name(runStatus)); |
2910 | 0 | break; |
2911 | 0 | } |
2912 | 0 | } |
2913 | 0 | } |
2914 | | |
2915 | 80 | notifyClientState(client); |
2916 | 80 | } |
2917 | | |
2918 | | UA_StatusCode |
2919 | 0 | UA_Client_disconnectSecureChannel(UA_Client *client) { |
2920 | 0 | lockClient(client); |
2921 | 0 | disconnectSecureChannel(client, true); |
2922 | 0 | unlockClient(client); |
2923 | 0 | return UA_STATUSCODE_GOOD; |
2924 | 0 | } |
2925 | | |
2926 | | UA_StatusCode |
2927 | 0 | UA_Client_disconnectSecureChannelAsync(UA_Client *client) { |
2928 | 0 | lockClient(client); |
2929 | 0 | disconnectSecureChannel(client, false); |
2930 | 0 | unlockClient(client); |
2931 | 0 | return UA_STATUSCODE_GOOD; |
2932 | 0 | } |
2933 | | |
2934 | | UA_StatusCode |
2935 | 80 | UA_Client_disconnect(UA_Client *client) { |
2936 | 80 | lockClient(client); |
2937 | 80 | if(client->sessionState == UA_SESSIONSTATE_ACTIVATED) |
2938 | 40 | sendCloseSession(client); |
2939 | 80 | cleanupSession(client); |
2940 | 80 | disconnectSecureChannel(client, true); |
2941 | 80 | unlockClient(client); |
2942 | 80 | return UA_STATUSCODE_GOOD; |
2943 | 80 | } |
2944 | | |
2945 | | static void |
2946 | | closeSessionCallback(UA_Client *client, void *userdata, |
2947 | 0 | UA_UInt32 requestId, void *response) { |
2948 | 0 | lockClient(client); |
2949 | 0 | cleanupSession(client); |
2950 | 0 | disconnectSecureChannel(client, false); |
2951 | 0 | notifyClientState(client); |
2952 | 0 | unlockClient(client); |
2953 | 0 | } |
2954 | | |
2955 | | UA_StatusCode |
2956 | 0 | UA_Client_disconnectAsync(UA_Client *client) { |
2957 | 0 | lockClient(client); |
2958 | |
|
2959 | 0 | if(client->sessionState == UA_SESSIONSTATE_CLOSED || |
2960 | 0 | client->sessionState == UA_SESSIONSTATE_CLOSING) { |
2961 | 0 | disconnectSecureChannel(client, false); |
2962 | 0 | notifyClientState(client); |
2963 | 0 | unlockClient(client); |
2964 | 0 | return UA_STATUSCODE_GOOD; |
2965 | 0 | } |
2966 | | |
2967 | | /* Set before sending the message to prevent recursion */ |
2968 | 0 | client->sessionState = UA_SESSIONSTATE_CLOSING; |
2969 | |
|
2970 | 0 | UA_CloseSessionRequest request; |
2971 | 0 | UA_CloseSessionRequest_init(&request); |
2972 | 0 | request.deleteSubscriptions = true; |
2973 | 0 | UA_StatusCode res = |
2974 | 0 | __Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST], |
2975 | 0 | (UA_ClientAsyncServiceCallback)closeSessionCallback, |
2976 | 0 | &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL, NULL); |
2977 | 0 | if(res != UA_STATUSCODE_GOOD) { |
2978 | | /* Sending the close request failed. Continue to close the connection |
2979 | | * anyway. */ |
2980 | 0 | cleanupSession(client); |
2981 | 0 | disconnectSecureChannel(client, false); |
2982 | 0 | } |
2983 | |
|
2984 | 0 | notifyClientState(client); |
2985 | 0 | unlockClient(client); |
2986 | 0 | return res; |
2987 | 0 | } |