1013 lines
28 KiB
C++
1013 lines
28 KiB
C++
#include <stdio.h> // for rename
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
|
|
|
|
#include "fnchecks.h"
|
|
#include "xrandom.h"
|
|
#include "fileops.hpp"
|
|
#include "memmail.hpp"
|
|
|
|
#include "tcgi_ses.hpp"
|
|
|
|
enum {
|
|
sess_filename_len = 16, // 64-bit number in hex
|
|
pending_time_max = (24*3600), // 1 day
|
|
tried_email_cooldown = (31*24*3600), // well, a month
|
|
email_change_cooldown = (24*3600) // 1 day
|
|
};
|
|
|
|
#define SESS_SUBDIR "_sessions"
|
|
#define USER_SUBDIR "_users"
|
|
#define USER_FILENAME "_data"
|
|
#define EMAIL_SUBDIR "_email"
|
|
#define NONCE_SUBDIR "_nonce"
|
|
#define PREMOD_QUEUE_SUBDIR "_premod_queue"
|
|
|
|
|
|
SessionData::SessionData(const ScriptVariable &dirpath)
|
|
: dirname(dirpath), just_created(false), just_removed(false),
|
|
username(ScriptVariableInv()), userinfo_read(false), logged_in(false),
|
|
email_data(0)
|
|
{
|
|
}
|
|
|
|
SessionData::~SessionData()
|
|
{
|
|
if(email_data)
|
|
delete email_data;
|
|
}
|
|
|
|
static bool acceptable_sess_id(const char *sess_id)
|
|
{
|
|
if(!sess_id || !*sess_id)
|
|
return false;
|
|
int len = 0;
|
|
while(sess_id[len]) {
|
|
char c = sess_id[len]; // just for short name
|
|
if(!((c>='0' && c<='9') || (c>='A' && c<='Z') || c=='_'))
|
|
return false;
|
|
len++;
|
|
}
|
|
#if 0 // the length check is performed later in SessionData::Validate
|
|
// actually, this check is wrong here, because all the cookie
|
|
// value is passed to this function, not the filename part
|
|
if(len != sess_filename_len+1)
|
|
return false;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static bool make_dir_if_necessary(const char *path)
|
|
{
|
|
FileStat fs(path);
|
|
if(fs.Exists() && fs.IsDir())
|
|
return true;
|
|
int r = mkdir(path, 0700);
|
|
return r != -1;
|
|
}
|
|
|
|
bool SessionData::Validate(ScriptVariable sid)
|
|
{
|
|
if(!acceptable_sess_id(sid.c_str()))
|
|
return false;
|
|
ScriptVariable::Substring delimpos = sid.Strchr('_');
|
|
if(delimpos.IsInvalid() || delimpos.Index() != sess_filename_len)
|
|
return false;
|
|
|
|
ScriptVariable fn = delimpos.Before().Get();
|
|
ScriptVariable token = delimpos.After().Get();
|
|
|
|
ScriptVariable fname = dirname + "/" SESS_SUBDIR "/" + fn;
|
|
info.Clear();
|
|
if(!info.FOpen(fname.c_str()))
|
|
return false;
|
|
bool rr = info.RunParser();
|
|
info.FClose();
|
|
if(!rr)
|
|
return false;
|
|
|
|
ScriptVariable tk = info.GetItem("token");
|
|
ScriptVariable oldtk = info.GetItem("old_token");
|
|
if(token != tk && token != oldtk)
|
|
return false;
|
|
|
|
sess_fn = fn;
|
|
username = info.GetItem("user");
|
|
ScriptVariable lgg = info.GetItem("logged_in");
|
|
lgg.Trim().Tolower();
|
|
logged_in = lgg == "yes";
|
|
if(logged_in)
|
|
UpdateUserLastSeen();
|
|
return true;
|
|
}
|
|
|
|
|
|
#if 0
|
|
static inline char hexdig(int n)
|
|
{
|
|
// YES, THIS IS BUG :-)
|
|
// definitely it should have been n+'A'-10 here,
|
|
// but the effect is aestetically pleasant
|
|
return n < 10 ? n + '0' : n + 'A';
|
|
}
|
|
#endif
|
|
|
|
static ScriptVariable uchar2hex(unsigned char *s, int n)
|
|
{
|
|
ScriptVariable res;
|
|
int i;
|
|
for(i = 0; i < n; i++) {
|
|
#if 0
|
|
res += hexdig((s[i] & 0xf0) >> 4);
|
|
res += hexdig(s[i] & 0x0f);
|
|
#endif
|
|
res += 'A' + ((s[i] & 0xf0) >> 4);
|
|
res += 'A' + (s[i] & 0x0f);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool SessionData::Create(int time_to_live)
|
|
{
|
|
unsigned char id[8];
|
|
unsigned char tok[8];
|
|
fill_random(id, sizeof(id));
|
|
fill_random(tok, sizeof(tok));
|
|
|
|
sess_fn = uchar2hex(id, sizeof(id));
|
|
unsigned long now = time(0);
|
|
info.Clear();
|
|
info.AddItem("token", uchar2hex(tok, sizeof(tok)));
|
|
info.AddItem("created", ScriptNumber(now));
|
|
info.AddItem("expire", ScriptNumber(now + time_to_live));
|
|
just_created = true;
|
|
bool ok = Save(true);
|
|
if(ok)
|
|
return true;
|
|
sess_fn = "";
|
|
info.Clear();
|
|
return false;
|
|
}
|
|
|
|
bool SessionData::Renew(int time_to_live)
|
|
{
|
|
if(!IsValid())
|
|
return false;
|
|
ScriptVariable oldold = info.GetItem("oldtoken");
|
|
ScriptVariable old = info.GetItem("token");
|
|
|
|
unsigned char tok[8];
|
|
fill_random(tok, sizeof(tok));
|
|
|
|
info.SetItem("token", uchar2hex(tok, sizeof(tok)));
|
|
info.SetItem("oldtoken", old);
|
|
unsigned long now = time(0);
|
|
info.SetItem("expire", ScriptNumber(now + time_to_live));
|
|
|
|
bool ok = Save(false);
|
|
if(ok)
|
|
return true;
|
|
sess_fn = "";
|
|
info.Clear();
|
|
return false;
|
|
}
|
|
|
|
bool SessionData::IsValid() const
|
|
{
|
|
return sess_fn.IsValid() && sess_fn != "";
|
|
}
|
|
|
|
ScriptVariable SessionData::GetId0() const
|
|
{
|
|
return sess_fn;
|
|
}
|
|
|
|
ScriptVariable SessionData::GetId() const
|
|
{
|
|
if(sess_fn.IsValid() && sess_fn != "")
|
|
return sess_fn + '_' + info.GetItem("token");
|
|
return ScriptVariableInv();
|
|
}
|
|
|
|
void SessionData::Remove()
|
|
{
|
|
if(sess_fn.IsInvalid() || sess_fn == "")
|
|
return;
|
|
ScriptVariable fname = dirname + "/" SESS_SUBDIR "/" + sess_fn;
|
|
unlink(fname.c_str());
|
|
info.Clear();
|
|
sess_fn.Invalidate();
|
|
just_removed = true;
|
|
}
|
|
|
|
void SessionData::GetCurrentRoles(class ScriptVector &roles) const
|
|
{
|
|
roles.Clear();
|
|
if(!IsValid())
|
|
return;
|
|
if(IsLoggedIn()) {
|
|
ScriptVariable rstr = GetUserParameter("roles");
|
|
if(rstr.IsValid())
|
|
roles = ScriptVector(rstr, ",", " \t\r\n");
|
|
}
|
|
roles.AddItem(IsLoggedIn() ? "auth" : "anon");
|
|
roles.AddItem("all");
|
|
}
|
|
|
|
void SessionData::SaveUsedRealname(const ScriptVariable &name)
|
|
{
|
|
if(!IsValid() || IsLoggedIn())
|
|
return;
|
|
info.SetItem("realname", name);
|
|
Save(false);
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserStatus() const
|
|
{
|
|
return GetUserParameter("status");
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserRealname() const
|
|
{
|
|
return IsLoggedIn() ?
|
|
GetUserParameter("realname") : info.GetItem("realname");
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserEmail() const
|
|
{
|
|
return GetUserParameter("email");
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserSite() const
|
|
{
|
|
return GetUserParameter("site");
|
|
}
|
|
|
|
bool SessionData::IsChangingEmail() const
|
|
{
|
|
ScriptVariable ne = GetUserNewEmail();
|
|
return ne.IsValid() && ne != "";
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserNewEmail() const
|
|
{
|
|
return GetUserParameter("new_email");
|
|
}
|
|
|
|
ScriptVariable SessionData::GetUserParameter(const char *id) const
|
|
{
|
|
if(!HasUser())
|
|
return ScriptVariableInv();
|
|
EnsureUserinfoRead();
|
|
return userinfo.GetItem(id);
|
|
}
|
|
|
|
long long SessionData::GetUserLastEvent(const char *ev) const
|
|
{
|
|
ScriptVariable evname = ScriptVariable("last_") + ev;
|
|
ScriptVariable s = GetUserParameter(evname.c_str());
|
|
if(s.IsInvalid() || s == "")
|
|
return -1;
|
|
long long res;
|
|
if(!s.GetLongLong(res, 10))
|
|
return -1;
|
|
return res;
|
|
}
|
|
|
|
bool SessionData::CheckUserStatus(ScriptVariable login, bool allow_pending)
|
|
{
|
|
username = login;
|
|
ScriptVariable status = GetUserStatus();
|
|
if(status.IsInvalid() ||
|
|
allow_pending ? (status.Trim().Tolower() == "blocked") :
|
|
(status.Trim().Tolower() != "active"))
|
|
{
|
|
username.Invalidate();
|
|
userinfo.Clear();
|
|
userinfo_read = false;
|
|
logged_in = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SessionData::Login(ScriptVariable lgn, ScriptVariable pt)
|
|
{
|
|
lgn.Tolower();
|
|
if(HasUser() && username != lgn)
|
|
return false;
|
|
pt.Toupper();
|
|
if(!check_username_safe(lgn.c_str()) || !check_passtoken_safe(pt.c_str()))
|
|
return false;
|
|
|
|
bool ok = CheckUserStatus(lgn, true);
|
|
if(!ok)
|
|
return false;
|
|
|
|
if(GetUserStatus() == "pending") { // very special case
|
|
ScriptVariable cc = GetUserParameter("confirmation_code");
|
|
cc.Trim().Toupper();
|
|
if(cc != pt)
|
|
return false;
|
|
// success! now some bookkeeping for the new welcomed user
|
|
userinfo.SetItem("status", "active");
|
|
userinfo.SetItem("confirmation_code", ScriptVariableInv());
|
|
ScriptVariable email = GetUserEmail();
|
|
if(!email_data)
|
|
email_data = new EmailData(dirname + "/" EMAIL_SUBDIR);
|
|
EmailInfo emi;
|
|
emi.status = "active";
|
|
emi.user = username;
|
|
email_data->Set(email, emi); // the date is updated there
|
|
// both session and user are saved a bit later
|
|
} else {
|
|
// for an active user, just check the password
|
|
bool passwd_ok = CheckAndRemovePassword(lgn, pt);
|
|
if(!passwd_ok)
|
|
return false;
|
|
}
|
|
|
|
// okay, successfully passed
|
|
username = lgn;
|
|
logged_in = true;
|
|
unsigned long now = time(0);
|
|
info.SetItem("user", username);
|
|
info.SetItem("logged_in", "yes");
|
|
info.SetItem("login_time", ScriptNumber(now));
|
|
Save(false);
|
|
UpdateUserLastLogin();
|
|
return true;
|
|
}
|
|
|
|
bool SessionData::SetUser(ScriptVariable lgn)
|
|
{
|
|
lgn.Tolower();
|
|
if(HasUser() && username != lgn)
|
|
return false;
|
|
if(!check_username_safe(lgn.c_str()))
|
|
return false;
|
|
|
|
#if 0
|
|
ScriptVariable fname =
|
|
dirname + "/" USER_SUBDIR "/" + lgn + "/" USER_FILENAME;
|
|
FileStat fs(fname.c_str());
|
|
if(!fs.Exists() || !fs.IsRegularFile())
|
|
return false;
|
|
#endif
|
|
|
|
bool ok = CheckUserStatus(lgn, true);
|
|
if(!ok)
|
|
return false;
|
|
|
|
info.SetItem("user", username);
|
|
info.SetItem("logged_in", logged_in ? "yes" : "no");
|
|
Save(false);
|
|
return true;
|
|
}
|
|
|
|
int SessionData::GetUserRemainingPasswords() const
|
|
{
|
|
if(!HasUser())
|
|
return -1;
|
|
ScriptVariable passdir = dirname + "/" USER_SUBDIR "/" + username;
|
|
|
|
const char *s;
|
|
ReadDir rdir(passdir.c_str());
|
|
if(!rdir.OpenOk())
|
|
return -1;
|
|
int fcount = 0;
|
|
while((s = rdir.Next())) {
|
|
if(!*s || *s == '.' || *s == '_')
|
|
continue;
|
|
fcount++;
|
|
}
|
|
return fcount;
|
|
}
|
|
|
|
static ScriptVariable make_random_password()
|
|
{
|
|
static const char digits[33] = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
// please note this must be exactly 32 chars long
|
|
// we don't use O, I, 0 and 1, so there are 26+10-4 = 32 chars
|
|
long long n;
|
|
fill_random(&n, sizeof(n));
|
|
ScriptVariable res = "";
|
|
int i;
|
|
for(i = 0; i < 8; i++) {
|
|
res += digits[n & 0x1f];
|
|
n >>= 5;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool SessionData::GenerateNewPasswords(ScriptVector &pds, int count) const
|
|
{
|
|
if(!HasUser())
|
|
return false;
|
|
ScriptVariable passdir = dirname + "/" USER_SUBDIR "/" + username;
|
|
ScriptVariable userdataf = passdir + "/" USER_FILENAME;
|
|
|
|
ScriptVector oldpwds;
|
|
const char *s;
|
|
ReadDir rd(passdir.c_str());
|
|
if(!rd.OpenOk())
|
|
return false;
|
|
while((s = rd.Next())) {
|
|
if(!*s || *s == '.' || *s == '_')
|
|
continue;
|
|
oldpwds.AddItem(s);
|
|
}
|
|
int i;
|
|
for(i = 0; i < oldpwds.Length(); i++)
|
|
unlink((passdir + "/" + oldpwds[i]).c_str());
|
|
|
|
pds.Clear();
|
|
for(i = 0; i < count; i++) {
|
|
ScriptVariable p = make_random_password();
|
|
ScriptVariable pfname = passdir + "/" + p;
|
|
int res = link(userdataf.c_str(), pfname.c_str());
|
|
if(res == -1)
|
|
continue;
|
|
pds.AddItem(p);
|
|
}
|
|
return pds.Length() > 0;
|
|
}
|
|
|
|
bool SessionData::EnsureUserinfoRead() const
|
|
{
|
|
if(!HasUser())
|
|
return false;
|
|
if(userinfo_read)
|
|
return true;
|
|
|
|
ConfigInformation *ui = const_cast<ConfigInformation*>(&userinfo);
|
|
bool rr = GetProfile(username, *ui);
|
|
if(!rr)
|
|
return false;
|
|
|
|
const_cast<SessionData*>(this)->userinfo_read = true;
|
|
return true;
|
|
}
|
|
|
|
bool SessionData::UpdateUserLastEvent(const char *ev)
|
|
{
|
|
if(!HasUser())
|
|
return false;
|
|
EnsureUserinfoRead();
|
|
|
|
long long now = time(0);
|
|
ScriptVariable evname = ScriptVariable("last_") + ev;
|
|
userinfo.SetItem(evname.c_str(), ScriptNumber(now));
|
|
return SaveUserinfo();
|
|
}
|
|
|
|
static ScriptVariable serialize_script_map(const ScriptMap &map)
|
|
{
|
|
ScriptVariable res = "";
|
|
ScriptVariable key, val;
|
|
ScriptMap::Iterator iter(map);
|
|
while(iter.GetNext(key, val)) {
|
|
if(val.IsInvalid())
|
|
continue;
|
|
ScriptVector lines(val, "\n", " \t\r");
|
|
lines[0] = key + " = " + lines[0];
|
|
res += lines.Join("\n+");
|
|
res += "\n\n";
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool SessionData::Save(bool creation)
|
|
{
|
|
if(creation)
|
|
make_dir_if_necessary((dirname + "/" SESS_SUBDIR).c_str());
|
|
ScriptVariable infostr = serialize_script_map(info);
|
|
ScriptVariable fname = dirname + "/" SESS_SUBDIR "/" + sess_fn;
|
|
int fd = open(fname.c_str(),
|
|
creation ? O_WRONLY|O_CREAT|O_EXCL : O_WRONLY|O_TRUNC,
|
|
0600);
|
|
if(fd == -1)
|
|
return false;
|
|
int wc = write(fd, infostr.c_str(), infostr.Length());
|
|
int cr = close(fd);
|
|
return wc == infostr.Length() && cr != -1;
|
|
}
|
|
|
|
bool SessionData::SaveUserinfo()
|
|
{
|
|
if(!HasUser() || !userinfo_read)
|
|
return false; // actually this is a bug!
|
|
ScriptVariable userdataf =
|
|
dirname + "/" USER_SUBDIR "/" + username + "/" USER_FILENAME;
|
|
ScriptVariable infostr = serialize_script_map(userinfo);
|
|
int fd = open(userdataf.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600);
|
|
if(fd == -1)
|
|
return false;
|
|
int wc = write(fd, infostr.c_str(), infostr.Length());
|
|
int cr = close(fd);
|
|
return wc == infostr.Length() && cr != -1;
|
|
}
|
|
|
|
int SessionData::CheckAndReserveEmail(const ScriptVariable &email,
|
|
const ScriptVariable &login)
|
|
{
|
|
if(!email_data)
|
|
email_data = new EmailData(dirname + "/" EMAIL_SUBDIR);
|
|
EmailInfo emi;
|
|
bool ok = email_data->Get(email, emi);
|
|
if(!ok)
|
|
return cu_conf_error;
|
|
emi.status.Trim().Tolower();
|
|
if(emi.status == "banned" || emi.status == "blocked")
|
|
return cu_email_banned;
|
|
if(emi.status == "used" || emi.status == "active")
|
|
return cu_email_used;
|
|
if((emi.status == "replaced" || emi.status == "pending_replaced")
|
|
&& emi.user != login)
|
|
{
|
|
return cu_email_replaced;
|
|
}
|
|
if(emi.status == "pending") {
|
|
if(emi.date != -1 && time(0) - emi.date < tried_email_cooldown) {
|
|
return cu_email_recently_tried;
|
|
} else {
|
|
// in this case, the address was claimed some time ago,
|
|
// but then was never confirmed, so we allow for it
|
|
// to be used (in case the new claimer will confirm the claim)
|
|
|
|
// XXX XXX for pending, check THAT user, and update if necessary
|
|
|
|
// that user may be active (with another address), in this
|
|
// case there's nothing to be done
|
|
|
|
// however, if it is still pending, AND the address matches,
|
|
// we should perhaps just remove that user from the system
|
|
}
|
|
}
|
|
if(emi.status.IsValid() && emi.status != "" &&
|
|
emi.status != "pending" && emi.status != "replaced" &&
|
|
emi.status != "pending_replaced")
|
|
{
|
|
return cu_conf_error; // unknown email status
|
|
}
|
|
|
|
emi.user = login;
|
|
if(emi.status == "replaced")
|
|
emi.status = "pending_replaced";
|
|
else
|
|
emi.status = "pending";
|
|
email_data->Set(email, emi); // the date is updated there
|
|
|
|
return cu_success;
|
|
}
|
|
|
|
int SessionData::CreateUser(ScriptVariable lgn, const ScriptVariable &name,
|
|
const ScriptVariable &email, const ScriptVariable &site,
|
|
ScriptVariable &confirmation_code)
|
|
{
|
|
lgn.Tolower();
|
|
if(logged_in || (HasUser() && username != lgn))
|
|
return cu_bug;
|
|
if(!check_username_acceptable(lgn.c_str()))
|
|
return cu_bad_id;
|
|
|
|
ConfigInformation prof;
|
|
if(GetProfile(lgn, prof)) { // well, something exists
|
|
ScriptVariable status = prof.GetItem("status");
|
|
if(status != "pending")
|
|
return cu_exists;
|
|
// for "pending", let's check the time
|
|
long long t;
|
|
bool ok = prof.GetItem("last_pwdsent").GetLongLong(t, 10);
|
|
if(!ok || time(0) - t < pending_time_max)
|
|
return cu_exists;
|
|
// well, it is "pending" and too old, so just continue
|
|
}
|
|
|
|
int check_res = CheckAndReserveEmail(email, lgn);
|
|
if(check_res != cu_success)
|
|
return check_res;
|
|
|
|
// it seems no more obstacles to go on
|
|
|
|
username = lgn;
|
|
logged_in = false; // a bit of paranoia, it can't be true anyway
|
|
EnsureUserinfoRead(); // just in case there's more info than we set
|
|
userinfo_read = true; // we force it for the case (almost always)
|
|
// there was nothing to read (no file)
|
|
userinfo.SetItem("status", "pending");
|
|
userinfo.SetItem("email", email);
|
|
userinfo.SetItem("realname", name);
|
|
userinfo.SetItem("created", ScriptNumber(time(0)));
|
|
if(site.IsValid() && site != "")
|
|
userinfo.SetItem("site", site);
|
|
confirmation_code = make_random_password();
|
|
userinfo.SetItem("confirmation_code", confirmation_code);
|
|
|
|
make_dir_if_necessary((dirname + "/" USER_SUBDIR).c_str());
|
|
make_dir_if_necessary((dirname + "/" USER_SUBDIR "/" + username).c_str());
|
|
UpdateUserLastPwdsent(); // this will save userinfo
|
|
|
|
info.SetItem("user", username);
|
|
info.SetItem("logged_in", "no");
|
|
Save(false);
|
|
|
|
return cu_success;
|
|
}
|
|
|
|
bool SessionData::CanRequestEmailChange() const
|
|
{
|
|
if(!logged_in)
|
|
return false;
|
|
|
|
EnsureUserinfoRead();
|
|
ScriptVariable last_mc_str = userinfo.GetItem("last_mailchange");
|
|
if(last_mc_str.IsValid() && last_mc_str != "") {
|
|
long long last_mc;
|
|
bool ok = last_mc_str.GetLongLong(last_mc, 10);
|
|
if(ok && time(0) - last_mc < email_change_cooldown)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int SessionData::EmailChangeRequest(const ScriptVariable &newemail,
|
|
const ScriptVariable &passtoken,
|
|
ScriptVariable &confirmation_code)
|
|
{
|
|
if(!logged_in)
|
|
return cu_bug;
|
|
|
|
ScriptVariable pt = passtoken;
|
|
pt.Toupper();
|
|
if(!check_passtoken_safe(pt.c_str()))
|
|
return cu_change_req_bad_passwd;
|
|
|
|
bool passwd_ok = CheckAndRemovePassword(username, pt);
|
|
if(!passwd_ok)
|
|
return cu_change_req_bad_passwd;
|
|
|
|
if(!CanRequestEmailChange())
|
|
return cu_change_req_too_early;
|
|
|
|
int check_res = CheckAndReserveEmail(newemail, username);
|
|
if(check_res != cu_success)
|
|
return check_res;
|
|
|
|
confirmation_code = make_random_password();
|
|
|
|
userinfo.SetItem("new_email", newemail);
|
|
userinfo.SetItem("confirmation_code", confirmation_code);
|
|
UpdateUserLastEvent("mailchange"); // this will save userinfo
|
|
|
|
return cu_success;
|
|
}
|
|
|
|
bool SessionData::EmailChangeConfirm(ScriptVariable confirmation_code)
|
|
{
|
|
confirmation_code.Trim().Toupper();
|
|
EnsureUserinfoRead();
|
|
ScriptVariable code = userinfo.GetItem("confirmation_code");
|
|
code.Trim().Toupper();
|
|
if(confirmation_code != code)
|
|
return false;
|
|
|
|
ScriptVariable old_email = userinfo.GetItem("email");
|
|
ScriptVariable new_email = userinfo.GetItem("new_email");
|
|
userinfo.SetItem("email", new_email);
|
|
userinfo.SetItem("new_email", ScriptVariableInv());
|
|
userinfo.SetItem("confirmation_code", ScriptVariableInv());
|
|
SaveUserinfo();
|
|
|
|
if(!email_data)
|
|
email_data = new EmailData(dirname + "/" EMAIL_SUBDIR);
|
|
EmailInfo emi;
|
|
email_data->Get(old_email, emi);
|
|
emi.status = "replaced";
|
|
emi.user = username;
|
|
email_data->Set(old_email, emi);
|
|
email_data->Get(new_email, emi);
|
|
emi.status = "active";
|
|
emi.user = username;
|
|
email_data->Set(new_email, emi);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SessionData::EmailChangeCancel()
|
|
{
|
|
ScriptVariable new_email = userinfo.GetItem("new_email");
|
|
userinfo.SetItem("new_email", ScriptVariableInv());
|
|
userinfo.SetItem("confirmation_code", ScriptVariableInv());
|
|
SaveUserinfo();
|
|
|
|
if(!email_data)
|
|
email_data = new EmailData(dirname + "/" EMAIL_SUBDIR);
|
|
EmailInfo emi;
|
|
email_data->Get(new_email, emi);
|
|
if(emi.status == "pending_replaced") {
|
|
emi.status = "replaced";
|
|
email_data->Set(new_email, emi);
|
|
} else {
|
|
email_data->Forget(new_email);
|
|
}
|
|
}
|
|
|
|
bool SessionData::UpdateUser(const ScriptVariable &name,
|
|
const ScriptVariable &site)
|
|
{
|
|
if(!IsLoggedIn())
|
|
return false;
|
|
if(!EnsureUserinfoRead())
|
|
return false;
|
|
userinfo.SetItem("realname", name);
|
|
if(site.IsValid() && site != "")
|
|
userinfo.SetItem("site", site);
|
|
UpdateUserLastSeen();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SessionData::GetProfile(ScriptVariable login, ConfigInformation &prof) const
|
|
{
|
|
ScriptVariable fname =
|
|
dirname + "/" USER_SUBDIR "/" + login + "/" USER_FILENAME;
|
|
prof.Clear();
|
|
if(!prof.FOpen(fname.c_str()))
|
|
return false;
|
|
bool rr = prof.RunParser();
|
|
prof.FClose();
|
|
return rr;
|
|
}
|
|
|
|
|
|
ScriptVariable SessionData::GetPremodQueueDir() const
|
|
{
|
|
return dirname + "/" PREMOD_QUEUE_SUBDIR;
|
|
}
|
|
|
|
bool SessionData::CheckAndRemovePassword(const ScriptVariable &lgn,
|
|
const ScriptVariable &pt)
|
|
{
|
|
ScriptVariable passfname =
|
|
dirname + "/" USER_SUBDIR "/" + lgn + "/" + pt;
|
|
int res = unlink(passfname.c_str());
|
|
return res != -1;
|
|
}
|
|
|
|
#if 0
|
|
static void place_to_premod(const PremodQueueData &qd, int id,
|
|
const ScriptVariable &cmt_fname)
|
|
{
|
|
if(-1 == check_and_make_dir(qd.dir))
|
|
return;
|
|
ScriptVariable linkname =
|
|
qd.dir + "/" + qd.realm + "=" + qd.page_id + "=" + ScriptNumber(id);
|
|
ScriptVariable shortpath = short_link_path(cmt_fname, linkname);
|
|
symlink(shortpath.c_str(), linkname.c_str());
|
|
}
|
|
#endif
|
|
|
|
void SessionData::AddToPremodQueue(const ScriptVariable &pgid, int cmtid,
|
|
const ScriptVariable &cmt_file) const
|
|
{
|
|
ScriptVariable dir = GetPremodQueueDir();
|
|
if(!make_dir_if_necessary(dir.c_str()))
|
|
return;
|
|
ScriptVariable linkname =
|
|
dir + "/" + pgid + "=" + ScriptNumber(cmtid);
|
|
ScriptVariable shortpath = short_link_path(cmt_file, linkname);
|
|
symlink(shortpath.c_str(), linkname.c_str());
|
|
}
|
|
|
|
int SessionData::GetPremodQueue(ScriptVector &res) const
|
|
{
|
|
const char *s;
|
|
ReadDir rd(GetPremodQueueDir().c_str());
|
|
if(!rd.OpenOk())
|
|
return -1;
|
|
while((s = rd.Next())) {
|
|
if(!*s || *s == '.' || *s == '_')
|
|
continue;
|
|
ScriptVariable ssv(s);
|
|
res.AddItem(ssv);
|
|
}
|
|
return res.Length();
|
|
}
|
|
|
|
void SessionData::RemoveFromPremodQueue(const ScriptVariable &pgid, int cmtid)
|
|
{
|
|
ScriptVariable dir = GetPremodQueueDir();
|
|
if(!make_dir_if_necessary(dir.c_str()))
|
|
return;
|
|
ScriptVariable linkname =
|
|
dir + "/" + pgid + "=" + ScriptNumber(cmtid);
|
|
unlink(linkname.c_str());
|
|
}
|
|
|
|
bool SessionData::CheckAndStoreNonce(const ScriptVariable &timemark,
|
|
const ScriptVariable &nonce)
|
|
{
|
|
ScriptVariable dn(dirname + "/" NONCE_SUBDIR);
|
|
make_dir_if_necessary(dn.c_str());
|
|
ScriptVariable dummy_file(dn + "/_dummy");
|
|
int fd = open(dummy_file.c_str(), O_WRONLY|O_CREAT, 0600);
|
|
if(fd != -1)
|
|
close(fd);
|
|
ScriptVariable fname(dn + "/" + timemark + "_" + nonce);
|
|
int r = link(dummy_file.c_str(), fname.c_str());
|
|
if(r == -1)
|
|
return false;
|
|
|
|
////////////////////////
|
|
// -- cleanup --
|
|
// note cleanup is only done after a new nonce stored successfully
|
|
long long now = time(0);
|
|
|
|
ReadDir rdir(dn.c_str());
|
|
if(rdir.OpenOk()) {
|
|
const char *s;
|
|
while((s = rdir.Next())) {
|
|
if(!*s || *s == '.' || *s == '_')
|
|
continue;
|
|
ScriptVector comp(s, "_");
|
|
if(comp.Length() != 2)
|
|
continue;
|
|
long long t;
|
|
if(!comp[0].GetLongLong(t, 10))
|
|
continue;
|
|
if(now - t > captcha_ttl + 1) {
|
|
ScriptVariable fn(dn + "/" + s);
|
|
unlink(fn.c_str());
|
|
}
|
|
}
|
|
}
|
|
// -- end of cleanup
|
|
////////////////////////
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
// cleanup implementation
|
|
|
|
#define CLEANUP_LOCK_FILENAME "__cleanup_lock"
|
|
#define CLEANUP_TIME_FILENAME "__next_cleanup"
|
|
#define CLEANUP_PERIOD (3*3600)
|
|
#define CLEANUP_LOCK_REMOVAL_ADDITION 600
|
|
|
|
|
|
static bool write_next_cleanup_time(const ScriptVariable &dir, long long tm)
|
|
{
|
|
ScriptVariable fname = dir + "/" + CLEANUP_TIME_FILENAME;
|
|
ScriptVariable fname_tmp = fname + "." + ScriptNumber(getpid());
|
|
int fd = open(fname_tmp.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0600);
|
|
if(fd == -1)
|
|
return false;
|
|
int wc = write(fd, &tm, sizeof(tm));
|
|
close(fd);
|
|
if(wc != sizeof(tm)) {
|
|
unlink(fname_tmp.c_str());
|
|
return false;
|
|
}
|
|
return -1 != rename(fname_tmp.c_str(), fname.c_str());
|
|
}
|
|
|
|
static long long get_next_cleanup_time(const ScriptVariable &dir)
|
|
{
|
|
ScriptVariable fname = dir + "/" + CLEANUP_TIME_FILENAME;
|
|
int fd = open(fname.c_str(), O_RDONLY);
|
|
if(fd == -1)
|
|
return -1;
|
|
long long tm;
|
|
int rc;
|
|
rc = read(fd, &tm, sizeof(tm));
|
|
close(fd);
|
|
if(rc != sizeof(tm))
|
|
return -1;
|
|
return tm;
|
|
}
|
|
|
|
static void
|
|
remove_expired_session_files(const ScriptVariable &dir, long long now)
|
|
{
|
|
const char *s;
|
|
ReadDir rd(dir.c_str());
|
|
while((s = rd.Next())) {
|
|
ConfigInformation info;
|
|
bool ok = info.FOpen((dir + "/" + s).c_str());
|
|
if(!ok)
|
|
continue;
|
|
bool rd = info.RunParser();
|
|
info.FClose();
|
|
if(!rd)
|
|
continue;
|
|
ScriptVariable expire = info.GetItem("expire");
|
|
bool remv;
|
|
if(expire.IsInvalid())
|
|
remv = true;
|
|
else {
|
|
long long et;
|
|
if(expire.GetLongLong(et, 10))
|
|
remv = et < now;
|
|
else
|
|
remv = true;
|
|
}
|
|
if(remv)
|
|
unlink((dir + "/" + s).c_str());
|
|
}
|
|
}
|
|
|
|
static bool have_group(unsigned int gid)
|
|
{
|
|
if(getegid() == gid)
|
|
return true;
|
|
int cnt = getgroups(0, 0);
|
|
gid_t *v = new gid_t[cnt];
|
|
getgroups(cnt, v);
|
|
int i;
|
|
bool res = false;
|
|
for(i = 0; i < cnt; i++) {
|
|
if(gid == v[i]) {
|
|
res = true;
|
|
break;
|
|
}
|
|
}
|
|
delete[] v;
|
|
return res;
|
|
}
|
|
|
|
void SessionData::PerformCleanup(int ttl)
|
|
{
|
|
if(dirname.IsInvalid() || dirname == "")
|
|
return;
|
|
|
|
struct stat st;
|
|
int res;
|
|
|
|
long long now = time(0);
|
|
|
|
ScriptVariable sessdirname = dirname + "/" SESS_SUBDIR;
|
|
|
|
// most of the time we don't proceed because the time didn't come
|
|
// so this check is to be done first
|
|
long long tt = get_next_cleanup_time(sessdirname);
|
|
if(tt != -1 && tt >= now)
|
|
return;
|
|
|
|
// now check if the directory exists and is accessible
|
|
// note that we really need full access to the directory, including
|
|
// read access, or else we will not be able to perform cleanup
|
|
res = stat(sessdirname.c_str(), &st);
|
|
if(res == -1)
|
|
return;
|
|
bool accessible = ((0007 & st.st_mode) == 0007) ||
|
|
(have_group(st.st_gid) && ((0070 & st.st_mode) == 0070)) ||
|
|
((geteuid() == st.st_uid) && ((0700 & st.st_mode) == 0700));
|
|
if(!accessible)
|
|
return;
|
|
|
|
// now check the lock (may be someone else is already doing this)
|
|
ScriptVariable lockfname = sessdirname + "/" CLEANUP_LOCK_FILENAME;
|
|
res = stat(lockfname.c_str(), &st);
|
|
if(res != -1) {
|
|
// Lock exists, but if it is too old, we will remove it
|
|
// so that someone else will be able to perform the cleanup when
|
|
// the time comes. In this case, we'll add some time to the
|
|
// next cleanup time so that some time has to pass before
|
|
// someone actually clenup
|
|
if(st.st_mtime < now - CLEANUP_PERIOD) {
|
|
long long tt = now + CLEANUP_LOCK_REMOVAL_ADDITION;
|
|
if(!write_next_cleanup_time(sessdirname, tt))
|
|
return;
|
|
sync();
|
|
unlink(lockfname.c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// well, seems we're to go! try to get the lock
|
|
|
|
int fd = open(lockfname.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0600);
|
|
if(fd == -1) // failed to get the lock, let someone else try :-)
|
|
return;
|
|
close(fd);
|
|
|
|
if(!write_next_cleanup_time(sessdirname, now + CLEANUP_PERIOD))
|
|
return;
|
|
|
|
// now the cleanup!!!
|
|
remove_expired_session_files(sessdirname, now);
|
|
|
|
// remove the lock
|
|
unlink(lockfname.c_str());
|
|
}
|
|
|