X-Git-Url: https://git.stg.codes/stg.git/blobdiff_plain/4271ab433cd55bbd2612292bcf39e4dc3d7274f1..0907aa4037b12b6b88ee24495d4577a064d4f8db:/projects/stargazer/user_impl.cpp diff --git a/projects/stargazer/user_impl.cpp b/projects/stargazer/user_impl.cpp new file mode 100644 index 00000000..2b7cc3a9 --- /dev/null +++ b/projects/stargazer/user_impl.cpp @@ -0,0 +1,1543 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Date: 27.10.2002 + */ + +/* + * Author : Boris Mikhailenko + */ + +/* + $Revision: 1.101 $ + $Date: 2010/11/03 10:50:03 $ + $Author: faust $ + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "user_impl.h" +#include "settings_impl.h" +#include "stg_timer.h" + +#include "stg/users.h" +#include "stg/common.h" +#include "stg/scriptexecuter.h" +#include "stg/tariff.h" +#include "stg/tariffs.h" +#include "stg/services.h" +#include "stg/service_conf.h" +#include "stg/admin.h" + +#include +#include + +#include +#include +#include + +#include +#include // access + +using STG::UserImpl; + +namespace +{ + +std::string dirsToString(const bool * dirs) +{ +std::string res; +for (size_t i = 0; i < DIR_NUM; i++) + res += dirs[i] ? '1' : '0'; +return res; +} + +void dirsFromBits(bool * dirs, uint32_t bits) +{ +for (size_t i = 0; i < DIR_NUM; i++) + dirs[i] = bits & (1 << i); +} + +} + +UserImpl::UserImpl(const Settings * s, + const Store * st, + const Tariffs * t, + const Admin * a, + const Users * u, + const Services & svcs) + : users(u), + properties(*s), + WriteServLog(Logger::get()), + lastScanMessages(0), + id(0), + __connected(0), + connected(__connected), + __currIP(0), + currIP(__currIP), + lastIPForDisconnect(0), + pingTime(0), + sysAdmin(a), + store(st), + tariffs(t), + tariff(NULL), + m_services(svcs), + settings(s), + authorizedModificationTime(0), + deleted(false), + lastWriteStat(0), + lastWriteDetailedStat(0), + cash(properties.cash), + up(properties.up), + down(properties.down), + lastCashAdd(properties.lastCashAdd), + passiveTime(properties.passiveTime), + lastCashAddTime(properties.lastCashAddTime), + freeMb(properties.freeMb), + lastActivityTime(properties.lastActivityTime), + password(properties.password), + passive(properties.passive), + disabled(properties.disabled), + disabledDetailStat(properties.disabledDetailStat), + alwaysOnline(properties.alwaysOnline), + tariffName(properties.tariffName), + nextTariff(properties.nextTariff), + address(properties.address), + note(properties.note), + group(properties.group), + email(properties.email), + phone(properties.phone), + realName(properties.realName), + credit(properties.credit), + creditExpire(properties.creditExpire), + ips(properties.ips), + userdata0(properties.userdata0), + userdata1(properties.userdata1), + userdata2(properties.userdata2), + userdata3(properties.userdata3), + userdata4(properties.userdata4), + userdata5(properties.userdata5), + userdata6(properties.userdata6), + userdata7(properties.userdata7), + userdata8(properties.userdata8), + userdata9(properties.userdata9), + sessionUploadModTime(stgTime), + sessionDownloadModTime(stgTime), + passiveNotifier(this), + disabledNotifier(this), + tariffNotifier(this), + cashNotifier(this), + ipNotifier(this) +{ +Init(); +} +//----------------------------------------------------------------------------- +void UserImpl::Init() +{ +password = "*_EMPTY_PASSWORD_*"; +tariffName = NO_TARIFF_NAME; +tariff = tariffs->FindByName(tariffName); +ips = UserIPs::parse("*"); +lastWriteStat = stgTime + random() % settings->GetStatWritePeriod(); +lastWriteDetailedStat = stgTime; + +properties.tariffName.AddBeforeNotifier(&tariffNotifier); +properties.passive.AddBeforeNotifier(&passiveNotifier); +properties.disabled.AddAfterNotifier(&disabledNotifier); +properties.cash.AddBeforeNotifier(&cashNotifier); +ips.AddAfterNotifier(&ipNotifier); + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +} +//----------------------------------------------------------------------------- +UserImpl::UserImpl(const UserImpl & u) + : users(u.users), + properties(*u.settings), + WriteServLog(Logger::get()), + lastScanMessages(0), + login(u.login), + id(u.id), + __connected(0), + connected(__connected), + __currIP(u.__currIP), + currIP(__currIP), + lastIPForDisconnect(0), + pingTime(u.pingTime), + sysAdmin(u.sysAdmin), + store(u.store), + tariffs(u.tariffs), + tariff(u.tariff), + m_services(u.m_services), + traffStat(u.traffStat), + traffStatSaved(u.traffStatSaved), + settings(u.settings), + authorizedModificationTime(u.authorizedModificationTime), + messages(u.messages), + deleted(u.deleted), + lastWriteStat(u.lastWriteStat), + lastWriteDetailedStat(u.lastWriteDetailedStat), + cash(properties.cash), + up(properties.up), + down(properties.down), + lastCashAdd(properties.lastCashAdd), + passiveTime(properties.passiveTime), + lastCashAddTime(properties.lastCashAddTime), + freeMb(properties.freeMb), + lastActivityTime(properties.lastActivityTime), + password(properties.password), + passive(properties.passive), + disabled(properties.disabled), + disabledDetailStat(properties.disabledDetailStat), + alwaysOnline(properties.alwaysOnline), + tariffName(properties.tariffName), + nextTariff(properties.nextTariff), + address(properties.address), + note(properties.note), + group(properties.group), + email(properties.email), + phone(properties.phone), + realName(properties.realName), + credit(properties.credit), + creditExpire(properties.creditExpire), + ips(properties.ips), + userdata0(properties.userdata0), + userdata1(properties.userdata1), + userdata2(properties.userdata2), + userdata3(properties.userdata3), + userdata4(properties.userdata4), + userdata5(properties.userdata5), + userdata6(properties.userdata6), + userdata7(properties.userdata7), + userdata8(properties.userdata8), + userdata9(properties.userdata9), + sessionUpload(), + sessionDownload(), + sessionUploadModTime(stgTime), + sessionDownloadModTime(stgTime), + passiveNotifier(this), + disabledNotifier(this), + tariffNotifier(this), + cashNotifier(this), + ipNotifier(this) +{ +if (&u == this) + return; + +properties.tariffName.AddBeforeNotifier(&tariffNotifier); +properties.passive.AddBeforeNotifier(&passiveNotifier); +properties.disabled.AddAfterNotifier(&disabledNotifier); +properties.cash.AddBeforeNotifier(&cashNotifier); +ips.AddAfterNotifier(&ipNotifier); + +properties.SetProperties(u.properties); + +pthread_mutexattr_t attr; +pthread_mutexattr_init(&attr); +pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +pthread_mutex_init(&mutex, &attr); +} +//----------------------------------------------------------------------------- +UserImpl::~UserImpl() +{ +properties.tariffName.DelBeforeNotifier(&tariffNotifier); +properties.passive.DelBeforeNotifier(&passiveNotifier); +properties.disabled.DelAfterNotifier(&disabledNotifier); +properties.cash.DelBeforeNotifier(&cashNotifier); +pthread_mutex_destroy(&mutex); +} +//----------------------------------------------------------------------------- +void UserImpl::SetLogin(const std::string & l) +{ +STG_LOCKER lock(&mutex); +static int idGen = 0; +assert(login.empty() && "Login is already set"); +login = l; +id = idGen++; +} +//----------------------------------------------------------------------------- +int UserImpl::ReadConf() +{ +STG_LOCKER lock(&mutex); +UserConf conf; + +if (store->RestoreUserConf(&conf, login)) + { + WriteServLog("Cannot read conf for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot read conf for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +properties.SetConf(conf); + +tariff = tariffs->FindByName(tariffName); +if (tariff == NULL) + { + WriteServLog("Cannot read user %s. Tariff %s not exist.", + login.c_str(), properties.tariffName.Get().c_str()); + return -1; + } + +std::vector hdrsList; + +if (store->GetMessageHdrs(&hdrsList, login)) + { + printfd(__FILE__, "Error GetMessageHdrs %s\n", store->GetStrError().c_str()); + WriteServLog("Cannot read user %s. Error reading message headers: %s.", + login.c_str(), + store->GetStrError().c_str()); + return -1; + } + +std::vector::const_iterator it; +for (it = hdrsList.begin(); it != hdrsList.end(); ++it) + { + Message msg; + if (store->GetMessage(it->id, &msg, login) == 0) + { + messages.push_back(msg); + } + } + +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::ReadStat() +{ +STG_LOCKER lock(&mutex); +UserStat stat; + +if (store->RestoreUserStat(&stat, login)) + { + WriteServLog("Cannot read stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot read stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +properties.SetStat(stat); + +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::WriteConf() +{ +STG_LOCKER lock(&mutex); +UserConf conf(properties.GetConf()); + +printfd(__FILE__, "UserImpl::WriteConf()\n"); + +if (store->SaveUserConf(conf, login)) + { + WriteServLog("Cannot write conf for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write conf for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::WriteStat() +{ +STG_LOCKER lock(&mutex); +UserStat stat(properties.GetStat()); + +if (store->SaveUserStat(stat, login)) + { + WriteServLog("Cannot write stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +lastWriteStat = stgTime; + +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::WriteMonthStat() +{ +STG_LOCKER lock(&mutex); +time_t tt = stgTime - 3600; +struct tm t1; +localtime_r(&tt, &t1); + +UserStat stat(properties.GetStat()); +if (store->SaveMonthStat(stat, t1.tm_mon, t1.tm_year, login)) + { + WriteServLog("Cannot write month stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + printfd(__FILE__, "Cannot write month stat for user %s.\n", login.c_str()); + printfd(__FILE__, "%s\n", store->GetStrError().c_str()); + return -1; + } + +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::Authorize(uint32_t ip, uint32_t dirs, const Auth * auth) +{ +STG_LOCKER lock(&mutex); +/* + * Authorize user. It only means that user will be authorized. Nothing more. + * User can be connected or disconnected while authorized. + * Example: user is authorized but disconnected due to 0 money or blocking + */ + +/* + * TODO: in fact "authorization" means allowing access to a service. What we + * call "authorization" here, int STG, is "authentication". So this should be + * fixed in future. + */ + +/* + * Prevent double authorization by identical authorizers + */ +if (authorizedBy.find(auth) != authorizedBy.end()) + return 0; + +if (!ip) + return -1; + +dirsFromBits(enabledDirs, dirs); + +if (!authorizedBy.empty()) + { + if (currIP != ip) + { + // We are already authorized, but with different IP address + errorStr = "User " + login + " already authorized with IP address " + inet_ntostring(ip); + return -1; + } + + User * u = NULL; + if (!users->FindByIPIdx(ip, &u)) + { + // Address presents in IP-index. + // If it's not our IP - report it. + if (u != this) + { + errorStr = "IP address " + inet_ntostring(ip) + " is already in use"; + return -1; + } + } + } +else + { + if (users->IsIPInIndex(ip)) + { + // Address is already present in IP-index. + errorStr = "IP address " + inet_ntostring(ip) + " is already in use"; + return -1; + } + + if (ips.ConstData().find(ip)) + { + currIP = ip; + lastIPForDisconnect = currIP; + } + else + { + printfd(__FILE__, " user %s: ips = %s\n", login.c_str(), ips.ConstData().toString().c_str()); + errorStr = "IP address " + inet_ntostring(ip) + " does not belong to user " + login; + return -1; + } + } + +if (authorizedBy.empty()) + authorizedModificationTime = stgTime; +authorizedBy.insert(auth); + +ScanMessage(); + +return 0; +} +//----------------------------------------------------------------------------- +void UserImpl::Unauthorize(const Auth * auth, const std::string & reason) +{ +STG_LOCKER lock(&mutex); +/* + * Authorizer tries to unauthorize user, that was not authorized by it + */ +if (!authorizedBy.erase(auth)) + return; + +authorizedModificationTime = stgTime; + +if (authorizedBy.empty()) + { + lastDisconnectReason = reason; + lastIPForDisconnect = currIP; + currIP = 0; // DelUser in traffcounter + if (connected) + Disconnect(false, "not authorized"); + return; + } +} +//----------------------------------------------------------------------------- +bool UserImpl::IsAuthorizedBy(const Auth * auth) const +{ +STG_LOCKER lock(&mutex); +// Is this user authorized by specified authorizer? +return authorizedBy.find(auth) != authorizedBy.end(); +} +//----------------------------------------------------------------------------- +std::vector UserImpl::GetAuthorizers() const +{ + STG_LOCKER lock(&mutex); + std::vector list; + std::transform(authorizedBy.begin(), authorizedBy.end(), std::back_inserter(list), [](const auto auth){ return auth->GetVersion(); }); + return list; +} +//----------------------------------------------------------------------------- +void UserImpl::Connect(bool fakeConnect) +{ +/* + * Connect user to Internet. This function is differ from Authorize() !!! + */ + +STG_LOCKER lock(&mutex); + +if (!fakeConnect) + { + std::string scriptOnConnect = settings->GetScriptsDir() + "/OnConnect"; + + if (access(scriptOnConnect.c_str(), X_OK) == 0) + { + std::string dirs = dirsToString(enabledDirs); + + std::string scriptOnConnectParams; + strprintf(&scriptOnConnectParams, + "%s \"%s\" \"%s\" \"%f\" \"%d\" \"%s\"", + scriptOnConnect.c_str(), + login.c_str(), + inet_ntostring(currIP).c_str(), + cash.ConstData(), + id, + dirs.c_str()); + + std::vector::const_iterator it(settings->GetScriptParams().begin()); + while (it != settings->GetScriptParams().end()) + { + scriptOnConnectParams += " \"" + GetParamValue(it->c_str()) + "\""; + ++it; + } + + ScriptExec(scriptOnConnectParams.c_str()); + } + else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnConnect.c_str()); + } + + connected = true; + } + +if (!settings->GetDisableSessionLog() && store->WriteUserConnect(login, currIP)) + { + WriteServLog("Cannot write connect for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + +if (!fakeConnect) + lastIPForDisconnect = currIP; +} +//----------------------------------------------------------------------------- +void UserImpl::Disconnect(bool fakeDisconnect, const std::string & reason) +{ +/* + * Disconnect user from Internet. This function is differ from UnAuthorize() !!! + */ + +STG_LOCKER lock(&mutex); + +if (!lastIPForDisconnect) + { + printfd(__FILE__, "lastIPForDisconnect\n"); + return; + } + +if (!fakeDisconnect) + { + lastDisconnectReason = reason; + std::string scriptOnDisonnect = settings->GetScriptsDir() + "/OnDisconnect"; + + if (access(scriptOnDisonnect.c_str(), X_OK) == 0) + { + std::string dirs = dirsToString(enabledDirs); + + std::string scriptOnDisonnectParams; + strprintf(&scriptOnDisonnectParams, + "%s \"%s\" \"%s\" \"%f\" \"%d\" \"%s\"", + scriptOnDisonnect.c_str(), + login.c_str(), + inet_ntostring(lastIPForDisconnect).c_str(), + cash.ConstData(), + id, + dirs.c_str()); + + std::vector::const_iterator it(settings->GetScriptParams().begin()); + while (it != settings->GetScriptParams().end()) + { + scriptOnDisonnectParams += " \"" + GetParamValue(it->c_str()) + "\""; + ++it; + } + + ScriptExec(scriptOnDisonnectParams.c_str()); + } + else + { + WriteServLog("Script OnDisconnect cannot be executed. File not found."); + } + + connected = false; + } + +std::string reasonMessage(reason); +if (!lastDisconnectReason.empty()) + reasonMessage += ": " + lastDisconnectReason; + +if (!settings->GetDisableSessionLog() && store->WriteUserDisconnect(login, up, down, sessionUpload, sessionDownload, + cash, freeMb, reasonMessage)) + { + WriteServLog("Cannot write disconnect for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + } + +if (!fakeDisconnect) + lastIPForDisconnect = 0; + +sessionUpload.reset(); +sessionDownload.reset(); +sessionUploadModTime = stgTime; +sessionDownloadModTime = stgTime; +} +//----------------------------------------------------------------------------- +void UserImpl::Run() +{ +STG_LOCKER lock(&mutex); + +if (stgTime > static_cast(lastWriteStat + settings->GetStatWritePeriod())) + { + printfd(__FILE__, "UserImpl::WriteStat user=%s\n", GetLogin().c_str()); + WriteStat(); + } +if (creditExpire.ConstData() && creditExpire.ConstData() < stgTime) + { + WriteServLog("User: %s. Credit expired.", login.c_str()); + credit = 0; + creditExpire = 0; + WriteConf(); + } + +if (passive.ConstData() + && (stgTime % 30 == 0) + && (passiveTime.ModificationTime() != stgTime)) + { + passiveTime = passiveTime + (stgTime - passiveTime.ModificationTime()); + printfd(__FILE__, "===== %s: passiveTime=%d =====\n", login.c_str(), passiveTime.ConstData()); + } + +if (!authorizedBy.empty()) + { + if (connected) + properties.Stat().lastActivityTime = stgTime; + + if (!connected && IsInetable()) + Connect(); + + if (connected && !IsInetable()) + { + if (disabled) + Disconnect(false, "disabled"); + else if (passive) + Disconnect(false, "passive"); + else + Disconnect(false, "no cash"); + } + + if (stgTime - lastScanMessages > 10) + { + ScanMessage(); + lastScanMessages = stgTime; + } + } +else + { + if (connected) + Disconnect(false, "not authorized"); + } + +} +//----------------------------------------------------------------------------- +void UserImpl::UpdatePingTime(time_t t) +{ +STG_LOCKER lock(&mutex); +if (t) + pingTime = t; +else + pingTime = stgTime; +} +//----------------------------------------------------------------------------- +bool UserImpl::IsInetable() +{ +if (disabled || passive) + return false; + +if (settings->GetFreeMbAllowInet()) + { + if (freeMb >= 0) + return true; + } + +if (settings->GetShowFeeInCash() || tariff == NULL) + return (cash >= -credit); + +return (cash - tariff->GetFee() >= -credit); +} +//----------------------------------------------------------------------------- +std::string UserImpl::GetEnabledDirs() const +{ +return dirsToString(enabledDirs); +} +//----------------------------------------------------------------------------- +#ifdef TRAFF_STAT_WITH_PORTS +void UserImpl::AddTraffStatU(int dir, uint32_t ip, uint16_t port, uint32_t len) +#else +void UserImpl::AddTraffStatU(int dir, uint32_t ip, uint32_t len) +#endif +{ +STG_LOCKER lock(&mutex); + +if (!connected || tariff == NULL) + return; + +double cost = 0; +DirTraff dt(up); + +int64_t traff = tariff->GetTraffByType(up.ConstData()[dir], down.ConstData()[dir]); +int64_t threshold = tariff->GetThreshold(dir) * 1024 * 1024; + +dt[dir] += len; + +int tt = tariff->GetTraffType(); +if (tt == Tariff::TRAFF_UP || + tt == Tariff::TRAFF_UP_DOWN || + // Check NEW traff data + (tt == Tariff::TRAFF_MAX && dt[dir] > down.ConstData()[dir])) + { + double dc = 0; + if (traff < threshold && + traff + len >= threshold) + { + // cash = partBeforeThreshold * priceBeforeThreshold + + // partAfterThreshold * priceAfterThreshold + int64_t before = threshold - traff; // Chunk part before threshold + int64_t after = len - before; // Chunk part after threshold + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], // Traff before chunk + down.ConstData()[dir], + dir, + stgTime) * before + + tariff->GetPriceWithTraffType(dt[dir], // Traff after chunk + down.ConstData()[dir], + dir, + stgTime) * after; + } + else + { + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], + dir, + stgTime) * len; + } + + if (freeMb.ConstData() <= 0) // FreeMb is exhausted + cost = dc; + else if (freeMb.ConstData() < dc) // FreeMb is partially exhausted + cost = dc - freeMb.ConstData(); + + // Direct access to internal data structures via friend-specifier + properties.Stat().freeMb -= dc; + properties.Stat().cash -= cost; + cash.ModifyTime(); + freeMb.ModifyTime(); + } + +up = dt; +sessionUpload[dir] += len; +sessionUploadModTime = stgTime; + +//Add detailed stat + +if (!settings->GetWriteFreeMbTraffCost() && + freeMb.ConstData() >= 0) + cost = 0; + +#ifdef TRAFF_STAT_WITH_PORTS +IPDirPair idp(ip, dir, port); +#else +IPDirPair idp(ip, dir); +#endif + +std::map::iterator lb; +lb = traffStat.lower_bound(idp); +if (lb == traffStat.end() || lb->first != idp) + { + traffStat.insert(lb, + std::make_pair(idp, + StatNode(len, 0, cost))); + } +else + { + lb->second.cash += cost; + lb->second.up += len; + } +} +//----------------------------------------------------------------------------- +#ifdef TRAFF_STAT_WITH_PORTS +void UserImpl::AddTraffStatD(int dir, uint32_t ip, uint16_t port, uint32_t len) +#else +void UserImpl::AddTraffStatD(int dir, uint32_t ip, uint32_t len) +#endif +{ +STG_LOCKER lock(&mutex); + +if (!connected || tariff == NULL) + return; + +double cost = 0; +DirTraff dt(down); + +int64_t traff = tariff->GetTraffByType(up.ConstData()[dir], down.ConstData()[dir]); +int64_t threshold = tariff->GetThreshold(dir) * 1024 * 1024; + +dt[dir] += len; + +int tt = tariff->GetTraffType(); +if (tt == Tariff::TRAFF_DOWN || + tt == Tariff::TRAFF_UP_DOWN || + // Check NEW traff data + (tt == Tariff::TRAFF_MAX && up.ConstData()[dir] <= dt[dir])) + { + double dc = 0; + if (traff < threshold && + traff + len >= threshold) + { + // cash = partBeforeThreshold * priceBeforeThreshold + + // partAfterThreshold * priceAfterThreshold + int64_t before = threshold - traff; // Chunk part before threshold + int64_t after = len - before; // Chunk part after threshold + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], // Traff before chunk + dir, + stgTime) * before + + tariff->GetPriceWithTraffType(up.ConstData()[dir], + dt[dir], // Traff after chunk + dir, + stgTime) * after; + } + else + { + dc = tariff->GetPriceWithTraffType(up.ConstData()[dir], + down.ConstData()[dir], + dir, + stgTime) * len; + } + + if (freeMb.ConstData() <= 0) // FreeMb is exhausted + cost = dc; + else if (freeMb.ConstData() < dc) // FreeMb is partially exhausted + cost = dc - freeMb.ConstData(); + + properties.Stat().freeMb -= dc; + properties.Stat().cash -= cost; + cash.ModifyTime(); + freeMb.ModifyTime(); + } + +down = dt; +sessionDownload[dir] += len; +sessionDownloadModTime = stgTime; + +//Add detailed stat + +if (!settings->GetWriteFreeMbTraffCost() && + freeMb.ConstData() >= 0) + cost = 0; + +#ifdef TRAFF_STAT_WITH_PORTS +IPDirPair idp(ip, dir, port); +#else +IPDirPair idp(ip, dir); +#endif + +std::map::iterator lb; +lb = traffStat.lower_bound(idp); +if (lb == traffStat.end() || lb->first != idp) + { + traffStat.insert(lb, + std::make_pair(idp, + StatNode(0, len, cost))); + } +else + { + lb->second.cash += cost; + lb->second.down += len; + } +} +//----------------------------------------------------------------------------- +void UserImpl::AddCurrIPBeforeNotifier(CURR_IP_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +currIP.AddBeforeNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::DelCurrIPBeforeNotifier(const CURR_IP_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +currIP.DelBeforeNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::AddCurrIPAfterNotifier(CURR_IP_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +currIP.AddAfterNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::DelCurrIPAfterNotifier(const CURR_IP_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +currIP.DelAfterNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::AddConnectedBeforeNotifier(CONNECTED_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +connected.AddBeforeNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::DelConnectedBeforeNotifier(const CONNECTED_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +connected.DelBeforeNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::AddConnectedAfterNotifier(CONNECTED_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +connected.AddAfterNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::DelConnectedAfterNotifier(const CONNECTED_NOTIFIER * notifier) +{ +STG_LOCKER lock(&mutex); +connected.DelAfterNotifier(notifier); +} +//----------------------------------------------------------------------------- +void UserImpl::OnAdd() +{ +STG_LOCKER lock(&mutex); + +std::string scriptOnAdd = settings->GetScriptsDir() + "/OnUserAdd"; + +if (access(scriptOnAdd.c_str(), X_OK) == 0) + { + std::string scriptOnAddParams = scriptOnAdd + " \"" + login + "\""; + + ScriptExec(scriptOnAddParams.c_str()); + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnAdd.c_str()); + } +} +//----------------------------------------------------------------------------- +void UserImpl::OnDelete() +{ +STG_LOCKER lock(&mutex); + +std::string scriptOnDel = settings->GetScriptsDir() + "/OnUserDel"; + +if (access(scriptOnDel.c_str(), X_OK) == 0) + { + std::string scriptOnDelParams = scriptOnDel + " \"" + login + "\""; + + ScriptExec(scriptOnDelParams.c_str()); + } +else + { + WriteServLog("Script %s cannot be executed. File not found.", scriptOnDel.c_str()); + } + +Run(); +} +//----------------------------------------------------------------------------- +int UserImpl::WriteDetailStat(bool hard) +{ +printfd(__FILE__, "UserImpl::WriteDetailedStat() - saved size = %d\n", traffStatSaved.second.size()); + +if (!traffStatSaved.second.empty()) + { + if (store->WriteDetailedStat(traffStatSaved.second, traffStatSaved.first, login)) + { + printfd(__FILE__, "UserImpl::WriteDetailStat() - failed to write detail stat from queue\n"); + WriteServLog("Cannot write detail stat from queue (of size %d recs) for user %s.", traffStatSaved.second.size(), login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + return -1; + } + traffStatSaved.second.erase(traffStatSaved.second.begin(), traffStatSaved.second.end()); + } + +TraffStat ts; + + { + STG_LOCKER lock(&mutex); + ts.swap(traffStat); + } + +printfd(__FILE__, "UserImpl::WriteDetailedStat() - size = %d\n", ts.size()); + +if (ts.size() && !disabledDetailStat) + { + if (store->WriteDetailedStat(ts, lastWriteDetailedStat, login)) + { + printfd(__FILE__, "UserImpl::WriteDetailStat() - failed to write current detail stat\n"); + WriteServLog("Cannot write detail stat for user %s.", login.c_str()); + WriteServLog("%s", store->GetStrError().c_str()); + if (!hard) + { + printfd(__FILE__, "UserImpl::WriteDetailStat() - pushing detail stat to queue\n"); + STG_LOCKER lock(&mutex); + traffStatSaved.second.swap(ts); + traffStatSaved.first = lastWriteDetailedStat; + } + return -1; + } + } +lastWriteDetailedStat = stgTime; +return 0; +} +//----------------------------------------------------------------------------- +double UserImpl::GetPassiveTimePart() const +{ +STG_LOCKER lock(&mutex); + +static int daysInMonth[12] = +{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +struct tm tms; +time_t t = stgTime; +localtime_r(&t, &tms); + +time_t secMonth = daysInMonth[(tms.tm_mon + 11) % 12] * 24 * 3600; // Previous month + +if (tms.tm_year % 4 == 0 && tms.tm_mon == 1) + { + // Leap year + secMonth += 24 * 3600; + } + +time_t dt = secMonth - passiveTime; + +if (dt < 0) + dt = 0; + +return static_cast(dt) / secMonth; +} +//----------------------------------------------------------------------------- +void UserImpl::SetPassiveTimeAsNewUser() +{ +STG_LOCKER lock(&mutex); + +time_t t = stgTime; +struct tm tm; +localtime_r(&t, &tm); +int daysCurrMon = DaysInCurrentMonth(); +double pt = tm.tm_mday - 1; +pt /= daysCurrMon; + +passiveTime = static_cast(pt * 24 * 3600 * daysCurrMon); +} +//----------------------------------------------------------------------------- +void UserImpl::MidnightResetSessionStat() +{ +STG_LOCKER lock(&mutex); + +if (connected) + { + Disconnect(true, "fake"); + Connect(true); + } +} +//----------------------------------------------------------------------------- +void UserImpl::ProcessNewMonth() +{ +STG_LOCKER lock(&mutex); +// Reset traff +if (connected) + Disconnect(true, "fake"); + +WriteMonthStat(); + +properties.Stat().monthUp.reset(); +properties.Stat().monthDown.reset(); + +if (connected) + Connect(true); + +// Set new tariff +if (nextTariff.ConstData() != "") + { + const Tariff * nt = tariffs->FindByName(nextTariff); + if (nt == NULL) + { + WriteServLog("Cannot change tariff for user %s. Tariff %s not exist.", + login.c_str(), properties.tariffName.Get().c_str()); + } + else + { + std::string message = tariff->TariffChangeIsAllowed(*nt, stgTime); + if (message.empty()) + { + properties.tariffName.Set(nextTariff, *sysAdmin, login, *store); + } + else + { + WriteServLog("Tariff change is prohibited for user %s. %s", + login.c_str(), + message.c_str()); + } + } + ResetNextTariff(); + WriteConf(); + } +} +//----------------------------------------------------------------------------- +void UserImpl::ProcessDayFeeSpread() +{ +STG_LOCKER lock(&mutex); + +if (passive.ConstData() || tariff == NULL) + return; + +if (tariff->GetPeriod() != Tariff::MONTH) + return; + +double fee = tariff->GetFee() / DaysInCurrentMonth(); + +if (std::fabs(fee) < 1.0e-3) + return; + +double c = cash; +switch (settings->GetFeeChargeType()) + { + case 0: + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + case 1: + if (c + credit >= 0) + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + case 2: + if (c + credit >= fee) + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + case 3: + if (c >= 0) + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + } +ResetPassiveTime(); +} +//----------------------------------------------------------------------------- +void UserImpl::ProcessDayFee() +{ +STG_LOCKER lock(&mutex); + +if (tariff == NULL) + return; + +if (tariff->GetPeriod() != Tariff::MONTH) + return; + +double passiveTimePart = 1.0; +if (!settings->GetFullFee()) + { + passiveTimePart = GetPassiveTimePart(); + } +else + { + if (passive.ConstData()) + { + printfd(__FILE__, "Don't charge fee `cause we are passive\n"); + return; + } + } +double fee = tariff->GetFee() * passiveTimePart; + +ResetPassiveTime(); + +if (std::fabs(fee) < 1.0e-3) + { + SetPrepaidTraff(); + return; + } + +double c = cash; +printfd(__FILE__, "login: %8s Cash=%f Credit=%f Fee=%f PassiveTimePart=%f fee=%f\n", + login.c_str(), + cash.ConstData(), + credit.ConstData(), + tariff->GetFee(), + passiveTimePart, + fee); +switch (settings->GetFeeChargeType()) + { + case 0: + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + break; + case 1: + if (c + credit >= 0) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + case 2: + if (c + credit >= fee) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + case 3: + if (c >= 0) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + } +} +//----------------------------------------------------------------------------- +void UserImpl::ProcessDailyFee() +{ +STG_LOCKER lock(&mutex); + +if (passive.ConstData() || tariff == NULL) + return; + +if (tariff->GetPeriod() != Tariff::DAY) + return; + +double fee = tariff->GetFee(); + +if (fee == 0.0) + return; + +double c = cash; +switch (settings->GetFeeChargeType()) + { + case 0: + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + case 1: + if (c + credit >= 0) + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + case 2: + if (c + credit >= fee) + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + break; + } +ResetPassiveTime(); +} +//----------------------------------------------------------------------------- +void UserImpl::ProcessServices() +{ +struct tm tms; +time_t t = stgTime; +localtime_r(&t, &tms); + +double passiveTimePart = 1.0; +if (!settings->GetFullFee()) + { + passiveTimePart = GetPassiveTimePart(); + } +else + { + if (passive.ConstData()) + { + printfd(__FILE__, "Don't charge fee `cause we are passive\n"); + return; + } + } + +for (size_t i = 0; i < properties.Conf().services.size(); ++i) + { + ServiceConf conf; + if (m_services.Find(properties.Conf().services[i], &conf)) + continue; + if (conf.payDay == tms.tm_mday || + (conf.payDay == 0 && tms.tm_mday == DaysInCurrentMonth())) + { + double c = cash; + double fee = conf.cost * passiveTimePart; + printfd(__FILE__, "Service fee. login: %8s Cash=%f Credit=%f Fee=%f PassiveTimePart=%f fee=%f\n", + login.c_str(), + cash.ConstData(), + credit.ConstData(), + tariff->GetFee(), + passiveTimePart, + fee); + switch (settings->GetFeeChargeType()) + { + case 0: + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + break; + case 1: + if (c + credit >= 0) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + case 2: + if (c + credit >= fee) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + case 3: + if (c >= 0) + { + properties.cash.Set(c - fee, *sysAdmin, login, *store, "Subscriber fee charge"); + SetPrepaidTraff(); + } + break; + } + } + } +} +//----------------------------------------------------------------------------- +void UserImpl::SetPrepaidTraff() +{ +if (tariff != NULL) + properties.freeMb.Set(tariff->GetFree(), *sysAdmin, login, *store, "Prepaid traffic"); +} +//----------------------------------------------------------------------------- +int UserImpl::AddMessage(Message * msg) +{ +STG_LOCKER lock(&mutex); + +if (SendMessage(*msg)) + { + if (store->AddMessage(msg, login)) + { + errorStr = store->GetStrError(); + WriteServLog("Error adding message: '%s'", errorStr.c_str()); + printfd(__FILE__, "Error adding message: '%s'\n", errorStr.c_str()); + return -1; + } + messages.push_back(*msg); + } +else + { + if (msg->header.repeat > 0) + { + msg->header.repeat--; + #ifndef DEBUG + //TODO: gcc v. 4.x generate ICE on x86_64 + msg->header.lastSendTime = static_cast(time(NULL)); + #else + msg->header.lastSendTime = static_cast(stgTime); + #endif + if (store->AddMessage(msg, login)) + { + errorStr = store->GetStrError(); + WriteServLog("Error adding repeatable message: '%s'", errorStr.c_str()); + printfd(__FILE__, "Error adding repeatable message: '%s'\n", errorStr.c_str()); + return -1; + } + messages.push_back(*msg); + } + } +return 0; +} +//----------------------------------------------------------------------------- +int UserImpl::SendMessage(Message & msg) const +{ +// No lock `cause we are already locked from caller +int ret = -1; +std::set::iterator it(authorizedBy.begin()); +while (it != authorizedBy.end()) + { + if (!(*it++)->SendMessage(msg, currIP)) + ret = 0; + } +if (!ret) + { +#ifndef DEBUG + //TODO: gcc v. 4.x generate ICE on x86_64 + msg.header.lastSendTime = static_cast(time(NULL)); +#else + msg.header.lastSendTime = static_cast(stgTime); +#endif + msg.header.repeat--; + } +return ret; +} +//----------------------------------------------------------------------------- +void UserImpl::ScanMessage() +{ +// No lock `cause we are already locked from caller +// We need not check for the authorizedBy `cause it has already checked by caller + +auto it = messages.begin(); +while (it != messages.end()) + { + if (settings->GetMessageTimeout() > 0 && + difftime(stgTime, it->header.creationTime) > settings->GetMessageTimeout()) + { + // Timeout exceeded + if (store->DelMessage(it->header.id, login)) + { + WriteServLog("Error deleting message: '%s'", store->GetStrError().c_str()); + printfd(__FILE__, "Error deleting message: '%s'\n", store->GetStrError().c_str()); + } + messages.erase(it++); + continue; + } + if (it->GetNextSendTime() <= stgTime) + { + if (SendMessage(*it)) + { + // We need to check all messages in queue for timeout + ++it; + continue; + } + if (it->header.repeat < 0) + { + if (store->DelMessage(it->header.id, login)) + { + WriteServLog("Error deleting message: '%s'", store->GetStrError().c_str()); + printfd(__FILE__, "Error deleting message: '%s'\n", store->GetStrError().c_str()); + } + messages.erase(it++); + } + else + { + if (store->EditMessage(*it, login)) + { + WriteServLog("Error modifying message: '%s'", store->GetStrError().c_str()); + printfd(__FILE__, "Error modifying message: '%s'\n", store->GetStrError().c_str()); + } + ++it; + } + } + else + { + ++it; + } + } +} +//----------------------------------------------------------------------------- +std::string UserImpl::GetParamValue(const std::string & name) const +{ + std::string lowerName = ToLower(name); + if (lowerName == "id") + { + std::ostringstream stream; + stream << id; + return stream.str(); + } + if (lowerName == "login") return login; + if (lowerName == "currip") return currIP.ToString(); + if (lowerName == "enableddirs") return GetEnabledDirs(); + if (lowerName == "tariff") return properties.tariffName; + if (properties.Exists(lowerName)) + return properties.GetPropertyValue(lowerName); + else + { + WriteServLog("User’s parameter '%s' does not exist.", name.c_str()); + return ""; + } +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void STG::CHG_PASSIVE_NOTIFIER::Notify(const int & oldPassive, const int & newPassive) +{ +if (newPassive && !oldPassive && user->tariff != NULL) + user->properties.cash.Set(user->cash - user->tariff->GetPassiveCost(), + *user->sysAdmin, + user->login, + *user->store, + "Freeze"); +} +//----------------------------------------------------------------------------- +void STG::CHG_DISABLED_NOTIFIER::Notify(const int & oldValue, const int & newValue) +{ +if (oldValue && !newValue && user->GetConnected()) + user->Disconnect(false, "disabled"); +else if (!oldValue && newValue && user->IsInetable()) + user->Connect(false); +} +//----------------------------------------------------------------------------- +void STG::CHG_TARIFF_NOTIFIER::Notify(const std::string &, const std::string & newTariff) +{ +STG_LOCKER lock(&user->mutex); +if (user->settings->GetReconnectOnTariffChange() && user->connected) + user->Disconnect(false, "Change tariff"); +user->tariff = user->tariffs->FindByName(newTariff); +if (user->settings->GetReconnectOnTariffChange() && + !user->authorizedBy.empty() && + user->IsInetable()) + { + // This notifier gets called *before* changing the tariff, and in Connect we want to see new tariff name. + user->properties.Conf().tariffName = newTariff; + user->Connect(false); + } +} +//----------------------------------------------------------------------------- +void STG::CHG_CASH_NOTIFIER::Notify(const double & oldCash, const double & newCash) +{ +user->lastCashAddTime = *const_cast(&stgTime); +user->lastCashAdd = newCash - oldCash; +} +//----------------------------------------------------------------------------- +void STG::CHG_IPS_NOTIFIER::Notify(const UserIPs & from, const UserIPs & to) +{ +printfd(__FILE__, "Change IP from '%s' to '%s'\n", from.toString().c_str(), to.toString().c_str()); +if (user->connected) + user->Disconnect(false, "Change IP"); +if (!user->authorizedBy.empty() && user->IsInetable()) + user->Connect(false); +}