/* * 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 #include #include #include "request.h" #include "getparser.h" #include "postparser.h" #include "cookieparser.h" #include "log.h" #include "data.h" #include "plugin.h" #include "misc.h" #include "db.h" Request::Request() : char_empty(0) { id = 0; Clear(); } void Request::Init() { compress.Init(); } void Request::ClearPostFileTmp() { // deleting temporary files (if exists) while( !post_file_table.empty() ) { const std::string & tmp_filename = post_file_table.begin()->second.tmp_filename; if( unlink(tmp_filename.c_str()) == 0 ) log << log3 << "Request: deleted tmp file: " << tmp_filename << logend; post_file_table.erase(post_file_table.begin()); } } void Request::Clear() { // warning: don't clear: in, out, err, env // id is never 0 if( ++id == 0 ) ++id; get_table.clear(); post_table.clear(); post_file_table.clear(); cookie_table.clear(); method = none; role = responder; headers.str(""); page.str(""); debug.str(""); notify.str(""); env_request_method = &char_empty; env_request_uri = &char_empty; env_http_cookie = &char_empty; env_remote_addr = &char_empty; env_http_host = &char_empty; env_http_user_agent = &char_empty; env_http_accept_encoding = &char_empty; env_fcgi_role = &char_empty; env_content_type = &char_empty; session = 0; item_table.clear(); item.Clear(); dir_table.clear(); is_item = false; pfunction = 0; param_table.clear(); status = Error::ok; is_thread = false; thread.Clear(); thread_tab.clear(); is_ticket = false; ticket.Clear(); ticket_tab.clear(); notify_code = 0; browser_msie = false; redirect_to.clear(); x_sendfile.clear(); send_as_attachment = false; plugin.Call(WINIX_REQUEST_CLEAR); } // value can be null void Request::SetCookie(const char * name, const char * value, tm * expires) { headers << "Set-Cookie: " << name << "="; if( value && value[0]!=0 ) headers << value; else headers << "\"\""; if( expires ) headers << "; expires=" << DateToStrCookie(expires) << " GMT"; headers << "; path=/; domain=." << data.base_server << "\r\n"; } void Request::SetCookie(const char * name, long value, tm * expires) { headers << "Set-Cookie: " << name << "=" << value; if( expires ) headers << "; expires=" << DateToStrCookie(expires) << " GMT"; headers << "; path=/; domain=." << data.base_server << "\r\n"; } bool Request::IsPostVar(const char * var) { PostTable::iterator p; p = post_table.find(var); if( p == post_table.end() ) return false; return true; } std::string * Request::PostVar(const char * var) { PostTable::iterator p = post_table.find(var); if( p == post_table.end() ) return 0; return &(p->second); } bool Request::PostVar(const char * var, std::string & result) { PostTable::iterator p = post_table.find(var); if( p == post_table.end() ) { result.clear(); return false; } result = p->second; return true; } // void Request::PrintGetTable() { debug << "get_table: " << get_table.size() << "\n"; for(GetTable::iterator i = get_table.begin() ; i != get_table.end() ; ++i) debug << " \"" << *i << "\"\n"; debug << std::endl; } void Request::PrintEnv() { char ** e; debug << "environment variables:\n"; for( e = env ; *e ; ++e ) debug << ' ' << *e << "\n"; debug << std::endl; } void Request::PrintIn() { char buf[100]; int buf_len = sizeof(buf) / sizeof(char); int len; debug << "fcgi input:\n"; do { len = FCGX_GetStr(buf, buf_len - 1, in); if( len != 0 ) { buf[len] = 0; debug << buf; } } while( len == buf_len - 1 ); debug << std::endl; } const char * Request::SetEnvVar(const char * var) { const char * v = FCGX_GetParam(var, env); if( v ) return v; // char_empty contains '\0' return &char_empty; } void Request::ReadEnvVariables() { // we store that values because FCGX_GetParam has O(n) complexity // with this variables (env_*) we have O(1) env_request_method = SetEnvVar("REQUEST_METHOD"); env_request_uri = SetEnvVar("REQUEST_URI"); env_http_cookie = SetEnvVar("HTTP_COOKIE"); env_remote_addr = SetEnvVar("REMOTE_ADDR"); env_http_host = SetEnvVar("HTTP_HOST"); env_http_user_agent = SetEnvVar("HTTP_USER_AGENT"); env_http_accept_encoding = SetEnvVar("HTTP_ACCEPT_ENCODING"); env_fcgi_role = SetEnvVar("FCGI_ROLE"); env_content_type = SetEnvVar("CONTENT_TYPE"); } void Request::CheckIE() { char * msie = strstr(env_http_user_agent, "MSIE"); if( msie ) browser_msie = true; else browser_msie = false; } void Request::CheckKonqueror() { char * kon = strstr(env_http_user_agent, "Konqueror"); if( kon ) browser_konqueror = true; else browser_konqueror = false; } void Request::CheckMethod() { method = none; if( ToSmall(env_request_method[0]) == 'g' ) method = get; else if( ToSmall(env_request_method[0]) == 'p' ) method = post; // default we assume 'responder' role = responder; if( ToSmall(env_fcgi_role[0]) == 'a' ) role = authorizer; } bool Request::AllPostVarEmpty() { PostTable::iterator i; for(i=post_table.begin() ; i!=post_table.end() ; ++i) if( !i->second.empty() ) return false; return true; } void Request::ReadParameters() { // !! wrzucic jako skladowa klasy GetParser get_parser(env_request_uri, get_table); get_parser.Parse(); if( method == post ) { if( IsSubStringNoCase("multipart/form-data", env_content_type) ) { log << log3 << "Request: post content type: multipart/form-data" << logend; post_multi_parser.Parse(in, post_table, post_file_table); } else { // !! wrzucic jako skladowa klasy PostParser post_parser(in, post_table); post_parser.Parse(); } } CookieParser cookie_parser(env_http_cookie, cookie_table); cookie_parser.Parse(); accept_encoding_parser.Parse(env_http_accept_encoding); } void Request::StandardLog() { log.PutDate(log1); log << env_remote_addr << ' ' << env_request_method << ' '; log << env_http_host << env_request_uri << ' ' << env_http_user_agent << logend; } // reading everything void Request::Read() { ReadEnvVariables(); CheckMethod(); StandardLog(); ReadParameters(); CheckIE(); if( role == authorizer ) log << log3 << "Request: fast cgi role: authorizer" << logend; CheckKonqueror(); } 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_idle; 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::SendHeaders(bool compressing, Header header) { if( send_as_attachment ) FCGX_PutS("Content-Disposition: attachment\r\n", out); if( !redirect_to.empty() ) { FCGX_PutS("Status: 301 Moved Permanently\r\n", out); FCGX_FPrintF(out, "Location: %s\r\n", redirect_to.c_str()); log << log2 << "Redirect to: " << redirect_to << logend; } else if( !x_sendfile.empty() ) { FCGX_FPrintF(out, "X-LIGHTTPD-send-file: %s\r\n", x_sendfile.c_str()); FCGX_PutS("Status: 200 OK\r\n", out); log << log2 << "Sending file: " << x_sendfile << logend; } else { switch(header) { case h_404: FCGX_PutS("Status: 404 Not Found\r\n", out); FCGX_PutS("Content-Type: text/html\r\n", out); log << log2 << "Request: response: 404 Not Found" << logend; break; case h_403: FCGX_PutS("Status: 403 Forbidden\r\n", out); FCGX_PutS("Content-Type: text/html\r\n", out); log << log2 << "Request: response: 403 Forbidden" << logend; break; default: FCGX_PutS("Status: 200 OK\r\n", out); if( role != authorizer ) FCGX_PutS("Content-Type: text/html\r\n", out); } } if( compressing ) FCGX_PutS("Content-Encoding: deflate\r\n", out); FCGX_PutS(headers.str().c_str(), out); FCGX_PutS("\r\n", out); } void Request::AddDebugInfo() { const std::string & d = debug.str(); if( !d.empty() ) { page << "\n\n"; } } void Request::SendPage(bool compressing, const std::string & source_ref) { const std::string * source = &source_ref; bool raw = request.is_item && request.item.content_type == Item::ct_raw && request.status == Error::ok && request.pfunction && (request.pfunction->code == FUN_CAT || request.pfunction->code == FUN_RUN); if( data.html_filter && !raw ) { html_filter.TrimWhite(true); html_filter.BreakLongLines(true); html_filter.InsertTabs(2); html_filter.Filter(*source, clean_html); source = &clean_html; } if( compressing ) compress.CompressAndPut(source->c_str(), source->length(), out); else FCGX_PutS(source->c_str(), out); } void Request::SendAll() { const std::string & source = page.str(); Header header = h_200; bool compressing = data.compression && role == responder && redirect_to.empty() && x_sendfile.empty() && !browser_msie && !browser_konqueror && accept_encoding_parser.AcceptDeflate() && source.size() >= 512; if( status == Error::no_item || status == Error::no_function || status == Error::unknown_param ) header = h_404; if( status == Error::permission_denied || status == Error::cant_change_user || status == Error::cant_change_group ) header = h_403; SendSessionCookie(); SendHeaders(compressing, header); if( !redirect_to.empty() || !x_sendfile.empty() ) // if there is a redirect or a file to send then we do not send a content return; if( header == h_200 && role == authorizer && is_item && item.static_auth != Item::static_none ) // if there is an item and the item has 'file' storage we do not send a content return; // adding debug info if exists AddDebugInfo(); // sending content SendPage(compressing, source); } bool Request::IsParam(const char * s) { std::vector::iterator i; for(i=param_table.begin() ; i!=param_table.end() ; ++i) { if( **i == s ) return true; } return false; } bool Request::CanChangeUser(const Item & item, long new_user_id) { if( !session ) // session must be set return false; if( session->puser && session->puser->super_user ) // super user is allowed everything return true; if( item.user_id != new_user_id ) // only super user can change the owner of an item return false; return true; } bool Request::CanChangeGroup(const Item & item, long new_group_id) { if( !session ) // session must be set return false; if( session->puser && 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( !session->puser ) return false; if( session->puser->id != item.user_id ) return false; if( new_group_id == -1 ) return true; if( !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 Request::CanChangePrivileges(const Item & item, int new_priv) { if( !session ) // session must be set return false; if( session->puser && 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( !session->puser ) return false; if( session->puser->id != item.user_id ) return false; } return true; } bool Request::HasAccess(const Item & item, int mask) { if( !session ) // session must be set return false; if( session->puser && session->puser->super_user ) // super user is allowed everything return true; if( session->puser && session->puser->id == item.user_id ) { // the owner return ((item.privileges >> 6) & mask) == mask; } if( session->puser && session->puser->IsMemberOf(item.group_id) ) { // group return ((item.privileges >> 3) & mask) == mask; } // others return (item.privileges & mask) == mask; } bool Request::HasReadAccess(const Item & item) { return HasAccess(item, 4); } bool Request::HasWriteAccess(const Item & item) { return HasAccess(item, 2); } bool Request::HasReadWriteAccess(const Item & item) { return HasAccess(item, 6); // r+w } bool Request::HasReadExecAccess(const Item & item) { if( session && session->puser && session->puser->super_user ) { // there must be at least one 'x' (for the root) return (item.privileges & 0111) != 0; } return HasAccess(item, 5); // r+x } bool Request::HasReadExecAccessForRoot(const Item & item) { // there must be at least one 'x' (for the root) return (item.privileges & 0111) != 0; } // returning true if we can create a thread in the current directory bool Request::CanCreateThread(bool check_root) { if( request.dir_table.empty() ) return false; if( request.is_item ) return false; if( !HasWriteAccess(*request.dir_table.back()) ) return false; if( !data.mounts.pmount || data.mounts.pmount->type != Mount::thread ) return false; if( !check_root && session && session->puser && session->puser->super_user ) // super can create thread regardless of the restrictcreatethread option return true; if( !data.mounts.pmount->IsPar(Mount::par_createthread_on) ) return true; if( data.mounts.pmount->IsArg(Mount::par_createthread_on, request.dir_table.size()) ) return true; return false; } // returning true if we can create a ticket in the current directory bool Request::CanCreateTicket(bool check_root) { if( request.dir_table.empty() ) return false; if( request.is_item ) return false; if( !HasWriteAccess(*request.dir_table.back()) ) return false; if( !data.mounts.pmount || data.mounts.pmount->type != Mount::ticket ) return false; // checking for par_createticket_on mount option if( !check_root && session && session->puser && session->puser->super_user ) // super can create tickets regardless of the createticket_on option return true; if( !data.mounts.pmount->IsPar(Mount::par_createticket_on) ) return true; if( data.mounts.pmount->IsArg(Mount::par_createticket_on, request.dir_table.size()) ) return true; return false; } bool Request::CanEditTicket() { // not logged users cannot edit tickets if( !request.session->puser ) return false; if( request.dir_table.empty() ) return false; if( request.is_item || !request.is_ticket ) return false; if( !HasWriteAccess(*request.dir_table.back()) ) return false; if( !data.mounts.pmount || data.mounts.pmount->type != Mount::ticket ) return false; return true; } bool Request::CanRemove(const Item & item) { // !! temporarily (we're waiting for the sticky bit to be implemented) // not logged users cannot remove anything if( !request.session->puser ) return false; if( item.parent_id == -1 ) { // rm for the root dir // only the superuser can do it if( !request.session->puser || !request.session->puser->super_user ) return false; } else { Item * last_but_one_dir = data.dirs.GetDir(item.parent_id); if( !last_but_one_dir ) // ops, there is no a parent dir return false; if( !request.HasWriteAccess(*last_but_one_dir) ) return false; } if( data.mounts.pmount->IsPar(Mount::par_only_root_remove) ) if( !request.session->puser || !request.session->puser->super_user ) return false; return true; } bool Request::CanUseEmacs(const Item & item, bool check_root) { if( !check_root && request.session->puser && request.session->puser->super_user ) // super user can use emacs everywhere return true; if( !request.HasWriteAccess(item) ) return false; if( !data.mounts.pmount->IsPar(Mount::par_emacs_on) ) return true; if( data.mounts.pmount->IsArg(Mount::par_emacs_on, request.dir_table.size()) ) return true; return false; } bool Request::CanUseMkdir(const Item & item, bool check_root) { // you can create a directory only in a directory if( item.type != Item::dir ) return false; if( !check_root && request.session->puser && request.session->puser->super_user ) // super user can use mkdir everywhere return true; if( !request.HasWriteAccess(item) ) return false; if( !data.mounts.pmount->IsPar(Mount::par_mkdir_on) ) return true; if( data.mounts.pmount->IsArg(Mount::par_mkdir_on, request.dir_table.size()) ) return true; return false; } bool Request::CanUseUpload(const Item & item, bool check_root) { // you can use 'upload' only in a directory if( item.type != Item::dir ) return false; // we must know where to store the file if( !data.mounts.pmount ) return false; if( data.mounts.pmount->fs == Mount::simplefs && data.static_simplefs_dir.empty() ) { log << log1 << "Request: can't use upload function, static_simplefs_dir must be set in the config file" << logend; return false; } if( data.mounts.pmount->fs == Mount::hashfs && data.static_hashfs_dir.empty() ) { log << log1 << "Request: can't use upload function, static_hashfs_dir must be set in the config file" << logend; return false; } if( data.static_tmp_dir.empty() ) { log << log1 << "Request: can't use upload function, static_tmp_dir must be set in the config file" << logend; return false; } if( !check_root && request.session->puser && request.session->puser->super_user ) // super user can use upload everywhere return true; if( !request.HasWriteAccess(item) ) return false; return true; } bool Request::CanUseHtml(long user_id) { return CanUse(user_id, "allow_html"); } bool Request::CanUseBBCode(long user_id) { return CanUse(user_id, "allow_bbcode"); } bool Request::CanUseRaw(long user_id) { return CanUse(user_id, "allow_raw"); } bool Request::CanUse(long user_id, const char * group_name) { User * puser = data.users.GetUser(user_id); if( !puser ) return false; if( puser->super_user ) return true; long group = data.groups.GetGroupId(group_name); if( group == -1 ) // there is no such a group return false; if( puser->IsMemberOf(group) ) return true; return false; } bool Request::MakePathSimpleFs(std::string & path, bool create_dir) { size_t i; path = data.static_simplefs_dir; if( path.empty() ) { log << log1 << "Request: static_simplefs_dir is not set in the config file" << logend; return false; } // skipping the first - the first is root for(i=1 ; iurl; if( create_dir && !CreateDir(path, 0755) ) return false; } path += '/'; return true; } // the path depends on id bool Request::MakePathHashFs(std::string & path, long id, bool create_dir) { char buffer[50]; char * hash = buffer; // get 'id' as hexadecimal buffer[0] = '0'; sprintf(buffer+1, "%lx", (unsigned long)id); path = data.static_hashfs_dir; if( path.empty() ) { log << log1 << "Request: static_hashfs_dir is not set in the config file" << logend; return false; } path += '/'; // make sure that the length is even if( (strlen(hash) & 1) != 0 ) hash = buffer + 1; // the first character was our zero // creating dirs without the last part // the last part is a part of a file (not a directory) for(size_t i=0 ; hash[i] != 0 ; i+=2) { path += hash[i]; path += hash[i+1]; if( hash[i+2] != 0 ) { if( create_dir && !CreateDir(path, 0755) ) return false; path += '/'; } } // one character more to make sure the path is unique // (we can have a directory without the character) path += "_"; return true; } // making a complete path to a request.item static file bool Request::MakePath(std::string & path, bool create_dir) { bool res; if( data.mounts.pmount->fs == Mount::hashfs ) { res = MakePathHashFs(path, request.item.id, create_dir); } else { res = MakePathSimpleFs(path, create_dir); } if( res ) path += request.item.url; else path.clear(); return res; }