| /* 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-2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2014, 2017 (c) Florian Palm |
| * Copyright 2015 (c) Sten GrĂ¼ner |
| * Copyright 2015 (c) Oleksiy Vasylyev |
| * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
| */ |
| |
| #include "ua_session_manager.h" |
| #include "ua_server_internal.h" |
| #include "ua_subscription.h" |
| |
| UA_StatusCode |
| UA_SessionManager_init(UA_SessionManager *sm, UA_Server *server) { |
| LIST_INIT(&sm->sessions); |
| sm->currentSessionCount = 0; |
| sm->server = server; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Delayed callback to free the session memory */ |
| static void |
| removeSessionCallback(UA_Server *server, session_list_entry *entry) { |
| UA_Session_deleteMembersCleanup(&entry->session, server); |
| } |
| |
| static void |
| removeSession(UA_SessionManager *sm, session_list_entry *sentry) { |
| UA_Server *server = sm->server; |
| UA_Session *session = &sentry->session; |
| |
| /* Remove the Subscriptions */ |
| #ifdef UA_ENABLE_SUBSCRIPTIONS |
| UA_Subscription *sub, *tempsub; |
| LIST_FOREACH_SAFE(sub, &session->serverSubscriptions, listEntry, tempsub) { |
| UA_Session_deleteSubscription(server, session, sub->subscriptionId); |
| } |
| |
| UA_PublishResponseEntry *entry; |
| while((entry = UA_Session_dequeuePublishReq(session))) { |
| UA_PublishResponse_deleteMembers(&entry->response); |
| UA_free(entry); |
| } |
| #endif |
| |
| /* Callback into userland access control */ |
| if(server->config.accessControl.closeSession) |
| server->config.accessControl.closeSession(server, &server->config.accessControl, |
| &session->sessionId, session->sessionHandle); |
| |
| /* Detach the Session from the SecureChannel */ |
| UA_Session_detachFromSecureChannel(session); |
| |
| /* Deactivate the session */ |
| sentry->session.activated = false; |
| |
| /* Detach the session from the session manager and make the capacity |
| * available */ |
| LIST_REMOVE(sentry, pointers); |
| UA_atomic_subUInt32(&sm->currentSessionCount, 1); |
| |
| /* Add a delayed callback to remove the session when the currently |
| * scheduled jobs have completed */ |
| sentry->cleanupCallback.callback = (UA_ApplicationCallback)removeSessionCallback; |
| sentry->cleanupCallback.application = sm->server; |
| sentry->cleanupCallback.data = sentry; |
| UA_WorkQueue_enqueueDelayed(&server->workQueue, &sentry->cleanupCallback); |
| } |
| |
| void UA_SessionManager_deleteMembers(UA_SessionManager *sm) { |
| session_list_entry *current, *temp; |
| LIST_FOREACH_SAFE(current, &sm->sessions, pointers, temp) { |
| removeSession(sm, current); |
| } |
| } |
| |
| void |
| UA_SessionManager_cleanupTimedOut(UA_SessionManager *sm, |
| UA_DateTime nowMonotonic) { |
| session_list_entry *sentry, *temp; |
| LIST_FOREACH_SAFE(sentry, &sm->sessions, pointers, temp) { |
| /* Session has timed out? */ |
| if(sentry->session.validTill >= nowMonotonic) |
| continue; |
| UA_LOG_INFO_SESSION(&sm->server->config.logger, &sentry->session, |
| "Session has timed out"); |
| removeSession(sm, sentry); |
| } |
| } |
| |
| UA_Session * |
| UA_SessionManager_getSessionByToken(UA_SessionManager *sm, const UA_NodeId *token) { |
| session_list_entry *current = NULL; |
| LIST_FOREACH(current, &sm->sessions, pointers) { |
| /* Token does not match */ |
| if(!UA_NodeId_equal(¤t->session.header.authenticationToken, token)) |
| continue; |
| |
| /* Session has timed out */ |
| if(UA_DateTime_nowMonotonic() > current->session.validTill) { |
| UA_LOG_INFO_SESSION(&sm->server->config.logger, ¤t->session, |
| "Client tries to use a session that has timed out"); |
| return NULL; |
| } |
| |
| /* Ok, return */ |
| return ¤t->session; |
| } |
| |
| /* Session not found */ |
| #if UA_LOGLEVEL <= 300 |
| UA_String nodeIdStr = UA_STRING_NULL; |
| UA_NodeId_toString(token, &nodeIdStr); |
| UA_LOG_INFO(&sm->server->config.logger, UA_LOGCATEGORY_SESSION, |
| "Try to use Session with token %.*s but is not found", |
| (int)nodeIdStr.length, nodeIdStr.data); |
| UA_String_deleteMembers(&nodeIdStr); |
| #endif |
| return NULL; |
| } |
| |
| UA_Session * |
| UA_SessionManager_getSessionById(UA_SessionManager *sm, const UA_NodeId *sessionId) { |
| session_list_entry *current = NULL; |
| LIST_FOREACH(current, &sm->sessions, pointers) { |
| /* Token does not match */ |
| if(!UA_NodeId_equal(¤t->session.sessionId, sessionId)) |
| continue; |
| |
| /* Session has timed out */ |
| if(UA_DateTime_nowMonotonic() > current->session.validTill) { |
| UA_LOG_INFO_SESSION(&sm->server->config.logger, ¤t->session, |
| "Client tries to use a session that has timed out"); |
| return NULL; |
| } |
| |
| /* Ok, return */ |
| return ¤t->session; |
| } |
| |
| /* Session not found */ |
| UA_String sessionIdStr = UA_STRING_NULL; |
| UA_NodeId_toString(sessionId, &sessionIdStr); |
| UA_LOG_INFO(&sm->server->config.logger, UA_LOGCATEGORY_SESSION, |
| "Try to use Session with identifier %.*s but is not found", |
| (int)sessionIdStr.length, sessionIdStr.data); |
| UA_String_deleteMembers(&sessionIdStr); |
| return NULL; |
| } |
| |
| /* Creates and adds a session. But it is not yet attached to a secure channel. */ |
| UA_StatusCode |
| UA_SessionManager_createSession(UA_SessionManager *sm, UA_SecureChannel *channel, |
| const UA_CreateSessionRequest *request, UA_Session **session) { |
| if(sm->currentSessionCount >= sm->server->config.maxSessions) |
| return UA_STATUSCODE_BADTOOMANYSESSIONS; |
| |
| session_list_entry *newentry = (session_list_entry *)UA_malloc(sizeof(session_list_entry)); |
| if(!newentry) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| UA_atomic_addUInt32(&sm->currentSessionCount, 1); |
| UA_Session_init(&newentry->session); |
| newentry->session.sessionId = UA_NODEID_GUID(1, UA_Guid_random()); |
| newentry->session.header.authenticationToken = UA_NODEID_GUID(1, UA_Guid_random()); |
| |
| if(request->requestedSessionTimeout <= sm->server->config.maxSessionTimeout && |
| request->requestedSessionTimeout > 0) |
| newentry->session.timeout = request->requestedSessionTimeout; |
| else |
| newentry->session.timeout = sm->server->config.maxSessionTimeout; |
| |
| UA_Session_updateLifetime(&newentry->session); |
| LIST_INSERT_HEAD(&sm->sessions, newentry, pointers); |
| *session = &newentry->session; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| UA_StatusCode |
| UA_SessionManager_removeSession(UA_SessionManager *sm, const UA_NodeId *token) { |
| session_list_entry *current; |
| LIST_FOREACH(current, &sm->sessions, pointers) { |
| if(UA_NodeId_equal(¤t->session.header.authenticationToken, token)) |
| break; |
| } |
| if(!current) |
| return UA_STATUSCODE_BADSESSIONIDINVALID; |
| |
| removeSession(sm, current); |
| return UA_STATUSCODE_GOOD; |
| } |