/* * This file is a part of Winix * and is not publicly distributed * * Copyright (c) 2010-2014, Tomasz Sowa * All rights reserved. * */ #include #include #include #include #include #include #include #include #include #include "app.h" #include "plugin.h" #include "misc.h" #include "functions/functions.h" #include "utf8/utf8.h" namespace Winix { App::App() { stdout_is_closed = false; last_sessions_save = std::time(0); fcgi_socket = -1; // temporary there is only one request cur.request = &req; cur.session = session_manager.GetTmpSession(); cur.mount = system.mounts.GetEmptyMount(); db.SetConn(db_conn); 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); req.SetConfig(&config); functions.SetConfig(&config); functions.SetCur(&cur); functions.SetDb(&db); functions.SetSystem(&system); functions.SetTemplates(&templates); functions.SetSynchro(&synchro); functions.SetSessionManager(&session_manager); system.SetConfig(&config); system.SetCur(&cur); system.SetDb(&db); system.SetSynchro(&synchro); system.SetFunctions(&functions); system.SetSessionManager(&session_manager); templates.SetConfig(&config); templates.SetCur(&cur); templates.SetDb(&db); templates.SetSystem(&system); templates.SetFunctions(&functions); templates.SetSessionManager(&session_manager); session_manager.SetLastContainer(&system.users.last); session_manager.SetConfig(&config); session_manager.SetCur(&cur); session_manager.SetSystem(&system); session_manager.SetSynchro(&synchro); post_multi_parser.SetConfig(&config); slog.SetCur(&cur); slog.SetLocale(&TemplatesFunctions::locale); } bool App::InitFCGI() { const char * sock = config.fcgi_socket.c_str(); unlink(sock); fcgi_socket = FCGX_OpenSocket(sock, 100); // !! dodac 100 do konfiga 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; chmod(sock, config.fcgi_socket_chmod); passwd * pw = getpwnam(config.fcgi_socket_user.c_str()); if( !pw ) { log << log1 << "App: there is no user: " << config.fcgi_socket_user << logend; return false; } group * gr = getgrnam(config.fcgi_socket_group.c_str()); if( !gr ) { log << log1 << "App: there is no group: " << config.fcgi_socket_group << logend; return false; } chown(sock, pw->pw_uid, gr->gr_gid); 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::Init() { 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.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(); session_manager.InitBanList(); session_manager.InitTmpSession(); session_manager.LoadSessions(); CreateStaticTree(); post_parser.UTF8(config.utf8); post_parser.LogValueSize(config.log_post_value_size); // post_multi_parser has a pointer to the config cookie_parser.UTF8(config.utf8); plugin.Call((Session*)0, WINIX_PLUGIN_INIT); return true; } void App::Close() { session_manager.SaveSessions(); session_manager.DeleteSessions(); cur.request->Clear(); session_manager.UninitTmpSession(); } void App::BaseUrlRedirect(int code, bool add_subdomain) { system.PutUrlProto(config.use_ssl, 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; AssignString(cur.request->env_request_uri, cur.request->redirect_to, false); // 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( Equal(config.base_url.c_str(), 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( !cur.request->using_ssl ) { if( !config.use_ssl_only_for_logged_users || cur.session->puser || (cur.request->function && cur.request->function->need_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() ) { session_manager.SetSession(); cur.session = session_manager.GetCurSession(); SetLocale(); if( cur.session->new_session ) { cur.session->plugin_data.Resize(plugin.Size()); plugin.Call(WINIX_SESSION_CREATED); } plugin.Call(WINIX_SESSION_CHANGED); functions.Parse(); // parsing directories,files,functions and parameters cur.mount = system.mounts.CalcCurMount(); if( cur.mount->type != system.mounts.MountTypeStatic() ) Make(); } SendAnswer(); } void App::ProcessRequest() { try { cur.request->RequestStarts(); system.load_avg.StartRequest(); log << log2 << config.log_delimiter << logend; ProcessRequestThrow(); SaveSessionsIfNeeded(); // !! IMPROVE ME move to the session's thread system.load_avg.StopRequest(); } catch(const std::exception & e) { log << log1 << "App: there was an exception: std::exception: " << e.what() << logend; } catch(const Error & e) { log << log1 << "App: there was an exception: Error: " << e << logend; } catch(...) { log << log1 << "App: there was an unknown exception" << logend; } ClearAfterRequest(); } void App::ClearAfterRequest() { try { plugin.Call(WINIX_END_REQUEST); } catch(...) { log << log1 << "App: an exception from a plugin when clearing after a request" << logend; } try { // simple operations which should not throw an exception json_out_stream.Clear(); templates.ClearAfterRequest(); cur.request->Clear(); cur.session->ClearAfterRequest(); cur.session = session_manager.GetTmpSession(); output_8bit.clear(); compressed_output.clear(); html_filtered.clear(); aheader_name.clear(); aheader_value.clear(); // send_data_buf doesn't have to be cleared and it is better to not clear it (optimizing) log << logendrequest; } catch(...) { log << log1 << "App: an exception when clearing after a request" << logend; } } void App::Start() { while( !synchro.was_stop_signal && FCGX_Accept_r(&fcgi_request) == 0 ) { Lock(); if( synchro.was_stop_signal ) { FCGX_Finish_r(&fcgi_request); } else { ProcessRequest(); } Unlock(); } } void App::SaveSessionsIfNeeded() { time_t t = std::time(0); if( last_sessions_save + 86400 > t ) return; // saving once a day for safety last_sessions_save = t; session_manager.SaveSessions(); } void App::CreateJSONAnswer() { Request & req = *cur.request; json_out_stream.Clear(); if( !req.return_info_only ) { json_out_stream << L"{\n"; for(size_t i=1 ; iSerialize(req.info, json_out_stream, true); } else { json_out_stream << L"{}"; log << log1 << "App: Request::info_serializer not defined" << logend; } log << log3 << "App: sending JSON answer"; if( !req.return_info_only ) json_out_stream << L"}\n"; else log << " (Request::info only)"; log << logend; } // !! zmienic na lepsza nazwe void App::MakePage() { bool sent = false; if( cur.request->page_generated || !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() ) return; templates.SetEzcParameters( cur.request->gen_trim_white, cur.request->gen_skip_new_line, cur.request->gen_use_special_chars); if( cur.request->is_item && cur.request->item.file_type == WINIX_ITEM_FILETYPE_NONE && cur.request->item.content_type == Item::ct_raw && cur.request->status == WINIX_ERR_OK && cur.request->function ) { if( cur.request->function == &functions.fun_cat ) { cur.request->out_streams[0] << cur.request->item.content; // !! CHECK ME is it ok? sent = true; } else if( cur.request->function == &functions.fun_run ) { templates.GenerateRunRaw(); sent = true; } } if( !sent ) { templates.Generate(); } } void App::CheckPostRedirect() { if( cur.request->method == Request::post ) { if( cur.request->IsParam(L"postredirect") ) { cur.request->redirect_to = cur.request->ParamValue(L"postredirect"); cur.request->redirect_type = 303; } else if( cur.request->IsPostVar(L"postredirect") ) { cur.request->redirect_to = cur.request->PostVar(L"postredirect"); cur.request->redirect_type = 303; } } } // zmienic nazwe np na ProcessRequest // !! ta nazwa chyba juz zajeta... // !! IMPROVE ME need some refactoring void App::Make() { if( cur.request->dir_tab.empty() ) { log << log1 << "App: there is no a root dir (dir_tab is empty)" << logend; return; } if( cur.request->ParamValue(L"reqtype") == L"json" ) cur.request->return_json = true; if( cur.session->ip_ban && cur.session->ip_ban->IsIPBanned() ) { PT::Date date(cur.session->ip_ban->expires); log << log2 << "App: this IP address is banned until to: " << date << " UTC" << logend; slog << logerror << T("this_ip_is_banned_until") << ' ' << date << " UTC" << logend; cur.request->status = WINIX_ERR_PERMISSION_DENIED; } // cur.request->status can be changed by function_parser if( cur.request->status == WINIX_ERR_OK ) plugin.Call(WINIX_PREPARE_REQUEST); if( cur.request->status == WINIX_ERR_OK ) functions.CheckFunctionAndSymlink(); CheckAccessFromPlugins(); // !! CHECK ME CheckFunctionAndSymlink can set redirect_to // may it should be tested before calling CheckIfNeedSSLredirect? CheckIfNeedSSLredirect(); if( !cur.request->redirect_to.empty() ) return; if( cur.request->status == WINIX_ERR_OK ) functions.MakeFunction(); if( cur.session->spam_score > 0 ) log << log1 << "App: spam score: " << cur.session->spam_score << logend; if( cur.request->IsParam(L"noredirect") ) cur.request->redirect_to.clear(); if( cur.request->status == WINIX_ERR_OK ) plugin.Call(WINIX_PROCESS_REQUEST); CheckPostRedirect(); if( !cur.request->redirect_to.empty() ) return; if( cur.request->dir_tab.empty() ) { log << log1 << "App: there is no a root dir (dir_tab is empty -- after calling a function)" << logend; return; } if( !cur.request->info_serializer ) { json_generic_serializer.Clear(); // !! IMPROVE ME add to the end of a request cur.request->info_serializer = &json_generic_serializer; } plugin.Call(WINIX_CONTENT_MAKE); MakePage(); } void App::LogEnvironmentVariables() { for(char ** e = fcgi_request.envp ; *e ; ++e ) log << log1 << "Env: " << *e << logend; } void App::ReadRequest() { ReadEnvVariables(); CheckRequestMethod(); CheckSSL(); SetSubdomain(); LogAccess(); ReadGetPostVars(); cookie_parser.Parse(cur.request->env_http_cookie, cur.request->cookie_tab); accept_encoding_parser.ParseAndLog(cur.request->env_http_accept_encoding); if( config.log_env_variables ) LogEnvironmentVariables(); CheckIE(); CheckKonqueror(); if( cur.request->using_ssl ) log << log3 << "App: connection secure through SSL" << logend; } void App::SetEnv(const char * & env, const char * name) { const char * v = FCGX_GetParam(name, fcgi_request.envp); if( v ) env = v; // by default env is set to an empty string (in cur.request->Clear() method) } void App::ReadEnvVariables() { // we store that values because FCGX_GetParam has O(n) complexity // with this variables (env_*) we have O(1) SetEnv(cur.request->env_request_method, "REQUEST_METHOD"); // !! mozna nie uzywac tego, teraz mamy w strukturze fcgi_request SetEnv(cur.request->env_request_uri, "REQUEST_URI"); SetEnv(cur.request->env_http_cookie, "HTTP_COOKIE"); SetEnv(cur.request->env_remote_addr, "REMOTE_ADDR"); SetEnv(cur.request->env_http_host, "HTTP_HOST"); SetEnv(cur.request->env_http_user_agent, "HTTP_USER_AGENT"); SetEnv(cur.request->env_fcgi_role, "FCGI_ROLE"); SetEnv(cur.request->env_content_type, "CONTENT_TYPE"); SetEnv(cur.request->env_http_accept_encoding, "HTTP_ACCEPT_ENCODING"); SetEnv(cur.request->env_https, "HTTPS"); cur.request->ip = (int)inet_addr(cur.request->env_remote_addr); } void App::CheckRequestMethod() { cur.request->method = Request::unknown_method; if( ToSmall(cur.request->env_request_method[0]) == 'g' ) cur.request->method = Request::get; else if( ToSmall(cur.request->env_request_method[0]) == 'p' ) cur.request->method = Request::post; else if( ToSmall(cur.request->env_request_method[0]) == 'h' ) cur.request->method = Request::head; } void App::CheckSSL() { // !! CHECK ME // value "on" exists in lighttpd server // make sure that for other servers is "on" too if( EqualNoCase(cur.request->env_https, "on") ) cur.request->using_ssl = true; } void App::SetSubdomain() { CreateSubdomain(config.base_url.c_str(), cur.request->env_http_host, cur.request->subdomain); } void App::LogAccess() { log << log1; log.PrintDate(cur.request->start_date, config.log_time_zone_id); log << ' ' << cur.request->env_remote_addr << ' ' << cur.request->env_request_method << ' ' << cur.request->env_http_host << cur.request->env_request_uri << ' ' << cur.request->env_http_user_agent << logend; if( !cur.request->subdomain.empty() ) log << log3 << "Subdomain: " << cur.request->subdomain << logend; } void App::ReadGetPostVars() { // get parameters we have always //get_parser.Parse(cur.request->env_request_uri, cur.request->get_tab); if( cur.request->method == Request::post ) { if( IsSubStringNoCase("multipart/form-data", cur.request->env_content_type) ) { log << log3 << "App: post content type: multipart/form-data" << logend; post_multi_parser.Parse(fcgi_request.in, cur.request->post_tab, cur.request->post_file_tab); } else { post_parser.Parse(fcgi_request.in, cur.request->post_tab); } } } void App::CheckIE() { const char * msie = strstr(cur.request->env_http_user_agent, "MSIE"); if( msie ) cur.request->browser_msie = true; else cur.request->browser_msie = false; } void App::CheckKonqueror() { const char * kon = strstr(cur.request->env_http_user_agent, "Konqueror"); if( kon ) cur.request->browser_konqueror = true; else cur.request->browser_konqueror = false; } void App::PrepareSessionCookie() { if( !cur.session || cur.session->id==0 ) return; if( !cur.session->puser || !cur.session->remember_me ) { cur.request->AddCookie(config.http_session_id_name, cur.session->id); } else { PT::Date expires = cur.request->start_time + config.session_remember_max_idle; cur.request->AddCookie(config.http_session_id_name, cur.session->id, expires); } } bool App::AddHeader(const wchar_t * name, const wchar_t * value) { if( !cur.request->out_headers.GetValue(name) ) { cur.request->out_headers.Add(name, value); return true; } return false; } bool App::AddHeader(const std::wstring & name, const std::wstring & value) { if( !cur.request->out_headers.GetValue(name) ) { cur.request->out_headers.Add(name, value); return true; } return false; } bool App::AddHeader(const wchar_t * name, const PT::WTextStream & value) { if( !cur.request->out_headers.GetValue(name) ) { cur.request->out_headers.Add(name, value); return true; } return false; } bool App::AddHeader(const std::wstring & name, const PT::WTextStream & value) { if( !cur.request->out_headers.GetValue(name) ) { cur.request->out_headers.Add(name, value); return true; } return false; } bool App::PrepareHeadersStaticCreateResource(PT::WTextStream & out_path) { size_t i = 0; Item * dir = system.dirs.GetDir(system.mounts.pmount->dir_id); if( !dir ) { log << log1 << "App: cannot find the mount directory" << logend; return false; } size_t how_many_dirs = system.dirs.DirLevel(dir->id); const char * path = SkipDirs(cur.request->env_request_uri, how_many_dirs); // the path begins with a slash only if how_many_dirs is zero while( *path == '/' ) path += 1; while( path[i]!=0 && path[i]!='?' && path[i]!='#' ) ++i; if( i > 0 ) out_path.write(path, i); return true; } void App::PrepareHeadersStatic() { if( PathHasUpDir(cur.request->env_request_uri) ) { log << log1 << "App: incorrect path for a static file" << logend; PrepareHeadersForbidden(); return; } const std::wstring & index_str = system.mounts.pmount->FirstArg(system.mounts.MountParStatic()); size_t index = Toi(index_str); if( index >= config.static_dirs.size() ) { log << log1 << "App: static dir with index " << index << " is not defined in the config" << logend; PrepareHeadersForbidden(); return; } PT::WTextStream path; path << config.static_dirs[index] << L"/"; if( !PrepareHeadersStaticCreateResource(path) ) { PrepareHeadersForbidden(); return; } AddHeader(L"Status", L"200 OK"); if( AddHeader(config.http_header_send_file, path) ) log << log2 << "App: sending a file from a static mountpoint: " << path << logend; } void App::PrepareHeaderContentType() { std::wstring * value = 0; if( !cur.request->out_headers.GetValue(L"Content-Type") ) { if( cur.request->return_json ) { value = &cur.request->out_headers.Add(L"Content-Type", L"application/json"); } else { switch( config.content_type_header ) { case 1: value = &cur.request->out_headers.Add(L"Content-Type", L"application/xhtml+xml"); break; case 2: value = &cur.request->out_headers.Add(L"Content-Type", L"application/xml"); break; case 0: default: value = &cur.request->out_headers.Add(L"Content-Type", L"text/html"); } } if( value && config.utf8 ) *value += L"; charset=UTF-8"; } } void App::PrepareHeadersForbidden() { AddHeader(L"Status", L"403 Forbidden"); PrepareHeaderContentType(); } void App::PrepareHeadersRedirect() { switch(cur.request->redirect_type) { case 300: AddHeader(L"Status", L"300 Multiple Choices"); break; case 301: AddHeader(L"Status", L"301 Moved Permanently"); break; case 302: AddHeader(L"Status", L"302 Found"); break; case 307: AddHeader(L"Status", L"307 Temporary Redirect"); break; case 303: default: AddHeader(L"Status", L"303 See Other"); break; } AddHeader(L"Location", cur.request->redirect_to); log << log2 << "App: redirect to: " << cur.request->redirect_to << logend; } void App::PrepareHeadersSendFile() { AddHeader(L"Status", L"200 OK"); if( AddHeader(config.http_header_send_file, cur.request->x_sendfile) ) log << log2 << "App: sending file: " << cur.request->x_sendfile << logend; } void App::PrepareHeadersCompression(int compress_encoding) { if( compress_encoding == 0 || compress_encoding == 1 ) AddHeader(L"Content-Encoding", L"deflate"); else AddHeader(L"Content-Encoding", L"gzip"); } void App::PrepareHeadersNormal(Header header, size_t output_size) { switch( header ) { case h_404: AddHeader(L"Status", L"404 Not Found"); PrepareHeaderContentType(); break; case h_403: PrepareHeadersForbidden(); break; default: AddHeader(L"Status", L"200 OK"); PrepareHeaderContentType(); } if( output_size != static_cast(-1) ) { PT::WTextStream buf; buf << output_size; AddHeader(L"Content-Length", buf); } } // we can improve SendHeaders and SendCookies methods by checking // whether there is a new line character in either a name or a value // and if such character exists and is being sent to the client it breaks the http headers and content // and if compression is enabled the client's browser will not be able to decompress the stream void App::SendHeaders() { PT::Space::TableSingle::iterator i; PT::Space & headers = cur.request->out_headers; plugin.Call(WINIX_PREPARE_TO_SEND_HTTP_HEADERS, &headers); for(i=headers.table_single.begin() ; i != headers.table_single.end() ; ++i) { PT::WideToUTF8(i->first, aheader_name); PT::WideToUTF8(i->second, aheader_value); FCGX_PutS(aheader_name.c_str(), fcgi_request.out); FCGX_PutS(": ", fcgi_request.out); FCGX_PutS(aheader_value.c_str(), fcgi_request.out); FCGX_PutS("\r\n", fcgi_request.out); if( config.log_http_answer_headers ) log << "HTTP Header: " << aheader_name << ": " << aheader_value << logend; } } void App::SendCookies() { PT::Space::TableSingle::iterator i; PT::Space & cookies = cur.request->out_cookies; plugin.Call(WINIX_PREPARE_TO_SEND_HTTP_COOKIES, &cookies); for(i=cookies.table_single.begin() ; i != cookies.table_single.end() ; ++i) { PT::WideToUTF8(i->first, aheader_name); PT::WideToUTF8(i->second, aheader_value); FCGX_PutS("Set-Cookie: ", fcgi_request.out); FCGX_PutS(aheader_name.c_str(), fcgi_request.out); FCGX_PutS("=", fcgi_request.out); FCGX_PutS(aheader_value.c_str(), fcgi_request.out); FCGX_PutS("\r\n", fcgi_request.out); if( config.log_http_answer_headers ) log << "HTTP Header: " << "Set-Cookie: " << aheader_name << "=" << aheader_value << logend; } } void App::PrepareHeaders(bool compressing, int compress_encoding, Header header, size_t output_size) { PrepareSessionCookie(); if( cur.request->send_as_attachment ) AddHeader(L"Content-Disposition", L"attachment"); if( !cur.request->redirect_to.empty() ) { PrepareHeadersRedirect(); } else if( system.mounts.pmount->type == system.mounts.MountTypeStatic() ) { PrepareHeadersStatic(); } else if( !cur.request->x_sendfile.empty() ) { PrepareHeadersSendFile(); } else { PrepareHeadersNormal(header, output_size); } if( compressing ) PrepareHeadersCompression(compress_encoding); } void App::FilterContent() { Request & req = *cur.request; bool raw = req.is_item && req.item.content_type == Item::ct_raw && req.status == WINIX_ERR_OK && req.function && (req.function == &functions.fun_cat || req.function == &functions.fun_run); size_t start = req.out_streams.size(); // default nothing should be filtered size_t end = req.out_streams.size(); if( config.html_filter && !req.send_bin_stream && !raw ) { if( req.return_json ) { if( !req.return_info_only ) { start = 1; } } else { start = 0; end = 1; } } for(size_t i=start ; ibrowser_msie ) return 0; // raw deflate else return 1; // deflate } void App::SelectCompression(size_t source_len, bool & compression_allowed, int & compression_encoding) { compression_allowed = false; compression_encoding = 0; if( config.compression && cur.request->redirect_to.empty() && cur.request->x_sendfile.empty() && !cur.request->browser_konqueror && /* !! sprawdzic czy Konqueror bedzie obslugiwal raw deflate */ source_len >= config.compression_page_min_size ) { if( config.compression_encoding == 1 || config.compression_encoding == 10 ) { if( accept_encoding_parser.AcceptDeflate() ) { compression_allowed = true; compression_encoding = SelectDeflateVersion(); } else if( config.compression_encoding == 10 && accept_encoding_parser.AcceptGzip() ) { compression_allowed = true; compression_encoding = 2; // gzip } } if( config.compression_encoding == 2 || config.compression_encoding == 20 ) { if( accept_encoding_parser.AcceptGzip() ) { compression_allowed = true; compression_encoding = 2; // gzip } else if( config.compression_encoding == 20 && accept_encoding_parser.AcceptDeflate() ) { compression_allowed = true; compression_encoding = SelectDeflateVersion(); } } } } bool App::CanSendContent() { if( !cur.request->redirect_to.empty() || !cur.request->x_sendfile.empty() ) // if there is a redirect or a file to send then we do not send a content return false; /* we don't have to check the HEAD method the server (lighttpd) doesn't send the body of its own */ if( cur.request->method == Request::head ) return false; return true; } App::Header App::GetHTTPStatusCode() { Error status = cur.request->status; Header header = h_200; if( status == WINIX_ERR_NO_ITEM || status == WINIX_ERR_NO_FUNCTION || status == WINIX_ERR_UNKNOWN_PARAM ) { header = h_404; log << log2 << "App: http response: 404 Not Found" << logend; } if( status == WINIX_ERR_PERMISSION_DENIED || status == WINIX_ERR_CANT_CHANGE_USER || status == WINIX_ERR_CANT_CHANGE_GROUP ) { header = h_403; log << log2 << "App: http response: 403 Forbidden" << logend; } if( cur.request->use_200_status_for_not_found_and_permission_denied && (header == h_404 || header == h_403) ) { log << log3 << "App: changing the http response to: 200 OK" << logend; header = h_200; } return header; } void App::SendTextAnswer() { const std::wstring * source; bool compressing = false; int compress_encoding = 0; size_t output_size = 0; Header header = GetHTTPStatusCode(); if( CanSendContent() ) { FilterContent(); if( cur.request->return_json ) { CreateJSONAnswer(); source = &json_out_stream.Str(); // json_out_stream was prepared by CreateJSONAnswer() } else { source = &cur.request->out_streams[0].Str(); } SelectCompression(source->length(), compressing, compress_encoding); if( config.utf8 ) PT::WideToUTF8(*source, output_8bit); else AssignString(*source, output_8bit); // !! IMPROVE ME add to log the binary stream as well if( config.log_server_answer ) log << log1 << "App: the server's answer is:\n" << output_8bit << "\nApp: end of the server's answer" << logend; if( compressing ) { compress.Compressing(output_8bit.c_str(), output_8bit.length(), compressed_output, compress_encoding); output_size = compressed_output.size(); } else { output_size = output_8bit.size(); } } PrepareHeaders(compressing, compress_encoding, header, output_size); SendHeaders(); SendCookies(); FCGX_PutS("\r\n", fcgi_request.out); if( CanSendContent() ) { if( compressing ) SendData(compressed_output, fcgi_request.out); else FCGX_PutStr(output_8bit.c_str(), output_8bit.size(), fcgi_request.out); } } void App::SendData(const BinaryPage & page, FCGX_Stream * out) { const size_t buf_size = 4096; if( send_data_buf.size() != buf_size ) send_data_buf.resize(buf_size); BinaryPage::const_iterator i = page.begin(); BinaryPage::const_iterator end = page.end(); while( i != end ) { size_t s = 0; for( ; i != end && s < buf_size ; ++i, ++s) send_data_buf[s] = *i; if( s > 0 ) FCGX_PutStr(send_data_buf.c_str(), s, out); } } void App::SendBinaryAnswer() { BinaryPage & source = cur.request->out_bin_stream; Header header = h_200; Error status = cur.request->status; bool compressing; int compress_encoding; SelectCompression(source.size(), compressing, compress_encoding); if( status == WINIX_ERR_NO_ITEM || status == WINIX_ERR_NO_FUNCTION || status == WINIX_ERR_UNKNOWN_PARAM ) header = h_404; if( status == WINIX_ERR_PERMISSION_DENIED || status == WINIX_ERR_CANT_CHANGE_USER || status == WINIX_ERR_CANT_CHANGE_GROUP ) header = h_403; // !! IMPROVE ME add header: content-length (size from Item struct) // warning: if someone changed a file on the disk (in the real os) // then winix would send an incorrect content-lenght header, // we are waiting for the fsck winix function to be implemented PrepareHeaders(compressing, compress_encoding, header, static_cast(-1)); SendHeaders(); SendCookies(); FCGX_PutS("\r\n", fcgi_request.out); if( CanSendContent() ) { if( compressing ) { compress.Compressing(source, compressed_output, compress_encoding); SendData(compressed_output, fcgi_request.out); } else { SendData(source, fcgi_request.out); } } } void App::SendAnswer() { if( cur.request->send_bin_stream ) SendBinaryAnswer(); else SendTextAnswer(); } void App::LogUser(const char * msg, uid_t id) { log << log3 << msg << " "; passwd * p = getpwuid(id); if( p ) log << p->pw_name; else log << (int)id; log << logend; } void App::LogGroup(const char * msg, gid_t id, bool put_logend) { log << log3 << msg << " "; group * g = getgrgid(id); if( g ) log << g->gr_name; else log << (int)id; if( put_logend ) log << logend; } void App::LogUsers() { uid_t eid, rid; eid = geteuid(); rid = getuid(); if( eid == rid ) { LogUser("App: effective/real user:", eid); } else { LogUser("App: effective user:", eid); LogUser("App: real user:", rid); } } void App::LogEffectiveGroups(std::vector & tab) { log << log3 << "App: effective groups:"; for(size_t i=0 ; i tab; gid_t rgid; int len; rgid = getgid(); len = getgroups(0, 0); if( len <= 0 ) { log << log3 << "App: I can't read how many groups there are" << logend; return; } tab.resize(len); len = getgroups(len, &(tab[0])); if( len == -1 ) { log << log3 << "App: I can't read groups" << logend; return; } if( len == 1 && rgid == tab[0] ) { LogGroup("App: effective/real group:", rgid); } else { tab.resize(len); LogEffectiveGroups(tab); LogGroup("App: real group:", rgid); } } void App::LogUserGroups() { LogUsers(); LogGroups(); } bool App::DropPrivileges(const std::string & user, uid_t uid, gid_t gid, bool additional_groups) { if( additional_groups ) { if( initgroups(user.c_str(), gid) < 0 ) { log << log1 << "App: I can't init groups for user: " << user << logend; return false; } } else { if( setgroups(1, &gid) < 0 ) { log << log1 << "App: I can't init groups for user: " << user << logend; return false; } } // for setting real and saved gid too if( setgid(gid) ) { log << log1 << "App: I can't change real and saved gid" << logend; return false; } if( setuid(uid) < 0 ) { log << log1 << "App: I can't drop privileges to user: " << user << " (uid:" << (int)uid << ")" << logend; return false; } if( getuid()==0 || geteuid()==0 ) { log << log1 << "App: sorry, for security reasons you should not run me as the root" << logend; return false; } return true; } bool App::DropPrivileges() { if( getuid()!=0 && geteuid()!=0 ) return true; log << log2 << "App: dropping privileges" << logend; if( config.user.empty() ) { log << log1 << "App: you should specify user name in the config file " << "to which I have to drop privileges" << logend; return false; } if( config.group.empty() ) { log << log1 << "App: you should specify group name in the config file " << "to which I have to drop privileges" << logend; return false; } passwd * p = getpwnam(config.user.c_str()); group * g = getgrnam(config.group.c_str()); if( !p ) { log << log1 << "App: there is no such a user as: \"" << config.user << "\"" << logend; return false; } if( !g ) { log << log1 << "App: there is no such a group as: \"" << config.group << "\"" << logend; return false; } if( !DropPrivileges(config.user, p->pw_uid, g->gr_gid, config.additional_groups) ) return false; return true; } bool App::Demonize() { // in linux fork() should be used twice int pid = fork(); if( pid == -1 ) { log << log1 << "App: I can't demonize myself (fork problem)" << logend; return false; } if( pid != 0 ) { // parent exit(0); } // child if( setsid() == -1 ) { log << log1 << "App: I can't demonize myself (setsid problem)" << logend; return false; } return true; } // this method is called from a signal routine void App::SetStopSignal() { synchro.was_stop_signal = true; } bool App::WasStopSignal() { return synchro.was_stop_signal; } bool App::Lock() { return synchro.Lock(); } void App::Unlock() { synchro.Unlock(); } void App::WaitForThreads() { pthread_join(signal_thread, 0); system.thread_manager.StopAll(); } void App::FetchPageOnExit() { // stupid trick to break FCGX_Accept_r() function // even with FCGX_InitRequest(..., ..., FCGI_FAIL_ACCEPT_ON_INTR) the FCGX_Accept_r // doesn't want to break on a signal // so we request one page from the server for exiting from FCGX_Accept_r Lock(); CURL * curl = curl_easy_init(); Unlock(); if( curl ) { curl_easy_setopt(curl, CURLOPT_URL, url_to_fetch_on_exit.c_str()); curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_perform(curl); curl_easy_cleanup(curl); } } void * App::SpecialThreadForSignals(void * app_object) { sigset_t set; int sig; App * app = reinterpret_cast(app_object); sigemptyset(&set); sigaddset(&set, SIGTERM); sigaddset(&set, SIGINT); // waiting for SIGTERM or SIGINT sigwait(&set, &sig); app->Lock(); app->synchro.was_stop_signal = true; FCGX_ShutdownPending(); // here we don't have to use SSL version so we always use config.url_proto PT::WideToUTF8(app->config.url_proto, app->url_to_fetch_on_exit); PT::WideToUTF8(app->config.base_url, app->url_to_fetch_on_exit, false); app->Unlock(); app->FetchPageOnExit(); pthread_exit(0); return 0; } void App::StartThreads() { // make sure system.thread_manager.Init() was called beforehand // it is called in Init() -> system.Init() // special thread only for signals pthread_create(&signal_thread, 0, SpecialThreadForSignals, this); system.thread_manager.Add(&session_manager, L"session_manager"); system.thread_manager.StartAll(); } void App::CreateStaticTree() { if( config.upload_dir.empty() ) { log << log1 << "App: config: upload_dir not set, you are not allowed to upload static content" << logend; return; } CreateDirs(L"/", config.upload_dir.c_str(), config.upload_dirs_chmod); CreateDirs(config.upload_dir.c_str(), L"simplefs/normal", config.upload_dirs_chmod); CreateDirs(config.upload_dir.c_str(), L"simplefs/thumb", config.upload_dirs_chmod); CreateDirs(config.upload_dir.c_str(), L"hashfs/normal", config.upload_dirs_chmod); CreateDirs(config.upload_dir.c_str(), L"hashfs/thumb", config.upload_dirs_chmod); CreateDirs(config.upload_dir.c_str(), L"tmp", config.upload_dirs_chmod); } } // namespace Winix