thalassa/cms/thalcgi.cpp

1043 lines
33 KiB
C++
Raw Permalink Normal View History

2026-03-19 01:23:52 +00:00
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <scriptpp/scrvar.hpp>
#include <scriptpp/scrmacro.hpp>
#include <scriptpp/scrmsg.hpp>
#include <scriptpp/cmd.hpp>
#include "xcgi.hpp"
#include "xcaptcha.hpp"
#include "xrandom.h"
#include "tcgi_db.hpp"
#include "tcgi_ses.hpp"
#include "tcgi_rpl.hpp"
#include "invoke.h"
#include "emailval.h"
#include "fnchecks.h"
#ifndef THALASSA_CGI_CONFIG_PATH
#define THALASSA_CGI_CONFIG_PATH "thalcgi.ini"
#endif
#ifndef THALASSA_CGI_SESSION_TTL
#define THALASSA_CGI_SESSION_TTL (2*24*3600)
#endif
#ifndef THALASSA_CGI_SESSID_COOKIE
#define THALASSA_CGI_SESSID_COOKIE "thalassa_sessid"
#endif
#ifndef THALASSA_PASSWD_RESEND_MIN
#define THALASSA_PASSWD_RESEND_MIN (24*3600)
#endif
#ifndef THALASSA_PASSWD_GEN_COUNT
#define THALASSA_PASSWD_GEN_COUNT 20
#endif
/////////////////////////////////////////////////////////////////
// functions that actually send the response
//
// NOTE there are exactly 5 possibilities to finish the job:
// send_error_page, send_nocookie_page, send_retrycaptcha_page,
// send_the_page and send_result_page
//
// NB: in case of error, no cookie, or failed captcha,
// we do nothing with cookies and hence we don't need the session
static void send_error_page(Cgi &cgi, const ThalassaCgiDb &db,
int code, const char *cmt)
{
ScriptVariable page = db.MakeErrorPage(code, cmt);
cgi.SetStatus(code, cmt);
cgi.SetBody(page);
cgi.Commit();
}
static void send_nocookie_page(Cgi &cgi, const ThalassaCgiDb &db)
{
ScriptVariable page = db.MakeNocookiePage();
cgi.SetBody(page);
cgi.Commit();
}
static void send_retrycaptcha_page(Cgi &cgi, const ThalassaCgiDb &db,
int captcha_res)
{
ScriptVariable page = db.MakeRetryCaptchaPage(captcha_res);
cgi.SetBody(page);
cgi.Commit();
}
// every non-"error_page" response is done through this function
static void commit_response(Cgi &cgi, SessionData &session,
const ScriptVariable &pg)
{
if(session.IsValid()) {
cgi.SetCookie(THALASSA_CGI_SESSID_COOKIE, session.GetId().c_str(),
THALASSA_CGI_SESSION_TTL, true, false);
} else {
if(session.JustRemoved())
cgi.DiscardCookie(THALASSA_CGI_SESSID_COOKIE);
}
cgi.SetBody(pg);
cgi.Commit();
}
static void renew_if_needed(SessionData &session)
{
if(session.IsValid() && !session.JustCreated())
session.Renew(THALASSA_CGI_SESSION_TTL);
}
static void send_the_page(Cgi &cgi, const ThalassaCgiDb &db,
PathData &page, SessionData &session)
{
renew_if_needed(session);
ScriptVariable pg = db.BuildPage(page);
commit_response(cgi, session, pg);
}
static void send_result_page(Cgi &cgi, const ThalassaCgiDb &db,
PathData &page, SessionData &session,
const char *msgid, bool is_it_ok)
{
renew_if_needed(session);
ScriptVariable pg = db.BuildResultPage(page, msgid, is_it_ok);
commit_response(cgi, session, pg);
}
// end of the response-sending infrastructure
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// POST request processing implementation -- yes, it is huge
//
static void process_set_cookie_request(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable form_ip = cgi.GetParam("captcha_ip");
ScriptVariable form_time = cgi.GetParam("captcha_time");
ScriptVariable form_nonce = cgi.GetParam("captcha_nonce");
ScriptVariable form_token = cgi.GetParam("captcha_token");
ScriptVariable response = cgi.GetParam("captcha_response");
int cap_res =
captcha_validate(form_ip, form_time, form_nonce, form_token, response);
if(cap_res != captcha_result_ok) {
send_retrycaptcha_page(cgi, db, cap_res);
return;
}
bool nonce_ok = sess.CheckAndStoreNonce(form_time, form_nonce);
if(!nonce_ok) {
send_error_page(cgi, db, 500, "Nonce check error");
return;
}
bool ok = sess.Create(THALASSA_CGI_SESSION_TTL);
if(ok) {
// It is definitely unneeded to perform MORE cleanup scans
// than we set cookies.
// So, this is THE place we do it
sess.PerformCleanup(THALASSA_CGI_SESSION_TTL);
// send that 'cookie set' message to the user
send_result_page(cgi, db, page, sess, "cookie_set", true);
} else {
send_error_page(cgi, db, 500, "Session creation error");
}
}
static bool
get_and_check_field(Cgi &cgi, const ThalassaCgiDb &db,
PathData &page, SessionData &sess,
const char *name, ScriptVariable &field)
{
field = cgi.GetParam(name);
if(field.IsInvalid() || field.Trim() == "") {
send_result_page(cgi, db, page, sess, "field_not_filled", false);
return false;
}
return true;
}
static bool
get_and_check_feedback_category(Cgi &cgi, const ThalassaCgiDb &db,
PathData &page, SessionData &sess,
ScriptVariable &receiver_addr)
{
ScriptVariable cat = cgi.GetParam("category");
if(cat.IsValid() && cat.Trim() != "") {
receiver_addr = db.GetContactRecipient(cat);
receiver_addr.Trim();
} else {
receiver_addr.Invalidate();
}
if(receiver_addr.IsInvalid() || receiver_addr == "") {
send_result_page(cgi, db, page, sess,
"invalid_feedback_category", false);
return false;
}
return true;
}
enum { email_text_width = 75 };
static ScriptVariable join_to_columns(const ScriptVector &v)
{
static const char colsep[] = " ";
ScriptVariable res;
ScriptVariable s;
int vlen = v.Length();
int i;
for(i = 0; i < vlen; i++) {
if(s == "") {
s = v[i];
continue;
}
int lnlen = s.Length() + (sizeof(colsep)-1) + v[i].Length();
if(lnlen <= email_text_width) {
s += colsep;
s += v[i];
} else {
s += "\n";
res += s;
s = v[i];
}
}
if(s != "") {
s += "\n";
res += s;
}
return res;
}
static bool send_service_email(const ThalassaCgiDb &db, const char *id,
const ScriptVariable &email, const ScriptVector &dict)
{
ScriptVector sendmail_cmd;
ScriptVariable body;
db.BuildServiceEmail(id, email, dict, sendmail_cmd, body);
char **argv = sendmail_cmd.MakeArgv();
int res = invoke_command(argv, body.c_str(), body.Length());
ScriptVector::DeleteArgv(argv);
return res == 0;
}
static void process_send_passwords(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable login;
if(!get_and_check_field(cgi, db, page, sess, "login", login))
return;
bool ok;
ok = sess.SetUser(login);
if(!ok) {
send_result_page(cgi, db, page, sess, "login_unknown", false);
return;
}
int rem_passwds = sess.GetUserRemainingPasswords();
if(rem_passwds < 0) {
//send_result_page(cgi, db, page, sess, "server_side_error", false);
send_result_page(cgi, db, page, sess, "pass_req_too_early", false);
return;
}
long long last_sent = sess.GetUserLastPwdsent();
long long now = time(0);
if(rem_passwds > 0 && last_sent > 0 &&
now - last_sent < THALASSA_PASSWD_RESEND_MIN)
{
send_result_page(cgi, db, page, sess, "pass_req_too_early", false);
return;
}
ScriptVector passw;
ok = sess.GenerateNewPasswords(passw, THALASSA_PASSWD_GEN_COUNT);
if(!ok) {
send_result_page(cgi, db, page, sess, "server_side_error", false);
return;
}
ScriptVariable user_email = sess.GetUserEmail();
if(user_email.IsInvalid() || user_email == "") {
send_result_page(cgi, db, page, sess, "server_side_error", false);
return;
}
ScriptVector dict;
dict.AddItem("passwords");
dict.AddItem(join_to_columns(passw));
dict.AddItem("event");
dict.AddItem("passwords");
ok = send_service_email(db, "passwords", user_email, dict);
if(ok) {
sess.UpdateUserLastPwdsent();
send_result_page(cgi, db, page, sess, "email_sent_to_you", true);
} else {
send_result_page(cgi, db, page, sess, "error_sending_email", false);
}
}
static void process_login(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable login, passtoken;
if(!get_and_check_field(cgi, db, page, sess, "login", login))
return;
if(!get_and_check_field(cgi, db, page, sess, "passtoken", passtoken))
return;
bool ok = sess.Login(login, passtoken);
send_result_page(cgi, db, page, sess,
ok ? "logged_in" : "wrong_login_password", ok);
}
static const char *diag_id_by_user_creation_result(int ucr)
{
switch(ucr) {
case SessionData::cu_bad_id: return "invalid_login_name";
case SessionData::cu_exists: return "login_not_available";
case SessionData::cu_bad_email: return "invalid_email_address";
case SessionData::cu_email_used: return "email_already_used";
case SessionData::cu_email_replaced: return "email_belonged_to_other";
case SessionData::cu_email_recently_tried: return "email_recently_tried";
case SessionData::cu_email_banned: return "email_banned";
case SessionData::cu_change_req_bad_passwd: return "wrong_password";
case SessionData::cu_change_req_too_early: return "changemail_too_early";
case SessionData::cu_conf_error: return "server_side_error";
case SessionData::cu_bug:
default: return "bug_in_the_code";
}
}
static void process_signup(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable userid, username, useremail, usersite;
if(!get_and_check_field(cgi, db, page, sess, "userid", userid))
return;
if(!get_and_check_field(cgi, db, page, sess, "username", username))
return;
if(!get_and_check_field(cgi, db, page, sess, "useremail", useremail))
return;
usersite = cgi.GetParam("usersite");
if(usersite.IsInvalid())
usersite = "";
int errcode, errpos; /* will be ignored */
if(!email_address_validate(useremail.c_str(), &errcode, &errpos)) {
send_result_page(cgi, db, page, sess, "invalid_email_address", false);
return;
}
ScriptVariable code;
int cr = sess.CreateUser(userid, username, useremail, usersite, code);
if(cr != SessionData::cu_success) {
const char *diag_id = diag_id_by_user_creation_result(cr);
send_result_page(cgi, db, page, sess, diag_id, false);
return;
}
ScriptVector dict;
dict.AddItem("confirmcode");
dict.AddItem(code);
dict.AddItem("event");
dict.AddItem("signup");
bool ok = send_service_email(db, "confirm", useremail, dict);
send_result_page(cgi, db, page, sess,
ok ? "email_sent_to_you" : "error_sending_email", ok);
}
static void process_save_profile(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
if(!sess.IsLoggedIn()) {
send_result_page(cgi, db, page, sess, "not_logged_in", false);
return;
}
ScriptVariable username, usersite;
if(!get_and_check_field(cgi, db, page, sess, "username", username))
return;
usersite = cgi.GetParam("usersite");
bool ok = sess.UpdateUser(username, usersite);
send_result_page(cgi, db, page, sess,
ok ? "new_values_saved" : "server_side_error", ok);
}
static void process_change_mail(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
if(!sess.IsLoggedIn()) {
send_result_page(cgi, db, page, sess, "not_logged_in", false);
return;
}
ScriptVariable newemail, passtoken;
if(!get_and_check_field(cgi, db, page, sess, "newemail", newemail))
return;
if(!get_and_check_field(cgi, db, page, sess, "passtoken", passtoken))
return;
int errcode, errpos; /* will be ignored */
if(!email_address_validate(newemail.c_str(), &errcode, &errpos)) {
send_result_page(cgi, db, page, sess, "invalid_email_address", false);
return;
}
ScriptVariable code;
int cr = sess.EmailChangeRequest(newemail, passtoken, code);
if(cr != SessionData::cu_success) {
const char *diag_id = diag_id_by_user_creation_result(cr);
send_result_page(cgi, db, page, sess, diag_id, false);
return;
}
ScriptVector dict;
dict.AddItem("confirmcode");
dict.AddItem(code);
dict.AddItem("event");
dict.AddItem("changemail");
bool ok = send_service_email(db, "confirm", newemail, dict);
send_result_page(cgi, db, page, sess,
ok ? "email_sent_to_you" : "error_sending_email", ok);
}
static bool check_really_confirmed(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable really;
if(!get_and_check_field(cgi, db, page, sess, "really", really))
return false;
really.Trim().Tolower();
if(really != "really") {
send_result_page(cgi, db, page, sess, "not_really_confirm", false);
return false;
}
return true;
}
static void process_confirm_mail_change(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
if(!sess.IsLoggedIn()) {
send_result_page(cgi, db, page, sess, "not_logged_in", false);
return;
}
if(!sess.IsChangingEmail()) {
send_result_page(cgi, db, page, sess, "not_this_way", false);
return;
}
if(cgi.GetParam("cancel_change") == "yes") {
bool really = check_really_confirmed(cgi, db, sess, page);
if(!really)
return;
sess.EmailChangeCancel();
send_result_page(cgi, db, page, sess, "changemail_canceled", true);
return;
}
ScriptVariable confirmcode;
if(!get_and_check_field(cgi, db, page, sess, "confirmcode", confirmcode))
return;
bool ok = sess.EmailChangeConfirm(confirmcode);
send_result_page(cgi, db, page, sess,
ok ? "email_changed" : "wrong_confirmation_code", ok);
}
static void process_send_email(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
ScriptVariable name, email, subject, receiver_addr, body;
// nb: email here means the sender's address typed into the form
if(!get_and_check_field(cgi, db, page, sess, "name", name))
return;
if(!get_and_check_field(cgi, db, page, sess, "mail", email))
return;
int errcode, errpos; /* will be ignored */
if(!email_address_validate(email.c_str(), &errcode, &errpos)) {
send_result_page(cgi, db, page, sess, "invalid_email_address", false);
return;
}
if(!get_and_check_field(cgi, db, page, sess, "subject", subject))
return;
if(!get_and_check_feedback_category(cgi, db, page, sess, receiver_addr))
return;
if(!get_and_check_field(cgi, db, page, sess, "msgbody", body))
return;
ScriptVector sendmail_cmd;
ScriptVariable cmdin;
db.BuildContactFormMessage(receiver_addr, sendmail_cmd, cmdin);
char **argv = sendmail_cmd.MakeArgv();
bool ok = 0 == invoke_command(argv, cmdin.c_str(), cmdin.Length());
ScriptVector::DeleteArgv(argv);
send_result_page(cgi, db, page, sess,
ok ? "your_email_sent" : "error_sending_email", ok);
}
static bool run_regeneration(const ThalassaCgiDb &db)
{
ScriptVector command;
bool ok = db.MakeCommentPageRegenCmd(command);
if(!ok)
return false;
char **argv = command.MakeArgv();
int res = invoke_command(argv, "", 0);
ScriptVector::DeleteArgv(argv);
return res == 0;
}
static void process_comment_add(Cgi &cgi, ThalassaCgiDb &db,
SessionData &sess, PathData &page,
const ScriptVector &action)
{
// NB: action[1] == cmt_id
if(action[1].IsValid() && action[1] != "" &&
!check_fname_safe(action[1].c_str()))
{
send_error_page(cgi, db, 406, "path not acceptable");
return;
}
bool ok;
DiscussionInfo reply_info;
ok = db.GetDiscussionInfo(action[1], reply_info);
if(!ok) {
send_result_page(cgi, db, page, sess, "not_this_way", false);
return;
}
bool bypass_premod;
ok = db.CanPost(bypass_premod);
if(!ok) {
send_result_page(cgi, db, page, sess,
sess.IsLoggedIn() ? "permission_denied" : "anon_denied", false);
return;
}
ScriptVariable subject, body;
if(!get_and_check_field(cgi, db, page, sess, "subject", subject))
return;
if(!get_and_check_field(cgi, db, page, sess, "cmtbody", body))
return;
NewCommentData com_data;
if(sess.IsLoggedIn()) {
com_data.user_id = sess.GetUser();
com_data.user_name = sess.GetUserRealname();
} else {
com_data.user_id.Invalidate();
ScriptVariable name;
if(!get_and_check_field(cgi, db, page, sess, "name", name))
return;
com_data.user_name = name;
sess.SaveUsedRealname(name);
}
com_data.title = subject;
com_data.body = body;
com_data.parent_comment_id = 0;
if(action[1].IsValid() && action[1] != "") {
long par;
if(action[1].GetLong(par, 10))
com_data.parent_comment_id = par;
}
com_data.creator_addr =
cgi.GetRemoteAddr() + ":" + ScriptNumber(cgi.GetRemotePort());
com_data.creator_session = sess.GetId0();
com_data.creator_date = ScriptNumber(time(0));
if(cgi.GetParam("preview") == "yes") {
db.SetPreview(com_data);
send_the_page(cgi, db, page, sess);
return;
}
if(bypass_premod) {
int res = save_new_comment(reply_info.cmt_tree_dir, com_data, 0);
// NB: here we intentionally ignore possible regeneration errors
run_regeneration(db);
if(res > 0)
db.SetJustPostedSubst(ScriptNumber(res), false);
send_result_page(cgi, db, page, sess,
res > 0 ? "comment_saved" : "server_side_error", res > 0);
} else {
ScriptVariable cmtf;
int res = save_new_comment(reply_info.cmt_tree_dir, com_data, &cmtf);
if(res > 0) {
sess.AddToPremodQueue(reply_info.premodq_page_id, res, cmtf);
ScriptNumber cmt_id(res);
db.SetJustPostedSubst(cmt_id, true);
}
send_result_page(cgi, db, page, sess,
res > 0 ? "comment_queued_for_premod" : "server_side_error",
res > 0);
}
}
/* may be apply_moderation_request should be moved to the tcgi_rpl module */
static void apply_moderation_request(ScriptVariable moder,
HeadedTextMessage &comment,
bool &should_dequeue)
{
ScriptVariable flagsstr = comment.FindHeader("flags");
if(flagsstr.IsInvalid())
flagsstr = "";
ScriptVector flags(flagsstr, ",", " \t\r\n");
ScriptVector newflags;
int fl = flags.Length();
bool hidden = false;
bool premod = false;
int i;
for(i = 0; i < fl; i++) {
if(flags[i] == "hidden") {
hidden = true;
continue;
}
if(flags[i] == "premod") {
premod = true;
continue;
}
if(flags[i] == "")
continue;
// preserve out-of-interest flags, if any
newflags.AddItem(flags[i]);
}
should_dequeue = false;
moder.Tolower();
moder.Trim();
if(moder == "hide") {
newflags.AddItem("hidden");
if(premod)
newflags.AddItem("premod");
} else
if(moder == "unhide") {
if(premod)
should_dequeue = true;
} else
if(moder == "dequeue") {
newflags.AddItem("hidden");
should_dequeue = true;
} else {
// request unknown... it's an error, but we just leave all as is
if(hidden)
newflags.AddItem("hidden");
if(premod)
newflags.AddItem("premod");
}
comment.SetHeader("flags", newflags.Join(", "));
}
static void process_comment_edit(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page,
const ScriptVector &action)
{
// NB: action[1] == cmt_id
if(!check_fname_safe(action[1].c_str()))
{
send_error_page(cgi, db, 406, "path not acceptable");
return;
}
bool ok;
DiscussionInfo discuss_info;
ok = db.GetDiscussionInfo(action[1], discuss_info);
if(!ok || discuss_info.comment_id < 1) {
send_result_page(cgi, db, page, sess, "not_this_way", false);
return;
}
HeadedTextMessage comment_file;
ok = get_comment(discuss_info, comment_file);
if(!ok) {
send_result_page(cgi, db, page, sess, "server_side_error", false);
return;
}
ScriptVariable moder = cgi.GetParam("moderation");
if(moder.IsValid()) { // this is a moderation request, that's simple
if(!db.CanModerate()) {
send_error_page(cgi, db, 403, "Forbidden");
return;
}
if(moder == "regenerate") {
ok = run_regeneration(db);
send_result_page(cgi, db, page, sess,
ok ? "page_regenerated" : "server_side_error", ok);
return;
}
bool should_dequeue = false;
apply_moderation_request(moder, comment_file, should_dequeue);
ok = save_comment(discuss_info, comment_file);
if(!ok) {
send_result_page(cgi, db, page, sess, "server_side_error", false);
return;
}
run_regeneration(db); // result ignored intentionally!
db.CurrentCommentChanged(action[1]);
if(should_dequeue)
sess.RemoveFromPremodQueue(discuss_info.premodq_page_id,
discuss_info.comment_id);
send_result_page(cgi, db, page, sess, "new_values_saved", true);
return;
}
// okay, now it is either a save or a delete request
ScriptVariable cmt_owner;
long long cmt_unixdate;
get_owner_and_date_from_htm(comment_file, cmt_owner, cmt_unixdate);
if(!db.CanEdit(cmt_owner, cmt_unixdate)) {
send_error_page(cgi, db, 403, "Forbidden");
return;
}
// check if deletion is requested
ScriptVariable delreq = cgi.GetParam("delete");
if(delreq.IsValid() && delreq != "") {
delreq.Trim().Tolower();
if(delreq != "yes") {
send_result_page(cgi, db, page, sess, "not_this_way", false);
return;
}
bool really = check_really_confirmed(cgi, db, sess, page);
if(!really) // the result is sent by check_really_confirmed
return;
// please note this is the only correct sequence:
// FIRST we delete the comment, then (when the queue isn't
// yet modified) we freeze the moder_queue position, and
// only then we actually remove the item from the moder_queue
ok = delete_comment(discuss_info);
db.CurrentCommentChanged(action[1]);
sess.RemoveFromPremodQueue(discuss_info.premodq_page_id,
discuss_info.comment_id);
// XXX actually, we should check if it was visible!
// no regeneration is needed after deleting hidden comment
run_regeneration(db); // result ignored intentionally
send_result_page(cgi, db, page, sess,
ok ? "comment_deleted" : "server_side_error", ok);
return;
}
// the last possibility is content changing request
ScriptVariable subject, body;
if(!get_and_check_field(cgi, db, page, sess, "subject", subject))
return;
if(!get_and_check_field(cgi, db, page, sess, "cmtbody", body))
return;
ScriptVariable logmark =
cgi.GetRemoteAddr() + ":" + ScriptNumber(cgi.GetRemotePort()) +
" " + ScriptNumber(time(0)) + " " + sess.GetId0() + " " +
(sess.IsLoggedIn() ? sess.GetUser() : "-");
replace_comment_content(comment_file, subject, body, logmark);
ok = save_comment(discuss_info, comment_file);
db.CurrentCommentChanged(action[1]);
// NB: here we intentionally ignore possible regeneration errors
run_regeneration(db);
send_result_page(cgi, db, page, sess,
ok ? "new_values_saved" : "server_side_error", ok);
}
#if 0
static void process_whatever_unimplemented(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
send_result_page(cgi, db, page, sess, "not_implemented_yet", false);
}
#endif
static void process_post_request(Cgi &cgi, ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
if(!sess.IsValid()) {
int lim = db.GeneralPostLimit();
if(cgi.ContentLength() > lim * 1024) {
send_error_page(cgi, db, 413, "request entity too large");
return;
}
cgi.ParseBody();
// setcookie is the only case when POST request is
// processed despite there's no valid session
if(cgi.GetParam("command") == "setcookie") {
process_set_cookie_request(cgi, db, sess, page);
return;
}
// no other POST requests are allowed to proceed
// outside of a session, so just refuse it
send_nocookie_page(cgi, db);
return;
}
if(!page.post_allowed) {
send_error_page(cgi, db, 405, "method not allowed");
return;
}
ScriptVector action;
if(!db.GetPageAction(page, action)) {
send_error_page(cgi, db, 500, "No action defined for this path");
return;
}
fprintf(stderr, "ACTION: ");
int i;
for(i = 0; i < action.Length(); i++)
fprintf(stderr, "[%s]", action[i].c_str());
fprintf(stderr, "\n");
// requests that don't need the body go first
// as of now, only rmsession falls to this category
if(action[0] == "rmsession") {
sess.Remove();
send_result_page(cgi, db, page, sess, "cookie_removed", true);
return;
}
// now's the time to check for the content length limit, because
// all other requests use request bodies
if(page.post_content_limit >= 0 &&
cgi.ContentLength() > page.post_content_limit * 1024)
{
send_error_page(cgi, db, 413, "request entity too large");
return;
}
// XXXXX perhaps we need to check here for requests that involve
// the multipart/form-data format (e.g. for file upload),
// as they need to setup file download callbacks and establish
// per-field limits before calling ParseBody
// the rest of the requests don't need any special care, and
// the content length is checked already, so feel free to parse the body
cgi.ParseBody();
if(action[0] == "login") {
if(cgi.GetParam("sendmorepass") == "yes") {
process_send_passwords(cgi, db, sess, page);
return;
}
process_login(cgi, db, sess, page);
return;
}
if(action[0] == "signup") {
process_signup(cgi, db, sess, page);
return;
}
if(action[0] == "profile") {
if(action.Length() > 1 && action[1].IsValid() && action[1] != "") {
send_result_page(cgi, db, page, sess, "not_this_way", true);
return;
}
process_save_profile(cgi, db, sess, page);
return;
}
if(action[0] == "changemail") {
if(sess.IsChangingEmail())
process_confirm_mail_change(cgi, db, sess, page);
else
process_change_mail(cgi, db, sess, page);
return;
}
if(action[0] == "feedback") {
if(cgi.GetParam("catchange") == "yes") {
send_the_page(cgi, db, page, sess);
return;
}
process_send_email(cgi, db, sess, page);
return;
}
if(action[0] == "comment_add") {
process_comment_add(cgi, db, sess, page, action);
return;
}
if(action[0] == "comment_edit") {
process_comment_edit(cgi, db, sess, page, action);
return;
}
send_error_page(cgi, db, 500, "unknown action, check the cgi config");
}
//
// end of the POST request processing implementation
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// subroutines for the very start
//
static void try_session_cookie(Cgi &cgi, SessionData &session)
{
ScriptVariable c = cgi.GetCookie(THALASSA_CGI_SESSID_COOKIE);
if(c.IsValid() && c != "")
session.Validate(c);
// don't forget: renewing the session right here is not
// a good idea because it is possible we run into error later
}
// OK, processing the GET is much much easier than for POST
//
static void process_get_request(Cgi &cgi, const ThalassaCgiDb &db,
SessionData &sess, PathData &page)
{
if(page.session_required && !sess.IsValid()) {
send_nocookie_page(cgi, db);
return;
}
send_the_page(cgi, db, page, sess);
}
/////////////////////////////////////////////////////////////////
// the main function infrastructure
static void captcha_setup(ThalassaCgiDb &db, SessionData &sess)
{
ScriptVariable secret;
int time_to_live;
db.GetCaptchaParameters(secret, time_to_live);
set_captcha_info(secret, time_to_live);
sess.SetCaptchaTtl(time_to_live);
}
static bool check_conffile_mode()
{
FileStat st(THALASSA_CGI_CONFIG_PATH);
if(!st.Exists())
return true; /* it doesn't exist, yet it's NOT world-accessible */
int uid, gid, mode;
st.GetCreds(uid, gid, mode);
int my_uid = getuid();
if(my_uid != uid) /* perhaps there's no suexec here */
return true; /* this doesn't mean all right, just check abort */
return (mode & 0007) == 0;
}
int main()
{
Cgi cgi;
ThalassaCgiDb db(&cgi);
if(!check_conffile_mode()) {
send_error_page(cgi, db, 500,
"Check permissions of your config file "
THALASSA_CGI_CONFIG_PATH);
return 0;
}
if(!db.Load(THALASSA_CGI_CONFIG_PATH)) {
ScriptVariable diag = db.MakeErrorMessage();
send_error_page(cgi, db, 500, diag.c_str());
return 0;
}
randomize();
if(!cgi.ParseHead()) {
cgi.Commit();
return 0;
}
#if 0
db.SetRequest(&cgi);
#endif
ScriptVariable datadir = db.GetUserdataDirectory();
FileStat dds(datadir.c_str());
if(!dds.Exists() || !dds.IsDir()) {
send_error_page(cgi, db, 500, "Check userdata directory");
return 0;
}
ScriptVariable sessdir = db.GetUserdataDirectory();
FileStat sds(sessdir.c_str());
if(!sds.Exists()) {
int r = mkdir(sessdir.c_str(), 0700);
if(r == -1) {
send_error_page(cgi, db, 500, "Can't make sessions directory");
return 0;
}
} else
if(!sds.IsDir()) {
send_error_page(cgi, db, 500, "Sessions dir isn't a dir O_o");
return 0;
}
SessionData session(sessdir.c_str());
db.SetSession(&session);
captcha_setup(db, session); // XXX the second arg. to be removed one day
try_session_cookie(cgi, session);
fprintf(stderr, "PATH: [%s]\n", cgi.GetPath().c_str());
PathData page;
int pathres = db.FindPath(cgi.GetPath(), page);
switch(pathres) {
case ThalassaCgiDb::path_ok:
break;
case ThalassaCgiDb::path_bad:
send_error_page(cgi, db, 400, "requested path bad");
return 0;
case ThalassaCgiDb::path_noent:
send_error_page(cgi, db, 404, "path not configured");
return 0;
case ThalassaCgiDb::path_noaccess:
send_error_page(cgi, db, 403, "forbidden");
return 0;
case ThalassaCgiDb::path_notfound:
send_error_page(cgi, db, 404, "path not found");
return 0;
case ThalassaCgiDb::path_invalid:
send_error_page(cgi, db, 406, "path not acceptable");
return 0;
default:
send_error_page(cgi, db, 500, "bug in the CGI code");
return 0;
}
if(cgi.IsPost())
process_post_request(cgi, db, session, page);
else
process_get_request(cgi, db, session, page);
return 0;
}