diff --git a/content/login.cpp b/content/login.cpp index 2df0ea9..925f204 100755 --- a/content/login.cpp +++ b/content/login.cpp @@ -35,6 +35,7 @@ void Content::PostFunLogin() { std::string * login = request.PostVar("login"); std::string * pass = request.PostVar("password"); + std::string * remem = request.PostVar("rememberme"); long user_id; if( login && pass && db.CheckUser(*login, *pass, user_id) ) @@ -47,6 +48,9 @@ void Content::PostFunLogin() return; } + if( remem ) + request.session->remember_me = true; + data.last.UserLogin(user_id, *login, inet_addr(request.env_remote_addr), request.session->id); log << log2 << "User " << login << " (id: " << user_id << ") logged" << logend; diff --git a/content/logout.cpp b/content/logout.cpp index b8ea1f7..6ebdff9 100755 --- a/content/logout.cpp +++ b/content/logout.cpp @@ -31,6 +31,7 @@ void Content::FunLogout() data.last.UserLogout(request.session->puser->id, request.session->id); request.session->puser = 0; + request.session->remember_me = 0; } std::string path; diff --git a/core/Makefile.dep b/core/Makefile.dep index 64b12b0..ab3d0fa 100755 --- a/core/Makefile.dep +++ b/core/Makefile.dep @@ -60,7 +60,7 @@ request.o: user.h rebus.h function.h thread.h compress.h request.o: acceptencodingparser.h acceptbaseparser.h getparser.h request.o: httpsimpleparser.h postparser.h cookieparser.h data.h dirs.h request.o: dircontainer.h users.h ugcontainer.h groups.h group.h functions.h -request.o: lastcontainer.h mounts.h mount.h plugin.h pluginmsg.h +request.o: lastcontainer.h mounts.h mount.h plugin.h pluginmsg.h misc.h requestcontroller.o: requestcontroller.h ../content/content.h ../core/item.h requestcontroller.o: ../templates/templates.h ../templates/patterncacher.h requestcontroller.o: ../core/thread.h sessionmanager.h sessioncontainer.h @@ -82,6 +82,10 @@ sessionmanager.o: error.h log.h user.h rebus.h request.h requesttypes.h sessionmanager.o: function.h thread.h compress.h acceptencodingparser.h sessionmanager.o: acceptbaseparser.h data.h dirs.h dircontainer.h users.h sessionmanager.o: ugcontainer.h groups.h group.h functions.h lastcontainer.h -sessionmanager.o: mounts.h mount.h +sessionmanager.o: mounts.h mount.h sessionparser.h +sessionparser.o: sessionparser.h session.h done.h item.h error.h log.h user.h +sessionparser.o: rebus.h sessioncontainer.h data.h dirs.h dircontainer.h +sessionparser.o: users.h ugcontainer.h groups.h group.h functions.h +sessionparser.o: function.h lastcontainer.h mounts.h mount.h users.o: users.h user.h ugcontainer.h log.h db.h item.h group.h thread.h users.o: error.h dircontainer.h diff --git a/core/Makefile.o.dep b/core/Makefile.o.dep index 67a33bc..52d30b7 100755 --- a/core/Makefile.o.dep +++ b/core/Makefile.o.dep @@ -1 +1 @@ -o = acceptbaseparser.o compress.o config.o data.o db.o db_itemcolumns.o dircontainer.o dirs.o done.o error.o function.o functioncodeparser.o functionparser.o functions.o groups.o httpsimpleparser.o lastcontainer.o log.o misc.o mount.o mountparser.o mounts.o notify.o plugin.o rebus.o request.o requestcontroller.o session.o sessioncontainer.o sessionmanager.o users.o +o = acceptbaseparser.o compress.o config.o data.o db.o db_itemcolumns.o dircontainer.o dirs.o done.o error.o function.o functioncodeparser.o functionparser.o functions.o groups.o httpsimpleparser.o lastcontainer.o log.o misc.o mount.o mountparser.o mounts.o notify.o plugin.o rebus.o request.o requestcontroller.o session.o sessioncontainer.o sessionmanager.o sessionparser.o users.o diff --git a/core/config.cpp b/core/config.cpp index a91c435..bee976f 100755 --- a/core/config.cpp +++ b/core/config.cpp @@ -118,7 +118,10 @@ void Config::AssignValues() data.priv_no_user = Text("priv_no_user"); data.priv_no_group = Text("priv_no_group"); - data.session_max_iddle = Int("session_max_iddle"); + data.session_max_iddle = Int("session_max_iddle"); + data.session_remember_max_iddle = Int("session_remember_max_iddle"); + data.session_file = Text("session_file"); + data.compression = Bool("compression"); std::string p = Text("plugin"); diff --git a/core/data.h b/core/data.h index 46ee687..fbead21 100755 --- a/core/data.h +++ b/core/data.h @@ -82,9 +82,17 @@ public: std::string priv_no_user; std::string priv_no_group; - // time in second when the user will be automatically logged out (iddle time) + // time in seconds when the user will be automatically logged out (iddle time) int session_max_iddle; + + // time in seconds when the user will be automatically logged out (when he selected 'remember me' option) + // this time is usually greater than session_max_iddle + int session_remember_max_iddle; + // this file is used when the program is starting and ending + std::string session_file; + + // allow the html ouput to be compressed bool compression; // plugins diff --git a/core/misc.cpp b/core/misc.cpp index 27a2652..57075d9 100755 --- a/core/misc.cpp +++ b/core/misc.cpp @@ -261,15 +261,15 @@ static char buffer[100]; --month; if( month < 0 ) - month = 0; + month = 0; if( month > 11 ) month = 11; if( year == 0 ) - sprintf(buffer, "%s.%02d %02d:%02d:%02d", month_letter[month], day, hour, min, sec); + sprintf(buffer, "%s %02d %02d:%02d:%02d", month_letter[month], day, hour, min, sec); else - sprintf(buffer, "%02d.%s.%02d %02d:%02d:%02d", year, month_letter[month], day, hour, min, sec); + sprintf(buffer, "%02d %s %02d %02d:%02d:%02d", year, month_letter[month], day, hour, min, sec); // warning: not thread safe (we do not use threads) return buffer; @@ -290,6 +290,40 @@ return DateToStr(ptm); } +// this format is used with cookies +const char * DateToStrCookie(int year, int month, int day, int hour, int min, int sec) +{ +static const char * month_str[]={ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static char buffer[100]; + + --month; + + if( month < 0 ) + month = 0; + + if( month > 11 ) + month = 11; + + sprintf(buffer, "%02d-%s-%04d %02d:%02d:%02d", day, month_str[month], year, hour, min, sec); + +return buffer; +} + + +const char * DateToStrCookie(tm * ptm) +{ + return DateToStrCookie(ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec); +} + + +const char * DateToStrCookie(time_t t) +{ + tm * ptm = std::localtime(&t); + +return DateToStrCookie(ptm); +} + + const char * IpToStr(unsigned int ip_) { static char buffer[100]; diff --git a/core/misc.h b/core/misc.h index 196fa09..9726fd9 100755 --- a/core/misc.h +++ b/core/misc.h @@ -37,6 +37,11 @@ std::string HtmlEscapeFormTxt(const std::string & in); const char * DateToStr(int year, int month, int day, int hour, int min, int sec); const char * DateToStr(tm * ptm); const char * DateToStr(time_t t); + +const char * DateToStrCookie(int year, int month, int day, int hour, int min, int sec); +const char * DateToStrCookie(tm * ptm); +const char * DateToStrCookie(time_t t); + const char * IpToStr(unsigned int ip_); bool IsWhite(int s); diff --git a/core/request.cpp b/core/request.cpp index 7603e5c..975694d 100755 --- a/core/request.cpp +++ b/core/request.cpp @@ -6,7 +6,8 @@ * All rights reserved. * */ - + +#include #include "request.h" #include "getparser.h" #include "postparser.h" @@ -14,7 +15,7 @@ #include "log.h" #include "data.h" #include "plugin.h" - +#include "misc.h" @@ -84,29 +85,35 @@ void Request::Clear() // value can be null -void Request::SetCookie(const char * name, const char * value) +void Request::SetCookie(const char * name, const char * value, tm * expires) { - request.headers << "Set-Cookie: " << name << "="; + headers << "Set-Cookie: " << name << "="; if( value && value[0]!=0 ) - request.headers << value; + headers << value; else - request.headers << "\"\""; + headers << "\"\""; - request.headers << "; path=/\r\n"; + if( expires ) + headers << "; expires=" << DateToStrCookie(expires) << " GMT"; + + headers << "; path=/\r\n"; } -void Request::SetCookie(const char * name, long value) +void Request::SetCookie(const char * name, long value, tm * expires) { - request.headers << "Set-Cookie: " << name << "=" << value << "; path=/\r\n"; + headers << "Set-Cookie: " << name << "=" << value; + + if( expires ) + headers << "; expires=" << DateToStrCookie(expires) << " GMT"; + + headers << "; path=/\r\n"; } - - bool Request::IsPostVar(const char * var) { PostTable::iterator p; @@ -298,8 +305,36 @@ void Request::Read() +void Request::SendSessionCookie() +{ + if( !session || session->id==0 ) + return; + + if( !session->puser || !session->remember_me ) + { + SetCookie(data.http_session_id_name.c_str(), session->id); + return; + } + + time_t t = time(0) + data.session_remember_max_iddle; + tm * expires = localtime(&t); + + if( !expires ) + { + // oops, something wrong + SetCookie(data.http_session_id_name.c_str(), session->id); + return; + } + + SetCookie(data.http_session_id_name.c_str(), session->id, expires); +} + + void Request::SendAll() { + SendSessionCookie(); + + if( !redirect_to.empty() ) { FCGX_PutS("Status: 301 Moved Permanently\r\n", out); diff --git a/core/request.h b/core/request.h index c7f6971..59f42d2 100755 --- a/core/request.h +++ b/core/request.h @@ -112,8 +112,8 @@ struct Request bool IsParam(const char * s); - void SetCookie(const char * name, const char * value); - void SetCookie(const char * name, long value); + void SetCookie(const char * name, const char * value, tm * expires = 0); + void SetCookie(const char * name, long value, tm * expires = 0); bool IsPostVar(const char * var); std::string * PostVar(const char * var); // it can return null when there is no such a post variable @@ -147,6 +147,7 @@ struct Request private: + void SendSessionCookie(); void CheckIE(); // used to set some env_* variables into it, when the server didn't set that variable diff --git a/core/requestcontroller.cpp b/core/requestcontroller.cpp index 5a5c228..b8a84ff 100755 --- a/core/requestcontroller.cpp +++ b/core/requestcontroller.cpp @@ -19,6 +19,7 @@ RequestController::RequestController() { + last_sessions_save = time(0); } @@ -121,6 +122,29 @@ return true; } +void RequestController::LoadSessions() +{ + session_manager.LoadSessions(); +} + +void RequestController::SaveSessions() +{ + session_manager.SaveSessions(); +} + + +void RequestController::SaveSessionsIfNeeded() +{ + time_t t = time(0); + + if( last_sessions_save + 86400 > t ) + return; + + // saving once a day for safety + last_sessions_save = t; + SaveSessions(); +} + bool RequestController::BaseUrlRedirect() { @@ -182,6 +206,7 @@ void RequestController::Loop() log << log1 << "uncaught exception" << logend; } + SaveSessionsIfNeeded(); // !! this should be immediately after FCGX_Accept() but signals don't want to break FCGX_Accept if( data.signal_hup ) diff --git a/core/requestcontroller.h b/core/requestcontroller.h index b2de620..4c36435 100755 --- a/core/requestcontroller.h +++ b/core/requestcontroller.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "../content/content.h" #include "sessionmanager.h" @@ -42,10 +43,14 @@ public: void Close(); void Loop(); + void LoadSessions(); + void SaveSessions(); + void SaveSessionsIfNeeded(); // saving only once a day SessionContainer::Iterator SessionBegin(); SessionContainer::Iterator SessionEnd(); + time_t last_sessions_save; }; diff --git a/core/session.cpp b/core/session.cpp index 174da42..749b701 100755 --- a/core/session.cpp +++ b/core/session.cpp @@ -33,6 +33,8 @@ void Session::Clear() item.Clear(); done_timer = 0; rebus_item = 0; + remember_me = false; + new_session = true; dir_old.clear(); } diff --git a/core/session.h b/core/session.h index d470c52..2157fcf 100755 --- a/core/session.h +++ b/core/session.h @@ -23,6 +23,9 @@ struct Session // 0 - means that there is no session long id; + // true if the session was created now + bool new_session; + // when this session was created // (the same values) time_t time; @@ -37,6 +40,9 @@ struct Session // 0 - means that nobody is logged User * puser; + // if false the session will end when the user browser is shutdown + bool remember_me; + // what is done Done done; Error done_status; @@ -47,7 +53,7 @@ struct Session // used for many purposes, depending on 'done' Item item; - + // rebus - set by rebus_question(Info & i) from templates Rebus::Item * rebus_item; std::string dir_old; @@ -55,7 +61,7 @@ struct Session // ------------------- Session(); - virtual void Clear(); + void Clear(); bool operator==(const Session & s) const; bool operator<(const Session & s) const; bool DecTimer(int & timer); diff --git a/core/sessioncontainer.cpp b/core/sessioncontainer.cpp index 7dbbee7..f011ab4 100755 --- a/core/sessioncontainer.cpp +++ b/core/sessioncontainer.cpp @@ -20,6 +20,12 @@ void SessionContainer::Clear() } +SessionContainer::TableSize SessionContainer::Size() +{ + return table.size(); +} + + SessionContainer::Iterator SessionContainer::Begin() { return table.begin(); @@ -71,18 +77,22 @@ return i->second; -void SessionContainer::DelFirstByTimeInterval(time_t interval) +void SessionContainer::DelFirstByTimeInterval(time_t interval, bool skip_remember_flag) { IndexTime::iterator i = index_time.begin(); IndexTime::iterator iold; time_t limit = std::time(0) - interval; - for( ; i != index_time.end() && i->second->last_time < limit ; ) + while( i != index_time.end() && i->second->last_time < limit ) { long id = i->second->id; iold = i; ++i; // incrementing before deleting old one + if( skip_remember_flag && iold->second->puser && iold->second->remember_me ) + // don't delete sessions which have 'remember_me' flag (and a user is logged) + continue; + if( iold->second->puser ) data.last.UserLogout(iold->second->puser->id, iold->second->id); diff --git a/core/sessioncontainer.h b/core/sessioncontainer.h index d48c6b6..8ef5c16 100755 --- a/core/sessioncontainer.h +++ b/core/sessioncontainer.h @@ -25,6 +25,7 @@ class SessionContainer public: typedef std::list Table; typedef Table::iterator Iterator; + typedef Table::size_type TableSize; typedef std::map IndexId; typedef std::multimap IndexTime; @@ -41,7 +42,9 @@ private: public: void Clear(); - + + TableSize Size(); + Iterator Begin(); Iterator End(); @@ -50,8 +53,8 @@ public: bool PushBack(const Session & session); Iterator FindById(long); - void DelFirstByTimeInterval(time_t interval); - + void DelFirstByTimeInterval(time_t interval, bool skip_remember_flag = true); + void UpdateLastTime(Iterator iter, time_t new_time); }; diff --git a/core/sessionmanager.cpp b/core/sessionmanager.cpp index 615cb06..ba9f6bc 100755 --- a/core/sessionmanager.cpp +++ b/core/sessionmanager.cpp @@ -7,13 +7,20 @@ * */ +#include #include "sessionmanager.h" #include "request.h" #include "log.h" #include "data.h" #include "session.h" +#include "sessionparser.h" +SessionManager::SessionManager() +{ + session_checker = 0; +} + bool SessionManager::IsSession(long id) { @@ -87,7 +94,8 @@ int attempts = 100; if( added ) { request.session = &session_table.Back(); - request.SetCookie(data.http_session_id_name.c_str(), request.session->id); + request.session->new_session = true; + log << log2 << "SM: created a new session: " << request.session->id << logend; return; @@ -119,6 +127,7 @@ void SessionManager::SetSession() { // that session is in the table request.session = &(*s); + request.session->new_session = false; session_table.UpdateLastTime(s, std::time(0)); @@ -163,12 +172,63 @@ SessionContainer::Iterator SessionManager::SessionEnd() void SessionManager::DeleteOldSessions() { session_table.DelFirstByTimeInterval(data.session_max_iddle); + + if( ++session_checker > 1000 ) + { + // we make the test after 1000 requests + log << log3 << "SM: checking sessions which have 'remember me' flag set" << logend; + + session_checker = 0; + session_table.DelFirstByTimeInterval(data.session_remember_max_iddle, false); + } +} + + +void SessionManager::LoadSessions() +{ +SessionParser sp; + + sp.Parse(data.session_file, session_table); + +} + + +void SessionManager::SaveSessions() +{ + if( data.session_file.empty() ) + return; + + std::ofstream file(data.session_file.c_str()); + + if( !file ) + { + log << log1 << "SM: cannot open the session file for writing - sessions lost" << logend; + return; + } + + log << log2 << "SM: saving sessions" << logend; + long len = 0; + + SessionContainer::Iterator i = session_table.Begin(); + + for( ; i!=session_table.End() ; ++i ) + { + if( i->id != 0 && i->puser ) + { + file << i->id << ' ' << i->puser->id << ' ' << i->remember_me << ' '; + file << (long)i->time << ' ' << (long)i->last_time << std::endl; + + ++len; + } + } + + file.close(); + chmod(data.session_file.c_str(), 0600); + + log << log2 << "SM: saved " << len << " session(s)" << logend; } - - - diff --git a/core/sessionmanager.h b/core/sessionmanager.h index 4525625..dd6f8cc 100755 --- a/core/sessionmanager.h +++ b/core/sessionmanager.h @@ -29,12 +29,17 @@ class SessionManager void CreateTemporarySession(); void CreateSession(); + int session_checker; + public: - + SessionManager(); void SetSession(); void DeleteOldSessions(); - + + void LoadSessions(); + void SaveSessions(); + SessionContainer::Iterator SessionBegin(); SessionContainer::Iterator SessionEnd(); }; diff --git a/core/sessionparser.cpp b/core/sessionparser.cpp new file mode 100755 index 0000000..3413311 --- /dev/null +++ b/core/sessionparser.cpp @@ -0,0 +1,148 @@ +/* + * This file is a part of CMSLU -- Content Management System like Unix + * and is not publicly distributed + * + * Copyright (c) 2008-2009, Tomasz Sowa + * All rights reserved. + * + */ + +#include "sessionparser.h" +#include "log.h" +#include "data.h" + + +bool SessionParser::Parse(const std::string & path, SessionContainer & container) +{ + return Parse(path.c_str(), container); +} + + +bool SessionParser::Parse(const char * path, SessionContainer & container) +{ + container.Clear(); + file.open(path, std::ios_base::in | std::ios_base::binary); + + if( !file ) + { + log << log1 << "SP: cannot open the session file for reading (file: '" << path << "')" << logend; + return false; + } + + bool res = Parse(container); + + file.close(); + +return res; +} + + + +bool SessionParser::Parse(SessionContainer & container) +{ + /* + file format, each rows: + session_id(long) user_id(long) remember_me(0|1) time(long) last_time(long) + */ + + long user_id; + + session.Clear(); + last = file.get(); + + log << log2 << "SP: reading sessions from the session file" << logend; + + while( true ) + { + session.id = ReadLong(); + + if( file.eof() ) + break; + + user_id = ReadLong(); + session.remember_me = ReadLong(); + session.time = ReadLong(); + session.last_time = ReadLong(); + + if( MakeSession(user_id) ) + { + container.PushBack(session); + log << log2 << "SP: read session id: " << session.id << " for user: " << session.puser->name << logend; + } + + SkipLine(); + } + + log << log2 << "SP: read " << container.Size() << " session(s)" << logend; + +return true; +} + + +bool SessionParser::MakeSession(long user_id) +{ + User * puser = data.users.GetUser(user_id); + + if( !puser ) + { + log << log1 << "SP: there is no a user with id: " << user_id << " (skipped)" << logend; + return false; + } + + session.puser = puser; + session.new_session = true; + session.tm_time = *std::localtime(&session.time); + session.tm_last_time = *std::localtime(&session.last_time); + +return true; +} + + +bool SessionParser::IsWhite(int c) +{ + if( c==' ' || c=='\t' || c==13 ) + return true; + +return false; +} + +bool SessionParser::IsDigit(int c) +{ + if( c>='0' && c<='9' ) + return true; + +return false; +} + +void SessionParser::SkipWhite() +{ + while( IsWhite(last) ) + last = file.get(); +} + + +void SessionParser::SkipLine() +{ + while( last != 10 ) + last = file.get(); + + last = file.get(); // first character from the new line +} + + + +long SessionParser::ReadLong() +{ +long res = 0; + + SkipWhite(); + + while( IsDigit(last) ) + { + res = res*10 + (last-'0'); + last = file.get(); + } + +return res; +} + diff --git a/core/sessionparser.h b/core/sessionparser.h new file mode 100755 index 0000000..aa823e6 --- /dev/null +++ b/core/sessionparser.h @@ -0,0 +1,46 @@ +/* + * This file is a part of CMSLU -- Content Management System like Unix + * and is not publicly distributed + * + * Copyright (c) 2008-2009, Tomasz Sowa + * All rights reserved. + * + */ + +#ifndef headerfilecmslucoresessionparser +#define headerfilecmslucoresessionparser + +#include +#include +#include "session.h" +#include "sessioncontainer.h" + +class SessionParser +{ +public: + + bool Parse(const char * path, SessionContainer & container); + bool Parse(const std::string & path, SessionContainer & container); + + +private: + + bool Parse(SessionContainer & container); + bool MakeSession(long user_id); + + bool IsWhite(int c); + bool IsDigit(int c); + void SkipWhite(); + void SkipLine(); + + long ReadLong(); + + std::ifstream file; + int last; // last character + Session session; + +}; + + +#endif + diff --git a/main/main.cpp b/main/main.cpp index 9fb19b2..db808e6 100755 --- a/main/main.cpp +++ b/main/main.cpp @@ -37,7 +37,9 @@ Plugin plugin; void signal_term(int) { + req_controller.SaveSessions(); log << log1 << "cmslu stopped" << logend; + exit(0); } @@ -108,14 +110,14 @@ int main(int argv, char ** argc) signal(SIGINT, signal_term); signal(SIGHUP, signal_hup); + req_controller.LoadSessions(); + log << log1 << "cmslu started" << logend; while( true ) { //log << log2 << "checking for table consistency:" << logend; // !! zrobic wyjatek dla root //db.CheckAllUrlSubject(); - - log << log1 << "cmslu started" << logend; req_controller.Loop();