/* * This file is a part of Winix * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2008-2019, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include "sessionmanager.h" #include "request.h" #include "log.h" #include "session.h" #include "sessionparser.h" #include "functions/functionbase.h" namespace Winix { SessionManager::SessionManager() { config = nullptr; cur = nullptr; system = nullptr; last_container = nullptr; current_ip_ban = nullptr; temporary_session.id = 0; session = &temporary_session; session_tab.SetTmpSession(&temporary_session); // thread work mode work_mode = 1; // for another thread deleted = 0; } void SessionManager::SetCur(Cur * pcur) { cur = pcur; session_tab.SetCur(pcur); } void SessionManager::SetSystem(System * psystem) { system = psystem; } void SessionManager::SetLastContainer(LastContainer * plast_container) { last_container = plast_container; } void SessionManager::set_dependency(WinixModelDeprecated * winix_model) { WinixModelDeprecated::set_dependency(winix_model); session_tab.set_dependency(winix_model); session_id_manager.set_dependency(winix_model); } void SessionManager::InitBanList() { ban_tab.SetMaxSize(config->ban_list_soft_max_size, config->ban_list_max_size); } void SessionManager::InitCookieEncoding() { if( config->session_cookie_encode && !config->session_keys_file.empty() ) session_id_manager.Init(config->session_keys_file); session_id_manager.SetKeyRenewTime(config->session_key_renew_time); } size_t SessionManager::Size() { return session_tab.Size(); } bool SessionManager::IsSession(long id) { if( session_tab.FindById(id) == session_tab.End() ) return false; return true; } bool SessionManager::EncodeSessionId(long id, unsigned int index, std::wstring & str) { return session_id_manager.EncodeToken((size_t)id, index, cur->request->start_time, str); } /* * IMPROVE ME we need a better algorithm */ long SessionManager::CreateSessionId() { long id; // make sure to call std::srand() somewhere at the beginning // id must be != 0 (0 is reserved) do { if( sizeof(long) == 8 ) { id = (((unsigned long)std::rand()) << 32) + std::rand(); } else { id = std::rand(); } id += std::time(0); if( id < 0 ) id = -id; } while( id == 0 ); // 0 reserved for a temporary session return id; } void SessionManager::CreateSession() { int attempts = 100; SessionContainer::Iterator i = session_tab.End(); if( config->session_max == 0 || session_tab.Size() < config->session_max ) { for( ; i == session_tab.End() && attempts > 0 ; --attempts ) { long id = CreateSessionId(); i = session_tab.AddSession(id); } } else { main_log << log2 << "SM: sessions limit exceeded (" << config->session_max << ")" << logend; } if( i != session_tab.End() ) { session = &(*i); session->new_session = true; session->SetTimesTo(cur->request->start_time); session->id_index = (unsigned int)session->id; session->id_index += std::rand(); main_log << log2 << "SM: created a new session: " << session->id << logend; } else { // there is a problem with generating a new session id main_log << log1 << "SM: cannot create a session id" << logend; } } void SessionManager::SetTemporarySession() { session = &temporary_session; session->Clear(false); session->SetTimesTo(cur->request->start_time); session->new_session = false; // temporary session was initialized at the beginning main_log << log2 << "SM: using temporary session" << logend; } unsigned int SessionManager::CalculateIndexDifference(Session & ses, unsigned int index) { unsigned int difference; if( index > ses.id_index ) difference = std::numeric_limits::max() - index + ses.id_index + 1; else difference = ses.id_index - index; return difference; } void SessionManager::SetSessionPutLogInfo(Session & ses, bool has_index, unsigned int difference) { main_log << log2 << "SM: session: " << ses.id; if( has_index ) main_log << ", index difference: " << (size_t)difference; if( ses.puser ) main_log << log2 << ", user: " << ses.puser->login << ", id: " << ses.puser->id; main_log << log2 << logend; } void SessionManager::IncrementBanLevel(IPBan * ip_ban) { ip_ban->SetFlag(WINIX_IPBAN_FLAG_ACTIVE); ip_ban->IncrementBanLevel(cur->request->start_time + (time_t)config->ban_level_1_delay, cur->request->start_time + (time_t)config->ban_level_2_delay, cur->request->start_time + (time_t)config->ban_level_3_delay); pt::Date date(ip_ban->expires); main_log << log2 << "SM: this IP address has been banned to: " << date << " UTC" << logend; } void SessionManager::SetFirstExpirationTime(IPBan * ip_ban) { time_t expiry = cur->request->start_time + (time_t)config->ban_level_1_delay; if( ip_ban->expires < expiry ) ip_ban->expires = expiry; } void SessionManager::BrokenCookieCheckBan() { if( !current_ip_ban ) current_ip_ban = &AddIPToBanList(cur->request->ip, cur->request->start_time); if( current_ip_ban->broken_encoded_cookie_events < config->broken_encoded_cookie_treshold ) { current_ip_ban->broken_encoded_cookie_events += 1; SetFirstExpirationTime(current_ip_ban); } else { main_log << log2 << "SM: too many incorrect encoded cookies were sent from this IP" << logend; IncrementBanLevel(current_ip_ban); } } void SessionManager::IncorrectSessionCheckBan() { if( !current_ip_ban ) current_ip_ban = &AddIPToBanList(cur->request->ip, cur->request->start_time); if( current_ip_ban->session_hijacking_events < config->session_hijacking_treshold ) { current_ip_ban->session_hijacking_events += 1; SetFirstExpirationTime(current_ip_ban); } else { main_log << log2 << "SM: too many incorrect sessions identifiers were sent from this IP" << logend; IncrementBanLevel(current_ip_ban); } } void SessionManager::NoSessionCookieWasSent() { if( !current_ip_ban ) current_ip_ban = &AddIPToBanList(cur->request->ip, cur->request->start_time); if( current_ip_ban->no_session_cookie_events < config->no_session_cookie_treshold ) { current_ip_ban->no_session_cookie_events += 1; SetFirstExpirationTime(current_ip_ban); } else { main_log << log2 << "SM: too many times you have not sent a session cookie" << logend; if( config->no_session_cookie_ban_mode == 1 ) IncrementBanLevel(current_ip_ban); } } bool SessionManager::IsSessionCorrect(long id, bool has_index, unsigned int index, const SessionContainer::Iterator & s, unsigned int & difference) { difference = 0; if( id == 0 ) { main_log << log3 << "SM: id 0 is reserved for the temporary session" << logend; IncorrectSessionCheckBan(); return false; } if( s == session_tab.End() ) { main_log << log3 << "SM: there is no a session with id: " << id << logend; IncorrectSessionCheckBan(); return false; } if( s->remove_me ) { main_log << log3 << "SM: session: " << id << " is marked for removing" << logend; return false; } if( has_index ) { difference = CalculateIndexDifference(*s, index); if( (size_t)difference > config->session_allow_index_difference ) { main_log << log2 << "SM: an incorrect session index for session: " << id << ", index difference: " << (size_t)difference << logend; IncorrectSessionCheckBan(); return false; } } return true; } bool SessionManager::SetSessionFromCookie(long id, bool has_index, unsigned int index) { unsigned int difference; bool is_session_correct; SessionContainer::Iterator s = session_tab.FindById(id); is_session_correct = IsSessionCorrect(id, has_index, index, s, difference); if( is_session_correct ) { session = &(*s); session->new_session = false; session->last_time = cur->request->start_time; session->last_date = cur->request->start_date; if( session->id_index_changed + config->session_index_time_increment < cur->request->start_time ) { session->id_index += 1; session->id_index_changed = cur->request->start_time; } if( cur->request->method == Request::get ) session->last_time_get = cur->request->start_time; SetSessionPutLogInfo(*session, has_index, difference); } return is_session_correct; } bool SessionManager::SetSessionFromCookie(const std::wstring & cookie) { if( config->session_cookie_encode ) { size_t id; unsigned int index; if( !session_id_manager.DecodeToken(cookie, id, index) ) { main_log << log2 << "SM: an incorrect cookie string was sent" << logend; BrokenCookieCheckBan(); return false; } return SetSessionFromCookie((long)id, true, index); } else { long id = Tol(cookie.c_str()); return SetSessionFromCookie(id, false, 0); } } bool SessionManager::IsIPBanned() { current_ip_ban = ban_tab.FindIP(cur->request->ip); if( current_ip_ban ) { current_ip_ban->last_used = cur->request->start_time; if( current_ip_ban->expires != 0 && cur->request->start_time >= current_ip_ban->expires ) { main_log << log2 << "SM: resetting events counters for this IP" << logend; current_ip_ban->ResetEventsCounters(); } else if( current_ip_ban->IsIPBanned() ) { pt::Date date = current_ip_ban->expires; main_log << log2 << "SM: this ip is bannned to: " << date << " UTC" << logend; return true; } } return false; } /* * preparing a new session * * algorithm: * - if the IP is banned or there is no a winix function then we set a temporary session * - else * if there is a session's cookie sent by the client then: * - if the cookie is a correct session's cookie then we set the session from the cookie * - or if the cookie is not a correct session's cookie (e.g. session expired) and the winix function * requires a cookie then we set a new session * - or if there is no cookie sent then if a winix function requires a session we create a new session * * if there was an error creating a new session or event counters reach a ban limit then a temporary session will be used * so we always have a session * */ Session * SessionManager::PrepareSession() { session = nullptr; if( !IsIPBanned() ) { CookieTab::iterator i = cur->request->cookie_tab.find(config->http_session_id_name); if( i != cur->request->cookie_tab.end() ) { if( !SetSessionFromCookie(i->second) ) { cur->request->cookie_tab.erase(i); } } else { if( cur->request->function && cur->request->function->need_session ) { NoSessionCookieWasSent(); } } } if( !session && cur->request->function && cur->request->function->need_session ) { if( !current_ip_ban || !current_ip_ban->IsIPBanned() ) { CreateSession(); } } if( !session ) { SetTemporarySession(); } session->ip_ban = current_ip_ban; return session; } Session * SessionManager::CheckIfFunctionRequireSession() { if( cur->request->function && cur->request->function->need_session ) { if( session == &temporary_session ) { if( !current_ip_ban || !current_ip_ban->IsIPBanned() ) { CreateSession(); session->ip_ban = current_ip_ban; } } } return session; } Session * SessionManager::FindSession(long id) { SessionContainer::Iterator i = session_tab.FindById(id); if( i != session_tab.End() ) return &*i; return 0; } SessionContainer::Iterator SessionManager::SessionBegin() { return session_tab.Begin(); } SessionContainer::Iterator SessionManager::SessionEnd() { return session_tab.End(); } void SessionManager::DeleteSessions() { SessionContainer::Iterator i; for(i=session_tab.Begin() ; i!=session_tab.End() ; ++i) { if( i->puser && !i->remember_me ) { plugin->Call(&(*i), WINIX_PREPARE_USER_TO_LOGOUT, i->puser); last_container->UserLogout(i->puser->id, i->id); } } session_tab.Clear(); } /* don't change a session's id when a user is logged the session id is in last_container and the user would not be correctly removed from the container */ bool SessionManager::ChangeSessionId(long old_id) { int attempts = 100; bool changed = false; long new_id; SessionContainer::Iterator i = session_tab.FindById(old_id); if( i != session_tab.End() ) { for( ; !changed && attempts > 0 ; --attempts ) { new_id = CreateSessionId(); changed = session_tab.ChangeSessionId(i, new_id); } if( changed ) plugin->Call(&(*i), WINIX_SESSION_CHANGED_ID, old_id, new_id); else main_log << log1 << "SM: I cannot create a new session id (still uses old one)" << logend; } else { main_log << log2 << "SM: there is no a session with id: " << old_id << logend; } return changed; } void SessionManager::InitTmpSession() { Session * old_session = cur->session; main_log << log4 << "SM: initializing temporary session" << logend; cur->session = &temporary_session; plugin->Call(WINIX_SESSION_CREATED); cur->session = old_session; } void SessionManager::UninitTmpSession() { Session * old_session = cur->session; main_log << log4 << "SM: uninitializing temporary session" << logend; cur->session = &temporary_session; if( cur->session->plugin_data.HasAllocatedData() ) { plugin->Call(cur->session, WINIX_PLUGIN_SESSION_DATA_REMOVE); } //cur->session->plugin_data.DeleteAll(); // this will call plugin.Call(WINIX_PLUGIN_SESSION_DATA_REMOVE); cur->session->plugin_data.Resize(0); cur->session = old_session; } void SessionManager::LoadSessions() { SessionParser sp; SessionContainer::Iterator i; sp.set_dependency(this); // sessions will be overwritten (pointers are invalidated) cur->session = &temporary_session; sp.SetUsers(&system->users); sp.Parse(config->session_file, session_tab); for(i=session_tab.Begin() ; i != session_tab.End() ; ++i) { i->plugin_data.Resize(plugin->Size()); plugin->Call(&(*i), WINIX_SESSION_CREATED); /* !! IMPROVE ME we do not add it to the last_container (we don't have IP address stored yet) */ if( i->puser ) plugin->Call(&(*i), WINIX_USER_LOGGED); } cur->session = &temporary_session; } void SessionManager::SaveSessions() { char file_path[WINIX_OS_PATH_SIZE]; if( config->session_file.empty() ) return; if( !wide_to_utf8(config->session_file, file_path, WINIX_OS_PATH_SIZE) ) return; std::ofstream file(file_path); if( !file ) { main_log << log1 << "SM: cannot open the session file for writing - sessions lost" << logend; return; } main_log << log2 << "SM: saving sessions" << logend; long len = 0; SessionContainer::Iterator i = session_tab.Begin(); for( ; i!=session_tab.End() ; ++i ) { if( i->id != 0 && i->puser && !i->remove_me ) { file << i->id << ' ' << i->puser->id << ' ' << i->remember_me << ' '; file << (long)i->start_time << ' ' << (long)i->last_time << ' '; file << i->id_index << std::endl; ++len; } } file.close(); chmod(file_path, 0600); main_log << log2 << "SM: saved " << len << " session(s)" << logend; } Session * SessionManager::GetTmpSession() { return &temporary_session; } Session * SessionManager::GetCurSession() { return session; } // returns how many sessions was marked to remove size_t SessionManager::MarkAllSessionsToRemove(long user_id) { size_t how_many = 0; SessionContainer::Iterator i; for(i=session_tab.Begin() ; i!=session_tab.End() ; ++i) { if( i->puser && i->puser->id == user_id ) { plugin->Call(&(*i), WINIX_PREPARE_USER_TO_LOGOUT, i->puser); last_container->UserLogout(i->puser->id, i->id); i->remove_me = true; i->puser = 0; how_many += 1; } } return how_many; } IPBan & SessionManager::AddIPToBanList(int ip) { return ban_tab.AddIP(ip); } IPBan & SessionManager::AddIPToBanList(int ip, time_t last_used) { IPBan & ban = ban_tab.AddIP(ip); ban.last_used = last_used; return ban; } size_t SessionManager::BanListSize() { return ban_tab.Size(); } IPBan & SessionManager::GetIPBan(size_t index) { return ban_tab.GetIPBan(index); } void SessionManager::RemoveIPBan(int ip) { ban_tab.RemoveIP(ip); } void SessionManager::ClearIPBanList() { ban_tab.Clear(); } /* * * * sessions gc (second thread) * sessions are only removed here * SessionContainer::IndexId can be removed from the other thread * (when ChangeSessionId() method is called) * */ void SessionManager::Work() { bool exit = false; SessionContainer::Iterator i; deleted = 0; Lock(); i = session_tab.Begin(); Unlock(); while( !exit ) { Lock(); CheckWheterIPListIsSorted(); CheckSession(i); exit = synchro->was_stop_signal; Unlock(); } } // objects locked void SessionManager::CheckWheterIPListIsSorted() { if( !ban_tab.IsSorted() ) { log << log4 << "SM: sorting the ban list" << logend; ban_tab.Sort(); } } // it's called from the other thread (with Lock and Unlock) void SessionManager::CheckSession(SessionContainer::Iterator & i) { const int deleted_max_at_once = 10; if( i == session_tab.End() ) { if( deleted > 0 ) { deleted = 0; log << logsave; } i = session_tab.Begin(); WaitForSignalSleep(10); } else { if( i->remove_me || IsSessionOutdated(*i) ) { Session * ses = &(*i); ++i; DeleteSession(ses); ++deleted; } else { ++i; } if( deleted >= deleted_max_at_once ) { log << logsave; WaitForSignalSleep(1); deleted = 0; } } } // it's called from the other thread (with Lock and Unlock) bool SessionManager::IsSessionOutdated(const Session & s) const { bool outdated; if( s.remember_me ) { outdated = s.last_time < std::time(0) - config->session_remember_max_idle; } else { outdated = s.last_time < std::time(0) - config->session_max_idle; } return outdated; } // it's called from the other thread (with Lock and Unlock) void SessionManager::DeleteSession(Session * del_session) { if( del_session->puser ) { plugin->Call(del_session, WINIX_PREPARE_USER_TO_LOGOUT, del_session->puser); last_container->UserLogout(del_session->puser->id, del_session->id); del_session->puser = 0; } long id = del_session->id; plugin->Call(del_session, WINIX_PREPARE_SESSION_TO_REMOVE); session_tab.EraseById(del_session->id); plugin->Call((Session*)0, WINIX_SESSION_REMOVED, id); } /* * * * end of sessions gc * * */ } // namespace Winix