#include // for rand #include #include "xcaptcha.hpp" #include "tcgi_sub.hpp" #include "tcgi_rpl.hpp" #include "tcgi_ses.hpp" #include "makeargv.hpp" #include "roles.hpp" #include "fnchecks.h" #include "tcgi_db.hpp" #ifndef THALASSA_DEFAULT_USERDATA_DIR #define THALASSA_DEFAULT_USERDATA_DIR "/var/local/thalassa" #endif ThalassaCgiDb::ThalassaCgiDb(const Cgi *cgi) : the_session(0), access_checker(0) { inifile = new IniFileParser; subst = new ThalassaCgiDbSubstitution(this, cgi); } ThalassaCgiDb::~ThalassaCgiDb() { delete subst; delete inifile; if(access_checker) delete access_checker; } bool ThalassaCgiDb::Load(const ScriptVariable &filename) { conf_file = filename; return inifile->Load(filename.c_str()); } ScriptVariable ThalassaCgiDb::MakeErrorMessage() const { ScriptVariable msg(""); int el = inifile->GetLastErrorLine(); if(el != -1) msg += ScriptNumber(el) + ": "; else msg += " "; msg += inifile->GetLastErrorDescription(); return msg; } void ThalassaCgiDb:: GetFormatData(const char *&enc, const char *&tags, const char *&attrs) const { static const char defattrs[] = "a=href img=src img=alt"; enc = inifile->GetTextParameter("format", 0, "encoding", 0); tags = inifile->GetTextParameter("format", 0, "tags", ""); attrs = inifile->GetTextParameter("format", 0, "tag_attributes", defattrs); } ScriptVariable ThalassaCgiDb::GetUserdataDirectory() const { return inifile->GetTextParameter("general", 0, "userdata_dir", THALASSA_DEFAULT_USERDATA_DIR); } #if 0 ScriptVariable ThalassaCgiDb::GetSessionsDirectory() const { ScriptVariable d = GetUserdataDirectory(); if(d != "" && d[d.Length()-1] != '/') d += '/'; return d + "_sessions"; } #endif void ThalassaCgiDb:: GetCaptchaParameters(ScriptVariable &secret, int &time_to_live) const { const char *tmp = inifile->GetTextParameter("captcha", 0, "secret", 0); if(tmp) secret = tmp; else secret = ScriptNumber(rand()); // so it won't work! // please note we break it intentionally // well, the secret MUST be set in the config // otherwise, the captcha is easily circumvented time_to_live = inifile->GetIntegerParameter("captcha", 0, "expire", 300); } ScriptVariable ThalassaCgiDb::GetHtmlSnippet(const ScriptVariable &name) const { return inifile->GetTextParameter("html", 0, name.c_str(), ""); } ScriptVariable ThalassaCgiDb::GetContactCategories() const { return inifile->GetTextParameter("feedback", 0, "categories", ""); } ScriptVariable ThalassaCgiDb::GetContactCatTitle(const ScriptVariable &cg) const { const char *tmp = inifile-> GetModifiedTextParameter("feedback", 0, "cattitle", cg.c_str(), 0); if(tmp) return tmp; return ScriptVariable("[==") + cg + "==]"; } ScriptVariable ThalassaCgiDb::GetContactRecipient(const ScriptVariable &cg) const { const char *c = cg.c_str(); return inifile->GetModifiedTextParameter("feedback", 0, "email", c, ""); } static bool boolean_value_from_string(const char *str) { return str && ScriptVariable(str).Trim().Tolower() == "yes"; } bool ThalassaCgiDb::GetContactCatIsSel(const ScriptVariable &cg) const { const char *tmp = inifile-> GetModifiedTextParameter("feedback", 0, "selected", cg.c_str(), 0); return boolean_value_from_string(tmp); } ScriptVariable ThalassaCgiDb::GetContactEnvelopeFrom() const { return inifile->GetTextParameter("feedback", 0, "envelope_from", ""); } void ThalassaCgiDb::BuildContactFormMessage(const ScriptVariable &receiver, ScriptVector &command, ScriptVariable &input) const { ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("receiver", receiver)); ScriptVariable tmp; tmp = inifile->GetTextParameter("feedback", 0, "send_command", ""); bool ok = make_argv(sub_sub(tmp), command); if(!ok || command.Length() < 1) { command.Clear(); input.Invalidate(); return; } tmp = inifile->GetTextParameter("feedback", 0, "send_data", ""); tmp.Trim(); if(tmp == "") { input.Invalidate(); return; } input = sub_sub(tmp); } void ThalassaCgiDb::BuildServiceEmail(const ScriptVariable &serv_id, const ScriptVariable &receiver, const ScriptVector &dict, ScriptVector &command, ScriptVariable &input) const { ScriptVariable subject = inifile->GetModifiedTextParameter("servicemail", 0, "subject", serv_id.c_str(), ""); ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("receiver", receiver)); int i; for(i = 0; i < dict.Length(); i+=2) sub_sub.AddMacro(new ScriptMacroConst(dict[i], dict[i+1])); subject = sub_sub(subject); sub_sub.AddMacro(new ScriptMacroConst("subject", subject)); ScriptVariable tmp; tmp = inifile->GetModifiedTextParameter("servicemail", 0, "send_command", serv_id.c_str(), ""); bool ok = make_argv(sub_sub(tmp), command); if(!ok || command.Length() < 1) { command.Clear(); input.Invalidate(); return; } tmp = inifile->GetModifiedTextParameter("servicemail", 0, "header", serv_id.c_str(), ""); tmp.Trim(); if(tmp == "") { input.Invalidate(); return; } ScriptVariable header = sub_sub(tmp); tmp = inifile->GetModifiedTextParameter("servicemail", 0, "body", serv_id.c_str(), ""); tmp.Trim(); if(tmp == "") { input.Invalidate(); return; } input = header + "\n" + sub_sub(tmp); } static const char default_error_page_template[] = "\n" "\n" "\n" "Thalassa CMS default error page\n" "

Thalassa CMS default error page

\n" "

%errcode% %errmessage%

\n" "

If you see this page it probably means the configuration\n" "file for Thalassa CMS CGI program is either missing or\n" "broken. Please check.

\n"; ScriptVariable ThalassaCgiDb::MakeErrorPage(int code, const ScriptVariable &comment) const { ScriptVariable templ = inifile->GetTextParameter("errorpage", 0, "template", default_error_page_template); ScriptNumber code_s(code); ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("errcode", code_s)); sub_sub.AddMacro(new ScriptMacroConst("errmessage", comment)); return sub_sub.Process(templ); } long ThalassaCgiDb::GeneralPostLimit() const { return inifile-> GetIntegerParameter("general", 0, "post_content_limit", 16); } //#include int ThalassaCgiDb::FindPath(const ScriptVariable &path, PathData &data) const { const char *tmp; const char *pgn = path.c_str(); bool multipath; tmp = inifile->GetTextParameter("page", pgn, "template", 0); if(tmp) { data.args.Clear(); multipath = false; } else { data.args = ScriptWordVector(path, "/"); // note that it's word vector, not token v., so there can't be // empty tokens AND leading and trailing slashes are removed if(data.args.Length() < 1) return path_bad; pgn = data.args[0].c_str(); tmp = inifile->GetTextParameter("page", pgn, "template", 0); if(!tmp) return path_noent; multipath = true; } data.page_id = pgn; // NB: may differ from path! tmp = inifile->GetTextParameter("page", pgn, "check_fnsafe", 0); if(tmp) { ScriptVector tokens; bool ok = make_argv(subst->Process(tmp, data.args), tokens); if(!ok) { //fprintf(stderr, "FAILED make_argv: %s\n", tmp); return path_invalid; } int i; for(i = 0; i < tokens.Length(); i++) if(!check_fname_safe(tokens[i].c_str())) { //fprintf(stderr, "FAILED check: %s\n", tokens[i].c_str()); return path_invalid; } //fprintf(stderr, "CHECK_FNSAFE OK\n"); } tmp = inifile->GetTextParameter("page", pgn, "session_required", 0); data.session_required = boolean_value_from_string(tmp); tmp = inifile->GetTextParameter("page", pgn, "embedded", 0); data.embedded = boolean_value_from_string(tmp); tmp = inifile->GetTextParameter("page", pgn, "post_allowed", 0); data.post_allowed = boolean_value_from_string(tmp); if(data.post_allowed) { data.post_content_limit = inifile->GetIntegerParameter("page", pgn, "post_content_limit", -1); if(data.post_content_limit == -1) data.post_content_limit = inifile-> GetIntegerParameter("general", 0, "post_content_limit", -1); data.post_param_limit = inifile->GetIntegerParameter("page", pgn, "post_param_limit", -1); if(data.post_param_limit == -1) data.post_param_limit = inifile-> GetIntegerParameter("general", 0, "post_param_limit", -1); } tmp = inifile->GetTextParameter("page", pgn, "reqargs", 0); //fprintf(stderr, "REQARGS: %s\n", tmp); if(tmp) { ScriptWordVector reqargs(tmp); int i; for(i = 0; i < reqargs.Length(); i++) { const char *ra = reqargs[i].c_str(); tmp = inifile-> GetModifiedTextParameter("page", pgn, "reqarg", ra, 0); if(!tmp) continue; subst->SetReqArg(reqargs[i], subst->Process(tmp, data.args)); } } if(multipath) { tmp = inifile->GetTextParameter("page", pgn, "path_predicate", "yes"); //fprintf(stderr, "PATH_PREDICATE: %s\n", tmp); ScriptVariable r = subst->Process(tmp, data.args); r.Trim(); r.Tolower(); //fprintf(stderr, "RESULT: %s\n", r.c_str()); if(r == "reject") return path_noaccess; if(r != "yes") return path_notfound; } return path_ok; } ScriptVariable ThalassaCgiDb::BuildPage(PathData &data) const { return BuildResultPage(data, 0, true); } ScriptVariable ThalassaCgiDb::BuildResultPage(PathData &data, const char *msgid, bool is_it_ok) const { subst->SetPageData(&data); if(msgid && *msgid) { ScriptVariable message; const char *tmp = inifile->GetTextParameter("message", 0, msgid, 0); if(tmp) message = (*subst)(tmp); else message = ScriptVariable("[==") + msgid + "==]"; bool requested_action_result = ScriptVariable(msgid) != "cookie_set"; subst->SetMessage(message, is_it_ok, requested_action_result); } const char *tmp; const char *pgid = data.page_id.c_str(); tmp = inifile->GetTextParameter("page", pgid, "selector", 0); if(tmp) { data.selector = subst->Process(tmp, data.args); data.selector.Trim(); } else { data.selector = ScriptVariableInv(); } tmp = inifile->GetTextParameter("page", pgid, "template", 0); ScriptVariable res = subst->Process(tmp, data.args); subst->ForgetMessage(); subst->ForgetPageData(); return res; } static bool is_alphanumeric(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } bool ThalassaCgiDb::GetPageAction(const PathData &data, ScriptVector &action) const { const char *pgn = data.page_id.c_str(); const char *tmp = inifile->GetTextParameter("page", pgn, "action", 0); if(!tmp) return false; ScriptVariable actstr(tmp); actstr.Trim(); if(actstr.Length() == 0) return false; if(is_alphanumeric(actstr[0])) { // just words, then action = ScriptWordVector(actstr); } else { // otherwise, use it as a delimiter for tokens char dlm[2] = { 0, 0 }; dlm[0] = actstr[0]; actstr.Range(0, 1).Erase(); action = ScriptTokenVector(actstr, dlm, " \t\r\n"); } if(action.Length() == 0) return false; subst->SetPageData(&data); int i; for(i = 0; i < action.Length(); i++) action[i] = subst->Process(action[i], data.args); subst->ForgetPageData(); return true; } ScriptVariable ThalassaCgiDb::GetPageProperty(const PathData &data, const ScriptVariable &prop_id) const { const char *pgn = data.page_id.c_str(); const char *prid = prop_id.c_str(); const char *tmp = data.selector.IsValid() ? inifile->GetModifiedTextParameter("page", pgn, prid, data.selector.c_str(), "") : inifile->GetTextParameter("page", pgn, prid, ""); return subst->Process(tmp, data.args); //return ScriptVariable("--[") + data.page_id + "|" + prop_id + "]--"; } ScriptVariable ThalassaCgiDb::MakeNocookiePage() const { ScriptVariable templ = inifile->GetTextParameter("nocookiepage", 0, "template", ""); return subst->Process(templ); } ScriptVariable ThalassaCgiDb::MakeRetryCaptchaPage(int captcha_result) const { ScriptVariable templ = inifile->GetTextParameter("retrycaptchapage", 0, "template", ""); const char *modif; switch(captcha_result) { case captcha_result_ip_mismatch: modif = "ip_mismatch"; break; case captcha_result_expired: modif = "expired"; break; case captcha_result_broken_data: modif = "broken_data"; break; case captcha_result_wrong: modif = "wrong_answer"; break; default: modif = "unknown"; break; } const char *msg = inifile->GetModifiedTextParameter("retrycaptchapage", 0, "errmessage", modif, "captcha problem"); ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("errmessage", msg)); return sub_sub.Process(templ); } ScriptVariable ThalassaCgiDb::GetCommentDir() const { const char *tmp; tmp = inifile->GetTextParameter("comments", 0, "dir", "."); return (*subst)(tmp); } bool ThalassaCgiDb::GetDiscussionInfo(const ScriptVariable &comment, DiscussionInfo &result) const { ScriptMacroprocessor sub_sub(subst); sub_sub.AddMacro(new ScriptMacroConst("comment_id", comment)); if(comment.IsInvalid() || comment == "") { result.comment_id = 0; } else { long n; bool ok = comment.GetLong(n, 10); if(!ok) return false; result.comment_id = n; } const char *tmp; ScriptVariableInv inv; tmp = inifile->GetTextParameter("comments", 0, "subdir", 0); if(!tmp) return false; result.cmt_tree_dir = GetCommentDir() + "/" + (*subst)(tmp).Trim(); tmp = inifile->GetTextParameter("comments", 0, "page_source", 0); result.page_source = tmp ? (*subst)(tmp).Trim() : inv; tmp = inifile->GetTextParameter("comments", 0, "page_html_file", 0); result.page_html_file = tmp ? (*subst)(tmp).Trim() : inv; tmp = inifile->GetTextParameter("comments", 0, "page_url", 0); result.page_url = tmp ? (*subst)(tmp).Trim() : inv; tmp = inifile->GetTextParameter("comments", 0, "orig_url", 0); result.orig_url = tmp ? sub_sub(tmp).Trim() : inv; // NB: the only use of sub_sub! result.html_start_mark.Invalidate(); result.html_end_mark.Invalidate(); tmp = inifile->GetTextParameter("comments", 0, "page_html_marks", 0); if(tmp) { ScriptTokenVector v(tmp, "\n", " \t\r"); int vl = v.Length(); int i = 0; while(i < vl && v[i] == "") i++; if(i < vl) result.html_start_mark = v[i]; if(i+1 < vl) result.html_end_mark = v[i+1]; } tmp = inifile->GetTextParameter("comments", 0, "access", 0); result.access = tmp ? (*subst)(tmp) : inv; tmp = inifile->GetTextParameter("comments", 0, "premodq_page_id", 0); result.premodq_page_id = tmp ? (*subst)(tmp).Trim() : inv; result.recent_timeout = inifile->GetIntegerParameter("comments", 0, "recent_timeout", 0); result.recent_timeout *= 60; /* it is in minutes */ return true; } static bool any_whitespace(const char *s) { const char *p; for(p = s; *p; p++) if(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') return true; return false; } static bool scan_subdir(const ScriptVariable &path, ScriptVector &res) { bool have_subdirs = false; res.Clear(); ReadDir rd(path.c_str()); const char *nm; while((nm = rd.Next())) { if(*nm == '.' || *nm == '_' || any_whitespace(nm)) continue; ScriptVariable fname = path + "/" + nm; FileStat fs(fname.c_str()); if(!fs.IsDir()) continue; have_subdirs = true; ScriptVector sub_subs; bool sub_has_subs = scan_subdir(fname, sub_subs); if(!sub_has_subs) { res.AddItem(nm); continue; } // it's a subdir with subdirs! (the most messy case) ScriptVariable basename(nm); basename += "/"; int i; for(i = 0; i < sub_subs.Length(); i++) res.AddItem(basename + sub_subs[i]); } return have_subdirs; } void ThalassaCgiDb::GetCommentTopics(ScriptVector &topics) const { ScriptVariable cdr = GetCommentDir(); scan_subdir(cdr, topics); } bool ThalassaCgiDb::CommentTopicExists(const ScriptVariable &topic) const { ScriptVariable cdr = GetCommentDir(); ScriptVariable fname = cdr + "/" + topic; FileStat fs(fname.c_str()); if(!fs.Exists() || !fs.IsDir()) return false; ScriptVector v; bool has_subdirs = scan_subdir(fname, v); return !has_subdirs; } bool ThalassaCgiDb::MakeCommentPageRegenCmd(ScriptVector &result) const { const char *tmp; tmp = inifile->GetTextParameter("comments", 0, "page_regen_command", 0); if(!tmp) return false; bool ok = make_argv((*subst)(tmp), result); if(!ok || result.Length() < 1) { result.Clear(); return false; } return true; } bool ThalassaCgiDb::CanPost(bool &bypass_premod) const { if(!the_session) // this is actually a bug! return false; EnsureAccessChecker(); ScriptVector roles; the_session->GetCurrentRoles(roles); return access_checker->CanPost(roles, bypass_premod); } bool ThalassaCgiDb::CanSeeHidden(const ScriptVariable &comment_owner_id) const { if(!the_session) // this is actually a bug! return false; EnsureAccessChecker(); ScriptVector roles; the_session->GetCurrentRoles(roles); bool own = the_session->IsLoggedIn() && the_session->GetUser() == comment_owner_id; return access_checker->CanSeeHidden(roles, own); } bool ThalassaCgiDb::CanModerate() const { if(!the_session) // this is actually a bug! return false; EnsureAccessChecker(); ScriptVector roles; the_session->GetCurrentRoles(roles); return access_checker->CanModerate(roles); } bool ThalassaCgiDb::CanEdit(const ScriptVariable &comment_owner_id, long long comment_unixdate) const { if(!the_session) // this is actually a bug! return false; EnsureAccessChecker(); ScriptVector roles; the_session->GetCurrentRoles(roles); bool own = the_session->IsLoggedIn() && the_session->GetUser() == comment_owner_id; return access_checker->CanEdit(roles, own, comment_unixdate); } void ThalassaCgiDb::EnsureAccessChecker() const { if(access_checker) return; const char *rules = inifile->GetTextParameter("comments", 0, "access", ""); int recent = inifile->GetIntegerParameter("comments", 0, "recent_timeout", 0); if(!access_checker) const_cast(this)->access_checker = new AccessChecker(); access_checker->SetRules(rules, recent * 60); } void ThalassaCgiDb::SetJustPostedSubst(const ScriptVariable &comment, bool hidden) { subst->SetJustPosted(comment, hidden); } void ThalassaCgiDb::CurrentCommentChanged(const ScriptVariable &c) const { const char *tmp = inifile->GetTextParameter("comments", 0, "premodq_page_id", ""); ScriptVariable pg = tmp ? (*subst)(tmp) : ScriptVariableInv(); subst->CurrentCommentChanged(pg, c); } void ThalassaCgiDb::SetPreview(const NewCommentData &ncd) { subst->SetPreview(ncd); }