winix/winixd/core/app.cpp

2499 lines
55 KiB
C++
Raw Normal View History

/*
* This file is a part of Winix
* and is distributed under the 2-Clause BSD licence.
* Author: Tomasz Sowa <t.sowa@ttmath.org>
*/
/*
* Copyright (c) 2010-2021, 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <utility>
#include <fastcgi.h>
#include <stddef.h>
#include "app.h"
#include "plugin.h"
#include "misc.h"
#include "functions/functions.h"
#include "utf8/utf8.h"
#include "convert/convert.h"
#include "models/migration.h"
namespace Winix
{
App::App()
{
stdout_is_closed = false;
last_sessions_save = std::time(0);
fcgi_socket = -1;
file_log.set_synchro(&synchro);
file_log.set_time_zones(&system.time_zones);
log.set_log_buffer(&log_buffer);
log.set_file_log(&file_log);
// objects dependency for main thread
winix_base.set_config(&config);
winix_base.set_log_buffer(&log_buffer);
winix_base.set_file_log(&file_log);
winix_base.set_synchro(&synchro);
winix_model.set_dependency(&winix_base);
winix_model.set_plugin(&plugin);
winix_model.set_model_connector(&model_connector);
winix_system.set_dependency(&winix_model);
winix_system.set_system(&system);
winix_request.set_dependency(&winix_system);
winix_request.set_cur(&cur);
winix_request.set_session_manager(&session_manager);
winix_request.set_locale(&TemplatesFunctions::locale);
winix_request.set_model_connector(&model_connector);
// //////////////////////////////////
config.SetFileLog(&file_log);
config.SetLogBuffer(&log_buffer);
// temporary there is only one request
cur.request = &req;
cur.session = session_manager.GetTmpSession();
cur.mount = system.mounts.GetEmptyMount();
db_conn.set_dependency(&winix_base);
db.set_dependency(&winix_base);
db.SetConn(db_conn);
plugin.set_dependency(&winix_base);
plugin.SetDb(&db);
//plugin.SetConfig(&config);
plugin.SetCur(&cur);
plugin.SetSystem(&system);
plugin.SetFunctions(&functions);
plugin.SetTemplates(&templates);
//plugin.SetSynchro(&synchro);
plugin.SetSessionManager(&session_manager);
plugin.SetWinixRequest(&winix_request);
req.SetConfig(&config);
req.set_connector(&model_connector);
functions.set_dependency(&winix_request);
// functions.set_config(&config);
// functions.set_file_log(&file_log);
// functions.set_model_connector(&model_connector);
// functions.set_synchro(&synchro);
//functions.SetConfig(&config);
functions.SetCur(&cur);
functions.SetDb(&db);
functions.SetSystem(&system);
functions.SetTemplates(&templates);
//functions.SetSynchro(&synchro);
functions.SetSessionManager(&session_manager);
system.set_dependency(&winix_model);
//system.SetConfig(&config);
system.SetCur(&cur);
system.SetDb(&db);
//system.SetSynchro(&synchro);
system.SetFunctions(&functions);
system.SetSessionManager(&session_manager);
templates.set_dependency(&winix_request);
templates.SetConfig(&config);
templates.SetCur(&cur);
templates.SetDb(&db);
templates.SetSystem(&system);
templates.SetFunctions(&functions);
templates.SetSessionManager(&session_manager);
session_manager.set_dependency(&winix_model); // zobaczyc czy wskoczy do przeciazonej metody w session manager
session_manager.SetLastContainer(&system.users.last);
session_manager.SetCur(&cur);
session_manager.SetSystem(&system);
post_multi_parser.set_dependency(&winix_base);
post_multi_parser.SetConfig(&config);
}
void App::InitLoggers()
{
file_log.init(config.log_file, config.log_stdout, config.log_level, config.log_save_each_line, config.log_time_zone_id);
log.SetMaxRequests(config.log_request);
}
Log & App::GetMainLog()
{
return log;
}
void App::InitPlugins()
{
plugin.LoadPlugins(config.plugins_dir, config.plugin_file);
}
bool App::InitFCGI(char * sock, char * sock_user, char * sock_group)
{
if( !wide_to_utf8(config.fcgi_socket, sock, WINIX_OS_PATH_SIZE) )
return false;
if( !wide_to_utf8(config.fcgi_socket_user, sock_user, WINIX_OS_USERNAME_SIZE) )
return false;
if( !wide_to_utf8(config.fcgi_socket_group, sock_group, WINIX_OS_USERNAME_SIZE) )
return false;
return true;
}
/*
* chmod and chown of the socket are set before winix drops privileges
*/
bool App::InitFCGIChmodChownSocket(char * sock, char * sock_user, char * sock_group)
{
if( chmod(sock, config.fcgi_socket_chmod) < 0 )
{
log << log1 << "App: I cannot chmod a FastCGI socket, check fcgi_socket_chmod in the config" << logend;
return false;
}
passwd * pw = getpwnam(sock_user);
if( !pw )
{
log << log1 << "App: there is no a user: " << config.fcgi_socket_user << logend;
return false;
}
group * gr = getgrnam(sock_group);
if( !gr )
{
log << log1 << "App: there is no a group: " << config.fcgi_socket_group << logend;
return false;
}
if( chown(sock, pw->pw_uid, gr->gr_gid) < 0 )
{
log << log1 << "App: I cannot chown a FastCGI socket, check fcgi_socket_user "
<< "and fcgi_socket_group in the config" << logend;
return false;
}
return true;
}
bool App::InitFCGI()
{
char sock[WINIX_OS_PATH_SIZE];
char sock_user[WINIX_OS_USERNAME_SIZE];
char sock_group[WINIX_OS_USERNAME_SIZE];
if( !InitFCGI(sock, sock_user, sock_group) )
return false;
unlink(sock);
fcgi_socket = FCGX_OpenSocket(sock, config.fcgi_socket_listen);
if( fcgi_socket < 0 )
{
log << log1 << "App: An error during creating a fcgi socket" << logend;
return false;
}
log << log3 << "App: FastCGI socket number: " << fcgi_socket << logend;
if( !InitFCGIChmodChownSocket(sock, sock_user, sock_group) )
return false;
if( FCGX_Init() != 0 )
{
log << log1 << "App: FCGX_Init fails" << logend;
return false;
}
if( FCGX_InitRequest(&fcgi_request, fcgi_socket, FCGI_FAIL_ACCEPT_ON_INTR) != 0 )
{
log << log1 << "App: FCGX_InitRequest fails" << logend;
return false;
}
return true;
}
bool App::DoDatabaseMigration()
{
bool ok = true;
Migration migration;
User user;
ItemContent item_content;
Item item;
Group group;
ok = ok && Migration::do_migration(&model_connector, migration);
ok = ok && Migration::do_migration(&model_connector, user);
/*
* do migration of ItemContent before Item
* Item::do_migration_to_3() requires that ItemContent should have new columns
*
*/
ok = ok && Migration::do_migration(&model_connector, item_content);
ok = ok && Migration::do_migration(&model_connector, item);
ok = ok && Migration::do_migration(&model_connector, group);
return ok;
}
bool App::TryToMakeDatabaseMigration()
{
if( config.db_make_migration_if_needed )
{
if( !DoDatabaseMigration() )
{
if( config.db_stop_if_migration_fails )
{
log << log1 << "App: database migration failed, stopping winix" << logend;
return false;
}
}
PluginRes res = plugin.Call(nullptr, WINIX_MAKE_DATABASE_MIGRATION);
if( config.db_stop_if_migration_fails && res.res_false > 0 )
{
log << log1 << "App: database migration of some plugins failed, stopping winix" << logend;
return false;
}
}
return true;
}
bool App::Init()
{
postgresql_connector.set_conn_param(config.db_database, config.db_user, config.db_pass);
postgresql_connector.set_logger(log);
postgresql_connector.set_log_queries(config.log_db_query);
postgresql_connector.wait_for_connection();
model_connector.set_flat_connector(json_connector);
model_connector.set_db_connector(postgresql_connector);
model_connector.set_logger(log);
model_connector.set_winix_config(&config);
model_connector.set_winix_request(&req);
model_connector.set_winix_logger(&log);
model_connector.set_winix_dirs(&system.dirs);
model_connector.set_winix_mounts(&system.mounts);
model_connector.set_winix_users(&system.users);
model_connector.set_winix_groups(&system.groups);
model_connector.set_winix_session_logger(nullptr); // will be set for each request
model_connector.set_winix_session(nullptr); // will be set for each request
model_connector.set_winix_locale(&TemplatesFunctions::locale);
model_connector.set_winix_session_manager(&session_manager);
model_connector.set_winix_time_zones(&system.time_zones);
model_connector.set_winix_pattern_cacher(&TemplatesFunctions::pattern_cacher);
if( !TryToMakeDatabaseMigration() )
return false;
db_conn.SetConnParam(config.db_database, config.db_user, config.db_pass);
db_conn.WaitForConnection();
db.LogQueries(config.log_db_query);
cur.request->Clear();
compress.set_dependency(&winix_base);
compress.Init();
system.Init();
functions.Init();
templates.Init(); // init templates after functions are created
// init notify after templates (it uses locales from templates)
system.notify.ReadTemplates();
added: possibility to encode the session cookie (added files core/sessionidmanager.h and core/sessionidmanager.cpp) added: config options: // whether or not we should encode the session cookie // (we have a special algorithm) // default: false bool session_cookie_encode; // if session_cookie_encode is true then you should provide // a file where AES keys will be stored std::wstring session_keys_file; // each session has an index -- an unsigned int value // this value is sent in the cookie string (is encoded) // and is incremented when session_index_time_increment time is passed since the last incrementing // if a client sent the cookie back the difference between // current index and the index in the cookie should be less than or equal to session_allow_index_difference // default: 8 size_t session_allow_index_difference; // the time which should pass after the session index is incremented // default: 30 // (session_allow_index_difference + 1) * session_index_time_increment should be less than a time // load of a page and all elements on it such as images (of course it depends on client's download too) time_t session_index_time_increment; // time in seconds after a new AES key pair should be generated // we have 256 pairs of keys so this time multiplied by 256 should not be less than // the max time of a session (session_remember_max_idle), // by default: 256 * 2 days = 512 days = 1.4 year > 3 months (session_remember_max_idle) // default: 172800 = 2 days (max: 2678400 = 1 month, min: 10) size_t session_key_renew_time; changed: when printing the time of a request we print only two non-zero digits git-svn-id: svn://ttmath.org/publicrep/winix/trunk@994 e52654a7-88a9-db11-a3e9-0013d4bc506e
2014-11-22 16:30:56 +01:00
session_manager.InitTmpSession();
added: possibility to encode the session cookie (added files core/sessionidmanager.h and core/sessionidmanager.cpp) added: config options: // whether or not we should encode the session cookie // (we have a special algorithm) // default: false bool session_cookie_encode; // if session_cookie_encode is true then you should provide // a file where AES keys will be stored std::wstring session_keys_file; // each session has an index -- an unsigned int value // this value is sent in the cookie string (is encoded) // and is incremented when session_index_time_increment time is passed since the last incrementing // if a client sent the cookie back the difference between // current index and the index in the cookie should be less than or equal to session_allow_index_difference // default: 8 size_t session_allow_index_difference; // the time which should pass after the session index is incremented // default: 30 // (session_allow_index_difference + 1) * session_index_time_increment should be less than a time // load of a page and all elements on it such as images (of course it depends on client's download too) time_t session_index_time_increment; // time in seconds after a new AES key pair should be generated // we have 256 pairs of keys so this time multiplied by 256 should not be less than // the max time of a session (session_remember_max_idle), // by default: 256 * 2 days = 512 days = 1.4 year > 3 months (session_remember_max_idle) // default: 172800 = 2 days (max: 2678400 = 1 month, min: 10) size_t session_key_renew_time; changed: when printing the time of a request we print only two non-zero digits git-svn-id: svn://ttmath.org/publicrep/winix/trunk@994 e52654a7-88a9-db11-a3e9-0013d4bc506e
2014-11-22 16:30:56 +01:00
session_manager.InitBanList();
session_manager.InitCookieEncoding();
session_manager.LoadSessions();
CreateStaticTree();
post_parser.set_dependency(&winix_model);
post_parser.LogValueSize(config.log_post_value_size);
// post_multi_parser has a pointer to the config
cookie_parser.set_dependency(&winix_model);
accept_encoding_parser.set_dependency(&winix_base);
plugin.Call((Session*)0, WINIX_PLUGIN_INIT);
return true;
}
void App::Close()
{
{
Winix::Lock lock(synchro);
plugin.Call((Winix::Session*)0, WINIX_CLOSE);
session_manager.SaveSessions();
session_manager.DeleteSessions();
cur.request->Clear();
session_manager.UninitTmpSession();
functions.Finish();
// now all sessions are cleared
}
WaitForThreads();
// now all others threads are terminated
}
void App::BaseUrlRedirect(int code, bool add_subdomain)
{
system.PutUrlProto(cur.request->redirect_to);
if( add_subdomain && !cur.request->subdomain.empty() )
{
cur.request->redirect_to += cur.request->subdomain;
cur.request->redirect_to += '.';
}
cur.request->redirect_to += config.base_url;
cur.request->redirect_to += cur.request->env_request_uri;
// cur.request->env_request_uri should not be UrlEncoded because it contains slashes
cur.request->redirect_type = code;
}
bool App::BaseUrlRedirect()
{
plugin.Call((Session*)0, WINIX_BASE_URL_REDIRECT);
if( !cur.request->redirect_to.empty() )
return true;
if( !config.base_url_redirect )
return false;
if( config.base_url.empty() )
return false;
if( cur.request->method == Request::post )
return false;
if( config.base_url == cur.request->env_http_host )
return false;
BaseUrlRedirect(config.base_url_redirect_code, false);
log << log3 << "App: BaseUrlRedirect from: " << cur.request->env_http_host << logend;
return true;
}
void App::CheckIfNeedSSLredirect()
{
if( cur.request->method == Request::post )
{
// something comes via POST, don't do the redirect because you lose the date
return;
}
if( config.use_ssl )
{
if( config.use_ssl_only_for_logged_users )
{
bool function_need_ssl = (cur.request->function && cur.request->function->need_ssl);
if( cur.request->using_ssl )
{
if( !cur.session->puser && !function_need_ssl )
{
log << log3 << "App: this operation should NOT be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
else
{
if( cur.session->puser || function_need_ssl )
{
log << log3 << "App: this operation should be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
else
{
/*
* use ssl for everyone
*/
if( !cur.request->using_ssl )
{
log << log3 << "App: this operation should be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
else
{
if( cur.request->using_ssl )
{
log << log3 << "App: this operation should NOT be used through SSL" << logend;
BaseUrlRedirect(config.use_ssl_redirect_code, true);
}
}
}
void App::SetLocale()
{
size_t locale_id;
if( cur.session->puser )
{
locale_id = cur.session->puser->locale_id;
if( !TemplatesFunctions::locale.HasLanguage(locale_id) )
locale_id = config.locale_default_id;
}
else
{
locale_id = config.locale_default_id;
}
TemplatesFunctions::locale.SetCurLang(locale_id);
}
bool App::CheckAccessFromPlugins()
{
PluginRes res = plugin.Call(WINIX_CHECK_PLUGIN_ACCESS);
if( res.res_false > 0 )
{
cur.request->status = WINIX_ERR_PERMISSION_DENIED;
log << log2 << "App: access prevented by a plugin" << logend;
return false;
}
return true;
}
void App::ProcessRequestThrow()
{
ReadRequest();
// when BaseUrlRedirect() return true we didn't have to set everything in cur.request->Read()
// in the future cur.request->Read() can be split and at the beginning only environment variables will be read
// and then BaseUrlRedirect() will be called (for performance)
if( !BaseUrlRedirect() )
{
if( cur.request->env_request_uri.size() <= WINIX_URL_MAX_SIZE )
{
functions.Parse(); // parsing directories, files, functions and parameters
if( cur.request->function )
{
cur.request->function->fun.set_connector(model_connector); // IMPROVEME may would be better to add set_connector() method to functions?
cur.request->function->fun.propagate_connector();
}
/*
* set global connector for now
* in the future each thread will have its own model_connector
*
* don't set connector for item_tab - it will be moved out from request
*/
cur.request->item.set_connector(model_connector);
if( !cur.request->dir_tab.empty() )
{
cur.mount = system.mounts.CalcCurMount();
cur.session = session_manager.PrepareSession();
model_connector.set_winix_session(cur.session);
functions.CheckFunctionAndSymlink(); // here a function can be changed
if( cur.request->function )
{
cur.request->function->fun.set_connector(model_connector);
cur.request->function->fun.propagate_connector();
}
cur.session = session_manager.CheckIfFunctionRequireSession();
model_connector.set_winix_session(cur.session);
SetLocale();
if( cur.session->new_session )
{
cur.session->plugin_data.Resize(plugin.Size());
plugin.Call(WINIX_SESSION_CREATED);
}
plugin.Call(WINIX_SESSION_CHANGED);
}
}
else
{
/*
* IMPROVE ME
* it will not have the root directory set
* so as a response only a blank page is shown
* (root directory is set in funcions.Parse())
*
* IMPROVE ME
* we can add a better return code (http status):
* http://www.ietf.org/rfc/rfc2616.txt
* "A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle"
*
*/
cur.request->status = WINIX_ERR_PERMISSION_DENIED;
log << log1 << "App: the URL is too long: " << cur.request->env_request_uri.size() << logend;
}
if( cur.request->dir_tab.empty() )
{
log << log1 << "App: there is no a root dir (dir_tab is empty), adding a root dir" << logend;
Item * root_dir = system.dirs.GetRootDir();
if( root_dir )
{
cur.request->dir_tab.push_back(root_dir);
cur.request->last_item = cur.request->dir_tab.back();
cur.mount = system.mounts.CalcCurMount();
}
else
{
log << log1 << "App: oops, we do not have a root dir" << logend;
}
}