#include <open62541/types.h>
#include "open62541/transport_generated.h"
#include "ua_client_internal.h"
#include "../ua_types_encoding_binary.h"
#include "mp_printf.h"
#define UA_MINMESSAGESIZE 8192
#define MAX_DATA_SIZE 4096
static void initConnect(UA_Client *client);
static UA_StatusCode createSessionAsync(UA_Client *client);
static UA_UserTokenPolicy *
findUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint,
char *logPrefix);
static UA_String
getEndpointUrl(UA_Client *client) {
if(client->endpoint.endpointUrl.length > 0)
return client->endpoint.endpointUrl;
if(client->discoveryUrl.length > 0)
return client->discoveryUrl;
return client->config.endpointUrl;
}
static UA_StatusCode
fallbackEndpointUrl(UA_Client* client) {
UA_String currentUrl = getEndpointUrl(client);
if(UA_String_equal(¤tUrl, &client->config.endpointUrl)) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not open a TCP connection to the Endpoint at %S",
client->config.endpointUrl);
return UA_STATUSCODE_BADCONNECTIONREJECTED;
}
if(client->endpoint.endpointUrl.length > 0) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not open a TCP connection to the Endpoint at %S. "
"Overriding the endpoint description with the initial "
"EndpointUrl at %S.",
client->config.endpoint.endpointUrl,
client->config.endpointUrl);
UA_String_clear(&client->endpoint.endpointUrl);
return UA_String_copy(&client->config.endpointUrl,
&client->endpoint.endpointUrl);
} else {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The DiscoveryUrl returned by the FindServers service (%S) "
"could not be connected. Continuing with the initial EndpointUrl "
"%S for the GetEndpoints service.",
client->config.endpointUrl, client->config.endpointUrl);
UA_String_clear(&client->discoveryUrl);
return UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl);
}
}
static UA_SecurityPolicy *
getSecurityPolicy(UA_Client *client, UA_String policyUri) {
for(size_t i = 0; i < client->config.securityPoliciesSize; i++) {
if(UA_String_equal(&policyUri, &client->config.securityPolicies[i].policyUri))
return &client->config.securityPolicies[i];
}
return NULL;
}
static UA_SecurityPolicy *
getAuthSecurityPolicy(UA_Client *client, const UA_String policyUri,
const UA_ByteString *certificate) {
for(size_t i = 0; i < client->config.authSecurityPoliciesSize; i++) {
UA_SecurityPolicy *sp = &client->config.authSecurityPolicies[i];
if(!UA_String_equal(&policyUri, &sp->policyUri))
continue;
if(certificate && !UA_ByteString_equal(certificate, &sp->localCertificate))
continue;
return sp;
}
return NULL;
}
static UA_Boolean
endpointUnconfigured(const UA_EndpointDescription *endpoint) {
UA_EndpointDescription tmp;
UA_EndpointDescription_init(&tmp);
return UA_equal(&tmp, endpoint, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
}
UA_Boolean
isFullyConnected(UA_Client *client) {
if(client->channel.state != UA_SECURECHANNELSTATE_OPEN)
return false;
if(client->endpointsHandshake || endpointUnconfigured(&client->endpoint))
return false;
if(client->findServersHandshake || client->discoveryUrl.length == 0)
return false;
if(!client->config.noSession) {
if(client->sessionState != UA_SESSIONSTATE_ACTIVATED)
return false;
if(client->namespacesHandshake || !client->haveNamespaces)
return false;
}
return true;
}
static UA_StatusCode
initUserTokenPolicy(UA_Client *client, const UA_UserTokenPolicy **outUtp,
char *logPrefix) {
const UA_UserTokenPolicy *utp =
findUserTokenPolicy(client, &client->endpoint, logPrefix);
if(!utp) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: Could not find a matching UserTokenPolicy "
"in the endpoint", logPrefix);
return UA_STATUSCODE_BADSECURITYPOLICYREJECTED;
}
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: Using UserTokenPolicy %S", logPrefix, utp->policyId);
UA_String tokenSecurityPolicyUri = (utp->securityPolicyUri.length > 0) ?
utp->securityPolicyUri : client->endpoint.securityPolicyUri;
UA_SecurityPolicy *utsp;
if(utp->tokenType == UA_USERTOKENTYPE_CERTIFICATE) {
UA_X509IdentityToken *token = (UA_X509IdentityToken*)
client->config.userIdentityToken.content.decoded.data;
utsp = getAuthSecurityPolicy(client, tokenSecurityPolicyUri,
&token->certificateData);
} else {
utsp = getAuthSecurityPolicy(client, tokenSecurityPolicyUri, NULL);
if(!utsp)
utsp = getSecurityPolicy(client, tokenSecurityPolicyUri);
}
if(!utsp) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: SecurityPolicy %S not available for the "
"UserTokenPolicy", logPrefix, tokenSecurityPolicyUri);
return UA_STATUSCODE_BADSECURITYPOLICYREJECTED;
}
if(client->utpSp) {
if(!UA_String_equal(&client->utpSp->policyUri,
&tokenSecurityPolicyUri)) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: SecurityPolicy %S cannot be instantiated. "
"A different SecurityPolicy %S is in place already",
logPrefix, tokenSecurityPolicyUri,
client->utpSp->policyUri);
return UA_STATUSCODE_BADSECURITYPOLICYREJECTED;
}
*outUtp = utp;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode res =
utsp->newChannelContext(utsp, &client->endpoint.serverCertificate,
&client->utpSpContext);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S could not be instantiated with the "
"server certificate", logPrefix, tokenSecurityPolicyUri);
return UA_STATUSCODE_BADSECURITYPOLICYREJECTED;
}
client->utpSp = utsp;
*outUtp = utp;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
signLegacyCertNonce(const UA_SecurityPolicy *sp, void *spContext,
const UA_ByteString *remoteCertificate,
const UA_ByteString *serverNonce, UA_ByteString *outSignature) {
UA_ByteString leaf = getLeafCertificate(*remoteCertificate);
size_t signDataSize = leaf.length + serverNonce->length;
if(signDataSize > MAX_DATA_SIZE)
return UA_STATUSCODE_BADINTERNALERROR;
UA_Byte buf[MAX_DATA_SIZE];
UA_ByteString signData = {signDataSize, buf};
memcpy(buf, leaf.data, leaf.length);
memcpy(buf + leaf.length, serverNonce->data, serverNonce->length);
return sp->asymSignatureAlgorithm.sign(sp, spContext, &signData, outSignature);
}
static UA_StatusCode
signClientSignature(UA_Client *client, UA_ActivateSessionRequest *request) {
UA_SecureChannel *channel = &client->channel;
const UA_SecurityPolicy *sp = channel->securityPolicy;
void *cc = channel->channelContext;
UA_SignatureData *sd = &request->clientSignature;
const UA_SecurityPolicySignatureAlgorithm *signAlg = &sp->asymSignatureAlgorithm;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(!UA_SecurityPolicy_isEnhancedSecurity(sp))
retval = UA_String_copy(&signAlg->uri, &sd->algorithm);
if(retval != UA_STATUSCODE_GOOD)
return retval;
size_t signatureSize = signAlg->getLocalSignatureSize(sp, cc);
retval = UA_ByteString_allocBuffer(&sd->signature, signatureSize);
if(retval != UA_STATUSCODE_GOOD)
return retval;
if(UA_SecurityPolicy_isEnhancedSecurity(sp)) {
UA_ByteString signData = UA_BYTESTRING_NULL;
retval = UA_SecureChannel_buildActivateSessionSignatureData(
channel, &client->serverSessionNonce, &client->clientSessionNonce,
&channel->remoteCertificate, &channel->remoteCertificate,
&sp->localCertificate, &signData);
if(retval != UA_STATUSCODE_GOOD)
return retval;
retval = signAlg->sign(sp, cc, &signData, &sd->signature);
UA_ByteString_clear(&signData);
return retval;
}
return signLegacyCertNonce(sp, cc, &channel->remoteCertificate,
&client->serverSessionNonce, &sd->signature);
}
static UA_StatusCode
signUserTokenSignature(UA_Client *client,
UA_ActivateSessionRequest *request) {
UA_assert(client->config.userIdentityToken.content.decoded.type ==
&UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]);
UA_SecurityPolicy *utpSp = client->utpSp;
UA_assert(utpSp);
UA_SecureChannel *channel = &client->channel;
UA_SecurityPolicySignatureAlgorithm *signAlg = &utpSp->asymSignatureAlgorithm;
UA_SignatureData *utsd = &request->userTokenSignature;
UA_StatusCode retval = UA_String_copy(&signAlg->uri, &utsd->algorithm);
if(retval != UA_STATUSCODE_GOOD)
return retval;
size_t sigLen = signAlg->getLocalSignatureSize(utpSp, client->utpSpContext);
retval = UA_ByteString_allocBuffer(&utsd->signature, sigLen);
if(retval != UA_STATUSCODE_GOOD)
return retval;
if(UA_SecurityPolicy_isEnhancedSecurity(channel->securityPolicy) &&
UA_SecurityPolicy_isEnhancedSecurity(utpSp)) {
const UA_ByteString *clientCert = &channel->securityPolicy->localCertificate;
UA_ByteString signData = UA_BYTESTRING_NULL;
retval = UA_SecureChannel_buildUserTokenSignatureData(
channel, &client->serverSessionNonce, &client->clientSessionNonce,
&channel->remoteCertificate, &channel->remoteCertificate,
clientCert, clientCert, &signData);
if(retval == UA_STATUSCODE_GOOD)
retval = signAlg->sign(utpSp, client->utpSpContext, &signData, &utsd->signature);
UA_ByteString_clear(&signData);
return retval;
}
return signLegacyCertNonce(utpSp, client->utpSpContext,
&channel->remoteCertificate,
&client->serverSessionNonce, &utsd->signature);
}
static UA_StatusCode
encryptUserIdentityToken(UA_Client *client, UA_ExtensionObject *userIdentityToken) {
UA_IssuedIdentityToken *iit = NULL;
UA_UserNameIdentityToken *unit = NULL;
UA_String *encryptionAlg;
UA_ByteString *tokenData;
const UA_DataType *tokenType = userIdentityToken->content.decoded.type;
if(tokenType == &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) {
iit = (UA_IssuedIdentityToken*)userIdentityToken->content.decoded.data;
tokenData = &iit->tokenData;
encryptionAlg = &iit->encryptionAlgorithm;
} else {
UA_assert(tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]);
unit = (UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data;
tokenData = &unit->password;
encryptionAlg = &unit->encryptionAlgorithm;
}
UA_SecurityPolicy *utpSp = client->utpSp;
UA_assert(utpSp);
if(utpSp->policyType == UA_SECURITYPOLICYTYPE_NONE) {
if(client->channel.securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"!!! Warning !!! AuthenticationToken is transmitted "
"without encryption");
return UA_STATUSCODE_GOOD;
}
UA_StatusCode res = UA_STATUSCODE_GOOD;
if(UA_SecurityPolicy_isEcc(utpSp)) {
res = encryptUserIdentityTokenEcc(client->config.logging, &client->channel,
utpSp, client->utpSpContext, tokenData,
client->serverSessionNonce,
client->serverEphemeralPubKey);
UA_ByteString_clear(&client->serverEphemeralPubKey);
return res;
}
res = encryptSecretLegacy(utpSp, client->utpSpContext,
client->serverSessionNonce, tokenData);
return res | UA_String_copy(&utpSp->asymEncryptionAlgorithm.uri, encryptionAlg);
}
static UA_StatusCode
checkCreateSessionSignature(UA_Client *client, const UA_SecureChannel *channel,
const UA_CreateSessionResponse *response) {
if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN &&
channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
return UA_STATUSCODE_GOOD;
if(!channel->securityPolicy)
return UA_STATUSCODE_BADINTERNALERROR;
const UA_SecurityPolicy *sp = channel->securityPolicy;
const UA_ByteString *lc = &sp->localCertificate;
UA_ByteString dataToVerify = UA_BYTESTRING_NULL;
UA_StatusCode retval;
if(UA_SecurityPolicy_isEnhancedSecurity(sp)) {
retval = UA_SecureChannel_buildCreateSessionSignatureData(
channel, &client->clientSessionNonce, &response->serverNonce,
&channel->remoteCertificate, lc, &dataToVerify);
} else {
retval = UA_ByteString_allocBuffer(&dataToVerify,
lc->length + client->clientSessionNonce.length);
if(retval == UA_STATUSCODE_GOOD) {
memcpy(dataToVerify.data, lc->data, lc->length);
memcpy(dataToVerify.data + lc->length, client->clientSessionNonce.data,
client->clientSessionNonce.length);
}
}
if(retval != UA_STATUSCODE_GOOD)
return retval;
const UA_SecurityPolicySignatureAlgorithm *signAlg = &sp->asymSignatureAlgorithm;
retval = signAlg->verify(sp, channel->channelContext, &dataToVerify,
&response->serverSignature.signature);
UA_ByteString_clear(&dataToVerify);
return retval;
}
void
processERRResponse(UA_Client *client, const UA_ByteString *chunk) {
size_t offset = 0;
UA_TcpErrorMessage errMessage;
UA_StatusCode res =
UA_decodeBinaryInternal(chunk, &offset, &errMessage,
&UA_TRANSPORT[UA_TRANSPORT_TCPERRORMESSAGE], NULL);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel,
"Received an ERR response that could not be decoded "
"with StatusCode %s", UA_StatusCode_name(res));
setConnectStatus(client, res);
return;
}
UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel,
"Received an ERR response with StatusCode %s and "
"the following reason: \"%S\"",
UA_StatusCode_name(errMessage.error),
errMessage.reason);
setConnectStatus(client, errMessage.error);
UA_TcpErrorMessage_clear(&errMessage);
}
void
processACKResponse(UA_Client *client, const UA_ByteString *chunk) {
UA_SecureChannel *channel = &client->channel;
if(channel->state != UA_SECURECHANNELSTATE_HEL_SENT) {
UA_LOG_ERROR_CHANNEL(client->config.logging, channel,
"SecureChannel not in the HEL-sent state");
setConnectStatus(client, UA_STATUSCODE_BADSECURECHANNELCLOSED);
return;
}
size_t offset = 0;
UA_TcpAcknowledgeMessage ackMessage;
UA_StatusCode res =
UA_decodeBinaryInternal(chunk, &offset, &ackMessage,
&UA_TRANSPORT[UA_TRANSPORT_TCPACKNOWLEDGEMESSAGE], NULL);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_NETWORK,
"Decoding ACK message failed");
setConnectStatus(client, res);
return;
}
res = UA_SecureChannel_processHELACK(channel, &ackMessage);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_NETWORK,
"Processing the ACK message failed with StatusCode %s",
UA_StatusCode_name(res));
setConnectStatus(client, res);
return;
}
client->channel.state = UA_SECURECHANNELSTATE_ACK_RECEIVED;
}
static UA_StatusCode
sendHELMessage(UA_Client *client) {
UA_ConnectionManager *cm = client->channel.connectionManager;
if(!UA_SecureChannel_isConnected(&client->channel))
return UA_STATUSCODE_BADNOTCONNECTED;
UA_ByteString message;
UA_StatusCode retval = cm->allocNetworkBuffer(cm, client->channel.connectionId,
&message, UA_MINMESSAGESIZE);
if(retval != UA_STATUSCODE_GOOD)
return retval;
UA_TcpHelloMessage hello;
hello.protocolVersion = 0;
hello.receiveBufferSize = client->config.localConnectionConfig.recvBufferSize;
hello.sendBufferSize = client->config.localConnectionConfig.sendBufferSize;
hello.maxMessageSize = client->config.localConnectionConfig.localMaxMessageSize;
hello.maxChunkCount = client->config.localConnectionConfig.localMaxChunkCount;
hello.endpointUrl = getEndpointUrl(client);
UA_Byte *bufPos = &message.data[8];
const UA_Byte *bufEnd = &message.data[message.length];
retval = UA_encodeBinaryInternal(&hello, &UA_TRANSPORT[UA_TRANSPORT_TCPHELLOMESSAGE],
&bufPos, &bufEnd, NULL, NULL, NULL);
if(retval != UA_STATUSCODE_GOOD) {
cm->freeNetworkBuffer(cm, client->channel.connectionId, &message);
return retval;
}
UA_TcpMessageHeader messageHeader;
messageHeader.messageTypeAndChunkType = UA_CHUNKTYPE_FINAL + UA_MESSAGETYPE_HEL;
messageHeader.messageSize = (UA_UInt32) ((uintptr_t)bufPos - (uintptr_t)message.data);
bufPos = message.data;
retval = UA_encodeBinaryInternal(&messageHeader,
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER],
&bufPos, &bufEnd, NULL, NULL, NULL);
if(retval != UA_STATUSCODE_GOOD) {
cm->freeNetworkBuffer(cm, client->channel.connectionId, &message);
return retval;
}
message.length = messageHeader.messageSize;
retval = cm->sendWithConnection(cm, client->channel.connectionId,
&UA_KEYVALUEMAP_NULL, &message);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Sending HEL failed");
setConnectStatus(client, retval);
return retval;
}
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Sent HEL message");
client->channel.state = UA_SECURECHANNELSTATE_HEL_SENT;
return UA_STATUSCODE_GOOD;
}
void processRHEMessage(UA_Client *client, const UA_ByteString *chunk) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT, "RHE received");
size_t offset = 0;
UA_TcpReverseHelloMessage rheMessage;
static const UA_DataType *rheType = &UA_TRANSPORT[UA_TRANSPORT_TCPREVERSEHELLOMESSAGE];
UA_StatusCode res = UA_decodeBinaryInternal(chunk, &offset, &rheMessage, rheType, NULL);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_NETWORK,
"Decoding RHE message failed");
setConnectStatus(client, res);
return;
}
UA_String_clear(&client->discoveryUrl);
client->discoveryUrl = rheMessage.endpointUrl;
UA_String_init(&rheMessage.endpointUrl);
UA_TcpReverseHelloMessage_clear(&rheMessage);
setConnectStatus(client, sendHELMessage(client));
}
void
processOPNResponse(UA_Client *client, const UA_ByteString *message) {
size_t offset = 0;
UA_NodeId responseId;
UA_OpenSecureChannelResponse response;
UA_NodeId expectedId = UA_NS0ID(OPENSECURECHANNELRESPONSE_ENCODING_DEFAULTBINARY);
UA_StatusCode res = UA_NodeId_decodeBinary(message, &offset, &responseId);
if(res != UA_STATUSCODE_GOOD)
goto finish_decode;
if(!UA_NodeId_equal(&responseId, &expectedId)) {
res = UA_STATUSCODE_BADDECODINGERROR;
goto finish_decode;
}
res = UA_decodeBinaryInternal(message, &offset, &response,
&UA_TYPES[UA_TYPES_OPENSECURECHANNELRESPONSE], NULL);
finish_decode:
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel,
"Could not decode the OpenSecureChannelResponse");
setConnectStatus(client, res);
return;
}
if(client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE &&
UA_ByteString_equal(&client->channel.remoteNonce, &response.serverNonce)) {
UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel,
"The server reused the last nonce");
setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED);
return;
}
if(response.serverNonce.length < client->channel.securityPolicy->nonceLength) {
UA_LOG_ERROR_CHANNEL(client->config.logging, &client->channel,
"The server nonce is too short");
setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED);
return;
}
UA_ByteString_clear(&client->channel.remoteNonce);
client->channel.remoteNonce = response.serverNonce;
UA_ByteString_init(&response.serverNonce);
UA_ResponseHeader_clear(&response.responseHeader);
client->channel.altSecurityToken = client->channel.securityToken;
client->channel.securityToken = response.securityToken;
client->channel.renewState = UA_SECURECHANNELRENEWSTATE_NEWTOKEN_CLIENT;
UA_EventLoop *el = client->config.eventLoop;
UA_DateTime wallClockNow = el->dateTime_now(el);
UA_ChannelSecurityToken *st = &client->channel.securityToken;
if(wallClockNow - st->createdAt >= UA_DATETIME_SEC * 10 ||
wallClockNow - st->createdAt <= -UA_DATETIME_SEC * 10)
UA_LOG_WARNING_CHANNEL(client->config.logging, &client->channel,
"The \"CreatedAt\" timestamp of the received "
"ChannelSecurityToken does not match "
"with the local system clock");
client->channel.securityToken.createdAt = el->dateTime_nowMonotonic(el);
client->nextChannelRenewal = client->channel.securityToken.createdAt +
(UA_DateTime) (response.securityToken.revisedLifetime *
(UA_Double) UA_DATETIME_MSEC * 0.75);
res = UA_SecureChannel_generateLocalKeys(&client->channel);
if(res != UA_STATUSCODE_GOOD) {
setConnectStatus(client, res);
return;
}
UA_Float lifetime = (UA_Float)response.securityToken.revisedLifetime / 1000;
UA_Boolean renew = (client->channel.state == UA_SECURECHANNELSTATE_OPEN);
if(renew) {
UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel, "SecureChannel "
"renewed with a revised lifetime of %.2fs", lifetime);
} else {
UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel,
"SecureChannel opened with SecurityMode %s for "
"SecurityPolicy %S and a revised lifetime of %.2fs",
securityModeNames[client->channel.securityMode],
client->channel.securityPolicy->policyUri,
lifetime);
}
client->channel.state = UA_SECURECHANNELSTATE_OPEN;
}
static UA_StatusCode
sendOPNAsync(UA_Client *client, UA_Boolean renew) {
if(!UA_SecureChannel_isConnected(&client->channel))
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode res = UA_SecureChannel_generateLocalNonce(&client->channel);
if(res != UA_STATUSCODE_GOOD)
return res;
UA_EventLoop *el = client->config.eventLoop;
UA_OpenSecureChannelRequest opnSecRq;
UA_OpenSecureChannelRequest_init(&opnSecRq);
opnSecRq.requestHeader.timestamp = el->dateTime_now(el);
opnSecRq.requestHeader.authenticationToken = client->authenticationToken;
opnSecRq.securityMode = client->channel.securityMode;
opnSecRq.clientNonce = client->channel.localNonce;
opnSecRq.requestedLifetime = client->config.secureChannelLifeTime;
if(renew) {
opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_RENEW;
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
"Requesting to renew the SecureChannel");
} else {
opnSecRq.requestType = UA_SECURITYTOKENREQUESTTYPE_ISSUE;
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
"Requesting to open a SecureChannel");
}
UA_UInt32 requestId = ++client->requestId;
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL,
"Requesting to open a SecureChannel");
static const UA_DataType *opnReqType = &UA_TYPES[UA_TYPES_OPENSECURECHANNELREQUEST];
res = UA_SecureChannel_sendOPN(&client->channel, requestId, &opnSecRq, opnReqType);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL,
"Sending OPN message failed with error %s",
UA_StatusCode_name(res));
return res;
}
client->channel.renewState = UA_SECURECHANNELRENEWSTATE_SENT;
if(client->channel.state < UA_SECURECHANNELSTATE_OPN_SENT)
client->channel.state = UA_SECURECHANNELSTATE_OPN_SENT;
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SECURECHANNEL,
"OPN message sent");
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
__Client_renewSecureChannel(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_EventLoop *el = client->config.eventLoop;
UA_DateTime now = el->dateTime_nowMonotonic(el);
if(client->channel.state != UA_SECURECHANNELSTATE_OPEN ||
client->channel.renewState == UA_SECURECHANNELRENEWSTATE_SENT ||
client->nextChannelRenewal > now)
return UA_STATUSCODE_GOODCALLAGAIN;
UA_StatusCode res = sendOPNAsync(client, true);
setConnectStatus(client, res);
return res;
}
UA_StatusCode
UA_Client_renewSecureChannel(UA_Client *client) {
lockClient(client);
UA_StatusCode res = __Client_renewSecureChannel(client);
unlockClient(client);
return res;
}
static void
responseReadNamespacesArray(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
client->namespacesHandshake = false;
client->haveNamespaces = true;
UA_ReadResponse *resp = (UA_ReadResponse *)response;
if(!resp->results || !resp->results[0].value.data) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"No result in the read namespace array response");
return;
}
UA_String *ns = (UA_String *)resp->results[0].value.data;
size_t nsSize = resp->results[0].value.arrayLength;
UA_String_copy(&ns[1], &client->namespaces[1]);
for(size_t i = 2; i < nsSize; ++i) {
UA_UInt16 nsIndex = 0;
UA_Client_addNamespace(client, ns[i], &nsIndex);
}
UA_NamespaceMapping *nsMapping = (UA_NamespaceMapping*)
UA_calloc(1, sizeof(UA_NamespaceMapping));
if(!nsMapping) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
return;
}
UA_StatusCode retval =
UA_Array_copy(client->namespaces, client->namespacesSize,
(void**)&nsMapping->namespaceUris,
&UA_TYPES[UA_TYPES_STRING]);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Failed to copy the namespaces with StatusCode %s.",
UA_StatusCode_name(retval));
UA_NamespaceMapping_delete(nsMapping);
return;
}
nsMapping->namespaceUrisSize = client->namespacesSize;
nsMapping->remote2local = (UA_UInt16*)UA_calloc( nsSize, sizeof(UA_UInt16));
if(!nsMapping->remote2local) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
UA_NamespaceMapping_delete(nsMapping);
return;
}
nsMapping->remote2localSize = nsSize;
nsMapping->remote2local[0] = 0;
nsMapping->remote2local[1] = 1;
for(size_t i = 2; i < nsSize; ++i) {
UA_UInt16 nsIndex = 0;
UA_Client_getNamespaceIndex(client, ns[i], &nsIndex);
nsMapping->remote2local[i] = nsIndex;
}
size_t l2rSize = client->namespacesSize > nsSize ? client->namespacesSize : nsSize;
nsMapping->local2remote = (UA_UInt16*)UA_calloc(l2rSize, sizeof(UA_UInt16));
if(!nsMapping->local2remote) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Namespace mapping creation failed. Out of Memory.");
UA_NamespaceMapping_delete(nsMapping);
return;
}
nsMapping->local2remoteSize = l2rSize;
nsMapping->local2remote[0] = 0;
nsMapping->local2remote[1] = 1;
for(size_t i = 2; i < nsMapping->remote2localSize; ++i) {
UA_UInt16 localIndex = nsMapping->remote2local[i];
nsMapping->local2remote[localIndex] = (UA_UInt16)i;
}
client->channel.namespaceMapping = nsMapping;
}
static void
readNamespacesArrayAsync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
if(client->sessionState != UA_SESSIONSTATE_CREATED &&
client->sessionState != UA_SESSIONSTATE_ACTIVATED) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot read the namespaces array, session neither created nor "
"activated. Actual state: '%u'", client->sessionState);
return;
}
UA_ReadRequest rr;
UA_ReadRequest_init(&rr);
UA_ReadValueId nodesToRead;
UA_ReadValueId_init(&nodesToRead);
nodesToRead.nodeId = UA_NS0ID(SERVER_NAMESPACEARRAY);
nodesToRead.attributeId = UA_ATTRIBUTEID_VALUE;
rr.nodesToRead = &nodesToRead;
rr.nodesToReadSize = 1;
UA_StatusCode res =
__Client_AsyncService(client, &rr, &UA_TYPES[UA_TYPES_READREQUEST],
(UA_ClientAsyncServiceCallback)responseReadNamespacesArray,
&UA_TYPES[UA_TYPES_READRESPONSE],
NULL, NULL);
if(res == UA_STATUSCODE_GOOD)
client->namespacesHandshake = true;
else
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not read the namespace array with error code %s",
UA_StatusCode_name(res));
}
static void
extractEphemeralKeyFromAddHeader(UA_Client *client, UA_ExtensionObject *ah) {
const UA_DataType *ahType = &UA_TYPES[UA_TYPES_ADDITIONALPARAMETERSTYPE];
if(!UA_ExtensionObject_hasDecodedType(ah, ahType))
return;
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_SESSION,
"Server Ephemeral Key in the response");
UA_KeyValueMap *map = (UA_KeyValueMap*)ah->content.decoded.data;
UA_EphemeralKeyType *ephKey = (UA_EphemeralKeyType*)(uintptr_t)
UA_KeyValueMap_getScalar(map, UA_QUALIFIEDNAME(0, "ECDHKey"),
&UA_TYPES[UA_TYPES_EPHEMERALKEYTYPE]);
if(!ephKey)
return;
UA_ByteString_clear(&client->serverEphemeralPubKey);
client->serverEphemeralPubKey = ephKey->publicKey;
UA_ByteString_init(&ephKey->publicKey);
}
static void
responseActivateSession(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_ActivateSessionResponse *ar = (UA_ActivateSessionResponse*)response;
if(ar->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
cleanupSession(client);
if(client->config.noNewSession) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Session cannot be activated with StatusCode %s. "
"The client is configured not to create a new Session.",
UA_StatusCode_name(ar->responseHeader.serviceResult));
setConnectStatus(client, ar->responseHeader.serviceResult);
return;
}
if(ar->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONIDINVALID ||
ar->responseHeader.serviceResult == UA_STATUSCODE_BADSESSIONCLOSED) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Session to be activated no longer exists. Create a new Session.");
setConnectStatus(client, createSessionAsync(client));
return;
}
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Session cannot be activated with StatusCode %s. "
"The client cannot recover from this, closing the connection.",
UA_StatusCode_name(ar->responseHeader.serviceResult));
setConnectStatus(client, ar->responseHeader.serviceResult);
return;
}
if(ar->serverNonce.length < 32) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Session cannot be activated with a nonce "
"that is too short");
setConnectStatus(client, UA_STATUSCODE_BADSECURITYCHECKSFAILED);
return;
}
UA_ByteString_clear(&client->serverSessionNonce);
client->serverSessionNonce = ar->serverNonce;
UA_ByteString_init(&ar->serverNonce);
extractEphemeralKeyFromAddHeader(client, &ar->responseHeader.additionalHeader);
client->sessionState = UA_SESSIONSTATE_ACTIVATED;
notifyClientState(client);
if(!client->haveNamespaces)
readNamespacesArrayAsync(client);
#ifdef UA_ENABLE_SUBSCRIPTIONS
__Client_Subscriptions_backgroundPublish(client);
#endif
}
static UA_StatusCode
activateSessionAsync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
if(client->sessionState != UA_SESSIONSTATE_CREATED &&
client->sessionState != UA_SESSIONSTATE_ACTIVATED) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Can not activate session, session neither created nor activated. "
"Actual state: '%u'", client->sessionState);
return UA_STATUSCODE_BADSESSIONCLOSED;
}
const UA_UserTokenPolicy *utp = NULL;
UA_StatusCode retval = initUserTokenPolicy(client, &utp, "ActivateSession");
if(retval != UA_STATUSCODE_GOOD)
return retval;
UA_ActivateSessionRequest request;
UA_ActivateSessionRequest_init(&request);
if(client->config.sessionLocaleIdsSize && client->config.sessionLocaleIds) {
retval = UA_Array_copy(client->config.sessionLocaleIds,
client->config.sessionLocaleIdsSize,
(void **)&request.localeIds, &UA_TYPES[UA_TYPES_LOCALEID]);
if(retval != UA_STATUSCODE_GOOD)
return retval;
request.localeIdsSize = client->config.sessionLocaleIdsSize;
}
UA_AnonymousIdentityToken anonToken;
retval = UA_ExtensionObject_copy(&client->config.userIdentityToken,
&request.userIdentityToken);
if(request.userIdentityToken.encoding != UA_EXTENSIONOBJECT_ENCODED_NOBODY) {
UA_String *policyId = (UA_String*)request.userIdentityToken.content.decoded.data;
UA_String_clear(policyId);
retval = UA_String_copy(&utp->policyId, policyId);
if(retval != UA_STATUSCODE_GOOD) {
UA_ActivateSessionRequest_clear(&request);
return retval;
}
} else {
UA_AnonymousIdentityToken_init(&anonToken);
UA_ExtensionObject_setValueNoDelete(&request.userIdentityToken, &anonToken,
&UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]);
anonToken.policyId = utp->policyId;
}
switch(utp->tokenType) {
case UA_USERTOKENTYPE_ANONYMOUS:
break;
case UA_USERTOKENTYPE_USERNAME:
case UA_USERTOKENTYPE_ISSUEDTOKEN:
retval = encryptUserIdentityToken(client, &request.userIdentityToken);
break;
case UA_USERTOKENTYPE_CERTIFICATE:
retval = signUserTokenSignature(client, &request);
break;
default:
retval = UA_STATUSCODE_BADINTERNALERROR;
}
if(retval != UA_STATUSCODE_GOOD) {
UA_ActivateSessionRequest_clear(&request);
return retval;
}
UA_SecureChannel *channel = &client->channel;
if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
retval |= signClientSignature(client, &request);
if(UA_LIKELY(retval == UA_STATUSCODE_GOOD))
retval = __Client_AsyncService(client, &request,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONREQUEST],
(UA_ClientAsyncServiceCallback)responseActivateSession,
&UA_TYPES[UA_TYPES_ACTIVATESESSIONRESPONSE],
NULL, NULL);
if(retval == UA_STATUSCODE_GOOD)
client->sessionState = UA_SESSIONSTATE_ACTIVATE_REQUESTED;
else
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"ActivateSession failed when sending the request with error code %s",
UA_StatusCode_name(retval));
UA_ActivateSessionRequest_clear(&request);
return retval;
}
static const UA_String binaryTransport =
UA_STRING_STATIC("http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary");
static UA_Boolean
matchEndpoint(UA_Client *client, const UA_EndpointDescription *endpoint, unsigned i) {
if(client->config.applicationUri.length > 0 &&
!UA_String_equal(&client->config.applicationUri,
&endpoint->server.applicationUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, the server's ApplicationUri %S "
"does not match the client configuration", i,
endpoint->server.applicationUri);
return false;
}
if(endpoint->transportProfileUri.length != 0 &&
!UA_String_equal(&endpoint->transportProfileUri, &binaryTransport)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, the TransportProfileUri %S "
"is not supported", i, endpoint->transportProfileUri);
return false;
}
if(endpoint->securityMode < 1 || endpoint->securityMode > 3) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, invalid SecurityMode %u",
i, (unsigned)endpoint->securityMode);
return false;
}
UA_MessageSecurityMode configuredSM = client->config.securityMode;
if(configuredSM > UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Bad SecurityMode set in the client config");
return false;
}
if(configuredSM > 0 && configuredSM != endpoint->securityMode) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, the SecurityMode %s "
"does not match the client configuration (%s)",
i, securityModeNames[endpoint->securityMode],
securityModeNames[configuredSM]);
return false;
}
if(client->config.securityPolicyUri.length > 0 &&
!UA_String_equal(&client->config.securityPolicyUri,
&endpoint->securityPolicyUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, the SecurityPolicy %S does not "
"match the configuration", i, endpoint->securityPolicyUri);
return false;
}
if(!getSecurityPolicy(client, endpoint->securityPolicyUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, the SecurityPolicy %S not supported",
i, endpoint->securityPolicyUri);
return false;
}
return true;
}
static UA_Boolean
matchUserToken(UA_Client *client,
const UA_UserTokenPolicy *tokenPolicy) {
const UA_DataType *tokenType =
client->config.userIdentityToken.content.decoded.type;
if(tokenPolicy->tokenType == UA_USERTOKENTYPE_ANONYMOUS &&
(tokenType == &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] || !tokenType))
return true;
if(tokenPolicy->tokenType == UA_USERTOKENTYPE_USERNAME &&
tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN])
return true;
if(tokenPolicy->tokenType == UA_USERTOKENTYPE_CERTIFICATE &&
tokenType == &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN])
return true;
if(tokenPolicy->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN &&
tokenType == &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN])
return true;
return false;
}
static UA_Boolean
matchUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint,
UA_UserTokenPolicy *utp, char *logPrefix) {
if(!matchUserToken(client, utp)) {
if(logPrefix) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S rejected -- does not match "
"the type of UserIdentityToken configured in the client",
logPrefix, utp->policyId);
}
return false;
}
UA_String tokenPolicyUri =
(UA_String_isEmpty(&utp->securityPolicyUri)) ?
endpoint->securityPolicyUri : utp->securityPolicyUri;
if(!UA_String_isEmpty(&client->config.authSecurityPolicyUri) &&
!UA_String_equal(&client->config.authSecurityPolicyUri,
&tokenPolicyUri)) {
if(logPrefix) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S rejected -- uses SecurityPolicy %S, "
"but the client configuration requires SecurityPolicy %S",
logPrefix, utp->policyId,
tokenPolicyUri, client->config.authSecurityPolicyUri);
}
return false;
}
if(utp->tokenType == UA_USERTOKENTYPE_ANONYMOUS)
return true;
UA_SecurityPolicy *utsp;
if(utp->tokenType == UA_USERTOKENTYPE_CERTIFICATE) {
UA_X509IdentityToken *token = (UA_X509IdentityToken*)
client->config.userIdentityToken.content.decoded.data;
utsp = getAuthSecurityPolicy(client, tokenPolicyUri,
&token->certificateData);
} else {
utsp = getAuthSecurityPolicy(client, tokenPolicyUri, NULL);
if(!utsp)
utsp = getSecurityPolicy(client, tokenPolicyUri);
}
if(!utsp) {
if(logPrefix) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S rejected -- the "
"SecurityPolicy %S is not available",
logPrefix, utp->policyId, tokenPolicyUri);
}
return false;
}
if(endpoint->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT &&
utsp->policyType == UA_SECURITYPOLICYTYPE_NONE) {
if(!client->config.allowNonePolicyPassword) {
if(logPrefix) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S rejected -- the "
"AuthenticationToken must not be transmitted "
"without encryption (override with"
"allowNonePolicyPassword setting)",
logPrefix, utp->policyId);
}
return false;
}
}
return true;
}
static UA_UserTokenPolicy *
findUserTokenPolicy(UA_Client *client, UA_EndpointDescription *endpoint,
char *logPrefix) {
UA_UserTokenPolicy *requiredTokenPolicy = NULL;
UA_UserTokenPolicy tmp;
UA_UserTokenPolicy_init(&tmp);
if(!UA_equal(&tmp, &client->config.userTokenPolicy,
&UA_TYPES[UA_TYPES_USERTOKENPOLICY]))
requiredTokenPolicy = &client->config.userTokenPolicy;
for(size_t j = 0; j < endpoint->userIdentityTokensSize; ++j) {
UA_UserTokenPolicy *tokenPolicy = &endpoint->userIdentityTokens[j];
if(requiredTokenPolicy &&
!UA_equal(requiredTokenPolicy, tokenPolicy,
&UA_TYPES[UA_TYPES_USERTOKENPOLICY])) {
if(logPrefix) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"%s: UserTokenPolicy %S rejected -- a different "
"UserTokenPolicy %S is specified in the client config",
logPrefix, tokenPolicy->policyId,
requiredTokenPolicy->policyId);
}
continue;
}
if(matchUserTokenPolicy(client, endpoint, tokenPolicy, logPrefix))
return tokenPolicy;
}
return NULL;
}
static void
responseGetEndpoints(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
UA_LOCK_ASSERT(&client->clientMutex);
client->endpointsHandshake = false;
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Received GetEndpointsResponse");
UA_GetEndpointsResponse *resp = (UA_GetEndpointsResponse*)response;
if(resp->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
if(UA_SecureChannel_isConnected(&client->channel)) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"GetEndpointRequest failed with error code %s",
UA_StatusCode_name(resp->responseHeader.serviceResult));
setConnectStatus(client, resp->responseHeader.serviceResult);
}
UA_GetEndpointsResponse_clear(resp);
return;
}
Client_warnEndpointsResult(client, resp, &client->discoveryUrl);
const size_t notFound = (size_t)-1;
size_t bestEndpointIndex = notFound;
UA_Byte bestEndpointLevel = 0;
UA_UserTokenPolicy *utp = NULL;
for(size_t i = 0; i < resp->endpointsSize; ++i) {
UA_EndpointDescription* endpoint = &resp->endpoints[i];
if(endpoint->securityLevel < bestEndpointLevel) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, better SecurityLevel found before",
(unsigned)i);
continue;
}
if(!matchEndpoint(client, endpoint, (unsigned)i))
continue;
if(!client->config.noSession) {
char logPrefix[32];
mp_snprintf(logPrefix, 32, "Endpoint %u", (unsigned)i);
utp = findUserTokenPolicy(client, endpoint, logPrefix);
if(!utp) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Rejected, no matching UserTokenPolicy",
(unsigned)i);
continue;
}
}
bestEndpointLevel = endpoint->securityLevel;
bestEndpointIndex = i;
if(utp) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Best endpoint so far with "
"UserTokenPolicy %S", (unsigned)i, utp->policyId);
} else {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u: Best endpoint so far", (unsigned)i);
}
}
if(bestEndpointIndex == notFound) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"No suitable endpoint found");
setConnectStatus(client, UA_STATUSCODE_BADIDENTITYTOKENREJECTED);
return;
}
UA_EndpointDescription_clear(&client->endpoint);
client->endpoint = resp->endpoints[bestEndpointIndex];
UA_EndpointDescription_init(&resp->endpoints[bestEndpointIndex]);
if(utp) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u selected with SecurityMode "
"%s, SecurityPolicy %S and UserTokenPolicy %S",
bestEndpointIndex,
securityModeNames[client->endpoint.securityMode],
client->endpoint.securityPolicyUri,
utp->policyId);
} else {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Endpoint %u selected with SecurityMode "
"%s and SecurityPolicy %S", bestEndpointIndex,
securityModeNames[client->endpoint.securityMode],
client->endpoint.securityPolicyUri);
}
UA_SecurityPolicy *sp = client->channel.securityPolicy;
if(client->endpoint.securityMode != client->channel.securityMode ||
!UA_String_equal(&client->endpoint.securityPolicyUri, &sp->policyUri)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"A different SecurityMode or SecurityPolicy is defined "
"by the selected Endpoint. Close the SecureChannel "
"and reconnect.");
closeSecureChannel(client);
return;
}
if(client->discoveryUrl.length > 0 &&
!UA_String_equal(&client->discoveryUrl, &client->endpoint.endpointUrl)) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The selected endpoint defines an EndpointUrl %S different "
"from the Url %S used to connect before calling "
"GetEndpoints. Close the SecureChannel and reconnect with "
"the new EndpointUrl to ensure the Endpoint is available.",
client->discoveryUrl, client->endpoint.endpointUrl);
closeSecureChannel(client);
return;
}
if(client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE) {
void *cc = client->channel.channelContext;
UA_StatusCode res = sp->compareCertificate(sp, cc,
&client->endpoint.serverCertificate);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The selected endpoint defines a different server certificate "
"from then one used by the server for the initial SecureChannel "
"(to call GetEndpoints). Close the SecureChannel and reconnect.");
closeSecureChannel(client);
return;
}
}
}
static UA_StatusCode
requestGetEndpoints(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_GetEndpointsRequest request;
UA_GetEndpointsRequest_init(&request);
request.endpointUrl = getEndpointUrl(client);
UA_StatusCode retval =
__Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_GETENDPOINTSREQUEST],
(UA_ClientAsyncServiceCallback) responseGetEndpoints,
&UA_TYPES[UA_TYPES_GETENDPOINTSRESPONSE], NULL, NULL);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"RequestGetEndpoints failed when sending the request with error code %s",
UA_StatusCode_name(retval));
return retval;
}
client->endpointsHandshake = true;
return UA_STATUSCODE_GOOD;
}
static void
responseFindServers(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
client->findServersHandshake = false;
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Received FindServersResponse");
UA_FindServersResponse *fsr = (UA_FindServersResponse*)response;
if(fsr->responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"FindServers failed with error code %s. Continue with the "
"EndpointURL %S.",
UA_StatusCode_name(fsr->responseHeader.serviceResult),
client->config.endpointUrl);
UA_String_clear(&client->discoveryUrl);
UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl);
return;
}
for(size_t i = 0; i < fsr->serversSize; i++) {
UA_ApplicationDescription *server = &fsr->servers[i];
if(client->config.applicationUri.length > 0 &&
!UA_String_equal(&client->config.applicationUri, &server->applicationUri))
continue;
for(size_t j = 0; j < server->discoveryUrlsSize; j++) {
if(UA_String_equal(&client->config.endpointUrl, &server->discoveryUrls[j])) {
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The initially defined EndpointURL %S "
"is valid for the server", client->config.endpointUrl);
UA_String_clear(&client->discoveryUrl);
client->discoveryUrl = server->discoveryUrls[j];
UA_String_init(&server->discoveryUrls[j]);
return;
}
}
}
for(size_t i = 0; i < fsr->serversSize; i++) {
UA_ApplicationDescription *server = &fsr->servers[i];
if(server->applicationType != UA_APPLICATIONTYPE_SERVER &&
server->applicationType != UA_APPLICATIONTYPE_CLIENTANDSERVER &&
server->applicationType != UA_APPLICATIONTYPE_DISCOVERYSERVER)
continue;
if(client->config.applicationUri.length > 0 &&
!UA_String_equal(&client->config.applicationUri, &server->applicationUri))
continue;
for(size_t j = 0; j < server->discoveryUrlsSize; j++) {
UA_String hostname, path;
UA_UInt16 port;
UA_StatusCode res =
UA_parseEndpointUrl(&server->discoveryUrls[j], &hostname, &port, &path);
if(res != UA_STATUSCODE_GOOD)
continue;
UA_String_clear(&client->discoveryUrl);
client->discoveryUrl = server->discoveryUrls[j];
UA_String_init(&server->discoveryUrls[j]);
UA_LOG_INFO(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Use the EndpointURL %S returned from FindServers and reconnect",
client->discoveryUrl);
closeSecureChannel(client);
return;
}
}
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"FindServers did not returned a suitable DiscoveryURL. "
"Continue with the EndpointURL %S.", client->config.endpointUrl);
UA_String_clear(&client->discoveryUrl);
UA_String_copy(&client->config.endpointUrl, &client->discoveryUrl);
}
static UA_StatusCode
requestFindServers(UA_Client *client) {
UA_FindServersRequest request;
UA_FindServersRequest_init(&request);
request.requestHeader.timeoutHint = 10000;
request.endpointUrl = client->config.endpointUrl;
UA_StatusCode retval =
__Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_FINDSERVERSREQUEST],
(UA_ClientAsyncServiceCallback) responseFindServers,
&UA_TYPES[UA_TYPES_FINDSERVERSRESPONSE], NULL, NULL);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"FindServers failed when sending the request with error code %s",
UA_StatusCode_name(retval));
return retval;
}
client->findServersHandshake = true;
return UA_STATUSCODE_GOOD;
}
static void
createSessionCallback(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_CreateSessionResponse *csr = (UA_CreateSessionResponse*)response;
UA_StatusCode res = csr->responseHeader.serviceResult;
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
if(client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
client->channel.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
if(!UA_ByteString_equal(&csr->serverCertificate,
&client->channel.remoteCertificate)) {
res = UA_STATUSCODE_BADCERTIFICATEINVALID;
goto cleanup;
}
res = checkCreateSessionSignature(client, &client->channel, csr);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
}
UA_NodeId_clear(&client->sessionId);
res |= UA_NodeId_copy(&csr->sessionId, &client->sessionId);
if(csr->serverNonce.length < 32) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Session cannot be created with a nonce "
"that is too short");
res = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
goto cleanup;
}
UA_ByteString_clear(&client->serverSessionNonce);
UA_NodeId_clear(&client->authenticationToken);
res |= UA_ByteString_copy(&csr->serverNonce, &client->serverSessionNonce);
res |= UA_NodeId_copy(&csr->authenticationToken, &client->authenticationToken);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
extractEphemeralKeyFromAddHeader(client, &csr->responseHeader.additionalHeader);
client->sessionState = UA_SESSIONSTATE_CREATED;
cleanup:
setConnectStatus(client, res);
if(client->connectStatus != UA_STATUSCODE_GOOD)
client->sessionState = UA_SESSIONSTATE_CLOSED;
}
static UA_StatusCode
requestServerEphemeralKey(UA_Client *client, UA_RequestHeader *rh) {
UA_SecurityPolicy *utpSp = client->utpSp;
if(!UA_SecurityPolicy_isEcc(utpSp))
return UA_STATUSCODE_GOOD;
UA_AdditionalParametersType *ap = UA_AdditionalParametersType_new();
if(!ap)
return UA_STATUSCODE_BADOUTOFMEMORY;
ap->parameters = (UA_KeyValuePair*)
UA_Array_new(1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]);
if(!ap->parameters) {
UA_AdditionalParametersType_delete(ap);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
ap->parametersSize = 1;
ap->parameters[0].key = UA_QUALIFIEDNAME_ALLOC(0, "ECDHPolicyUri");
UA_StatusCode res =
UA_Variant_setScalarCopy(&ap->parameters[0].value, &utpSp->policyUri,
&UA_TYPES[UA_TYPES_STRING]);
if(res != UA_STATUSCODE_GOOD) {
UA_AdditionalParametersType_delete(ap);
return res;
}
UA_ExtensionObject_setValue(&rh->additionalHeader, ap,
&UA_TYPES[UA_TYPES_ADDITIONALPARAMETERSTYPE]);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
createSessionAsync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
const UA_UserTokenPolicy *utp = NULL;
UA_StatusCode res = initUserTokenPolicy(client, &utp, "CreateSession");
if(res != UA_STATUSCODE_GOOD)
return res;
(void)utp;
UA_SecurityPolicy *sp = client->channel.securityPolicy;
if(sp && client->channel.securityMode != UA_MESSAGESECURITYMODE_NONE) {
size_t nonceLength = 32;
if(client->clientSessionNonce.length != nonceLength) {
UA_ByteString_clear(&client->clientSessionNonce);
res = UA_ByteString_allocBuffer(&client->clientSessionNonce, nonceLength);
if(res != UA_STATUSCODE_GOOD)
return res;
}
client->clientSessionNonce.data[0] = 0;
res = sp->generateNonce(sp, client->channel.channelContext,
&client->clientSessionNonce);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"CreateSession could not create the client nonce");
return res;
}
}
UA_CreateSessionRequest request;
UA_CreateSessionRequest_init(&request);
request.clientNonce = client->clientSessionNonce;
request.requestedSessionTimeout = client->config.requestedSessionTimeout;
request.maxResponseMessageSize = UA_INT32_MAX;
request.endpointUrl = client->endpoint.endpointUrl;
request.clientDescription = client->config.clientDescription;
request.sessionName = client->config.sessionName;
if(sp)
request.clientCertificate = sp->localCertificate;
res = requestServerEphemeralKey(client, &request.requestHeader);
if(res != UA_STATUSCODE_GOOD)
return res;
res = __Client_AsyncService(client, &request,
&UA_TYPES[UA_TYPES_CREATESESSIONREQUEST],
(UA_ClientAsyncServiceCallback)createSessionCallback,
&UA_TYPES[UA_TYPES_CREATESESSIONRESPONSE], NULL, NULL);
UA_ExtensionObject_clear(&request.requestHeader.additionalHeader);
if(res == UA_STATUSCODE_GOOD)
client->sessionState = UA_SESSIONSTATE_CREATE_REQUESTED;
else
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"CreateSession failed when sending the request with "
"error code %s", UA_StatusCode_name(res));
return res;
}
static UA_StatusCode
initSecurityPolicy(UA_Client *client) {
UA_String secPolicyUri = client->endpoint.securityPolicyUri;
if(secPolicyUri.length == 0)
secPolicyUri = UA_SECURITY_POLICY_NONE_URI;
UA_SecurityPolicy *sp = getSecurityPolicy(client, secPolicyUri);
if(!sp) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"SecurityPolicy %S not available", secPolicyUri);
return UA_STATUSCODE_BADINTERNALERROR;
}
if(client->channel.securityPolicy)
return (client->channel.securityPolicy == sp) ?
UA_STATUSCODE_GOOD : UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode res;
if(client->endpoint.serverCertificate.length > 0) {
res = client->config.certificateVerification.
verifyCertificate(&client->config.certificateVerification,
&client->endpoint.serverCertificate);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot validate the server certificate "
"defined for the selected endpoint");
return res;
}
}
UA_ByteString appInstCert =
getLeafCertificate(client->endpoint.serverCertificate);
res = UA_SecureChannel_setSecurityPolicy(&client->channel, sp, &appInstCert);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot instantiate the SecurityPolicy %S "
"with the supplied server certificate", sp->policyUri);
return res;
}
UA_MessageSecurityMode securityMode = client->endpoint.securityMode;
if(securityMode == UA_MESSAGESECURITYMODE_INVALID)
securityMode = UA_MESSAGESECURITYMODE_NONE;
res = UA_SecureChannel_setSecurityMode(&client->channel, securityMode);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Client configuration uses mismatching "
"MessageSecurityMode==%u for SecurityPolicy %S",
securityMode, sp->policyUri);
}
return res;
}
static void
connectActivity(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_LOG_TRACE(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Client connect iterate");
if(client->connectStatus != UA_STATUSCODE_GOOD)
return;
if(client->sessionState == UA_SESSIONSTATE_ACTIVATED)
return;
switch(client->channel.state) {
case UA_SECURECHANNELSTATE_CONNECTING:
case UA_SECURECHANNELSTATE_REVERSE_CONNECTED:
case UA_SECURECHANNELSTATE_CLOSING:
case UA_SECURECHANNELSTATE_HEL_SENT:
case UA_SECURECHANNELSTATE_OPN_SENT:
return;
case UA_SECURECHANNELSTATE_CONNECTED:
setConnectStatus(client, sendHELMessage(client));
return;
case UA_SECURECHANNELSTATE_ACK_RECEIVED:
setConnectStatus(client, sendOPNAsync(client, false));
return;
case UA_SECURECHANNELSTATE_OPEN:
break;
case UA_SECURECHANNELSTATE_CLOSED:
if(client->config.noReconnect)
setConnectStatus(client, UA_STATUSCODE_BADNOTCONNECTED);
else
initConnect(client);
return;
default:
setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR);
return;
}
if(client->endpointsHandshake || client->findServersHandshake ||
client->namespacesHandshake)
return;
if(client->discoveryUrl.length == 0) {
setConnectStatus(client, requestFindServers(client));
return;
}
if(endpointUnconfigured(&client->endpoint)) {
setConnectStatus(client, requestGetEndpoints(client));
return;
}
if(client->config.noSession)
return;
switch(client->sessionState) {
case UA_SESSIONSTATE_CLOSED:
setConnectStatus(client, createSessionAsync(client));
return;
case UA_SESSIONSTATE_CREATED:
setConnectStatus(client, activateSessionAsync(client));
return;
case UA_SESSIONSTATE_CREATE_REQUESTED:
case UA_SESSIONSTATE_ACTIVATE_REQUESTED:
case UA_SESSIONSTATE_ACTIVATED:
case UA_SESSIONSTATE_CLOSING:
return;
default:
setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR);
break;
}
}
static UA_StatusCode
verifyClientSecureChannelHeader(void *application, UA_SecureChannel *channel,
const UA_AsymmetricAlgorithmSecurityHeader *asymHeader) {
UA_Client *client = (UA_Client*)application;
const UA_SecurityPolicy *sp = channel->securityPolicy;
UA_assert(sp != NULL);
if((asymHeader->securityPolicyUri.length > 0 ||
channel->securityMode != UA_MESSAGESECURITYMODE_NONE) &&
!UA_String_equal(&sp->policyUri, &asymHeader->securityPolicyUri)) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The server uses a different SecurityPolicy than "
"the SecureChannel/Endpoint configured in the client");
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
}
UA_StatusCode res;
if(channel->securityMode != UA_MESSAGESECURITYMODE_NONE) {
void *cc = channel->channelContext;
res = sp->compareCertificate(sp, cc, &asymHeader->senderCertificate);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The server certificate in the OPN message is "
"different from the EndpointDescription");
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
}
}
res = sp->compareCertThumbprint(sp, &asymHeader->receiverCertificateThumbprint);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The client certificate thumprint in the OPN response "
"is incorrect");
return res;
}
return UA_STATUSCODE_GOOD;
}
static void
verifyClientApplicationUri(const UA_Client *client) {
#if UA_LOGLEVEL <= 400
const UA_ClientConfig *cc = &client->config;
for(size_t i = 0; i < cc->securityPoliciesSize; i++) {
UA_SecurityPolicy *sp = &cc->securityPolicies[i];
if(sp->policyType == UA_SECURITYPOLICYTYPE_NONE &&
!sp->localCertificate.data)
continue;
UA_StatusCode retval =
UA_CertificateUtils_verifyApplicationUri(&sp->localCertificate,
&cc->clientDescription.applicationUri);
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(cc->logging, UA_LOGCATEGORY_CLIENT,
"The ApplicationUri %S in the client's ApplicationDescription "
"does not match the URI specified in the certificate "
"for the SecurityPolicy %S",
cc->clientDescription.applicationUri, sp->policyUri);
}
}
#endif
}
static void
delayedNetworkCallback(void *application, void *context);
static void
__Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void *application, void **connectionContext,
UA_ConnectionState state, const UA_KeyValueMap *params,
UA_ByteString msg) {
UA_Client *client = (UA_Client*)application;
lockClient(client);
UA_LOG_TRACE(client->config.logging, UA_LOGCATEGORY_CLIENT, "Client network callback");
if(!*connectionContext) {
if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED &&
client->channel.state != UA_SECURECHANNELSTATE_REVERSE_LISTENING) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot open a connection, the SecureChannel is already used");
setConnectStatus(client, UA_STATUSCODE_BADINTERNALERROR);
notifyClientState(client);
unlockClient(client);
return;
}
client->channel.connectionManager = cm;
client->channel.connectionId = connectionId;
*connectionContext = &client->channel;
}
if(state == UA_CONNECTIONSTATE_CLOSING) {
UA_LOG_INFO_CHANNEL(client->config.logging, &client->channel,
"SecureChannel closed");
UA_SecureChannelState oldState = client->channel.state;
client->channel.state = UA_SECURECHANNELSTATE_CLOSING;
if(client->sessionState == UA_SESSIONSTATE_ACTIVATED)
client->sessionState = UA_SESSIONSTATE_CREATED;
__Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSECURECHANNELCLOSED);
UA_SecureChannel_clear(&client->channel);
if(oldState == UA_SECURECHANNELSTATE_CONNECTING &&
client->connectStatus == UA_STATUSCODE_GOOD)
setConnectStatus(client, fallbackEndpointUrl(client));
if(!isFullyConnected(client))
connectActivity(client);
notifyClientState(client);
unlockClient(client);
return;
}
if(UA_LIKELY(state == UA_CONNECTIONSTATE_ESTABLISHED)) {
if(client->channel.state < UA_SECURECHANNELSTATE_CONNECTED)
client->channel.state = UA_SECURECHANNELSTATE_CONNECTED;
} else {
client->channel.state = UA_SECURECHANNELSTATE_CONNECTING;
}
UA_EventLoop *el = client->config.eventLoop;
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
UA_StatusCode res = UA_SecureChannel_loadBuffer(&client->channel, msg);
while(UA_LIKELY(res == UA_STATUSCODE_GOOD)) {
UA_MessageType messageType;
UA_UInt32 requestId = 0;
UA_ByteString payload = UA_BYTESTRING_NULL;
UA_Boolean copied = false;
res = UA_SecureChannel_getCompleteMessage(&client->channel, &messageType, &requestId,
&payload, &copied, nowMonotonic);
if(res != UA_STATUSCODE_GOOD || payload.length == 0)
break;
res = processServiceResponse(client, &client->channel,
messageType, requestId, &payload);
if(copied)
UA_ByteString_clear(&payload);
if(res == UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY) {
if(client->channel.unprocessed.length > client->channel.unprocessedOffset &&
client->channel.unprocessedDelayed.callback == NULL) {
client->channel.unprocessedDelayed.callback = delayedNetworkCallback;
client->channel.unprocessedDelayed.application = client;
client->channel.unprocessedDelayed.context = &client->channel;
el->addDelayedCallback(el, &client->channel.unprocessedDelayed);
}
res = UA_STATUSCODE_GOOD;
break;
}
}
res |= UA_SecureChannel_persistBuffer(&client->channel);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Processing the message returned the error code %s",
UA_StatusCode_name(res));
if(client->channel.state != UA_SECURECHANNELSTATE_OPEN)
setConnectStatus(client, res);
closeSecureChannel(client);
unlockClient(client);
return;
}
if(!isFullyConnected(client))
connectActivity(client);
notifyClientState(client);
unlockClient(client);
}
static void
delayedNetworkCallback(void *application, void *context) {
UA_Client *client = (UA_Client*)application;
client->channel.unprocessedDelayed.callback = NULL;
if(client->channel.state >= UA_SECURECHANNELSTATE_CONNECTING)
__Client_networkCallback(client->channel.connectionManager,
client->channel.connectionId,
client, &context,
UA_CONNECTIONSTATE_ESTABLISHED,
&UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
}
static void
initConnect(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Client connection already initiated");
return;
}
UA_StatusCode res;
if(!endpointUnconfigured(&client->config.endpoint)) {
UA_EndpointDescription_clear(&client->endpoint);
res = UA_EndpointDescription_copy(&client->config.endpoint, &client->endpoint);
if(res != UA_STATUSCODE_GOOD) {
setConnectStatus(client, res);
return;
}
}
setConnectStatus(client, __UA_Client_startup(client));
if(client->connectStatus != UA_STATUSCODE_GOOD)
return;
verifyClientApplicationUri(client);
UA_SecureChannel_clear(&client->channel);
client->channel.config = client->config.localConnectionConfig;
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
client->channel.processOPNHeaderApplication = client;
setConnectStatus(client, initSecurityPolicy(client));
if(client->connectStatus != UA_STATUSCODE_GOOD)
return;
UA_String hostname = UA_STRING_NULL;
UA_String path = UA_STRING_NULL;
UA_UInt16 port = 4840;
res = UA_parseEndpointUrl(&client->config.endpointUrl, &hostname, &port, &path);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_NETWORK,
"Endpoint URL is invalid: %S", client->config.endpointUrl);
setConnectStatus(client, res);
return;
}
UA_String tcpString = UA_STRING("tcp");
for(UA_EventSource *es = client->config.eventLoop->eventSources;
es != NULL; es = es->next) {
if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER)
continue;
UA_ConnectionManager *cm = (UA_ConnectionManager*)es;
if(!UA_String_equal(&tcpString, &cm->protocol))
continue;
UA_KeyValuePair params[3];
params[0].key = UA_QUALIFIEDNAME(0, "port");
UA_Variant_setScalar(¶ms[0].value, &port, &UA_TYPES[UA_TYPES_UINT16]);
params[1].key = UA_QUALIFIEDNAME(0, "address");
UA_Variant_setScalar(¶ms[1].value, &hostname, &UA_TYPES[UA_TYPES_STRING]);
params[2].key = UA_QUALIFIEDNAME(0, "reuse");
UA_Variant_setScalar(¶ms[2].value, &client->config.tcpReuseAddr,
&UA_TYPES[UA_TYPES_BOOLEAN]);
UA_KeyValueMap paramMap;
paramMap.map = params;
paramMap.mapSize = 3;
res = cm->openConnection(cm, ¶mMap, client, NULL, __Client_networkCallback);
if(res == UA_STATUSCODE_GOOD)
break;
}
if(client->channel.state == UA_SECURECHANNELSTATE_CLOSED)
res = UA_STATUSCODE_BADINTERNALERROR;
if(res != UA_STATUSCODE_GOOD || client->connectStatus != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not open a TCP connection to %S",
client->config.endpointUrl);
setConnectStatus(client, UA_STATUSCODE_BADCONNECTIONCLOSED);
}
}
void
connectSync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
initConnect(client);
notifyClientState(client);
if(client->connectStatus != UA_STATUSCODE_GOOD)
return;
UA_EventLoop *el = client->config.eventLoop;
UA_assert(el);
UA_DateTime now = el->dateTime_nowMonotonic(el);
UA_DateTime maxDate = now + ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC);
while((client->connectStatus == UA_STATUSCODE_GOOD && !isFullyConnected(client)) ||
client->channel.state == UA_SECURECHANNELSTATE_CLOSING) {
now = el->dateTime_nowMonotonic(el);
if(maxDate < now) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The connection has timed out before it could be fully opened");
setConnectStatus(client, UA_STATUSCODE_BADTIMEOUT);
}
UA_StatusCode res = el->run(el, (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC));
if(res != UA_STATUSCODE_GOOD)
setConnectStatus(client, res);
}
}
UA_StatusCode
connectInternal(UA_Client *client, UA_Boolean async) {
UA_LOCK_ASSERT(&client->clientMutex);
client->connectStatus = UA_STATUSCODE_GOOD;
if(async)
initConnect(client);
else
connectSync(client);
notifyClientState(client);
return client->connectStatus;
}
UA_StatusCode
connectSecureChannel(UA_Client *client, const char *endpointUrl) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_ClientConfig *cc = UA_Client_getConfig(client);
cc->noSession = true;
UA_String_clear(&cc->endpointUrl);
cc->endpointUrl = UA_STRING_ALLOC(endpointUrl);
return connectInternal(client, false);
}
UA_StatusCode
__UA_Client_connect(UA_Client *client, UA_Boolean async, const char *endpointUrl) {
lockClient(client);
UA_ClientConfig *cc = UA_Client_getConfig(client);
if(endpointUrl) {
UA_String_clear(&cc->endpointUrl);
cc->endpointUrl = UA_STRING_ALLOC(endpointUrl);
}
connectInternal(client, async);
unlockClient(client);
return client->connectStatus;
}
static UA_StatusCode
activateSessionSync(UA_Client *client) {
UA_LOCK_ASSERT(&client->clientMutex);
UA_EventLoop *el = client->config.eventLoop;
UA_assert(el);
UA_DateTime now = el->dateTime_nowMonotonic(el);
UA_DateTime maxDate = now + ((UA_DateTime)client->config.timeout * UA_DATETIME_MSEC);
UA_StatusCode res = activateSessionAsync(client);
if(res != UA_STATUSCODE_GOOD)
return res;
while((client->sessionState != UA_SESSIONSTATE_ACTIVATED &&
client->connectStatus == UA_STATUSCODE_GOOD) ||
client->channel.state == UA_SECURECHANNELSTATE_CLOSING) {
now = el->dateTime_nowMonotonic(el);
if(maxDate < now) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"The connection has timed out before it could be fully opened");
setConnectStatus(client, UA_STATUSCODE_BADTIMEOUT);
}
res = el->run(el, (UA_UInt32)((maxDate - now) / UA_DATETIME_MSEC));
if(res != UA_STATUSCODE_GOOD)
setConnectStatus(client, res);
}
return client->connectStatus;
}
UA_StatusCode
UA_Client_activateCurrentSession(UA_Client *client) {
lockClient(client);
UA_StatusCode res = activateSessionSync(client);
notifyClientState(client);
unlockClient(client);
return res != UA_STATUSCODE_GOOD ? res : client->connectStatus;
}
UA_StatusCode
UA_Client_activateCurrentSessionAsync(UA_Client *client) {
lockClient(client);
UA_StatusCode res = activateSessionAsync(client);
notifyClientState(client);
unlockClient(client);
return res != UA_STATUSCODE_GOOD ? res : client->connectStatus;
}
UA_StatusCode
UA_Client_getSessionAuthenticationToken(UA_Client *client,
UA_NodeId *authenticationToken,
UA_ByteString *serverNonce) {
lockClient(client);
if(client->sessionState != UA_SESSIONSTATE_CREATED &&
client->sessionState != UA_SESSIONSTATE_ACTIVATED) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"There is no current session");
unlockClient(client);
return UA_STATUSCODE_BADSESSIONCLOSED;
}
UA_StatusCode res =
UA_NodeId_copy(&client->authenticationToken, authenticationToken);
res |= UA_ByteString_copy(&client->serverSessionNonce, serverNonce);
unlockClient(client);
return res;
}
static UA_StatusCode
switchSession(UA_Client *client,
const UA_NodeId authenticationToken,
const UA_ByteString serverNonce) {
if(client->sessionState != UA_SESSIONSTATE_CLOSED) {
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Cannot activate a session with a different AuthenticationToken "
"when the client already has a Session.");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_NodeId_clear(&client->authenticationToken);
UA_ByteString_clear(&client->serverSessionNonce);
UA_StatusCode res = UA_NodeId_copy(&authenticationToken,
&client->authenticationToken);
res |= UA_ByteString_copy(&serverNonce, &client->serverSessionNonce);
if(res != UA_STATUSCODE_GOOD)
return res;
client->sessionState = UA_SESSIONSTATE_CREATED;
notifyClientState(client);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Client_activateSession(UA_Client *client,
const UA_NodeId authenticationToken,
const UA_ByteString serverNonce) {
lockClient(client);
UA_StatusCode res = switchSession(client, authenticationToken, serverNonce);
if(res != UA_STATUSCODE_GOOD) {
unlockClient(client);
return res;
}
res = activateSessionSync(client);
notifyClientState(client);
unlockClient(client);
return res != UA_STATUSCODE_GOOD ? res : client->connectStatus;
}
UA_StatusCode
UA_Client_activateSessionAsync(UA_Client *client,
const UA_NodeId authenticationToken,
const UA_ByteString serverNonce) {
lockClient(client);
UA_StatusCode res = switchSession(client, authenticationToken, serverNonce);
if(res != UA_STATUSCODE_GOOD) {
unlockClient(client);
return res;
}
res = activateSessionAsync(client);
notifyClientState(client);
unlockClient(client);
return res != UA_STATUSCODE_GOOD ? res : client->connectStatus;
}
static void
disconnectListenSockets(UA_Client *client) {
UA_ConnectionManager *cm = client->reverseConnectionCM;
for(size_t i = 0; i < 16; i++) {
if(client->reverseConnectionIds[i] != 0)
cm->closeConnection(cm, client->reverseConnectionIds[i]);
}
}
static void
__Client_reverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
void *application, void **connectionContext,
UA_ConnectionState state, const UA_KeyValueMap *params,
UA_ByteString msg) {
UA_Client *client = (UA_Client*)application;
lockClient(client);
if(!*connectionContext) {
size_t i = 0;
for(; i < 16; i++) {
if(client->reverseConnectionIds[i] == 0) {
client->reverseConnectionIds[i] = connectionId;
client->reverseConnectionCM = cm;
*connectionContext = &client->reverseConnectionIds[i];
if(client->channel.state == UA_SECURECHANNELSTATE_CLOSED)
client->channel.state = UA_SECURECHANNELSTATE_REVERSE_LISTENING;
break;
}
}
if(i == 16) {
cm->closeConnection(cm, connectionId);
unlockClient(client);
return;
}
} else if(*connectionContext == &client->channel ||
*(uintptr_t*)*connectionContext != connectionId) {
if(*connectionContext != &client->channel) {
if(client->channel.connectionId) {
cm->closeConnection(cm, connectionId);
unlockClient(client);
return;
}
client->channel.connectionId = connectionId;
client->channel.connectionManager = cm;
*connectionContext = &client->channel;
disconnectListenSockets(client);
if(client->channel.state == UA_SECURECHANNELSTATE_REVERSE_LISTENING)
client->channel.state = UA_SECURECHANNELSTATE_REVERSE_CONNECTED;
}
unlockClient(client);
__Client_networkCallback(cm, connectionId, application,
connectionContext, state, params, msg);
return;
}
if(state == UA_CONNECTIONSTATE_CLOSING) {
UA_Byte count = 0;
for(size_t i = 0; i < 16; i++) {
if(client->reverseConnectionIds[i] == connectionId)
client->reverseConnectionIds[i] = 0;
if(client->reverseConnectionIds[i] != 0)
count++;
}
if(count == 0 && client->channel.connectionId == 0)
client->channel.state = UA_SECURECHANNELSTATE_CLOSED;
}
notifyClientState(client);
unlockClient(client);
}
UA_StatusCode
UA_Client_startListeningForReverseConnect(UA_Client *client,
const UA_String *listenHostnames,
size_t listenHostnamesLength,
UA_UInt16 port) {
lockClient(client);
if(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Unable to listen for reverse connect while the client "
"is connected or already listening");
unlockClient(client);
return UA_STATUSCODE_BADINVALIDSTATE;
}
const UA_String tcpString = UA_STRING_STATIC("tcp");
UA_StatusCode res = UA_STATUSCODE_BADINTERNALERROR;
client->connectStatus = UA_STATUSCODE_GOOD;
client->channel.renewState = UA_SECURECHANNELRENEWSTATE_NORMAL;
UA_SecureChannel_init(&client->channel);
client->channel.config = client->config.localConnectionConfig;
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
client->channel.processOPNHeaderApplication = client;
client->channel.connectionId = 0;
setConnectStatus(client, initSecurityPolicy(client));
if(client->connectStatus != UA_STATUSCODE_GOOD)
return client->connectStatus;
UA_EventLoop *el = client->config.eventLoop;
if(!el) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"No EventLoop configured");
unlockClient(client);
return UA_STATUSCODE_BADINTERNALERROR;
}
if(el->state != UA_EVENTLOOPSTATE_STARTED) {
res = el->start(el);
UA_CHECK_STATUS(res, unlockClient(client); return res);
}
UA_ConnectionManager *cm = NULL;
for(UA_EventSource *es = el->eventSources; es != NULL; es = es->next) {
if(es->eventSourceType != UA_EVENTSOURCETYPE_CONNECTIONMANAGER)
continue;
cm = (UA_ConnectionManager*)es;
if(UA_String_equal(&tcpString, &cm->protocol))
break;
cm = NULL;
}
if(!cm) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Could not find a TCP connection manager, unable to "
"listen for reverse connect");
unlockClient(client);
return UA_STATUSCODE_BADINTERNALERROR;
}
client->channel.connectionManager = cm;
UA_KeyValuePair params[4];
bool booleanTrue = true;
params[0].key = UA_QUALIFIEDNAME(0, "port");
UA_Variant_setScalar(¶ms[0].value, &port,
&UA_TYPES[UA_TYPES_UINT16]);
params[1].key = UA_QUALIFIEDNAME(0, "address");
UA_Variant_setArray(¶ms[1].value, (void *)(uintptr_t)listenHostnames,
listenHostnamesLength, &UA_TYPES[UA_TYPES_STRING]);
params[2].key = UA_QUALIFIEDNAME(0, "listen");
UA_Variant_setScalar(¶ms[2].value, &booleanTrue,
&UA_TYPES[UA_TYPES_BOOLEAN]);
params[3].key = UA_QUALIFIEDNAME(0, "reuse");
UA_Variant_setScalar(¶ms[3].value, &client->config.tcpReuseAddr,
&UA_TYPES[UA_TYPES_BOOLEAN]);
UA_KeyValueMap paramMap;
paramMap.map = params;
paramMap.mapSize = 4;
res = cm->openConnection(cm, ¶mMap, client, NULL,
__Client_reverseConnectCallback);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(client->config.logging, UA_LOGCATEGORY_CLIENT,
"Failed to open a listening TCP socket for "
"reverse connect");
res = UA_STATUSCODE_BADCONNECTIONCLOSED;
}
unlockClient(client);
return res;
}
void
closeSecureChannel(UA_Client *client) {
if(client->sessionState == UA_SESSIONSTATE_ACTIVATED)
client->sessionState = UA_SESSIONSTATE_CREATED;
if(client->channel.state == UA_SECURECHANNELSTATE_CLOSING ||
client->channel.state == UA_SECURECHANNELSTATE_CLOSED)
return;
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
"Closing the channel");
disconnectListenSockets(client);
if(client->channel.state == UA_SECURECHANNELSTATE_OPEN) {
UA_LOG_DEBUG_CHANNEL(client->config.logging, &client->channel,
"Sending the CLO message");
UA_EventLoop *el = client->config.eventLoop;
UA_CloseSecureChannelRequest request;
UA_CloseSecureChannelRequest_init(&request);
request.requestHeader.requestHandle = ++client->requestHandle;
request.requestHeader.timestamp = el->dateTime_now(el);
request.requestHeader.timeoutHint = client->config.timeout;
request.requestHeader.authenticationToken = client->authenticationToken;
UA_SecureChannel_sendCLO(&client->channel, ++client->requestId, &request);
}
UA_SecureChannel_shutdown(&client->channel, UA_SHUTDOWNREASON_CLOSE);
}
static void
sendCloseSession(UA_Client *client) {
UA_CloseSessionRequest request;
UA_CloseSessionRequest_init(&request);
request.deleteSubscriptions = true;
UA_CloseSessionResponse response;
__Client_Service(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST],
&response, &UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE]);
UA_CloseSessionRequest_clear(&request);
UA_CloseSessionResponse_clear(&response);
client->sessionState = UA_SESSIONSTATE_CLOSING;
}
void
cleanupSession(UA_Client *client) {
UA_NodeId_clear(&client->sessionId);
UA_NodeId_clear(&client->authenticationToken);
client->requestHandle = 0;
#ifdef UA_ENABLE_SUBSCRIPTIONS
__Client_Subscriptions_clear(client);
#endif
__Client_AsyncService_removeAll(client, UA_STATUSCODE_BADSESSIONCLOSED);
#ifdef UA_ENABLE_SUBSCRIPTIONS
client->currentlyOutStandingPublishRequests = 0;
#endif
client->sessionState = UA_SESSIONSTATE_CLOSED;
UA_ByteString_clear(&client->serverEphemeralPubKey);
if(client->utpSp && client->utpSpContext) {
client->utpSp->deleteChannelContext(client->utpSp, client->utpSpContext);
client->utpSp = NULL;
client->utpSpContext = NULL;
}
}
static void
disconnectSecureChannel(UA_Client *client, UA_Boolean sync) {
UA_String_clear(&client->discoveryUrl);
UA_EndpointDescription_clear(&client->endpoint);
closeSecureChannel(client);
if(client->connectStatus == UA_STATUSCODE_GOOD)
client->connectStatus = UA_STATUSCODE_BADCONNECTIONCLOSED;
UA_EventLoop *el = client->config.eventLoop;
if(sync && el &&
el->state != UA_EVENTLOOPSTATE_FRESH &&
el->state != UA_EVENTLOOPSTATE_STOPPED) {
while(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) {
UA_StatusCode runStatus = el->run(el, 100);
if(runStatus != UA_STATUSCODE_GOOD) {
UA_LOG_DEBUG(client->config.logging, UA_LOGCATEGORY_CLIENT,
"EventLoop run returned %s during synchronous disconnect, "
"stopping wait loop", UA_StatusCode_name(runStatus));
break;
}
}
}
notifyClientState(client);
}
UA_StatusCode
UA_Client_disconnectSecureChannel(UA_Client *client) {
lockClient(client);
disconnectSecureChannel(client, true);
unlockClient(client);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Client_disconnectSecureChannelAsync(UA_Client *client) {
lockClient(client);
disconnectSecureChannel(client, false);
unlockClient(client);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Client_disconnect(UA_Client *client) {
lockClient(client);
if(client->sessionState == UA_SESSIONSTATE_ACTIVATED)
sendCloseSession(client);
cleanupSession(client);
disconnectSecureChannel(client, true);
unlockClient(client);
return UA_STATUSCODE_GOOD;
}
static void
closeSessionCallback(UA_Client *client, void *userdata,
UA_UInt32 requestId, void *response) {
lockClient(client);
cleanupSession(client);
disconnectSecureChannel(client, false);
notifyClientState(client);
unlockClient(client);
}
UA_StatusCode
UA_Client_disconnectAsync(UA_Client *client) {
lockClient(client);
if(client->sessionState == UA_SESSIONSTATE_CLOSED ||
client->sessionState == UA_SESSIONSTATE_CLOSING) {
disconnectSecureChannel(client, false);
notifyClientState(client);
unlockClient(client);
return UA_STATUSCODE_GOOD;
}
client->sessionState = UA_SESSIONSTATE_CLOSING;
UA_CloseSessionRequest request;
UA_CloseSessionRequest_init(&request);
request.deleteSubscriptions = true;
UA_StatusCode res =
__Client_AsyncService(client, &request, &UA_TYPES[UA_TYPES_CLOSESESSIONREQUEST],
(UA_ClientAsyncServiceCallback)closeSessionCallback,
&UA_TYPES[UA_TYPES_CLOSESESSIONRESPONSE], NULL, NULL);
if(res != UA_STATUSCODE_GOOD) {
cleanupSession(client);
disconnectSecureChannel(client, false);
}
notifyClientState(client);
unlockClient(client);
return res;
}