| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| * Copyright 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2014-2017 (c) Florian Palm |
| * Copyright 2014-2016 (c) Sten GrĂ¼ner |
| * Copyright 2015 (c) Chris Iatrou |
| * Copyright 2015 (c) Oleksiy Vasylyev |
| * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
| * Copyright 2017-2018 (c) Mark Giraud, Fraunhofer IOSB |
| * Copyright 2019 (c) Kalycito Infotech Private Limited |
| */ |
| |
| #include "ua_services.h" |
| #include "ua_server_internal.h" |
| #include "ua_session_manager.h" |
| |
| static UA_StatusCode |
| signCreateSessionResponse(UA_Server *server, UA_SecureChannel *channel, |
| const UA_CreateSessionRequest *request, |
| UA_CreateSessionResponse *response) { |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return UA_STATUSCODE_GOOD; |
| |
| const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy; |
| UA_SignatureData *signatureData = &response->serverSignature; |
| |
| /* Prepare the signature */ |
| size_t signatureSize = securityPolicy->certificateSigningAlgorithm. |
| getLocalSignatureSize(securityPolicy, channel->channelContext); |
| UA_StatusCode retval = UA_String_copy(&securityPolicy->certificateSigningAlgorithm.uri, |
| &signatureData->algorithm); |
| retval |= UA_ByteString_allocBuffer(&signatureData->signature, signatureSize); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| /* Allocate a temp buffer */ |
| size_t dataToSignSize = request->clientCertificate.length + request->clientNonce.length; |
| UA_ByteString dataToSign; |
| retval = UA_ByteString_allocBuffer(&dataToSign, dataToSignSize); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; /* signatureData->signature is cleaned up with the response */ |
| |
| /* Sign the signature */ |
| memcpy(dataToSign.data, request->clientCertificate.data, request->clientCertificate.length); |
| memcpy(dataToSign.data + request->clientCertificate.length, |
| request->clientNonce.data, request->clientNonce.length); |
| retval = securityPolicy->certificateSigningAlgorithm. |
| sign(securityPolicy, channel->channelContext, &dataToSign, &signatureData->signature); |
| |
| /* Clean up */ |
| UA_ByteString_deleteMembers(&dataToSign); |
| return retval; |
| } |
| |
| void |
| Service_CreateSession(UA_Server *server, UA_SecureChannel *channel, |
| const UA_CreateSessionRequest *request, |
| UA_CreateSessionResponse *response) { |
| if(!channel) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
| return; |
| } |
| |
| if(!channel->connection) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
| return; |
| } |
| |
| UA_LOG_DEBUG_CHANNEL(&server->config.logger, channel, "Trying to create session"); |
| |
| if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN || |
| channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) { |
| /* Compare the clientCertificate with the remoteCertificate of the channel. |
| * Both the clientCertificate of this request and the remoteCertificate |
| * of the channel may contain a partial or a complete certificate chain. |
| * The compareCertificate function of the channelModule will compare the |
| * first certificate of each chain. The end certificate shall be located |
| * first in the chain according to the OPC UA specification Part 6 (1.04), |
| * chapter 6.2.3.*/ |
| UA_StatusCode retval = channel->securityPolicy->channelModule. |
| compareCertificate(channel->channelContext, &request->clientCertificate); |
| if(retval != UA_STATUSCODE_GOOD) { |
| UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, |
| "The client certificate did not validate"); |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADCERTIFICATEINVALID; |
| return; |
| } |
| } |
| |
| if(channel->securityToken.channelId == 0) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADSECURECHANNELIDINVALID; |
| return; |
| } |
| |
| if(!UA_ByteString_equal(&channel->securityPolicy->policyUri, |
| &UA_SECURITY_POLICY_NONE_URI) && |
| request->clientNonce.length < 32) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADNONCEINVALID; |
| return; |
| } |
| |
| /* TODO: Compare application URI with certificate uri (decode certificate) */ |
| UA_CertificateVerification *cv = channel->securityPolicy->certificateVerification; |
| if(cv && cv->verifyApplicationURI) { |
| response->responseHeader.serviceResult = |
| cv->verifyApplicationURI(cv->context, &request->clientCertificate, |
| &request->clientDescription.applicationUri); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, |
| "The client's ApplicationURI did not match the certificate"); |
| return; |
| } |
| } |
| |
| UA_Session *newSession = NULL; |
| response->responseHeader.serviceResult = |
| UA_SessionManager_createSession(&server->sessionManager, channel, request, &newSession); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_WARNING_CHANNEL(&server->config.logger, channel, |
| "Processing CreateSessionRequest failed"); |
| return; |
| } |
| |
| UA_assert(newSession != NULL); |
| |
| /* Allocate the response */ |
| response->serverEndpoints = (UA_EndpointDescription *) |
| UA_Array_new(server->config.endpointsSize, |
| &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]); |
| if(!response->serverEndpoints) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY; |
| UA_SessionManager_removeSession(&server->sessionManager, |
| &newSession->header.authenticationToken); |
| return; |
| } |
| response->serverEndpointsSize = server->config.endpointsSize; |
| |
| /* Copy the server's endpointdescriptions into the response */ |
| for(size_t i = 0; i < server->config.endpointsSize; ++i) |
| response->responseHeader.serviceResult |= |
| UA_EndpointDescription_copy(&server->config.endpoints[i], |
| &response->serverEndpoints[i]); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_SessionManager_removeSession(&server->sessionManager, |
| &newSession->header.authenticationToken); |
| return; |
| } |
| |
| /* Mirror back the endpointUrl */ |
| for(size_t i = 0; i < response->serverEndpointsSize; ++i) { |
| UA_String_deleteMembers(&response->serverEndpoints[i].endpointUrl); |
| response->responseHeader.serviceResult |= |
| UA_String_copy(&request->endpointUrl, |
| &response->serverEndpoints[i].endpointUrl); |
| } |
| |
| /* Attach the session to the channel. But don't activate for now. */ |
| UA_Session_attachToSecureChannel(newSession, channel); |
| |
| /* Fill the session information */ |
| newSession->maxResponseMessageSize = request->maxResponseMessageSize; |
| newSession->maxRequestMessageSize = |
| channel->connection->config.maxMessageSize; |
| response->responseHeader.serviceResult |= |
| UA_ApplicationDescription_copy(&request->clientDescription, |
| &newSession->clientDescription); |
| |
| /* Prepare the response */ |
| response->sessionId = newSession->sessionId; |
| response->revisedSessionTimeout = (UA_Double)newSession->timeout; |
| response->authenticationToken = newSession->header.authenticationToken; |
| response->responseHeader.serviceResult |= |
| UA_String_copy(&request->sessionName, &newSession->sessionName); |
| |
| UA_ByteString_init(&response->serverCertificate); |
| |
| if(server->config.endpointsSize > 0) |
| for(size_t i = 0; i < response->serverEndpointsSize; ++i) { |
| if(response->serverEndpoints[i].securityMode==channel->securityMode && |
| UA_ByteString_equal(&response->serverEndpoints[i].securityPolicyUri, |
| &channel->securityPolicy->policyUri) && |
| UA_String_equal(&response->serverEndpoints[i].endpointUrl, |
| &request->endpointUrl)) |
| { |
| response->responseHeader.serviceResult |= |
| UA_ByteString_copy(&response->serverEndpoints[i].serverCertificate, |
| &response->serverCertificate); |
| } |
| } |
| |
| /* Create a session nonce */ |
| response->responseHeader.serviceResult |= UA_Session_generateNonce(newSession); |
| response->responseHeader.serviceResult |= |
| UA_ByteString_copy(&newSession->serverNonce, &response->serverNonce); |
| |
| /* Sign the signature */ |
| response->responseHeader.serviceResult |= |
| signCreateSessionResponse(server, channel, request, response); |
| |
| /* Failure -> remove the session */ |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_SessionManager_removeSession(&server->sessionManager, |
| &newSession->header.authenticationToken); |
| return; |
| } |
| |
| UA_LOG_INFO_CHANNEL(&server->config.logger, channel, |
| "Session " UA_PRINTF_GUID_FORMAT " created", |
| UA_PRINTF_GUID_DATA(newSession->sessionId.identifier.guid)); |
| } |
| |
| static UA_StatusCode |
| checkSignature(const UA_Server *server, const UA_SecureChannel *channel, |
| UA_Session *session, const UA_ActivateSessionRequest *request) { |
| if(channel->securityMode != UA_MESSAGESECURITYMODE_SIGN && |
| channel->securityMode != UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) |
| return UA_STATUSCODE_GOOD; |
| |
| /* Check for zero signature length in client signature */ |
| if(request->clientSignature.signature.length == 0) { |
| return UA_STATUSCODE_BADAPPLICATIONSIGNATUREINVALID; |
| } |
| |
| if(!channel->securityPolicy) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| const UA_SecurityPolicy *securityPolicy = channel->securityPolicy; |
| const UA_ByteString *localCertificate = &securityPolicy->localCertificate; |
| |
| size_t dataToVerifySize = localCertificate->length + session->serverNonce.length; |
| |
| UA_ByteString dataToVerify; |
| UA_StatusCode retval = UA_ByteString_allocBuffer(&dataToVerify, dataToVerifySize); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| memcpy(dataToVerify.data, localCertificate->data, localCertificate->length); |
| memcpy(dataToVerify.data + localCertificate->length, |
| session->serverNonce.data, session->serverNonce.length); |
| |
| retval = securityPolicy->certificateSigningAlgorithm.verify(securityPolicy, channel->channelContext, &dataToVerify, |
| &request->clientSignature.signature); |
| UA_ByteString_deleteMembers(&dataToVerify); |
| return retval; |
| } |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| static UA_StatusCode |
| decryptPassword(UA_SecurityPolicy *securityPolicy, void *tempChannelContext, |
| const UA_ByteString *serverNonce, UA_UserNameIdentityToken *userToken) { |
| UA_SecurityPolicyEncryptionAlgorithm *asymEnc = |
| &securityPolicy->asymmetricModule.cryptoModule.encryptionAlgorithm; |
| if(!UA_String_equal(&userToken->encryptionAlgorithm, &asymEnc->uri)) |
| return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| |
| UA_UInt32 tokenSecretLength; |
| UA_ByteString decryptedTokenSecret, tokenServerNonce; |
| if(UA_ByteString_copy(&userToken->password, &decryptedTokenSecret) != UA_STATUSCODE_GOOD) |
| return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| |
| UA_StatusCode retval = UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| if(asymEnc->decrypt(securityPolicy, tempChannelContext, |
| &decryptedTokenSecret) != UA_STATUSCODE_GOOD) |
| goto cleanup; |
| |
| memcpy(&tokenSecretLength, decryptedTokenSecret.data, sizeof(UA_UInt32)); |
| |
| /* The decrypted data must be large enough to include the Encrypted Token |
| * Secret Format and the length field must indicate enough data to include |
| * the server nonce. */ |
| if(decryptedTokenSecret.length < sizeof(UA_UInt32) + serverNonce->length || |
| decryptedTokenSecret.length < sizeof(UA_UInt32) + tokenSecretLength || |
| tokenSecretLength < serverNonce->length) |
| goto cleanup; |
| |
| /* If the Encrypted Token Secret contains padding, the padding must be |
| * zeroes according to the 1.04.1 specification errata, chapter 3. */ |
| for(size_t i = sizeof(UA_UInt32) + tokenSecretLength; i < decryptedTokenSecret.length; i++) { |
| if(decryptedTokenSecret.data[i] != 0) |
| goto cleanup; |
| } |
| |
| /* The server nonce must match according to the 1.04.1 specification errata, |
| * chapter 3. */ |
| tokenServerNonce.length = serverNonce->length; |
| tokenServerNonce.data = &decryptedTokenSecret.data[sizeof(UA_UInt32) + tokenSecretLength - serverNonce->length]; |
| if(!UA_ByteString_equal(serverNonce, &tokenServerNonce)) |
| goto cleanup; |
| |
| /* The password was decrypted successfully. Replace usertoken with the |
| * decrypted password. The encryptionAlgorithm and policyId fields are left |
| * in the UserToken as an indication for the AccessControl plugin that |
| * evaluates the decrypted content. */ |
| memcpy(userToken->password.data, &decryptedTokenSecret.data[sizeof(UA_UInt32)], |
| tokenSecretLength - serverNonce->length); |
| userToken->password.length = tokenSecretLength - serverNonce->length; |
| retval = UA_STATUSCODE_GOOD; |
| |
| cleanup: |
| UA_ByteString_deleteMembers(&decryptedTokenSecret); |
| return retval; |
| } |
| #endif |
| |
| /* TODO: Check all of the following: |
| * |
| * Part 4, §5.6.3: When the ActivateSession Service is called for the first time |
| * then the Server shall reject the request if the SecureChannel is not same as |
| * the one associated with the CreateSession request. Subsequent calls to |
| * ActivateSession may be associated with different SecureChannels. If this is |
| * the case then the Server shall verify that the Certificate the Client used to |
| * create the new SecureChannel is the same as the Certificate used to create |
| * the original SecureChannel. In addition, the Server shall verify that the |
| * Client supplied a UserIdentityToken that is identical to the token currently |
| * associated with the Session. Once the Server accepts the new SecureChannel it |
| * shall reject requests sent via the old SecureChannel. */ |
| |
| void |
| Service_ActivateSession(UA_Server *server, UA_SecureChannel *channel, |
| UA_Session *session, const UA_ActivateSessionRequest *request, |
| UA_ActivateSessionResponse *response) { |
| UA_LOG_DEBUG_SESSION(&server->config.logger, session, "Execute ActivateSession"); |
| |
| if(session->validTill < UA_DateTime_nowMonotonic()) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "ActivateSession: SecureChannel %i wants " |
| "to activate, but the session has timed out", |
| channel->securityToken.channelId); |
| response->responseHeader.serviceResult = |
| UA_STATUSCODE_BADSESSIONIDINVALID; |
| return; |
| } |
| |
| /* Check if the signature corresponds to the ServerNonce that was last sent |
| * to the client */ |
| response->responseHeader.serviceResult = checkSignature(server, channel, session, request); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "Signature check failed with status code %s", |
| UA_StatusCode_name(response->responseHeader.serviceResult)); |
| return; |
| } |
| |
| /* Find the matching endpoint */ |
| const UA_EndpointDescription *ed = NULL; |
| for(size_t i = 0; ed == NULL && i < server->config.endpointsSize; ++i) { |
| const UA_EndpointDescription *e = &server->config.endpoints[i]; |
| |
| /* Match the Security Mode */ |
| if(e->securityMode != channel->securityMode) |
| continue; |
| |
| /* Match the SecurityPolicy */ |
| if(!UA_String_equal(&e->securityPolicyUri, &channel->securityPolicy->policyUri)) |
| continue; |
| |
| /* Match the UserTokenType */ |
| for(size_t j = 0; j < e->userIdentityTokensSize; j++) { |
| const UA_UserTokenPolicy *u = &e->userIdentityTokens[j]; |
| if(u->tokenType == UA_USERTOKENTYPE_ANONYMOUS) { |
| /* Part 4, Section 5.6.3.2, Table 17: A NULL or empty |
| * UserIdentityToken should be treated as Anonymous */ |
| if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN] && |
| request->userIdentityToken.encoding != UA_EXTENSIONOBJECT_ENCODED_NOBODY) |
| continue; |
| } else if(u->tokenType == UA_USERTOKENTYPE_USERNAME) { |
| if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) |
| continue; |
| } else if(u->tokenType == UA_USERTOKENTYPE_CERTIFICATE) { |
| if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) |
| continue; |
| } else if(u->tokenType == UA_USERTOKENTYPE_ISSUEDTOKEN) { |
| if(request->userIdentityToken.content.decoded.type != &UA_TYPES[UA_TYPES_ISSUEDIDENTITYTOKEN]) |
| continue; |
| } else { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| return; |
| } |
| |
| /* Match found */ |
| ed = e; |
| break; |
| } |
| |
| } |
| |
| /* No matching endpoint found */ |
| if(!ed) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| return; |
| } |
| |
| #ifdef UA_ENABLE_ENCRYPTION |
| /* If it is a UserNameIdentityToken, decrypt the password if encrypted */ |
| if((request->userIdentityToken.encoding == UA_EXTENSIONOBJECT_DECODED) && |
| (request->userIdentityToken.content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN])) { |
| UA_UserNameIdentityToken *userToken = (UA_UserNameIdentityToken *) |
| request->userIdentityToken.content.decoded.data; |
| |
| /* Find the UserTokenPolicy */ |
| UA_Byte tokenIndex = 0; |
| for(; tokenIndex < ed->userIdentityTokensSize; tokenIndex++) { |
| if(ed->userIdentityTokens[tokenIndex].tokenType != UA_USERTOKENTYPE_USERNAME) |
| continue; |
| if(UA_String_equal(&userToken->policyId, &ed->userIdentityTokens[tokenIndex].policyId)) |
| break; |
| } |
| if(tokenIndex == ed->userIdentityTokensSize) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| return; |
| } |
| |
| /* Get the SecurityPolicy. If the userTokenPolicy doesn't specify a |
| * security policy the security policy of the secure channel is used. */ |
| UA_SecurityPolicy* securityPolicy; |
| if(ed->userIdentityTokens[tokenIndex].securityPolicyUri.data == NULL) |
| securityPolicy = UA_SecurityPolicy_getSecurityPolicyByUri(server, &ed->securityPolicyUri); |
| else |
| securityPolicy = UA_SecurityPolicy_getSecurityPolicyByUri(server, &ed->userIdentityTokens[tokenIndex].securityPolicyUri); |
| if(!securityPolicy) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADINTERNALERROR; |
| return; |
| } |
| |
| /* Encrypted password? */ |
| if(!UA_String_equal(&securityPolicy->policyUri, &UA_SECURITY_POLICY_NONE_URI)) { |
| /* Test if the encryption algorithm is correctly specified */ |
| if(!UA_String_equal(&userToken->encryptionAlgorithm, |
| &securityPolicy->asymmetricModule.cryptoModule. |
| encryptionAlgorithm.uri)) { |
| response->responseHeader.serviceResult = UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
| return; |
| } |
| |
| /* Create a temporary channel context if a different SecurityPolicy is |
| * used for the password from the SecureChannel */ |
| void *tempChannelContext = channel->channelContext; |
| if(securityPolicy != channel->securityPolicy) { |
| /* TODO: This is a hack. We use our own certificate to create a |
| * channel context. Because the client does not provide one in a |
| * #None SecureChannel. We should not need a ChannelContext at all |
| * for asymmetric decryption where the remote certificate is not |
| * used. */ |
| response->responseHeader.serviceResult = |
| securityPolicy->channelModule.newContext(securityPolicy, |
| &securityPolicy->localCertificate, |
| &tempChannelContext); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_WARNING_SESSION(&server->config.logger, session, "ActivateSession: " |
| "Failed to create a context for the SecurityPolicy %.*s", |
| (int)securityPolicy->policyUri.length, |
| securityPolicy->policyUri.data); |
| return; |
| } |
| } |
| |
| /* Decrypt */ |
| response->responseHeader.serviceResult = |
| decryptPassword(securityPolicy, tempChannelContext, &session->serverNonce, userToken); |
| |
| /* Remove the temporary channel context */ |
| if(securityPolicy != channel->securityPolicy) |
| securityPolicy->channelModule.deleteContext(tempChannelContext); |
| } |
| |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, "ActivateSession: " |
| "Failed to decrypt the password with the status code %s", |
| UA_StatusCode_name(response->responseHeader.serviceResult)); |
| } |
| |
| } |
| #endif |
| |
| /* Callback into userland access control */ |
| response->responseHeader.serviceResult = |
| server->config.accessControl.activateSession(server, &server->config.accessControl, |
| ed, &channel->remoteCertificate, |
| &session->sessionId, |
| &request->userIdentityToken, |
| &session->sessionHandle); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "ActivateSession: The AccessControl plugin " |
| "denied the access with the status code %s", |
| UA_StatusCode_name(response->responseHeader.serviceResult)); |
| return; |
| } |
| |
| if(session->header.channel && session->header.channel != channel) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "ActivateSession: Detach from old channel"); |
| /* Detach the old SecureChannel and attach the new */ |
| UA_Session_detachFromSecureChannel(session); |
| UA_Session_attachToSecureChannel(session, channel); |
| } |
| |
| /* Activate the session */ |
| session->activated = true; |
| UA_Session_updateLifetime(session); |
| |
| /* Generate a new session nonce for the next time ActivateSession is called */ |
| response->responseHeader.serviceResult = UA_Session_generateNonce(session); |
| response->responseHeader.serviceResult |= |
| UA_ByteString_copy(&session->serverNonce, &response->serverNonce); |
| if(response->responseHeader.serviceResult != UA_STATUSCODE_GOOD) { |
| UA_Session_detachFromSecureChannel(session); |
| session->activated = false; |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "ActivateSession: Could not generate a server nonce"); |
| return; |
| } |
| |
| UA_LOG_INFO_SESSION(&server->config.logger, session, |
| "ActivateSession: Session activated"); |
| } |
| |
| void |
| Service_CloseSession(UA_Server *server, UA_Session *session, |
| const UA_CloseSessionRequest *request, |
| UA_CloseSessionResponse *response) { |
| UA_LOG_INFO_SESSION(&server->config.logger, session, "CloseSession"); |
| UA_LOCK_ASSERT(server->serviceMutex, 1); |
| |
| response->responseHeader.serviceResult = |
| UA_SessionManager_removeSession(&server->sessionManager, |
| &session->header.authenticationToken); |
| } |