/* * This file is a part of Winix * and is not publicly distributed * * Copyright (c) 2010-2012, Tomasz Sowa * All rights reserved. * */ #include "system.h" #include "misc.h" #include "error.h" #include "templates/templates.h" #include "functions/functions.h" #include "plugin.h" void System::SetCur(Cur * pcur) { cur = pcur; } void System::SetConfig(Config * pconfig) { config = pconfig; } void System::SetDb(Db * pdb) { db = pdb; } void System::SetSynchro(Synchro * psynchro) { synchro = psynchro; } void System::SetFunctions(Functions * pfunctions) { functions = pfunctions; } void System::SetSessionManager(SessionManager * sm) { session_manager = sm; } void System::Init() { thread_manager.SetSynchro(synchro); thread_manager.Init(); dirs.SetDb(db); dirs.SetCur(cur); dirs.SetNotify(¬ify); dirs.ReadDirs(); mounts.SkipStaticDirs(config->dont_use_static_dirs); mounts.SetDirs(&dirs); mounts.SetDb(db); mounts.SetCur(cur); mounts.CreateMounts(); mounts.ReadMounts(); users.SetCur(cur); users.SetSessionManager(session_manager); users.ReadUsers(db); users.SetTimeZoneOffset(config->time_zone_offset); groups.ReadGroups(db); // !! chwilowe przekazanie argumentu, db bedzie zmienione rebus.SetCur(cur); rebus.Init(); notify.SetCur(cur); notify.SetConfig(config); notify.SetUsers(&users); notify.SetDirs(&dirs); notify.SetThreadManager(&thread_manager); notify.Init(); image.SetDb(db); image.SetConfig(config); image.SetSystem(this); thread_manager.Add(&image); crypt.SetConfig(config); } void System::PutUrlProto(bool can_use_ssl, std::wstring & str, bool clear_str) { bool ssl = false; if( clear_str ) str.clear(); if( can_use_ssl ) { if( !config->use_ssl_only_for_logged_users || cur->session->puser || cur->request->function == &functions->fun_login || cur->request->function == &functions->fun_adduser) { str += config->url_ssl_proto; ssl = true; } } if( !ssl ) str += config->url_proto; } // !! IMPROVE ME // !! mozna zrobic jakas obsluge kiedy nie mozemy sie redirectnac, np gdy wystapil blad // !! moze zwracac jakas wartosc? /* postfix will not be UrlEncoded */ void System::RedirectTo(const Item & item, const wchar_t * postfix) { PutUrlProto(config->use_ssl, cur->request->redirect_to); if( !cur->request->subdomain.empty() ) { cur->request->redirect_to += cur->request->subdomain; cur->request->redirect_to += '.'; } cur->request->redirect_to += config->base_url; if( item.type == Item::dir ) { // item_id is pointing to a directory dirs.MakePath(item.id, cur->request->redirect_to, false); } else { // item_id is pointing to a file or a symlink if( dirs.MakePath(item.parent_id, cur->request->redirect_to, false) ) cur->request->redirect_to += item.url; } if( postfix ) cur->request->redirect_to += postfix; } /* postfix will not be UrlEncoded */ void System::RedirectTo(long item_id, const wchar_t * postfix) { PutUrlProto(config->use_ssl, cur->request->redirect_to); if( !cur->request->subdomain.empty() ) { cur->request->redirect_to += cur->request->subdomain; cur->request->redirect_to += '.'; } cur->request->redirect_to += config->base_url; Item * pdir = dirs.GetDir(item_id); if( pdir ) { // item_id is pointing to a directory dirs.MakePath(pdir->id, cur->request->redirect_to, false); } else { // item_id is pointing to a file DbItemQuery iq; iq.SetAllSel(false); iq.WhereId(item_id); iq.sel_parent_id = true; iq.sel_url = true; if( db->GetItem(item_temp, iq) == WINIX_ERR_OK ) { if( dirs.MakePath(item_temp.parent_id, cur->request->redirect_to, false) ) cur->request->redirect_to += item_temp.url; } else { log << log1 << "System: can't redirect: no such item: id: " << item_id << logend; } } if( postfix ) cur->request->redirect_to += postfix; } /* url will not be UrlEncoded */ void System::RedirectTo(const wchar_t * url) { PutUrlProto(config->use_ssl, cur->request->redirect_to); if( !cur->request->subdomain.empty() ) { cur->request->redirect_to += cur->request->subdomain; cur->request->redirect_to += '.'; } cur->request->redirect_to += config->base_url; if( url[0] == '/' ) { // absolute path cur->request->redirect_to += url; } else { // relative path if( !cur->request->dir_tab.empty() ) { if( dirs.MakePath(cur->request->dir_tab.back()->id, cur->request->redirect_to, false) ) cur->request->redirect_to += url; } else { cur->request->redirect_to += '/'; cur->request->redirect_to += url; } } } /* url will not be UrlEncoded */ void System::RedirectTo(const std::wstring & url) { RedirectTo(url.c_str()); } /* params will be UrlEncoded */ void System::AddParams(const ParamTab & param_tab, std::wstring & str, bool clear_str) { if( clear_str ) str.clear(); for(size_t i=0 ; irequest->function ) return; cur->request->redirect_to += '/'; cur->request->redirect_to += cur->request->function->fun.url; AddParams(cur->request->param_tab, cur->request->redirect_to, false); } /* url will not be UrlEncoded params will be UrlEncoded */ void System::RedirectWithFunctionAndParamsTo(const std::wstring & url) { RedirectWithFunctionAndParamsTo(url.c_str()); } void System::RedirectToLastDir() { if( !cur->request->dir_tab.empty() ) RedirectTo( *cur->request->dir_tab.back() ); } void System::RedirectToLastItem() { if( cur->request->last_item ) RedirectTo( *cur->request->last_item ); } bool System::CanChangeUser(const Item & item, long new_user_id) { if( !cur->session ) // session must be set return false; if( cur->session->puser && cur->session->puser->super_user ) // super user is allowed everything return true; if( item.user_id == -1 || new_user_id == -1 || item.user_id != new_user_id ) // only super user can change the owner of an item return false; // item.user_id is equal new_user_id -- we return true return true; } bool System::CanChangeGroup(const Item & item, long new_group_id) { if( !cur->session ) // session must be set return false; if( cur->session->puser && cur->session->puser->super_user ) // super user is allowed everything return true; if( item.group_id != new_group_id ) { // user is allowed to change the group only if he is an owner of the item // he can change only into a group in which he is a member of, or into a 'no_group' if( !cur->session->puser || cur->session->puser->id == -1 ) return false; if( item.user_id == -1 || cur->session->puser->id != item.user_id ) return false; if( new_group_id == -1 ) return true; if( !cur->session->puser->IsMemberOf(new_group_id) ) return false; // is logged, is the owner of the item, is the member of the new group } return true; } bool System::CanChangePrivileges(const Item & item, int new_priv) { if( !cur->session ) // session must be set return false; if( cur->session->puser && cur->session->puser->super_user ) // super user is allowed everything return true; if( item.privileges != new_priv ) { // the owner of an item is allowed to change the privileges if( !cur->session->puser || cur->session->puser->id == -1 ) return false; if( item.user_id == -1 || cur->session->puser->id != item.user_id ) return false; } return true; } // private bool System::HasAccess(const Item & item, int mask) { if( !cur->session ) // session must be set return false; if( cur->session->puser && cur->session->puser->super_user ) // super user is allowed everything return true; if( cur->session->puser && item.user_id != -1 && cur->session->puser->id == item.user_id ) { // the owner return ((item.privileges >> 9) & mask) == mask; } if( cur->session->puser && item.group_id != -1 && cur->session->puser->IsMemberOf(item.group_id) ) { // group return ((item.privileges >> 6) & mask) == mask; } if( cur->session->puser ) { // others -- others logged people return ((item.privileges >> 3) & mask) == mask; } // guests -- not logged people return (item.privileges & mask) == mask; } bool System::HasReadAccess(const Item & item) { return HasAccess(item, 4); } bool System::HasWriteAccess(const Item & item) { return HasAccess(item, 2); } bool System::HasReadWriteAccess(const Item & item) { return HasAccess(item, 6); // r+w } bool System::HasReadExecAccess(const Item & item) { if( cur->session && cur->session->puser && cur->session->puser->super_user ) { // there must be at least one 'x' (for the root) // !! CHECK ME: is it applicable to directories too? return (item.privileges & 01111) != 0; } return HasAccess(item, 5); // r+x } bool System::HasReadExecAccessToPath(long dir_id) { while( true ) { Item * pdir = dirs.GetDir(dir_id); if( !pdir ) return false; if( !HasReadExecAccess(*pdir) ) return false; dir_id = pdir->parent_id; if( dir_id == -1 ) return true; } } bool System::HasReadExecAccessToPath(const std::vector & dir_tab) { for(size_t i=0 ; i < dir_tab.size() ; ++i) { if( !HasReadExecAccess(*dir_tab[i]) ) return false; } return true; } bool System::DirsHaveReadExecPerm() { return HasReadExecAccessToPath(cur->request->dir_tab); } // if we don't have access we only remove the item from the table // !! moze zamienic nazwe na CheckReadAccessToItems ? void System::CheckAccessToItems(std::vector & item_tab) { size_t i = 0; while( i < item_tab.size() ) { if( !HasReadAccess(item_tab[i]) ) { item_tab.erase(item_tab.begin() + i); } else { i += 1; } } } void System::CheckWriteAccessToItems(std::vector & item_tab) { size_t i = 0; while( i < item_tab.size() ) { if( !HasWriteAccess(item_tab[i]) ) { item_tab.erase(item_tab.begin() + i); } else { i += 1; } } } int System::NewPrivileges(int creation_mask) { if( cur && cur->session && cur->session->puser ) { int umask = cur->session->puser->env.Int(L"umask", config->umask); return (~umask) & creation_mask; } else { return (~config->umask) & creation_mask; } } /* from man sticky: A directory whose `sticky bit' is set becomes an append-only directory, or, more accurately, a directory in which the deletion of files is restricted. A file in a sticky directory may only be removed or renamed by a user if the user has write permission for the directory and the user is the owner of the file, the owner of the directory, or the super-user. This feature is usefully applied to directories such as /tmp which must be publicly writable but should deny users the license to arbitrarily delete or rename each others' files. */ bool System::CanRemoveRenameChild(const Item & dir, long child_item_user_id) { if( dir.type != Item::dir ) return false; if( !HasWriteAccess(dir) ) return false; if( (dir.privileges & 010000) == 0 ) // there is no a sticky bit set to this directory return true; if( cur->session->puser ) { if( cur->session->puser->super_user ) return true; if( dir.user_id != -1 && cur->session->puser->id != -1 && child_item_user_id != -1 ) { if( cur->session->puser->id == child_item_user_id || cur->session->puser->id == dir.user_id ) return true; } } return false; } int System::NewFilePrivileges() { return NewPrivileges(06666); } int System::NewDirPrivileges() { return NewPrivileges(07777); } bool System::CanUseHtml(long user_id) { return IsMemberOfGroup(user_id, L"allow_html"); } bool System::CanUseBBCode(long user_id) { // logged users can use bbcode return (user_id != -1); } bool System::CanUseRaw(long user_id) { return IsMemberOfGroup(user_id, L"allow_raw"); } bool System::IsMemberOfGroup(long user_id, const wchar_t * group_name) { User * puser = users.GetUser(user_id); if( !puser ) return false; if( puser->super_user ) return true; // !! ?? zakladamy ze administrator jest czlonkiem wszystkich grup? dlaczego? long group = groups.GetGroupId(group_name); if( group == -1 ) // there is no such a group return false; return puser->IsMemberOf(group); } // the path depends on parent_id bool System::CreateNewFileSimpleFs(Item & item) { bool res = dirs.MakePath(item.parent_id, item.file_path); if( res ) { if( !item.file_path.empty() && item.file_path[0] == '/' ) item.file_path.erase(0, 1); } else { log << log1 << "System: CreateNewFileSimpleFs: can't create a path to item.id: " << item.id << ", item.parent_id: " << item.parent_id << logend; } return res; } // the path depends on id bool System::CreateNewFileHashFs(Item & item) { wchar_t buffer[50]; wchar_t * hash = buffer; size_t buffer_len = sizeof(buffer)/sizeof(wchar_t); // get 'id' as hexadecimal buffer[0] = '0'; swprintf(buffer+1, buffer_len, L"%lx", (unsigned long)item.id); item.file_path.clear(); // make sure that the length is even if( (wcslen(hash) & 1) != 0 ) hash = buffer + 1; // the first character was zero for(size_t i=0 ; hash[i] != 0 ; i+=2) { item.file_path += hash[i]; item.file_path += hash[i+1]; if( hash[i+2] != 0 ) item.file_path += '/'; } // one character more to make sure the path is unique // (we can have a directory without the character) item.file_path += '_'; return true; } // creating item.file_path and item.file_fs // you should set file_type yourself // this method uses: item.id, item.url, item.parent_id (for selecting a mountpoint) bool System::CreateNewFile(Item & item) { bool res; if( item.type != Item::file ) { log << log1 << "System: CreateNewFile: the item should be a file" << logend; return false; } Mount * pmount = mounts.CalcMount(item.parent_id); if( !pmount || pmount->fs != mounts.MountFsHashfs() ) { res = CreateNewFileSimpleFs(item); item.file_fs = mounts.MountFsSimplefs(); } else { res = CreateNewFileHashFs(item); item.file_fs = mounts.MountFsHashfs(); } if( res ) item.file_path += item.url; else item.file_path.clear(); return res; } // making a global file path (in the unix file system) // you should call CreateNewFile before bool System::MakeFilePath(const Item & item, std::wstring & path, bool thumb, bool create_dir, int chmod) { path.clear(); if( config->upload_dir.empty() ) { log << log1 << "System: MakePath: upload_dir is not set in the config file" << logend; return false; } if( item.file_path.empty() || item.file_type == WINIX_ITEM_FILETYPE_NONE ) { log << log1 << "System: MakePath: this item has not a static file" << logend; return false; } path = config->upload_dir; if( item.file_fs == mounts.MountFsHashfs() ) path += L"/hashfs"; else path += L"/simplefs"; if( thumb ) path += L"/thumb"; else path += L"/normal"; if( create_dir && !CreateDirs(path, item.file_path, chmod, true) ) return false; path += '/'; path += item.file_path; return true; } // item can be a directory, file or a symlink // if item is a directory then the path will be with a slash at the end bool System::MakePath(const Item & item, std::wstring & path, bool clear_path) { bool res; if( clear_path ) path.clear(); if( item.type == Item::dir ) { res = dirs.MakePath(item.id, path); } else { res = dirs.MakePath(item.parent_id, path); if( res ) path += item.url; } return res; } Error System::AddFile(Item & item, int notify_code, bool call_plugins) { if( item.type != Item::file ) return WINIX_ERR_FILE_EXPECTED; Error status = db->AddItem(item); if( status == WINIX_ERR_OK ) { log << log2 << "System: added a new file, url: " << item.url << ", id: " << item.id << ", parent_id: " << item.parent_id << logend; if( notify_code ) notify.ItemChanged(notify_code, item); if( call_plugins ) plugin.Call(WINIX_FILE_ADDED, &item); } return status; } Error System::EditFile(Item & item, bool with_url, int notify_code, bool call_plugins) { if( item.type != Item::file ) return WINIX_ERR_FILE_EXPECTED; if( cur->session && cur->session->puser ) cur->request->item.modification_user_id = cur->session->puser->id; else cur->request->item.modification_user_id = -1; item.SetDateModifyToNow(); Error status = db->EditItemById(item, with_url); if( status == WINIX_ERR_OK ) { TemplatesFunctions::pattern_cacher.UpdatePattern(item); log << log2 << "System: modified an item" << logend; if( notify_code ) notify.ItemChanged(notify_code, item); if( call_plugins ) plugin.Call(WINIX_FILE_CHANGED, &item); } return status; } time_t System::LocalTime(time_t gmt_time) { int time_offset; if( cur->session && cur->session->puser ) time_offset = cur->session->puser->time_zone_offset; else time_offset = config->time_zone_offset_guest; return gmt_time + (time_t)time_offset; } tm System::LocalTime(const tm * ptm) { time_t t; tm rtm; t = Time(ptm); t = LocalTime(t); rtm = Time(t); return rtm; } tm System::LocalTime(const tm & ptm) { return LocalTime(&ptm); } /* return codes: ok: 0 - the link_to is a path to a directory (out_item skipped, out_dir_tab will not be empty) 1 - the link_to is a path to a file or a symlink (out_item is used, out_dir_tab will not be empty) error: 2 - incorrect link_to 3 - there is not a root dir 4 - current_dir_tab was empty current_dir_tab can be the same container as out_dir_tab link_to can be a relative path (without the first slash) */ int System::FollowLink(const std::vector & current_dir_tab, const std::wstring & link_to, std::vector & out_dir_tab, Item & out_item) { link_to_temp = link_to; // copy the link because it can be from out_item (and will be cleared) out_item.Clear(); int res = dirs.FollowLink(current_dir_tab, link_to_temp, out_dir_tab, name_temp); if( res == 1 ) { if( db->GetItem(out_dir_tab.back()->id, name_temp, out_item) == WINIX_ERR_OK ) return 1; else return 2; } return res; } bool System::FollowAllLinksDirFound(std::vector & out_dir_tab, bool follow_dir_default, bool stop_on_link_redirect, bool check_access) { log << log3 << "System: link to a directory: "; dirs.LogDir(out_dir_tab); log << logend; if( check_access && !HasReadExecAccessToPath(out_dir_tab) ) { log << log1 << "System: no access to the directory structure" << logend; return false; } if( !out_dir_tab.back()->link_to.empty() ) { if( follow_dir_default ) { if( !(stop_on_link_redirect && out_dir_tab.back()->link_redirect==1) ) link_to_temp = out_dir_tab.back()->link_to; } } return true; } bool System::FollowAllLinksFileOrSymlinkFound(std::vector & out_dir_tab, Item & out_item, bool stop_on_link_redirect, bool check_access) { if( out_item.type == Item::symlink ) log << log3 << "System: link to a symlink: "; else log << log3 << "System: link to a file: "; dirs.LogDir(out_dir_tab); log << out_item.url << logend; if( check_access && !HasReadExecAccessToPath(out_dir_tab) ) { log << log1 << "System: no access to the directory structure" << logend; return false; } if( check_access && !HasReadAccess(out_item) ) { log << log1 << "System: no read access to the file or symlink" << logend; return false; } if( out_item.type == Item::symlink ) { if( out_item.link_to.empty() ) { log << log1 << "System: symlink empty" << logend; return false; } else { if( !(stop_on_link_redirect && out_item.link_redirect==1) ) link_to_temp = out_item.link_to; } } return true; } /* return codes: ok: 0 - the link_to is a path to a directory (out_item skipped, out_dir_tab will not be empty) 1 - the link_to is a path to a file (out_item is used, out_dir_tab will not be empty) (link_to can be a path to a symlink if stop_on_link_redirect is true) error: 2 - incorrect link_to 3 - there is not a root dir 4 - current_dir_tab was empty 5 - limit of symlinks exceeded 6 - permission denied to a file/symlink or a directory (or a symlink is empty) current_dir_tab can be the same container as out_dir_tab link_to can be a relative path (without the first slash) if follow_dir_default is true then directories with 'default' flags are followed as well if stop_on_link_redirect is true then the method stops on a symbolic link (or a directory with 'default' flag set) where the redirection should be done, you should check then whether the out_item.back()->link_to is not empty (if the result was 0 ) or whether out_item.type is symlink (if the result was 1) to make the redirection */ int System::FollowAllLinks(const std::vector & current_dir_tab, const std::wstring & link_to, std::vector & out_dir_tab, Item & out_item, bool follow_dir_default, bool stop_on_link_redirect, bool check_access) { int res; size_t level = 0; link_to_temp = link_to; if( current_dir_tab.empty() ) return 4; do { if( ++level > config->symlinks_follow_max ) { log << log1 << "System: the number of maximum symlinks exceeded (" << config->symlinks_follow_max << ")" << logend; res = 5; } else { res = FollowLink(current_dir_tab, link_to_temp, out_dir_tab, out_item); link_to_temp.clear(); if( res == 0 ) { out_item.Clear(); res = FollowAllLinksDirFound(out_dir_tab, follow_dir_default, stop_on_link_redirect, check_access) ? 0 : 6; } else if( res == 1 ) { res = FollowAllLinksFileOrSymlinkFound(out_dir_tab, out_item, stop_on_link_redirect, check_access) ? 1 : 6; } else { log << log2 << "System: incorrect link: " << link_to << logend; } } } while( !link_to_temp.empty() ); return res; } // the same as FollowAllLinks but starts from the root directory // link_to must begin with a slash (or can be empty then the root directory is returned) int System::FollowAllLinks(const std::wstring & link_to, std::vector & out_dir_tab, Item & out_item, bool follow_dir_default, bool stop_on_link_redirect, bool check_access) { if( !link_to.empty() && link_to[0] != '/' ) return 2; if( root_follow_dir_tab.size() != 1 ) root_follow_dir_tab.resize(1); Item * root_dir = dirs.GetRootDir(); if( !root_dir ) return 3; root_follow_dir_tab[0] = root_dir; return FollowAllLinks(root_follow_dir_tab, link_to, out_dir_tab, out_item, follow_dir_default, stop_on_link_redirect, check_access); } // the same as FollowAllLinks but operates on cur->request->dir_tab and cur->request->item // and returns bool // the method is making a redirection if needed bool System::FollowAllLinks(const std::wstring & link_to, bool follow_dir_default, bool stop_on_link_redirect, bool check_access) { int res = FollowAllLinks(cur->request->dir_tab, link_to, temp_follow_dir_tab, temp_follow_item, follow_dir_default, stop_on_link_redirect, check_access); bool ok = (res == 0 || res == 1); if( ok ) { cur->request->dir_tab = temp_follow_dir_tab; if( res == 0 ) { cur->request->is_item = false; cur->request->item.Clear(); cur->request->last_item = cur->request->dir_tab.back(); if( !cur->request->dir_tab.back()->link_to.empty() ) RedirectTo(cur->request->dir_tab.back()->link_to); log << log3 << "System: current directory changed" << logend; } else { cur->request->is_item = true; cur->request->item = temp_follow_item; cur->request->last_item = &cur->request->item; if( cur->request->item.type == Item::symlink ) RedirectTo(cur->request->item.link_to); // cur->request->item.link_to is not empty log << log3 << "System: current directory changed and the new file loaded" << logend; } mounts.CalcCurMount(); } else { if( res == 5 || res == 6 ) cur->request->status = WINIX_ERR_PERMISSION_DENIED; else cur->request->status = WINIX_ERR_NO_ITEM; } return ok; } /* adding a new file to winix file_path - a name of a static file (from common directory) url - url of a file which will be inserted (in /var directory) current limitation: warning: the url is not prepared by PrepareUrl() (PrepareUrl is from functions) */ bool System::AddCommonFileToVar(const wchar_t * file_path, const wchar_t * url, bool overwrite_existing) { if( config->common_dir.empty() ) { log << log1 << "System: can't open a file from common directory, common_dir not set in the config" << logend; return false; } file_name = config->common_dir; file_name += '/'; file_name += file_path; if( !GetUTF8File(file_name, file_content) ) { log << log1 << "System: can't open a file: " << file_name << logend; return false; } Item * var = dirs.CreateVarDir(); if( !var ) { log << log1 << "System: can't create /var directory" << logend; return false; } if( db->GetItem(var->id, url, file_content_item) == WINIX_ERR_OK ) { if( overwrite_existing ) db->DelItem(file_content_item); else return true; } file_content_item.Clear(); file_content_item.parent_id = var->id; file_content_item.user_id = var->user_id; file_content_item.group_id = var->group_id; file_content_item.privileges = 07555; // !! IMPROVE ME: may it should be added as a parameter to this function? file_content_item.subject = url; file_content_item.url = url; file_content_item.type = Item::file; file_content_item.content_type = Item::ct_raw; file_content_item.content = file_content; return AddFile(file_content_item, false) == WINIX_ERR_OK; }